lrf402788946 3 роки тому
батько
коміт
ce20988a0a

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+VUE_APP_AXIOS_BASE_URL = ''
+VUE_APP_ROUTER="market"
+VUE_APP_HOST="http://broadcast.waityou24.cn"

+ 33 - 0
.eslintrc.js

@@ -0,0 +1,33 @@
+// https://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: ['plugin:vue/essential', '@vue/prettier'],
+  plugins: ['vue'],
+  rules: {
+    'max-len': [
+      'warn',
+      {
+        code: 10000,
+      },
+    ],
+    'no-unused-vars': 'off',
+    'no-console': 'off',
+    'prettier/prettier': [
+      'warn',
+      {
+        singleQuote: true,
+        trailingComma: 'es5',
+        bracketSpacing: true,
+        jsxBracketSameLine: true,
+        printWidth: 160,
+      },
+    ],
+  },
+  parserOptions: {
+    parser: 'babel-eslint',
+  },
+};

+ 1 - 1
.gitignore

@@ -1,7 +1,7 @@
 .DS_Store
 node_modules
 /dist
-
+package-lock.json
 
 # local env files
 .env.local

+ 18 - 1
babel.config.js

@@ -1,3 +1,20 @@
 module.exports = {
-  presets: ["@vue/cli-plugin-babel/preset"],
+  presets: ['@vue/cli-plugin-babel/preset'],
+  plugins: [
+    [
+      'component',
+      {
+        libraryName: 'element-ui',
+        styleLibraryName: 'theme-chalk',
+      },
+    ],
+    [
+      'import',
+      {
+        libraryName: 'vant',
+        libraryDirectory: 'es',
+        style: true,
+      },
+    ],
+  ],
 };

+ 15 - 1
package.json

@@ -8,10 +8,22 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@stomp/stompjs": "^5.4.4",
+    "axios": "^0.21.1",
     "core-js": "^3.6.5",
+    "echarts": "^5.0.2",
+    "element-ui": "^2.15.3",
+    "jsonwebtoken": "^8.5.1",
+    "lodash": "^4.17.20",
+    "moment": "^2.29.1",
+    "naf-core": "^0.1.2",
+    "qrcode": "^1.4.4",
+    "vant": "^2.12.23",
     "vue": "^2.6.11",
+    "vue-meta": "^2.4.0",
     "vue-router": "^3.2.0",
-    "vuex": "^3.4.0"
+    "vuex": "^3.4.0",
+    "wangeditor": "^4.7.4"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "~4.5.0",
@@ -21,6 +33,8 @@
     "@vue/cli-service": "~4.5.0",
     "@vue/eslint-config-prettier": "^6.0.0",
     "babel-eslint": "^10.1.0",
+    "babel-plugin-component": "^1.1.1",
+    "babel-plugin-import": "^1.13.3",
     "eslint": "^6.7.2",
     "eslint-plugin-prettier": "^3.3.1",
     "eslint-plugin-vue": "^6.2.2",

+ 10 - 6
public/index.html

@@ -1,15 +1,19 @@
 <!DOCTYPE html>
 <html lang="">
   <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
     <title><%= htmlWebpackPlugin.options.title %></title>
   </head>
-  <body>
+  <body style="padding: 0; margin: 0">
     <noscript>
-      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+      <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 -->

+ 2 - 27
src/App.vue

@@ -1,32 +1,7 @@
 <template>
   <div id="app">
-    <div id="nav">
-      <router-link to="/">Home</router-link> |
-      <router-link to="/about">About</router-link>
-    </div>
-    <router-view />
+    <router-view style="height: 100vh" />
   </div>
 </template>
 
-<style lang="less">
-#app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
-}
-
-#nav {
-  padding: 30px;
-
-  a {
-    font-weight: bold;
-    color: #2c3e50;
-
-    &.router-link-exact-active {
-      color: #42b983;
-    }
-  }
-}
-</style>
+<style lang="less"></style>

+ 0 - 130
src/components/HelloWorld.vue

@@ -1,130 +0,0 @@
-<template>
-  <div class="hello">
-    <h1>{{ msg }}</h1>
-    <p>
-      For a guide and recipes on how to configure / customize this project,<br />
-      check out the
-      <a href="https://cli.vuejs.org" target="_blank" rel="noopener"
-        >vue-cli documentation</a
-      >.
-    </p>
-    <h3>Installed CLI Plugins</h3>
-    <ul>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
-          target="_blank"
-          rel="noopener"
-          >babel</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
-          target="_blank"
-          rel="noopener"
-          >router</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
-          target="_blank"
-          rel="noopener"
-          >vuex</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
-          target="_blank"
-          rel="noopener"
-          >eslint</a
-        >
-      </li>
-    </ul>
-    <h3>Essential Links</h3>
-    <ul>
-      <li>
-        <a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
-      </li>
-      <li>
-        <a href="https://forum.vuejs.org" target="_blank" rel="noopener"
-          >Forum</a
-        >
-      </li>
-      <li>
-        <a href="https://chat.vuejs.org" target="_blank" rel="noopener"
-          >Community Chat</a
-        >
-      </li>
-      <li>
-        <a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
-          >Twitter</a
-        >
-      </li>
-      <li>
-        <a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
-      </li>
-    </ul>
-    <h3>Ecosystem</h3>
-    <ul>
-      <li>
-        <a href="https://router.vuejs.org" target="_blank" rel="noopener"
-          >vue-router</a
-        >
-      </li>
-      <li>
-        <a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-devtools#vue-devtools"
-          target="_blank"
-          rel="noopener"
-          >vue-devtools</a
-        >
-      </li>
-      <li>
-        <a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
-          >vue-loader</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/awesome-vue"
-          target="_blank"
-          rel="noopener"
-          >awesome-vue</a
-        >
-      </li>
-    </ul>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "HelloWorld",
-  props: {
-    msg: String,
-  },
-};
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped lang="less">
-h3 {
-  margin: 40px 0 0;
-}
-ul {
-  list-style-type: none;
-  padding: 0;
-}
-li {
-  display: inline-block;
-  margin: 0 10px;
-}
-a {
-  color: #42b983;
-}
-</style>

+ 35 - 0
src/frame/client.vue

@@ -0,0 +1,35 @@
+<template>
+  <div id="client">
+    <van-nav-bar :title="$metaInfo.title" left-text="返回" left-arrow @click-left="toBack" />
+    <router-view />
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'client',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    toBack() {
+      this.$router.go(-1);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 6 - 5
src/main.js

@@ -1,7 +1,8 @@
-import Vue from "vue";
-import App from "./App.vue";
-import router from "./router";
-import store from "./store";
+import Vue from 'vue';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+import '@/plugins/index';
 
 Vue.config.productionTip = false;
 
@@ -9,4 +10,4 @@ new Vue({
   router,
   store,
   render: (h) => h(App),
-}).$mount("#app");
+}).$mount('#app');

+ 19 - 0
src/plugins/axios.js

@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import AxiosWrapper from '@/util/axios-wrapper';
+
+const Plugin = {
+  install(vue, options) {
+    // 3. 注入组件
+    vue.mixin({
+      created() {
+        if (this.$store && !this.$store.$axios) {
+          this.$store.$axios = this.$axios;
+        }
+      },
+    });
+    // 4. 添加实例方法
+    vue.prototype.$axios = new AxiosWrapper(options);
+  },
+};
+
+Vue.use(Plugin, { baseUrl: process.env.VUE_APP_AXIOS_BASE_URL });

+ 39 - 0
src/plugins/check-res.js

@@ -0,0 +1,39 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable no-param-reassign */
+/* eslint-disable no-unused-vars */
+/* eslint-disable no-shadow */
+import Vue from 'vue';
+import _ from 'lodash';
+import { Message } from 'element-ui';
+
+const vm = new Vue({});
+const Plugin = {
+  install(Vue, options) {
+    // 4. 添加实例方法
+    Vue.prototype.$checkRes = (res, okText, errText) => {
+      let _okText = okText;
+      let _errText = errText;
+      if (!_.isFunction(okText) && _.isObject(okText) && okText != null) {
+        ({ okText: _okText, errText: _errText } = okText);
+      }
+      const { errcode = 0, errmsg } = res || {};
+      if (errcode === 0) {
+        if (_.isFunction(_okText)) {
+          return _okText();
+        }
+        if (_okText) {
+          Message.success(_okText);
+        }
+        return true;
+      }
+      if (_.isFunction(_errText)) {
+        return _errText();
+      }
+      Message.error(_errText || errmsg);
+      // Message({ message: _errText || errmsg, duration: 60000 });
+      return false;
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 12 - 0
src/plugins/components.js

@@ -0,0 +1,12 @@
+import Vue from 'vue';
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import dataForm from '@common/src/components/frame/form.vue';
+import eUpload from '@common/src/components/frame/e-upload.vue';
+const Plugin = vue => {
+  vue.prototype.$dev_mode = process.env.NODE_ENV === 'development';
+  vue.component('data-table', dataTable);
+  vue.component('data-form', dataForm);
+  vue.component('eUpload', eUpload);
+};
+
+Vue.use(Plugin);

+ 9 - 0
src/plugins/element.js

@@ -0,0 +1,9 @@
+import Vue from 'vue';
+import Element from 'element-ui';
+const _ = require('lodash');
+const useList = ['Button', 'Row', 'Col', 'Image', 'Carousel', 'CarouselItem', 'Avatar', 'Divider', 'Tag'];
+
+for (const key of useList) {
+  const component = Element[_.upperFirst(key)];
+  Vue.use(component);
+}

+ 8 - 0
src/plugins/index.js

@@ -0,0 +1,8 @@
+import './axios';
+import './check-res';
+import './element';
+import './meta';
+import './setting';
+import './vant';
+import InitStomp from '@/plugins/stomp';
+InitStomp();

+ 4 - 0
src/plugins/meta.js

@@ -0,0 +1,4 @@
+import Vue from 'vue';
+import Meta from 'vue-meta';
+
+Vue.use(Meta);

+ 21 - 0
src/plugins/setting.js

@@ -0,0 +1,21 @@
+import Vue from 'vue';
+
+Vue.config.weixin = {
+  // baseUrl: process.env.BASE_URL + 'weixin',
+  baseUrl: `http://${location.host}`,
+};
+
+Vue.config.stomp = {
+  // brokerURL: 'ws://192.168.1.118/ws',
+  brokerURL: '/ws', // ws://${location.host}/ws
+  // brokerURL: 'ws://127.0.0.1:8000/ws',
+  connectHeaders: {
+    host: 'test',
+    login: 'test', //visit
+    passcode: '123456', //visit123
+  },
+  // debug: true,
+  reconnectDelay: 5000,
+  heartbeatIncoming: 4000,
+  heartbeatOutgoing: 4000,
+};

+ 65 - 0
src/plugins/stomp.js

@@ -0,0 +1,65 @@
+/**
+ * 基于WebStomp的消息处理插件
+ */
+
+import Vue from 'vue';
+import _ from 'lodash';
+import assert from 'assert';
+import { Client } from '@stomp/stompjs/esm5/client';
+
+const Plugin = {
+  install(Vue, options) {
+    assert(_.isObject(options));
+    if (options.debug && !_.isFunction(options.debug)) {
+      options.debug = (str) => {
+        console.log(str);
+      };
+    }
+    assert(_.isString(options.brokerURL));
+    if (!options.brokerURL.startsWith('ws://')) {
+      options.brokerURL = `ws://${location.host}${options.brokerURL}`;
+    }
+
+    // 3. 注入组件
+    Vue.mixin({
+      beforeDestroy: function () {
+        if (this.$stompClient) {
+          this.$stompClient.deactivate();
+          delete this.$stompClient;
+        }
+      },
+    });
+
+    // 4. 添加实例方法
+    Vue.prototype.$stomp = function (subscribes = {}) {
+      // connect to mq
+      const client = new Client(options);
+      client.onConnect = (frame) => {
+        // Do something, all subscribes must be done is this callback
+        // This is needed because this will be executed after a (re)connect
+        console.log('[stomp] connected');
+        Object.keys(subscribes)
+          .filter((p) => _.isFunction(subscribes[p]))
+          .forEach((key) => {
+            client.subscribe(key, subscribes[key]);
+          });
+      };
+
+      client.onStompError = (frame) => {
+        // Will be invoked in case of error encountered at Broker
+        // Bad login/passcode typically will cause an error
+        // Complaint brokers will set `message` header with a brief message. Body may contain details.
+        // Compliant brokers will terminate the connection after any error
+        console.log('Broker reported error: ' + frame.headers['message']);
+        console.log('Additional details: ' + frame.body);
+      };
+
+      client.activate();
+
+      this.$stompClient = client;
+    };
+  },
+};
+export default () => {
+  Vue.use(Plugin, Vue.config.stomp);
+};

+ 9 - 0
src/plugins/vant.js

@@ -0,0 +1,9 @@
+import Vue from 'vue';
+import { Grid, GridItem, Sidebar, SidebarItem, NavBar, Card } from 'vant';
+
+Vue.use(Grid);
+Vue.use(GridItem);
+Vue.use(Sidebar);
+Vue.use(SidebarItem);
+Vue.use(NavBar);
+Vue.use(Card);

+ 41 - 14
src/router/index.js

@@ -1,28 +1,55 @@
-import Vue from "vue";
-import VueRouter from "vue-router";
-import Home from "../views/Home.vue";
+import Vue from 'vue';
+import VueRouter from 'vue-router';
 
 Vue.use(VueRouter);
 
+const test = [
+  {
+    path: '/home',
+    name: 'home',
+    component: () => import(/* webpackChunkName: "about" */ '../views/test/home.vue'),
+  },
+  {
+    path: '/p2',
+    name: 'p2',
+    component: () => import(/* webpackChunkName: "p2" */ '../views/test/p2.vue'),
+  },
+  {
+    path: '/p3',
+    name: 'p3',
+    component: () => import(/* webpackChunkName: "p2" */ '../views/test/p3.vue'),
+  },
+];
+
 const routes = [
+  ...test,
+  {
+    path: '/',
+    name: 'home',
+    component: () => import(/* webpackChunkName: "home" */ '../views/home.vue'),
+  },
   {
-    path: "/",
-    name: "Home",
-    component: Home,
+    path: '/login',
+    name: 'login',
+    component: () => import(/* webpackChunkName: "login" */ '../views/login.vue'),
   },
   {
-    path: "/about",
-    name: "About",
-    // route level code-splitting
-    // this generates a separate chunk (about.[hash].js) for this route
-    // which is lazy-loaded when the route is visited.
-    component: () =>
-      import(/* webpackChunkName: "about" */ "../views/About.vue"),
+    path: '/',
+    name: 'client',
+    component: () => import(/* webpackChunkName: "client" */ '../frame/client.vue'),
+    children: [
+      {
+        path: '/shop',
+        name: 'shop',
+        meta: { title: '选择商品' },
+        component: () => import(/* webpackChunkName: "shop" */ '../views/shop/index.vue'),
+      },
+    ],
   },
 ];
 
 const router = new VueRouter({
-  mode: "history",
+  mode: 'history',
   base: process.env.BASE_URL,
   routes,
 });

+ 16 - 3
src/store/index.js

@@ -1,11 +1,24 @@
-import Vue from "vue";
-import Vuex from "vuex";
+import Vue from 'vue';
+import Vuex from 'vuex';
 
 Vue.use(Vuex);
 
 export default new Vuex.Store({
   state: {},
   mutations: {},
-  actions: {},
+  actions: {
+    async toOnline({ commit }, payload) {
+      console.log('online');
+      console.log(payload);
+      const res = await this.$axios.$get('/api/online', { person: payload });
+      return res;
+    },
+    async send({ commit }, payload) {
+      console.log('send');
+      console.log(payload);
+      const res = await this.$axios.$post('/api/send', payload);
+      return res;
+    },
+  },
   modules: {},
 });

+ 117 - 0
src/util/axios-wrapper.js

@@ -0,0 +1,117 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import _ from 'lodash';
+import Axios from 'axios';
+import { Util, Error } from 'naf-core';
+// import { Indicator } from 'mint-ui';
+import util from './user-util';
+
+const { trimData, isNullOrUndefined } = Util;
+const { ErrorCode } = Error;
+
+let currentRequests = 0;
+
+export default class AxiosWrapper {
+  constructor({ baseUrl = '', unwrap = true } = {}) {
+    this.baseUrl = baseUrl;
+    this.unwrap = unwrap;
+  }
+
+  // 替换uri中的参数变量
+  static merge(uri, query = {}) {
+    if (!uri.includes(':')) {
+      return uri;
+    }
+    const keys = [];
+    const regexp = /\/:([a-z0-9_]+)/gi;
+    let res;
+    // eslint-disable-next-line no-cond-assign
+    while ((res = regexp.exec(uri)) != null) {
+      keys.push(res[1]);
+    }
+    keys.forEach((key) => {
+      if (!isNullOrUndefined(query[key])) {
+        uri = uri.replace(`:${key}`, query[key]);
+      }
+    });
+    return uri;
+  }
+
+  $get(uri, query, options) {
+    return this.$request(uri, null, query, options);
+  }
+
+  $post(uri, data = {}, query, options) {
+    return this.$request(uri, data, query, options);
+  }
+  $delete(uri, data = {}, router, query, options = {}) {
+    options = { ...options, method: 'delete' };
+    return this.$request(uri, data, query, options, router);
+  }
+  async $request(uri, data, query, options) {
+    // TODO: 合并query和options
+    if (_.isObject(query) && _.isObject(options)) {
+      options = { ...options, params: query, method: 'get' };
+    } else if (_.isObject(query) && !query.params) {
+      options = { params: query };
+    } else if (_.isObject(query) && query.params) {
+      options = query;
+    }
+    if (!options) options = {};
+    if (options.params) options.params = trimData(options.params);
+    const url = AxiosWrapper.merge(uri, options.params);
+    currentRequests += 1;
+    // Indicator.open({
+    //   spinnerType: 'fading-circle',
+    // });
+
+    try {
+      const axios = Axios.create({
+        baseURL: this.baseUrl,
+      });
+      axios.defaults.headers.common.Authorization = util.token;
+      let res = await axios.request({
+        method: isNullOrUndefined(data) ? 'get' : 'post',
+        url,
+        data,
+        responseType: 'json',
+        ...options,
+      });
+      res = res.data;
+      const { errcode, errmsg, details } = res;
+      if (errcode) {
+        console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`);
+        return res;
+      }
+      // unwrap data
+      if (this.unwrap) {
+        res = _.omit(res, ['errmsg', 'details']);
+        const keys = Object.keys(res);
+        if (keys.length === 1 && keys.includes('data')) {
+          res = res.data;
+        }
+      }
+      return res;
+    } catch (err) {
+      let errmsg = '接口请求失败,请稍后重试';
+      if (err.response) {
+        const { status } = err.response;
+        if (status === 401) errmsg = '用户认证失败,请重新登录';
+        if (status === 403) errmsg = '当前用户不允许执行该操作';
+      }
+      console.error(
+        `[AxiosWrapper] 接口请求失败: ${err.config && err.config.url} - 
+        ${err.message}`
+      );
+      return { errcode: ErrorCode.SERVICE_FAULT, errmsg, details: err.message };
+    } finally {
+      /* eslint-disable */
+      currentRequests -= 1;
+      if (currentRequests <= 0) {
+        currentRequests = 0;
+        // Indicator.close();
+      }
+    }
+  }
+}

+ 69 - 0
src/util/user-util.js

@@ -0,0 +1,69 @@
+/* eslint-disable no-console */
+export default {
+  get user() {
+    const val = sessionStorage.getItem('user');
+    try {
+      if (val) return JSON.parse(val);
+    } catch (err) {
+      console.error(err);
+    }
+    return null;
+  },
+  set user(userinfo) {
+    sessionStorage.setItem('user', JSON.stringify(userinfo));
+  },
+  get token() {
+    return sessionStorage.getItem('token');
+  },
+  set token(token) {
+    sessionStorage.setItem('token', token);
+  },
+  get openid() {
+    return sessionStorage.getItem('openid');
+  },
+  set openid(openid) {
+    sessionStorage.setItem('openid', openid);
+  },
+  get isGuest() {
+    return !this.user || this.user.role === 'guest';
+  },
+  save({ userinfo, token }) {
+    sessionStorage.setItem('user', JSON.stringify(userinfo));
+    sessionStorage.setItem('token', token);
+  },
+
+  get corpInfo() {
+    const val = sessionStorage.getItem('corpInfo');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set corpInfo(corpInfo) {
+    sessionStorage.setItem('corpInfo', JSON.stringify(corpInfo));
+  },
+  saveCorpInfo(corpInfo) {
+    sessionStorage.setItem('corpInfo', JSON.stringify(corpInfo));
+  },
+
+  get unit() {
+    const val = sessionStorage.getItem('unit');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set unit(unitList) {
+    sessionStorage.setItem('unit', JSON.stringify(unitList));
+  },
+  saveUnit(unitList) {
+    sessionStorage.setItem('unit', JSON.stringify(unitList));
+  },
+  get userInfo() {
+    const val = sessionStorage.getItem('userInfo');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set userInfo(userInfo) {
+    sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
+  },
+  saveUserInfo(userInfo) {
+    sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
+  },
+};

+ 0 - 5
src/views/About.vue

@@ -1,5 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>

+ 46 - 9
src/views/Home.vue

@@ -1,18 +1,55 @@
 <template>
-  <div class="home">
-    <img alt="Vue logo" src="../assets/logo.png" />
-    <HelloWorld msg="Welcome to Your Vue.js App" />
+  <div id="Home">
+    <template name="轮播">
+      <el-carousel :interval="4000" height="200px" arrow="always">
+        <el-carousel-item v-for="item in 3" :key="item">
+          <el-image style="width: 100%; height: 100%" :src="url" fit="fit"></el-image>
+        </el-carousel-item>
+      </el-carousel>
+    </template>
+    <template name="微信用户">
+      <el-row type="flex" :gutter="10" style="margin-bottom: 10px">
+        <el-col :span="5">
+          <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" style="width: 70px; height: 70px"></el-avatar>
+        </el-col>
+        <el-col :span="19">
+          <el-col :span="24" style="margin-top: 10px">凪稀冥</el-col>
+        </el-col>
+      </el-row>
+    </template>
+    <template name="菜单">
+      <van-grid :column-num="3">
+        <van-grid-item icon="photo-o" text="购买" to="/shop" />
+        <van-grid-item icon="photo-o" text="我的订单" />
+        <van-grid-item icon="photo-o" text="地址管理" />
+      </van-grid>
+    </template>
   </div>
 </template>
 
 <script>
-// @ is an alias to /src
-import HelloWorld from "@/components/HelloWorld.vue";
-
+import { mapState, createNamespacedHelpers } from 'vuex';
 export default {
-  name: "Home",
-  components: {
-    HelloWorld,
+  name: 'Home',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
   },
 };
 </script>
+
+<style lang="less" scoped></style>

+ 37 - 0
src/views/login.vue

@@ -0,0 +1,37 @@
+<template>
+  <div id="login">
+    <p>web-view将code传过来,由这边请求处理code换取信息并存储</p>
+    <p>支付时,需要将数据准备好,跳转回小程序中支付,然后再回来</p>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'login',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    code() {
+      return this.$route.query.code;
+    },
+    route() {
+      return this.$route.query.route;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 107 - 0
src/views/shop/index.vue

@@ -0,0 +1,107 @@
+<template>
+  <div id="index">
+    <el-row type="flex" :gutter="10" style="height: 85vh">
+      <el-col :span="6">
+        <van-sidebar v-model="tag" @change="changeTab">
+          <van-sidebar-item v-for="(tag, ti) in tagList" :key="`tag-${ti}`" :title="tag.title" />
+        </van-sidebar>
+      </el-col>
+      <el-col :span="19" style="overflow-y: auto" id="view">
+        <template v-for="(gt, gti) in list" name="标签分割线">
+          <el-divider :key="`good-tag-${gti}`" content-position="left" :id="gt.key">{{ gt.title }}</el-divider>
+          <template name="商品">
+            <van-card v-for="(g, gi) in gt.goods" :key="`good-${gti}-${gi}`" :price="g.money" :desc="g.desc" :title="g.name" :thumb="g.images[0].url">
+              <template name="标签自定义" #tags>
+                <div style="margin: 3px 0" class="van-multi-ellipsis--l3">
+                  <el-tag size="mini" v-for="(t, ti) in g.tags" :key="`g-t-${gti}-${gi}-${ti}`" style="margin-right: 2px" plain>{{ t }}</el-tag>
+                </div>
+              </template>
+              <template name="按钮自定义区域" #footer>
+                <div style="text-align: right">
+                  <el-button icon="el-icon-minus" size="mini" circle></el-button>
+                  1
+                  <el-button icon="el-icon-plus" size="mini" circle></el-button>
+                </div>
+              </template>
+            </van-card>
+          </template>
+        </template>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      tag: 0,
+      list: [],
+      tagList: [],
+    };
+  },
+  created() {
+    this.imitateGoodsList();
+  },
+  methods: {
+    changeTab(i) {
+      console.log(i);
+      const stag = this.tagList[i];
+      const totag = document.getElementById(stag.key);
+      document.getElementById('view').scrollTop = totag.offsetTop - 30;
+      console.log(totag.offsetTop);
+    },
+    // 模拟标签
+    imitateTagList() {
+      return [
+        { title: '折扣', key: 'discount' },
+        { title: '推荐', key: 'recommend' },
+        { title: '酒水', key: 'drink' },
+        { title: '零食', key: 'snack' },
+      ];
+    },
+    // 模拟货物
+    imitateGoodsList() {
+      const tagList = this.imitateTagList();
+      const goodsList = tagList.map((i) => {
+        let index = 1;
+        const { title, key } = i;
+        const goods = [];
+        while (index <= 10) {
+          goods.push({
+            name: `${title}商品-${index}`,
+            id: `${key}-${index}`,
+            money: Math.ceil(Math.random() * 10),
+            images: [{ url: 'https://img.yzcdn.cn/vant/cat.jpeg' }],
+            tags: ['1人份', '220ml', '微甜', '220ml', '微甜', '220ml', '微甜', '220ml', '微甜', '220ml', '微甜', '220ml', '微甜', '220ml'],
+          });
+          index++;
+        }
+        i.goods = goods;
+        return i;
+      });
+      this.$set(this, `list`, goodsList);
+      this.$set(this, `tagList`, tagList);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.el-col {
+  float: none;
+}
+</style>

+ 42 - 0
src/views/test/home.vue

@@ -0,0 +1,42 @@
+<template>
+  <div id="Home">
+    <p>Home</p>
+    <el-button type="primary" @click="channel()">连接</el-button>
+  </div>
+</template>
+
+<script>
+import { mapState, mapActions, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'Home',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  mounted() {},
+  methods: {
+    ...mapActions(['']),
+    channel() {
+      this.$stomp({
+        [`/exchange/test/123456`]: this.onMessage,
+      });
+    },
+    onMessage(message) {
+      console.log('receive a message: ', message.body);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 49 - 0
src/views/test/p2.vue

@@ -0,0 +1,49 @@
+<template>
+  <div id="p2">
+    <p>p2</p>
+    <el-button type="primary" @click="login()">连接</el-button>
+    <p v-for="(i, index) in info" :key="`info-${index}`">{{ i }}</p>
+  </div>
+</template>
+
+<script>
+import { mapState, mapActions, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'p2',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      info: [],
+    };
+  },
+  created() {},
+  methods: {
+    ...mapActions(['toOnline', 'send']),
+    async login() {
+      const res = await this.toOnline('p3');
+      console.log(res);
+    },
+    channel() {
+      this.$stomp({
+        [`/exchange/test/p2`]: this.onMessage,
+      });
+    },
+    onMessage(message) {
+      console.log('receive a message: ', message.body);
+      this.info.push(message.body);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 49 - 0
src/views/test/p3.vue

@@ -0,0 +1,49 @@
+<template>
+  <div id="p3">
+    <p>p3</p>
+    <el-button type="primary" @click="login()">连接</el-button>
+    <p v-for="(i, index) in info" :key="`info-${index}`">{{ i }}</p>
+  </div>
+</template>
+
+<script>
+import { mapState, mapActions, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'p3',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      info: [],
+    };
+  },
+  created() {},
+  methods: {
+    ...mapActions(['toOnline', 'send']),
+    async login() {
+      const res = await this.toOnline('p3');
+      console.log(res);
+    },
+    channel() {
+      this.$stomp({
+        [`/exchange/test/p3`]: this.onMessage,
+      });
+    },
+    onMessage(message) {
+      console.log('receive a message: ', message.body);
+      this.info.push(message.body);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 52 - 0
vue.config.js

@@ -0,0 +1,52 @@
+const path = require('path');
+module.exports = {
+  publicPath: `/${process.env.VUE_APP_ROUTER}`,
+  // 打包文件
+  outputDir: 'platlive',
+  productionSourceMap: false,
+  configureWebpack: (config) => {
+    Object.assign(config, {
+      // 开发生产共同配置
+      resolve: {
+        alias: {
+          '@': path.resolve(__dirname, './src'),
+          '@c': path.resolve(__dirname, './src/components'),
+          '@a': path.resolve(__dirname, './src/assets'),
+        },
+      },
+    });
+  },
+  devServer: {
+    port: '11110',
+    //api地址前缀
+    proxy: {
+      '/weixin': {
+        target: 'http://smart.cc-lotus.info',
+        changeOrigin: true,
+        ws: false,
+      },
+      '/files': {
+        target: 'http://broadcast.waityou24.cn',
+      },
+      '/api/question': {
+        target: 'http://broadcast.waityou24.cn', //http://192.168.1.19:9101
+        changeOrigin: true,
+        ws: false,
+      },
+      '/api': {
+        target: 'http://192.168.1.19:11111', //http://192.168.1.19:9101
+        changeOrigin: true,
+        ws: false,
+      },
+      '/site': {
+        target: 'http://broadcast.waityou24.cn', //http://broadcast.waityou24.cn
+        changeOrigin: true,
+        ws: false,
+      },
+      '/ws': {
+        target: 'http://192.168.1.19:15674',
+        ws: true,
+      },
+    },
+  },
+};