lrf402788946 5 years ago
parent
commit
b61b852a25

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+VUE_APP_AXIOS_BASE_URL = ''
+VUE_APP_ROOT_URL=/admin/
+VUE_APP_MODULE='center'

+ 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: 250,
+      },
+    ],
+    '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
 .env.*.local

+ 8 - 0
.prettierrc

@@ -0,0 +1,8 @@
+{
+  "trailingComma": "es5",
+  "tabWidth": 2,
+  "printWidth": 180,
+  "semi": true,
+  "singleQuote": true,
+  "bracketSpacing": true
+}

+ 7 - 1
package.json

@@ -8,10 +8,16 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "axios": "^0.19.1",
     "core-js": "^3.4.4",
+    "element-ui": "^2.13.0",
+    "lodash": "^4.17.15",
+    "naf-core": "^0.1.2",
     "vue": "^2.6.10",
+    "vue-meta": "^2.3.1",
     "vue-router": "^3.1.3",
-    "vuex": "^3.1.2"
+    "vuex": "^3.1.2",
+    "wangeditor": "^3.1.1"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "^4.1.0",

+ 17 - 22
src/App.vue

@@ -1,32 +1,27 @@
 <template>
   <div id="app">
-    <div id="nav">
-      <router-link to="/">Home</router-link> |
-      <router-link to="/about">About</router-link>
-    </div>
     <router-view />
   </div>
 </template>
+<script>
+export default {
+  name: 'App',
+  components: {},
+};
+</script>
 
 <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;
-    }
+html {
+  overflow: hidden;
+  body {
+    margin: 0;
+    padding: 0;
+  }
+  margin: 0;
+  padding: 0;
+  p {
+    margin: 0;
+    padding: 0;
   }
 }
 </style>

+ 10 - 6
src/main.js

@@ -1,12 +1,16 @@
-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/meta';
+import '@/plugins/axios';
+import '@/plugins/check-res';
+import '@/plugins/element';
 
 Vue.config.productionTip = false;
 
 new Vue({
   router,
   store,
-  render: h => h(App)
-}).$mount("#app");
+  render: h => h(App),
+}).$mount('#app');

+ 1 - 0
src/plugins/README.md

@@ -0,0 +1 @@
+### 框架使用的 vue plugin

+ 22 - 0
src/plugins/axios.js

@@ -0,0 +1,22 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import Vue from 'vue';
+import AxiosWrapper from '@frame/utils/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, unwrap: true });

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

@@ -0,0 +1,40 @@
+/* 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);
+          // Message({ message: _okText, type: 'success', duration: 60000 });
+        }
+        return true;
+      }
+      if (_.isFunction(_errText)) {
+        return _errText();
+      }
+      Message.error(_errText || errmsg);
+      // Message({ message: _errText || errmsg, duration: 60000 });
+      return false;
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 5 - 0
src/plugins/element.js

@@ -0,0 +1,5 @@
+import Vue from 'vue';
+import Element from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+// 注册element-ui组件
+Vue.use(Element);

+ 4 - 0
src/plugins/meta.js

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

+ 26 - 0
src/plugins/naf-dict.js

@@ -0,0 +1,26 @@
+/**
+ * 字典数据处理插件
+ */
+
+import Vue from 'vue';
+import _ from 'lodash';
+import assert from 'assert';
+
+const Plugin = {
+  install(vue, options) {
+    // 4. 添加实例方法
+    vue.prototype.$dict = function(codeType, code) {
+      assert(_.isString(codeType));
+      const state = this.$store.state.naf.dict;
+      if (!state) {
+        throw new Error("can't find store for naf dict");
+      }
+      if (_.isString(code)) {
+        return (state.codes[codeType] && state.codes[codeType][code]) || code;
+      } else {
+        return state.items[codeType];
+      }
+    };
+  },
+};
+Vue.use(Plugin);

+ 6 - 0
src/plugins/nut-ui.js

@@ -0,0 +1,6 @@
+import Vue from 'vue';
+import { Toast } from '@nutui/nutui';
+// import '@nutui/nutui/dist/nutui.css';
+
+// 注册nut-ui组件
+Toast.install(Vue); // 按需加载

+ 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);
+};

+ 20 - 16
src/router/index.js

@@ -1,28 +1,32 @@
-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 routes = [
   {
-    path: "/",
-    name: "home",
-    component: Home
+    path: '/',
+    name: 'frame',
+    component: () => import('@/views/index.vue'),
+    children: [
+      {
+        path: '/list',
+        name: 'test_list',
+        component: () => import('@/views/test/list.vue'),
+      },
+      {
+        path: '/detail',
+        name: 'test_detail',
+        component: () => import('@/views/test/detail.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")
-  }
 ];
 
 const router = new VueRouter({
-  routes
+  mode: 'history',
+  base: process.env.NODE_ENV === 'development' ? '' : process.env.VUE_APP_ROOT_URL + 'center',
+  routes,
 });
 
 export default router;

+ 3 - 3
src/store/index.js

@@ -1,5 +1,5 @@
-import Vue from "vue";
-import Vuex from "vuex";
+import Vue from 'vue';
+import Vuex from 'vuex';
 
 Vue.use(Vuex);
 
@@ -7,5 +7,5 @@ export default new Vuex.Store({
   state: {},
   mutations: {},
   actions: {},
-  modules: {}
+  modules: {},
 });

+ 0 - 5
src/views/About.vue

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

+ 0 - 18
src/views/Home.vue

@@ -1,18 +0,0 @@
-<template>
-  <div class="home">
-    <img alt="Vue logo" src="../assets/logo.png" />
-    <HelloWorld msg="Welcome to Your Vue.js App" />
-  </div>
-</template>
-
-<script>
-// @ is an alias to /src
-import HelloWorld from "@/components/HelloWorld.vue";
-
-export default {
-  name: "home",
-  components: {
-    HelloWorld
-  }
-};
-</script>

+ 60 - 0
src/views/index.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="admin-index">
+    <div class="app-wrapper">
+      <admin-menu class="sidebar-container"></admin-menu>
+      <div class="main-container">
+        <admin-bar></admin-bar>
+        <fw-admin class="display"></fw-admin>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import adminMenu from '@frame/layout/admin/admin-menu.vue';
+import adminBar from '@frame/layout/admin/navBar.vue';
+import fwAdmin from '@frame/layout/admin/fw-admin.vue';
+import { devMenu } from '@frame/config/menu-config';
+export default {
+  name: 'admin-index',
+  metaInfo: { title: 'test' },
+  props: {},
+  components: {
+    fwAdmin,
+    adminMenu,
+    adminBar,
+    // breadcrumb,
+  },
+  data: () => ({
+    devMenu,
+  }),
+  created() {},
+  computed: {},
+  methods: {},
+};
+</script>
+
+<style lang="less" scoped>
+.display {
+  height: ~'calc(100% - 30px)';
+  padding: 1rem;
+}
+
+.app-wrapper {
+  position: relative;
+  height: 100%;
+  width: 100%;
+  &:after {
+    content: '';
+    display: table;
+    clear: both;
+  }
+}
+
+.main-container {
+  min-height: 100vh;
+  transition: margin-left 0.28s;
+  margin-left: 12rem;
+  background-color: #f0f2f5;
+}
+</style>

+ 59 - 0
src/views/test/detail.vue

@@ -0,0 +1,59 @@
+<template>
+  <div id="detail">
+    <detail-frame title="测试详情页" returns="/list">
+      <data-form :fields="fields" :rules="rules" @save="handleSave" :isNew="isNew"></data-form>
+    </detail-frame>
+  </div>
+</template>
+
+<script>
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataForm from '@frame/components/form';
+export default {
+  metaInfo: { title: '测试详情页' },
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+    dataForm,
+  },
+  data: () => ({
+    fields: [
+      { label: '姓名', required: true, model: 'name' },
+      { label: '性别', required: true, model: 'gender', type: `select` },
+      { label: '年龄', required: true, model: 'birthday' },
+      { label: '地址', required: true, model: 'address' },
+      { label: '电话', required: true, model: 'tel', options: { maxlength: 11, minlength: 11 } },
+      { label: '紧急联系人', required: true, model: 'urgentname' },
+      { label: '紧急联系人电话', required: true, model: 'urgenttel', options: { maxlength: 11, minlength: 11 } },
+      { label: '个人简介', model: 'content', custom: true },
+    ],
+    rules: {
+      name: [{ required: true, message: '请输入姓名' }],
+      sex: [{ required: true, message: '请选择性别' }],
+      birthday: [{ required: true, message: '请填写年龄' }],
+      address: [{ required: true, message: '请输入地址' }],
+      tel: [
+        { required: true, message: '请输入电话' },
+        { min: 11, max: 11, message: '请输入11位电话号码', trigger: 'blur' },
+      ],
+      aid: [{ required: true, message: '请输入紧急联系人' }],
+      aid_tel: [
+        { required: true, message: '请输入紧急联系人电话' },
+        { min: 11, max: 11, message: '请输入11位电话号码', trigger: 'blur' },
+      ],
+    },
+  }),
+  created() {},
+  computed: {
+    isNew() {
+      return this.$route.query.id ? true : false;
+    },
+  },
+  methods: {
+    async handleSave({ isNew, data }) {},
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 69 - 0
src/views/test/list.vue

@@ -0,0 +1,69 @@
+<template>
+  <div id="list">
+    <list-frame title="测试列表页" @query="search" :filter="filFields">
+      <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit"></data-table>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/layout/admin/data-table';
+export default {
+  metaInfo: { title: '测试列表页' },
+  name: 'list',
+  props: {},
+  components: {
+    listFrame,
+    dataTable,
+  },
+  data: () => ({
+    opera: [
+      {
+        label: '编辑',
+        icon: 'el-icon-edit',
+        method: 'edit',
+      },
+    ],
+    fields: [
+      { label: '姓名', prop: 'name' },
+      {
+        label: '性别',
+        prop: 'gender',
+        format: item => {
+          return item === '1' ? '男' : '女';
+        },
+      },
+    ],
+    list: [
+      { name: 'test1', gender: '1' },
+      { name: 'test2', gender: '0' },
+    ],
+    filFields: [
+      { label: '姓名', model: 'name' },
+      {
+        label: '性别',
+        model: 'gender',
+        type: 'select',
+        list: [
+          { label: '男', value: 1 },
+          { label: '女', value: 0 },
+        ],
+      },
+    ],
+  }),
+  created() {},
+  computed: {},
+  methods: {
+    search({ skip = 0, limit = 15, ...info } = {}) {
+      console.log(`in search`);
+    },
+    toEdit(data) {
+      console.log(`in toEdit`);
+      console.log(data);
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 58 - 1
vue.config.js

@@ -1,3 +1,60 @@
+const path = require('path');
+const frame = path.resolve(__dirname, '../frame');
 module.exports = {
-  lintOnSave: false
+  publicPath: process.env.NODE_ENV === 'development' ? '/' : process.env.VUE_APP_ROOT_URL + 'center',
+  configureWebpack: {
+    // externals: {
+    //   'element-ui': 'Element',
+    //   vue: 'Vue',
+    // },
+    // 开发生产共同配置
+    resolve: {
+      alias: {
+        '@': path.resolve(__dirname, './src'),
+        '@c': path.resolve(__dirname, './src/components'),
+        '@a': path.resolve(__dirname, './src/assets'),
+        '@frame': frame,
+      },
+    },
+  },
+  devServer: {
+    port: '8001',
+    //api地址前缀
+    proxy: {
+      '/api': {
+        target: 'http://smart.cc-lotus.info',
+        changeOrigin: true,
+        ws: true,
+      },
+      '/files': {
+        target: 'http://smart.cc-lotus.info',
+        changeOrigin: true,
+        ws: true,
+      },
+      '/ws': {
+        target: 'http://smart.cc-lotus.info',
+        ws: true,
+      },
+      '/weixin': {
+        target: 'http://smart.cc-lotus.info',
+        changeOrigin: true,
+        ws: true,
+      },
+      '/admin/center': {
+        target: 'http://localhost:8001',
+      },
+      '/admin/director': {
+        target: 'http://localhost:8002',
+      },
+      '/admin/teacher': {
+        target: 'http://localhost:8003',
+      },
+      '/admin/student': {
+        target: 'http://localhost:8004',
+      },
+      '/admin/school': {
+        target: 'http://localhost:8005',
+      },
+    },
+  },
 };