zhangzengfei
2021-11-17 5186227a467bd34dc253e64b23bc96d3a07bb399
添加用户权限控制
26个文件已修改
2个文件已添加
18个文件已删除
7678 ■■■■ 已修改文件
.eslintrc.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LICENSE 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/ad.js 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/changeLog.js 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/colorfulIcon.js 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/goodsList.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/icon.js 985 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/menuManagement.js 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/notice.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/personalCenter.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/remixIcon.js 2294 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/roleManagement.js 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/router.js 292 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/table.js 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/tree.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/user.js 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/controller/userManagement.js 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/index.js 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/utils/index.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/package.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/user.js 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SelectTree/index.vue 292 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/VabCharge/index.vue 296 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/VabProfile/index.vue 450 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/VabSnow/index.vue 115 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/VabUpload/index.vue 300 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/net.config.js 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/permission.js 81 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/setting.config.js 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/VabAvatar/index.vue 144 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/VabNavBar/index.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/echarts.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/element.js 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/index.js 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/routes.js 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/errorLog.js 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/handleRoutes.js 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.js 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/validate.js 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login/index.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/components/ProjectEdit.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/index.vue 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/user/components/UserEdit.vue 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/user/index.vue 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc.js
@@ -8,24 +8,24 @@
  env: {
    node: true,
  },
  extends: ['plugin:vue/recommended', '@vue/prettier'],
  extends: ["plugin:vue/recommended", "@vue/prettier"],
  rules: {
    // 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // 'vue/no-v-html': 'off',
  },
  parserOptions: {
    parser: 'babel-eslint',
    parser: "babel-eslint",
  },
  overrides: [
    {
      files: [
        '**/__tests__/*.{j,t}s?(x)',
        '**/tests/unit/**/*.spec.{j,t}s?(x)',
        "**/__tests__/*.{j,t}s?(x)",
        "**/tests/unit/**/*.spec.{j,t}s?(x)",
      ],
      env: {
        jest: true,
      },
    },
  ],
}
};
LICENSE
File was deleted
mock/controller/ad.js
File was deleted
mock/controller/changeLog.js
File was deleted
mock/controller/colorfulIcon.js
File was deleted
mock/controller/goodsList.js
File was deleted
mock/controller/icon.js
File was deleted
mock/controller/menuManagement.js
File was deleted
mock/controller/notice.js
File was deleted
mock/controller/personalCenter.js
File was deleted
mock/controller/remixIcon.js
File was deleted
mock/controller/roleManagement.js
File was deleted
mock/controller/router.js
File was deleted
mock/controller/table.js
File was deleted
mock/controller/tree.js
File was deleted
mock/controller/user.js
File was deleted
mock/controller/userManagement.js
File was deleted
mock/index.js
File was deleted
mock/utils/index.js
File was deleted
src/api/package.js
@@ -8,6 +8,14 @@
  });
}
export function rebuild(data) {
  return request({
    url: "/package/" + data.id + "/rebuild",
    method: "post",
    data,
  });
}
export function download(query) {
  return request({
    url: "/package/download",
src/api/user.js
@@ -1,11 +1,17 @@
import request from "@/utils/request";
import { loginRSA, tokenName } from "@/config";
export async function login(data) {
  return request({
    url: "/user/login",
    url: "/account/login",
    method: "post",
    data,
  });
}
export function logout() {
  return request({
    url: "/account/logout",
    method: "post",
  });
}
@@ -13,22 +19,44 @@
  return request({
    url: "/user/info",
    method: "post",
    data: {
      [tokenName]: accessToken,
    },
  });
}
export function logout() {
export function register(data) {
  return request({
    url: "/logout",
    url: "/user/register",
    method: "post",
    data,
  });
}
export function register() {
export function doEdit(data) {
  return request({
    url: "/register",
    url: "/user/" + data.id,
    method: "put",
    data,
  });
}
export function getUsers(query) {
  return request({
    url: "/user/list",
    method: "get",
    params: query,
  });
}
export function deleteUser(id) {
  return request({
    url: "/user/" + id,
    method: "delete",
  });
}
export function updatePassword(id, data) {
  return request({
    url: "/user/" + id + "/password",
    method: "post",
    data,
  });
}
src/components/SelectTree/index.vue
@@ -31,168 +31,168 @@
</template>
<script>
  export default {
    name: 'SelectTreeTemplate',
    props: {
      /* 树形结构数据 */
      treeOptions: {
        type: Array,
        default: () => {
          return []
        },
      },
      /* 单选/多选 */
      selectType: {
        type: String,
        default: () => {
          return 'single'
        },
      },
      /* 初始选中值key */
      selectedKey: {
        type: String,
        default: () => {
          return ''
        },
      },
      /* 初始选中值name */
      selectedValue: {
        type: String,
        default: () => {
          return ''
        },
      },
      /* 可做选择的层级 */
      selectLevel: {
        type: [String, Number],
        default: () => {
          return ''
        },
      },
      /* 可清空选项 */
      clearable: {
        type: Boolean,
        default: () => {
          return true
        },
export default {
  name: "SelectTreeTemplate",
  props: {
    /* 树形结构数据 */
    treeOptions: {
      type: Array,
      default: () => {
        return [];
      },
    },
    data() {
      return {
        defaultProps: {
          children: 'children',
          label: 'name',
        },
        defaultSelectedKeys: [], //初始选中值数组
        currentNodeKey: this.selectedKey,
        selectValue:
          this.selectType == 'multiple'
            ? this.selectedValue.split(',')
            : this.selectedValue, //下拉框选中值label
        selectKey:
          this.selectType == 'multiple'
            ? this.selectedKey.split(',')
            : this.selectedKey, //下拉框选中值value
    /* 单选/多选 */
    selectType: {
      type: String,
      default: () => {
        return "single";
      },
    },
    /* 初始选中值key */
    selectedKey: {
      type: String,
      default: () => {
        return "";
      },
    },
    /* 初始选中值name */
    selectedValue: {
      type: String,
      default: () => {
        return "";
      },
    },
    /* 可做选择的层级 */
    selectLevel: {
      type: [String, Number],
      default: () => {
        return "";
      },
    },
    /* 可清空选项 */
    clearable: {
      type: Boolean,
      default: () => {
        return true;
      },
    },
  },
  data() {
    return {
      defaultProps: {
        children: "children",
        label: "name",
      },
      defaultSelectedKeys: [], //初始选中值数组
      currentNodeKey: this.selectedKey,
      selectValue:
        this.selectType == "multiple"
          ? this.selectedValue.split(",")
          : this.selectedValue, //下拉框选中值label
      selectKey:
        this.selectType == "multiple"
          ? this.selectedKey.split(",")
          : this.selectedKey, //下拉框选中值value
    };
  },
  mounted() {
    const that = this;
    this.initTree();
  },
  methods: {
    // 初始化树的值
    initTree() {
      const that = this;
      if (that.selectedKey) {
        that.defaultSelectedKeys = that.selectedKey.split(","); // 设置默认展开
        if (that.selectType == "single") {
          that.$refs.treeOption.setCurrentKey(that.selectedKey); // 设置默认选中
        } else {
          that.$refs.treeOption.setCheckedKeys(that.defaultSelectedKeys);
        }
      }
    },
    mounted() {
      const that = this
      this.initTree()
    // 清除选中
    clearHandle() {
      const that = this;
      this.selectValue = "";
      this.selectKey = "";
      this.defaultSelectedKeys = [];
      this.currentNodeKey = "";
      this.clearSelected();
      if (that.selectType == "single") {
        that.$refs.treeOption.setCurrentKey(""); // 设置默认选中
      } else {
        that.$refs.treeOption.setCheckedKeys([]);
      }
    },
    methods: {
      // 初始化树的值
      initTree() {
        const that = this
        if (that.selectedKey) {
          that.defaultSelectedKeys = that.selectedKey.split(',') // 设置默认展开
          if (that.selectType == 'single') {
            that.$refs.treeOption.setCurrentKey(that.selectedKey) // 设置默认选中
          } else {
            that.$refs.treeOption.setCheckedKeys(that.defaultSelectedKeys)
          }
        }
      },
      // 清除选中
      clearHandle() {
        const that = this
        this.selectValue = ''
        this.selectKey = ''
        this.defaultSelectedKeys = []
        this.currentNodeKey = ''
        this.clearSelected()
        if (that.selectType == 'single') {
          that.$refs.treeOption.setCurrentKey('') // 设置默认选中
        } else {
          that.$refs.treeOption.setCheckedKeys([])
        }
      },
      /* 清空选中样式 */
      clearSelected() {
        const allNode = document.querySelectorAll('#treeOption .el-tree-node')
        allNode.forEach((element) => element.classList.remove('is-current'))
      },
      // select多选时移除某项操作
      removeTag(val) {
        this.$refs.treeOption.setCheckedKeys([])
      },
      // 点击叶子节点
      nodeClick(data, node, el) {
        if (data.rank >= this.selectLevel) {
          this.selectValue = data.name
          this.selectKey = data.id
        }
      },
      // 节点选中操作
      checkNode(data, node, el) {
        const checkedNodes = this.$refs.treeOption.getCheckedNodes()
        const keyArr = []
        const valueArr = []
        checkedNodes.forEach((item) => {
          if (item.rank >= this.selectLevel) {
            keyArr.push(item.id)
            valueArr.push(item.name)
          }
        })
        this.selectValue = valueArr
        this.selectKey = keyArr
      },
    /* 清空选中样式 */
    clearSelected() {
      const allNode = document.querySelectorAll("#treeOption .el-tree-node");
      allNode.forEach((element) => element.classList.remove("is-current"));
    },
  }
    // select多选时移除某项操作
    removeTag(val) {
      this.$refs.treeOption.setCheckedKeys([]);
    },
    // 点击叶子节点
    nodeClick(data, node, el) {
      if (data.rank >= this.selectLevel) {
        this.selectValue = data.name;
        this.selectKey = data.id;
      }
    },
    // 节点选中操作
    checkNode(data, node, el) {
      const checkedNodes = this.$refs.treeOption.getCheckedNodes();
      const keyArr = [];
      const valueArr = [];
      checkedNodes.forEach((item) => {
        if (item.rank >= this.selectLevel) {
          keyArr.push(item.id);
          valueArr.push(item.name);
        }
      });
      this.selectValue = valueArr;
      this.selectKey = keyArr;
    },
  },
};
</script>
<style lang="scss" scoped>
  .el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
    height: auto;
    max-height: 274px;
    padding: 0;
    overflow-y: auto;
  }
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
  height: auto;
  max-height: 274px;
  padding: 0;
  overflow-y: auto;
}
  .el-select-dropdown__item.selected {
    font-weight: normal;
  }
.el-select-dropdown__item.selected {
  font-weight: normal;
}
  ul li > .el-tree .el-tree-node__content {
    height: auto;
    padding: 0 20px;
  }
ul li > .el-tree .el-tree-node__content {
  height: auto;
  padding: 0 20px;
}
  .el-tree-node__label {
    font-weight: normal;
  }
.el-tree-node__label {
  font-weight: normal;
}
  .el-tree > .is-current .el-tree-node__label {
    font-weight: 700;
    color: #409eff;
  }
.el-tree > .is-current .el-tree-node__label {
  font-weight: 700;
  color: #409eff;
}
  .el-tree > .is-current .el-tree-node__children .el-tree-node__label {
    font-weight: normal;
    color: #606266;
  }
.el-tree > .is-current .el-tree-node__children .el-tree-node__label {
  font-weight: normal;
  color: #606266;
}
</style>
<style lang="scss">
  /* .vab-tree-select{
/* .vab-tree-select{
      .el-tag__close.el-icon-close{
        width:0;
        overflow:hidden;
src/components/VabCharge/index.vue
@@ -23,169 +23,169 @@
</template>
<script>
  export default {
    name: 'VabCharge',
    props: {
      styleObj: {
        type: Object,
        default: () => {
          return {}
        },
      },
      startVal: {
        type: Number,
        default: 0,
      },
      endVal: {
        type: Number,
        default: 100,
export default {
  name: "VabCharge",
  props: {
    styleObj: {
      type: Object,
      default: () => {
        return {};
      },
    },
    data() {
      return {
        decimals: 2,
        prefix: '',
        suffix: '%',
        separator: ',',
        duration: 3000,
      }
    startVal: {
      type: Number,
      default: 0,
    },
    created() {},
    mounted() {},
    methods: {},
  }
    endVal: {
      type: Number,
      default: 100,
    },
  },
  data() {
    return {
      decimals: 2,
      prefix: "",
      suffix: "%",
      separator: ",",
      duration: 3000,
    };
  },
  created() {},
  mounted() {},
  methods: {},
};
</script>
<style lang="scss" scoped>
  .content {
.content {
  position: relative;
  display: flex;
  align-items: center; /* 垂直居中 */
  justify-content: center; /* 水平居中 */
  width: 100%;
  background: #000;
  .g-number {
    position: absolute;
    top: 27%;
    z-index: 99;
    width: 300px;
    font-size: 32px;
    color: #fff;
    text-align: center;
  }
  .g-container {
    position: relative;
    display: flex;
    align-items: center; /* 垂直居中 */
    justify-content: center; /* 水平居中 */
    width: 100%;
    background: #000;
    width: 300px;
    height: 400px;
    margin: auto;
  }
    .g-number {
  .g-contrast {
    width: 300px;
    height: 400px;
    overflow: hidden;
    background-color: #000;
    filter: contrast(15) hue-rotate(0);
    animation: hueRotate 10s infinite linear;
  }
  .g-circle {
    position: relative;
    box-sizing: border-box;
    width: 300px;
    height: 300px;
    filter: blur(8px);
    &::after {
      position: absolute;
      top: 27%;
      z-index: 99;
      width: 300px;
      font-size: 32px;
      color: #fff;
      text-align: center;
    }
    .g-container {
      position: relative;
      width: 300px;
      height: 400px;
      margin: auto;
    }
    .g-contrast {
      width: 300px;
      height: 400px;
      overflow: hidden;
      background-color: #000;
      filter: contrast(15) hue-rotate(0);
      animation: hueRotate 10s infinite linear;
    }
    .g-circle {
      position: relative;
      box-sizing: border-box;
      width: 300px;
      height: 300px;
      filter: blur(8px);
      &::after {
        position: absolute;
        top: 40%;
        left: 50%;
        width: 200px;
        height: 200px;
        content: '';
        background-color: #00ff6f;
        border-radius: 42% 38% 62% 49% / 45%;
        transform: translate(-50%, -50%) rotate(0);
        animation: rotate 10s infinite linear;
      }
      &::before {
        position: absolute;
        top: 40%;
        left: 50%;
        z-index: 99;
        width: 176px;
        height: 176px;
        content: '';
        background-color: #000;
        border-radius: 50%;
        transform: translate(-50%, -50%);
      }
    }
    .g-bubbles {
      position: absolute;
      bottom: 0;
      top: 40%;
      left: 50%;
      width: 100px;
      height: 40px;
      width: 200px;
      height: 200px;
      content: "";
      background-color: #00ff6f;
      filter: blur(5px);
      border-radius: 100px 100px 0 0;
      transform: translate(-50%, 0);
      border-radius: 42% 38% 62% 49% / 45%;
      transform: translate(-50%, -50%) rotate(0);
      animation: rotate 10s infinite linear;
    }
    li {
    &::before {
      position: absolute;
      background: #00ff6f;
      top: 40%;
      left: 50%;
      z-index: 99;
      width: 176px;
      height: 176px;
      content: "";
      background-color: #000;
      border-radius: 50%;
    }
    @for $i from 0 through 15 {
      li:nth-child(#{$i}) {
        $width: 15 + random(15) + px;
        top: 50%;
        left: 15 + random(70) + px;
        width: $width;
        height: $width;
        transform: translate(-50%, -50%);
        animation: moveToTop
          #{random(6) +
          3}s
          ease-in-out -#{random(5000) /
          1000}s
          infinite;
      }
    }
    @keyframes rotate {
      50% {
        border-radius: 45% / 42% 38% 58% 49%;
      }
      100% {
        transform: translate(-50%, -50%) rotate(720deg);
      }
    }
    @keyframes moveToTop {
      90% {
        opacity: 1;
      }
      100% {
        opacity: 0.1;
        transform: translate(-50%, -180px);
      }
    }
    @keyframes hueRotate {
      100% {
        filter: contrast(15) hue-rotate(360deg);
      }
      transform: translate(-50%, -50%);
    }
  }
  .g-bubbles {
    position: absolute;
    bottom: 0;
    left: 50%;
    width: 100px;
    height: 40px;
    background-color: #00ff6f;
    filter: blur(5px);
    border-radius: 100px 100px 0 0;
    transform: translate(-50%, 0);
  }
  li {
    position: absolute;
    background: #00ff6f;
    border-radius: 50%;
  }
  @for $i from 0 through 15 {
    li:nth-child(#{$i}) {
      $width: 15 + random(15) + px;
      top: 50%;
      left: 15 + random(70) + px;
      width: $width;
      height: $width;
      transform: translate(-50%, -50%);
      animation: moveToTop
        #{random(6) +
        3}s
        ease-in-out -#{random(5000) /
        1000}s
        infinite;
    }
  }
  @keyframes rotate {
    50% {
      border-radius: 45% / 42% 38% 58% 49%;
    }
    100% {
      transform: translate(-50%, -50%) rotate(720deg);
    }
  }
  @keyframes moveToTop {
    90% {
      opacity: 1;
    }
    100% {
      opacity: 0.1;
      transform: translate(-50%, -180px);
    }
  }
  @keyframes hueRotate {
    100% {
      filter: contrast(15) hue-rotate(360deg);
    }
  }
}
</style>
src/components/VabProfile/index.vue
@@ -25,289 +25,289 @@
</template>
<script>
  export default {
    name: 'VabProfile',
    props: {
      styleObj: {
        type: Object,
        default: () => {
          return {}
        },
      },
      username: {
        type: String,
        default: '',
      },
      avatar: {
        type: String,
        default: '',
      },
      iconArray: {
        type: Array,
        default: () => {
          return [
            { icon: 'bell', url: '' },
            { icon: 'bookmark', url: '' },
            { icon: 'cloud-sun', url: '' },
          ]
        },
export default {
  name: "VabProfile",
  props: {
    styleObj: {
      type: Object,
      default: () => {
        return {};
      },
    },
    data() {
      return {}
    username: {
      type: String,
      default: "",
    },
    created() {},
    mounted() {},
    methods: {},
  }
    avatar: {
      type: String,
      default: "",
    },
    iconArray: {
      type: Array,
      default: () => {
        return [
          { icon: "bell", url: "" },
          { icon: "bookmark", url: "" },
          { icon: "cloud-sun", url: "" },
        ];
      },
    },
  },
  data() {
    return {};
  },
  created() {},
  mounted() {},
  methods: {},
};
</script>
<style lang="scss" scoped>
  .card {
    --card-bg-color: hsl(240, 31%, 25%);
    --card-bg-color-transparent: hsla(240, 31%, 25%, 0.7);
.card {
  --card-bg-color: hsl(240, 31%, 25%);
  --card-bg-color-transparent: hsla(240, 31%, 25%, 0.7);
    position: relative;
  position: relative;
  width: 100%;
  height: 100%;
  .card-borders {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    .card-borders {
    .border-top {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 2px;
      background: var(--card-bg-color);
      transform: translateX(-100%);
      animation: slide-in-horizontal 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
        forwards;
    }
    .border-right {
      position: absolute;
      right: 0;
      width: 2px;
      height: 100%;
      overflow: hidden;
      background: var(--card-bg-color);
      transform: translateY(100%);
      animation: slide-in-vertical 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
        forwards;
    }
      .border-top {
    .border-bottom {
      position: absolute;
      bottom: 0;
      width: 100%;
      height: 2px;
      background: var(--card-bg-color);
      transform: translateX(100%);
      animation: slide-in-horizontal-reverse 0.8s
        cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
    }
    .border-left {
      position: absolute;
      top: 0;
      width: 2px;
      height: 100%;
      background: var(--card-bg-color);
      transform: translateY(-100%);
      animation: slide-in-vertical-reverse 0.8s
        cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
    }
  }
  .card-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
    padding: 40px 0 40px 0;
    background: var(--card-bg-color-transparent);
    opacity: 0;
    transform: scale(0.6);
    animation: bump-in 0.5s 0.8s forwards;
    .avatar {
      width: 80px;
      height: 80px;
      border: 1px solid $base-color-white;
      border-radius: 50%;
      opacity: 0;
      transform: scale(0.6);
      animation: bump-in 0.5s 1s forwards;
    }
    .username {
      position: relative;
      margin-top: 20px;
      margin-bottom: 20px;
      font-size: 26px;
      color: transparent;
      letter-spacing: 2px;
      animation: fill-text-white 1.2s 2s forwards;
      &::before {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 2px;
        background: var(--card-bg-color);
        transform: translateX(-100%);
        animation: slide-in-horizontal 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
          forwards;
      }
      .border-right {
        position: absolute;
        right: 0;
        width: 2px;
        height: 100%;
        background: var(--card-bg-color);
        transform: translateY(100%);
        animation: slide-in-vertical 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
          forwards;
      }
      .border-bottom {
        position: absolute;
        bottom: 0;
        width: 100%;
        height: 2px;
        background: var(--card-bg-color);
        transform: translateX(100%);
        animation: slide-in-horizontal-reverse 0.8s
          cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
      }
      .border-left {
        position: absolute;
        top: 0;
        width: 2px;
        height: 100%;
        background: var(--card-bg-color);
        transform: translateY(-100%);
        animation: slide-in-vertical-reverse 0.8s
          cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
        color: black;
        content: "";
        background: #35b9f1;
        transform: scaleX(0);
        transform-origin: left;
        animation: slide-in-out 1.2s 1.2s cubic-bezier(0.75, 0, 0, 1) forwards;
      }
    }
    .card-content {
    .social-icons {
      display: flex;
      flex-direction: column;
      align-items: center;
      height: 100%;
      padding: 40px 0 40px 0;
      background: var(--card-bg-color-transparent);
      opacity: 0;
      transform: scale(0.6);
      animation: bump-in 0.5s 0.8s forwards;
      .avatar {
        width: 80px;
        height: 80px;
        border: 1px solid $base-color-white;
        border-radius: 50%;
        opacity: 0;
        transform: scale(0.6);
        animation: bump-in 0.5s 1s forwards;
      }
      .username {
      .social-icon {
        position: relative;
        margin-top: 20px;
        margin-bottom: 20px;
        font-size: 26px;
        color: transparent;
        letter-spacing: 2px;
        animation: fill-text-white 1.2s 2s forwards;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 2.5em;
        height: 2.5em;
        margin: 0 15px;
        color: white;
        text-decoration: none;
        border-radius: 50%;
        &::before {
        @for $i from 1 through 3 {
          &:nth-child(#{$i}) {
            &::before {
              animation-delay: 2s + 0.1s * $i;
            }
            &::after {
              animation-delay: 2.1s + 0.1s * $i;
            }
            svg {
              animation-delay: 2.2s + 0.1s * $i;
            }
          }
        }
        &::before,
        &::after {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          color: black;
          content: '';
          background: #35b9f1;
          transform: scaleX(0);
          transform-origin: left;
          animation: slide-in-out 1.2s 1.2s cubic-bezier(0.75, 0, 0, 1) forwards;
          content: "";
          border-radius: inherit;
          transform: scale(0);
        }
      }
      .social-icons {
        display: flex;
        &::before {
          background: #f7f1e3;
          animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
        }
        .social-icon {
          position: relative;
          display: flex;
          align-items: center;
          justify-content: center;
          width: 2.5em;
          height: 2.5em;
          margin: 0 15px;
          color: white;
          text-decoration: none;
          border-radius: 50%;
        &::after {
          background: #2c3e50;
          animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
        }
          @for $i from 1 through 3 {
            &:nth-child(#{$i}) {
              &::before {
                animation-delay: 2s + 0.1s * $i;
              }
              &::after {
                animation-delay: 2.1s + 0.1s * $i;
              }
              svg {
                animation-delay: 2.2s + 0.1s * $i;
              }
            }
          }
          &::before,
          &::after {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            content: '';
            border-radius: inherit;
            transform: scale(0);
          }
          &::before {
            background: #f7f1e3;
            animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
          }
          &::after {
            background: #2c3e50;
            animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
          }
          svg {
            z-index: 99;
            transform: scale(0);
            animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
          }
        svg {
          z-index: 99;
          transform: scale(0);
          animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
        }
      }
    }
  }
}
  @keyframes bump-in {
    50% {
      transform: scale(1.05);
    }
    to {
      opacity: 1;
      transform: scale(1);
    }
@keyframes bump-in {
  50% {
    transform: scale(1.05);
  }
  @keyframes slide-in-horizontal {
    50% {
      transform: translateX(0);
    }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
    to {
      transform: translateX(100%);
    }
@keyframes slide-in-horizontal {
  50% {
    transform: translateX(0);
  }
  @keyframes slide-in-horizontal-reverse {
    50% {
      transform: translateX(0);
    }
  to {
    transform: translateX(100%);
  }
}
    to {
      transform: translateX(-100%);
    }
@keyframes slide-in-horizontal-reverse {
  50% {
    transform: translateX(0);
  }
  @keyframes slide-in-vertical {
    50% {
      transform: translateY(0);
    }
  to {
    transform: translateX(-100%);
  }
}
    to {
      transform: translateY(-100%);
    }
@keyframes slide-in-vertical {
  50% {
    transform: translateY(0);
  }
  @keyframes slide-in-vertical-reverse {
    50% {
      transform: translateY(0);
    }
  to {
    transform: translateY(-100%);
  }
}
    to {
      transform: translateY(100%);
    }
@keyframes slide-in-vertical-reverse {
  50% {
    transform: translateY(0);
  }
  @keyframes slide-in-out {
    50% {
      transform: scaleX(1);
      transform-origin: left;
    }
  to {
    transform: translateY(100%);
  }
}
    50.1% {
      transform-origin: right;
    }
    100% {
      transform: scaleX(0);
      transform-origin: right;
    }
@keyframes slide-in-out {
  50% {
    transform: scaleX(1);
    transform-origin: left;
  }
  @keyframes fill-text-white {
    to {
      color: white;
    }
  50.1% {
    transform-origin: right;
  }
  @keyframes scale-in {
    to {
      transform: scale(1);
    }
  100% {
    transform: scaleX(0);
    transform-origin: right;
  }
}
@keyframes fill-text-white {
  to {
    color: white;
  }
}
@keyframes scale-in {
  to {
    transform: scale(1);
  }
}
</style>
src/components/VabSnow/index.vue
@@ -5,78 +5,77 @@
</template>
<script>
  export default {
    name: 'VabSnow',
    props: {
      styleObj: {
        type: Object,
        default: () => {
          return {}
        },
export default {
  name: "VabSnow",
  props: {
    styleObj: {
      type: Object,
      default: () => {
        return {};
      },
    },
    data() {
      return {}
    },
    created() {},
    mounted() {},
    methods: {},
  }
  },
  data() {
    return {};
  },
  created() {},
  mounted() {},
  methods: {},
};
</script>
<style lang="scss" scoped>
  .content {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
    filter: drop-shadow(0 0 10px white);
  }
.content {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
  filter: drop-shadow(0 0 10px white);
}
  @function random_range($min, $max) {
    $rand: random();
    $random_range: $min + floor($rand * (($max - $min) + 1));
@function random_range($min, $max) {
  $rand: random();
  $random_range: $min + floor($rand * (($max - $min) + 1));
    @return $random_range;
  }
  @return $random_range;
}
  .snow {
    $total: 200;
.snow {
  $total: 200;
    position: absolute;
    width: 10px;
    height: 10px;
    background: white;
    border-radius: 50%;
  position: absolute;
  width: 10px;
  height: 10px;
  background: white;
  border-radius: 50%;
    @for $i from 1 through $total {
      $random-x: random(1000000) * 0.0001vw;
      $random-offset: random_range(-100000, 100000) * 0.0001vw;
      $random-x-end: $random-x + $random-offset;
      $random-x-end-yoyo: $random-x + ($random-offset / 2);
      $random-yoyo-time: random_range(30000, 80000) / 100000;
      $random-yoyo-y: $random-yoyo-time * 100vh;
      $random-scale: random(10000) * 0.0001;
      $fall-duration: random_range(10, 30) * 1s;
      $fall-delay: random(30) * -1s;
  @for $i from 1 through $total {
    $random-x: random(1000000) * 0.0001vw;
    $random-offset: random_range(-100000, 100000) * 0.0001vw;
    $random-x-end: $random-x + $random-offset;
    $random-x-end-yoyo: $random-x + ($random-offset / 2);
    $random-yoyo-time: random_range(30000, 80000) / 100000;
    $random-yoyo-y: $random-yoyo-time * 100vh;
    $random-scale: random(10000) * 0.0001;
    $fall-duration: random_range(10, 30) * 1s;
    $fall-delay: random(30) * -1s;
      &:nth-child(#{$i}) {
        opacity: random(10000) * 0.0001;
        transform: translate($random-x, -10px) scale($random-scale);
        animation: fall-#{$i} $fall-duration $fall-delay linear infinite;
    &:nth-child(#{$i}) {
      opacity: random(10000) * 0.0001;
      transform: translate($random-x, -10px) scale($random-scale);
      animation: fall-#{$i} $fall-duration $fall-delay linear infinite;
    }
    @keyframes fall-#{$i} {
      #{percentage($random-yoyo-time)} {
        transform: translate($random-x-end, $random-yoyo-y) scale($random-scale);
      }
      @keyframes fall-#{$i} {
        #{percentage($random-yoyo-time)} {
          transform: translate($random-x-end, $random-yoyo-y)
            scale($random-scale);
        }
        to {
          transform: translate($random-x-end-yoyo, 100vh) scale($random-scale);
        }
      to {
        transform: translate($random-x-end-yoyo, 100vh) scale($random-scale);
      }
    }
  }
}
</style>
src/components/VabUpload/index.vue
@@ -75,181 +75,181 @@
</template>
<script>
  import { baseURL, tokenName } from '@/config'
import { baseURL, tokenName } from "@/config";
  export default {
    name: 'VabUpload',
    props: {
      url: {
        type: String,
        default: '/upload',
        required: true,
      },
      name: {
        type: String,
        default: 'file',
        required: true,
      },
      limit: {
        type: Number,
        default: 50,
        required: true,
      },
      size: {
        type: Number,
        default: 1,
        required: true,
      },
export default {
  name: "VabUpload",
  props: {
    url: {
      type: String,
      default: "/upload",
      required: true,
    },
    data() {
      return {
        show: false,
        loading: false,
        dialogVisible: false,
        dialogImageUrl: '',
        action: 'https://vab-unicloud-3a9da9.service.tcloudbase.com/upload',
        headers: {},
        fileList: [],
        picture: 'picture',
        imgNum: 0,
        imgSuccessNum: 0,
        imgErrorNum: 0,
        typeList: null,
        title: '上传',
        dialogFormVisible: false,
        data: {},
    name: {
      type: String,
      default: "file",
      required: true,
    },
    limit: {
      type: Number,
      default: 50,
      required: true,
    },
    size: {
      type: Number,
      default: 1,
      required: true,
    },
  },
  data() {
    return {
      show: false,
      loading: false,
      dialogVisible: false,
      dialogImageUrl: "",
      action: "https://vab-unicloud-3a9da9.service.tcloudbase.com/upload",
      headers: {},
      fileList: [],
      picture: "picture",
      imgNum: 0,
      imgSuccessNum: 0,
      imgErrorNum: 0,
      typeList: null,
      title: "上传",
      dialogFormVisible: false,
      data: {},
    };
  },
  computed: {
    percentage() {
      if (this.allImgNum == 0) return 0;
      return this.$baseLodash.round(this.imgNum / this.allImgNum, 2) * 100;
    },
  },
  methods: {
    submitUpload() {
      this.$refs.upload.submit();
    },
    handleProgress(event, file, fileList) {
      this.loading = true;
      this.show = true;
    },
    handleChange(file, fileList) {
      if (file.size > 1048576 * this.size) {
        fileList.map((item, index) => {
          if (item === file) {
            fileList.splice(index, 1);
          }
        });
        this.fileList = fileList;
      } else {
        this.allImgNum = fileList.length;
      }
    },
    computed: {
      percentage() {
        if (this.allImgNum == 0) return 0
        return this.$baseLodash.round(this.imgNum / this.allImgNum, 2) * 100
      },
    },
    methods: {
      submitUpload() {
        this.$refs.upload.submit()
      },
      handleProgress(event, file, fileList) {
        this.loading = true
        this.show = true
      },
      handleChange(file, fileList) {
        if (file.size > 1048576 * this.size) {
          fileList.map((item, index) => {
            if (item === file) {
              fileList.splice(index, 1)
            }
          })
          this.fileList = fileList
        } else {
          this.allImgNum = fileList.length
        }
      },
      handleSuccess(response, file, fileList) {
        this.imgNum = this.imgNum + 1
        this.imgSuccessNum = this.imgSuccessNum + 1
        if (fileList.length === this.imgNum) {
          setTimeout(() => {
            this.$baseMessage(
              `上传完成! 共上传${fileList.length}张图片`,
              'success'
            )
          }, 1000)
        }
    handleSuccess(response, file, fileList) {
      this.imgNum = this.imgNum + 1;
      this.imgSuccessNum = this.imgSuccessNum + 1;
      if (fileList.length === this.imgNum) {
        setTimeout(() => {
          this.$baseMessage(
            `上传完成! 共上传${fileList.length}张图片`,
            "success"
          );
        }, 1000);
      }
        setTimeout(() => {
          this.loading = false
          this.show = false
        }, 1000)
      },
      handleError(err, file, fileList) {
        this.imgNum = this.imgNum + 1
        this.imgErrorNum = this.imgErrorNum + 1
        this.$baseMessage(
          `文件[${file.raw.name}]上传失败,文件大小为${this.$baseLodash.round(
            file.raw.size / 1024,
            0
          )}KB`,
          'error'
        )
        setTimeout(() => {
          this.loading = false
          this.show = false
        }, 1000)
      },
      handleRemove(file, fileList) {
        this.imgNum = this.imgNum - 1
        this.allNum = this.allNum - 1
      },
      handlePreview(file) {
        this.dialogImageUrl = file.url
        this.dialogVisible = true
      },
      handleExceed(files, fileList) {
        this.$baseMessage(
          `当前限制选择 ${this.limit} 个文件,本次选择了
      setTimeout(() => {
        this.loading = false;
        this.show = false;
      }, 1000);
    },
    handleError(err, file, fileList) {
      this.imgNum = this.imgNum + 1;
      this.imgErrorNum = this.imgErrorNum + 1;
      this.$baseMessage(
        `文件[${file.raw.name}]上传失败,文件大小为${this.$baseLodash.round(
          file.raw.size / 1024,
          0
        )}KB`,
        "error"
      );
      setTimeout(() => {
        this.loading = false;
        this.show = false;
      }, 1000);
    },
    handleRemove(file, fileList) {
      this.imgNum = this.imgNum - 1;
      this.allNum = this.allNum - 1;
    },
    handlePreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },
    handleExceed(files, fileList) {
      this.$baseMessage(
        `当前限制选择 ${this.limit} 个文件,本次选择了
             ${files.length}
             个文件`,
          'error'
        )
      },
      handleShow(data) {
        this.title = '上传'
        this.data = data
        this.dialogFormVisible = true
      },
      handleClose() {
        this.fileList = []
        this.picture = 'picture'
        this.allImgNum = 0
        this.imgNum = 0
        this.imgSuccessNum = 0
        this.imgErrorNum = 0
        /* if ("development" === process.env.NODE_ENV) {
        "error"
      );
    },
    handleShow(data) {
      this.title = "上传";
      this.data = data;
      this.dialogFormVisible = true;
    },
    handleClose() {
      this.fileList = [];
      this.picture = "picture";
      this.allImgNum = 0;
      this.imgNum = 0;
      this.imgSuccessNum = 0;
      this.imgErrorNum = 0;
      /* if ("development" === process.env.NODE_ENV) {
          this.api = process.env.VUE_APP_BASE_API;
        } else {
          this.api = `${window.location.protocol}//${window.location.host}`;
        }
        this.action = this.api + this.url; */
        this.dialogFormVisible = false
      },
      this.dialogFormVisible = false;
    },
  }
  },
};
</script>
<style lang="scss" scoped>
  .upload {
    height: 500px;
.upload {
  height: 500px;
    .upload-content {
      .el-upload__tip {
        display: block;
        height: 30px;
        line-height: 30px;
  .upload-content {
    .el-upload__tip {
      display: block;
      height: 30px;
      line-height: 30px;
    }
    ::v-deep {
      .el-upload--picture-card {
        width: 128px;
        height: 128px;
        margin: 3px 8px 8px 8px;
        border: 2px dashed #c0ccda;
      }
      ::v-deep {
        .el-upload--picture-card {
      .el-upload-list--picture {
        margin-bottom: 20px;
      }
      .el-upload-list--picture-card {
        .el-upload-list__item {
          width: 128px;
          height: 128px;
          margin: 3px 8px 8px 8px;
          border: 2px dashed #c0ccda;
        }
        .el-upload-list--picture {
          margin-bottom: 20px;
        }
        .el-upload-list--picture-card {
          .el-upload-list__item {
            width: 128px;
            height: 128px;
            margin: 3px 8px 8px 8px;
          }
        }
      }
    }
  }
}
</style>
src/config/net.config.js
@@ -4,20 +4,20 @@
const network = {
  // 默认的接口地址 如果是开发环境和生产环境走vab-mock-server,当然你也可以选择自己配置成需要的接口地址
  baseURL:
    process.env.NODE_ENV === 'development'
      ? 'http://192.168.20.10:9696/api/v1/'
      : 'http://192.168.20.10:9696/api/v1/',
    process.env.NODE_ENV === "development"
      ? "http://192.168.20.189:9696/api/v1/"
      : "http://192.168.20.189:9696/api/v1/",
  //配后端数据的接收方式application/json;charset=UTF-8或者application/x-www-form-urlencoded;charset=UTF-8
  contentType: 'application/json;charset=UTF-8',
  contentType: "application/json;charset=UTF-8",
  //消息框消失时间
  messageDuration: 3000,
  //最长请求时间
  requestTimeout: 5000,
  //操作正常code,支持String、Array、int多种类型
  successCode: [200, 0],
  successCode: [200, 204, 0],
  //登录失效code
  invalidCode: 402,
  //无权限code
  noPermissionCode: 401,
}
module.exports = network
};
module.exports = network;
src/config/permission.js
@@ -2,83 +2,86 @@
 * @author chuzhixin 1204505056@qq.com (不想保留author可删除)
 * @description 路由守卫,目前两种模式:all模式与intelligence模式
 */
import router from '@/router'
import store from '@/store'
import VabProgress from 'nprogress'
import 'nprogress/nprogress.css'
import getPageTitle from '@/utils/pageTitle'
import router from "@/router";
import store from "@/store";
import VabProgress from "nprogress";
import "nprogress/nprogress.css";
import getPageTitle from "@/utils/pageTitle";
import {
  authentication,
  loginInterception,
  progressBar,
  recordRoute,
  routesWhiteList,
} from '@/config'
} from "@/config";
VabProgress.configure({
  easing: 'ease',
  easing: "ease",
  speed: 500,
  trickleSpeed: 200,
  showSpinner: false,
})
});
router.beforeResolve(async (to, from, next) => {
  if (progressBar) VabProgress.start()
  let hasToken = store.getters['user/accessToken']
  if (progressBar) VabProgress.start();
  let hasToken = store.getters["user/accessToken"];
  if (!loginInterception) hasToken = true
  if (!loginInterception) hasToken = true;
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      if (progressBar) VabProgress.done()
    if (to.path === "/login") {
      next({ path: "/" });
      if (progressBar) VabProgress.done();
    } else {
      const hasPermissions =
        store.getters['user/permissions'] &&
        store.getters['user/permissions'].length > 0
        store.getters["user/permissions"] &&
        store.getters["user/permissions"].length > 0;
      if (hasPermissions) {
        next()
        next();
      } else {
        try {
          let permissions
          let permissions;
          if (!loginInterception) {
            //settings.js loginInterception为false时,创建虚拟权限
            await store.dispatch('user/setPermissions', ['admin'])
            permissions = ['admin']
            await store.dispatch("user/setPermissions", ["admin"]);
            permissions = ["admin"];
          } else {
            permissions = await store.dispatch('user/getUserInfo')
            permissions = await store.dispatch("user/getUserInfo");
          }
          let accessRoutes = []
          if (authentication === 'intelligence') {
            accessRoutes = await store.dispatch('routes/setRoutes', permissions)
          } else if (authentication === 'all') {
            accessRoutes = await store.dispatch('routes/setAllRoutes')
          let accessRoutes = [];
          if (authentication === "intelligence") {
            accessRoutes = await store.dispatch(
              "routes/setRoutes",
              permissions
            );
          } else if (authentication === "all") {
            accessRoutes = await store.dispatch("routes/setAllRoutes");
          }
          accessRoutes.forEach((item) => {
            router.addRoute(item)
          })
          next({ ...to, replace: true })
            router.addRoute(item);
          });
          next({ ...to, replace: true });
        } catch {
          await store.dispatch('user/resetAccessToken')
          if (progressBar) VabProgress.done()
          await store.dispatch("user/resetAccessToken");
          if (progressBar) VabProgress.done();
        }
      }
    }
  } else {
    if (routesWhiteList.indexOf(to.path) !== -1) {
      next()
      next();
    } else {
      if (recordRoute) {
        next(`/login?redirect=${to.path}`)
        next(`/login?redirect=${to.path}`);
      } else {
        next('/login')
        next("/login");
      }
      if (progressBar) VabProgress.done()
      if (progressBar) VabProgress.done();
    }
  }
  document.title = getPageTitle(to.meta.title)
})
  document.title = getPageTitle(to.meta.title);
});
router.afterEach(() => {
  if (progressBar) VabProgress.done()
})
  if (progressBar) VabProgress.done();
});
src/config/setting.config.js
@@ -3,25 +3,25 @@
 */
const setting = {
  // 开发以及部署时的URL
  publicPath: '',
  publicPath: "",
  // 生产环境构建文件的目录名
  outputDir: 'dist',
  outputDir: "dist",
  // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
  assetsDir: 'static',
  assetsDir: "static",
  // 开发环境每次保存时是否输出为eslint编译警告
  lintOnSave: false,
  // 进行编译的依赖
  transpileDependencies: [],
  //标题 (包括初次加载雪花屏的标题 页面的标题 浏览器的标题)
  title: 'Basic自动构建系统',
  title: "Basic自动构建系统",
  //简写
  abbreviation: 'vab',
  abbreviation: "vab",
  //开发环境端口号
  devPort: '81',
  devPort: "81",
  //版本号
  version: process.env.VUE_APP_VERSION,
  //这一项非常重要!请务必保留MIT协议下package.json及copyright作者信息 即可免费商用,不遵守此项约定你将无法使用该框架,如需自定义版权信息请联系QQ1204505056
  copyright: 'vab',
  copyright: "vab",
  //是否显示页面底部自定义版权信息
  footerCopyright: true,
  //是否显示顶部进度条
@@ -29,42 +29,42 @@
  //缓存路由的最大数量
  keepAliveMaxNum: 99,
  // 路由模式,可选值为 history 或 hash
  routerMode: 'hash',
  routerMode: "hash",
  //不经过token校验的路由
  routesWhiteList: ['/login', '/register', '/404', '/401'],
  routesWhiteList: ["/login", "/register", "/404", "/401"],
  //加载时显示文字
  loadingText: '正在加载中...',
  loadingText: "正在加载中...",
  //token名称
  tokenName: 'accessToken',
  tokenName: "access_token",
  //token在localStorage、sessionStorage存储的key的名称
  tokenTableName: 'vue-admin-beautiful-2021',
  tokenTableName: "va-integrate",
  //token存储位置localStorage sessionStorage
  storage: 'localStorage',
  storage: "localStorage",
  //token失效回退到登录页时是否记录本次的路由
  recordRoute: true,
  //是否显示logo,不显示时设置false,显示时请填写remixIcon图标名称,暂时只支持设置remixIcon
  logo: 'vuejs-fill',
  logo: "vuejs-fill",
  //是否显示在页面高亮错误
  errorLog: ['development', 'production'],
  errorLog: ["development", "production"],
  //是否开启登录拦截
  loginInterception: true,
  //是否开启登录RSA加密
  loginRSA: true,
  //intelligence和all两种方式,前者后端权限只控制permissions不控制view文件的import(前后端配合,减轻后端工作量),all方式完全交给后端前端只负责加载
  authentication: 'intelligence',
  authentication: "intelligence",
  //vertical布局时是否只保持一个子菜单的展开
  uniqueOpened: true,
  //vertical布局时默认展开的菜单path,使用逗号隔开建议只展开一个
  defaultOopeneds: ['/vab'],
  defaultOopeneds: ["/vab"],
  //需要加loading层的请求,防止重复提交
  debounce: ['doEdit'],
  debounce: ["doEdit"],
  //需要自动注入并加载的模块
  providePlugin: { maptalks: 'maptalks', 'window.maptalks': 'maptalks' },
  providePlugin: { maptalks: "maptalks", "window.maptalks": "maptalks" },
  //npm run build时是否自动生成7z压缩包
  build7z: false,
  //代码生成机生成在view下的文件夹名称
  templateFolder: 'project',
  templateFolder: "project",
  //是否显示终端donation打印
  donation: false,
}
module.exports = setting
};
module.exports = setting;
src/layouts/components/VabAvatar/index.vue
@@ -10,51 +10,159 @@
    </span>
    <el-dropdown-menu slot="dropdown">
      <el-dropdown-item command="password" divided>修改密码</el-dropdown-item>
      <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
    </el-dropdown-menu>
    <el-dialog
      title="修改密码"
      :visible.sync="dialogVisible"
      width="25%"
      :modal="false"
      :close-on-click-modal="false"
      destroy-on-close
    >
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="原密码" prop="oldPassword">
          <el-input
            v-model.trim="form.oldPassword"
            autocomplete="off"
            show-password
          ></el-input>
        </el-form-item>
        <el-form-item label="新密码" prop="newPassword">
          <el-input
            v-model.trim="form.newPassword"
            autocomplete="off"
            show-password
          ></el-input>
        </el-form-item>
        <el-form-item label="确认密码" prop="confirmPassword">
          <el-input
            v-model.trim="form.confirmPassword"
            autocomplete="off"
            show-password
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="resetPassword">确 定</el-button>
      </span>
    </el-dialog>
  </el-dropdown>
</template>
<script>
import { mapGetters } from 'vuex'
import { recordRoute } from '@/config'
import { mapGetters } from "vuex";
import { recordRoute } from "@/config";
import { updatePassword } from "@/api/user";
export default {
  name: 'VabAvatar',
  name: "VabAvatar",
  data() {
    var validatePass = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请再次输入密码"));
      } else if (value.length < 6) {
        callback(new Error("请输入6位以上密码"));
      } else {
        callback();
      }
    };
    var validatePass2 = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请再次输入密码"));
      } else if (value !== this.form.newPassword) {
        callback(new Error("两次输入密码不一致!"));
      } else {
        callback();
      }
    };
    return {
      form: {},
      rules: {
        oldPassword: [
          {
            required: true,
            trigger: "blur",
            message: "原密码不能为空",
          },
        ],
        newPassword: [
          {
            validator: validatePass,
            required: true,
            trigger: "blur",
          },
        ],
        confirmPassword: [
          {
            validator: validatePass2,
            required: true,
            trigger: "blur",
          },
        ],
      },
      dialogVisible: false,
    };
  },
  computed: {
    ...mapGetters({
      avatar: 'user/avatar',
      username: 'user/username',
      avatar: "user/avatar",
      username: "user/username",
      userId: "user/userId",
    }),
  },
  methods: {
    handleCommand(command) {
      switch (command) {
        case 'logout':
          this.logout()
          break
        case "logout":
          this.logout();
          break;
        case "password":
          this.dialogVisible = true;
          this.form = {};
          break;
      }
    },
    personalCenter() {
      this.$router.push('/personalCenter/personalCenter')
    },
    logout() {
      this.$baseConfirm(
        '您确定要退出' + this.$baseTitle + '吗?',
        "您确定要退出" + this.$baseTitle + "吗?",
        null,
        async () => {
          await this.$store.dispatch('user/logout')
          await this.$store.dispatch("user/logout");
          if (recordRoute) {
            const fullPath = this.$route.fullPath
            this.$router.push(`/login?redirect=${fullPath}`)
            const fullPath = this.$route.fullPath;
            this.$router.push(`/login?redirect=${fullPath}`);
          } else {
            this.$router.push('/login')
            this.$router.push("/login");
          }
        }
      )
      );
    },
    resetPassword() {
      this.$refs["form"].validate(async (valid) => {
        if (valid) {
          let rsp = await updatePassword(this.userId, {
            oldPassword: this.form.oldPassword,
            newPassword: this.form.newPassword,
          });
          if (rsp && rsp.success) {
            this.$baseMessage(rsp.msg, "success");
            this.$refs["form"].resetFields();
            this.dialogVisible = false;
          }
        }
      });
    },
    handleClose() {
      console.log("close");
      this.$refs["form"].resetFields();
    },
  },
}
};
</script>
<style lang="scss" scoped>
.avatar-dropdown {
src/layouts/components/VabNavBar/index.vue
@@ -16,7 +16,12 @@
        <div class="right-panel">
          <vab-error-log />
          <vab-full-screen-bar @refresh="refreshRoute" />
          <vab-icon title="重载所有路由" :pulse="pulse" :icon="['fas', 'redo']" @click="refreshRoute" />
          <vab-icon
            title="重载所有路由"
            :pulse="pulse"
            :icon="['fas', 'redo']"
            @click="refreshRoute"
          />
          <vab-avatar />
          <!--  <vab-icon
            title="退出系统"
@@ -30,39 +35,39 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { mapActions, mapGetters } from "vuex";
export default {
  name: 'VabNavBar',
  name: "VabNavBar",
  data() {
    return {
      pulse: false,
    }
    };
  },
  computed: {
    ...mapGetters({
      collapse: 'settings/collapse',
      visitedRoutes: 'tabsBar/visitedRoutes',
      device: 'settings/device',
      routes: 'routes/routes',
      collapse: "settings/collapse",
      visitedRoutes: "tabsBar/visitedRoutes",
      device: "settings/device",
      routes: "routes/routes",
    }),
  },
  methods: {
    ...mapActions({
      changeCollapse: 'settings/changeCollapse',
      changeCollapse: "settings/changeCollapse",
    }),
    handleCollapse() {
      this.changeCollapse()
      this.changeCollapse();
    },
    async refreshRoute() {
      this.$baseEventBus.$emit('reload-router-view')
      this.pulse = true
      this.$baseEventBus.$emit("reload-router-view");
      this.pulse = true;
      setTimeout(() => {
        this.pulse = false
      }, 1000)
        this.pulse = false;
      }, 1000);
    },
  },
}
};
</script>
<style lang="scss" scoped>
src/plugins/echarts.js
@@ -1,4 +1,4 @@
import 'echarts'
import VabChart from 'vue-echarts'
import "echarts";
import VabChart from "vue-echarts";
export default VabChart
export default VabChart;
src/plugins/element.js
@@ -1,9 +1,9 @@
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/display.css'
import Vue from "vue";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/display.css";
import '@/styles/element-variables.scss'
import "@/styles/element-variables.scss";
Vue.use(ElementUI, {
  size: 'small',
})
  size: "small",
});
src/plugins/index.js
@@ -1,17 +1,17 @@
/* 公共引入,勿随意修改,修改时需经过确认 */
import Vue from 'vue'
import './element'
import './support'
import '@/styles/vab.scss'
import '@/remixIcon'
import '@/colorfulIcon'
import '@/config/permission'
import '@/utils/errorLog'
import './vabIcon'
import VabPermissions from 'zx-layouts/Permissions'
import Vab from '@/utils/vab'
import VabCount from 'zx-count'
import Vue from "vue";
import "./element";
import "./support";
import "@/styles/vab.scss";
import "@/remixIcon";
import "@/colorfulIcon";
import "@/config/permission";
import "@/utils/errorLog";
import "./vabIcon";
import VabPermissions from "zx-layouts/Permissions";
import Vab from "@/utils/vab";
import VabCount from "zx-count";
Vue.use(Vab)
Vue.use(VabPermissions)
Vue.use(VabCount)
Vue.use(Vab);
Vue.use(VabPermissions);
Vue.use(VabCount);
src/router/index.js
@@ -1,42 +1,42 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/layouts'
import { publicPath, routerMode } from '@/config'
import Vue from "vue";
import VueRouter from "vue-router";
import Layout from "@/layouts";
import { publicPath, routerMode } from "@/config";
Vue.use(VueRouter)
Vue.use(VueRouter);
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    path: "/login",
    component: () => import("@/views/login/index"),
    hidden: true,
  },
  {
    path: '/401',
    name: '401',
    component: () => import('@/views/401'),
    path: "/401",
    name: "401",
    component: () => import("@/views/401"),
    hidden: true,
  },
  {
    path: '/404',
    name: '404',
    component: () => import('@/views/404'),
    path: "/404",
    name: "404",
    component: () => import("@/views/404"),
    hidden: true,
  },
]
];
export const asyncRoutes = [
  {
    path: '/',
    path: "/",
    component: Layout,
    redirect: '/index',
    redirect: "/index",
    children: [
      {
        path: 'index',
        name: 'Index',
        component: () => import('@/views/index/index'),
        path: "index",
        name: "Index",
        component: () => import("@/views/index/index"),
        meta: {
          title: '首页',
          icon: 'home',
          title: "首页",
          icon: "home",
          affix: true,
        },
      },
@@ -44,31 +44,52 @@
  },
  {
    path: '/project',
    path: "/project",
    component: Layout,
    redirect: 'project',
    redirect: "project",
    // name: 'Vab',
    // alwaysShow: true,
    children: [
      {
        path: 'index',
        component: () => import('@/views/project/index'),
        name: 'Project',
        path: "index",
        component: () => import("@/views/project/index"),
        name: "Project",
        meta: {
          title: '项目',
          icon: 'box-open',
          permissions: ['admin'],
          title: "项目",
          icon: "box-open",
          affix: true,
        },
      },
    ],
  },
  {
    path: '*',
    redirect: '/404',
    path: "/user",
    component: Layout,
    redirect: "user",
    // name: 'Vab',
    // alwaysShow: true,
    children: [
      {
        path: "index",
        component: () => import("@/views/user/index"),
        name: "User",
        meta: {
          title: "用户",
          icon: "user",
          permissions: ["admin"],
        },
      },
    ],
    meta: { permissions: ["admin"] },
  },
  {
    path: "*",
    redirect: "/404",
    hidden: true,
  },
]
];
const router = new VueRouter({
  base: publicPath,
@@ -77,10 +98,10 @@
    y: 0,
  }),
  routes: constantRoutes,
})
});
export function resetRouter() {
  location.reload()
  location.reload();
}
export default router
export default router;
src/store/modules/routes.js
@@ -2,41 +2,41 @@
 * @author chuzhixin 1204505056@qq.com (不想保留author可删除)
 * @description 路由拦截状态管理,目前两种模式:all模式与intelligence模式,其中partialRoutes是菜单暂未使用
 */
import { asyncRoutes, constantRoutes } from '@/router'
import { convertRouter, filterAsyncRoutes } from '@/utils/handleRoutes'
import { asyncRoutes, constantRoutes } from "@/router";
import { convertRouter, filterAsyncRoutes } from "@/utils/handleRoutes";
const state = () => ({
  routes: [],
  partialRoutes: [],
})
});
const getters = {
  routes: (state) => state.routes,
  partialRoutes: (state) => state.partialRoutes,
}
};
const mutations = {
  setRoutes(state, routes) {
    state.routes = constantRoutes.concat(routes)
    state.routes = constantRoutes.concat(routes);
  },
  setAllRoutes(state, routes) {
    // state.routes = constantRoutes.concat(routes)
  },
  setPartialRoutes(state, routes) {
    state.partialRoutes = constantRoutes.concat(routes)
    state.partialRoutes = constantRoutes.concat(routes);
  },
}
};
const actions = {
  async setRoutes({ commit }, permissions) {
    //开源版只过滤动态路由permissions,admin不再默认拥有全部权限
    const finallyAsyncRoutes = await filterAsyncRoutes(
      [...asyncRoutes],
      permissions
    )
    commit('setRoutes', finallyAsyncRoutes)
    return finallyAsyncRoutes
    );
    commit("setRoutes", finallyAsyncRoutes);
    return finallyAsyncRoutes;
  },
  setPartialRoutes({ commit }, accessRoutes) {
    commit('setPartialRoutes', accessRoutes)
    return accessRoutes
    commit("setPartialRoutes", accessRoutes);
    return accessRoutes;
  },
}
export default { state, getters, mutations, actions }
};
export default { state, getters, mutations, actions };
src/store/modules/user.js
@@ -3,97 +3,103 @@
 * @description 登录、获取用户信息、退出登录、清除accessToken逻辑,不建议修改
 */
import Vue from 'vue'
import { getUserInfo, login, logout } from '@/api/user'
import Vue from "vue";
import { getUserInfo, login, logout } from "@/api/user";
import {
  getAccessToken,
  removeAccessToken,
  setAccessToken,
} from '@/utils/accessToken'
import { resetRouter } from '@/router'
import { title, tokenName } from '@/config'
} from "@/utils/accessToken";
import { resetRouter } from "@/router";
import { title, tokenName } from "@/config";
const state = () => ({
  accessToken: getAccessToken(),
  username: '',
  avatar: '',
  username: "",
  userId: "",
  avatar: "",
  permissions: [],
})
});
const getters = {
  accessToken: (state) => state.accessToken,
  username: (state) => state.username,
  userId: (state) => state.userId,
  avatar: (state) => state.avatar,
  permissions: (state) => state.permissions,
}
};
const mutations = {
  setAccessToken(state, accessToken) {
    state.accessToken = accessToken
    setAccessToken(accessToken)
    state.accessToken = accessToken;
    setAccessToken(accessToken);
  },
  setUsername(state, username) {
    state.username = username
    state.username = username;
  },
  setUserId(state, id) {
    state.userId = id;
  },
  setAvatar(state, avatar) {
    state.avatar = avatar
    state.avatar = avatar;
  },
  setPermissions(state, permissions) {
    state.permissions = permissions
    state.permissions = permissions;
  },
}
};
const actions = {
  setPermissions({ commit }, permissions) {
    commit('setPermissions', permissions)
    commit("setPermissions", permissions);
  },
  async login({ commit }, userInfo) {
    const { data } = await login(userInfo)
    const accessToken = data[tokenName]
    const { data } = await login(userInfo);
    const accessToken = data[tokenName];
    if (accessToken) {
      commit('setAccessToken', accessToken)
      const hour = new Date().getHours()
      commit("setAccessToken", accessToken);
      const hour = new Date().getHours();
      const thisTime =
        hour < 8
          ? '早上好'
          ? "早上好"
          : hour <= 11
          ? '上午好'
          ? "上午好"
          : hour <= 13
          ? '中午好'
          ? "中午好"
          : hour < 18
          ? '下午好'
          : '晚上好'
      Vue.prototype.$baseNotify(`欢迎登录${title}`, `${thisTime}!`)
          ? "下午好"
          : "晚上好";
      Vue.prototype.$baseNotify(`欢迎登录${title}`, `${thisTime}!`);
    } else {
      Vue.prototype.$baseMessage(
        `登录接口异常,未正确返回${tokenName}...`,
        'error'
      )
        "error"
      );
    }
  },
  async getUserInfo({ commit, state }) {
    const { data } = await getUserInfo(state.accessToken)
    const { data } = await getUserInfo(state.accessToken);
    if (!data) {
      Vue.prototype.$baseMessage('验证失败,请重新登录...', 'error')
      return false
      Vue.prototype.$baseMessage("验证失败,请重新登录...", "error");
      return false;
    }
    let { permissions, username, avatar } = data
    let { permissions, username, userId, avatar } = data;
    if (permissions && username && Array.isArray(permissions)) {
      commit('setPermissions', permissions)
      commit('setUsername', username)
      commit('setAvatar', avatar)
      return permissions
      commit("setPermissions", permissions);
      commit("setUsername", username);
      commit("setUserId", userId);
      commit("setAvatar", avatar);
      return permissions;
    } else {
      Vue.prototype.$baseMessage('用户信息接口异常', 'error')
      return false
      Vue.prototype.$baseMessage("用户信息接口异常", "error");
      return false;
    }
  },
  async logout({ dispatch }) {
    await logout(state.accessToken)
    await dispatch('resetAccessToken')
    await resetRouter()
    await logout(state.accessToken);
    await dispatch("resetAccessToken");
    await resetRouter();
  },
  resetAccessToken({ commit }) {
    commit('setPermissions', [])
    commit('setAccessToken', '')
    removeAccessToken()
    commit("setPermissions", []);
    commit("setAccessToken", "");
    removeAccessToken();
  },
}
export default { state, getters, mutations, actions }
};
export default { state, getters, mutations, actions };
src/utils/errorLog.js
@@ -1,25 +1,25 @@
import Vue from 'vue'
import store from '@/store'
import { isArray, isString } from '@/utils/validate'
import { errorLog } from '@/config'
import Vue from "vue";
import store from "@/store";
import { isArray, isString } from "@/utils/validate";
import { errorLog } from "@/config";
const needErrorLog = errorLog
const needErrorLog = errorLog;
const checkNeed = () => {
  const env = process.env.NODE_ENV
  const env = process.env.NODE_ENV;
  if (isString(needErrorLog)) {
    return env === needErrorLog
    return env === needErrorLog;
  }
  if (isArray(needErrorLog)) {
    return needErrorLog.includes(env)
    return needErrorLog.includes(env);
  }
  return false
}
  return false;
};
if (checkNeed()) {
  Vue.config.errorHandler = (err, vm, info) => {
    console.error('vue-admin-beautiful错误拦截:', err, vm, info)
    const url = window.location.href
    console.error("vue-admin-beautiful错误拦截:", err, vm, info);
    const url = window.location.href;
    Vue.nextTick(() => {
      store.dispatch('errorLog/addErrorLog', { err, vm, info, url })
    })
  }
      store.dispatch("errorLog/addErrorLog", { err, vm, info, url });
    });
  };
}
src/utils/handleRoutes.js
@@ -7,23 +7,23 @@
export function convertRouter(asyncRoutes) {
  return asyncRoutes.map((route) => {
    if (route.component) {
      if (route.component === 'Layout') {
        route.component = (resolve) => require(['@/layouts'], resolve)
      } else if (route.component === 'EmptyLayout') {
      if (route.component === "Layout") {
        route.component = (resolve) => require(["@/layouts"], resolve);
      } else if (route.component === "EmptyLayout") {
        route.component = (resolve) =>
          require(['@/layouts/EmptyLayout'], resolve)
          require(["@/layouts/EmptyLayout"], resolve);
      } else {
        const index = route.component.indexOf('views')
        const index = route.component.indexOf("views");
        const path =
          index > 0 ? route.component.slice(index) : `views/${route.component}`
        route.component = (resolve) => require([`@/${path}`], resolve)
          index > 0 ? route.component.slice(index) : `views/${route.component}`;
        route.component = (resolve) => require([`@/${path}`], resolve);
      }
    }
    if (route.children && route.children.length)
      route.children = convertRouter(route.children)
    if (route.children && route.children.length === 0) delete route.children
    return route
  })
      route.children = convertRouter(route.children);
    if (route.children && route.children.length === 0) delete route.children;
    return route;
  });
}
/**
@@ -35,9 +35,9 @@
 */
function hasPermission(permissions, route) {
  if (route.meta && route.meta.permissions) {
    return permissions.some((role) => route.meta.permissions.includes(role))
    return permissions.some((role) => route.meta.permissions.includes(role));
  } else {
    return true
    return true;
  }
}
@@ -49,15 +49,15 @@
 * @returns {[]}
 */
export function filterAsyncRoutes(routes, permissions) {
  const finallyRoutes = []
  const finallyRoutes = [];
  routes.forEach((route) => {
    const item = { ...route }
    const item = { ...route };
    if (hasPermission(permissions, item)) {
      if (item.children) {
        item.children = filterAsyncRoutes(item.children, permissions)
        item.children = filterAsyncRoutes(item.children, permissions);
      }
      finallyRoutes.push(item)
      finallyRoutes.push(item);
    }
  })
  return finallyRoutes
  });
  return finallyRoutes;
}
src/utils/request.js
@@ -1,5 +1,5 @@
import Vue from 'vue'
import axios from 'axios'
import Vue from "vue";
import axios from "axios";
import {
  baseURL,
  contentType,
@@ -10,116 +10,118 @@
  successCode,
  tokenName,
  loginInterception,
} from '@/config'
import store from '@/store'
import qs from 'qs'
import router from '@/router'
import { isArray } from '@/utils/validate'
} from "@/config";
import store from "@/store";
import qs from "qs";
import router from "@/router";
import { isArray } from "@/utils/validate";
let loadingInstance
let loadingInstance;
/**
 * @author chuzhixin 1204505056@qq.com (不想保留author可删除)
 * @description 处理code异常
 * @param {*} code
 * @param {*} msg
 */
const handleCode = (code, msg) => {
  console.log(code, msg);
  switch (code) {
    case invalidCode:
      Vue.prototype.$baseMessage(msg || `后端接口${code}异常`, 'error')
      store.dispatch('user/resetAccessToken').catch(() => {})
      Vue.prototype.$baseMessage(msg || `后端接口${code}异常`, "error");
      store.dispatch("user/resetAccessToken").catch(() => {});
      if (loginInterception) {
        location.reload()
        location.reload();
      }
      break
      break;
    case noPermissionCode:
      router.push({ path: '/401' }).catch(() => {})
      break
      router.push({ path: "/401" }).catch(() => {});
      break;
    default:
      Vue.prototype.$baseMessage(msg || `后端接口${code}异常`, 'error')
      break
      Vue.prototype.$baseMessage(msg || `后端接口${code}异常`, "error");
      break;
  }
}
};
const instance = axios.create({
  baseURL,
  timeout: requestTimeout,
  headers: {
    'Content-Type': contentType,
    "Content-Type": contentType,
  },
})
});
instance.interceptors.request.use(
  (config) => {
    if (store.getters['user/accessToken']) {
      config.headers[tokenName] = store.getters['user/accessToken']
    if (store.getters["user/accessToken"]) {
      config.headers["Authorization"] =
        "Bearer " + store.getters["user/accessToken"];
    }
    //这里会过滤所有为空、0、false的key,如果不需要请自行注释
    if (config.data)
      config.data = Vue.prototype.$baseLodash.pickBy(
        config.data,
        Vue.prototype.$baseLodash.identity
      )
      );
    if (
      config.data &&
      config.headers['Content-Type'] ===
        'application/x-www-form-urlencoded;charset=UTF-8'
      config.headers["Content-Type"] ===
        "application/x-www-form-urlencoded;charset=UTF-8"
    )
      config.data = qs.stringify(config.data)
      config.data = qs.stringify(config.data);
    if (debounce.some((item) => config.url.includes(item)))
      loadingInstance = Vue.prototype.$baseLoading()
    return config
      loadingInstance = Vue.prototype.$baseLoading();
    return config;
  },
  (error) => {
    return Promise.reject(error)
    return Promise.reject(error);
  }
)
);
instance.interceptors.response.use(
  (response) => {
    if (loadingInstance) loadingInstance.close()
    if (loadingInstance) loadingInstance.close();
    const { data, config } = response
    const { code, msg } = data
    const { data, config } = response;
    const { code, msg } = data;
    // 操作正常Code数组
    const codeVerificationArray = isArray(successCode)
      ? [...successCode]
      : [...[successCode]]
      : [...[successCode]];
    // 是否操作正常
    if (codeVerificationArray.includes(code)) {
      return data
      return data;
    } else {
      handleCode(code, msg)
      handleCode(code, msg);
      return Promise.reject(
        'vue-admin-beautiful请求异常拦截:' +
          JSON.stringify({ url: config.url, code, msg }) || 'Error'
      )
        "请求异常拦截:" + JSON.stringify({ url: config.url, code, msg }) ||
          "Error"
      );
    }
  },
  (error) => {
    if (loadingInstance) loadingInstance.close()
    const { response, message } = error
    if (loadingInstance) loadingInstance.close();
    const { response, message } = error;
    if (error.response && error.response.data) {
      const { status, data } = response
      handleCode(status, data.msg || message)
      return Promise.reject(error)
      const { status, data } = response;
      handleCode(status, data.msg || message);
      return Promise.reject(error);
    } else {
      let { message } = error
      if (message === 'Network Error') {
        message = '后端接口连接异常'
      let { message } = error;
      if (message === "Network Error") {
        message = "后端接口连接异常";
      }
      if (message.includes('timeout')) {
        message = '后端接口请求超时'
      if (message.includes("timeout")) {
        message = "后端接口请求超时";
      }
      if (message.includes('Request failed with status code')) {
        const code = message.substr(message.length - 3)
        message = '后端接口' + code + '异常'
      if (message.includes("Request failed with status code")) {
        const code = message.substr(message.length - 3);
        message = "后端接口" + code + "异常";
      }
      Vue.prototype.$baseMessage(message || `后端接口未知异常`, 'error')
      return Promise.reject(error)
      Vue.prototype.$baseMessage(message || `后端接口未知异常`, "error");
      return Promise.reject(error);
    }
  }
)
);
export default instance
export default instance;
src/utils/validate.js
@@ -5,7 +5,7 @@
 * @returns {boolean}
 */
export function isExternal(path) {
  return /^(https?:|mailto:|tel:)/.test(path)
  return /^(https?:|mailto:|tel:)/.test(path);
}
/**
@@ -15,7 +15,7 @@
 * @returns {boolean}
 */
export function isPassword(str) {
  return str.length >= 6
  return str.length >= 5;
}
/**
@@ -25,8 +25,8 @@
 * @returns {boolean}
 */
export function isNumber(value) {
  const reg = /^[0-9]*$/
  return reg.test(value)
  const reg = /^[0-9]*$/;
  return reg.test(value);
}
/**
@@ -36,8 +36,8 @@
 * @returns {boolean}
 */
export function isName(value) {
  const reg = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/
  return reg.test(value)
  const reg = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/;
  return reg.test(value);
}
/**
@@ -48,8 +48,8 @@
 */
export function isIP(ip) {
  const reg =
    /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
  return reg.test(ip)
    /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
  return reg.test(ip);
}
/**
@@ -60,8 +60,8 @@
 */
export function isUrl(url) {
  const reg =
    /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
  return reg.test(url)
    /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
  return reg.test(url);
}
/**
@@ -71,8 +71,8 @@
 * @returns {boolean}
 */
export function isLowerCase(str) {
  const reg = /^[a-z]+$/
  return reg.test(str)
  const reg = /^[a-z]+$/;
  return reg.test(str);
}
/**
@@ -82,8 +82,8 @@
 * @returns {boolean}
 */
export function isUpperCase(str) {
  const reg = /^[A-Z]+$/
  return reg.test(str)
  const reg = /^[A-Z]+$/;
  return reg.test(str);
}
/**
@@ -93,8 +93,8 @@
 * @returns {boolean}
 */
export function isAlphabets(str) {
  const reg = /^[A-Za-z]+$/
  return reg.test(str)
  const reg = /^[A-Za-z]+$/;
  return reg.test(str);
}
/**
@@ -104,7 +104,7 @@
 * @returns {boolean}
 */
export function isString(str) {
  return typeof str === 'string' || str instanceof String
  return typeof str === "string" || str instanceof String;
}
/**
@@ -114,10 +114,10 @@
 * @returns {arg is any[]|boolean}
 */
export function isArray(arg) {
  if (typeof Array.isArray === 'undefined') {
    return Object.prototype.toString.call(arg) === '[object Array]'
  if (typeof Array.isArray === "undefined") {
    return Object.prototype.toString.call(arg) === "[object Array]";
  }
  return Array.isArray(arg)
  return Array.isArray(arg);
}
/**
@@ -128,8 +128,8 @@
 */
export function isPort(str) {
  const reg =
    /^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
  return reg.test(str)
    /^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/;
  return reg.test(str);
}
/**
@@ -139,8 +139,8 @@
 * @returns {boolean}
 */
export function isPhone(str) {
  const reg = /^1\d{10}$/
  return reg.test(str)
  const reg = /^1\d{10}$/;
  return reg.test(str);
}
/**
@@ -151,8 +151,8 @@
 */
export function isIdCard(str) {
  const reg =
    /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
  return reg.test(str)
    /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
  return reg.test(str);
}
/**
@@ -162,8 +162,8 @@
 * @returns {boolean}
 */
export function isEmail(str) {
  const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
  return reg.test(str)
  const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
  return reg.test(str);
}
/**
@@ -173,8 +173,8 @@
 * @returns {boolean}
 */
export function isChina(str) {
  const reg = /^[\u4E00-\u9FA5]{2,4}$/
  return reg.test(str)
  const reg = /^[\u4E00-\u9FA5]{2,4}$/;
  return reg.test(str);
}
/**
@@ -187,10 +187,10 @@
  return (
    str == null ||
    false ||
    str === '' ||
    str.trim() === '' ||
    str.toLocaleLowerCase().trim() === 'null'
  )
    str === "" ||
    str.trim() === "" ||
    str.toLocaleLowerCase().trim() === "null"
  );
}
/**
@@ -201,8 +201,8 @@
 */
export function isTel(str) {
  const reg =
    /^(400|800)([0-9\\-]{7,10})|(([0-9]{4}|[0-9]{3})(-| )?)?([0-9]{7,8})((-| |转)*([0-9]{1,4}))?$/
  return reg.test(str)
    /^(400|800)([0-9\\-]{7,10})|(([0-9]{4}|[0-9]{3})(-| )?)?([0-9]{7,8})((-| |转)*([0-9]{1,4}))?$/;
  return reg.test(str);
}
/**
@@ -212,6 +212,6 @@
 * @returns {boolean}
 */
export function isNum(str) {
  const reg = /^\d+(\.\d{1,2})?$/
  return reg.test(str)
  const reg = /^\d+(\.\d{1,2})?$/;
  return reg.test(str);
}
src/views/login/index.vue
@@ -1,11 +1,5 @@
<template>
  <div class="login-container">
    <el-alert
      title="beautiful boys and girls欢迎加入vue-admin-beautifulQQ群:972435319"
      type="success"
      :closable="false"
      style="position: fixed"
    ></el-alert>
    <el-row>
      <el-col :xs="24" :sm="24" :md="12" :lg="16" :xl="16">
        <div style="color: transparent">占位符</div>
@@ -18,8 +12,7 @@
          class="login-form"
          label-position="left"
        >
          <div class="title">hello !</div>
          <div class="title-tips">欢迎来到{{ title }}!</div>
          <div class="title-tips">欢迎使用{{ title }}!</div>
          <el-form-item style="margin-top: 40px" prop="username">
            <span class="svg-container svg-container-admin">
              <vab-icon :icon="['fas', 'user']" />
@@ -64,9 +57,6 @@
          >
            登录
          </el-button>
          <router-link to="/register">
            <div style="margin-top: 20px">注册</div>
          </router-link>
        </el-form>
      </el-col>
    </el-row>
@@ -95,7 +85,7 @@
    };
    const validatePassword = (rule, value, callback) => {
      if (!isPassword(value)) {
        callback(new Error("密码不能少于6位"));
        callback(new Error("密码不能少于5位"));
      } else {
        callback();
      }
@@ -142,13 +132,6 @@
  beforeDestroy() {
    document.body.style.overflow = "auto";
  },
  mounted() {
    this.form.username = "admin";
    this.form.password = "123456";
    setTimeout(() => {
      this.handleLogin();
    }, 3000);
  },
  methods: {
    handlePassword() {
      this.passwordType === "password"
@@ -170,7 +153,7 @@
                  ? "/"
                  : this.redirect;
              this.$router.push(routerPath).catch(() => {});
              this.loading = false;
              this.loading = true;
            })
            .catch(() => {
              this.loading = false;
src/views/project/components/ProjectEdit.vue
@@ -22,10 +22,13 @@
            <el-input
              v-model.trim="form.srcPassword"
              autocomplete="off"
            ></el-input>
              show-password
            >
              ></el-input
            >
          </el-form-item>
          <el-form-item label="项目描述" prop="desc">
            <el-input v-model.trim="form.desc" autocomplete="off"></el-input>
            <el-input v-model="form.desc" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="编译分支" prop="branch">
            <el-input v-model.trim="form.branch" autocomplete="off"></el-input>
@@ -103,8 +106,10 @@
    save() {
      this.$refs["form"].validate(async (valid) => {
        if (valid) {
          const { msg } = await doEdit(this.form);
          this.$baseMessage(msg, "success");
          const rsp = await doEdit(this.form);
          if (rsp && rsp.success) {
            this.$baseMessage(rsp.msg, "success");
          }
          this.$refs["form"].resetFields();
          this.dialogFormVisible = false;
          this.$emit("fetch-data");
src/views/project/index.vue
@@ -68,16 +68,24 @@
            <el-table-column prop="state" width="150">
              <template #default="{ row }">
                <el-tag>{{ packageState[row.state] }}</el-tag>
                <el-tag :type="row.state == 0 ? 'success' : 'danger'">{{
                  packageState[row.state]
                }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column width="200">
            <el-table-column width="300">
              <template #default="scope">
                <el-button size="small" @click="handlePublish(scope.row)"
                  >发布</el-button
                >
                <el-button size="small" @click="handleDownload(scope.row)"
                  >下载</el-button
                >
                <el-button
                  size="small"
                  :disabled="scope.row.state === 0"
                  @click="handleRebuild(scope.row)"
                  >重置</el-button
                >
              </template>
            </el-table-column>
@@ -106,7 +114,9 @@
      <!-- <el-table-column show-overflow-tooltip label="当前版本" prop="latestVersion"></el-table-column> -->
      <el-table-column label="状态">
        <template #default="{ row }">
          <el-tag>{{ row.state | stateFilter }}</el-tag>
          <el-tag :type="row.state == 2 ? 'danger' : 'success'">{{
            row.state | stateFilter
          }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column
@@ -147,7 +157,7 @@
<script>
import { getList, deletePrj, getPkgList, buildPkg } from "@/api/project";
import { publish, download } from "@/api/package";
import { publish, download, rebuild } from "@/api/package";
import TableEdit from "./components/ProjectEdit.vue";
export default {
@@ -172,15 +182,14 @@
  },
  data() {
    return {
      imgShow: true,
      list: [],
      listLoading: true,
      layout: "total, sizes, prev, pager, next, jumper",
      packageState: [
        "完成",
        "已提交",
        "排队中",
        "打包中",
        "打包完成",
        "已 提 交",
        "排 队 中",
        "打 包 中",
        "编译失败",
        "打包失败",
      ],
@@ -277,7 +286,7 @@
      const { data, total } = await getPkgList(row.id);
      this.list.forEach((item, idx) => {
        if (item.id === row.id) {
          this.list[idx].pkgList = data;
          this.list[idx].pkgList = data.reverse();
        }
      });
    },
@@ -316,6 +325,15 @@
        }
      });
    },
    handleRebuild(row) {
      rebuild(row).then((rsp) => {
        if (rsp && rsp.success) {
          this.$baseMessage(rsp.msg, "success");
        } else {
          this.$baseMessage("保存失败", "error");
        }
      });
    },
  },
};
</script>
src/views/user/components/UserEdit.vue
New file
@@ -0,0 +1,116 @@
<template>
  <el-dialog
    :title="title"
    :visible.sync="dialogFormVisible"
    width="500px"
    :close-on-click-modal="false"
    @close="close"
  >
    <el-form ref="form" :model="form" :rules="rules" label-width="80px">
      <el-form-item label="用户名" prop="username">
        <el-tag v-if="!createAction">{{ form.username }}</el-tag>
        <el-input
          v-else
          v-model.trim="form.username"
          autocomplete="off"
        ></el-input>
      </el-form-item>
      <el-form-item label="姓名" prop="name">
        <el-input v-model.trim="form.name" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="手机号" prop="tel">
        <el-input v-model.trim="form.tel" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="权限" prop="permissions">
        <el-radio-group v-model="form.permissions">
          <el-radio label="admin">管理员</el-radio>
          <el-radio label="user">用户</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item v-if="createAction" label="密码" prop="tel">
        <el-input v-model.trim="form.password" autocomplete="off"></el-input>
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button @click="close">取 消</el-button>
      <el-button type="primary" @click="save">确 定</el-button>
    </div>
  </el-dialog>
</template>
<script>
import { doEdit, register } from "@/api/user";
export default {
  name: "UserEdit",
  data() {
    return {
      activeName: "base",
      form: {
        name: "",
        username: "",
      },
      rules: {
        username: [
          { required: true, trigger: "blur", message: "请输入用户名" },
        ],
        password: [{ required: true, trigger: "blur", message: "请输入密码" }],
      },
      title: "",
      dialogFormVisible: false,
      createAction: false,
    };
  },
  created() {},
  methods: {
    showEdit(row) {
      if (!row) {
        this.title = "添加";
        this.createAction = true;
        this.form.permissions = "user";
      } else {
        this.title = "编辑";
        this.form = Object.assign({}, row);
        this.createAction = false;
      }
      this.dialogFormVisible = true;
    },
    close() {
      this.$refs["form"].resetFields();
      this.form = this.$options.data().form;
      this.dialogFormVisible = false;
      this.$emit("fetch-data");
    },
    async save() {
      if (this.createAction) {
        this.$refs["form"].validate(async (valid) => {
          if (valid) {
            let rsp = await register(this.form);
            if (rsp && rsp.success) {
              console.log(rsp);
              this.$baseMessage(rsp.msg, "success");
              this.closeDialog();
            }
          }
        });
      } else {
        let rsp = await doEdit(this.form);
        if (rsp && rsp.success) {
          this.$baseMessage(rsp.msg, "success");
          this.closeDialog();
        }
      }
    },
    closeDialog() {
      this.$refs["form"].resetFields();
      this.dialogFormVisible = false;
      this.$emit("fetch-data");
      this.form = this.$options.data().form;
    },
    handleClick(tab, event) {
      // console.log(tab, event)
    },
  },
};
</script>
src/views/user/index.vue
New file
@@ -0,0 +1,254 @@
<template>
  <div class="table-container">
    <vab-query-form>
      <vab-query-form-left-panel>
        <el-form
          ref="form"
          :model="queryForm"
          :inline="true"
          @submit.native.prevent
        >
          <el-form-item>
            <el-input
              v-model="queryForm.name"
              placeholder="姓名/用户名/手机号"
              clearable
            />
          </el-form-item>
          <el-form-item>
            <el-button
              icon="el-icon-search"
              type="primary"
              native-type="submit"
              @click="handleQuery"
              >查询</el-button
            >
          </el-form-item>
        </el-form>
      </vab-query-form-left-panel>
      <vab-query-form-right-panel>
        <el-button icon="el-icon-plus" type="primary" @click="handleAdd">
          添加</el-button
        >
        <el-button icon="el-icon-delete" type="danger" @click="handleDelete"
          >删除</el-button
        >
      </vab-query-form-right-panel>
    </vab-query-form>
    <el-table
      ref="tableSort"
      v-loading="listLoading"
      stripe
      :data="list"
      :element-loading-text="elementLoadingText"
      :height="height"
      @selection-change="setSelectRows"
    >
      <!-- <el-table-column show-overflow-tooltip type="selection" width="55"></el-table-column> -->
      <el-table-column show-overflow-tooltip label="序号" width="95">
        <template #default="scope">{{ scope.$index + 1 }}</template>
      </el-table-column>
      <el-table-column
        show-overflow-tooltip
        prop="username"
        label="用户名"
      ></el-table-column>
      <el-table-column
        show-overflow-tooltip
        prop="name"
        label="姓名"
      ></el-table-column>
      <el-table-column prop="tel" label="手机号"> </el-table-column>
      <el-table-column label="权限">
        <template #default="{ row }">
          <el-tag :type="row.permissions == 'admin' ? 'danger' : 'success'">{{
            row.permissions
          }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column
        label="创建时间"
        prop="createdAt"
        width="200"
      ></el-table-column>
      <el-table-column label="操作" width="180px">
        <template #default="{ row }">
          <el-button type="text" @click="handleEdit(row)">编辑</el-button>
          <el-button
            type="text"
            style="color: red"
            @click="handleDeleteRow(row)"
            >删除</el-button
          >
          <el-button type="text" @click="resetPassword(row)"
            >重置密码</el-button
          >
        </template>
      </el-table-column>
    </el-table>
    <el-pagination
      :background="background"
      :current-page="queryForm.pageNo"
      :layout="layout"
      :page-size="queryForm.pageSize"
      :total="total"
      @current-change="handleCurrentChange"
      @size-change="handleSizeChange"
    ></el-pagination>
    <table-edit ref="edit" @fetch-data="fetchData"></table-edit>
  </div>
</template>
<script>
import { getUsers, deleteUser, updatePassword } from "@/api/user";
import TableEdit from "./components/UserEdit.vue";
export default {
  name: "ComprehensiveTable",
  components: {
    TableEdit,
  },
  data() {
    return {
      list: [],
      listLoading: true,
      layout: "total, sizes, prev, pager, next, jumper",
      total: 0,
      background: true,
      selectRows: "",
      elementLoadingText: "正在加载...",
      queryForm: {
        pageNo: 1,
        pageSize: 20,
        name: "",
      },
    };
  },
  computed: {
    height() {
      return this.$baseTableHeight();
    },
  },
  mounted() {
    this.fetchData();
  },
  methods: {
    setSelectRows(val) {
      this.selectRows = val;
    },
    handleAdd() {
      this.$refs["edit"].showEdit();
    },
    handleEdit(row) {
      this.$refs["edit"].showEdit(row);
    },
    handleDelete(row) {
      if (this.selectRows.length > 0) {
        const ids = this.selectRows.map((item) => item.id).join();
        this.$baseConfirm("你确定要删除选中项吗", null, async () => {
          const { msg } = await doDelete({ ids: ids });
          this.$baseMessage(msg, "success");
          this.fetchData();
        });
      } else {
        this.$baseMessage("未选中任何行", "error");
        return false;
      }
    },
    handleDeleteRow(row) {
      if (row.id) {
        this.$baseConfirm("你确定要删除当前项吗", null, async () => {
          const { msg } = await deleteUser(row.id);
          this.$baseMessage(msg, "success");
          this.fetchData();
        });
      }
    },
    handleSizeChange(val) {
      this.queryForm.pageSize = val;
      this.fetchData();
    },
    handleCurrentChange(val) {
      this.queryForm.pageNo = val;
      this.fetchData();
    },
    handleQuery() {
      this.queryForm.pageNo = 1;
      this.fetchData();
    },
    async fetchData() {
      this.listLoading = true;
      this.list = [];
      const { data, total } = await getUsers(this.queryForm);
      if (data) {
        this.list = data;
      }
      this.total = total;
      setTimeout(() => {
        this.listLoading = false;
      }, 500);
    },
    resetPassword(row) {
      const h = this.$createElement;
      let desc = ""; // 这里保存输入的值
      this.$msgbox({
        // msgbox里面如果是dom元素,只能是原生的dom,如果想用elmentui,就应该手动给他添加类
        title: "重置密码",
        message: h(
          "div",
          {
            class: {
              "el-input": true,
              "el-input--small": true,
              "el-input--suffix": true,
            },
          },
          [
            h("input", {
              domProps: {
                type: "text",
                value: desc,
                placeholder: "请输入6位以上密码",
              },
              class: {
                "el-input__inner": true,
              },
              on: {
                change: function (event) {
                  desc = event.target.value; // 监听change事件,手动的将值给保存到desc中
                },
              },
            }),
          ]
        ),
        showCancelButton: true,
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        inputPattern: /^[0-9A-Za-z]{6,}$/,
        inputErrorMessage: "密码长度要大于6位",
        beforeClose: (action, instance, done) => {
          if (action === "confirm") {
            instance.confirmButtonLoading = true;
            instance.confirmButtonText = "提交...";
            updatePassword(row.id, { newPassword: desc }).then((rsp) => {
              if (rsp && rsp.success) {
                this.$baseMessage(rsp.msg, "success");
                done();
              }
              instance.confirmButtonLoading = false;
            });
          } else {
            done();
          }
        },
      });
    },
  },
};
</script>