王贵旺 4 éve
commit
167eb1aa57

+ 5 - 0
.editorconfig

@@ -0,0 +1,5 @@
+[*.{js,jsx,ts,tsx,vue}]
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+insert_final_newline = true

+ 4 - 0
.env

@@ -0,0 +1,4 @@
+VUE_APP_BASE_URL=/api
+VUE_APP_FILE_URL=https://dwx.auyen.com/
+VUE_APP_BASE_URL_T=/t
+VUE_APP_WEBSS_URL=wss://dwx.auyen.com/

+ 4 - 0
.env.dev

@@ -0,0 +1,4 @@
+NODE_ENV=dev
+VUE_APP_BASE_URL=https://dapi.auyen.com/
+VUE_APP_FILE_URL=https://dwx.auyen.com/
+VUE_APP_WEBSS_URL=wss://dwx.auyen.com/

+ 4 - 0
.env.prod

@@ -0,0 +1,4 @@
+NODE_ENV=prod
+VUE_APP_BASE_URL=https://api.auyen.com/
+VUE_APP_FILE_URL=https://wx.auyen.com/
+VUE_APP_WEBSS_URL=wss://wx.auyen.com/

+ 4 - 0
.env.test

@@ -0,0 +1,4 @@
+.NODE_ENV=dev-test
+VUE_APP_BASE_URL=https://tapi.auyen.com/
+VUE_APP_FILE_URL=https://twx.auyen.com/
+VUE_APP_WEBSS_URL=wss://twx.auyen.com/

+ 9 - 0
.eslintignore

@@ -0,0 +1,9 @@
+node_modules/**
+# 打包后的文件
+/dist
+# table列表
+/src/store/columns/**/*.js
+
+.eslintrc.js
+package-lock.json
+

+ 21 - 0
.eslintrc.js

@@ -0,0 +1,21 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true
+  },
+  extends: [
+    'plugin:vue/essential',
+    '@vue/standard'
+  ],
+  parserOptions: {
+    parser: 'babel-eslint'
+  },
+  rules: {
+    'eqeqeq': 0,
+    'no-prototype-builtins': 0,
+    'space-before-function-paren': 0,
+    'standard/no-callback-literal': 0,
+    'no-multiple-empty-lines': [0, { max: 2 }],
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
+  }
+}

+ 22 - 0
.gitignore

@@ -0,0 +1,22 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 24 - 0
README.md

@@ -0,0 +1,24 @@
+# boss mobile
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 13 - 0
babel.config.js

@@ -0,0 +1,13 @@
+module.exports = {
+  presets: ['@vue/cli-plugin-babel/preset'],
+  plugins: [
+    [
+      'import',
+      { libraryName: 'vant', libraryDirectory: 'es', style: true },
+      'vant'
+    ],
+    [
+      '@babel/plugin-syntax-dynamic-import'
+    ]
+  ]
+}

+ 57 - 0
package.json

@@ -0,0 +1,57 @@
+{
+  "name": "wxmobile",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "http": "http-server ./dist",
+    "serve": "vue-cli-service serve",
+    "test": "vue-cli-service serve --mode test",
+    "build-dev": "vue-cli-service build --mode dev",
+    "build-test": "vue-cli-service build --mode test",
+    "build-prod": "vue-cli-service build --mode prod",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "axios": "^0.19.2",
+    "core-js": "^3.6.5",
+    "exif-js": "^2.3.0",
+    "moment": "^2.26.0",
+    "sockjs-client": "^1.4.0",
+    "stompjs": "^2.3.3",
+    "vant": "^2.12.24",
+    "vue": "^2.6.11",
+    "vue-router": "^3.2.0",
+    "vuex": "^3.4.0",
+    "weixin-js-sdk": "^1.6.0"
+  },
+  "devDependencies": {
+    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+    "@vue/cli-plugin-babel": "~4.4.0",
+    "@vue/cli-plugin-eslint": "~4.4.0",
+    "@vue/cli-plugin-router": "~4.4.0",
+    "@vue/cli-plugin-vuex": "~4.4.0",
+    "@vue/cli-service": "~4.4.0",
+    "@vue/eslint-config-standard": "^5.1.2",
+    "babel-eslint": "^10.1.0",
+    "babel-plugin-import": "^1.13.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-import": "^2.20.2",
+    "eslint-plugin-node": "^11.1.0",
+    "eslint-plugin-promise": "^4.2.1",
+    "eslint-plugin-standard": "^4.0.0",
+    "eslint-plugin-vue": "^6.2.2",
+    "http-server": "^0.12.3",
+    "node-sass": "^4.12.0",
+    "postcss-px-to-viewport": "^1.1.1",
+    "sass-loader": "^8.0.2",
+    "timeline-vuejs": "^1.1.7",
+    "vue-baidu-map": "^0.21.22",
+    "vue-bmap-gl": "^0.0.17",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

+ 22 - 0
postcss.config.js

@@ -0,0 +1,22 @@
+module.exports = {
+  plugins: {
+    autoprefixer: {},
+    'postcss-px-to-viewport': {
+      unitToConvert: 'px', // 默认值`px`,需要转换的单位
+      viewportWidth: 375, // 视窗的宽度,对应设计稿宽度
+      viewportHeight: 667, // 视窗的高度, 根据375设备的宽度来指定,一般是667,也可不配置
+      unitPrecision: 5, // 指定`px`转换为视窗单位值的小数位数
+      propList: ['*'], // 转化为vw的属性列表
+      viewportUnit: 'vw', // 指定需要转换成视窗单位
+      fontViewportUnit: 'vw', // 字体使用的视窗单位
+      selectorBlaskList: ['.ignore-'], // 指定不需要转换为视窗单位的类
+      mediaQuery: false, // 允许在媒体查询中转换`px`
+      minPixelValue: 1, // 小于或等于`1px`时不转换为视窗单位
+      replace: true, // 是否直接更换属性值而不添加备用属性
+      exclude: /(\/|\\)(node_modules)(\/|\\)/, // 忽略某些文件夹下的文件或特定文件
+      landscape: false, // 是否添加根据landscapeWidth生成的媒体查询条件 @media (orientation: landscape)
+      landscapeUnit: 'vw', // 横屏时使用的单位
+      landscapeWidth: 1134 // 横屏时使用的视窗宽度
+    }
+  }
+}

BIN
public/favicon.ico


+ 42 - 0
public/index.html

@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+    <style>
+      .image-loading{
+        width:100%;
+        height:100vh;
+        top: 0;
+        left: 0;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        position: absolute;
+        background-color: rgba(24, 24, 24, .08);
+      }
+
+    </style>
+  </head>
+  <body>
+    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
+      <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
+    <% } %>
+    <!--<div class="image-loading"><img src="<%= BASE_URL %>loading.svg" style="width: 100px;height: 100px;" /></div>-->
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+    <!--//下列是css ,script的话注释换一下,仔细看很好理解,config配置是添加一个cdn变量,然后在模板中遍历添加-->
+    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
+      <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>" crossorigin="anonymous"></script>
+    <% } %>
+  </body>
+</html>

BIN
public/loading.png


+ 59 - 0
src/App.vue

@@ -0,0 +1,59 @@
+<template>
+  <div id="app">
+    <!--<transition :name="transitionName">
+      <router-view class="Router" />
+    </transition>-->
+    <router-view />
+  </div>
+</template>
+<script>
+export default {
+  data() {
+    return {
+      transitionName: 'slide-right' // 初始过渡动画方向
+    }
+  },
+  watch: {
+    /* $route(to, from) {
+      // 切换动画
+      var isBack = this.$router.isBack // 监听路由变化时的状态为前进还是后退
+      if (isBack) {
+        this.transitionName = 'slide-left'
+      } else {
+        this.transitionName = 'slide-right'
+      }
+      this.$router.isBack = false
+    } */
+  }
+}
+</script>
+
+<style lang="scss">
+  #app {
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    width: 100%;
+    height: 100%;
+
+    .Router {
+      height: 100%;
+      transition: all .377s ease;
+      will-change: transform;
+      top: 0;
+      backface-visibility: hidden;
+      perspective: 1000;
+    }
+    .slide-left-enter,
+    .slide-right-leave-active {
+      opacity: 0;
+      transform: translate3d(-100%, 0, 0);
+    }
+
+    .slide-left-leave-active,
+    .slide-right-enter {
+      opacity: 0;
+      transform: translate3d(100%, 0 ,0);
+    }
+  }
+
+</style>

+ 119 - 0
src/assets/reset.css

@@ -0,0 +1,119 @@
+/**
+ * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
+ * http://cssreset.com
+ */
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video, input {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    font-size: 100%;
+    font-weight: normal;
+    vertical-align: baseline;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, menu, nav, section {
+    display: block;
+}
+
+body {
+    line-height: 1;
+}
+
+blockquote, q {
+    quotes: none;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+    content: none;
+}
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}
+
+/* custom */
+a {
+    color: #7e8c8d;
+    text-decoration: none;
+    -webkit-backface-visibility: hidden;
+}
+
+li {
+    list-style: none;
+}
+
+::-webkit-scrollbar {
+    width: 5px;
+    height: 5px;
+}
+
+::-webkit-scrollbar-track-piece {
+    background-color: rgba(0, 0, 0, 0.2);
+    -webkit-border-radius: 6px;
+}
+
+::-webkit-scrollbar-thumb:vertical {
+    height: 5px;
+    background-color: rgba(125, 125, 125, 0.7);
+    -webkit-border-radius: 6px;
+}
+
+::-webkit-scrollbar-thumb:horizontal {
+    width: 5px;
+    background-color: rgba(125, 125, 125, 0.7);
+    -webkit-border-radius: 6px;
+}
+
+html, body {
+    width: 100%;
+    height: 100%;
+    background: #f4f7fc;
+}
+
+body {
+    -webkit-text-size-adjust: none;
+    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+#app {
+    text-align: center;
+    color: #2c3e50;
+    width: 100%;
+    height: 100%;
+    font-family: Avenir,Helvetica,Arial,sans-serif;
+}
+
+.van-list__finished-text {
+    margin-top: 5px;
+}
+
+.van-field--error .van-field__control, .van-field--error .van-field__control::placeholder {
+    color: #ccc!important;
+}
+
+.van-dropdown-menu__bar {
+    box-shadow: none!important;
+    height: 40px!important;
+}
+.van-search {
+    background-color: transparent!important;
+}
+.van-search__content {
+    background-color: #fff!important;
+}

+ 127 - 0
src/components/MapPoint.vue

@@ -0,0 +1,127 @@
+<template>
+  <div>
+    <baidu-map v-bind:style="mapStyle" class="bm-view" :ak="akey"
+               :center="center"
+               :zoom="zoom"
+               :scroll-wheel-zoom="true"
+               @click="getClickInfo"
+               @moving="syncCenterAndZoom"
+               @moveend="syncCenterAndZoom"
+               @zoomend="syncCenterAndZoom">
+      <bm-view style="width: 100%; height:100vh;"></bm-view>
+      <bm-marker :position="{lng: center.lng, lat: center.lat}" :dragging="true" animation="BMAP_ANIMATION_BOUNCE"></bm-marker>
+      <bm-control :offset="{width: '10px', height: '10px'}">
+        <bm-auto-complete :sugStyle="{zIndex: 999999}">
+          <input type="text" v-model="address" placeholder="请输入搜索关键字" class="serachinput">
+        </bm-auto-complete>
+      </bm-control>
+      <bm-local-search :keyword="address" :auto-viewport="true" style="width:0px;height:0px;overflow: hidden;"></bm-local-search>
+    </baidu-map>
+    <div slot="footer" class="footer">
+      <van-button plain type="info" @click="cancel" class="nt-info nt-btn-bottom">取消</van-button>
+      <van-button type="info" class="nt-primary nt-btn-bottom" @click="confirm">确定位置</van-button>
+    </div>
+  </div>
+</template>
+<script>
+import { BaiduMap, BmControl, BmView, BmAutoComplete, BmLocalSearch, BmMarker } from 'vue-baidu-map'
+export default {
+  name: 'MapPoint',
+  components: {
+    BaiduMap,
+    BmControl,
+    BmView,
+    BmAutoComplete,
+    BmLocalSearch,
+    BmMarker
+  },
+  data: function () {
+    return {
+      address: this.addressKey,
+      showMapComponent: this.value,
+      mapStyle: {
+        width: '100%',
+        height: this.mapHeight + 'px'
+      },
+      center: { lng: 116.404, lat: 39.915 },
+      zoom: 15
+    }
+  },
+  watch: {},
+  props: {
+    addressKey: {
+      type: String,
+      default: ''
+    },
+    value: {
+      type: Boolean
+    },
+    mapHeight: {
+      type: Number,
+      default: 280
+    },
+    akey: {
+      type: String,
+      default: 'dfhycORtYDMz78dNLo9oNiDO1ufI2TZS'
+    }
+  },
+  methods: {
+    /***
+     * 地图点击事件。
+     */
+    getClickInfo (e) {
+      this.center.lng = e.point.lng
+      this.center.lat = e.point.lat
+    },
+    syncCenterAndZoom (e) {
+      const { lng, lat } = e.target.getCenter()
+      this.center.lng = lng
+      this.center.lat = lat
+      this.zoom = e.target.getZoom()
+    },
+    /** *确认 */
+    confirm: function () {
+      this.showMapComponent = false
+      this.$emit('confirm', this.center)
+    },
+    /** *取消 */
+    cancel: function () {
+      this.showMapComponent = false
+      this.$emit('cancel', this.showMapComponent)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.serachinput{
+  width: 375px;
+  box-sizing: border-box;
+  padding: 9px;
+  border: 1px solid #dddee1;
+  line-height: 12px;
+  font-size: 12px;
+  height: 40px;
+  color: #333;
+  outline-width: 0;
+  outline-color: rgba(0, 0, 0, 0);
+  position: relative;
+  border-radius: 4px;
+  -webkit-box-shadow: #666 0px 0px 10px;
+  -moz-box-shadow: #666 0px 0px 10px;
+  box-shadow: #666 0px 0px 10px;
+}
+.footer {
+  position: absolute;
+  padding-top: 10px;
+  text-align: right;
+  bottom: 30px;
+  left: 50%;
+  margin-left: -160px;
+  .nt-btn-bottom {
+    width: 150px;
+    height: 35px;
+    margin-left: 10px;
+  }
+}
+</style>

+ 208 - 0
src/components/TimeLineList.vue

@@ -0,0 +1,208 @@
+<template>
+  <ul class="timeline-wrapper">
+    <li class="timeline-item" v-for="(item, index) of timelineList" :key="index">
+      <div class="timeline-box">
+        <div class="out-circle">
+          <div class="in-circle"></div>
+        </div>
+        <div class="long-line"></div>
+      </div>
+      <div class="timeline-content" v-if="item.children && item.children.length > 0">
+        <div v-for="(cxt, cxtIndex) of item.title" :key="index + '-t-' + cxtIndex" :class="cxt.class" :style="cxt.style">
+          <span v-if="cxt.ishtml" v-html="cxt.text"></span>
+          <span v-else="">{{ cxt.text }}</span>
+        </div>
+        <div v-for="(cxt, cxtIndex) of item.contents" :key="index + '-c-' + cxtIndex" :class="cxt.class" :style="cxt.style">
+          <span v-if="cxt.ishtml" v-html="cxt.text"></span>
+          <span v-else>{{ cxt.text }}</span>
+        </div>
+        <time-line-list class="children" :timelineList="item.children"></time-line-list>
+      </div>
+      <div class="timeline-content" v-else>
+        <div v-for="(cxt, cxtIndex) of item.title" :key="index + '-t-' + cxtIndex" :class="cxt.class" :style="cxt.style">
+          <span v-if="cxt.ishtml" v-html="cxt.text"></span>
+          <span v-else="">{{ cxt.text }}</span>
+        </div>
+        <div v-for="(cxt, cxtIndex) of item.contents" :key="index + '-c-' + cxtIndex" :class="cxt.class" :style="cxt.style">
+          <span v-if="cxt.ishtml" v-html="cxt.text"></span>
+          <span v-else>{{ cxt.text }}</span>
+        </div>
+        <div v-if="item.credentials" class="credentials" @click="swipeDataEvent(item)">{{item.credentials.name}}</div>
+      </div>
+    </li>
+    <van-popup v-model="swipeDataStatus" style="width: 350px;height: 245px;">
+      <van-swipe v-if="swipeDataStatus">
+        <van-swipe-item v-for="(image, index) in currentImage.list" :key="index">
+          <van-image :src="image" fit="fill" />
+        </van-swipe-item>
+      </van-swipe>
+    </van-popup>
+  </ul>
+</template>
+
+<script type="text/babel">
+import Vue from 'vue'
+import { TimeLineList } from '@/components'
+import { isHttpHeaderURL } from '@/utils/tools'
+
+export default Vue.component('TimeLines', {
+  name: 'TimeLine',
+  components: { TimeLineList },
+  props: {
+    timelineList: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      currentImage: {},
+      swipeDataStatus: false
+    }
+  },
+  methods: {
+    swipeDataEvent(item) {
+      if (item.credentials && item.credentials.list && item.credentials.list.length > 0) {
+        item.credentials.list.forEach(node => {
+          if (!isHttpHeaderURL(node)) {
+            node = process.env.VUE_APP_FILE_URL + node
+          }
+        })
+      }
+
+      this.currentImage = item.credentials
+      this.swipeDataStatus = true
+    }
+  }
+})
+</script>
+
+<style scoped lang="scss">
+ul.timeline-wrapper {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  &.children {
+    margin-left: -40px;
+    .timeline-box {
+      height: 0;
+
+      .out-circle {
+        width: 20px;
+        height: 20px;
+        background: #ffffff;
+        margin-left: 5px;
+
+        .in-circle {
+          width: 10px;
+          height: 10px;
+          background: #cccccc;
+        }
+      }
+    }
+  }
+}
+
+/* 时间线 */
+.timeline-item {
+  position: relative;
+
+  .timeline-box {
+    text-align: center;
+    position: absolute;
+    height: 100%;
+
+    .out-circle {
+      width: 28px;
+      height: 28px;
+      background: #ffffff;
+      // box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 1);
+      /*opacity: 0.1;*/
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+
+      .in-circle {
+        width: 14px;
+        height: 14px;
+        margin: 0 auto;
+        background: #2765E2;
+        border-radius: 50%;
+        // box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.1);
+      }
+    }
+
+    .long-line {
+      width: 1px;
+      height: 100%;
+      background: #cccccc;
+      // box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.1);
+      // opacity: 0.1;
+      margin-left: 13px;
+    }
+  }
+
+  .timeline-content {
+    box-sizing: border-box;
+    margin-left: 20px;
+    padding: 0 0 0 20px;
+    text-align: left;
+    margin-bottom: 30px;
+
+    .credentials {
+      padding: 10px 0 0;
+      color: #2765E2;
+      font-size: 14px;
+      font-weight: bold;
+    }
+    .title > span {
+      font-size: 18px;
+      color: #333333;
+      font-weight: bold;
+      padding-top: 2px;
+      line-height: 25px;
+    }
+    .subtitle > span {
+      display: flex;
+      padding: 2px 0 10px 0;
+      align-items: center;
+      font-size: 14px;
+      justify-content: space-between;
+    }
+    .bottom-20 {
+      padding-bottom: 20px;
+    }
+    .content-text {
+      padding: 8px 15px;
+      border-radius: 5px;
+      background-color: #f7f7f7;
+    }
+    .timeline-title {
+      font-size: 14px;
+      word-break: break-all;
+      margin-bottom: 16px;
+      color: #333;
+      font-weight: 500;
+      /*display: inline;*/
+    }
+
+    .timeline-date {
+      font-size: 16px;
+      color: #333;
+      font-weight: 500;
+      margin-bottom: 16px;
+    }
+    .timeline-desc {
+      font-size: 14px;
+      color: #999999;
+    }
+  }
+
+}
+
+.timeline-item:last-of-type .timeline-content {
+  // margin-bottom: 0;
+}
+</style>

+ 2 - 0
src/components/index.js

@@ -0,0 +1,2 @@
+export { default as TimeLineList } from './TimeLineList'
+export { default as MapPoint } from './MapPoint'

BIN
src/jetbrains-agent.jar


+ 37 - 0
src/main.js

@@ -0,0 +1,37 @@
+import Vue from 'vue'
+import App from './App.vue'
+import router from './router'
+import store from './store'
+import { longtap } from './utils/directive'
+import './assets/reset.css'
+import './utils/vant.js'
+import moment from 'moment'
+import 'moment/locale/zh-cn'
+import * as filters from '@/utils/filters' // 过滤器
+import VueBMap from 'vue-bmap-gl'
+
+Vue.use(VueBMap)
+VueBMap.initBMapApiLoader({
+  ak: 'dfhycORtYDMz78dNLo9oNiDO1ufI2TZS',
+  v: '1.0'
+})
+Vue.prototype.$moment = moment
+
+Vue.config.productionTip = false
+Vue.use(longtap)
+
+// 加载全局过滤器
+Object.keys(filters).forEach(key => {
+  Vue.filter(key, filters[key])
+})
+
+// 动画过度效果
+/* window.addEventListener('popstate', function (e) {
+  router.isBack = true
+}, false) */
+
+new Vue({
+  router,
+  store,
+  render: h => h(App)
+}).$mount('#app')

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/mock/map/anhui.json


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/mock/map/china.json


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/mock/map/hebei.json


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/mock/map/henan.json


+ 53 - 0
src/mock/map/index.js

@@ -0,0 +1,53 @@
+import { isTypeof, inArray } from '@/utils/tools'
+
+// 地图下载json数据地址:https://hxkj.vip/demo/echartsMap/
+const china = require('./china.json')
+
+const MODEL_MAP_JSON = ['china', 'hebei', 'henan', 'shandong', 'anhui', 'jiangsu']
+
+// 获取地图json数据
+export function mapJsonData(model) {
+  let map = {}
+
+  if (isTypeof(model) === 'string' && model === 'all') {
+    MODEL_MAP_JSON.forEach(item => {
+      const tmpMapJson = require('./' + item + '.json')
+
+      map = mapConcat(map, tmpMapJson)
+    })
+  } else if (isTypeof(model) === 'array') {
+    model.forEach(item => {
+      if (inArray(item, MODEL_MAP_JSON) !== -1) {
+        const tmpMapJson = require('./' + item + '.json')
+
+        map = mapConcat(map, tmpMapJson)
+      }
+    })
+  }
+
+  return map
+}
+
+// 地图数据合并
+function mapConcat(map, tmpMapJson) {
+  if (Object.keys(map).length > 0) {
+    // 校验地图map.features是否存在
+    // eslint-disable-next-line no-prototype-builtins
+    if (!map.hasOwnProperty('features') || !Array.isArray(map.features)) {
+      map.features = []
+    }
+    // eslint-disable-next-line no-prototype-builtins
+    if (tmpMapJson.hasOwnProperty('features') && Array.isArray(tmpMapJson.features)) {
+      map.features.push(...new Set(tmpMapJson.features))
+    }
+  } else {
+    map = Object.assign({}, tmpMapJson)
+  }
+
+  return map
+}
+
+export default {
+  china,
+  mapJsonData
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/mock/map/jiangsu.json


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/mock/map/shandong.json


+ 39 - 0
src/router/index.js

@@ -0,0 +1,39 @@
+import Vue from 'vue'
+import { getLocalStorage } from '@/utils/storage'
+import VueRouter from 'vue-router'
+Vue.use(VueRouter)
+
+const routes = [
+  { path: '/', name: 'main' },
+  {
+    path: '/login',
+    name: 'login',
+    meta: { title: '登录' },
+    component: () => import('@/views/main/login')
+  },
+  {
+    path: '/agreement',
+    name: 'agreement',
+    meta: { title: '服务协议' },
+    component: () => import(/* webpackChunkName:"name" */'@/views/main/agreement')
+  }
+]
+
+const router = new VueRouter({
+  routes: [...routes]
+})
+router.beforeEach((to, from, next) => {
+  const mwxtoken = getLocalStorage('mwxtoken')
+
+  if (to.meta.title) { // 每页增加title
+    document.title = to.meta.title
+  }
+  if (mwxtoken) {
+    if (to.name === 'main') return next('/boss')
+    return next()
+  }
+  if (to.name === 'login' || to.name === 'agreement') return next()
+  return next('/login')
+})
+
+export default router

+ 28 - 0
src/service/message.js

@@ -0,0 +1,28 @@
+import R from '@/utils/request'
+
+import store from '@/store'
+import Axios from 'axios'
+
+const axios = Axios.create({
+  timeout: 30000,
+  baseURL: process.env.VUE_APP_BASE_URL
+})
+
+export async function $verifyCode (data) {
+  return await R({ url: 'message/verify_code/send', data })
+}
+
+export async function $uploadFile (data, type) {
+  const fd = new FormData()
+  const headers = {
+    'Content-Type': 'multipart/form-data',
+    Authorization: 'Bearer ' + store.getters.mwxtoken,
+    Identifier: store.getters.mwxidntf
+  }
+  if (type === 'rotate') {
+    fd.append('file', data)
+  } else {
+    fd.append('file', data.file)
+  }
+  return axios.post('message/upload/file', fd, { headers: headers })
+}

+ 21 - 0
src/store/index.js

@@ -0,0 +1,21 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import app from './modules/app'
+import user from './modules/user'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  modules: {
+    app,
+    user
+  },
+  getters: {
+    debug: state => state.app.debug,
+    mwxtoken: state => state.user.mwxtoken,
+    mwxidntf: state => state.user.mwxidntf,
+    mwxuser: state => state.user.mwxuser,
+    mwxrole: state => state.user.mwxrole,
+    mwxorg: state => state.user.mwxorg
+  }
+})

+ 9 - 0
src/store/modules/app.js

@@ -0,0 +1,9 @@
+const app = {
+  state: {
+    debug: false
+  },
+  mutations: {},
+  actions: {}
+}
+
+export default app

+ 107 - 0
src/store/modules/user.js

@@ -0,0 +1,107 @@
+import { $login, $logout } from '@/service/main'
+import { setLocalStorage, getLocalStorage, removeLocalStorage } from '@/utils/storage'
+
+const Base64 = require('js-base64').Base64
+
+const user = {
+  state: {
+    mwxtoken: getLocalStorage('mwxtoken') || '',
+    mwxidntf: getLocalStorage('mwxidntf') || '',
+    mwxuser: JSON.parse(getLocalStorage('mwxuser')) || {},
+    mwxrole: getLocalStorage('mwxrole') || '',
+    mwxorg: getLocalStorage('mwxorg') || ''
+  },
+
+  mutations: {
+    setmwxtoken: (state, payload) => {
+      state.mwxtoken = payload
+    },
+    setmwxidntf: (state, payload) => {
+      state.mwxidntf = payload
+    },
+    setmwxuser: (state, payload) => {
+      state.mwxuser = payload
+    },
+    setmwxrole: (state, payload) => {
+      state.mwxrole = payload
+    },
+    setmwxorg: (state, payload) => {
+      state.mwxorg = payload
+    }
+  },
+  actions: {
+    login({ commit, state }, data) {
+      return new Promise((resolve, reject) => {
+        $login(data).then(response => {
+          if (response.data.code === 0) {
+            const token = response.data.data.access_token
+            const user = response.data.data.user
+            const orgId = user.authorities[0].orgId
+            const roleId = user.authorities[0].roles[0].roleId
+            const identifier = Base64.encode(
+              'woperation:' + orgId + ':' + user.user_id + ':' + roleId
+            )
+
+            setLocalStorage('mwxtoken', token)
+            setLocalStorage('mwxuser', JSON.stringify(user))
+            setLocalStorage('mwxidntf', identifier)
+            setLocalStorage('mwxorg', orgId)
+            setLocalStorage('mwxrole', roleId)
+            commit('setmwxtoken', token)
+            commit('setmwxidntf', identifier)
+            commit('setmwxuser', user)
+            commit('setmwxrole', roleId)
+            commit('setmwxorg', orgId)
+
+            resolve(response)
+          }
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+    logout({ commit, state }) {
+      return new Promise((resolve, reject) => {
+        const data = {
+          client_id: 'woperation',
+          user_id: state.mwxuser.user_id
+        }
+        $logout(data).then(response => {
+          removeLocalStorage('mwxtoken')
+          removeLocalStorage('mwxuser')
+          removeLocalStorage('mwxidntf')
+          removeLocalStorage('mwxorg')
+          removeLocalStorage('mwxrole')
+          removeLocalStorage('location_pointer')
+          commit('setmwxtoken')
+          commit('setmwxidntf')
+          commit('setmwxuser')
+          commit('setmwxrole')
+          commit('setmwxorg')
+
+          resolve()
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+    clear({ commit, state }) {
+      return new Promise((resolve, reject) => {
+        removeLocalStorage('mwxtoken')
+        removeLocalStorage('mwxuser')
+        removeLocalStorage('mwxidntf')
+        removeLocalStorage('mwxorg')
+        removeLocalStorage('mwxrole')
+        removeLocalStorage('location_pointer')
+        commit('setmwxtoken')
+        commit('setmwxidntf')
+        commit('setmwxuser')
+        commit('setmwxrole')
+        commit('setmwxorg')
+        resolve()
+      })
+    }
+  }
+}
+
+export default user

+ 111 - 0
src/utils/directive/index.js

@@ -0,0 +1,111 @@
+import Vue from 'vue'
+
+function vueTouch(el, binding, type) {
+  var _this = this
+  this.obj = el
+  this.binding = binding
+  this.touchType = type
+  this.vueTouches = { x: 0, y: 0 }
+  this.vueMoves = true
+  this.vueLeave = true
+  this.longTouch = true
+  this.vueCallBack = typeof (binding.value) == 'object' ? binding.value.fn : binding.value
+  this.obj.addEventListener('touchstart', function (e) {
+    _this.start(e)
+  }, false)
+  this.obj.addEventListener('touchend', function (e) {
+    _this.end(e)
+  }, false)
+  this.obj.addEventListener('touchmove', function (e) {
+    _this.move(e)
+  }, false)
+}
+vueTouch.prototype = {
+  start: function (e) {
+    this.vueMoves = true
+    this.vueLeave = true
+    this.longTouch = true
+    this.vueTouches = { x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY }
+    this.time = setTimeout(function () {
+      if (this.vueLeave && this.vueMoves) {
+        this.touchType == 'longtap' && this.vueCallBack(this.binding.value, e)
+        this.longTouch = false
+      }
+    }.bind(this), 1000)
+  },
+  end: function (e) {
+    var disX = e.changedTouches[0].pageX - this.vueTouches.x
+    var disY = e.changedTouches[0].pageY - this.vueTouches.y
+    clearTimeout(this.time)
+    if (Math.abs(disX) > 10 || Math.abs(disY) > 100) {
+      this.touchType == 'swipe' && this.vueCallBack(this.binding.value, e)
+      if (Math.abs(disX) > Math.abs(disY)) {
+        if (disX > 10) {
+          this.touchType == 'swiperight' && this.vueCallBack(this.binding.value, e)
+        }
+        if (disX < -10) {
+          this.touchType == 'swipeleft' && this.vueCallBack(this.binding.value, e)
+        }
+      } else {
+        if (disY > 10) {
+          this.touchType == 'swipedown' && this.vueCallBack(this.binding.value, e)
+        }
+        if (disY < -10) {
+          this.touchType == 'swipeup' && this.vueCallBack(this.binding.value, e)
+        }
+      }
+    } else {
+      if (this.longTouch && this.vueMoves) {
+        this.touchType == 'tap' && this.vueCallBack(this.binding.value, e)
+        this.vueLeave = false
+      }
+    }
+  },
+  move: function (e) {
+    this.vueMoves = false
+  }
+}
+const tap = Vue.directive('tap', {
+  bind: function (el, binding) {
+    // eslint-disable-next-line no-new,new-cap
+    new vueTouch(el, binding, 'tap')
+  }
+})
+const swipe = Vue.directive('swipe', {
+  bind: function (el, binding) {
+    // eslint-disable-next-line no-new,new-cap
+    new vueTouch(el, binding, 'swipe')
+  }
+})
+const swipeleft = Vue.directive('swipeleft', {
+  bind: function (el, binding) {
+    // eslint-disable-next-line no-new,new-cap
+    new vueTouch(el, binding, 'swipeleft')
+  }
+})
+const swiperight = Vue.directive('swiperight', {
+  bind: function (el, binding) {
+    // eslint-disable-next-line no-new,new-cap
+    new vueTouch(el, binding, 'swiperight')
+  }
+})
+const swipedown = Vue.directive('swipedown', {
+  bind: function (el, binding) {
+    // eslint-disable-next-line no-new,new-cap
+    new vueTouch(el, binding, 'swipedown')
+  }
+})
+const swipeup = Vue.directive('swipeup', {
+  bind: function (el, binding) {
+    // eslint-disable-next-line no-new,new-cap
+    new vueTouch(el, binding, 'swipeup')
+  }
+})
+const longtap = Vue.directive('longtap', {
+  bind: function (el, binding) {
+    // eslint-disable-next-line no-new,new-cap
+    new vueTouch(el, binding, 'longtap')
+  }
+})
+
+export { longtap, swipeup, swipedown, swiperight, swipeleft, swipe, tap }

+ 86 - 0
src/utils/filters.js

@@ -0,0 +1,86 @@
+import { formatDate } from '@/utils/tools'
+
+const gasstationImage = item => {
+  return item
+}
+
+const formateTData = (date, fmt) => {
+  if (date) {
+    const sign = date.indexOf('T') >= 0 ? 'T' : ' '
+    const times = date.split(sign)
+    const time = times[1] && times[1].split(':')
+
+    if (fmt == 'date') {
+      return times[0]
+    }
+    return times[0] + ' ' + (time[0] || '00') + ':' + (time[1] || '00') + (fmt == 'all' ? (':' + (time[2] || '00')) : '')
+  }
+
+  return ''
+}
+
+const formateZeroToBar = (value, type = 'int') => {
+  if (type === 'int') {
+    return value || '-'
+  } else {
+    return value ? (value > 0 && value < 1) ? Number(value).toFixed(2) : parseInt(value) : '-'
+  }
+}
+
+const formateTextDeleteNULL = (value) => {
+  return value ? (value === 'null' ? '-' : value) : '-'
+}
+
+const formateMoney = (value) => {
+  return currency(value, '', '')
+}
+
+const currency = (value, unit, currency, decimals) => {
+  const digitsRE = /(\d{3})(?=\d)/g
+  value = parseFloat(value)
+  if (!isFinite(value) || (!value && value === 0)) return '-'
+  currency = currency != null ? currency : '¥'
+  decimals = decimals != null ? decimals : 2
+  var stringified = Math.abs(value).toFixed(decimals)
+  var _int = decimals
+    ? stringified.slice(0, -1 - decimals)
+    : stringified
+  var i = _int.length % 3
+  var head = i > 0
+    ? (_int.slice(0, i) + (_int.length > 3 ? ',' : ''))
+    : ''
+  var _float = decimals
+    ? stringified.slice(-1 - decimals)
+    : ''
+  var sign = value < 0 ? '-' : ''
+  return sign + currency + head +
+    _int.slice(i).replace(digitsRE, '$1,') +
+    _float +
+    (unit ? ' ' + unit : '')
+}
+
+const stockStatus = (value) => {
+  if (value == 0) return '已取消'
+  else if (value == 1) return '已下单'
+  else if (value == 2) return '已确认'
+  else if (value == 3) return '已出港'
+  else if (value == 4) return '已签收'
+  else if (value == 5) return '已完成'
+}
+const printerStatus = (value) => {
+  if (value == 0) return '离线'
+  else if (value == 1) return '在线'
+  else if (value == 2) return '缺纸'
+}
+
+export {
+  gasstationImage,
+  printerStatus,
+  formateTData,
+  formatDate,
+  currency,
+  stockStatus,
+  formateMoney,
+  formateZeroToBar,
+  formateTextDeleteNULL
+}

+ 100 - 0
src/utils/format.js

@@ -0,0 +1,100 @@
+import Vue from 'vue'
+import moment from 'moment'
+Vue.prototype.$moment = moment
+// 里面的字符可以根据自己的需要进行调整
+moment.locale('zh-cn', {
+  months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split(
+    '_'
+  ),
+  monthsShort: '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
+  weekdays: '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
+  weekdaysShort: '周日_周一_周二_周三_周四_周五_周六'.split('_'),
+  weekdaysMin: '日_一_二_三_四_五_六'.split('_'),
+  longDateFormat: {
+    LT: 'HH:mm',
+    LTS: 'HH:mm:ss',
+    L: 'YYYY-MM-DD',
+    LL: 'YYYY年MM月DD日',
+    LLL: 'YYYY年MM月DD日Ah点mm分',
+    LLLL: 'YYYY年MM月DD日ddddAh点mm分',
+    l: 'YYYY-M-D',
+    ll: 'YYYY年M月D日',
+    lll: 'YYYY年M月D日 HH:mm',
+    llll: 'YYYY年M月D日dddd HH:mm'
+  },
+  meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
+  meridiemHour: function (hour, meridiem) {
+    if (hour === 12) {
+      hour = 0
+    }
+    if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') {
+      return hour
+    } else if (meridiem === '下午' || meridiem === '晚上') {
+      return hour + 12
+    } else {
+      // '中午'
+      return hour >= 11 ? hour : hour + 12
+    }
+  },
+  meridiem: function (hour, minute) {
+    const hm = hour * 100 + minute
+    if (hm < 600) {
+      return '凌晨'
+    } else if (hm < 900) {
+      return '早上'
+    } else if (hm < 1130) {
+      return '上午'
+    } else if (hm < 1230) {
+      return '中午'
+    } else if (hm < 1800) {
+      return '下午'
+    } else {
+      return '晚上'
+    }
+  },
+  calendar: {
+    sameDay: '[今天]LT',
+    nextDay: '[明天]LT',
+    nextWeek: '[下]ddddLT',
+    lastDay: '[昨天]LT',
+    lastWeek: '[上]ddddLT',
+    sameElse: 'L'
+  },
+  dayOfMonthOrdinalParse: /\d{1,2}(日|月|周)/,
+  ordinal: function (number, period) {
+    switch (period) {
+      case 'd':
+      case 'D':
+      case 'DDD':
+        return number + '日'
+      case 'M':
+        return number + '月'
+      case 'w':
+      case 'W':
+        return number + '周'
+      default:
+        return number
+    }
+  },
+  relativeTime: {
+    future: '%s内',
+    past: '%s前',
+    s: '几秒',
+    ss: '%d秒',
+    m: '1分钟',
+    mm: '%d分钟',
+    h: '1小时',
+    hh: '%d小时',
+    d: '1天',
+    dd: '%d天',
+    M: '1个月',
+    MM: '%d个月',
+    y: '1年',
+    yy: '%d年'
+  },
+  week: {
+    // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
+    dow: 1, // Monday is the first day of the week.
+    doy: 4 // The week that contains Jan 4th is the first week of the year.
+  }
+})

+ 59 - 0
src/utils/list.js

@@ -0,0 +1,59 @@
+import { isValidateMobile } from '@/utils/validate'
+
+const truck = [
+  { field: 'truckName', name: '卡车品牌', type: 'autocomplete', required: true, placeholder: '请输入卡车品牌', rules: [{ required: true, message: '请输入卡车品牌' }] },
+  { field: 'carNumber', name: '车牌号', required: true, readonly: 'edit', placeholder: '请输入车牌号', rules: [{ required: true, message: '请输入车牌号' }] },
+  { field: 'linked', name: '车辆类型', type: 'radio', required: true, rules: [{ required: true, message: '请选择' }], list: [{ name: '其他', value: 1 }, { name: '自营', value: 0 }] },
+  { field: 'frameNumber', name: '车架号', required: true, placeholder: '请输入车架号', rules: [{ required: true, message: '请输入车架号' }] },
+  { field: 'deadWeight', name: '载重量(吨)', type: 'number', required: true, placeholder: '请输入载重量', rules: [{ required: true, message: '请输入载重量' }] },
+  { field: 'capacity', name: '储气罐容量(升)', type: 'number', required: true, placeholder: '请输入储气罐容量', rules: [{ required: true, message: '请输入储气罐容量' }] },
+  { field: 'purchaseDate', type: 'date', readonly: 'add', name: '购车日期', required: true, placeholder: '请选择购车时间', rules: [{ required: true, message: '请选择购车时间' }] },
+  { field: 'trailerNumber', name: '挂车牌号', placeholder: '请输入挂车牌号', style: 'margin-top: 10px;' },
+  { field: 'color', name: '颜色', placeholder: '请输入颜色' },
+  { field: 'manufacturer', name: '生产厂家', type: 'autocomplete', placeholder: '请输入生产厂家' },
+  // { field: 'status', queryType: 'edit', active: 0, inactive: 1, type: 'switch', name: '车辆状态', style: 'margin-top: 10px;' },
+  { field: 'status', type: 'radio', name: '车辆状态', style: 'margin-top: 10px;', required: true, list: [{ name: '停用', value: 1 }, { name: '启用', value: 0 }] },
+  { field: 'autoAccount', name: '圈存方式', type: 'radio', required: true, rules: [{ required: true, message: '请选择' }], list: [{ name: '自动圈存', value: 1 }, { name: '手动圈存', value: 0 }] }
+]
+
+const driver = [
+  { field: 'userName', name: '姓名', required: true, placeholder: '请输入姓名', rules: [{ required: true, message: '请输入姓名' }] },
+  { field: 'mobile', name: '手机号', required: true, placeholder: '请输入手机号', rules: [{ required: true, message: '请输入手机号' }, { validator: isValidateMobile, message: '请输入正确手机号码' }] },
+  { field: 'userCode', name: '登录名称', placeholder: '请输入登录名称', style: 'margin-top: 10px;' },
+  { field: 'status', queryType: 'edit', active: 0, inactive: 1, type: 'switch', name: '账号状态', style: 'margin-top: 10px;' },
+  { field: 'resetPwd', queryType: 'edit', type: 'btn', name: '重置密码' }
+]
+
+const cashier = [
+  { field: 'userName', name: '姓名', required: true, placeholder: '请输入姓名', rules: [{ required: true, message: '请输入姓名' }] },
+  { field: 'mobile', name: '手机号', required: true, placeholder: '请输入手机号', rules: [{ required: true, message: '请输入手机号' }, { validator: isValidateMobile, message: '请输入正确手机号码' }] },
+  { field: 'userCode', name: '登录名称', placeholder: '请输入登录名称', style: 'margin-top: 10px;' },
+  { field: 'status', queryType: 'edit', active: 0, inactive: 1, type: 'switch', name: '账号状态', style: 'margin-top: 10px;' },
+  { field: 'resetPwd', queryType: 'edit', type: 'btn', name: '重置密码' }
+]
+
+const printer = [
+  { field: 'printName', readonly: 'edit', name: '打印机名称', required: true, labelWidth: '5.8rem', placeholder: '请输入打印机名称', rules: [{ required: true, message: '请输入打印机名称' }] },
+  { field: 'machineCode', readonly: 'edit', name: '终端号', required: true, labelWidth: '5.8rem', placeholder: '请输入终端号', rules: [{ required: true, message: '请输入终端号' }] },
+  { field: 'msign', readonly: 'edit', name: '密钥', required: true, labelWidth: '5.8rem', placeholder: '请输入密钥', rules: [{ required: true, message: '请输入密钥' }] },
+  { field: 'phone', readonly: 'edit', name: '流量卡号', required: true, labelWidth: '5.8rem', placeholder: '请输入流量卡号', rules: [{ required: true, message: '请输入流量卡号' }] },
+  { field: 'status', type: 'label', queryType: 'edit', disabled: 'edit', name: '状态', list: [{ name: '离线', value: 0 }, { name: '在线', value: 1 }, { name: '缺纸', value: 2 }], labelWidth: '5.8rem' },
+  { field: 'print', type: 'radio', labelWidth: '4rem', name: '打印小票', required: true, list: [{ name: '是', value: 1 }, { name: '否', value: 0 }], style: 'margin-top: 10px;' },
+  { field: 'voice', type: 'radio', labelWidth: '4rem', name: '语音播报', required: true, list: [{ name: '关闭', value: 0 }, { name: '小', value: 1 }, { name: '中', value: 2 }, { name: '大', value: 3 }] }
+]
+
+const stock = [
+  { field: 'planTime', type: 'datetime', name: '期望到站时间', required: true, labelWidth: '6.5rem', placeholder: '请选择期望到站时间', rules: [{ required: true, message: '请选择期望到站时间' }] },
+  { field: 'downloadContactName', name: '卸车联系人', required: true, labelWidth: '6.5rem', placeholder: '请输入卸车联系人', rules: [{ required: true, message: '请输入卸车联系人' }] },
+  { field: 'downloadContactPhone', name: '卸车联系电话', required: true, labelWidth: '6.5rem', placeholder: '请输入卸车联系电话', rules: [{ required: true, message: '请输入卸车联系电话' }, { validator: isValidateMobile, message: '请输入正确手机号码' }] },
+  { field: 'businessContactName', readonly: 'add', name: '平台联系人', placeholder: '请输入平台联系人', labelWidth: '6.5rem' },
+  { field: 'businessContactPhone', readonly: 'add', name: '平台联系电话', placeholder: '请输入平台联系电话', labelWidth: '6.5rem' }
+]
+
+export {
+  truck,
+  driver,
+  cashier,
+  printer,
+  stock
+}

+ 20 - 0
src/utils/mixins/clearStorage.js

@@ -0,0 +1,20 @@
+import { setLocalStorage, getLocalStorage, removeLocalStorage } from '@/utils/storage'
+
+export default {
+  data() {
+    return {
+      orderStorageKey: 'order_filter'
+    }
+  },
+  methods: {
+    orderLocalStorage(filter) {
+      setLocalStorage(this.orderStorageKey, filter)
+    },
+    getOrderLocalStorage() {
+      return getLocalStorage(this.orderStorageKey)
+    },
+    clearOrderLocalStorage() {
+      removeLocalStorage(this.orderStorageKey)
+    }
+  }
+}

+ 30 - 0
src/utils/mixins/createOrder.js

@@ -0,0 +1,30 @@
+import { $createGasOrder } from '@/service/pay'
+import { $purchaseGasInfo } from '@/service/strategy'
+
+export default {
+  created() {
+    this.getGasstationInfo()
+  },
+  methods: {
+    getGasstationInfo() {
+      $purchaseGasInfo({ gasstationId: this.gasstationId }).then(response => {
+        this.options.info.value = response.platformPrice
+      })
+    },
+    createGasOrderEvent(param, path) {
+      this.$toast.loading({
+        duration: 0, // 持续展示 toast
+        forbidClick: true, // 禁用背景点击
+        message: '订单创建中...'
+      })
+      // alert("参数!!!" + JSON.stringify(param))
+      $createGasOrder(param).then(response => {
+        this.$toast('提交完成!')
+        setTimeout(() => {
+          this.$router.replace({ path: path })
+        }, 1000)
+        this.$toast.clear()
+      })
+    }
+  }
+}

+ 11 - 0
src/utils/mixins/main.js

@@ -0,0 +1,11 @@
+export default {
+  methods: {
+    openPage() {
+      const t = new Date().getTime()
+      this.$router.replace({
+        path: '/boss',
+        query: { t }
+      })
+    }
+  }
+}

+ 131 - 0
src/utils/mixins/wechat.js

@@ -0,0 +1,131 @@
+import { $wxSdkSign } from '@/service/pay'
+
+const wx = require('weixin-js-sdk')
+
+export default {
+  methods: {
+    async wechatShare(info) {
+      // 判断苹果手机
+      let _url = ''
+
+      if (window.__wxjs_is_wkwebview === true) {
+        _url = window.location.href.split('#')[0] || window.location.href
+      } else {
+        _url = window.location.href
+      }
+      // 这里是封装的axios,换成任意请求皆可
+      $wxSdkSign({ url: _url }).then(res => {
+        wx.config({
+          // beta: true,
+          debug: false,
+          appId: res.appId, // 必填,公众号的唯一标识
+          timestamp: res.timestamp, // 必填,生成签名的时间戳
+          nonceStr: res.nonceStr, // 必填,生成签名的随机串
+          signature: res.signature, // 必填,签名,见附录1
+          jsApiList: [
+            'checkJsApi',
+            'scanQRCode',
+            'updateTimelineShareData',
+            'updateAppMessageShareData',
+            'onMenuShareTimeline',
+            'onMenuShareAppMessage',
+            'getLocation',
+            'openLocation',
+            'chooseWXPay'
+          ] // 必填,需要使用的 JS 接口列表,所有JS接口列表见附录2
+        })
+        wx.ready(() => {
+          // info 接口返回的数据,用来配置分享内容
+          // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
+          // 分享给朋友圈
+          const shareTitle = '软件分享'
+          const shareLink = window.location.href
+          const shareDesc = '是一款方便司机用户加气的手机软件,整合全xxx家LNG加气站信息,提供包括位置、电话、气价参考、在线充值加气优惠折扣等全面服务,用户开通定位功能后会自动帮助用户推荐附近的加气站,并且提供加气导航'
+
+          wx.checkJsApi({
+            jsApiList: ['getLocation'],
+            success: function (res) {
+              if (res.checkResult.getLocation == false) {
+                alert('你的微信版本太低,不支持微信JS接口,请升级到最新的微信版本!')
+              }
+            }
+          })
+
+          wx.updateTimelineShareData({
+            title: shareTitle, // 分享标题
+            link: shareLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
+            // imgUrl: require('../assets/'), // 分享图标
+            success: function () {
+              // 设置成功
+            }
+          })
+          // 分享给朋友
+          wx.updateAppMessageShareData({
+            title: shareTitle, // 分享标题
+            desc: shareDesc, // 分享描述
+            link: shareLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
+            // imgUrl: require('../assets/'), // 分享图标
+            success: function () {
+              // 设置成功
+            }
+          })
+          // 获取地理位置
+          wx.getLocation({
+            type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
+            success: function (res) {
+              localStorage.setItem('location_pointer', JSON.stringify({
+                latitude: res.latitude,
+                longitude: res.longitude
+              }))
+            }
+          })
+        })
+
+        wx.error(function (res) {
+          // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
+          console.log(JSON.stringify(res))
+        })
+      })
+    },
+    // 调用扫码摄像头
+    wxScanCode(callback) {
+      wx.scanQRCode({
+        needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
+        scanType: ['qrCode'], // 可以指定扫二维码还是一维码,默认二者都有
+        success: function (res) {
+          callback(res.resultStr)
+          // 扫码后获取结果参数赋值给Input
+        },
+        fail: function (err) {
+          alert(JSON.stringify(err))
+        }
+      })
+    },
+    // 获取地理位置
+    getLocation(callback) {
+      wx.ready(function (res) {
+        wx.getLocation({
+          type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
+          success: function (res) {
+            if (typeof callback === 'function') {
+              console.log(res, '获取地理位置成功')
+              callback(res.latitude, res.longitude)
+            }
+          }
+        })
+      })
+    },
+    openLocation(options) {
+      wx.ready(function (res) {
+        wx.openLocation({
+          latitude: options.la, // 纬度,浮点数,范围为90 ~ -90
+          longitude: options.lo, // 经度,浮点数,范围为180 ~ -180。
+          name: options.name, // 位置名
+          address: options.desc, // 地址详情说明
+          scale: 12, // 地图缩放级别,整形值,范围从1~28。默认为最大
+          infoUrl: options.url // 在查看位置界面底部显示的超链接,可点击跳转
+        })
+      })
+    }
+  }
+}

+ 60 - 0
src/utils/request.js

@@ -0,0 +1,60 @@
+import Axios from 'axios'
+import store from '@/store'
+import { Toast } from 'vant'
+
+const axios = Axios.create({
+  timeout: 30000,
+  baseURL: process.env.VUE_APP_BASE_URL
+})
+axios.interceptors.request.use(
+  config => {
+    config.headers.Authorization = 'Bearer ' + store.getters.mwxtoken
+    config.headers.Identifier = store.getters.mwxidntf
+    store.getters.debug && console.log('axios.interceptors.request.use...', config)
+    return config
+  },
+  error => {
+    return Promise.reject(error)
+  }
+)
+axios.interceptors.response.use(
+  response => {
+    store.getters.debug && console.log('axios.interceptors.response.use...', response)
+    if (response.data.code === 0) {
+      return Promise.resolve(response.data.data)
+    } else if (response.data.code === 2814) {
+      Toast(response.data.message)
+      store.dispatch('clear').then(() => {
+        location.reload() // 为了重新实例化vue-router对象 避免bug
+      })
+    } else {
+      Toast(response.data.message)
+      return Promise.reject(response.data.message)
+    }
+  },
+  err => {
+    if (err && err.response) {
+      switch (err.response.status) {
+        case 400 : err.message = '请求错误(400)'; break
+        case 401: err.message = '未授权,请重新登录(401)'; break
+        case 403: err.message = '拒绝访问(403)'; break
+        case 404: err.message = '请求出错(404)'; break
+        case 408: err.message = '请求超时(408)'; break
+        case 500: err.message = '服务器错误(500)'; break
+        case 501: err.message = '服务未实现(501)'; break
+        case 502: err.message = '网络错误(502)'; break
+        case 503: err.message = '服务不可用(503)'; break
+        case 504: err.message = '网络超时(504)'; break
+        case 505: err.message = 'HTTP版本不受支持(505)'; break
+        default: err.message = `连接出错(${err.response.status})!`
+      }
+    } else {
+      err.message = '连接服务器失败!'
+    }
+    Toast(err.message)
+    return Promise.reject(err.message)
+  }
+)
+export default ({ url, method = 'POST', data = {}, params = {} }) => {
+  return axios({ url, method, data, params })
+}

+ 11 - 0
src/utils/storage.js

@@ -0,0 +1,11 @@
+export function setLocalStorage(key, token) {
+  return localStorage.setItem(key, token)
+}
+
+export function removeLocalStorage(key) {
+  return localStorage.removeItem(key)
+}
+
+export function getLocalStorage(key) {
+  return localStorage.getItem(key)
+}

+ 286 - 0
src/utils/tools.js

@@ -0,0 +1,286 @@
+export function byteLength(val) {
+  var byteLen = 0
+  var len = val.length // 初始化字节数递加变量并获取字符串参数的字符个数
+  if (len) { // 如果存在字符串,则执行计划
+    for (var i = 0; i < len; i++) { // 遍历字符串,枚举每个字符
+      if (val.charCodeAt(i) > 255) { // 字符编码大于255,说明是双字节字符
+        byteLen += 2 // 则累加2个
+      } else {
+        byteLen++ // 否则递加一次
+      }
+    }
+  }
+
+  return byteLen // 返回字节数
+}
+
+export function isTypeof(option) {
+  var value = Object.prototype.toString.call(option)
+
+  if (value === '[object Undefined]') return 'undefined'
+  else if (value === '[object String]') return 'string'
+  else if (value === '[object Boolean]') return 'boolean'
+  else if (value === '[object Number]') return 'number'
+  else if (value === '[object Null]') return 'null'
+  else if (value === '[object Object]') return 'object'
+  else if (value === '[object Array]') return 'array'
+  else if (value === '[object Date]') return 'date'
+  else if (value === '[object RegExp]') return 'regexp'
+  else if (value === '[object Function]') return 'function'
+}
+
+export function formateTData(date, fmt) { // 字符串
+  if (date) {
+    if (fmt == 'date') {
+      return formatDate(date, 'yyyy-MM-dd')
+    } else if (fmt == 'all') {
+      return formatDate(date, 'yyyy-MM-dd hh:mm:ss')
+    } else {
+      return formatDate(date, 'yyyy-MM-dd hh:mm')
+    }
+  }
+
+  return ''
+}
+export function formatDate(date, fmt) { // date对象
+  if (!date) return '-'
+  var padLeftZero = function (str) {
+    return ('00' + str).substr(str.length)
+  }
+  if (isTypeof(date) === 'string') {
+    let tmpDate = date.replace(/-/g, '/')
+    tmpDate = tmpDate.replace(/T/g, ' ')
+
+    date = new Date(tmpDate)
+  } else if (isTypeof(date) == 'number') {
+    date = new Date(date)
+  }
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
+  }
+  const o = {
+    'M+': date.getMonth() + 1,
+    'd+': date.getDate(),
+    'h+': date.getHours(),
+    'm+': date.getMinutes(),
+    's+': date.getSeconds()
+  }
+  for (var k in o) {
+    if (new RegExp(`(${k})`).test(fmt)) {
+      var str = o[k] + ''
+      fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str))
+    }
+  }
+
+  return fmt
+}
+
+export function inArray(value, array) {
+  if (Array.prototype.indexOf) {
+    return array.indexOf(value)
+  } else {
+    for (var i = 0; i < array.length; i++) {
+      if (array[i] === value) return i
+    }
+  }
+
+  return -1
+}
+
+export function dateTimePickerRange(timeString, sign) {
+  sign = sign || '#'
+  const times = timeString.split(sign)
+  var start = times[0].split('-')
+  var end = times[1].split('-')
+
+  return {
+    start: formatDate(new Date(start[0], (parseInt(start[1]) - 1), start[2]), 'yyyy-MM-dd 00:00:00'),
+    end: formatDate(new Date(end[0], (parseInt(end[1]) - 1), end[2]), 'yyyy-MM-dd 23:59:59')
+  }
+}
+export function fillerTimePickerRange(timeString, sign) {
+  sign = sign || '#'
+  const times = timeString.split(sign)
+  var startDate = times[0].split('-')
+  var endDate = times[1].split('-')
+  var startDay = startDate[2].split(' ')
+  var endDay = endDate[2].split(' ')
+  var startTime = startDay[1].split(':')
+  var endTime = endDay[1].split(':')
+  var start = [startDate[0], startDate[1], startDay[0], startTime[0], startTime[1]]
+  var end = [endDate[0], endDate[1], endDay[0], endTime[0], endTime[1]]
+  return {
+    start: formatDate(new Date(start[0], (parseInt(start[1]) - 1), start[2], start[3], start[4]), 'yyyy-MM-dd hh:mm:00'),
+    end: formatDate(new Date(end[0], (parseInt(end[1]) - 1), end[2], end[3], end[4]), 'yyyy-MM-dd hh:mm:59')
+  }
+}
+export function monthTimeArea(now) {
+  var nowYear = now.getFullYear()
+  var nowMonth = now.getMonth()
+
+  var monthStartDate = new Date(nowYear, nowMonth, 1)
+  var monthEndDate = new Date(nowYear, nowMonth + 1, 1)
+
+  return {
+    start: formatDate(monthStartDate, 'yyyy-MM-dd 00:00:00'),
+    end: formatDate(monthEndDate, 'yyyy-MM-dd 00:00:00')
+  }
+}
+export function monthDay(now) {
+  var nowYear = now.getFullYear()
+  var nowMonth = now.getMonth()
+
+  var monthStartDate = new Date(nowYear, nowMonth, 1)
+  var monthEndDate = new Date(nowYear, nowMonth + 1, 1)
+
+  return {
+    start: formatDate(monthStartDate, 'yyyy-MM-dd'),
+    end: formatDate(monthEndDate, 'yyyy-MM-dd')
+  }
+}
+export function storeLogout(_this) {
+  _this.$store.dispatch('logout').then(() => {
+    location.reload()
+  })
+}
+
+export function uploadImageDate(path, upload, auth, status) {
+  return [{
+    url: (location.href.split('#/')[0].includes('localhost') ? 'http://dwx.auyen.com/' : location.href.split('#/')[0]) + path,
+    isUpload: upload,
+    isAuth: auth,
+    status: status
+  }]
+}
+// 加气站名片图片
+export function fillerIntroducePreview(path, id) {
+  return {
+    path: process.env.VUE_APP_FILE_URL + path,
+    id: id
+  }
+}
+
+/* 对象深度合并 */
+export function objectMerge(target, source) {
+  if (typeof target !== 'object') {
+    target = {}
+  }
+  if (Array.isArray(source)) {
+    return source.slice()
+  }
+  Object.keys(source).forEach((property) => {
+    const sourceProperty = source[property]
+    if (sourceProperty && typeof sourceProperty === 'object') {
+      target[property] = objectMerge(target[property], sourceProperty)
+    } else {
+      target[property] = sourceProperty
+    }
+  })
+
+  return target
+}
+
+// 对象深度赋值
+export function objectDepthAssignment(source) {
+  const sourceCopy = Object.prototype.toString.call(source) === '[object Array]' ? [] : {}
+
+  for (const item in source) {
+    sourceCopy[item] = typeof source[item] === 'object' ? objectDepthAssignment(source[item]) : source[item]
+  }
+
+  return sourceCopy
+}
+
+// 数组根据某个字段排序
+export function arrayResetSort(sourceColumn, key = 'serial', order = 'asc') {
+  const arrayList = objectDepthAssignment(sourceColumn)
+
+  if (order == 'asc') {
+    arrayList.sort((arr1, arr2) => {
+      return Number(arr1[key]) - Number(arr2[key])
+    })
+  } else if (order == 'desc') {
+    arrayList.sort((arr1, arr2) => {
+      return Number(arr2[key]) - Number(arr1[key])
+    })
+  }
+
+  return arrayList
+}
+// 加气站类型判断
+export function gasstationCheckType(type) {
+  if (type === 2001 || type === 2002 || type === 2003 || type === 2004) {
+    return true
+  }
+
+  return false
+}
+
+// 校验URL地址是否是http://或https://开头的
+export function isHttpHeaderURL(url) {
+  if (url && isTypeof(url) === 'string' && (url.indexOf('http://') === 0 || url.indexOf('https://') === 0)) {
+    return true
+  }
+
+  return false
+}
+
+// 站点类型
+export function utilSelectGasstationType(type = '') {
+  let result = [
+    { value: 2001, text: '中海油' },
+    { value: 2002, text: '中石油' },
+    { value: 2003, text: '中石化' },
+    { value: 2004, text: '社会' }
+  ]
+  if (type === 'inner') {
+    result = [
+      { value: 1001, text: '大象自营' },
+      { value: 1002, text: '大象合作' },
+      { value: 1003, text: '大象加盟' }
+    ]
+  }
+
+  if (type === 'all') {
+    result.unshift({ value: 1003, text: '大象加盟' })
+    result.unshift({ value: 1002, text: '大象合作' })
+    result.unshift({ value: 1001, text: '大象自营' })
+    result.unshift({ value: 100, text: '大象站' })
+    result.unshift({ value: null, text: '全部站点' })
+  }
+
+  return result
+}
+
+export function mapCenterToPointList() {
+  return {
+    鲁东: [120.7806, 36.8515],
+    鲁西: [117.1551, 36.3753],
+    '冀南&长治': [114.1888, 37.2461],
+    豫北: [113.1671, 34.4776],
+    冀北: [116.8036, 39.4642],
+    豫南: [113.4747, 32.0621],
+    '苏北&皖北': [118.05606, 33.5121]
+  }
+}
+
+// 去掉前后空格
+export function trim(str) {
+  return typeof str === 'string' ? str.replace(/^\s+|\s+$/g, '') : str
+}
+
+export function treeEqualValue (nodes, key, value) {
+  if (Array.isArray(nodes)) {
+    for (var i = 0, l = nodes.length; i < l; i++) {
+      if (nodes[i][key] == value) {
+        return nodes[i]
+      } else {
+        if (Array.isArray(nodes[i].children) && nodes[i].children.length > 0) {
+          return treeEqualValue(nodes[i].children, key, value)
+        }
+      }
+    }
+  } else {
+    return false
+  }
+}

+ 66 - 0
src/utils/validate.js

@@ -0,0 +1,66 @@
+// 验证金额
+export function validateMoneyNumber(rule, value, callback) {
+  var reg = /^([1-9][\d]{0,10}|0)(\.[\d]{1,2})?$/
+
+  if (!reg.test(value)) {
+    callback(new Error(rule.message))
+  } else {
+    callback()
+  }
+}
+
+// 验证手机号码
+export function isValidateMobile(value) {
+  var reg = /^((\+86)|(86))?[1][0-9]{10}$/
+
+  if (value == '') {
+    return true
+  }
+  return reg.test(value)
+}
+
+// 验证电话号码号码
+export function isValidatePhones(rule, value, callback) {
+  var reg = /^((\+86)|(86))((([0+]d{2,3}-)?(0d{2,3})-)(d{7,8})(-(d{3,}))?)|([1][0-9]{10})?$/
+
+  // console.log(rule, value, callback);
+  if (!reg.test(value)) {
+    callback(new Error(rule.message))
+  } else {
+    callback()
+  }
+}
+
+// 验证URL
+export function isUrl(rule, value, callback) {
+  // eslint-disable-next-line no-useless-escape
+  var reg = /^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_]*)?/
+
+  if (!reg.test(value)) {
+    callback(new Error(rule.message))
+  } else {
+    callback()
+  }
+}
+
+// 验证Email
+export function isEmail(rule, value, callback) {
+  var reg = /^[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/
+
+  if (!reg.test(value)) {
+    callback(new Error(rule.message))
+  } else {
+    callback()
+  }
+}
+
+// 验证身份证号码
+export function isIdCard(rule, value, callback) {
+  var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$/
+
+  if (!reg.test(value)) {
+    callback(new Error(rule.message))
+  } else {
+    callback()
+  }
+}

+ 44 - 0
src/utils/vant.js

@@ -0,0 +1,44 @@
+import Vue from 'vue'
+import { Toast, Dialog, Button, Checkbox, Field, Icon, Popup, TreeSelect, List, Cell, CellGroup, Form, NumberKeyboard, Row, Col, PullRefresh, DatetimePicker, Loading, DropdownMenu, DropdownItem, Search, Tag, CountDown, RadioGroup, Radio, Switch, Calendar, Picker, Uploader, Tab, Tabs, Divider, ActionSheet, NoticeBar, Steps, Step, Swipe, SwipeItem, Tabbar, TabbarItem, Image as VanImage } from 'vant'
+
+Vue.use(Toast)
+Vue.use(Dialog)
+Vue.use(Button)
+Vue.use(Checkbox)
+Vue.use(Field)
+Vue.use(Icon)
+Vue.use(Popup)
+Vue.use(TreeSelect)
+Vue.use(List)
+Vue.use(Cell)
+Vue.use(CellGroup)
+Vue.use(Form)
+Vue.use(NumberKeyboard)
+Vue.use(Row)
+Vue.use(Col)
+Vue.use(PullRefresh)
+Vue.use(DatetimePicker)
+Vue.use(Loading)
+Vue.use(DropdownMenu)
+Vue.use(DropdownItem)
+Vue.use(Search)
+Vue.use(Tag)
+Vue.use(CountDown)
+Vue.use(RadioGroup)
+Vue.use(Radio)
+Vue.use(Switch)
+Vue.use(Calendar)
+Vue.use(Picker)
+Vue.use(Uploader)
+Vue.use(Tab)
+Vue.use(Tabs)
+Vue.use(Divider)
+Vue.use(ActionSheet)
+Vue.use(NoticeBar)
+Vue.use(Steps)
+Vue.use(Step)
+Vue.use(Swipe)
+Vue.use(SwipeItem)
+Vue.use(Tabbar)
+Vue.use(TabbarItem)
+Vue.use(VanImage)

+ 54 - 0
src/utils/websocket.js

@@ -0,0 +1,54 @@
+import Stomp from 'stompjs'
+import router from '@/router'
+import store from '@/store'
+import { Dialog } from 'vant'
+
+let stompClient = null
+const wsurl = process.env.VUE_APP_WEBSS_URL + 'websocket/sockjs'
+function connect() {
+  if (stompClient == null || !stompClient.connected) {
+    const socket = new WebSocket(wsurl)
+    stompClient = Stomp.over(socket)
+
+    const headers = {
+      access_token: store.getters.mwxtoken,
+      identifier: store.getters.mwxidntf
+    }
+    stompClient.connect(headers, successcb, errorcb)
+  }
+}
+function successcb() {
+  stompClient.subscribe(
+    '/user/' + store.getters.mwxuser.user_id + '/msg',
+    callback
+  )
+}
+function callback(res) {
+  const body = JSON.parse(res.body)
+  if (body.type == 1) {
+    router.push('/perpayOrder')
+  } else if (body.type == 0) {
+    var content = JSON.parse(body.content)
+    Dialog.alert({
+      title: content.data,
+      message: content.message
+    }).then(() => {})
+  }
+}
+async function errorcb() {
+  await sleep(8000)
+  disconnect()
+  await sleep(2000)
+  stompClient = null
+  connect()
+}
+function sleep (time) {
+  return new Promise(resolve => setTimeout(resolve, time))
+}
+function disconnect() {
+  if (stompClient) {
+    stompClient.disconnect()
+  }
+}
+
+export default { connect, disconnect }

+ 135 - 0
src/utils/wscoordinate.js

@@ -0,0 +1,135 @@
+/**
+ *  判断经纬度是否超出中国境内
+ */
+function isLocationOutOfChina(latitude, longitude) {
+  if (longitude < 72.004 || longitude > 137.8347 || latitude < 0.8293 || latitude > 55.8271) return true
+  return false
+}
+
+/**
+ *  将WGS-84(国际标准)转为GCJ-02(火星坐标):
+ */
+function transformFromWGSToGCJ(latitude, longitude) {
+  var ee = 0.00669342162296594323
+  var a = 6378245.0
+  var pi = 3.14159265358979324
+
+  if (isLocationOutOfChina(latitude, longitude)) {
+    // lat = latitude
+    // lon = longitude
+  } else {
+    var adjustLat = transformLatWithXY(longitude - 105.0, latitude - 35.0)
+    var adjustLon = transformLonWithXY(longitude - 105.0, latitude - 35.0)
+    var radLat = latitude / 180.0 * pi
+    var magic = Math.sin(radLat)
+    magic = 1 - ee * magic * magic
+    var sqrtMagic = Math.sqrt(magic)
+    adjustLat = (adjustLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi)
+    adjustLon = (adjustLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi)
+    latitude = latitude + adjustLat
+    longitude = longitude + adjustLon
+  }
+  return { latitude: latitude, longitude: longitude }
+}
+
+/**
+ *  将GCJ-02(火星坐标)转为百度坐标:
+ */
+function transformFromGCJToBaidu(latitude, longitude) {
+  var pi = 3.14159265358979324 * 3000.0 / 180.0
+
+  var z = Math.sqrt(longitude * longitude + latitude * latitude) + 0.00002 * Math.sin(latitude * pi)
+  var theta = Math.atan2(latitude, longitude) + 0.000003 * Math.cos(longitude * pi)
+  var aLatitude = (z * Math.sin(theta) + 0.006)
+  var aLongitude = (z * Math.cos(theta) + 0.0065)
+
+  return { latitude: aLatitude, longitude: aLongitude }
+}
+
+/**
+ *  将百度坐标转为GCJ-02(火星坐标):
+ */
+function transformFromBaiduToGCJ(latitude, longitude) {
+  var xPi = 3.14159265358979323846264338327950288 * 3000.0 / 180.0
+
+  var x = longitude - 0.0065
+  var y = latitude - 0.006
+  var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * xPi)
+  var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * xPi)
+  var aLatitude = z * Math.sin(theta)
+  var aLongitude = z * Math.cos(theta)
+
+  return { latitude: aLatitude, longitude: aLongitude }
+}
+
+/**
+ *  将GCJ-02(火星坐标)转为WGS-84:
+ */
+function transformFromGCJToWGS(latitude, longitude) {
+  var threshold = 0.00001
+
+  // The boundary
+  var minLat = latitude - 0.5
+  var maxLat = latitude + 0.5
+  var minLng = longitude - 0.5
+  var maxLng = longitude + 0.5
+
+  var delta = 1
+  var maxIteration = 30
+
+  while (true) {
+    var leftBottom = transformFromWGSToGCJ(minLat, minLng)
+    var rightBottom = transformFromWGSToGCJ(minLat, maxLng)
+    var leftUp = transformFromWGSToGCJ(maxLat, minLng)
+    var midPoint = transformFromWGSToGCJ((minLat + maxLat) / 2, (minLng + maxLng) / 2)
+    delta = Math.abs(midPoint.latitude - latitude) + Math.abs(midPoint.longitude - longitude)
+
+    if (maxIteration-- <= 0 || delta <= threshold) {
+      return { latitude: (minLat + maxLat) / 2, longitude: (minLng + maxLng) / 2 }
+    }
+
+    if (isContains({ latitude: latitude, longitude: longitude }, leftBottom, midPoint)) {
+      maxLat = (minLat + maxLat) / 2
+      maxLng = (minLng + maxLng) / 2
+    } else if (isContains({ latitude: latitude, longitude: longitude }, rightBottom, midPoint)) {
+      maxLat = (minLat + maxLat) / 2
+      minLng = (minLng + maxLng) / 2
+    } else if (isContains({ latitude: latitude, longitude: longitude }, leftUp, midPoint)) {
+      minLat = (minLat + maxLat) / 2
+      maxLng = (minLng + maxLng) / 2
+    } else {
+      minLat = (minLat + maxLat) / 2
+      minLng = (minLng + maxLng) / 2
+    }
+  }
+}
+
+function isContains(point, p1, p2) {
+  return (point.latitude >= Math.min(p1.latitude, p2.latitude) && point.latitude <= Math.max(p1.latitude, p2.latitude)) && (point.longitude >= Math.min(p1.longitude, p2.longitude) && point.longitude <= Math.max(p1.longitude, p2.longitude))
+}
+
+function transformLatWithXY(x, y) {
+  var pi = 3.14159265358979324
+  var lat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x))
+  lat += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0
+  lat += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0
+  lat += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0
+  return lat
+}
+
+function transformLonWithXY(x, y) {
+  var pi = 3.14159265358979324
+  var lon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x))
+  lon += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0
+  lon += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0
+  lon += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0
+  return lon
+}
+
+module.exports = {
+  isLocationOutOfChina: isLocationOutOfChina,
+  transformFromWGSToGCJ: transformFromWGSToGCJ,
+  transformFromGCJToBaidu: transformFromGCJToBaidu,
+  transformFromBaiduToGCJ: transformFromBaiduToGCJ,
+  transformFromGCJToWGS: transformFromGCJToWGS
+}

+ 59 - 0
vue.config.js

@@ -0,0 +1,59 @@
+const cdn = {
+  // 忽略打包的第三方库
+  externals: {
+    /* vue: 'Vue',
+    vuex: 'Vuex',
+    axios: 'axios',
+    'vue-router': 'VueRouter' */
+  },
+  // 通过cdn方式使用
+  js: [
+    /* 'https://cdn.bootcss.com/vue/2.6.12/vue.min.js',
+    'https://cdn.bootcss.com/vue-router/3.2.0/vue-router.min.js',
+    'https://cdn.bootcss.com/vuex/3.1.1/vuex.min.js',
+    'https://cdn.bootcss.com/axios/0.19.2/axios.min.js' */
+  ],
+  css: []
+}
+
+module.exports = {
+  publicPath: './',
+  devServer: {
+    port: 8118,
+    open: true,
+    proxy: {
+      '/api': {
+        target: 'https://dapi.auyen.com',
+        changeOrigin: true,
+        pathRewrite: {
+          '^/api': '/'
+        }
+      }
+    }
+  },
+  chainWebpack: config => {
+    config.plugin('html').tap(args => {
+      args[0].cdn = cdn
+      return args
+    })
+    config.plugins.delete('preload')
+    config.plugins.delete('prefetch')
+
+    config.module
+      .rule('images')
+      .use('url-loader')
+      .loader('url-loader')
+      .tap(options => Object.assign(options, {
+        limit: 10240
+
+      })) // 配置图片Base64编码的阀值
+
+    if (process.env.NODE_ENV !== 'development') {
+      config.output.filename('js/[name].[contenthash].js').chunkFilename('js/[name].[contenthash].js').end()
+    }
+  },
+  // 打包忽略第三方库
+  configureWebpack: {
+    externals: cdn.externals
+  }
+}