zs 9 maanden geleden
commit
0cd68fd97d
50 gewijzigde bestanden met toevoegingen van 2484 en 0 verwijderingen
  1. 1 0
      .env
  2. 33 0
      .eslintrc.js
  3. 25 0
      .gitignore
  4. 0 0
      README.md
  5. 3 0
      babel.config.js
  6. 65 0
      package.json
  7. BIN
      public/favicon.ico
  8. 47 0
      public/index.html
  9. 7 0
      src/App.vue
  10. BIN
      src/assets/logo.png
  11. 74 0
      src/components/Breadcrumb/index.vue
  12. 46 0
      src/components/frame/c-button.vue
  13. 81 0
      src/components/frame/c-code.vue
  14. 50 0
      src/components/frame/c-dialog.vue
  15. 88 0
      src/components/frame/c-editor.vue
  16. 218 0
      src/components/frame/c-form.vue
  17. 53 0
      src/components/frame/c-page.vue
  18. 173 0
      src/components/frame/c-search.vue
  19. 294 0
      src/components/frame/c-table.vue
  20. 97 0
      src/components/frame/c-upload.vue
  21. 138 0
      src/layout/site.js
  22. 8 0
      src/main.js
  23. 12 0
      src/router/index.js
  24. 52 0
      src/store/admin.js
  25. 42 0
      src/store/app/appapk.js
  26. 42 0
      src/store/app/appbanner.js
  27. 42 0
      src/store/app/appbasic.js
  28. 42 0
      src/store/app/discuss.js
  29. 42 0
      src/store/app/hotlink.js
  30. 42 0
      src/store/app/moneylog.js
  31. 42 0
      src/store/app/scenedata.js
  32. 42 0
      src/store/app/scenetype.js
  33. 42 0
      src/store/app/videos.js
  34. 42 0
      src/store/app/vipsetting.js
  35. 11 0
      src/store/index.js
  36. 42 0
      src/store/program.js
  37. 42 0
      src/store/system/dictdata.js
  38. 42 0
      src/store/system/dicttype.js
  39. 52 0
      src/store/user.js
  40. 8 0
      src/store/user/mutations.js
  41. 2 0
      src/store/user/state.js
  42. 42 0
      src/store/web/banner.js
  43. 42 0
      src/store/web/cases.js
  44. 42 0
      src/store/web/company.js
  45. 42 0
      src/store/web/leavemess.js
  46. 42 0
      src/store/web/news.js
  47. 43 0
      src/util/htmlToPdf.js
  48. 7 0
      src/util/simfang-normal.js
  49. 36 0
      src/views/index.vue
  50. 4 0
      vue.config.js

+ 1 - 0
.env

@@ -0,0 +1 @@
+VUE_APP_AXIOS_BASE_URL = ''

+ 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',
+  },
+};

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+.DS_Store
+node_modules
+package-lock.json
+/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?
+*.history

+ 0 - 0
README.md


+ 3 - 0
babel.config.js

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

+ 65 - 0
package.json

@@ -0,0 +1,65 @@
+{
+  "name": "common",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "^1.0.2",
+    "animate.css": "^4.1.1",
+    "axios": "^1.2.2",
+    "core-js": "^3.6.5",
+    "dom-to-image": "^2.6.0",
+    "element-ui": "^2.15.12",
+    "jsonwebtoken": "^9.0.0",
+    "jspdf": "^2.5.1",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.4",
+    "naf-core": "^0.1.2",
+    "vue": "^2.6.11",
+    "vue-meta": "^2.4.0",
+    "vue-router": "^3.2.0",
+    "vuex": "^3.4.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.19",
+    "@vue/cli-plugin-eslint": "~4.5.19",
+    "@vue/cli-plugin-router": "~4.5.19",
+    "@vue/cli-plugin-vuex": "~4.5.19",
+    "@vue/cli-service": "~4.5.19",
+    "@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": "^6.2.2",
+    "less": "^3.0.4",
+    "less-loader": "^5.0.0",
+    "prettier": "^2.2.1",
+    "vue-loader": "^17.0.1",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended",
+      "@vue/prettier"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

BIN
public/favicon.ico


+ 47 - 0
public/index.html

@@ -0,0 +1,47 @@
+<!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,user-scalable=yes,initial-scale=0.1">
+  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+  <title>加载中......</title>
+  <style>
+    /*修改滚动条样式*/
+    div::-webkit-scrollbar {
+      width: 5px;
+      height: 10px;
+    }
+
+    div::-webkit-scrollbar-track {
+      background: rgb(239, 239, 239);
+      border-radius: 2px;
+    }
+
+    div::-webkit-scrollbar-thumb {
+      background: #bfbfbf;
+      border-radius: 10px;
+    }
+
+    div::-webkit-scrollbar-thumb:hover {
+      background: #333;
+    }
+
+    div::-webkit-scrollbar-corner {
+      background: #179a16;
+    }
+
+  </style>
+</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>

+ 7 - 0
src/App.vue

@@ -0,0 +1,7 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<style lang="less"></style>

BIN
src/assets/logo.png


+ 74 - 0
src/components/Breadcrumb/index.vue

@@ -0,0 +1,74 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
+        <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span>
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      levelList: null,
+    };
+  },
+  watch: {
+    $route(route) {
+      // if you go to the redirect page, do not update the breadcrumbs
+      if (route.path.startsWith('/redirect/')) {
+        return;
+      }
+      this.getBreadcrumb();
+    },
+  },
+  created() {
+    this.getBreadcrumb();
+  },
+  methods: {
+    getBreadcrumb() {
+      // only show routes with meta.title
+      let matched = this.$route.matched.filter((item) => item.meta && item.meta.title);
+      const first = matched[0];
+
+      if (!this.isDashboard(first)) {
+        matched = [{ path: '/homeIndex', meta: { title: '系统首页' } }].concat(matched);
+      }
+
+      this.levelList = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false);
+    },
+    isDashboard(route) {
+      const name = route && route.name;
+      if (!name) {
+        return false;
+      }
+      return name.trim() === 'homeIndex';
+    },
+    handleLink(item) {
+      const { redirect, path } = item;
+      if (redirect) {
+        this.$router.push(redirect);
+        return;
+      }
+      this.$router.push(path);
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.app-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  margin-left: 8px;
+
+  .no-redirect {
+    color: #97a8be;
+    cursor: text;
+  }
+}
+</style>

+ 46 - 0
src/components/frame/c-button.vue

@@ -0,0 +1,46 @@
+<template>
+  <div id="c-button">
+    <el-row>
+      <el-col :span="24" class="button">
+        <el-button type="primary" size="small" icon="el-icon-plus" @click="toAdd()">新增</el-button>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'c-button',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    toAdd() {
+      this.$emit('toAdd');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.button {
+  margin: 0 0 10px 0;
+}
+</style>

+ 81 - 0
src/components/frame/c-code.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="ValidCode disabled-select" :style="`width:${width}; height:${height}`" @click="refreshCode">
+    <span v-for="(item, index) in codeList" :key="index" :style="getStyle(item)">
+      {{ item.code }}
+    </span>
+  </div>
+</template>
+<script>
+export default {
+  name: 'ValidCode',
+  model: {
+    prop: 'value',
+    event: 'input',
+  },
+  props: {
+    width: {
+      type: String,
+      default: '100px',
+    },
+    height: {
+      type: String,
+      default: '38px',
+    },
+    length: {
+      type: Number,
+      default: 4,
+    },
+  },
+  data() {
+    return {
+      codeList: [],
+    };
+  },
+  mounted() {
+    this.createdCode();
+  },
+  methods: {
+    refreshCode() {
+      this.createdCode();
+    },
+    createdCode() {
+      const len = this.length;
+      const codeList = [];
+      const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789';
+      const charsLen = chars.length;
+      // 生成
+      for (let i = 0; i < len; i++) {
+        const rgb = [Math.round(Math.random() * 220), Math.round(Math.random() * 240), Math.round(Math.random() * 200)];
+        codeList.push({
+          code: chars.charAt(Math.floor(Math.random() * charsLen)),
+          color: `rgb(${rgb})`,
+          fontSize: `1${[Math.floor(Math.random() * 10)]}px`,
+          padding: `${[Math.floor(Math.random() * 10)]}px`,
+          transform: `rotate(${Math.floor(Math.random() * 90) - Math.floor(Math.random() * 90)}deg)`,
+        });
+      }
+      // 指向
+      this.codeList = codeList;
+      // 将当前数据派发出去
+      this.$emit('input', codeList.map((item) => item.code).join(''));
+    },
+    getStyle(data) {
+      return `color: ${data.color}; font-size: ${data.fontSize}; padding: ${data.padding}; transform: ${data.transform}`;
+    },
+  },
+};
+</script>
+<style lang="less" scoped>
+.ValidCode {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  margin: 0 0 0 2px;
+  span {
+    display: inline-block;
+  }
+}
+</style>

+ 50 - 0
src/components/frame/c-dialog.vue

@@ -0,0 +1,50 @@
+<template>
+  <div id="e-dialog">
+    <el-dialog :title="dialog.title" :visible.sync="dialog.show" :width="width" :before-close="toClose" :close-on-click-modal="false" :append-to-body="true">
+      <slot name="info"></slot>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'e-dialog',
+  props: {
+    dialog: { type: Object, default: () => {} },
+    width: { type: String, default: '50%' },
+  },
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    toClose() {
+      this.$emit('toClose');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.el-dialog__body {
+  padding: 10px;
+  min-height: 30px;
+  max-height: 400px;
+  overflow-y: auto;
+}
+</style>

+ 88 - 0
src/components/frame/c-editor.vue

@@ -0,0 +1,88 @@
+<template>
+  <div id="c-editor">
+    <div style="border: 1px solid #ccc">
+      <Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="tb" :mode="mode" />
+      <Editor class="editor" :style="{ height }" :value="text" @input="toInput" :defaultConfig="ec" :mode="mode" @onCreated="onCreated" />
+    </div>
+  </div>
+</template>
+
+<script>
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
+export default {
+  name: 'c-editor',
+  props: {
+    text: { type: String },
+    mode: { type: String, default: 'default' }, // 或者simple
+    height: { type: String, default: '500px' },
+    url: { type: String },
+  },
+  model: {
+    prop: 'text',
+    event: 'input',
+  },
+  components: { Editor, Toolbar },
+  data: function () {
+    return {
+      editor: null,
+      ec: {
+        placeholder: '请输入内容...',
+        MENU_CONF: { uploadImage: { server: this.url, customInsert: this.customPicInsert } },
+      },
+      tb: {
+        excludeKeys: ['insertImage', 'insertVideo', 'uploadVideo', 'video'],
+      },
+    };
+  },
+  created() {},
+  methods: {
+    onCreated(editor) {
+      this.editor = Object.seal(editor); // 一定要用 Object.seal() ,否则会报错
+    },
+    toInput(e) {
+      this.$emit('input', e);
+    },
+    /**
+     * 自定义上传处理
+     * @param {Object} result 上传结果
+     * @param {Function} insertFn 编辑器添加上传结果函数
+     * @prop {result} {
+     *  errcode,
+     *  errmsg,
+     *  id,
+     *  name,
+     *  uri:短地址
+     * }
+     */
+    customPicInsert(result, insertFn) {
+      const { errcode, uri, name } = result;
+      const url = `${process.env.VUE_APP_HOST}${uri}`;
+      if (errcode === 0) {
+        insertFn(url, name);
+      }
+    },
+  },
+  beforeDestroy() {
+    const editor = this.editor;
+    if (editor == null) return;
+    editor.destroy(); // 组件销毁时,及时销毁编辑器
+  },
+  watch: {
+    url: {
+      immediate: true,
+      handler(val) {
+        if (!val) return;
+        const MENU_CONF = { uploadImage: { server: val }, customInsert: this.customPicInsert };
+        // this.ec['MENU_CONF'] = MENU_CONF;
+      },
+    },
+  },
+};
+</script>
+
+<style src="@wangeditor/editor/dist/css/style.css"></style>
+<style lang="less" scoped>
+.editor {
+  overflow-y: hidden;
+}
+</style>

+ 218 - 0
src/components/frame/c-form.vue

@@ -0,0 +1,218 @@
+<template>
+  <div id="c-form">
+    <el-row>
+      <el-col :span="24" class="c-form">
+        <el-form ref="form" :model="form" :rules="rules" :label-width="labelWidth" @submit.native.prevent>
+          <el-col :span="24" class="form">
+            <el-col :span="span" class="form-1" v-for="(item, index) in fields" :key="'form-field-' + index">
+              <el-form-item v-if="display(item)" :label="getField('label', item)" :prop="item.model" :required="item.required">
+                <template v-if="!item.custom">
+                  <template v-if="item.type === 'textarea'">
+                    <el-input
+                      clearable
+                      v-model="form[item.model]"
+                      :type="item.type"
+                      :placeholder="getField('placeholder', item)"
+                      v-bind="item.options"
+                      @change="dataChange(item.model)"
+                      show-word-limit
+                    ></el-input>
+                  </template>
+                  <template v-else-if="item.type === 'radio'">
+                    <el-radio-group v-model="form[item.model]" :type="item.type" v-bind="item.options" @change="dataChange(item.model)">
+                      <slot :name="item.model" v-bind="{ item }"></slot>
+                    </el-radio-group>
+                  </template>
+                  <template v-else-if="item.type === 'checkbox'">
+                    <el-checkbox-group v-model="form[item.model]" :type="item.type" v-bind="item.options">
+                      <slot :name="item.model" v-bind="{ item }"></slot>
+                    </el-checkbox-group>
+                  </template>
+                  <template v-else-if="item.type === 'select'">
+                    <el-select
+                      clearable
+                      filterable
+                      v-model="form[item.model]"
+                      :type="item.type"
+                      :placeholder="getField('selectplaceholder', item)"
+                      v-bind="item.options"
+                      @change="dataChange(item.model)"
+                      style="width: 100%"
+                    >
+                      <slot :name="item.model" v-bind="{ item }"></slot>
+                    </el-select>
+                  </template>
+                  <template v-else-if="item.type === 'selectMany'">
+                    <el-select
+                      filterable
+                      clearable
+                      multiple
+                      collapse-tags
+                      v-model="form[item.model]"
+                      :type="item.type"
+                      :placeholder="getField('selectplaceholder', item)"
+                      v-bind="item.options"
+                      @change="dataChange(item.model)"
+                      style="width: 100%"
+                    >
+                      <slot :name="item.model" v-bind="{ item }"></slot>
+                    </el-select>
+                  </template>
+                  <template
+                    v-else-if="
+                      item.type === `year` ||
+                      item.type == 'month' ||
+                      item.type == 'date' ||
+                      item.type == 'daterange' ||
+                      item.type == 'datetime' ||
+                      item.type == 'datetimerange'
+                    "
+                  >
+                    <el-date-picker
+                      v-model="form[item.model]"
+                      :type="item.type"
+                      :placeholder="getField('selectplaceholder', item)"
+                      :format="getDateFormat(item.type)"
+                      :value-format="getDateFormat(item.type)"
+                      v-bind="item.options"
+                      @change="dataChange(item.model)"
+                      range-separator="至"
+                      style="width: 100%"
+                    >
+                    </el-date-picker>
+                  </template>
+                  <template v-else-if="item.type === `time`">
+                    <el-time-picker
+                      v-model="form[item.model]"
+                      :placeholder="getField('selectplaceholder', item)"
+                      :format="getDateFormat(item.type)"
+                      :value-format="getDateFormat(item.type)"
+                      v-bind="item.options"
+                      @change="dataChange(item.model)"
+                      style="width: 100%"
+                    >
+                    </el-time-picker>
+                  </template>
+                  <template v-else>
+                    <el-input
+                      clearable
+                      v-model="form[item.model]"
+                      :type="getField('type', item)"
+                      :placeholder="getField('placeholder', item)"
+                      :show-password="getField('type', item) === 'password'"
+                      v-bind="item.options"
+                      @change="dataChange(item.model)"
+                    ></el-input>
+                  </template>
+                </template>
+                <template v-else>
+                  <slot :name="item.model" v-bind="{ item }"></slot>
+                </template>
+              </el-form-item>
+            </el-col>
+          </el-col>
+          <el-col :span="24" class="btn" v-if="isSave">
+            <slot name="submit">
+              <el-button type="primary" size="small" @click="save">{{ submitText }}</el-button>
+            </slot>
+          </el-col>
+        </el-form>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'c-form',
+  props: {
+    fields: { type: Array, default: () => [{ readonly: false }] },
+    form: null,
+    rules: { type: Object, default: () => {} },
+    labelWidth: { type: String, default: 'auto' },
+    isSave: { type: Boolean, default: true },
+    submitText: { type: String, default: '提交保存' },
+    reset: { type: Boolean, default: false },
+    span: { type: Number, default: 12 }, // 限制表单显示长度
+  },
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    // 提交保存
+    save() {
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          this.$emit(`save`, { data: JSON.parse(JSON.stringify(this.form)) });
+          if (this.reset) this.$refs.form.resetFields();
+        } else {
+          console.warn('form validate error!!!');
+        }
+      });
+    },
+    display(field) {
+      let dis = _.get(field, `display`);
+      if (!_.isFunction(dis)) return true;
+      else return dis(field, this.form);
+    },
+    // 表单数据类型,提示语,是否禁用,是否错误
+    getField(item, data) {
+      let res = _.get(data, item, null);
+      if (item === 'type') res = res === null ? `text` : res;
+      if (item === 'placeholder') res = res === null ? `请输入${data.label}` : res;
+      if (item === 'selectplaceholder') res = res === null ? `请选择${data.label}` : res;
+      if (item === 'required') res = res === null ? false : res;
+      if (item === `error`) res = res === null ? `${data.label}错误` : res;
+      return res;
+    },
+    dataChange(model) {
+      const value = JSON.parse(JSON.stringify(this.form[model]));
+      this.$emit('dataChange', { model, value });
+    },
+    getDateFormat(e) {
+      if (e === 'year') return 'yyyy';
+      if (e === 'month') return 'MM';
+      if (e === 'date') return 'yyyy-MM-dd';
+      if (e === 'daterange') return 'yyyy-MM-dd';
+      if (e === 'datetime') return 'yyyy-MM-dd HH:mm:ss';
+      if (e === 'datetimerange') return 'yyyy-MM-dd HH:mm:ss';
+      if (e === 'time') return 'HH:mm:ss';
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.c-form {
+  .form {
+    display: flex;
+    flex-wrap: wrap;
+    .form-1 {
+      padding: 0 0 0 10px;
+      .el-form-item {
+        float: left;
+        width: 100%;
+      }
+    }
+  }
+  .btn {
+    text-align: center;
+  }
+}
+</style>

+ 53 - 0
src/components/frame/c-page.vue

@@ -0,0 +1,53 @@
+<template>
+  <div id="c-page">
+    <el-pagination
+      background
+      layout="total, prev, pager, next"
+      :page-sizes="[10, 50, 100, 150, 200]"
+      :total="total"
+      :page-size="limit"
+      :current-page.sync="currentPage"
+      @current-change="changePage"
+    >
+    </el-pagination>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'c-page',
+  props: {
+    total: { type: Number },
+    limit: { type: Number, default: 10 },
+  },
+  components: {},
+  data: function () {
+    return {
+      currentPage: 1,
+    };
+  },
+  created() {},
+  methods: {
+    changePage(page = this.currentPage) {
+      this.$emit('query', { skip: (page - 1) * this.limit, limit: this.limit, ...this.searchInfo });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>
+G

+ 173 - 0
src/components/frame/c-search.vue

@@ -0,0 +1,173 @@
+<template>
+  <div id="c-search">
+    <el-row>
+      <el-col :span="24" class="title" v-if="is_title">
+        <el-col :span="24" class="title_1">
+          <span>{{ title || $route.meta.title }}</span>
+          <span>{{ tip }}</span>
+        </el-col>
+        <el-col :span="24" class="title_2">
+          <span>{{ remark }}</span>
+        </el-col>
+      </el-col>
+      <el-col :span="24" class="search" v-if="is_search">
+        <el-form ref="form" :model="form" label-width="auto">
+          <template v-for="(item, index) in fields">
+            <el-col :span="8" class="form_1" :key="'form-field-' + index" v-if="item.isSearch == true">
+              <el-form-item :label="getField('label', item)" :prop="item.model">
+                <template v-if="!item.custom">
+                  <template v-if="item.type === 'select'">
+                    <el-select v-model="form[item.model]" v-bind="item.options" filterable clearable @change="dataChange(item.model)" size="small">
+                      <slot :name="item.model" v-bind="{ item }"></slot>
+                    </el-select>
+                  </template>
+                  <template v-else>
+                    <el-input
+                      v-model="form[item.model]"
+                      :type="getField('type', item)"
+                      :placeholder="getField('place', item)"
+                      size="small"
+                      clearable
+                      v-bind="item.options"
+                      @change="dataChange(item.model)"
+                    ></el-input>
+                  </template>
+                </template>
+                <template v-else>
+                  <slot :name="item.model" v-bind="{ item }"></slot>
+                  <!-- <el-input v-model="form[item.model]" clearable :placeholder="`输入${item.label}`"></el-input> -->
+                </template>
+              </el-form-item>
+            </el-col>
+          </template>
+          <el-col :span="24" class="btn">
+            <el-button type="primary" size="mini" @click="toSubmit()">查询</el-button>
+            <el-button type="danger" size="mini" @click="toReset()">重置</el-button>
+          </el-col>
+        </el-form>
+      </el-col>
+      <el-col :span="24" class="back" v-if="is_back">
+        <el-button type="primary" size="small" @click="toBack()">返回</el-button>
+        <slot name="custombtn"></slot>
+      </el-col>
+      <el-col :span="24" class="slot">
+        <slot name="isslot"></slot>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const _ = require('lodash');
+export default {
+  name: 'c-search',
+  props: {
+    is_title: { type: Boolean, default: true },
+    is_search: { type: Boolean, default: false },
+    is_back: { type: Boolean, default: false },
+    fields: { type: Array },
+    title: { type: String },
+    tip: { type: String },
+    remark: { type: String },
+  },
+  components: {},
+  data: function () {
+    return {
+      form: {},
+    };
+  },
+  created() {},
+  methods: {
+    // 查询
+    toSubmit() {
+      const obj = _.pickBy(this.form);
+      this.$emit('search', { ...obj });
+    },
+    // 重置
+    toReset() {
+      this.$set(this, `form`, {});
+      this.$emit('search');
+      this.$emit('toReset');
+    },
+    // 文字描述
+    getField(item, data) {
+      let res = _.get(data, item, null);
+      if (item === 'type') res = res === null ? `text` : res;
+      if (item === 'place') res = res === null ? `请输入${data.label}` : res;
+      if (item === 'required') res = res === null ? false : res;
+      if (item === `error`) res = res === null ? `${data.label}错误` : res;
+      return res;
+    },
+    // 获取输入值
+    dataChange(model) {
+      // console.log(model);
+      const value = JSON.parse(JSON.stringify(this.form[model]));
+      // console.log(value);
+    },
+    // 返回
+    toBack() {
+      this.$emit('toBack');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.title {
+  margin: 0 0 5px 0;
+  .title_1 {
+    margin: 0 0 5px 0;
+    span:first-child {
+      font-size: 20px;
+      font-weight: 700;
+      margin-right: 10px;
+    }
+    span:last-child {
+      font-size: 14px;
+      color: #979797;
+    }
+  }
+  .title_2 {
+    span {
+      color: #8baae7;
+      font-size: 14px;
+      margin-top: 10px;
+    }
+  }
+}
+.search {
+  margin: 0 0 10px 0;
+  .form_1 {
+    padding: 0 0 0 10px;
+    .el-form-item {
+      float: left;
+      width: 100%;
+      margin: 0 0 10px 0;
+    }
+    .el-select {
+      width: 100%;
+    }
+  }
+  .btn {
+    text-align: right;
+  }
+}
+.back {
+  text-align: left;
+  margin: 0 0 10px 0;
+}
+</style>

+ 294 - 0
src/components/frame/c-table.vue

@@ -0,0 +1,294 @@
+<template>
+  <div id="c-table">
+    <el-table
+      ref="table"
+      :row-key="rowKey"
+      :data="data"
+      border
+      stripe
+      size="mini"
+      :max-height="height !== null ? height : ''"
+      @select="handleSelectionChange"
+      @select-all="handleSelectAll"
+      :show-summary="useSum"
+      @row-click="rowClick"
+      :summary-method="computedSum"
+    >
+      <el-table-column type="selection" width="55" v-if="select" :prop="rowKey" :reserve-selection="true"> </el-table-column>
+      <template v-for="(item, index) in fields">
+        <template v-if="item.custom">
+          <el-table-column
+            :key="index"
+            align="center"
+            :label="item.label"
+            v-bind="item.options"
+            :show-overflow-tooltip="item.showTip === false ? item.showTip : true"
+          >
+            <template v-slot="{ row }">
+              <slot :name="item.model" v-bind="{ item, row }"></slot>
+            </template>
+          </el-table-column>
+        </template>
+        <template v-else>
+          <el-table-column
+            :key="index"
+            align="center"
+            :label="item.label"
+            :prop="item.model"
+            :formatter="toFormatter"
+            :sortable="true"
+            v-bind="item.options"
+            :show-overflow-tooltip="item.showTip === false ? item.showTip : true"
+          >
+          </el-table-column>
+        </template>
+      </template>
+      <template v-if="opera.length > 0">
+        <el-table-column label="操作" align="center" :width="operaWidth">
+          <template v-slot="{ row, $index }">
+            <template v-for="(item, index) in opera">
+              <template v-if="display(item, row)">
+                <template v-if="vOpera">
+                  <el-link
+                    v-opera="item.method"
+                    :key="`${item.model}-column-${index}`"
+                    :type="item.type || 'primary'"
+                    :icon="item.icon || ''"
+                    size="mini"
+                    style="padding-right: 10px"
+                    :underline="false"
+                    @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
+                  >
+                    {{ item.label }}
+                  </el-link>
+                </template>
+                <template v-else>
+                  <el-link
+                    :key="`${item.model}-column-${index}`"
+                    :type="item.type || 'primary'"
+                    :icon="item.icon || ''"
+                    size="mini"
+                    style="padding-right: 10px"
+                    :underline="false"
+                    @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
+                  >
+                    {{ item.label }}
+                  </el-link>
+                </template>
+              </template>
+            </template>
+          </template>
+        </el-table-column>
+      </template>
+    </el-table>
+    <el-row type="flex" align="middle" justify="end" style="padding-top: 1rem" v-if="usePage">
+      <el-col :span="24" style="text-align: right">
+        <el-pagination
+          background
+          layout="total, prev, pager, next"
+          :page-sizes="[10, 50, 100, 150, 200]"
+          :total="total"
+          :page-size="limit"
+          :current-page.sync="currentPage"
+          @current-change="changePage"
+          @size-change="sizeChange"
+        >
+        </el-pagination>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'c-table',
+  props: {
+    fields: { type: Array, required: true },
+    data: { type: Array, required: true },
+    opera: { type: Array, default: () => [] },
+    rowKey: { type: String, default: '_id' },
+    select: { type: Boolean, default: false },
+    selected: { type: Array, default: () => [] },
+    usePage: { type: Boolean, default: true },
+    total: { type: Number, default: 0 },
+    useSum: { type: Boolean, default: false },
+    sumcol: { type: Array, default: () => [] },
+    sumres: { type: String, default: 'total' },
+    // limit: { type: Number, default: 10 },
+    height: null,
+    operaWidth: { type: Number, default: 200 },
+    vOpera: { type: Boolean, default: false },
+  },
+  components: {},
+  data: function () {
+    return {
+      pageSelected: [],
+      currentPage: 1,
+      limit: this.$limit,
+    };
+  },
+  created() {},
+  methods: {
+    toFormatter(row, column, cellValue, index) {
+      let this_fields = this.fields.filter((fil) => fil.model === column.property);
+      if (this_fields.length > 0) {
+        let format = _.get(this_fields[0], `format`, false);
+        if (format) {
+          let res;
+          if (_.isFunction(format)) {
+            res = format(cellValue);
+          } else {
+            res = this.toFormat({
+              model: this_fields[0].model,
+              value: cellValue,
+            });
+          }
+          return res;
+        } else return cellValue;
+      }
+    },
+    handleOpera(data, method, confirm = false, methodZh, label, index, confirmWord) {
+      let self = true;
+      if (_.isFunction(methodZh)) {
+        methodZh = methodZh(data);
+      } else if (!_.isString(methodZh)) {
+        methodZh = label;
+        self = false;
+      }
+      if (confirm) {
+        let word = self ? methodZh : `您确认${methodZh}该数据?`;
+        if (confirmWord) word = confirmWord;
+        this.$confirm(word, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        })
+          .then(() => {
+            this.$emit(method, { data, index });
+          })
+          .catch(() => {});
+      } else {
+        this.$emit(method, { data, index });
+      }
+    },
+    handleSelectionChange(selection, row) {
+      //根据row是否再pageSelected中,判断是添加还是删除
+      let isSelecteds = _.cloneDeep(this.pageSelected);
+      const is_has = isSelecteds.findIndex((f) => f[this.rowKey] === row[this.rowKey]);
+      if (is_has <= -1) {
+        // 没有找到,属于添加
+        isSelecteds.push(row);
+      } else {
+        // 找到了,删除
+        isSelecteds.splice(is_has, 1);
+      }
+      this.$set(this, 'pageSelected', isSelecteds);
+      this.$emit(`handleSelect`, isSelecteds);
+    },
+    handleSelectAll(selection) {
+      //处于没全选状态,选择之后一定是全选,只有处于全选状态时,才会反选(全取消)
+      // console.log(selection);
+      let res = [];
+      if (selection.length > 0) {
+        //全选
+        res = _.uniqBy(this.pageSelected.concat(selection), this.rowKey);
+      } else {
+        //全取消
+        res = _.differenceBy(this.pageSelected, this.data, this.rowKey);
+      }
+      this.$set(this, `pageSelected`, res);
+      this.$emit(`handleSelect`, res);
+    },
+    initSelection() {
+      this.$nextTick(() => {
+        this.$refs.table.clearSelection();
+        this.selected.forEach((info) => {
+          let d = this.data.filter((p) => p._id === info._id);
+          if (d.length > 0) this.$refs.table.toggleRowSelection(d[0]);
+        });
+      });
+    },
+    selectReset() {
+      this.$refs.table.clearSelection();
+    },
+    changePage(page = this.currentPage) {
+      this.$emit('query', { skip: (page - 1) * this.limit, limit: this.limit, ...this.searchInfo });
+    },
+    sizeChange(limit) {
+      this.limit = limit;
+      this.currentPage = 1;
+      this.$emit('query', { skip: 0, limit: this.limit, ...this.searchInfo });
+    },
+    rowClick(row, column, event) {
+      this.$emit(`rowClick`, row);
+    },
+    display(item, row) {
+      let display = _.get(item, `display`, true);
+      if (display === true) return true;
+      else {
+        let res = display(row);
+        return res;
+      }
+    },
+    // 计算合计
+    computedSum({ columns, data }) {
+      if (columns.length <= 0 || data.length <= 0) return '';
+      const result = [];
+      const reg = new RegExp(/^\d+$/);
+      for (const column of columns) {
+        // 判断有没有prop属性
+        const prop = _.get(column, 'property');
+        if (!prop) {
+          result.push('');
+          continue;
+        }
+        // 判断是否需要计算
+        const inlist = this.sumcol.find((f) => f == prop);
+        if (!inlist) {
+          result.push('');
+          continue;
+        }
+        let res = 0;
+        // 整理出要计算的属性(只取出数字或者可以为数字的值)
+        const resetList = data.map((i) => {
+          const d = _.get(i, prop);
+          return d * 1;
+        });
+        if (this.sumres === 'total') {
+          res = this.totalComputed(resetList);
+        } else if (this.sumres === 'avg') {
+          res = this.avgComputed(resetList);
+        } else if (this.sumres === 'max') {
+          res = this.maxComputed(resetList);
+        } else if (this.sumres === 'min') {
+          res = this.minComputed(resetList);
+        }
+        result.push(res);
+      }
+      result[0] = '合计';
+      return result;
+    },
+    // 合计计算
+    totalComputed(data) {
+      // console.log(data);
+      const total = data.reduce((p, n) => p + parseFloat(n), 0);
+      return total;
+    },
+    // 平均值计算
+    avgComputed(data) {
+      const total = this.totalComputed(data);
+      return _.round(_.divide(total, data.length), 2);
+    },
+    // 最大值计算
+    maxComputed(data) {
+      return _.max(data);
+    },
+    // 最小值计算
+    minComputed(data) {
+      return _.min(data);
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 97 - 0
src/components/frame/c-upload.vue

@@ -0,0 +1,97 @@
+<template>
+  <div id="c-upload">
+    <el-upload
+      v-if="url"
+      ref="upload"
+      :action="url"
+      :limit="limit"
+      :accept="accept"
+      :list-type="listType"
+      :file-list="list"
+      :on-exceed="outLimit"
+      :on-preview="filePreview"
+      :on-success="onSuccess"
+      :before-remove="onRemove"
+    >
+      <el-button type="primary" size="mini">选择文件</el-button>
+      <template #tip v-if="tip">
+        <p style="color: #ff0000">{{ tip }}</p>
+      </template>
+    </el-upload>
+    <el-dialog :visible.sync="dialog.show" append-to-body>
+      <img width="100%" :src="dialog.url" alt="" />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+export default {
+  name: 'c-upload',
+  props: {
+    // 图片上传地址
+    url: { type: String },
+    // 可上传文件数目
+    limit: { type: Number },
+    // 接收上传的文件类型
+    accept: { type: String, default: 'image/png, image/jpeg' },
+    // 文件列表的类型--picture-card---picture
+    listType: { type: String, default: 'picture-card' },
+    // 文件提醒
+    tip: { type: String, default: undefined },
+    // 已有数据,赋值,预览
+    list: { type: Array },
+  },
+  model: {
+    prop: 'list',
+    event: 'change',
+  },
+  data: function () {
+    return {
+      // 图片预览
+      dialog: { show: false, url: '' },
+      // 图片列表
+      fileList: [],
+    };
+  },
+  methods: {
+    // 图片预览
+    filePreview(file) {
+      // this.dialog = { show: true, url: file.url };
+      window.open(file.url);
+    },
+    // 只允许上传多少个文件
+    outLimit() {
+      this.$message.error(`只允许上传${this.limit}个文件`);
+    },
+    // 上传成功,response:成功信息,file:图片信息,fileList:图片列表
+    onSuccess(response, file, fileList) {
+      if (response.errcode !== 0) {
+        this.$message({ message: `上传失败`, type: 'error' });
+        return;
+      }
+      response = _.omit(response, ['errcode', 'errmsg']);
+      let list = _.cloneDeep(this.list);
+      console.log(process.env.VUE_APP_HOST);
+      console.log(response.uri);
+      if (_.isArray(list)) {
+        list.push({ ...response, name: file.name, url: `${process.env.VUE_APP_HOST}${response.uri}` });
+      } else {
+        list = [{ ...response, name: file.name, url: `${process.env.VUE_APP_HOST}${response.uri}` }];
+      }
+      this.$emit('change', list);
+    },
+    // 删除图片
+    onRemove(file, fileList) {
+      // console.log(file);
+      let list = _.cloneDeep(this.list);
+      // console.log(list);
+      list = list.filter((f) => f.uri !== _.get(file, 'uri'));
+      this.$emit('change', list);
+      return true;
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 138 - 0
src/layout/site.js

@@ -0,0 +1,138 @@
+// 网站基本设置
+export const siteInfo = {
+  zhTitle: '客服管理系统',
+  enTitle: 'Free Sky',
+  smallTtitle: '客服管理系统',
+};
+// 联系方式
+export const contactInfo = {
+  mobile: '0431-12345678',
+  qq: '123456789',
+  phone: '12345678901',
+  email: '123456@163.com',
+  bq: 'Copyright 2022-2024 free All Rights Reserved',
+  icpLink: '吉ICP备2023009254号-1',
+  ywLink: '吉公网安备22010202001113',
+};
+
+export const admin_style_one = {
+  // 主体背景颜色
+  main_bg_color: '#fff2cc',
+  // 左侧菜单部分
+  aside_bg_color: '#e2f0d9',
+  aside_border_color: '#727e96',
+  // 菜单
+  menus_bg_color: '#A2E1F7',
+  menus_txt_color: '#000000',
+  menus_actxt_color: '#ffffff',
+  menus_acbg_color: '#65cd94',
+  // 头部
+  header_bg_color: 'transparent',
+  // 页面内容部分
+  mains_bg_color: '#dae3f3',
+};
+export const admin_style_two = {
+  // 主体背景颜色
+  main_bg_color: '#f0f0f0',
+  // 头部
+  header_bg_color: '#07c4a8',
+  // 左侧菜单部分
+  aside_bg_color: '#07c4a8',
+  aside_border_color: '#ffffff',
+  // 菜单
+  menus_bg_color: '#07c4a8',
+  menus_txt_color: '#000000',
+  menus_actxt_color: '#ffffff',
+  menus_acbg_color: '#65cd94',
+  // 内容显示部分
+  routerview_bg_color: '#ffffff',
+};
+export const admin_style_thr = {
+  // 主体背景颜色
+  main_bg_color: '#304156',
+  // 头部
+  header_bg_color: '#ffffff',
+  // 左侧菜单部分
+  aside_bg_color: '#304156',
+  aside_border_color: '#304156',
+  // 菜单
+  menus_bg_color: '#304156',
+  menus_txt_color: '#BFCBD9',
+  menus_actxt_color: '#409EFF',
+  menus_acbg_color: '#304156',
+  // 内容显示部分
+  routerview_bg_color: '#ffffff',
+};
+// 菜单
+export const menus = [
+  {
+    id: 'menus_1',
+    icon: 'el-icon-user',
+    name: '系统首页',
+    path: '/homeIndex',
+    type: '0',
+  },
+  {
+    id: 'menus_2',
+    icon: 'el-icon-setting',
+    name: '系统管理',
+    type: '1',
+    children: [
+      {
+        id: 'menus_2_1',
+        icon: 'el-icon-setting',
+        name: '角色管理',
+        path: '/system/role',
+      },
+      {
+        id: 'menus_2_1',
+        icon: 'el-icon-setting',
+        name: '菜单管理',
+        path: '/system/menus',
+      },
+      {
+        id: 'menus_2_1',
+        icon: 'el-icon-setting',
+        name: '字典管理',
+        path: '/system/dict',
+      },
+      {
+        id: 'menus_2_2',
+        icon: 'el-icon-setting',
+        name: '三级菜单',
+        type: '2',
+        children: [
+          {
+            id: 'menus_2_2_1',
+            icon: 'el-icon-user',
+            name: '三级菜单-1',
+            path: '/test/test2/test1',
+          },
+        ],
+      },
+    ],
+  },
+];
+
+// 是否启用
+export const is_use = [
+  {
+    dict_label: '是',
+    dict_value: '0',
+  },
+  {
+    dict_label: '否',
+    dict_value: '1',
+  },
+];
+// 节目类型
+export const pro_type = [
+  {
+    dict_label: '电视节目',
+    dict_value: '0',
+  },
+  {
+    dict_label: '电影节目',
+    dict_value: '1',
+  },
+];

+ 8 - 0
src/main.js

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

+ 12 - 0
src/router/index.js

@@ -0,0 +1,12 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+
+Vue.use(VueRouter);
+
+const routes = [];
+
+const router = new VueRouter({
+  routes,
+});
+
+export default router;

+ 52 - 0
src/store/admin.js

@@ -0,0 +1,52 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/admin`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+  // 重置密码
+  async resetPwd({ commit }, payload) {
+    const id = _.get(payload, 'id', _.get(payload, '_id'));
+    const res = await this.$axios.$post(`${api.test}/resetPwd/${id}`, payload);
+    return res;
+  },
+  async login({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}/login`, payload);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/appapk.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/appapk`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/appbanner.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/appbanner`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/appbasic.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/appbasic`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/discuss.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/discuss`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/hotlink.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/hotlink`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/moneylog.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/moneylog`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/scenedata.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/scenedata`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/scenetype.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/scenetype`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/videos.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/videos`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/app/vipsetting.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/vipsetting`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 11 - 0
src/store/index.js

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

+ 42 - 0
src/store/program.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/program`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/system/dictdata.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/dictdata`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/system/dicttype.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/dicttype`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 52 - 0
src/store/user.js

@@ -0,0 +1,52 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/user`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+  // 重置密码
+  async resetPwd({ commit }, payload) {
+    const id = _.get(payload, 'id', _.get(payload, '_id'));
+    const res = await this.$axios.$post(`${api.test}/resetPwd/${id}`, payload);
+    return res;
+  },
+  async login({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}/login`, payload);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 8 - 0
src/store/user/mutations.js

@@ -0,0 +1,8 @@
+export const setUser = (state, payload) => {
+  state.user = payload;
+};
+
+export const deleteUser = (state, payload) => {
+  state.user = {};
+  sessionStorage.removeItem('token');
+};

+ 2 - 0
src/store/user/state.js

@@ -0,0 +1,2 @@
+export const user = {};
+export const test = { id: '11' };

+ 42 - 0
src/store/web/banner.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/banner`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/web/cases.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/cases`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/web/company.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/company`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/web/leavemess.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/leavemess`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 42 - 0
src/store/web/news.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+Vue.use(Vuex);
+const api = {
+  test: `/projectadmin/api/news`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.test}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.test}/${payload}`);
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.test}`, payload);
+    return res;
+  },
+  async update({ commit }, { _id, ...info } = {}) {
+    const res = await this.$axios.$post(`${api.test}/${_id}`, { ...info });
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.test}/${payload}`);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 43 - 0
src/util/htmlToPdf.js

@@ -0,0 +1,43 @@
+import domtoimage from 'dom-to-image';
+import JsPDF from 'jspdf';
+import './simfang-normal';
+/**
+ * @param  ele
+ * @param  pdfName
+ * */
+function createPDF(ele, pdfName, other) {
+  // 生成base64图片码
+  domtoimage
+    .toPng(ele, {})
+    .then(function (dataUrl) {
+      console.log('生成成功');
+      getPDF(ele, dataUrl, pdfName, other);
+    })
+    .catch(function (error) {
+      console.error('生成失败', error);
+    });
+}
+function getPDF(ele, url, name, other) {
+  // a4纸大小
+  var imgWidth = 595;
+  var imgHeight = 842;
+  var pdf = new JsPDF('', 'pt', 'a4');
+  // 添加字体
+  pdf.setFont('simfang');
+  // 水印
+  if (other && other.is_water == true) {
+    const pageHeight = pdf.getPageHeight();
+    pdf.saveGraphicsState();
+    pdf.setGState(pdf.GState({ opacity: 0.5 }));
+    pdf.setFontSize(18);
+    pdf.text(other.water, 100, pageHeight / 2, 42);
+    pdf.text(other.water, 110, pageHeight / 1.5, 42);
+    pdf.restoreGraphicsState();
+  }
+  pdf.addImage(url, 'JPEG', 0, 0, imgWidth, imgHeight);
+  pdf.save(name);
+}
+
+export default {
+  createPDF,
+};

File diff suppressed because it is too large
+ 7 - 0
src/util/simfang-normal.js


+ 36 - 0
src/views/index.vue

@@ -0,0 +1,36 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main"> test </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 4 - 0
vue.config.js

@@ -0,0 +1,4 @@
+const { defineConfig } = require('@vue/cli-service');
+module.exports = defineConfig({
+  transpileDependencies: true,
+});