lrf 3 lat temu
commit
faac882c63

+ 2 - 0
.env

@@ -0,0 +1,2 @@
+VUE_APP_BASE_URL = "dbInit"
+

+ 36 - 0
.eslintrc.js

@@ -0,0 +1,36 @@
+// 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,
+      },
+    ],
+    'vue/no-v-for-template-key': 'off',
+    'vue/no-template-key': 'off',
+    'vue/no-v-model-argument': 'off',
+  },
+  parserOptions: {
+    parser: 'babel-eslint',
+  },
+};

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+.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 @@
+# db-init-web
+
+## 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/).

+ 3 - 0
babel.config.js

@@ -0,0 +1,3 @@
+module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset"],
+};

Plik diff jest za duży
+ 28211 - 0
package-lock.json


+ 57 - 0
package.json

@@ -0,0 +1,57 @@
+{
+  "name": "db-init-web",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "axios": "^0.24.0",
+    "core-js": "^3.6.5",
+    "element-plus": "^1.2.0-beta.1",
+    "lodash": "^4.17.21",
+    "naf-core": "^0.1.2",
+    "vue": "^3.0.0",
+    "vue-meta": "^2.4.0",
+    "vue-router": "^4.0.0-0",
+    "vuex": "^4.0.0-0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/cli-plugin-router": "~4.5.0",
+    "@vue/cli-plugin-vuex": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "@vue/compiler-sfc": "^3.0.0",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-prettier": "^3.3.1",
+    "eslint-plugin-vue": "^7.0.0",
+    "less": "^3.0.4",
+    "less-loader": "^5.0.0",
+    "prettier": "^2.2.1"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/vue3-essential",
+      "eslint:recommended",
+      "@vue/prettier"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

BIN
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!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">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
+    <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 -->
+  </body>
+</html>

+ 12 - 0
src/App.vue

@@ -0,0 +1,12 @@
+<template>
+  <div>
+    <router-view />
+  </div>
+</template>
+
+<style lang="less">
+body {
+  padding: 0;
+  margin: 0;
+}
+</style>

+ 97 - 0
src/api/project.js

@@ -0,0 +1,97 @@
+import { ref } from 'vue';
+import { ElMessage } from 'element-plus';
+
+// 一般是指项目路由的前缀部分
+const prefix = '/api/util/dbInit/';
+// 确定具体接口位置
+const target = 'project';
+// 拼装成全路由,如果需要再拼接,自己到方法里去拼
+const route = `${prefix}${target}`;
+/**
+ * api初始化函数
+ * @return {Object} 返回该api自带的变量与函数
+ * @property {Array} list 查询列表的数组结果
+ * @property {Number} total 查询列表符合条件的总数
+ */
+const init = function () {
+  let list = ref([]);
+  let total = ref(0);
+  // 下面5个接口是针对单表的常规接口,可以全写,但是服务端不一定全都开放使用
+  /**
+   * 多查,根据条件返回数据列表
+   * @param {Object} query 查询条件
+   * @property {Number/String} skip 起始索引,可以直接解构出来
+   * @property {Number/String} limit 查询显示数量,可以直接解构出来
+   * @property {Object} condition 其余的条件,需要知道key才可以单独解构出想要的
+   */
+  const query = async function (query) {
+    const res = await this.$axios.$get(`${route}`, query);
+    if (this.$checkRes(res)) {
+      list.value = res.data;
+      total.value = res.total;
+    }
+  };
+  /**
+   * 单查,根据id找到唯一数据,返回数据
+   * @param {String} id 数据id
+   * @return {Object} 查询到的数据
+   */
+  const fetch = async function (id) {
+    const res = await this.$axios.$get(`${route}/${id}`);
+    if (this.$checkRes(res)) {
+      return res.data;
+    }
+  };
+  /**
+   * 创建数据
+   * @param {Object} data 数据
+   * @return {Object} 创建的数据结果
+   */
+  const create = async function (data) {
+    const res = await this.$axios.$post(`${route}`, data);
+    if (this.$checkRes(res, '创建成功', '创建失败')) {
+      return res.data;
+    } else {
+      console.error(`${target}-error:`);
+      console.error(res.errmsg);
+    }
+  };
+  /**
+   * 修改数据
+   * @param {Object} params
+   * @returns
+   */
+  const update = async function ({ id, _id, ...data }) {
+    if (!id && !_id) {
+      ElMessage.error('缺少要修改的数据信息,无法修改');
+      return false;
+    }
+    const res = await this.$axios.$post(`${route}/${id}`, data);
+    if (this.$checkRes(res, '修改成功', '修改失败')) {
+      return res.data;
+    } else {
+      console.error(`${target}-error:`);
+      console.error(res.errmsg);
+    }
+  };
+  /**
+   * 删除数据
+   * @param {String} id 数据id
+   * @returns 是否成功
+   */
+  const destory = async function (id) {
+    if (!id) {
+      ElMessage.error('缺少要修改的数据信息,无法修改');
+      return false;
+    }
+    const res = await this.$axios.$delete(`${route}/${id}`);
+    if (this.$checkRes(res, '删除成功', '删除失败')) {
+      return true;
+    } else {
+      console.error(`${target}-error:`);
+      console.error(res.errmsg);
+    }
+  };
+  return { list, total, query, create, fetch, update, destory };
+};
+export default init;

+ 97 - 0
src/api/table.js

@@ -0,0 +1,97 @@
+import { ref } from 'vue';
+import { ElMessage } from 'element-plus';
+
+// 一般是指项目路由的前缀部分
+const prefix = '/api/util/dbInit/';
+// 确定具体接口位置
+const target = 'table';
+// 拼装成全路由,如果需要再拼接,自己到方法里去拼
+const route = `${prefix}${target}`;
+/**
+ * api初始化函数
+ * @return {Object} 返回该api自带的变量与函数
+ * @property {Array} list 查询列表的数组结果
+ * @property {Number} total 查询列表符合条件的总数
+ */
+const init = function () {
+  let list = ref([]);
+  let total = ref(0);
+  // 下面5个接口是针对单表的常规接口,可以全写,但是服务端不一定全都开放使用
+  /**
+   * 多查,根据条件返回数据列表
+   * @param {Object} query 查询条件
+   * @property {Number/String} skip 起始索引,可以直接解构出来
+   * @property {Number/String} limit 查询显示数量,可以直接解构出来
+   * @property {Object} condition 其余的条件,需要知道key才可以单独解构出想要的
+   */
+  const query = async function (query) {
+    const res = await this.$axios.$get(`${route}`, query);
+    if (this.$checkRes(res)) {
+      list.value = res.data;
+      total.value = res.total;
+    }
+  };
+  /**
+   * 单查,根据id找到唯一数据,返回数据
+   * @param {String} id 数据id
+   * @return {Object} 查询到的数据
+   */
+  const fetch = async function (id) {
+    const res = await this.$axios.$get(`${route}/${id}`);
+    if (this.$checkRes(res)) {
+      return res.data;
+    }
+  };
+  /**
+   * 创建数据
+   * @param {Object} data 数据
+   * @return {Object} 创建的数据结果
+   */
+  const create = async function (data) {
+    const res = await this.$axios.$post(`${route}`, data);
+    if (this.$checkRes(res, '创建成功', '创建失败')) {
+      return res.data;
+    } else {
+      console.error(`${target}-error:`);
+      console.error(res.errmsg);
+    }
+  };
+  /**
+   * 修改数据
+   * @param {Object} params
+   * @returns
+   */
+  const update = async function ({ id, _id, ...data }) {
+    if (!id && !_id) {
+      ElMessage.error('缺少要修改的数据信息,无法修改');
+      return false;
+    }
+    const res = await this.$axios.$post(`${route}/${id}`, data);
+    if (this.$checkRes(res, '修改成功', '修改失败')) {
+      return res.data;
+    } else {
+      console.error(`${target}-error:`);
+      console.error(res.errmsg);
+    }
+  };
+  /**
+   * 删除数据
+   * @param {String} id 数据id
+   * @returns 是否成功
+   */
+  const destory = async function (id) {
+    if (!id) {
+      ElMessage.error('缺少要修改的数据信息,无法修改');
+      return false;
+    }
+    const res = await this.$axios.$delete(`${route}/${id}`);
+    if (this.$checkRes(res, '删除成功', '删除失败')) {
+      return true;
+    } else {
+      console.error(`${target}-error:`);
+      console.error(res.errmsg);
+    }
+  };
+  return { list, total, query, create, fetch, update, destory };
+};
+export default init;

BIN
src/assets/logo.png


+ 130 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,130 @@
+<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>

+ 10 - 0
src/main.js

@@ -0,0 +1,10 @@
+import { createApp } from 'vue';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+const app = createApp(App);
+app.use(store).use(router);
+// 插件初始化函数,其实就是把自己用的一些东西放到这个文件里给app.use了
+import pluginsInit from './plugins/index';
+pluginsInit(app);
+app.mount('#app');

+ 15 - 0
src/plugins/axios.js

@@ -0,0 +1,15 @@
+import AxiosWrapper from '../utils/axios-wrapper';
+
+export default {
+  install: (app, options) => {
+    // 添加实例方法
+    app.config.globalProperties.$axios = new AxiosWrapper(options);
+    app.mixin({
+      created() {
+        if (this.$store && !this.$store.$axios) {
+          this.$store.$axios = this.$axios;
+        }
+      },
+    });
+  },
+};

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

@@ -0,0 +1,36 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable no-param-reassign */
+/* eslint-disable no-unused-vars */
+/* eslint-disable no-shadow */
+import _ from 'lodash';
+import { ElMessage } from 'element-plus';
+
+export default {
+  install(app, options) {
+    // 4. 添加实例方法
+    app.config.globalProperties.$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) {
+          ElMessage.success(_okText);
+          // ElMessage({ ElMessage: _okText, type: 'success', duration: 60000 });
+        }
+        return true;
+      }
+      if (_.isFunction(_errText)) {
+        return _errText();
+      }
+      ElMessage.error(_errText || errmsg);
+      // ElMessage({ ElMessage: _errText || errmsg, duration: 60000 });
+      return false;
+    };
+  },
+};

+ 10 - 0
src/plugins/index.js

@@ -0,0 +1,10 @@
+import 'element-plus/dist/index.css';
+import ElementPlus from 'element-plus';
+import checkRes from './check-res';
+import axios from './axios';
+
+export default function (app) {
+  app.use(ElementPlus);
+  app.use(checkRes);
+  app.use(axios);
+}

+ 16 - 0
src/router/index.js

@@ -0,0 +1,16 @@
+import { createRouter, createWebHistory } from 'vue-router';
+
+const routes = [
+  {
+    path: '/',
+    name: 'index',
+    component: () => import(/* webpackChunkName: "index" */ '../views/index.vue'),
+  },
+];
+
+const router = createRouter({
+  history: createWebHistory(process.env.VUE_APP_BASE_URL),
+  routes,
+});
+
+export default router;

+ 8 - 0
src/store/index.js

@@ -0,0 +1,8 @@
+import { createStore } from 'vuex';
+
+export default createStore({
+  state: {},
+  mutations: {},
+  actions: {},
+  modules: {},
+});

+ 128 - 0
src/utils/axios-wrapper.js

@@ -0,0 +1,128 @@
+/* eslint-disable require-atomic-updates */
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import _ from 'lodash';
+import Axios from 'axios';
+import { Util, Error } from 'naf-core';
+
+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);
+  }
+
+  $delete(uri, query, options = {}) {
+    return this.$request(uri, null, query, { ...options, method: 'delete' });
+  }
+
+  $post(uri, data = {}, query, options) {
+    return this.$request(uri, data, query, options);
+  }
+
+  async $request(uri, data, query, options) {
+    // 过滤key:''的情况
+    query = _.pickBy(query, (val) => val !== '');
+    if (!uri) console.error('uri不能为空');
+    if (_.isObject(query) && _.isObject(options)) {
+      const params = query.params ? query.params : query;
+      options = { ...options, params };
+    } 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;
+    // const loadingInstance = Loading.service({ spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.5)' });
+
+    try {
+      const axios = Axios.create({
+        baseURL: this.baseUrl,
+      });
+      const user = localStorage.getItem('user');
+      if (user) {
+        axios.defaults.headers.common.Authorization = encodeURI(user);
+      }
+      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, ['errcode', '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, data = {} } = err.response;
+        if (status === 401) errmsg = '用户认证失败,请重新登录';
+        if (status === 403) errmsg = '当前用户不允许执行该操作';
+        if (status === 400 && data.errcode) {
+          const { errcode, errmsg, details } = data;
+          console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`);
+          return data;
+        }
+        if (data && data.error) {
+          const { status, error, message } = data;
+          console.warn(`[${uri}] fail: ${status}: ${error}-${message}`);
+          return { errcode: status || ErrorCode.SERVICE_FAULT, errmsg: error, details: message };
+        }
+      }
+      console.error(`[AxiosWrapper] 接口请求失败: ${err.config && err.config.url} - ${err.message}`);
+      return { errcode: ErrorCode.SERVICE_FAULT, errmsg, details: err.message };
+    } finally {
+      currentRequests -= 1;
+      if (currentRequests <= 0) {
+        currentRequests = 0;
+        // loadingInstance.close();
+      }
+    }
+  }
+}

+ 46 - 0
src/utils/user-util.js

@@ -0,0 +1,46 @@
+/* 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));
+    if (this.unit) {
+      this.lastUnit = this.unit;
+    }
+  },
+  get token() {
+    return sessionStorage.getItem('token') || '';
+  },
+  set token(token) {
+    sessionStorage.setItem('token', token);
+  },
+  get isGuest() {
+    return !this.user || this.user.role === 'guest';
+  },
+  get unit() {
+    if (!this.user || this.user.iss !== 'platform') return undefined;
+    const unit = this.user.sub.split('@', 2)[1] || 'master';
+    return unit;
+  },
+  get platform() {
+    const unit = this.unit || this.lastUnit;
+    return unit === 'master' ? 'master' : 'school';
+  },
+  set lastUnit(value) {
+    localStorage.setItem('unit', value);
+  },
+  get lastUnit() {
+    return localStorage.getItem('unit');
+  },
+  save({ userinfo, token }) {
+    this.user = userinfo;
+    this.token = token;
+  },
+};

+ 41 - 0
src/views/index.vue

@@ -0,0 +1,41 @@
+<template>
+  <div id="index">
+    <el-container style="height: 100vh; background: #ccc">
+      <el-aside width="200px">
+        <project v-model:value="project" />
+      </el-aside>
+      <el-main>
+        <el-header>
+          <el-alert type="success" effect="dark" center :closable="false">
+            <el-row style="height: 60px">
+              <el-col :span="24" style="font-size: 20px">当前项目为:</el-col>
+              <el-col :span="24" style="font-size: 20px; color: blue; font-weight: 700; padding-left: 50px">{{ project.name }}</el-col>
+            </el-row>
+          </el-alert>
+        </el-header>
+        <table-component v-model:project="project" />
+      </el-main>
+    </el-container>
+  </div>
+</template>
+
+<script>
+import project from './project/project.vue';
+import tableComponent from './project/table.vue';
+import { defineComponent, ref, reactive } from 'vue';
+export default defineComponent({
+  name: 'index',
+  components: { project, tableComponent },
+  props: {},
+  data() {
+    return {
+      project: {},
+    };
+  },
+  setup(props, context) {
+    return {};
+  },
+});
+</script>
+
+<style lang="less" scoped></style>

+ 138 - 0
src/views/project/project.vue

@@ -0,0 +1,138 @@
+<template>
+  <div id="index">
+    <el-row justify="center" style="height: 10vh; padding-top: 3vh">
+      <el-col :span="12">
+        <el-button type="primary" @click="toAdd()">添加项目</el-button>
+      </el-col>
+    </el-row>
+    <el-row justify="space-around" style="height: 5vh">
+      <el-col :span="10">
+        <el-button type="primary" size="small" @click="toUpdate" :disabled="!radioSelect">修改项目</el-button>
+      </el-col>
+      <el-col :span="10">
+        <el-button type="danger" size="small" @click="toDelete" :disabled="!radioSelect">删除项目</el-button>
+      </el-col>
+    </el-row>
+    <el-menu style="height: 85vh; padding-left: 20px" active-text-color="#ffd04b" background-color="#545c64" text-color="#fff" @select="selectProject">
+      <el-scrollbar height="85vh">
+        <el-radio-group v-model="radioSelect">
+          <template v-for="(i, index) in list" :key="`key-${index}`">
+            <el-row align="middle">
+              <el-col :span="3">
+                <el-radio :label="i.id">&nbsp;</el-radio>
+              </el-col>
+              <el-col :span="21">
+                <el-menu-item :index="i._id"> {{ i.name }} </el-menu-item>
+              </el-col>
+            </el-row>
+          </template>
+        </el-radio-group>
+      </el-scrollbar>
+    </el-menu>
+    <el-dialog v-model="dialog" title="项目管理" center destroy-on-close @closed="toClear()">
+      <el-form ref="form" :model="form" label-position="left" label-width="120px">
+        <el-form-item label="项目名" prop="name" :required="true">
+          <el-input v-model="form.name"></el-input>
+        </el-form-item>
+        <el-form-item label="描述" prop="desc">
+          <el-input type="textarea" :autosize="{ minRows: 4, maxRows: 4 }" v-model="form.desc"></el-input>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input type="textarea" :autosize="{ minRows: 4, maxRows: 4 }" v-model="form.remark"></el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="submitForm()">提交</el-button>
+          <el-button @click="resetForm()">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import api from '@/api/project';
+const _ = require('lodash');
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'project',
+  components: {},
+  props: {
+    value: Object,
+  },
+  emits: ['update:value'],
+  /**
+   * 接口及列表变量已经存到各个api中
+   * 如果需要重命名,直接对 api 进行解构重命名即可
+   */
+  setup(props, context) {
+    return api();
+  },
+  data() {
+    return {
+      form: {},
+      dialog: false,
+      radioSelect: undefined,
+    };
+  },
+  async beforeMount() {
+    await this.query();
+  },
+  // methods主要写的是本组件内的业务逻辑,接口部分的请求与处理已经由setup来暴露出来了.直接用就可以
+  methods: {
+    selectProject(index) {
+      const is_select = this.list.find((f) => f._id === index);
+      if (is_select) {
+        this.$emit('update:value', _.pick(is_select, ['name', 'id']));
+      }
+    },
+    toAdd() {
+      this.dialog = true;
+    },
+    async toUpdate() {
+      if (!this.radioSelect) {
+        this.$message.error('没有选择要修改的项目');
+        return false;
+      }
+      const id = _.cloneDeep(this.radioSelect);
+      const data = await this.fetch(id);
+      this.form = data;
+      this.dialog = true;
+    },
+    async toDelete() {
+      if (!this.radioSelect) {
+        this.$message.error('没有选择要删除的项目');
+        return false;
+      }
+      const id = _.cloneDeep(this.radioSelect);
+      const data = await this.destory(id);
+      if (data) await this.query();
+    },
+    toClear() {
+      this.form = {};
+      this.radioSelect = undefined;
+    },
+    submitForm() {
+      this.$refs.form.validate(async (valid) => {
+        if (valid) {
+          let res;
+          const data = _.cloneDeep(this.form);
+          if (!_.get(data, 'id')) res = await this.create(data);
+          else res = await this.update(data);
+          if (res) {
+            this.dialog = false;
+            await this.query();
+          }
+        } else {
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+    resetForm() {
+      this.$refs.form.resetFields();
+    },
+  },
+});
+</script>
+
+<style lang="less" scoped></style>

+ 39 - 0
src/views/project/table.vue

@@ -0,0 +1,39 @@
+<template>
+  <div id="table" style="padding: 20px"></div>
+</template>
+
+<script>
+import table from '@/api/table';
+const _ = require('lodash');
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'tables',
+  components: {},
+  props: {
+    project: Object,
+  },
+  setup(props, context) {
+    return table();
+  },
+  data() {
+    return {
+      table: undefined,
+    };
+  },
+  methods: {
+    async toSearch({ skip = 0, limit = 10, ...condition } = {}) {
+      await this.query({ skip, limit, ...condition });
+    },
+  },
+  watch: {
+    project: {
+      handler(val, oval) {
+        if (_.get(val, 'id')) this.toSearch({ skip: 0, limit: 10, project: _.get(val, 'id') });
+      },
+      deep: true,
+    },
+  },
+});
+</script>
+
+<style lang="less" scoped></style>

+ 26 - 0
vue.config.js

@@ -0,0 +1,26 @@
+const path = require('path');
+module.exports = {
+  publicPath: '/',
+  outputDir: 'process.env.VUE_APP_BASE_URL',
+  productionSourceMap: false,
+  configureWebpack: {
+    // 开发生产共同配置
+    resolve: {
+      alias: {
+        '@': path.resolve(__dirname, './src'),
+        '@c': path.resolve(__dirname, './src/components'),
+        '@a': path.resolve(__dirname, './src/assets'),
+      },
+    },
+  },
+  devServer: {
+    port: '6901',
+    //api地址前缀
+    proxy: {
+      '/api/util': {
+        target: 'http://localhost:6900',
+        changeOrigin: true,
+      },
+    },
+  },
+};