[
  {
    "path": ".browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot dead\n\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintignore",
    "content": "src/assets\nsrc/icons\npublic\ndist\nnode_modules\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    node: true,\n  },\n  extends: ['plugin:vue/recommended', '@vue/prettier'],\n  rules: {\n    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',\n    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',\n    'vue/no-v-html': 'off',\n  },\n  parserOptions: {\n    parser: 'babel-eslint',\n  },\n  overrides: [\n    {\n      files: [\n        '**/__tests__/*.{j,t}s?(x)',\n        '**/tests/unit/**/*.spec.{j,t}s?(x)',\n      ],\n      env: {\n        jest: true,\n      },\n    },\n  ],\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.html text eol=lf\n*.css text eol=lf\n*.js text eol=lf\n*.scss text eol=lf\n*.vue text eol=lf\n*.hbs text eol=lf\n*.sh text eol=lf\n*.md text eol=lf\n*.json text eol=lf\n*.yml text eol=lf\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\ndist\n.env.local\n.env.*.local\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\npublic/video\n*.zip\n*.7z\n/src/layouts/components/zx-layouts\n/zx-templates\n/src/styles/themes/glory.scss\n/src/styles/themes/green.scss\n/src/styles/themes/dark.scss\n"
  },
  {
    "path": ".stylelintrc.js",
    "content": "module.exports = {\n  extends: ['stylelint-config-recess-order', 'stylelint-config-prettier'],\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"[vue]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"editor.quickSuggestions\": {\n    \"strings\": true\n  },\n  \"workbench.colorTheme\": \"One Monokai\",\n  \"editor.tabSize\": 2,\n  \"editor.detectIndentation\": false,\n  \"emmet.triggerExpansionOnTab\": true,\n  \"editor.formatOnSave\": true,\n  \"javascript.format.enable\": true,\n  \"git.enableSmartCommit\": true,\n  \"git.autofetch\": true,\n  \"git.confirmSync\": false,\n  \"[json]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"liveServer.settings.donotShowInfoMsg\": true,\n  \"explorer.confirmDelete\": false,\n  \"javascript.updateImportsOnFileMove.enabled\": \"always\",\n  \"typescript.updateImportsOnFileMove.enabled\": \"always\",\n  \"files.exclude\": {\n    \"**/.idea\": true\n  },\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.stylelint\": true,\n    \"source.fixAll.eslint\": true\n  },\n  \"[javascript]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[scss]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[jsonc]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[html]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"editor.suggest.snippetsPreventQuickSuggestions\": false,\n  \"prettier.htmlWhitespaceSensitivity\": \"ignore\",\n  \"prettier.vueIndentScriptAndStyle\": true,\n  \"docthis.authorName\": \"chuzhixin 1204505056@qq.com\",\n  \"docthis.includeAuthorTag\": true,\n  \"docthis.includeDescriptionTag\": true,\n  \"docthis.enableHungarianNotationEvaluation\": true,\n  \"docthis.inferTypesFromNames\": true,\n  \"vetur.format.defaultFormatter.html\": \"prettier\"\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "README.en.md",
    "content": "[简体中文](./README.md) | English\n\n<div align=\"center\"><img width=\"200\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/logo/vab.png\"/>\n<h1> vue-admin-better（element-ui） </h1>\n<p>The flying snow all over the sky is a flying note, playing out expectations with blessings. May the epidemic dissipate as soon as possible, may you no longer have regrets next year, may you be warm in winter, may you not be cold in spring, and may you have lights in the dark and an umbrella in the rain.\n</p>\n</div>\n\n[![stars](https://img.shields.io/github/stars/chuzhixin/vue-admin-beautiful?style=flat-square&logo=GitHub)](https://github.com/chuzhixin/vue-admin-beautiful)\n[![star](https://gitee.com/chu1204505056/vue-admin-better/badge/star.svg?theme=gray)](https://gitee.com/chu1204505056/vue-admin-better)\n[![license](https://img.shields.io/github/license/chuzhixin/vue-admin-beautiful?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)\n\n---\n\n# 🎉 Characteristic\n\n- 💪 40 + high quality single page\n- 💅 RBAC model + JWT permission control\n- 🌍 100000 + practical application of the project\n- 👏 Good type definition\n- 🥳 The open source version supports free commercial use\n- 🚀 Cross platform PC, mobile terminal and tablet\n- 📦 Back end route dynamic rendering\n\n## 🌐 Address\n\n- [🎉 Vue2. X + element UI (free commercial, PC, tablet and mobile phone supported)](https://vue-admin-beautiful.com/vue-admin-beautiful-element/?hmsr=github&hmpl=&hmcu=&hmkw=&hmci=)\n\n- [⚡️ Vue3. X + element plus (alpha version, free commercial, supporting PC, tablet and mobile phone)](https://vue-admin-beautiful.com/vue-admin-beautiful-element-plus/?hmsr=github&hmpl=&hmcu=&hmkw=&hmci=)\n\n- [⚡️ Vue3. X + ant design Vue (beta version, free commercial, supporting PC, tablet and mobile phone)](https://vue-admin-beautiful.com/vue-admin-beautiful-antdv/?hmsr=github&hmpl=&hmcu=&hmkw=&hmci=)\n\n- [⚡️ vue3.x + vite + vue-admin-arco](https://vue-admin-beautiful.com/vue-admin-arco/?hmsr=github&hmpl=&hmcu=&hmkw=&hmci=)\n\n- [🚀 Admin Pro demo address (vue2.x paid version, supporting PC, tablet and mobile phone)](https://vue-admin-beautiful.com/admin-pro/?hmsr=github&hmpl=&hmcu=&hmkw=&hmci=)\n\n- [🚀 Admin plus demo address (vue3.x paid version, supporting PC, tablet and mobile phone)](https://vue-admin-beautiful.com/admin-plus/?hmsr=github&hmpl=&hmcu=&hmkw=&hmci=)\n\n- [📌 Pro and plus purchase address authorization](https://vue-admin-beautiful.com/authorization/)\n\n- [🌐 Github Warehouse address](https://github.com/chuzhixin/vue-admin-beautiful?utm_source=gold_browser_extension)\n\n- [🌐 Gitee Warehouse address](https://gitee.com/chu1204505056/vue-admin-better?_from=gitee_search)\n\n- Recently, the VAB official website has been frequently attacked by DDoS. We have taken relevant preventive measures. If the website cannot be accessed, please visit the backup address\n\n## 🌐 Backup address (support automatic update of HTTPS website)\n\n- [🚀 Admin Pro demo address (paid version, supporting PC, tablet and mobile phone)](https://chu1204505056.gitee.io/admin-pro/?hmsr=github&hmpl=&hmcu=&hmkw=&hmci=)\n\n- [🚀 Admin plus demo address (vue3. X paid version, supporting PC, tablet and mobile phone)](https://chu1204505056.gitee.io/admin-plus/?hmsr=github&hmpl=&hmcu=&hmkw=&hmci=)\n\n## 📦️ Desktop applications\n\n- [Admin Pro](https://gitee.com/chu1204505056/microsoft-store/raw/master/AdminPlus.zip)\n- [Admin Plus](https://gitee.com/chu1204505056/microsoft-store/raw/master/AdminPlus.zip)\n\n## 🌱 Vue3.x vue3.0-antdv [Click switch branch](https://github.com/chuzhixin/vue-admin-better/tree/vue3.0-antdv)\n\n```bash\ngit clone -b vue3.0-antdv https://github.com/chuzhixin/vue-admin-better.git\nnpm i --registry=http://mirrors.cloud.tencent.com/npm/\nnpm run serve\n```\n\n## 🌱 Vue2.xmain [Click switch branch](https://github.com/chuzhixin/vue-admin-better/tree/master)\n\n```bash\ngit clone -b master https://github.com/chuzhixin/vue-admin-better.git\nnpm i --registry=http://mirrors.cloud.tencent.com/npm/\nnpm run serve\n```\n\n## 🍻 Front end discussion QQ group\n\n- Let's have a cup of coffee, and then contact QQ 783963206 to invite you to the discussion group. Because of the large number of users, if you haven't passed a friend's request, please contact Alipay on the Alipay payment page, whether you invite or not, you can enjoy the open source code, thank you for your support and trust, and provide the vue-admin-beautifu basic version in the group. Automatic configuration tutorial of development tools and project development documents.\n\n<table>\n<tr>\n<!-- <td>\n<img width=\"200px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/qq_group/hbm.jpg\">\n</td> -->\n<td>\n<img width=\"200px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/zfb_kf.jpg\">\n</td>\n<td>\n<img width=\"200px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/qq_group/vab-2.jpg\">\n</td>\n<td>\n<img width=\"200px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/qq_group/vab-3.jpg\">\n</td>\n</tr>\n</table>\n\n## 🙈 We promise to sponsor open source projects regularly (thank giant)\n\n<a title=\"vue\" href=\"https://opencollective.com/vuejs\" target=\"_blank\">\n<img width=\"64px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/vue.png\"/>\n</a>\n<a title=\"element-plus\" href=\"https://opencollective.com/element-plus\" target=\"_blank\">\n<img width=\"64px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/element-plus.png\"/>\n</a>\n<a title=\"ant-design-vue\" href=\"https://opencollective.com/ant-design-vue\" target=\"_blank\">\n<img width=\"64px\" src=\"https://images.opencollective.com/ant-design-vue/2ec179b/logo/256.png\"/>\n</a>\n\n## 🎨 Acknowledge\n\n| Project                                                          |\n| ---------------------------------------------------------------- |\n| [vue](https://github.com/vuejs/vue)                              |\n| [element-ui](https://github.com/ElemeFE/element)                 |\n| [element-plus](https://github.com/element-plus/element-plus)     |\n| [ant-design-vue](https://github.com/vueComponent/ant-design-vue) |\n| [mock](https://github.com/nuysoft/Mock)                          |\n| [axios](https://github.com/axios/axios)                          |\n\n## 👷 Outstanding contributors to the framework (in no order)\n\n<a href=\"https://github.com/buuing\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/36689704?s=50\"/>\n</a>\n<a href=\"https://github.com/hipi\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/22478003?s=50\"/>\n</a>\n<a href=\"https://github.com/fwfmiao\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/29328241?s=50\"/>\n</a>\n<a href=\"https://github.com/hdtopku\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/14859466?s=50\"/>\n</a>\n<a href=\"https://github.com/shaonialife\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/16135960?s=50\"/>\n</a>\n\n## 📌 Advantages and precautions\n\n```\nCompared with other open source admin frameworks, it has the following advantages:\n1. Support the front-end control routing permission intelligence and the back-end control routing permission all mode\n2. It is known that the open source Vue admin framework is the first to support the automatic generation and export function of mock\n3. More than 50 global fine configurations are provided\n4. Support SCSS automatic sorting and eslint automatic repair\n5. Axios fine encapsulation supports multiple data sources, multiple successful code arrays, and application / JSON; charset=UTF-8、application/x-www-form-urlencoded; Charset = UTF-8 multiple parameter transfer modes\n6. Support login RSA encryption\n7. Support packaging to automatically generate 7z compressed packages\n8. Support errorlog error interception\n9. Support multi theme and multi layout switching\nPrecautions for use:\n1. The project uses lf line feed instead of CRLF line feed by default. When creating a new file, please pay attention to selecting the file line feed\n2. The project uses the strictest eslint verification specification (plugin: Vue / recommended) by default. Before using it, it is recommended to configure the development tool to realize automatic repair (vscode development is recommended)\n3. The project uses the MIT open source agreement with the broadest requirements, and the MIT open source agreement can be used for free\n\n```\n\n## 💚 Suitable for people\n\n- I am developing and want to use element UI / element plus, with 1 year of front-end development experience +.\n- Familiar with vue.js technology stack and developed several practical projects with it.\n- Students who are interested in principle and technology and want to improve.\n\n## 🎉 Function map\n\n![img](https://fastly.jsdelivr.net/gh/chuzhixin/image/vip/flow.drawio.png)\n\n## 🗃️ design sketch\n\nThe following is a screenshot of the pro version:\n\n<table>\n<tr>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/2.png\">\n</td>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/6.png\">\n</td>\n</tr>\n<tr>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/8.png\">\n</td>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/9.png\">\n</td>\n</tr>\n<tr>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/3.png\">\n</td>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/5.png\">\n</td>\n</tr>\n</table>\n\n## 📄 Commercial considerations\n\nThis project can be used for commercial purposes free of charge. Please abide by the MIT agreement and keep the author's technical support statement. For customized source code copyright information, please contact customer service QQ 783963206.\n\n<!-- ## 严正声明\n\n近期发现不少游手好闲之人有组织有预谋的利用码云、知乎、掘金等网站可用国外非法网站提供的匿名手机号注册的账号 bug 冒充 vab 去攻击 vue-element-admin，iview-admin，若依，d2-admin，ant-design-vue 的行为，恶意制造对立，试图让其他开源作者卷入其中，对各位开源作者造成的影响我们深表歉意，我们欢迎 vab 的用户去体验其他更优秀的框架，vue-admin-beautiful 走到今天实属不易，被人冒充，被人发帖诋毁，被人故意发布错误言论假装发帖表扬实则为我们招骂，无意动任何人的奶酪，从 2020 年至今坚持全职维护已过一年时间，说实在的我们靠技术生存并不丢人吧，一年来感谢 vab 的用户对我们不离不弃，也希望大家越来越好，加油！ -->\n"
  },
  {
    "path": "README.md",
    "content": "<div style=\"filter: grayscale(100%)\">\n\n简体中文 | [English](./README.en.md)\n\n<div align=\"center\"><img width=\"200\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/logo/vab.png\"/>\n<h1> vue-admin-better</h1>\n\n<p>众志成城，攻坚克难，愿所有美好纷沓而来！</p>\n</div>\n\n[![stars](https://img.shields.io/github/stars/chuzhixin/vue-admin-beautiful?style=flat-square&logo=GitHub)](https://github.com/chuzhixin/vue-admin-beautiful)\n[![star](https://gitee.com/chu1204505056/vue-admin-better/badge/star.svg?theme=gray)](https://gitee.com/chu1204505056/vue-admin-better)\n[![license](https://img.shields.io/github/license/chuzhixin/vue-admin-beautiful?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)\n\n---\n\n## 🎉 特性\n\n- 💪 40+高质量单页\n- 💅 RBAC 模型 + JWT 权限控制\n- 🌍 10 万+ 项目实际应用\n- 👏 良好的类型定义\n- 🥳 开源版本支持免费商用\n- 🚀 跨平台 PC、手机端、平板\n- 📦️ 后端路由动态渲染\n\n## 🌐 地址\n\n- [🎉 vue2.x + element-ui（免费商用，支持 PC、平板、手机）](https://vue-admin-beautiful.com/vue-admin-beautiful-element/)\n\n- [⚡️ vue3.x + element-plus（alpha 版本，免费商用，支持 PC、平板、手机）](https://vue-admin-beautiful.com/vue-admin-beautiful-element-plus/)\n\n- [⚡️ vue3.x + ant-design-vue（beta 版本，免费商用，支持 PC、平板、手机）](https://vue-admin-beautiful.com/vue-admin-beautiful-antdv/)\n\n- [⚡️ vue3.x + vite + arco](https://vue-admin-beautiful.com/vue-admin-arco/)\n\n- [🚀 admin pro 演示地址（vue2.x 付费版本，支持 PC、平板、手机）](https://vue-admin-beautiful.com/admin-pro/)\n\n- [🚀 admin plus 演示地址（vue3.x 付费版本，支持 PC、平板、手机）](https://vue-admin-beautiful.com/admin-plus/)\n\n- [📌 pro 及 plus 购买地址 authorization](https://vue-admin-beautiful.com/authorization/)\n\n- [🚀 Vue Shop Vite 商城（付费版本）](https://vue-admin-beautiful.com/shop-vite/)\n\n- [🌐 github 仓库地址](https://github.com/chuzhixin/vue-admin-beautiful?utm_source=gold_browser_extension)\n\n- [🌐 码云仓库地址](https://gitee.com/chu1204505056/vue-admin-better?_from=gitee_search)\n\n## 🌐 备份地址\n\n- [🚀 admin pro 演示地址（付费版本，支持 PC、平板、手机）](https://chu1204505056.gitee.io/admin-pro/)\n\n- [🚀 admin plus 演示地址（vue3.x 付费版本，支持 PC、平板、手机）](https://chu1204505056.gitee.io/admin-plus/)\n\n## 🍻 前端讨论 QQ 群\n\n- 请我们喝杯咖啡，打赏后联系 QQ 783963206 邀请您进入讨论群（由于用户数较多，如果您打赏后未通过好友请求，请联系商家），不管您请还是不请，您都可以享受到开源的代码，感谢您的支持和信任，群内提供 vue-admin-better 基础版本、开发工具自动配置教程及项目开发文档。\n<table>\n<tr>\n<td>\n<img width=\"200px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/zfb_kf.jpg\">\n</td>\n<td>\n<img width=\"200px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/qq_group/vab-2.jpg\">\n</td>\n<td>\n<img width=\"200px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/qq_group/vab-3.jpg\">\n</td>\n</tr>\n</table>\n\n## 📦️ 桌面应用程序\n\n- [Admin Pro](https://gitee.com/chu1204505056/microsoft-store/raw/master/AdminPlus.zip)\n- [Admin Plus](https://gitee.com/chu1204505056/microsoft-store/raw/master/AdminPlus.zip)\n\n## 🌱 vue3.x vue3.0-antdv 分支（ant-design-vue）[点击切换分支](https://github.com/chuzhixin/vue-admin-better/tree/vue3.0-antdv)\n\n```bash\n# 克隆项目\ngit clone -b vue3.0-antdv https://github.com/chuzhixin/vue-admin-better.git\n# 安装依赖\nnpm i --registry=http://mirrors.cloud.tencent.com/npm/\n# 本地开发 启动项目\nnpm run serve\n```\n\n## 🌱 vue3.x arco-design [点击切换仓库](https://github.com/chuzhixin/vue-admin-arco)\n\n```bash\n# 克隆项目\ngit clone  https://github.com/chuzhixin/vue-admin-arco.git\n# 安装依赖\nnpm i --registry=http://mirrors.cloud.tencent.com/npm/\n# 本地开发 启动项目\nnpm run dev\n```\n\n## 🌱vue2.x master 分支（element-ui）[点击切换分支](https://github.com/chuzhixin/vue-admin-better/tree/master)\n\n```bash\n# 克隆项目\ngit clone -b master https://github.com/chuzhixin/vue-admin-better.git\n# 安装依赖\nnpm i --registry=http://mirrors.cloud.tencent.com/npm/\n# 本地开发 启动项目\nnpm run serve\n```\n\n## 🔊 友情链接\n\n- [OPSLI 基于 vue-admin-better 开源版的最佳实践](https://github.com/hiparker/opsli-boot)\n\n- [uView uni-app 生态最优秀的 UI 框架](https://github.com/YanxinNet/uView/)\n\n- [form-generator Element 表单设计代码生成器](https://github.com/JakHuang/form-generator/)\n\n- [wangEditor 国产最强开源富文本编辑](https://github.com/wangeditor-team/wangEditor)\n\n## 🙈 我们承诺将定期赞助的开源项目（感谢巨人）\n\n<a title=\"vue\" href=\"https://opencollective.com/vuejs\" target=\"_blank\">\n<img width=\"64px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/vue.png\"/>\n</a>\n<a title=\"element-plus\" href=\"https://opencollective.com/element-plus\" target=\"_blank\">\n<img width=\"64px\" src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/element-plus.png\"/>\n</a>\n<a title=\"ant-design-vue\" href=\"https://opencollective.com/ant-design-vue\" target=\"_blank\">\n<img width=\"64px\" src=\"https://images.opencollective.com/ant-design-vue/2ec179b/logo/256.png\"/>\n</a>\n\n## 🎨 鸣谢\n\n| Project                                                          |\n| ---------------------------------------------------------------- |\n| [vue](https://github.com/vuejs/vue)                              |\n| [element-ui](https://github.com/ElemeFE/element)                 |\n| [element-plus](https://github.com/element-plus/element-plus)     |\n| [ant-design-vue](https://github.com/vueComponent/ant-design-vue) |\n| [mock](https://github.com/nuysoft/Mock)                          |\n| [axios](https://github.com/axios/axios)                          |\n| [wangEditor](https://github.com/wangeditor-team/wangEditor)      |\n\n## 👷 框架杰出贡献者（排名不分先后）\n\n<a href=\"https://github.com/buuing\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/36689704?s=50\"/>\n</a>\n<a href=\"https://github.com/hipi\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/22478003?s=50\"/>\n</a>\n<a href=\"https://github.com/fwfmiao\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/29328241?s=50\"/>\n</a>\n<a href=\"https://github.com/hdtopku\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/14859466?s=50\"/>\n</a>\n<a href=\"https://github.com/shaonialife\" target=\"_blank\">\n<img width=\"50px\" style=\"border-radius:999px\" src=\"https://avatars.githubusercontent.com/u/16135960?s=50\"/>\n</a>\n\n## 📌 优势及注意事项\n\n```\n对比其他开源 admin 框架有如下优势:\n1. 支持前端控制路由权限 intelligence、后端控制路由权限 all 模式\n2. 已知开源 vue admin 框架中首家支持 mock 自动生成自动导出功能\n3. 提供 50 余项全局精细化配置\n4. 支持 scss 自动排序，eslint 自动修复\n5. axios 精细化封装，支持多数据源、多成功 code 数组，支持 application/json;charset=UTF-8、application/x-www-form-urlencoded;charset=UTF-8 多种传参方式\n6. 支持登录RSA加密\n7. 支持打包自动生成7Z压缩包\n8. 支持errorlog错误拦截\n9. 支持多主题、多布局切换\n\n使用注意事项:\n1. 项目默认使用lf换行符而非crlf换行符，新建文件时请注意选择文件换行符\n2. 项目默认使用的最严格的eslint校验规范（plugin:vue/recommended），使用之前建议配置开发工具实现自动修复（建议使用vscode开发）\n3. 项目使用的是要求最宽泛的MIT开源协议，保留MIT开源协议即可免费商用\n\n```\n\n## 💚 适合人群\n\n- 正在以及想使用 element-ui/element-plus 开发，前端开发经验 1 年+。\n- 熟悉 Vue.js 技术栈，使用它开发过几个实际项目。\n- 对原理技术感兴趣，想进阶和提升的同学。\n\n## 🎉 功能地图\n\n![img](https://fastly.jsdelivr.net/gh/chuzhixin/image/vip/flow.drawio.png)\n\n## 🗃️ 效果图\n\n以下是截取的是 pro 版的效果图展示：\n\n<table>\n<tr>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/2.png\">\n</td>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/6.png\">\n</td>\n</tr>\n<tr>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/8.png\">\n</td>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/9.png\">\n</td>\n</tr>\n<tr>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/3.png\">\n</td>\n<td>\n<img src=\"https://fastly.jsdelivr.net/gh/chuzhixin/image/5.png\">\n</td>\n</tr>\n</table>\n\n## 📄 商用注意事项\n\n此项目可免费用于商业用途，请遵守 MIT 协议并保留作者技术支持声明。\n\n</div>\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n  presets: ['@vue/cli-plugin-babel/preset'],\n}\n"
  },
  {
    "path": "deploy.sh",
    "content": "#!/usr/bin/env bash\nset -e\nnpm run build\ncd dist\ntouch .nojekyll\ngit init\ngit add -A\ngit commit -m 'deploy'\ngit push -f \"https://${access_token}@gitee.com/chu1204505056/vue-admin-better-template.git\" master:gh-pages\nstart \"https://gitee.com/chu1204505056/vue-admin-better-template/pages\"\ncd -\ncd -\nrimraf dist\nexec /bin/bash\n\n\n\n\n"
  },
  {
    "path": "http/mock.http",
    "content": "\n###/changeLog/getList###mockServer\nPOST http://localhost:80/mock-server/changeLog/getList\nContent-Type: application/x-www-form-urlencoded\n###\nmockServer\n###/colorfulIcon/list###\nPOST http://localhost:80/mock-server/colorfulIcon/list\nContent-Type: application/x-www-form-urlencoded\n###mockServer\n\n###/menu/navigate###\nPOST http://localhost:80/mock-server/menu/navigate\nContent-Type: application/x-www-form-urlenmockServer\n###\n\n###/icon/list###\nPOST http://localhost:80/mock-server/icon/mockServer\nContent-Type: application/x-www-form-urlencoded\n###\n\n###/face/list###mockServer\nPOST http://localhost:80/mock-server/face/list\nContent-Type: application/x-www-form-urlencoded\n###\nmockServer\n###/table/list###\nPOST http://localhost:80/mock-server/table/list\nContent-Type: application/x-www-form-urlencoded\n###mockServer\n\n###/remixicon/getList###\nPOST http://localhost:80/mock-server/remixicon/getList\nContent-Type: application/x-www-form-urlenmockServer\n###\n\n###/publicKey###\nPOST http://localhost:80/mock-server/pumockServer\nContent-Type: application/x-www-form-urlencoded\n###\n\n###/tree/list###mockServer\nPOST http://localhost:80/mock-server/tree/list\nContent-Type: application/x-www-form-urlencoded\n###\nmockServer\n###/upload###\nPOST http://localhost:80/mock-server/upload\nContent-Type: application/x-www-form-urlencoded\n###mockServer\n\n###/login###\nPOST http://localhost:80/mock-server/login\nContent-Type: application/x-www-form-urlenmockServer\n###\n\n###/waterfall/list###\nPOST http://localhost:80/mock-server/waterfall/list\nContent-Type: application/x-www-form-urlencoded\n###\n\n###/logout###\nPOST http://localhost:80/mock-server/logout\nContent-Type: application/x-www-form-urlencoded\n###\n\n###/user/info###\nPOST http://localhost:80/mock-server/user/info\nContent-Type: application/x-www-form-urlencoded\n###\n"
  },
  {
    "path": "layouts/Permissions/index.js",
    "content": "import permissions from './permissions'\n\nconst install = function (Vue) {\n  Vue.directive('permissions', permissions)\n}\n\nif (window.Vue) {\n  window['permissions'] = permissions\n  Vue.use(install)\n}\n\npermissions.install = install\nexport default permissions\n"
  },
  {
    "path": "layouts/Permissions/permissions.js",
    "content": "import store from '@/store'\n\nexport default {\n  inserted(element, binding) {\n    const { value } = binding\n    const permissions = store.getters['user/permissions']\n    if (value && value instanceof Array && value.length > 0) {\n      const hasPermission = permissions.some((role) => value.includes(role))\n      if (!hasPermission)\n        element.parentNode && element.parentNode.removeChild(element)\n    }\n  },\n}\n"
  },
  {
    "path": "layouts/VabColorfullIcon/index.vue",
    "content": "<template>\n  <img\n    v-if=\"isExternal\"\n    :src=\"styleExternalIcon\"\n    class=\"svg-external-icon svg-icon\"\n    v-on=\"$listeners\"\n  />\n  <svg v-else :class=\"svgClass\" aria-hidden=\"true\" v-on=\"$listeners\">\n    <use :xlink:href=\"iconName\" />\n  </svg>\n</template>\n\n<script>\n  import { isExternal } from '@/utils/validate'\n\n  export default {\n    name: 'VabColorfulIcon',\n    props: {\n      iconClass: {\n        type: String,\n        required: true,\n      },\n      className: {\n        type: String,\n        default: '',\n      },\n    },\n    computed: {\n      isExternal() {\n        return isExternal(this.iconClass)\n      },\n      iconName() {\n        return `#colorful-icon-${this.iconClass}`\n      },\n      svgClass() {\n        if (this.className) {\n          return 'svg-icon ' + this.className\n        } else {\n          return 'svg-icon'\n        }\n      },\n      styleExternalIcon() {\n        return this.iconClass\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .svg-icon {\n    width: 1em;\n    height: 1em;\n    overflow: hidden;\n    vertical-align: -0.15em;\n    fill: currentColor;\n\n    &:hover {\n      opacity: 0.8;\n    }\n  }\n\n  .svg-external-icon {\n    display: inline-block;\n  }\n</style>\n"
  },
  {
    "path": "layouts/VabErrorLog/index.vue",
    "content": "<template>\n  <div v-if=\"errorLogs.length > 0\">\n    <el-badge\n      :value=\"errorLogs.length\"\n      @click.native=\"dialogTableVisible = true\"\n    >\n      <el-button type=\"danger\">\n        <vab-icon :icon=\"['fas', 'bug']\" />\n      </el-button>\n    </el-badge>\n\n    <el-dialog\n      :visible.sync=\"dialogTableVisible\"\n      append-to-body\n      width=\"70%\"\n      title=\"vue-admin-better异常捕获(温馨提示：错误必须解决)\"\n    >\n      <el-table :data=\"errorLogs\">\n        <el-table-column label=\"报错路由\">\n          <template slot-scope=\"{ row }\">\n            <a :href=\"row.url\" target=\"_blank\">\n              <el-tag type=\"success\">{{ row.url }}</el-tag>\n            </a>\n          </template>\n        </el-table-column>\n        <el-table-column label=\"错误信息\">\n          <template slot-scope=\"{ row }\">\n            <el-tag type=\"danger\">{{ decodeUnicode(row.err.message) }}</el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column label=\"错误详情\" width=\"120\">\n          <template slot-scope=\"scope\">\n            <el-popover placement=\"top-start\" trigger=\"hover\">\n              <div style=\"color: red\">\n                {{ scope.row.err.stack }}\n              </div>\n              <el-button slot=\"reference\">查看</el-button>\n            </el-popover>\n          </template>\n        </el-table-column>\n        <el-table-column width=\"380\" label=\"操作\">\n          <template slot-scope=\"{ row }\">\n            <a\n              v-for=\"(item, index) in searchList\"\n              :key=\"index\"\n              :href=\"item.url + decodeUnicode(row.err.message)\"\n              target=\"_blank\"\n            >\n              <el-button style=\"margin-left: 5px\" type=\"primary\">\n                <vab-icon :icon=\"['fas', 'search']\" />\n                {{ item.title }}\n              </el-button>\n            </a>\n          </template>\n        </el-table-column>\n      </el-table>\n      <span slot=\"footer\" class=\"dialog-footer\">\n        <el-button @click=\"dialogTableVisible = false\">取 消</el-button>\n        <el-button type=\"danger\" icon=\"el-icon-delete\" @click=\"clearAll\">\n          暂不显示\n        </el-button>\n      </span>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\n  import { abbreviation, title } from '@/config'\n  import { mapGetters } from 'vuex'\n\n  export default {\n    name: 'VabErrorLog',\n\n    data() {\n      return {\n        dialogTableVisible: false,\n        title: title,\n        abbreviation: abbreviation,\n        searchList: [\n          {\n            title: '百度搜索',\n            url: 'https://www.baidu.com/baidu?wd=',\n          },\n          {\n            title: '谷歌搜索',\n            url: 'https://www.google.com/search?q=',\n          },\n          {\n            title: 'Magi搜索',\n            url: 'https://magi.com/search?q=',\n          },\n        ],\n      }\n    },\n\n    computed: {\n      ...mapGetters({\n        errorLogs: 'errorLog/errorLogs',\n      }),\n    },\n    methods: {\n      clearAll() {\n        this.dialogTableVisible = false\n        this.$store.dispatch('errorLog/clearErrorLog')\n      },\n      decodeUnicode(str) {\n        str = str.replace(/\\\\/g, '%')\n        str = unescape(str)\n        str = str.replace(/%/g, '\\\\')\n        str = str.replace(/\\\\/g, '')\n        return str\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  ::v-deep {\n    .el-badge {\n      .el-button {\n        display: flex;\n        align-items: center;\n        justify-items: center;\n        height: 28px;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "layouts/VabFullScreenBar/index.vue",
    "content": "<template>\n  <span :title=\"isFullscreen ? '退出全屏' : '进入全屏'\">\n    <vab-icon\n      :icon=\"[\n        'fas',\n        isFullscreen ? 'compress-arrows-alt' : 'expand-arrows-alt',\n      ]\"\n      @click=\"click\"\n    ></vab-icon>\n  </span>\n</template>\n\n<script>\n  import screenfull from 'screenfull'\n\n  export default {\n    name: 'VabFullScreenBar',\n    data() {\n      return {\n        isFullscreen: false,\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.destroy()\n    },\n    methods: {\n      click() {\n        if (!screenfull.isEnabled) {\n          this.$baseMessage('开启全屏失败', 'error')\n          return false\n        }\n        screenfull.toggle()\n        this.$emit('refresh')\n      },\n      change() {\n        this.isFullscreen = screenfull.isFullscreen\n      },\n      init() {\n        if (screenfull.isEnabled) {\n          screenfull.on('change', this.change)\n        }\n      },\n      destroy() {\n        if (screenfull.isEnabled) {\n          screenfull.off('change', this.change)\n        }\n      },\n    },\n  }\n</script>\n"
  },
  {
    "path": "layouts/VabGithubCorner/index.vue",
    "content": "<template>\n  <a\n    href=\"https://github.com/chuzhixin/vue-admin-better\"\n    target=\"_blank\"\n    class=\"github-corner\"\n    aria-label=\"View source on Github\"\n  >\n    <svg\n      width=\"80\"\n      height=\"80\"\n      viewBox=\"0 0 250 250\"\n      class=\"github-color\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z\" />\n      <path\n        d=\"M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2\"\n        fill=\"currentColor\"\n        style=\"transform-origin: 130px 106px\"\n        class=\"octo-arm\"\n      />\n      <path\n        d=\"M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z\"\n        fill=\"currentColor\"\n        class=\"octo-body\"\n      />\n    </svg>\n  </a>\n</template>\n<script>\n  export default {\n    name: 'VabGithubCorner',\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .github-corner {\n    position: absolute;\n    top: 0;\n    right: 0;\n    z-index: $base-z-index - 3;\n\n    .octo-arm {\n      animation: octocat-wave 560ms ease-in-out infinite;\n    }\n\n    &:hover {\n      .octo-arm {\n        animation: octocat-wave 560ms ease-in-out infinite;\n      }\n    }\n\n    .github-color {\n      color: #fff;\n      fill: $base-color-blue;\n    }\n  }\n\n  @keyframes octocat-wave {\n    0%,\n    100% {\n      transform: rotate(0);\n    }\n\n    20%,\n    60% {\n      transform: rotate(-25deg);\n    }\n\n    40%,\n    80% {\n      transform: rotate(100deg);\n    }\n  }\n</style>\n"
  },
  {
    "path": "layouts/VabQueryForm/VabQueryFormBottomPanel.vue",
    "content": "<template>\n  <el-col :span=\"24\">\n    <div class=\"bottom-panel\">\n      <slot></slot>\n    </div>\n  </el-col>\n</template>\n\n<script>\n  export default {\n    name: \"VabQueryFormBottomPanel\",\n    props: {},\n    data() {\n      return {};\n    },\n    created() {},\n    mounted() {},\n    methods: {},\n  };\n</script>\n"
  },
  {
    "path": "layouts/VabQueryForm/VabQueryFormLeftPanel.vue",
    "content": "<template>\n  <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"span\" :xl=\"span\">\n    <div class=\"left-panel\">\n      <slot></slot>\n    </div>\n  </el-col>\n</template>\n\n<script>\n  export default {\n    name: 'VabQueryFormLeftPanel',\n    props: {\n      span: {\n        type: Number,\n        default: 14,\n      },\n    },\n    data() {\n      return {}\n    },\n    created() {},\n    mounted() {},\n    methods: {},\n  }\n</script>\n"
  },
  {
    "path": "layouts/VabQueryForm/VabQueryFormRightPanel.vue",
    "content": "<template>\n  <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"span\" :xl=\"span\">\n    <div class=\"right-panel\">\n      <slot></slot>\n    </div>\n  </el-col>\n</template>\n\n<script>\n  export default {\n    name: 'VabQueryFormRightPanel',\n    props: {\n      span: {\n        type: Number,\n        default: 10,\n      },\n    },\n    data() {\n      return {}\n    },\n    created() {},\n    mounted() {},\n    methods: {},\n  }\n</script>\n"
  },
  {
    "path": "layouts/VabQueryForm/VabQueryFormTopPanel.vue",
    "content": "<template>\n  <el-col :span=\"24\">\n    <div class=\"top-panel\">\n      <slot></slot>\n    </div>\n  </el-col>\n</template>\n\n<script>\n  export default {\n    name: 'VabQueryFormTopPanel',\n    props: {},\n    data() {\n      return {}\n    },\n    created() {},\n    mounted() {},\n    methods: {},\n  }\n</script>\n"
  },
  {
    "path": "layouts/VabQueryForm/index.vue",
    "content": "<template>\n  <el-row :gutter=\"0\" class=\"vab-query-form\">\n    <slot></slot>\n  </el-row>\n</template>\n\n<script>\n  export default {\n    name: 'VabQueryForm',\n    props: {},\n    data() {\n      return {}\n    },\n    created() {},\n    mounted() {},\n    methods: {},\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  @mixin panel {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    justify-content: flex-start;\n  }\n\n  .vab-query-form {\n    margin-bottom: 10px;\n\n    ::v-deep {\n      .top-panel {\n        @include panel;\n      }\n\n      .bottom-panel {\n        @include panel;\n\n        padding-top: 14px;\n        border-top: 1px solid #dcdfe6;\n      }\n\n      .left-panel {\n        @include panel;\n\n        > .el-button,\n        .el-form-item {\n          margin: 5px;\n        }\n      }\n\n      .right-panel {\n        @include panel;\n\n        justify-content: flex-end;\n\n        .el-form-item {\n          margin: 5px;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "layouts/VabRemixIcon/index.vue",
    "content": "<template>\n  <div\n    v-if=\"isExternal\"\n    :style=\"styleExternalIcon\"\n    class=\"svg-external-icon svg-icon\"\n    v-on=\"$listeners\"\n  />\n  <svg v-else :class=\"svgClass\" aria-hidden=\"true\" v-on=\"$listeners\">\n    <use :xlink:href=\"iconName\" />\n  </svg>\n</template>\n\n<script>\n  import { isExternal } from '@/utils/validate'\n\n  export default {\n    name: 'VabRemixIcon',\n    props: {\n      iconClass: {\n        type: String,\n        required: true,\n      },\n      className: {\n        type: String,\n        default: '',\n      },\n    },\n    computed: {\n      isExternal() {\n        return isExternal(this.iconClass)\n      },\n      iconName() {\n        return `#remix-icon-${this.iconClass}`\n      },\n      svgClass() {\n        if (this.className) {\n          return 'svg-icon ' + this.className\n        } else {\n          return 'svg-icon'\n        }\n      },\n      styleExternalIcon() {\n        return {\n          mask: `url(${this.iconClass}) no-repeat 50% 50%`,\n          '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`,\n        }\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .svg-icon {\n    width: 1.125em;\n    height: 1.125em;\n    overflow: hidden;\n    fill: currentColor;\n\n    &:hover {\n      opacity: 0.8;\n    }\n  }\n\n  .svg-external-icon {\n    display: inline-block;\n    background-color: currentColor;\n    mask-size: cover !important;\n  }\n</style>\n"
  },
  {
    "path": "layouts/VabSideBar/components/VabMenuItem.vue",
    "content": "<template>\n  <el-menu-item :index=\"handlePath(routeChildren.path)\" @click=\"handleLink\">\n    <vab-icon\n      v-if=\"routeChildren.meta.icon\"\n      :icon=\"['fas', routeChildren.meta.icon]\"\n      class=\"vab-fas-icon\"\n    />\n    <span>{{ routeChildren.meta.title }}</span>\n    <el-tag\n      v-if=\"routeChildren.meta && routeChildren.meta.badge\"\n      type=\"danger\"\n      effect=\"dark\"\n    >\n      {{ routeChildren.meta.badge }}\n    </el-tag>\n  </el-menu-item>\n</template>\n\n<script>\n  import { isExternal } from '@/utils/validate'\n  import path from 'path'\n\n  export default {\n    name: 'VabMenuItem',\n    props: {\n      routeChildren: {\n        type: Object,\n        default() {\n          return null\n        },\n      },\n      item: {\n        type: Object,\n        default() {\n          return null\n        },\n      },\n      fullPath: {\n        type: String,\n        default: '',\n      },\n    },\n    methods: {\n      handlePath(routePath) {\n        if (isExternal(routePath)) {\n          return routePath\n        }\n        if (isExternal(this.fullPath)) {\n          return this.fullPath\n        }\n        return path.resolve(this.fullPath, routePath)\n      },\n      handleLink() {\n        const routePath = this.routeChildren.path\n        const target = this.routeChildren.meta.target\n\n        if (target === '_blank') {\n          if (isExternal(routePath)) {\n            window.open(routePath)\n          } else if (isExternal(this.fullPath)) {\n            window.open(this.fullPath)\n          } else if (\n            this.$route.path !== path.resolve(this.fullPath, routePath)\n          ) {\n            let routeData = this.$router.resolve(\n              path.resolve(this.fullPath, routePath)\n            )\n            window.open(routeData.href)\n          }\n        } else {\n          if (isExternal(routePath)) {\n            window.location.href = routePath\n          } else if (isExternal(this.fullPath)) {\n            window.location.href = this.fullPath\n          } else if (\n            this.$route.path !== path.resolve(this.fullPath, routePath)\n          ) {\n            this.$router.push(path.resolve(this.fullPath, routePath))\n          }\n        }\n      },\n    },\n  }\n</script>\n"
  },
  {
    "path": "layouts/VabSideBar/components/VabSideBarItem.vue",
    "content": "<template>\n  <component\n    :is=\"menuComponent\"\n    v-if=\"!item.hidden\"\n    :item=\"item\"\n    :full-path=\"fullPath\"\n    :route-children=\"routeChildren\"\n  >\n    <template v-if=\"item.children && item.children.length\">\n      <vab-side-bar-item\n        v-for=\"route in item.children\"\n        :key=\"route.path\"\n        :full-path=\"handlePath(route.path)\"\n        :item=\"route\"\n      />\n    </template>\n  </component>\n</template>\n\n<script>\n  import { isExternal } from '@/utils/validate'\n  import path from 'path'\n\n  export default {\n    name: 'VabSideBarItem',\n    props: {\n      item: {\n        type: Object,\n        required: true,\n      },\n      fullPath: {\n        type: String,\n        default: '',\n      },\n    },\n    data() {\n      this.onlyOneChild = null\n      return {}\n    },\n    computed: {\n      menuComponent() {\n        if (\n          this.handleChildren(this.item.children, this.item) &&\n          (!this.routeChildren.children ||\n            this.routeChildren.notShowChildren) &&\n          !this.item.alwaysShow\n        ) {\n          return 'VabMenuItem'\n        } else {\n          return 'VabSubmenu'\n        }\n      },\n    },\n    methods: {\n      handleChildren(children = [], parent) {\n        if (children === null) children = []\n        const showChildren = children.filter((item) => {\n          if (item.hidden) {\n            return false\n          } else {\n            this.routeChildren = item\n            return true\n          }\n        })\n        if (showChildren.length === 1) {\n          return true\n        }\n\n        if (showChildren.length === 0) {\n          this.routeChildren = {\n            ...parent,\n            path: '',\n            notShowChildren: true,\n          }\n          return true\n        }\n        return false\n      },\n      handlePath(routePath) {\n        if (isExternal(routePath)) {\n          return routePath\n        }\n        if (isExternal(this.fullPath)) {\n          return this.fullPath\n        }\n        return path.resolve(this.fullPath, routePath)\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .vab-nav-icon {\n    margin-right: 4px;\n  }\n\n  ::v-deep {\n    .el-tag {\n      float: right;\n      height: 16px;\n      padding-right: 4px;\n      padding-left: 4px;\n      margin-top: calc((#{$base-menu-item-height} - 16px) / 2);\n      line-height: 16px;\n      border: 0;\n    }\n  }\n</style>\n"
  },
  {
    "path": "layouts/VabSideBar/components/VabSubmenu.vue",
    "content": "<template>\n  <el-submenu\n    ref=\"subMenu\"\n    :index=\"handlePath(item.path)\"\n    :popper-append-to-body=\"false\"\n  >\n    <template slot=\"title\">\n      <vab-icon\n        v-if=\"item.meta && item.meta.icon\"\n        :icon=\"['fas', item.meta.icon]\"\n        class=\"vab-fas-icon\"\n      />\n      <vab-remix-icon\n        v-if=\"item.meta && item.meta.remixIcon\"\n        :icon-class=\"item.meta.remixIcon\"\n        class=\"vab-remix-icon\"\n      />\n      <span>{{ item.meta.title }}</span>\n    </template>\n    <slot />\n  </el-submenu>\n</template>\n\n<script>\n  import { isExternal } from '@/utils/validate'\n  import path from 'path'\n\n  export default {\n    name: 'VabSubmenu',\n    props: {\n      routeChildren: {\n        type: Object,\n        default() {\n          return null\n        },\n      },\n      item: {\n        type: Object,\n        default() {\n          return null\n        },\n      },\n      fullPath: {\n        type: String,\n        default: '',\n      },\n    },\n    methods: {\n      handlePath(routePath) {\n        if (isExternal(routePath)) {\n          return routePath\n        }\n        if (isExternal(this.fullPath)) {\n          return this.fullPath\n        }\n        return path.resolve(this.fullPath, routePath)\n      },\n    },\n  }\n</script>\n"
  },
  {
    "path": "layouts/VabSideBar/index.vue",
    "content": "<template>\n  <el-scrollbar class=\"side-bar-container\" :class=\"{ 'is-collapse': collapse }\">\n    <vab-logo />\n    <el-menu\n      :background-color=\"variables['menu-background']\"\n      :text-color=\"variables['menu-color']\"\n      :active-text-color=\"variables['menu-color-active']\"\n      :default-active=\"activeMenu\"\n      :collapse=\"collapse\"\n      :collapse-transition=\"false\"\n      :default-openeds=\"defaultOpens\"\n      :unique-opened=\"uniqueOpened\"\n      mode=\"vertical\"\n    >\n      <template v-for=\"route in routes\">\n        <vab-side-bar-item\n          :key=\"route.path\"\n          :full-path=\"route.path\"\n          :item=\"route\"\n        />\n      </template>\n    </el-menu>\n  </el-scrollbar>\n</template>\n<script>\n  import variables from '@/styles/variables.scss'\n  import { mapGetters } from 'vuex'\n  import { defaultOopeneds, uniqueOpened } from '@/config'\n\n  export default {\n    name: 'VabSideBar',\n    data() {\n      return {\n        uniqueOpened,\n      }\n    },\n    computed: {\n      ...mapGetters({\n        collapse: 'settings/collapse',\n        routes: 'routes/routes',\n      }),\n      defaultOpens() {\n        if (this.collapse) {\n        }\n        return defaultOopeneds\n      },\n      activeMenu() {\n        const route = this.$route\n        const { meta, path } = route\n        if (meta.activeMenu) {\n          return meta.activeMenu\n        }\n        return path\n      },\n      variables() {\n        return variables\n      },\n    },\n  }\n</script>\n<style lang=\"scss\" scoped>\n  @mixin active {\n    &:hover {\n      color: $base-color-white;\n      background-color: $base-menu-background-active !important;\n    }\n\n    &.is-active {\n      color: $base-color-white;\n      background-color: $base-menu-background-active !important;\n    }\n  }\n\n  .side-bar-container {\n    position: fixed;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    z-index: $base-z-index;\n    width: $base-left-menu-width;\n    height: 100vh;\n    overflow: hidden;\n    background: $base-menu-background;\n    box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);\n    transition: width $base-transition-time;\n\n    &.is-collapse {\n      width: $base-left-menu-width-min;\n      border-right: 0;\n\n      ::v-deep {\n        .el-menu {\n          transition: width $base-transition-time;\n        }\n\n        .el-menu--collapse {\n          border-right: 0;\n\n          .el-submenu__icon-arrow {\n            right: 10px;\n            margin-top: -3px;\n          }\n          .el-menu-item,\n          .el-submenu {\n            text-align: center;\n          }\n        }\n      }\n    }\n\n    ::v-deep {\n      .el-scrollbar__wrap {\n        overflow-x: hidden;\n      }\n\n      .el-menu {\n        border: 0;\n\n        .vab-fas-icon {\n          padding-right: 3px;\n          font-size: $base-font-size-default;\n          display: inline-block;\n          width: 14px;\n        }\n\n        .vab-remix-icon {\n          padding-right: 3px;\n          font-size: $base-font-size-default + 2;\n        }\n      }\n\n      .el-menu-item,\n      .el-submenu__title {\n        height: $base-menu-item-height;\n        line-height: $base-menu-item-height;\n        vertical-align: middle;\n      }\n\n      .el-menu-item {\n        @include active;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "layouts/VabTabsBar/index.vue",
    "content": "<template>\n  <div id=\"tabs-bar-container\" class=\"tabs-bar-container\">\n    <el-tabs\n      v-model=\"tabActive\"\n      type=\"card\"\n      class=\"tabs-content\"\n      @tab-click=\"handleTabClick\"\n      @tab-remove=\"handleTabRemove\"\n    >\n      <el-tab-pane\n        v-for=\"item in visitedRoutes\"\n        :key=\"item.path\"\n        :label=\"item.meta.title\"\n        :name=\"item.path\"\n        :closable=\"!isAffix(item)\"\n      ></el-tab-pane>\n    </el-tabs>\n\n    <el-dropdown @command=\"handleCommand\">\n      <span style=\"cursor: pointer\">\n        更多操作\n        <i class=\"el-icon-arrow-down el-icon--right\"></i>\n      </span>\n      <el-dropdown-menu slot=\"dropdown\" class=\"tabs-more\">\n        <el-dropdown-item command=\"closeOtherstabs\">\n          <vab-icon :icon=\"['fas', 'times-circle']\" />\n          关闭其他\n        </el-dropdown-item>\n        <el-dropdown-item command=\"closeLefttabs\">\n          <vab-icon :icon=\"['fas', 'arrow-alt-circle-left']\"></vab-icon>\n          关闭左侧\n        </el-dropdown-item>\n        <el-dropdown-item command=\"closeRighttabs\">\n          <vab-icon :icon=\"['fas', 'arrow-alt-circle-right']\"></vab-icon>\n          关闭右侧\n        </el-dropdown-item>\n        <el-dropdown-item command=\"closeAlltabs\">\n          <vab-icon :icon=\"['fas', 'ban']\"></vab-icon>\n          关闭全部\n        </el-dropdown-item>\n      </el-dropdown-menu>\n    </el-dropdown>\n  </div>\n</template>\n\n<script>\n  import path from 'path'\n  import { mapGetters } from 'vuex'\n\n  export default {\n    name: 'VabTabsBar',\n    data() {\n      return {\n        affixtabs: [],\n        tabActive: '',\n      }\n    },\n\n    computed: {\n      ...mapGetters({\n        visitedRoutes: 'tabsBar/visitedRoutes',\n        routes: 'routes/routes',\n      }),\n    },\n    watch: {\n      $route: {\n        handler(route) {\n          this.inittabs()\n          this.addtabs()\n          let tabActive = ''\n          this.visitedRoutes.forEach((item, index) => {\n            if (item.path === this.$route.path) {\n              tabActive = item.path\n            }\n          })\n          this.tabActive = tabActive\n        },\n        immediate: true,\n      },\n    },\n    mounted() {\n      //console.log(this.visitedRoutes);\n    },\n    methods: {\n      async handleTabRemove(tabActive) {\n        let view\n        this.visitedRoutes.forEach((item, index) => {\n          if (tabActive == item.path) {\n            view = item\n          }\n        })\n        const { visitedRoutes } = await this.$store.dispatch(\n          'tabsBar/delRoute',\n          view\n        )\n        if (this.isActive(view)) {\n          this.toLastTag(visitedRoutes, view)\n        }\n      },\n      handleTabClick(tab) {\n        const route = this.visitedRoutes.filter((item, index) => {\n          if (tab.index == index) return item\n        })[0]\n        if (this.$route.path !== route.path) {\n          this.$router.push({\n            path: route.path,\n            query: route.query,\n            fullPath: route.fullPath,\n          })\n        } else {\n          return false\n        }\n      },\n      isActive(route) {\n        return route.path === this.$route.path\n      },\n      isAffix(tag) {\n        return tag.meta && tag.meta.affix\n      },\n      filterAffixtabs(routes, basePath = '/') {\n        let tabs = []\n        routes.forEach((route) => {\n          if (route.meta && route.meta.affix) {\n            const tagPath = path.resolve(basePath, route.path)\n            tabs.push({\n              fullPath: tagPath,\n              path: tagPath,\n              name: route.name,\n              meta: { ...route.meta },\n            })\n          }\n          if (route.children) {\n            const temptabs = this.filterAffixtabs(route.children, route.path)\n            if (temptabs.length >= 1) {\n              tabs = [...tabs, ...temptabs]\n            }\n          }\n        })\n        return tabs\n      },\n      inittabs() {\n        const affixtabs = (this.affixtabs = this.filterAffixtabs(this.routes))\n        for (const tag of affixtabs) {\n          if (tag.name) {\n            this.$store.dispatch('tabsBar/addVisitedRoute', tag)\n          }\n        }\n      },\n      addtabs() {\n        const { name } = this.$route\n        if (name) {\n          this.$store.dispatch('tabsBar/addVisitedRoute', this.$route)\n        }\n        return false\n      },\n      handleCommand(command) {\n        switch (command) {\n          case 'refreshRoute':\n            this.refreshRoute()\n            break\n          case 'closeOtherstabs':\n            this.closeOtherstabs()\n            break\n          case 'closeLefttabs':\n            this.closeLefttabs()\n            break\n          case 'closeRighttabs':\n            this.closeRighttabs()\n            break\n          case 'closeAlltabs':\n            this.closeAlltabs()\n            break\n        }\n      },\n      async refreshRoute() {\n        this.$baseEventBus.$emit('reloadrouter-view')\n      },\n      async closeSelectedTag(view) {\n        const { visitedRoutes } = await this.$store.dispatch(\n          'tabsBar/delRoute',\n          view\n        )\n        if (this.isActive(view)) {\n          this.toLastTag(visitedRoutes, view)\n        }\n      },\n      async closeOtherstabs() {\n        const view = await this.toThisTag()\n        await this.$store.dispatch('tabsBar/delOthersRoutes', view)\n      },\n      async closeLefttabs() {\n        const view = await this.toThisTag()\n        await this.$store.dispatch('tabsBar/delLeftRoutes', view)\n      },\n      async closeRighttabs() {\n        const view = await this.toThisTag()\n        await this.$store.dispatch('tabsBar/delRightRoutes', view)\n      },\n      async closeAlltabs() {\n        const view = await this.toThisTag()\n        const { visitedRoutes } = await this.$store.dispatch(\n          'tabsBar/delAllRoutes'\n        )\n        if (this.affixtabs.some((tag) => tag.path === view.path)) {\n          return\n        }\n        this.toLastTag(visitedRoutes, view)\n      },\n      toLastTag(visitedRoutes, view) {\n        const latestView = visitedRoutes.slice(-1)[0]\n        if (latestView) {\n          this.$router.push(latestView)\n        } else {\n          this.$router.push('/')\n        }\n      },\n      async toThisTag() {\n        const view = this.visitedRoutes.filter((item, index) => {\n          if (item.path === this.$route.fullPath) {\n            return item\n          }\n        })[0]\n        if (this.$route.path !== view.path) this.$router.push(view)\n        return view\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .tabs-bar-container {\n    position: relative;\n    box-sizing: border-box;\n    display: flex;\n    align-content: center;\n    align-items: center;\n    justify-content: space-between;\n    height: $base-tabs-bar-height;\n    padding-right: $base-padding;\n    padding-left: $base-padding;\n    user-select: none;\n    background: $base-color-white;\n    border-top: 1px solid #f6f6f6;\n\n    ::v-deep {\n      .fold-unfold {\n        margin-right: $base-padding;\n      }\n    }\n\n    .tabs-content {\n      width: calc(100% - 90px);\n      height: $base-tag-item-height;\n\n      ::v-deep {\n        .el-tabs__nav-next,\n        .el-tabs__nav-prev {\n          height: $base-tag-item-height;\n          line-height: $base-tag-item-height;\n        }\n\n        .el-tabs__header {\n          border-bottom: 0;\n\n          .el-tabs__nav {\n            border: 0;\n          }\n\n          .el-tabs__item {\n            box-sizing: border-box;\n            height: $base-tag-item-height;\n            margin-right: 5px;\n            line-height: $base-tag-item-height;\n            border: 1px solid $base-border-color;\n            border-radius: $base-border-radius;\n            transition: padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important;\n\n            &.is-active {\n              border: 1px solid $base-color-blue;\n            }\n          }\n        }\n      }\n    }\n\n    .more {\n      display: flex;\n      align-content: center;\n      align-items: center;\n      cursor: pointer;\n    }\n  }\n</style>\n"
  },
  {
    "path": "layouts/VabTopBar/index.vue",
    "content": "<template>\n  <div class=\"top-bar-container\">\n    <div class=\"vab-main\">\n      <el-row>\n        <el-col :xl=\"7\" :lg=\"7\" :md=\"7\" :sm=\"7\" :xs=\"7\">\n          <vab-logo />\n        </el-col>\n        <el-col :xl=\"12\" :lg=\"12\" :md=\"12\" :sm=\"12\" :xs=\"12\">\n          <el-menu\n            :background-color=\"variables['menu-background']\"\n            :text-color=\"variables['menu-color']\"\n            :active-text-color=\"variables['menu-color-active']\"\n            :default-active=\"activeMenu\"\n            mode=\"horizontal\"\n            menu-trigger=\"hover\"\n          >\n            <template v-for=\"route in routes\">\n              <vab-side-bar-item\n                v-if=\"!route.hidden\"\n                :key=\"route.path\"\n                :full-path=\"route.path\"\n                :item=\"route\"\n              />\n            </template>\n          </el-menu>\n        </el-col>\n        <el-col :xl=\"5\" :lg=\"5\" :md=\"5\" :sm=\"5\" :xs=\"5\">\n          <div class=\"right-panel\">\n            <vab-error-log />\n            <vab-full-screen-bar @refresh=\"refreshRoute\" />\n            <vab-theme-bar class=\"hidden-md-and-down\" />\n            <vab-icon\n              title=\"重载路由\"\n              :pulse=\"pulse\"\n              :icon=\"['fas', 'redo']\"\n              @click=\"refreshRoute\"\n            />\n            <vab-avatar />\n          </div>\n        </el-col>\n      </el-row>\n    </div>\n  </div>\n</template>\n\n<script>\n  import variables from '@/styles/variables.scss'\n  import { mapGetters } from 'vuex'\n\n  export default {\n    name: 'VabTopBar',\n    data() {\n      return {\n        pulse: false,\n        menuTrigger: 'hover',\n      }\n    },\n    computed: {\n      ...mapGetters({\n        routes: 'routes/routes',\n        visitedRoutes: 'tabsBar/visitedRoutes',\n      }),\n      activeMenu() {\n        const route = this.$route\n        const { meta, path } = route\n        if (meta.activeMenu) {\n          return meta.activeMenu\n        }\n        return path\n      },\n      variables() {\n        return variables\n      },\n    },\n    methods: {\n      async refreshRoute() {\n        this.$baseEventBus.$emit('reload-router-view')\n        this.pulse = true\n        setTimeout(() => {\n          this.pulse = false\n        }, 1000)\n      },\n    },\n  }\n</script>\n<style lang=\"scss\" scoped>\n  .top-bar-container {\n    display: flex;\n    align-items: center;\n    justify-items: flex-end;\n    height: $base-top-bar-height;\n    background: $base-menu-background;\n\n    .vab-main {\n      background: $base-menu-background;\n\n      ::v-deep {\n        .el-menu {\n          &.el-menu--horizontal {\n            display: flex;\n            align-items: center;\n            justify-content: flex-end;\n            height: $base-top-bar-height;\n            border-bottom: 0 solid transparent !important;\n\n            .el-menu-item,\n            .el-submenu__title {\n              padding: 0 15px;\n            }\n\n            @media only screen and (max-width: 767px) {\n              .el-menu-item,\n              .el-submenu__title {\n                padding: 0 8px;\n              }\n\n              li:nth-child(4),\n              li:nth-child(5) {\n                display: none !important;\n              }\n            }\n\n            > .el-menu-item {\n              height: $base-top-bar-height;\n              line-height: $base-top-bar-height;\n            }\n\n            > .el-submenu {\n              .el-submenu__title {\n                height: $base-top-bar-height;\n                line-height: $base-top-bar-height;\n              }\n            }\n          }\n\n          svg {\n            width: 1rem;\n            margin-right: 3px;\n          }\n\n          &--horizontal {\n            .el-menu {\n              .el-menu-item,\n              .el-submenu__title {\n                height: $base-menu-item-height;\n                line-height: $base-menu-item-height;\n              }\n            }\n\n            .el-submenu,\n            .el-menu-item {\n              &.is-active {\n                background-color: $base-color-blue !important;\n                border-bottom: 0 solid transparent !important;\n\n                .el-submenu__title {\n                  border-bottom: 0 solid transparent !important;\n                }\n              }\n            }\n\n            > .el-menu-item {\n              .el-tag {\n                margin-top: calc(#{$base-top-bar-height} / 2 - 7.5px);\n                margin-left: 5px;\n              }\n\n              @media only screen and (max-width: 1199px) {\n                .el-tag {\n                  display: none;\n                }\n              }\n\n              &.is-active {\n                background-color: transparent !important;\n                border-bottom: 3px solid $base-color-blue !important;\n              }\n            }\n          }\n        }\n      }\n    }\n\n    .right-panel {\n      display: flex;\n      align-items: center;\n      justify-content: flex-end;\n      height: $base-top-bar-height;\n\n      ::v-deep {\n        .user-name {\n          color: rgba($base-color-white, 0.9);\n        }\n\n        .user-name + i {\n          color: rgba($base-color-white, 0.9);\n        }\n\n        svg {\n          width: 1em;\n          height: 1em;\n          margin-right: 15px;\n          font-size: $base-font-size-big;\n          color: rgba($base-color-white, 0.9);\n          cursor: pointer;\n          fill: rgba($base-color-white, 0.9);\n        }\n\n        button {\n          svg {\n            margin-right: 0;\n            color: rgba($base-color-white, 0.9);\n            cursor: pointer;\n            fill: rgba($base-color-white, 0.9);\n          }\n        }\n\n        .el-badge {\n          margin-right: 15px;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "layouts/index.js",
    "content": "module.exports = {\n  webpackBarName: 'vue-admin-better',\n  webpackBanner:\n    ' build: vue-admin-better \\n vue-admin-better.com \\n https://gitee.com/chu1204505056/vue-admin-better \\n time: ',\n  donationConsole() {\n    const chalk = require('chalk')\n    console.log(\n      chalk.green(\n        `> 欢迎使用vue-admin-better，github开源地址：https://github.com/chuzhixin/vue-admin-better`\n      )\n    )\n    console.log(\n      chalk.green(\n        `> 欢迎使用vue-admin-better，码云开源地址：https://gitee.com/chu1204505056/vue-admin-better`\n      )\n    )\n\n    console.log(\n      chalk.green(`> pro版演示地址：http://vue-admin-better.com/admin-pro`)\n    )\n\n    console.log(\n      chalk.green(`> plus版演示地址：http://vue-admin-better.com/admin-plus`)\n    )\n\n    console.log(\n      chalk.green(\n        `> 使用中出现任何问题可加QQ群反馈，获取基础版、文档，请我们喝杯咖啡（如若情况不允许，请勿勉强）：https://gitee.com/chu1204505056/vue-admin-better#-%E5%89%8D%E7%AB%AF%E8%AE%A8%E8%AE%BA-qq-%E7%BE%A4`\n      )\n    )\n\n    console.log(chalk.green(`> 如果您不希望显示以上信息，可在config中配置关闭`))\n    console.log('\\n')\n  },\n}\n"
  },
  {
    "path": "layouts/package.json",
    "content": "{\n  \"name\": \"layouts\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "layouts/prettier.config.js",
    "content": "module.exports = {\n  printWidth: 80,\n  tabWidth: 2,\n  useTabs: false,\n  semi: false,\n  singleQuote: true,\n  quoteProps: 'as-needed',\n  jsxSingleQuote: false,\n  trailingComma: 'es5',\n  bracketSpacing: true,\n  jsxBracketSameLine: false,\n  arrowParens: 'always',\n  htmlWhitespaceSensitivity: 'ignore',\n  vueIndentScriptAndStyle: true,\n  endOfLine: 'lf',\n}\n"
  },
  {
    "path": "license.md",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "mock/controller/ad.js",
    "content": "const data = [\n  {\n    title: 'vue-admin-better-pro 1.7版本已发布，点我提前体验',\n    url: 'https://chu1204505056.gitee.io/vue-admin-better-pro/#/index',\n  },\n  {\n    title: 'vue-admin-better（antdv） vue3.0版本已发布，点我提前体验',\n    url: 'https://chu1204505056.gitee.io/vue-admin-better-mini/#/index',\n  },\n]\nmodule.exports = [\n  {\n    url: '/ad/getList',\n    type: 'get',\n    response() {\n      return {\n        code: 200,\n        msg: 'success',\n        data,\n      }\n    },\n  },\n]\n"
  },
  {
    "path": "mock/controller/router.js",
    "content": "const data = [\n  {\n    path: '/',\n    component: 'Layout',\n    redirect: 'index',\n    children: [\n      {\n        path: 'index',\n        name: 'Index',\n        component: '@/views/index/index',\n        meta: {\n          title: '首页',\n          icon: 'home',\n          affix: true,\n        },\n      },\n    ],\n  },\n  {\n    path: '/error',\n    component: 'EmptyLayout',\n    redirect: 'noRedirect',\n    name: 'Error',\n    meta: { title: '错误页', icon: 'bug' },\n    children: [\n      {\n        path: '401',\n        name: 'Error401',\n        component: '@/views/401',\n        meta: { title: '401' },\n      },\n      {\n        path: '404',\n        name: 'Error404',\n        component: '@/views/404',\n        meta: { title: '404' },\n      },\n    ],\n  },\n]\nmodule.exports = [\n  {\n    url: '/menu/navigate',\n    type: 'post',\n    response() {\n      return { code: 200, msg: 'success', data: data }\n    },\n  },\n]\n"
  },
  {
    "path": "mock/controller/user.js",
    "content": "const accessTokens = {\n  admin: 'admin-accessToken',\n  editor: 'editor-accessToken',\n  test: 'test-accessToken',\n}\n\nmodule.exports = [\n  {\n    url: '/publicKey',\n    type: 'post',\n    response() {\n      return {\n        code: 200,\n        msg: 'success',\n        data: {\n          mockServer: true,\n          publicKey:\n            'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBT2vr+dhZElF73FJ6xiP181txKWUSNLPQQlid6DUJhGAOZblluafIdLmnUyKE8mMHhT3R+Ib3ssZcJku6Hn72yHYj/qPkCGFv0eFo7G+GJfDIUeDyalBN0QsuiE/XzPHJBuJDfRArOiWvH0BXOv5kpeXSXM8yTt5Na1jAYSiQ/wIDAQAB',\n        },\n      }\n    },\n  },\n  {\n    url: '/login',\n    type: 'post',\n    response(config) {\n      const { username } = config.body\n      const accessToken = accessTokens[username]\n      if (!accessToken) {\n        return {\n          code: 500,\n          msg: '帐户或密码不正确。',\n        }\n      }\n      return {\n        code: 200,\n        msg: 'success',\n        data: { accessToken },\n      }\n    },\n  },\n  {\n    url: '/register',\n    type: 'post',\n    response() {\n      return {\n        code: 200,\n        msg: '模拟注册成功',\n      }\n    },\n  },\n  {\n    url: '/userInfo',\n    type: 'post',\n    response(config) {\n      const { accessToken } = config.body\n      let permissions = ['admin']\n      let username = 'admin'\n      if ('admin-accessToken' === accessToken) {\n        permissions = ['admin']\n        username = 'admin'\n      }\n      if ('editor-accessToken' === accessToken) {\n        permissions = ['editor']\n        username = 'editor'\n      }\n      if ('test-accessToken' === accessToken) {\n        permissions = ['admin', 'editor']\n        username = 'test'\n      }\n      return {\n        code: 200,\n        msg: 'success',\n        data: {\n          permissions,\n          username,\n          'avatar|1': [\n            'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif',\n            'https://i.gtimg.cn/club/item/face/img/8/15918_100.gif',\n          ],\n        },\n      }\n    },\n  },\n  {\n    url: '/logout',\n    type: 'post',\n    response() {\n      return {\n        code: 200,\n        msg: 'success',\n      }\n    },\n  },\n]\n"
  },
  {
    "path": "mock/index.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com\n * @description 导入所有 controller 模块，npm run serve时在node环境中自动输出controller文件夹下Mock接口，请勿修改。\n */\n\nconst { handleMockArray } = require('./utils')\n\nconst mocks = []\nconst mockArray = handleMockArray()\nmockArray.forEach((item) => {\n  const obj = require(item)\n  mocks.push(...obj)\n})\nmodule.exports = {\n  mocks,\n}\n"
  },
  {
    "path": "mock/mockServer.js",
    "content": "const chokidar = require('chokidar')\nconst bodyParser = require('body-parser')\nconst chalk = require('chalk')\nconst path = require('path')\nconst Mock = require('mockjs')\nconst { baseURL } = require('../src/config')\nconst mockDir = path.join(process.cwd(), 'mock')\n\n/**\n *\n * @param app\n * @returns {{mockStartIndex: number, mockRoutesLength: number}}\n */\nconst registerRoutes = (app) => {\n  let mockLastIndex\n  const { mocks } = require('./index.js')\n  const mocksForServer = mocks.map((route) => {\n    return responseFake(route.url, route.type, route.response)\n  })\n  for (const mock of mocksForServer) {\n    app[mock.type](mock.url, mock.response)\n    mockLastIndex = app._router.stack.length\n  }\n  const mockRoutesLength = Object.keys(mocksForServer).length\n  return {\n    mockRoutesLength: mockRoutesLength,\n    mockStartIndex: mockLastIndex - mockRoutesLength,\n  }\n}\n\n/**\n *\n * @param url\n * @param type\n * @param respond\n * @returns {{response(*=, *=): void, type: (*|string), url: RegExp}}\n */\nconst responseFake = (url, type, respond) => {\n  return {\n    url: new RegExp(`${baseURL}${url}`),\n    type: type || 'get',\n    response(req, res) {\n      res.status(200)\n      if (JSON.stringify(req.body) !== '{}') {\n        console.log(chalk.green(`> 请求地址：${req.path}`))\n        console.log(chalk.green(`> 请求参数：${JSON.stringify(req.body)}\\n`))\n      } else {\n        console.log(chalk.green(`> 请求地址：${req.path}\\n`))\n      }\n      res.json(\n        Mock.mock(respond instanceof Function ? respond(req, res) : respond)\n      )\n    },\n  }\n}\n/**\n *\n * @param app\n */\nmodule.exports = (app) => {\n  app.use(bodyParser.json())\n  app.use(\n    bodyParser.urlencoded({\n      extended: true,\n    })\n  )\n\n  const mockRoutes = registerRoutes(app)\n  let mockRoutesLength = mockRoutes.mockRoutesLength\n  let mockStartIndex = mockRoutes.mockStartIndex\n  chokidar\n    .watch(mockDir, {\n      ignoreInitial: true,\n    })\n    .on('all', (event) => {\n      if (event === 'change' || event === 'add') {\n        try {\n          app._router.stack.splice(mockStartIndex, mockRoutesLength)\n\n          Object.keys(require.cache).forEach((item) => {\n            if (item.includes(mockDir)) {\n              delete require.cache[require.resolve(item)]\n            }\n          })\n          const mockRoutes = registerRoutes(app)\n          mockRoutesLength = mockRoutes.mockRoutesLength\n          mockStartIndex = mockRoutes.mockStartIndex\n        } catch (error) {\n          console.log(chalk.red(error))\n        }\n      }\n    })\n}\n"
  },
  {
    "path": "mock/utils/index.js",
    "content": "const { Random } = require('mockjs')\nconst { join } = require('path')\nconst fs = require('fs')\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 随机生成图片url。\n * @param width\n * @param height\n * @returns {string}\n */\nfunction handleRandomImage(width = 50, height = 50) {\n  return `https://picsum.photos/${width}/${height}?random=${Random.guid()}`\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 处理所有 controller 模块，npm run serve时在node环境中自动输出controller文件夹下Mock接口，请勿修改。\n * @returns {[]}\n */\nfunction handleMockArray() {\n  const mockArray = []\n  const getFiles = (jsonPath) => {\n    const jsonFiles = []\n    const findJsonFile = (path) => {\n      const files = fs.readdirSync(path)\n      files.forEach((item) => {\n        const fPath = join(path, item)\n        const stat = fs.statSync(fPath)\n        if (stat.isDirectory() === true) findJsonFile(item)\n        if (stat.isFile() === true) jsonFiles.push(item)\n      })\n    }\n    findJsonFile(jsonPath)\n    jsonFiles.forEach((item) => mockArray.push(`./controller/${item}`))\n  }\n  getFiles('mock/controller')\n  return mockArray\n}\nmodule.exports = {\n  handleRandomImage,\n  handleMockArray,\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vue-admin-better-template\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"author\": \"vue-admin-better\",\n  \"participants\": [],\n  \"homepage\": \"https://chu1204505056.gitee.io/vue-admin-better\",\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"serve:node18\": \"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\",\n    \"build:node18\": \"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build\",\n    \"lint\": \"vue-cli-service lint\",\n    \"clear\": \"rimraf node_modules&&npm install  --registry=--registry=https://registry.npmmirror.com\",\n    \"image-webpack-loader\": \"cnpm i image-webpack-loader -D\",\n    \"update\": \"ncu -u --reject layouts,sass-loader,sass,screenfull,eslint,chalk,vue-echarts,vue,vue-template-compiler,vue-router,vuex,@vue/cli-plugin-babel,@vue/cli-plugin-eslint,@vue/cli-service,eslint-plugin-vue --registry=https://registry.npmmirror.com&&cnpm i\",\n    \"push\": \"start ./push.sh\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/chuzhixin/vue-admin-better-template.git\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged\"\n    }\n  },\n  \"lint-staged\": {\n    \"src/**/*.{js,vue}\": [\n      \"eslint --fix\",\n      \"git add\"\n    ]\n  },\n  \"dependencies\": {\n    \"axios\": \"^0.21.1\",\n    \"core-js\": \"^3.15.2\",\n    \"dayjs\": \"^1.10.6\",\n    \"element-ui\": \"^2.15.3\",\n    \"js-cookie\": \"^3.0.0\",\n    \"jsencrypt\": \"3.2.1\",\n    \"lodash\": \"^4.17.21\",\n    \"mockjs\": \"^1.1.0\",\n    \"nprogress\": \"^0.2.0\",\n    \"qs\": \"^6.10.1\",\n    \"screenfull\": \"^5.1.0\",\n    \"vab-icon\": \"file:vab-icon\",\n    \"vue\": \"^2.6.14\",\n    \"vue-router\": \"^3.5.2\",\n    \"vuex\": \"^3.6.2\",\n    \"layouts\": \"file:layouts\"\n  },\n  \"devDependencies\": {\n    \"@vue/cli-plugin-babel\": \"^4.5.13\",\n    \"@vue/cli-plugin-eslint\": \"^4.5.13\",\n    \"@vue/cli-service\": \"^4.5.13\",\n    \"@vue/eslint-config-prettier\": \"^6.0.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"body-parser\": \"^1.19.0\",\n    \"chalk\": \"^4.1.1\",\n    \"chokidar\": \"^3.5.2\",\n    \"eslint\": \"^7.31.0\",\n    \"eslint-plugin-prettier\": \"^3.4.0\",\n    \"eslint-plugin-vue\": \"^7.14.0\",\n    \"filemanager-webpack-plugin\": \"^6.1.4\",\n    \"image-webpack-loader\": \"^7.0.1\",\n    \"lint-staged\": \"^11.1.1\",\n    \"plop\": \"^2.7.4\",\n    \"prettier\": \"^2.3.2\",\n    \"sass\": \"^1.32.8\",\n    \"sass-loader\": \"^10.1.1\",\n    \"stylelint\": \"^13.13.1\",\n    \"stylelint-config-prettier\": \"^8.0.2\",\n    \"stylelint-config-recess-order\": \"^2.4.0\",\n    \"svg-sprite-loader\": \"^6.0.9\",\n    \"vue-template-compiler\": \"^2.6.14\",\n    \"webpackbar\": \"^4.0.0\"\n  },\n  \"engines\": {\n    \"node\": \">=8.9\",\n    \"npm\": \">= 3.0.0\"\n  }\n}\n"
  },
  {
    "path": "plopfile.js",
    "content": "const viewGenerator = require('zx-templates/view/prompt')\nconst curdGenerator = require('zx-templates/curd/prompt')\nconst componentGenerator = require('zx-templates/component/prompt')\nconst mockGenerator = require('zx-templates/mock/prompt')\nconst vuexGenerator = require('zx-templates/vuex/prompt')\n\nmodule.exports = (plop) => {\n  plop.setGenerator('view', viewGenerator)\n  plop.setGenerator('curd', curdGenerator)\n  plop.setGenerator('component', componentGenerator)\n  plop.setGenerator('mock&api', mockGenerator)\n  plop.setGenerator('vuex', vuexGenerator)\n}\n"
  },
  {
    "path": "prettier.config.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 代码规范\n */\n\nmodule.exports = {\n  printWidth: 80,\n  tabWidth: 2,\n  useTabs: false,\n  semi: false,\n  singleQuote: true,\n  quoteProps: 'as-needed',\n  jsxSingleQuote: false,\n  trailingComma: 'es5',\n  bracketSpacing: true,\n  jsxBracketSameLine: false,\n  arrowParens: 'always',\n  htmlWhitespaceSensitivity: 'ignore',\n  vueIndentScriptAndStyle: true,\n  endOfLine: 'lf',\n}\n"
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-cmn-Hans\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\" />\n    <title><%= VUE_APP_TITLE %></title>\n    <meta\n      name=\"keywords\"\n      content=\"vab,vab官网,后台管理框架,vue后台管理框架,vue-admin-better,vue-admin-better官网,vue-admin-better文档,vue-element-admin,vue-element-admin官网,vue-element-admin文档,vue-admin,vue-admin官网,vue-admin文档\"\n    />\n    <meta\n      name=\"description\"\n      content=\"<%= VUE_APP_TITLE %>官网与文档基于vue-admin-better构建，简称vab（是一款超棒的vue+element中后台前端快速开发框架），QQ群972435319，作者：<%= VUE_APP_AUTHOR %>\"\n    />\n    <meta name=\"author\" content=\"<%= VUE_APP_AUTHOR %>\" />\n    <link rel=\"stylesheet\" href=\"<%= BASE_URL %>static/css/loading.css\" />\n    <script>\n      var _hmt = _hmt || []\n      ;(function () {\n        var hm = document.createElement('script')\n        hm.src = 'https://hm.baidu.com/hm.js?7174bade1219f9cc272e7978f9523fc8'\n        var s = document.getElementsByTagName('script')[0]\n        s.parentNode.insertBefore(hm, s)\n      })()\n    </script>\n  </head>\n  <body>\n    <noscript>\n      非常抱歉鉴于安全考量,您无法查看<%= VUE_APP_TITLE %>\n      源代码，该系统基于vue-admin-better开发\n    </noscript>\n    <div id=\"vue-admin-better\">\n      <div class=\"first-loading-wrp\">\n        <div class=\"loading-wrp\">\n          <span class=\"dot dot-spin\">\n            <i></i>\n            <i></i>\n            <i></i>\n            <i></i>\n          </span>\n        </div>\n        <h1><%= VUE_APP_TITLE %></h1>\n      </div>\n    </div>\n    <script>\n      ;/^http(s*):\\/\\//.test(location.href) ||\n        alert('基于vue-admin-better开发的项目需要部署到服务器下访问')\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "public/static/css/loading.css",
    "content": ".first-loading-wrp {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 90vh;\n  min-height: 90vh;\n}\n\n.first-loading-wrp > h1 {\n  font-size: 30px;\n  font-weight: bolder;\n}\n\n.first-loading-wrp .loading-wrp {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 98px;\n}\n\n.dot {\n  position: relative;\n  box-sizing: border-box;\n  display: inline-block;\n  width: 64px;\n  height: 64px;\n  font-size: 64px;\n  transform: rotate(45deg);\n  animation: antRotate 1.2s infinite linear;\n}\n\n.dot i {\n  position: absolute;\n  display: block;\n  width: 28px;\n  height: 28px;\n  background-color: #1890ff;\n  border-radius: 100%;\n  opacity: 0.3;\n  transform: scale(0.75);\n  transform-origin: 50% 50%;\n  animation: antSpinMove 1s infinite linear alternate;\n}\n\n.dot i:nth-child(1) {\n  top: 0;\n  left: 0;\n}\n\n.dot i:nth-child(2) {\n  top: 0;\n  right: 0;\n  -webkit-animation-delay: 0.4s;\n  animation-delay: 0.4s;\n}\n\n.dot i:nth-child(3) {\n  right: 0;\n  bottom: 0;\n  -webkit-animation-delay: 0.8s;\n  animation-delay: 0.8s;\n}\n\n.dot i:nth-child(4) {\n  bottom: 0;\n  left: 0;\n  -webkit-animation-delay: 1.2s;\n  animation-delay: 1.2s;\n}\n\n@keyframes antRotate {\n  to {\n    -webkit-transform: rotate(405deg);\n    transform: rotate(405deg);\n  }\n}\n\n@-webkit-keyframes antRotate {\n  to {\n    -webkit-transform: rotate(405deg);\n    transform: rotate(405deg);\n  }\n}\n\n@keyframes antSpinMove {\n  to {\n    opacity: 1;\n  }\n}\n\n@-webkit-keyframes antSpinMove {\n  to {\n    opacity: 1;\n  }\n}\n"
  },
  {
    "path": "push.sh",
    "content": "#!/usr/bin/env bash\nset -e\ngit init\ngit add -A\ngit commit -m 'deploy'\ngit push -f \"https://${access_token}@github.com/chuzhixin/vue-admin-better-template.git\" master\nstart \"https://github.com/chuzhixin/vue-admin-better-template\"\nexec /bin/bash\n\n\n\n\n"
  },
  {
    "path": "src/App.vue",
    "content": "<template>\n  <div id=\"vue-admin-better\">\n    <router-view />\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'App',\n    mounted() {},\n  }\n</script>\n"
  },
  {
    "path": "src/api/ad.js",
    "content": "import request from '@/utils/request'\n\nexport function getList(data) {\n  return request({\n    //url: '/ad/getList',\n    url: 'https://851edf02-46eb-43e6-828d-64c7e483ea41.bspapp.com/http/getAd',\n    method: 'get',\n    data,\n  })\n}\n"
  },
  {
    "path": "src/api/publicKey.js",
    "content": "import request from '@/utils/request'\n\nexport function getPublicKey() {\n  return request({\n    url: '/publicKey',\n    method: 'post',\n  })\n}\n"
  },
  {
    "path": "src/api/router.js",
    "content": "import request from '@/utils/request'\n\nexport function getRouterList(data) {\n  return request({\n    url: '/menu/navigate',\n    method: 'post',\n    data,\n  })\n}\n"
  },
  {
    "path": "src/api/user.js",
    "content": "import request from '@/utils/request'\nimport { encryptedData } from '@/utils/encrypt'\nimport { loginRSA, tokenName } from '@/config'\n\nexport async function login(data) {\n  if (loginRSA) {\n    data = await encryptedData(data)\n  }\n  return request({\n    url: '/login',\n    method: 'post',\n    data,\n  })\n}\n\nexport function getUserInfo(accessToken) {\n  return request({\n    url: '/userInfo',\n    method: 'post',\n    data: {\n      [tokenName]: accessToken,\n    },\n  })\n}\n\nexport function logout() {\n  return request({\n    url: '/logout',\n    method: 'post',\n  })\n}\n\nexport function register() {\n  return request({\n    url: '/register',\n    method: 'post',\n  })\n}\n"
  },
  {
    "path": "src/colorfulIcon/index.js",
    "content": "const req = require.context('./svg', false, /\\.svg$/),\n  requireAll = (requireContext) => {\n    /*let a = requireContext.keys().map(requireContext);\n    let arr = [];\n    for (let i = 0; i < a.length; i++) {\n      console.log();\n      let icon = a[i].default.id;\n      arr.push(icon);\n    }\n    console.log(JSON.stringify(arr));*/\n    return requireContext.keys().map(requireContext)\n  }\nrequireAll(req)\n"
  },
  {
    "path": "src/config/index.js",
    "content": "/**\n * @description 3个子配置，通用配置|主题配置|网络配置导出\n */\nconst setting = require('./setting.config')\nconst theme = require('./theme.config')\nconst network = require('./net.config')\nmodule.exports = Object.assign({}, setting, theme, network)\n"
  },
  {
    "path": "src/config/net.config.js",
    "content": "/**\n * @description 导出默认网路配置\n **/\nconst network = {\n  //配后端数据的接收方式application/json;charset=UTF-8或者application/x-www-form-urlencoded;charset=UTF-8\n  contentType: 'application/json;charset=UTF-8',\n  //消息框消失时间\n  messageDuration: 3000,\n  //最长请求时间\n  requestTimeout: 5000,\n  //操作正常code，支持String、Array、int多种类型\n  successCode: [200, 0],\n  //登录失效code\n  invalidCode: 402,\n  //无权限code\n  noPermissionCode: 401,\n}\nmodule.exports = network\n"
  },
  {
    "path": "src/config/permission.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 路由守卫，目前两种模式：all模式与intelligence模式\n */\nimport router from '@/router'\nimport store from '@/store'\nimport VabProgress from 'nprogress'\nimport 'nprogress/nprogress.css'\nimport getPageTitle from '@/utils/pageTitle'\nimport {\n  authentication,\n  loginInterception,\n  progressBar,\n  recordRoute,\n  routesWhiteList,\n} from '@/config'\n\nVabProgress.configure({\n  easing: 'ease',\n  speed: 500,\n  trickleSpeed: 200,\n  showSpinner: false,\n})\nrouter.beforeResolve(async (to, from, next) => {\n  if (progressBar) VabProgress.start()\n  let hasToken = store.getters['user/accessToken']\n\n  if (!loginInterception) hasToken = true\n\n  if (hasToken) {\n    if (to.path === '/login') {\n      next({ path: '/' })\n      if (progressBar) VabProgress.done()\n    } else {\n      const hasPermissions =\n        store.getters['user/permissions'] &&\n        store.getters['user/permissions'].length > 0\n      if (hasPermissions) {\n        next()\n      } else {\n        try {\n          let permissions\n          if (!loginInterception) {\n            //settings.js loginInterception为false时，创建虚拟权限\n            await store.dispatch('user/setPermissions', ['admin'])\n            permissions = ['admin']\n          } else {\n            permissions = await store.dispatch('user/getUserInfo')\n          }\n\n          let accessRoutes = []\n          if (authentication === 'intelligence') {\n            accessRoutes = await store.dispatch('routes/setRoutes', permissions)\n          } else if (authentication === 'all') {\n            accessRoutes = await store.dispatch('routes/setAllRoutes')\n          }\n          router.addRoutes(accessRoutes)\n          next({ ...to, replace: true })\n        } catch {\n          await store.dispatch('user/resetAccessToken')\n          if (progressBar) VabProgress.done()\n        }\n      }\n    }\n  } else {\n    if (routesWhiteList.indexOf(to.path) !== -1) {\n      next()\n    } else {\n      if (recordRoute) {\n        next(`/login?redirect=${to.path}`)\n      } else {\n        next('/login')\n      }\n\n      if (progressBar) VabProgress.done()\n    }\n  }\n  document.title = getPageTitle(to.meta.title)\n})\nrouter.afterEach(() => {\n  if (progressBar) VabProgress.done()\n})\n"
  },
  {
    "path": "src/config/setting.config.js",
    "content": "/**\n * @description 导出默认通用配置\n */\nconst setting = {\n  // 开发以及部署时的URL\n  publicPath: '',\n  // 生产环境构建文件的目录名\n  outputDir: 'dist',\n  // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。\n  assetsDir: 'static',\n  // 开发环境每次保存时是否输出为eslint编译警告\n  lintOnSave: true,\n  // 进行编译的依赖\n  transpileDependencies: ['vue-echarts', 'resize-detector'],\n  // 默认的接口地址 如果是开发环境和生产环境走vab-mock-server，当然你也可以选择自己配置成需要的接口地址\n  baseURL:\n    process.env.NODE_ENV === 'development'\n      ? 'vab-mock-server'\n      : 'vab-mock-server',\n  //标题 （包括初次加载雪花屏的标题 页面的标题 浏览器的标题）\n  title: 'vue-admin-better',\n  //简写\n  abbreviation: 'vab',\n  //开发环境端口号\n  devPort: '82',\n  //版本号\n  version: process.env.VUE_APP_VERSION,\n  //这一项非常重要！请务必保留MIT协议下package.json及copyright作者信息 即可免费商用，不遵守此项约定你将无法使用该框架，如需自定义版权信息请联系QQ1204505056\n  copyright: 'vab',\n  //是否显示页面底部自定义版权信息\n  footerCopyright: true,\n  //是否显示顶部进度条\n  progressBar: true,\n  //缓存路由的最大数量\n  keepAliveMaxNum: 99,\n  // 路由模式，可选值为 history 或 hash\n  routerMode: 'hash',\n  //不经过token校验的路由\n  routesWhiteList: ['/login', '/register', '/404', '/401'],\n  //加载时显示文字\n  loadingText: '正在加载中...',\n  //token名称\n  tokenName: 'accessToken',\n  //token在localStorage、sessionStorage存储的key的名称\n  tokenTableName: 'vue-admin-better',\n  //token存储位置localStorage sessionStorage\n  storage: 'localStorage',\n  //token失效回退到登录页时是否记录本次的路由\n  recordRoute: true,\n  //是否显示logo，不显示时设置false，显示时请填写remixIcon图标名称，暂时只支持设置remixIcon\n  logo: 'vuejs-fill',\n  //是否显示在页面高亮错误\n  errorLog: ['development'],\n  //是否开启登录拦截\n  loginInterception: true,\n  //是否开启登录RSA加密\n  loginRSA: false,\n  //intelligence和all两种方式，前者后端权限只控制permissions不控制view文件的import（前后端配合，减轻后端工作量），all方式完全交给后端前端只负责加载\n  authentication: 'intelligence',\n  //vertical布局时是否只保持一个子菜单的展开\n  uniqueOpened: true,\n  //vertical布局时默认展开的菜单path，使用逗号隔开建议只展开一个\n  defaultOopeneds: ['/vab'],\n  //需要加loading层的请求，防止重复提交\n  debounce: ['doEdit'],\n  //需要自动注入并加载的模块\n  providePlugin: { maptalks: 'maptalks', 'window.maptalks': 'maptalks' },\n  //npm run build时是否自动生成7z压缩包\n  build7z: false,\n  //代码生成机生成在view下的文件夹名称\n  templateFolder: 'project',\n  //是否显示终端donation打印\n  donation: true,\n}\nmodule.exports = setting\n"
  },
  {
    "path": "src/config/settings.js",
    "content": "/**\n * @description 3个子配置，通用配置|主题配置|网络配置\n */\n//默认配置\nconst { setting, theme, network } = require('./')\nmodule.exports = Object.assign({}, setting, theme, network)\n"
  },
  {
    "path": "src/config/theme.config.js",
    "content": "/**\n * @description 导出默认主题配置\n */\nconst theme = {\n  //是否国定头部 固定fixed 不固定noFixed\n  header: 'fixed',\n  //横纵布局 horizontal vertical\n  layout: 'vertical',\n  //是否开启主题配置按钮\n  themeBar: true,\n  //是否显示多标签页\n  tabsBar: true,\n}\nmodule.exports = theme\n"
  },
  {
    "path": "src/layouts/EmptyLayout.vue",
    "content": "<template>\n  <router-view />\n</template>\n"
  },
  {
    "path": "src/layouts/components/VabAd/index.vue",
    "content": "<template>\n  <div class=\"vab-ad\">\n    <el-carousel\n      v-if=\"adList\"\n      height=\"30px\"\n      direction=\"vertical\"\n      :autoplay=\"true\"\n      :interval=\"3000\"\n      indicator-position=\"none\"\n    >\n      <el-carousel-item v-for=\"(item, index) in adList\" :key=\"index\">\n        <el-tag type=\"warning\">Ad</el-tag>\n        <a target=\"_blank\" :href=\"item.url\">{{ item.title }}</a>\n      </el-carousel-item>\n    </el-carousel>\n  </div>\n</template>\n<script>\n  import { getList } from '@/api/ad'\n  export default {\n    name: 'VabAd',\n    data() {\n      return {\n        nodeEnv: process.env.NODE_ENV,\n        adList: [],\n      }\n    },\n    created() {\n      this.fetchData()\n    },\n    methods: {\n      async fetchData() {\n        const { data } = await getList()\n        this.adList = data\n      },\n    },\n  }\n</script>\n<style lang=\"scss\" scoped>\n  .vab-ad {\n    height: 30px;\n    padding-right: $base-padding;\n    padding-left: $base-padding;\n    margin-bottom: -20px;\n    line-height: 30px;\n    cursor: pointer;\n\n    a {\n      color: #999;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/layouts/components/VabAppMain/index.vue",
    "content": "<template>\n  <div v-if=\"routerView\" class=\"app-main-container\">\n    <vab-github-corner />\n    <transition mode=\"out-in\" name=\"fade-transform\">\n      <keep-alive :include=\"cachedRoutes\" :max=\"keepAliveMaxNum\">\n        <router-view :key=\"key\" class=\"app-main-height\" />\n      </keep-alive>\n    </transition>\n    <footer v-show=\"footerCopyright\" class=\"footer-copyright\">\n      Copyright\n      <vab-icon :icon=\"['fas', 'copyright']\"></vab-icon>\n      vue-admin-better-pro 开源免费版 {{ fullYear }}\n    </footer>\n  </div>\n</template>\n\n<script>\n  import { mapActions, mapGetters } from 'vuex'\n  import { copyright, footerCopyright, keepAliveMaxNum, title } from '@/config'\n\n  export default {\n    name: 'VabAppMain',\n    data() {\n      return {\n        show: false,\n        fullYear: new Date().getFullYear(),\n        copyright,\n        title,\n        keepAliveMaxNum,\n        routerView: true,\n        footerCopyright,\n      }\n    },\n    computed: {\n      ...mapGetters({\n        visitedRoutes: 'tabsBar/visitedRoutes',\n        device: 'settings/device',\n      }),\n      cachedRoutes() {\n        const cachedRoutesArr = []\n        this.visitedRoutes.forEach((item) => {\n          if (!item.meta.noKeepAlive) {\n            cachedRoutesArr.push(item.name)\n          }\n        })\n        return cachedRoutesArr\n      },\n      key() {\n        return this.$route.path\n      },\n    },\n    watch: {\n      $route: {\n        handler(route) {\n          if ('mobile' === this.device) this.foldSideBar()\n        },\n        immediate: true,\n      },\n    },\n    created() {\n      //重载所有路由\n      this.$baseEventBus.$on('reload-router-view', () => {\n        this.routerView = false\n        this.$nextTick(() => {\n          this.routerView = true\n        })\n      })\n    },\n    mounted() {},\n    methods: {\n      ...mapActions({\n        foldSideBar: 'settings/foldSideBar',\n      }),\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .app-main-container {\n    position: relative;\n    width: 100%;\n    overflow: hidden;\n    .vab-keel {\n      margin: $base-padding;\n    }\n    .app-main-height {\n      min-height: $base-app-main-height;\n    }\n\n    .footer-copyright {\n      min-height: 55px;\n      line-height: 55px;\n      color: rgba(0, 0, 0, 0.45);\n      text-align: center;\n      border-top: 1px dashed $base-border-color;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/layouts/components/VabAvatar/index.vue",
    "content": "<template>\n  <el-dropdown @command=\"handleCommand\">\n    <span class=\"avatar-dropdown\">\n      <!--<el-avatar class=\"user-avatar\" :src=\"avatar\"></el-avatar>-->\n      <img class=\"user-avatar\" :src=\"avatar\" alt=\"\" />\n      <div class=\"user-name\">\n        {{ username }}\n        <i class=\"el-icon-arrow-down el-icon--right\"></i>\n      </div>\n    </span>\n\n    <el-dropdown-menu slot=\"dropdown\">\n      <el-dropdown-item command=\"github\">github地址</el-dropdown-item>\n      <el-dropdown-item command=\"gitee\" divided>码云地址</el-dropdown-item>\n      <el-dropdown-item command=\"pro\" divided>pro付费版地址</el-dropdown-item>\n      <el-dropdown-item command=\"plus\" divided>plus付费版地址</el-dropdown-item>\n      <el-dropdown-item command=\"shop\" divided>\n        shop-vite付费版地址\n      </el-dropdown-item>\n      <el-dropdown-item command=\"logout\" divided>退出登录</el-dropdown-item>\n    </el-dropdown-menu>\n  </el-dropdown>\n</template>\n\n<script>\n  import { mapGetters } from 'vuex'\n  import { recordRoute } from '@/config'\n\n  export default {\n    name: 'VabAvatar',\n    computed: {\n      ...mapGetters({\n        avatar: 'user/avatar',\n        username: 'user/username',\n      }),\n    },\n    methods: {\n      handleCommand(command) {\n        switch (command) {\n          case 'logout':\n            this.logout()\n            break\n          case 'personalCenter':\n            this.personalCenter()\n            break\n          case 'github':\n            window.open('https://github.com/chuzhixin/vue-admin-better')\n            break\n          case 'gitee':\n            window.open('https://gitee.com/chu1204505056/vue-admin-better')\n            break\n          case 'pro':\n            window.open(\n              'https://vue-admin-beautiful.com/admin-pro/?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci='\n            )\n            break\n          case 'plus':\n            window.open(\n              'https://vue-admin-beautiful.com/admin-plus/?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci='\n            )\n          case 'shop':\n            window.open(\n              'https://vue-admin-beautiful.com/shop-vite/?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci='\n            )\n        }\n      },\n      personalCenter() {\n        this.$router.push('/personalCenter/personalCenter')\n      },\n      logout() {\n        this.$baseConfirm(\n          '您确定要退出' + this.$baseTitle + '吗?',\n          null,\n          async () => {\n            await this.$store.dispatch('user/logout')\n            if (recordRoute) {\n              const fullPath = this.$route.fullPath\n              this.$router.push(`/login?redirect=${fullPath}`)\n            } else {\n              this.$router.push('/login')\n            }\n          }\n        )\n      },\n    },\n  }\n</script>\n<style lang=\"scss\" scoped>\n  .avatar-dropdown {\n    display: flex;\n    align-content: center;\n    align-items: center;\n    justify-content: center;\n    justify-items: center;\n    height: 50px;\n    padding: 0;\n\n    .user-avatar {\n      width: 40px;\n      height: 40px;\n      cursor: pointer;\n      border-radius: 50%;\n    }\n\n    .user-name {\n      position: relative;\n      margin-left: 5px;\n      margin-left: 5px;\n      cursor: pointer;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/layouts/components/VabBreadcrumb/index.vue",
    "content": "<template>\n  <el-breadcrumb class=\"breadcrumb-container\" separator=\">\">\n    <el-breadcrumb-item v-for=\"item in list\" :key=\"item.path\">\n      {{ item.meta.title }}\n    </el-breadcrumb-item>\n  </el-breadcrumb>\n</template>\n\n<script>\n  export default {\n    name: 'VabBreadcrumb',\n    data() {\n      return {\n        list: this.getBreadcrumb(),\n      }\n    },\n    watch: {\n      $route() {\n        this.list = this.getBreadcrumb()\n      },\n    },\n    methods: {\n      getBreadcrumb() {\n        return this.$route.matched.filter(\n          (item) => item.name && item.meta.title\n        )\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .breadcrumb-container {\n    height: $base-nav-bar-height;\n    font-size: $base-font-size-default;\n    line-height: $base-nav-bar-height;\n\n    ::v-deep {\n      .el-breadcrumb__item {\n        .el-breadcrumb__inner {\n          a {\n            display: flex;\n            float: left;\n            font-weight: normal;\n            color: #515a6e;\n\n            i {\n              margin-right: 3px;\n            }\n          }\n        }\n\n        &:last-child {\n          .el-breadcrumb__inner {\n            a {\n              color: #999;\n            }\n          }\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/layouts/components/VabLogo/index.vue",
    "content": "<template>\n  <div :class=\"'logo-container-' + layout\">\n    <router-link to=\"/\">\n      <!-- 这里是logo变更的位置 -->\n      <vab-remix-icon v-if=\"logo\" class=\"logo\" :icon-class=\"logo\" />\n      <span\n        class=\"title\"\n        :class=\"{ 'hidden-xs-only': layout === 'horizontal' }\"\n        :title=\"title\"\n      >\n        {{ title }}\n      </span>\n    </router-link>\n  </div>\n</template>\n<script>\n  import { mapGetters } from 'vuex'\n\n  export default {\n    name: 'VabLogo',\n    data() {\n      return {\n        title: this.$baseTitle,\n      }\n    },\n    computed: {\n      ...mapGetters({\n        logo: 'settings/logo',\n        layout: 'settings/layout',\n      }),\n    },\n  }\n</script>\n<style lang=\"scss\" scoped>\n  @mixin container {\n    position: relative;\n    height: $base-top-bar-height;\n    overflow: hidden;\n    line-height: $base-top-bar-height;\n    background: $base-menu-background;\n  }\n\n  @mixin logo {\n    display: inline-block;\n    width: 28px;\n    height: 28px;\n    margin-right: 3px;\n    color: $base-title-color;\n    vertical-align: middle;\n  }\n\n  @mixin title {\n    display: inline-block;\n    overflow: hidden;\n    font-size: 18px;\n    line-height: 55px;\n    color: $base-title-color;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    vertical-align: middle;\n  }\n\n  .logo-container-horizontal {\n    @include container;\n\n    .logo {\n      @include logo;\n    }\n\n    .title {\n      @include title;\n    }\n  }\n\n  .logo-container-vertical {\n    @include container;\n\n    height: $base-logo-height;\n    line-height: $base-logo-height;\n    text-align: center;\n\n    .logo {\n      @include logo;\n    }\n\n    .title {\n      @include title;\n\n      max-width: calc(#{$base-left-menu-width} - 60px);\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/layouts/components/VabNavBar/index.vue",
    "content": "<template>\n  <div class=\"nav-bar-container\">\n    <el-row :gutter=\"15\">\n      <el-col :xs=\"4\" :sm=\"12\" :md=\"12\" :lg=\"12\" :xl=\"12\">\n        <div class=\"left-panel\">\n          <i\n            :class=\"collapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'\"\n            :title=\"collapse ? '展开' : '收起'\"\n            class=\"fold-unfold\"\n            @click=\"handleCollapse\"\n          ></i>\n          <vab-breadcrumb class=\"hidden-xs-only\" />\n        </div>\n      </el-col>\n      <el-col :xs=\"20\" :sm=\"12\" :md=\"12\" :lg=\"12\" :xl=\"12\">\n        <div class=\"right-panel\">\n          <vab-error-log />\n          <vab-full-screen-bar @refresh=\"refreshRoute\" />\n          <vab-theme-bar class=\"hidden-xs-only\" />\n          <vab-icon\n            title=\"重载所有路由\"\n            :pulse=\"pulse\"\n            :icon=\"['fas', 'redo']\"\n            @click=\"refreshRoute\"\n          />\n          <vab-avatar />\n          <!--  <vab-icon\n            title=\"退出系统\"\n            :icon=\"['fas', 'sign-out-alt']\"\n            @click=\"logout\"\n          />-->\n        </div>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n\n<script>\n  import { mapActions, mapGetters } from 'vuex'\n\n  export default {\n    name: 'VabNavBar',\n    data() {\n      return {\n        pulse: false,\n      }\n    },\n    computed: {\n      ...mapGetters({\n        collapse: 'settings/collapse',\n        visitedRoutes: 'tabsBar/visitedRoutes',\n        device: 'settings/device',\n        routes: 'routes/routes',\n      }),\n    },\n    methods: {\n      ...mapActions({\n        changeCollapse: 'settings/changeCollapse',\n      }),\n      handleCollapse() {\n        this.changeCollapse()\n      },\n      async refreshRoute() {\n        this.$baseEventBus.$emit('reload-router-view')\n        this.pulse = true\n        setTimeout(() => {\n          this.pulse = false\n        }, 1000)\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .nav-bar-container {\n    position: relative;\n    height: $base-nav-bar-height;\n    padding-right: $base-padding;\n    padding-left: $base-padding;\n    overflow: hidden;\n    user-select: none;\n    background: $base-color-white;\n    box-shadow: $base-box-shadow;\n\n    .left-panel {\n      display: flex;\n      align-items: center;\n      justify-items: center;\n      height: $base-nav-bar-height;\n\n      .fold-unfold {\n        color: $base-color-gray;\n        cursor: pointer;\n      }\n\n      ::v-deep {\n        .breadcrumb-container {\n          margin-left: 10px;\n        }\n      }\n    }\n\n    .right-panel {\n      display: flex;\n      align-content: center;\n      align-items: center;\n      justify-content: flex-end;\n      height: $base-nav-bar-height;\n\n      ::v-deep {\n        svg {\n          width: 1em;\n          height: 1em;\n          margin-right: 15px;\n          font-size: $base-font-size-small;\n          color: $base-color-gray;\n          cursor: pointer;\n          fill: $base-color-gray;\n        }\n\n        button {\n          svg {\n            margin-right: 0;\n            color: $base-color-white;\n            cursor: pointer;\n            fill: $base-color-white;\n          }\n        }\n\n        .el-badge {\n          margin-right: 15px;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/layouts/components/VabThemeBar/index.vue",
    "content": "<template>\n  <span v-if=\"themeBar\">\n    <vab-icon\n      title=\"主题配置\"\n      :icon=\"['fas', 'palette']\"\n      @click=\"handleOpenThemeBar\"\n    />\n    <div class=\"theme-bar-setting\">\n      <div @click=\"handleOpenThemeBar\">\n        <vab-icon :icon=\"['fas', 'palette']\" />\n        <p>主题配置</p>\n      </div>\n      <div @click=\"handleGetCode\">\n        <vab-icon :icon=\"['fas', 'laptop-code']\"></vab-icon>\n        <p>拷贝源码</p>\n      </div>\n    </div>\n\n    <el-drawer\n      title=\"主题配置\"\n      :visible.sync=\"drawerVisible\"\n      direction=\"rtl\"\n      append-to-body\n      size=\"470px\"\n    >\n      <el-scrollbar style=\"height: 94vh; overflow: hidden\">\n        <div class=\"el-drawer__body\">\n          <el-form ref=\"form\" :model=\"theme\" label-position=\"top\">\n            <el-form-item label=\"主题\">\n              <el-radio-group v-model=\"theme.name\">\n                <el-radio-button label=\"default\">默认</el-radio-button>\n                <el-radio-button label=\"green\">绿荫草场</el-radio-button>\n                <el-radio-button label=\"glory\">荣耀典藏</el-radio-button>\n                <!-- <el-radio-button label=\"orean\">海洋之心</el-radio-button>\n                <el-radio-button label=\"red\">月上重火</el-radio-button> -->\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item label=\"布局\">\n              <el-radio-group v-model=\"theme.layout\">\n                <el-radio-button label=\"vertical\">纵向布局</el-radio-button>\n                <el-radio-button label=\"horizontal\">横向布局</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item label=\"头部\">\n              <el-radio-group v-model=\"theme.header\">\n                <el-radio-button label=\"fixed\">固定头部</el-radio-button>\n                <el-radio-button label=\"noFixed\">不固定头部</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item label=\"多标签\">\n              <el-radio-group v-model=\"theme.tabsBar\">\n                <el-radio-button label=\"true\">开启</el-radio-button>\n                <el-radio-button label=\"false\">不开启</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item>\n              <el-button type=\"primary\" @click=\"handleSaveTheme\">\n                保存\n              </el-button>\n            </el-form-item>\n          </el-form>\n        </div>\n      </el-scrollbar>\n    </el-drawer>\n  </span>\n</template>\n\n<script>\n  import variables from '@/styles/variables.scss'\n  import { mapActions, mapGetters } from 'vuex'\n  import { layout as defaultLayout } from '@/config'\n  export default {\n    name: 'VabThemeBar',\n    data() {\n      return {\n        drawerVisible: false,\n        theme: {\n          name: 'default',\n          layout: '',\n          header: 'fixed',\n          tabsBar: '',\n        },\n      }\n    },\n    computed: {\n      ...mapGetters({\n        layout: 'settings/layout',\n        header: 'settings/header',\n        tabsBar: 'settings/tabsBar',\n        themeBar: 'settings/themeBar',\n      }),\n    },\n    created() {\n      this.$baseEventBus.$on('theme', () => {\n        this.handleOpenThemeBar()\n      })\n      const theme = localStorage.getItem('vue-admin-better-theme')\n      if (null !== theme) {\n        this.theme = JSON.parse(theme)\n        this.handleSetTheme()\n      } else {\n        this.theme.layout = this.layout\n        this.theme.header = this.header\n        this.theme.tabsBar = this.tabsBar\n      }\n    },\n    methods: {\n      ...mapActions({\n        changeLayout: 'settings/changeLayout',\n        changeHeader: 'settings/changeHeader',\n        changeTabsBar: 'settings/changeTabsBar',\n      }),\n      handleIsMobile() {\n        return document.body.getBoundingClientRect().width - 1 < 992\n      },\n      handleOpenThemeBar() {\n        this.drawerVisible = true\n      },\n      handleSetTheme() {\n        let { name, layout, header, tabsBar } = this.theme\n        localStorage.setItem(\n          'vue-admin-better-theme',\n          `{\n            \"name\":\"${name}\",\n            \"layout\":\"${layout}\",\n            \"header\":\"${header}\",\n            \"tabsBar\":\"${tabsBar}\"\n          }`\n        )\n        if (!this.handleIsMobile()) this.changeLayout(layout)\n        this.changeHeader(header)\n        this.changeTabsBar(tabsBar)\n        document.getElementsByTagName(\n          'body'\n        )[0].className = `vue-admin-better-theme-${name}`\n        this.drawerVisible = false\n      },\n      handleSaveTheme() {\n        this.handleSetTheme()\n      },\n      handleSetDfaultTheme() {\n        let { name } = this.theme\n        document\n          .getElementsByTagName('body')[0]\n          .classList.remove(`vue-admin-better-theme-${name}`)\n        localStorage.removeItem('vue-admin-better-theme')\n        this.$refs['form'].resetFields()\n        Object.assign(this.$data, this.$options.data())\n        this.changeHeader(defaultLayout)\n        this.theme.name = 'default'\n        this.theme.layout = this.layout\n        this.theme.header = this.header\n        this.theme.tabsBar = this.tabsBar\n        this.drawerVisible = false\n        location.reload()\n      },\n      handleGetCode() {\n        const url =\n          'https://github.com/chuzhixin/vue-admin-better/tree/master/src/views'\n        let path = this.$route.path + '/index.vue'\n        if (path === '/vab/menu1/menu1-1/menu1-1-1/index.vue') {\n          path = '/vab/nested/menu1/menu1-1/menu1-1-1/index.vue'\n        }\n        if (path === '/vab/icon/awesomeIcon/index.vue') {\n          path = '/vab/icon/index.vue'\n        }\n        if (path === '/vab/icon/remixIcon/index.vue') {\n          path = '/vab/icon/remixIcon.vue'\n        }\n        if (path === '/vab/icon/colorfulIcon/index.vue') {\n          path = '/vab/icon/colorfulIcon.vue'\n        }\n        if (path === '/vab/table/comprehensiveTable/index.vue') {\n          path = '/vab/table/index.vue'\n        }\n        if (path === '/vab/table/inlineEditTable/index.vue') {\n          path = '/vab/table/inlineEditTable.vue'\n        }\n        window.open(url + path)\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  @mixin right-bar {\n    position: fixed;\n    right: 0;\n    z-index: $base-z-index;\n    width: 60px;\n    min-height: 60px;\n    text-align: center;\n    cursor: pointer;\n    background: $base-color-blue;\n    border-radius: $base-border-radius;\n\n    > div {\n      padding-top: 10px;\n      border-bottom: 0 !important;\n\n      &:hover {\n        opacity: 0.9;\n      }\n\n      & + div {\n        border-top: 1px solid $base-color-white;\n      }\n\n      p {\n        padding: 0;\n        margin: 0;\n        font-size: $base-font-size-small;\n        line-height: 30px;\n        color: $base-color-white;\n      }\n    }\n  }\n\n  .theme-bar-setting {\n    @include right-bar;\n\n    top: calc((100vh - 110px) / 2);\n\n    ::v-deep {\n      svg:not(:root).svg-inline--fa {\n        display: block;\n        margin-right: auto;\n        margin-left: auto;\n        color: $base-color-white;\n      }\n\n      .svg-icon {\n        display: block;\n        margin-right: auto;\n        margin-left: auto;\n        font-size: 20px;\n        color: $base-color-white;\n        fill: $base-color-white;\n      }\n    }\n  }\n\n  .el-drawer__body {\n    padding: 20px;\n  }\n</style>\n<style lang=\"scss\">\n  .el-drawer__wrapper {\n    outline: none !important;\n\n    * {\n      outline: none !important;\n    }\n  }\n\n  .vab-color-picker {\n    .el-color-dropdown__link-btn {\n      display: none;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/layouts/export.js",
    "content": "/**\n * @author https://vue-admin-better.com （不想保留author可删除）\n * @description 公共布局及样式自动引入\n */\n\nimport Vue from 'vue'\n\nconst requireComponents = require.context('./components', true, /\\.vue$/)\nrequireComponents.keys().forEach((fileName) => {\n  const componentConfig = requireComponents(fileName)\n  const componentName = componentConfig.default.name\n  Vue.component(componentName, componentConfig.default || componentConfig)\n})\n\nconst requireZxLayouts = require.context('layouts', true, /\\.vue$/)\nrequireZxLayouts.keys().forEach((fileName) => {\n  const componentConfig = requireZxLayouts(fileName)\n  const componentName = componentConfig.default.name\n  Vue.component(componentName, componentConfig.default || componentConfig)\n})\n\nconst requireThemes = require.context('@/styles/themes', true, /\\.scss$/)\nrequireThemes.keys().forEach((fileName) => {\n  require(`@/styles/themes/${fileName.slice(2)}`)\n})\n"
  },
  {
    "path": "src/layouts/index.vue",
    "content": "<template>\n  <div class=\"vue-admin-better-wrapper\" :class=\"classObj\">\n    <div\n      v-if=\"'horizontal' === layout\"\n      class=\"layout-container-horizontal\"\n      :class=\"{\n        fixed: header === 'fixed',\n        'no-tabs-bar': tabsBar === 'false' || tabsBar === false,\n      }\"\n    >\n      <div :class=\"header === 'fixed' ? 'fixed-header' : ''\">\n        <vab-top-bar />\n        <div\n          v-if=\"tabsBar === 'true' || tabsBar === true\"\n          :class=\"{ 'tag-view-show': tabsBar }\"\n        >\n          <div class=\"vab-main\">\n            <vab-tabs-bar />\n          </div>\n        </div>\n      </div>\n      <div class=\"vab-main main-padding\">\n        <vab-ad />\n        <vab-app-main />\n      </div>\n    </div>\n    <div\n      v-else\n      class=\"layout-container-vertical\"\n      :class=\"{\n        fixed: header === 'fixed',\n        'no-tabs-bar': tabsBar === 'false' || tabsBar === false,\n      }\"\n    >\n      <div\n        v-if=\"device === 'mobile' && collapse === false\"\n        class=\"mask\"\n        @click=\"handleFoldSideBar\"\n      />\n      <vab-side-bar />\n      <div class=\"vab-main\" :class=\"collapse ? 'is-collapse-main' : ''\">\n        <div :class=\"header === 'fixed' ? 'fixed-header' : ''\">\n          <vab-nav-bar />\n          <vab-tabs-bar v-if=\"tabsBar === 'true' || tabsBar === true\" />\n        </div>\n        <vab-ad />\n        <vab-app-main />\n      </div>\n    </div>\n    <el-backtop />\n  </div>\n</template>\n\n<script>\n  import { mapActions, mapGetters } from 'vuex'\n  import { tokenName } from '@/config'\n  export default {\n    name: 'Layout',\n    data() {\n      return { oldLayout: '' }\n    },\n    computed: {\n      ...mapGetters({\n        layout: 'settings/layout',\n        tabsBar: 'settings/tabsBar',\n        collapse: 'settings/collapse',\n        header: 'settings/header',\n        device: 'settings/device',\n      }),\n      classObj() {\n        return {\n          mobile: this.device === 'mobile',\n        }\n      },\n    },\n    beforeMount() {\n      window.addEventListener('resize', this.handleResize)\n    },\n    beforeDestroy() {\n      window.removeEventListener('resize', this.handleResize)\n    },\n    mounted() {\n      this.oldLayout = this.layout\n      const userAgent = navigator.userAgent\n      if (userAgent.includes('Juejin')) {\n        this.$baseAlert(\n          'vue-admin-better不支持在掘金内置浏览器演示，请手动复制以下地址到浏览器中查看http://mpfhrd48.sanxing.uz7.cn/vue-admin-better'\n        )\n      }\n      const isMobile = this.handleIsMobile()\n      if (isMobile) {\n        if (isMobile) {\n          //横向布局时如果是手机端访问那么改成纵向版\n          this.$store.dispatch('settings/changeLayout', 'vertical')\n        } else {\n          this.$store.dispatch('settings/changeLayout', this.oldLayout)\n        }\n        this.$store.dispatch('settings/toggleDevice', 'mobile')\n        setTimeout(() => {\n          this.$store.dispatch('settings/foldSideBar')\n        }, 2000)\n      } else {\n        this.$store.dispatch('settings/openSideBar')\n      }\n      this.$nextTick(() => {\n        window.addEventListener(\n          'storage',\n          (e) => {\n            if (e.key === tokenName || e.key === null) window.location.reload()\n            if (e.key === tokenName && e.value === null)\n              window.location.reload()\n          },\n          false\n        )\n      })\n    },\n    methods: {\n      ...mapActions({\n        handleFoldSideBar: 'settings/foldSideBar',\n      }),\n      handleIsMobile() {\n        return document.body.getBoundingClientRect().width - 1 < 992\n      },\n      handleResize() {\n        if (!document.hidden) {\n          const isMobile = this.handleIsMobile()\n          if (isMobile) {\n            //横向布局时如果是手机端访问那么改成纵向版\n            this.$store.dispatch('settings/changeLayout', 'vertical')\n          } else {\n            this.$store.dispatch('settings/changeLayout', this.oldLayout)\n          }\n\n          this.$store.dispatch(\n            'settings/toggleDevice',\n            isMobile ? 'mobile' : 'desktop'\n          )\n        }\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  @mixin fix-header {\n    position: fixed;\n    top: 0;\n    right: 0;\n    left: 0;\n    z-index: $base-z-index - 2;\n    width: 100%;\n    overflow: hidden;\n  }\n\n  .vue-admin-better-wrapper {\n    position: relative;\n    width: 100%;\n    height: 100%;\n\n    .layout-container-horizontal {\n      position: relative;\n\n      &.fixed {\n        padding-top: calc(#{$base-top-bar-height} + #{$base-tabs-bar-height});\n      }\n\n      &.fixed.no-tabs-bar {\n        padding-top: $base-top-bar-height;\n      }\n\n      ::v-deep {\n        .vab-main {\n          width: 88%;\n          margin: auto;\n        }\n\n        .fixed-header {\n          @include fix-header;\n        }\n\n        .tag-view-show {\n          background: $base-color-white;\n          box-shadow: $base-box-shadow;\n        }\n\n        .nav-bar-container {\n          .fold-unfold {\n            display: none;\n          }\n        }\n\n        .main-padding {\n          .app-main-container {\n            margin-top: $base-padding;\n            margin-bottom: $base-padding;\n            background: $base-color-white;\n          }\n        }\n      }\n    }\n\n    .layout-container-vertical {\n      position: relative;\n\n      .mask {\n        position: fixed;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n        z-index: $base-z-index - 1;\n        width: 100%;\n        height: 100vh;\n        overflow: hidden;\n        background: #000;\n        opacity: 0.5;\n      }\n\n      &.fixed {\n        padding-top: calc(#{$base-nav-bar-height} + #{$base-tabs-bar-height});\n      }\n\n      &.fixed.no-tabs-bar {\n        padding-top: $base-nav-bar-height;\n      }\n\n      .vab-main {\n        position: relative;\n        min-height: 100%;\n        margin-left: $base-left-menu-width;\n        background: #f6f8f9;\n        transition: $base-transition;\n\n        ::v-deep {\n          .fixed-header {\n            @include fix-header;\n\n            left: $base-left-menu-width;\n            width: $base-right-content-width;\n            box-shadow: $base-box-shadow;\n            transition: $base-transition;\n          }\n\n          .nav-bar-container {\n            position: relative;\n            box-sizing: border-box;\n          }\n\n          .tabs-bar-container {\n            box-sizing: border-box;\n          }\n\n          .app-main-container {\n            width: calc(100% - #{$base-padding} - #{$base-padding});\n            margin: $base-padding auto;\n            background: $base-color-white;\n            border-radius: $base-border-radius;\n          }\n        }\n\n        &.is-collapse-main {\n          margin-left: $base-left-menu-width-min;\n\n          ::v-deep {\n            .fixed-header {\n              left: $base-left-menu-width-min;\n              width: calc(100% - 65px);\n            }\n          }\n        }\n      }\n    }\n\n    /* 手机端开始 */\n    &.mobile {\n      ::v-deep {\n        .el-pager,\n        .el-pagination__jump {\n          display: none;\n        }\n\n        .layout-container-vertical {\n          .el-scrollbar.side-bar-container.is-collapse {\n            width: 0;\n          }\n\n          .vab-main {\n            width: 100%;\n            margin-left: 0;\n          }\n        }\n\n        .vab-main {\n          .fixed-header {\n            left: 0 !important;\n            width: 100% !important;\n          }\n        }\n      }\n    }\n\n    /* 手机端结束 */\n  }\n</style>\n"
  },
  {
    "path": "src/main.js",
    "content": "import Vue from 'vue'\nimport App from './App'\nimport store from './store'\nimport router from './router'\nimport './plugins'\nimport '@/layouts/export'\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 生产环境默认都使用mock，如果正式用于生产环境时，记得去掉\n */\n\nif (process.env.NODE_ENV === 'production') {\n  const { mockXHR } = require('@/utils/static')\n  mockXHR()\n}\n\nVue.config.productionTip = false\n\nnew Vue({\n  el: '#vue-admin-better',\n  router,\n  store,\n  render: (h) => h(App),\n})\n"
  },
  {
    "path": "src/plugins/element.js",
    "content": "import Vue from 'vue'\nimport ElementUI from 'element-ui'\nimport 'element-ui/lib/theme-chalk/display.css'\n\nimport '@/styles/element-variables.scss'\n\nVue.use(ElementUI, {\n  size: 'small',\n})\n"
  },
  {
    "path": "src/plugins/index.js",
    "content": "/* 公共引入,勿随意修改,修改时需经过确认 */\nimport Vue from 'vue'\nimport './element'\nimport './support'\nimport '@/styles/vab.scss'\nimport '@/remixIcon'\nimport '@/colorfulIcon'\nimport '@/config/permission'\nimport '@/utils/errorLog'\nimport './vabIcon'\n\nimport Vab from '@/utils/vab'\nimport VabPermissions from 'layouts/Permissions'\n\nVue.use(Vab)\nVue.use(VabPermissions)\n"
  },
  {
    "path": "src/plugins/support.js",
    "content": "import { MessageBox } from 'element-ui'\nimport { donation } from '@/config'\nimport { dependencies, repository } from '../../package.json'\n\nif (!!window.ActiveXObject || 'ActiveXObject' in window) {\n  MessageBox({\n    title: '温馨提示',\n    message:\n      '自2015年3月起，微软已宣布弃用IE，且不再对IE提供任何更新维护，请<a target=\"_blank\" style=\"color:blue\" href=\"https://www.microsoft.com/zh-cn/edge/\">点击此处</a>访问微软官网更新浏览器，如果您使用的是双核浏览器,请您切换浏览器内核为极速模式',\n    type: 'warning',\n    showClose: false,\n    showConfirmButton: false,\n    closeOnClickModal: false,\n    closeOnPressEscape: false,\n    closeOnHashChange: false,\n    dangerouslyUseHTMLString: true,\n  })\n}\nif (!dependencies['vab-icon'] || !dependencies['layouts'])\n  document.body.innerHTML = ''\n"
  },
  {
    "path": "src/plugins/vabIcon.js",
    "content": "import Vue from 'vue'\nimport VabIcon from 'vab-icon'\n\nVue.component('VabIcon', VabIcon)\n"
  },
  {
    "path": "src/remixIcon/index.js",
    "content": "const req = require.context('./svg', false, /\\.svg$/),\n  requireAll = (requireContext) => {\n    /*let a = requireContext.keys().map(requireContext);\n    let arr = [];\n    for (let i = 0; i < a.length; i++) {\n      console.log();\n      let icon = a[i].default.id;\n      arr.push(icon);\n    }\n    console.log(JSON.stringify(arr));*/\n    return requireContext.keys().map(requireContext)\n  }\nrequireAll(req)\n"
  },
  {
    "path": "src/router/index.js",
    "content": "/**\n * @copyright chuzhixin 1204505056@qq.com\n * @description router全局配置，如有必要可分文件抽离\n */\n\nimport Vue from 'vue'\nimport VueRouter from 'vue-router'\nimport Layout from '@/layouts'\nimport EmptyLayout from '@/layouts/EmptyLayout'\nimport { publicPath, routerMode } from '@/config'\n\nVue.use(VueRouter)\n\nexport const constantRoutes = [\n  {\n    path: '/login',\n    component: () => import('@/views/login/index'),\n    hidden: true,\n  },\n  {\n    path: '/register',\n    component: () => import('@/views/register/index'),\n    hidden: true,\n  },\n  {\n    path: '/401',\n    name: '401',\n    component: () => import('@/views/401'),\n    hidden: true,\n  },\n  {\n    path: '/404',\n    name: '404',\n    component: () => import('@/views/404'),\n    hidden: true,\n  },\n]\n\n/*当settings.js里authentication配置的是intelligence时，views引入交给前端配置*/\nexport const asyncRoutes = [\n  {\n    path: '/',\n    component: Layout,\n    redirect: '/index',\n    children: [\n      {\n        path: '/index',\n        name: 'Index',\n        component: () => import('@/views/index/index'),\n        meta: {\n          title: '首页',\n          icon: 'home',\n          affix: true,\n          noKeepAlive: true,\n        },\n      },\n    ],\n  },\n  {\n    path: '*',\n    redirect: '/404',\n    hidden: true,\n  },\n]\n\nconst router = new VueRouter({\n  base: routerMode === 'history' ? publicPath : '',\n  mode: routerMode,\n  scrollBehavior: () => ({\n    y: 0,\n  }),\n  routes: constantRoutes,\n})\n//注释的地方是允许路由重复点击，如果你觉得框架路由跳转规范太过严格可选择放开\n/* const originalPush = VueRouter.prototype.push;\nVueRouter.prototype.push = function push(location, onResolve, onReject) {\n  if (onResolve || onReject)\n    return originalPush.call(this, location, onResolve, onReject);\n  return originalPush.call(this, location).catch((err) => err);\n}; */\n\nexport function resetRouter() {\n  router.matcher = new VueRouter({\n    base: routerMode === 'history' ? publicPath : '',\n    mode: routerMode,\n    scrollBehavior: () => ({\n      y: 0,\n    }),\n    routes: constantRoutes,\n  }).matcher\n}\n\nexport default router\n"
  },
  {
    "path": "src/store/index.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 导入所有 vuex 模块，自动加入namespaced:true，用于解决vuex命名冲突，请勿修改。\n */\n\nimport Vue from 'vue'\nimport Vuex from 'vuex'\n\nVue.use(Vuex)\nconst files = require.context('./modules', false, /\\.js$/)\nconst modules = {}\n\nfiles.keys().forEach((key) => {\n  modules[key.replace(/(\\.\\/|\\.js)/g, '')] = files(key).default\n})\nObject.keys(modules).forEach((key) => {\n  modules[key]['namespaced'] = true\n})\nconst store = new Vuex.Store({\n  modules,\n})\nexport default store\n"
  },
  {
    "path": "src/store/modules/errorLog.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 异常捕获的状态拦截，请勿修改\n */\n\nconst state = { errorLogs: [] }\nconst getters = {\n  errorLogs: (state) => state.errorLogs,\n}\nconst mutations = {\n  addErrorLog(state, errorLog) {\n    state.errorLogs.push(errorLog)\n  },\n  clearErrorLog: (state) => {\n    state.errorLogs.splice(0)\n  },\n}\nconst actions = {\n  addErrorLog({ commit }, errorLog) {\n    commit('addErrorLog', errorLog)\n  },\n  clearErrorLog({ commit }) {\n    commit('clearErrorLog')\n  },\n}\nexport default { state, getters, mutations, actions }\n"
  },
  {
    "path": "src/store/modules/routes.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 路由拦截状态管理，目前两种模式：all模式与intelligence模式，其中partialRoutes是菜单暂未使用\n */\nimport { asyncRoutes, constantRoutes } from '@/router'\nimport { getRouterList } from '@/api/router'\nimport { convertRouter, filterAsyncRoutes } from '@/utils/handleRoutes'\n\nconst state = { routes: [], partialRoutes: [] }\nconst getters = {\n  routes: (state) => state.routes,\n  partialRoutes: (state) => state.partialRoutes,\n}\nconst mutations = {\n  setRoutes(state, routes) {\n    state.routes = constantRoutes.concat(routes)\n  },\n  setAllRoutes(state, routes) {\n    state.routes = constantRoutes.concat(routes)\n  },\n  setPartialRoutes(state, routes) {\n    state.partialRoutes = constantRoutes.concat(routes)\n  },\n}\nconst actions = {\n  async setRoutes({ commit }, permissions) {\n    //开源版只过滤动态路由permissions，admin不再默认拥有全部权限\n    const finallyAsyncRoutes = await filterAsyncRoutes(\n      [...asyncRoutes],\n      permissions\n    )\n    commit('setRoutes', finallyAsyncRoutes)\n    return finallyAsyncRoutes\n  },\n  async setAllRoutes({ commit }) {\n    let { data } = await getRouterList()\n    data.push({ path: '*', redirect: '/404', hidden: true })\n    let accessRoutes = convertRouter(data)\n    commit('setAllRoutes', accessRoutes)\n    return accessRoutes\n  },\n  setPartialRoutes({ commit }, accessRoutes) {\n    commit('setPartialRoutes', accessRoutes)\n    return accessRoutes\n  },\n}\nexport default { state, getters, mutations, actions }\n"
  },
  {
    "path": "src/store/modules/settings.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 所有全局配置的状态管理，如无必要请勿修改\n */\n\nimport defaultSettings from '@/config'\n\nconst { tabsBar, logo, layout, header, themeBar } = defaultSettings\nconst theme = JSON.parse(localStorage.getItem('vue-admin-better-theme')) || ''\nconst state = {\n  tabsBar: theme.tabsBar || tabsBar,\n  logo,\n  collapse: false,\n  layout: theme.layout || layout,\n  header: theme.header || header,\n  device: 'desktop',\n  themeBar,\n}\nconst getters = {\n  collapse: (state) => state.collapse,\n  device: (state) => state.device,\n  header: (state) => state.header,\n  layout: (state) => state.layout,\n  logo: (state) => state.logo,\n  tabsBar: (state) => state.tabsBar,\n  themeBar: (state) => state.themeBar,\n}\nconst mutations = {\n  changeLayout: (state, layout) => {\n    if (layout) state.layout = layout\n  },\n  changeHeader: (state, header) => {\n    if (header) state.header = header\n  },\n  changeTabsBar: (state, tabsBar) => {\n    if (tabsBar) state.tabsBar = tabsBar\n  },\n  changeCollapse: (state) => {\n    state.collapse = !state.collapse\n  },\n  foldSideBar: (state) => {\n    state.collapse = true\n  },\n  openSideBar: (state) => {\n    state.collapse = false\n  },\n  toggleDevice: (state, device) => {\n    state.device = device\n  },\n}\nconst actions = {\n  changeLayout({ commit }, layout) {\n    commit('changeLayout', layout)\n  },\n  changeHeader({ commit }, header) {\n    commit('changeHeader', header)\n  },\n  changeTabsBar({ commit }, tabsBar) {\n    commit('changeTabsBar', tabsBar)\n  },\n  changeCollapse({ commit }) {\n    commit('changeCollapse')\n  },\n  foldSideBar({ commit }) {\n    commit('foldSideBar')\n  },\n  openSideBar({ commit }) {\n    commit('openSideBar')\n  },\n  toggleDevice({ commit }, device) {\n    commit('toggleDevice', device)\n  },\n}\nexport default { state, getters, mutations, actions }\n"
  },
  {
    "path": "src/store/modules/table.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 代码生成机状态管理\n */\n\nconst state = { srcCode: '' }\nconst getters = {\n  srcTableCode: (state) => state.srcCode,\n}\n\nconst mutations = {\n  setTableCode(state, srcCode) {\n    state.srcCode = srcCode\n  },\n}\nconst actions = {\n  setTableCode({ commit }, srcCode) {\n    commit('setTableCode', srcCode)\n  },\n}\nexport default { state, getters, mutations, actions }\n"
  },
  {
    "path": "src/store/modules/tabsBar.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description tabsBar多标签页逻辑，前期借鉴了很多开源项目发现都有个共同的特点很繁琐并不符合框架设计的初衷，后来在github用户cyea的启发下完成了重构，请勿修改\n */\n\nconst state = {\n  visitedRoutes: [],\n}\nconst getters = {\n  visitedRoutes: (state) => state.visitedRoutes,\n}\nconst mutations = {\n  addVisitedRoute(state, route) {\n    let target = state.visitedRoutes.find((item) => item.path === route.path)\n    if (target) {\n      if (route.fullPath !== target.fullPath) Object.assign(target, route)\n      return\n    }\n    state.visitedRoutes.push(Object.assign({}, route))\n  },\n  delVisitedRoute(state, route) {\n    state.visitedRoutes.forEach((item, index) => {\n      if (item.path === route.path) state.visitedRoutes.splice(index, 1)\n    })\n  },\n  delOthersVisitedRoute(state, route) {\n    state.visitedRoutes = state.visitedRoutes.filter(\n      (item) => item.meta.affix || item.path === route.path\n    )\n  },\n  delLeftVisitedRoute(state, route) {\n    let index = state.visitedRoutes.length\n    state.visitedRoutes = state.visitedRoutes.filter((item) => {\n      if (item.name === route.name) index = state.visitedRoutes.indexOf(item)\n      return item.meta.affix || index <= state.visitedRoutes.indexOf(item)\n    })\n  },\n  delRightVisitedRoute(state, route) {\n    let index = state.visitedRoutes.length\n    state.visitedRoutes = state.visitedRoutes.filter((item) => {\n      if (item.name === route.name) index = state.visitedRoutes.indexOf(item)\n      return item.meta.affix || index >= state.visitedRoutes.indexOf(item)\n    })\n  },\n  delAllVisitedRoutes(state) {\n    state.visitedRoutes = state.visitedRoutes.filter((item) => item.meta.affix)\n  },\n  updateVisitedRoute(state, route) {\n    state.visitedRoutes.forEach((item) => {\n      if (item.path === route.path) item = Object.assign(item, route)\n    })\n  },\n}\nconst actions = {\n  addVisitedRoute({ commit }, route) {\n    commit('addVisitedRoute', route)\n  },\n  async delRoute({ dispatch, state }, route) {\n    await dispatch('delVisitedRoute', route)\n    return {\n      visitedRoutes: [...state.visitedRoutes],\n    }\n  },\n  delVisitedRoute({ commit, state }, route) {\n    commit('delVisitedRoute', route)\n    return [...state.visitedRoutes]\n  },\n  async delOthersRoutes({ dispatch, state }, route) {\n    await dispatch('delOthersVisitedRoute', route)\n    return {\n      visitedRoutes: [...state.visitedRoutes],\n    }\n  },\n  async delLeftRoutes({ dispatch, state }, route) {\n    await dispatch('delLeftVisitedRoute', route)\n    return {\n      visitedRoutes: [...state.visitedRoutes],\n    }\n  },\n  async delRightRoutes({ dispatch, state }, route) {\n    await dispatch('delRightVisitedRoute', route)\n    return {\n      visitedRoutes: [...state.visitedRoutes],\n    }\n  },\n  delOthersVisitedRoute({ commit, state }, route) {\n    commit('delOthersVisitedRoute', route)\n    return [...state.visitedRoutes]\n  },\n  delLeftVisitedRoute({ commit, state }, route) {\n    commit('delLeftVisitedRoute', route)\n    return [...state.visitedRoutes]\n  },\n  delRightVisitedRoute({ commit, state }, route) {\n    commit('delRightVisitedRoute', route)\n    return [...state.visitedRoutes]\n  },\n  async delAllRoutes({ dispatch, state }, route) {\n    await dispatch('delAllVisitedRoutes', route)\n    return {\n      visitedRoutes: [...state.visitedRoutes],\n    }\n  },\n  delAllVisitedRoutes({ commit, state }) {\n    commit('delAllVisitedRoutes')\n    return [...state.visitedRoutes]\n  },\n  updateVisitedRoute({ commit }, route) {\n    commit('updateVisitedRoute', route)\n  },\n}\nexport default { state, getters, mutations, actions }\n"
  },
  {
    "path": "src/store/modules/user.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 登录、获取用户信息、退出登录、清除accessToken逻辑，不建议修改\n */\n\nimport Vue from 'vue'\nimport { getUserInfo, login, logout } from '@/api/user'\nimport {\n  getAccessToken,\n  removeAccessToken,\n  setAccessToken,\n} from '@/utils/accessToken'\nimport { resetRouter } from '@/router'\nimport { title, tokenName } from '@/config'\n\nconst state = {\n  accessToken: getAccessToken(),\n  username: '',\n  avatar: '',\n  permissions: [],\n}\nconst getters = {\n  accessToken: (state) => state.accessToken,\n  username: (state) => state.username,\n  avatar: (state) => state.avatar,\n  permissions: (state) => state.permissions,\n}\nconst mutations = {\n  setAccessToken(state, accessToken) {\n    state.accessToken = accessToken\n    setAccessToken(accessToken)\n  },\n  setUsername(state, username) {\n    state.username = username\n  },\n  setAvatar(state, avatar) {\n    state.avatar = avatar\n  },\n  setPermissions(state, permissions) {\n    state.permissions = permissions\n  },\n}\nconst actions = {\n  setPermissions({ commit }, permissions) {\n    commit('setPermissions', permissions)\n  },\n  async login({ commit }, userInfo) {\n    const { data } = await login(userInfo)\n    const accessToken = data[tokenName]\n    if (accessToken) {\n      commit('setAccessToken', accessToken)\n      const hour = new Date().getHours()\n      const thisTime =\n        hour < 8\n          ? '早上好'\n          : hour <= 11\n          ? '上午好'\n          : hour <= 13\n          ? '中午好'\n          : hour < 18\n          ? '下午好'\n          : '晚上好'\n      Vue.prototype.$baseNotify(`欢迎登录${title}`, `${thisTime}！`)\n    } else {\n      Vue.prototype.$baseMessage(\n        `登录接口异常，未正确返回${tokenName}...`,\n        'error'\n      )\n    }\n  },\n  async getUserInfo({ commit, state }) {\n    const { data } = await getUserInfo(state.accessToken)\n    if (!data) {\n      Vue.prototype.$baseMessage('验证失败，请重新登录...', 'error')\n      return false\n    }\n    let { permissions, username, avatar } = data\n    if (permissions && username && Array.isArray(permissions)) {\n      commit('setPermissions', permissions)\n      commit('setUsername', username)\n      commit('setAvatar', avatar)\n      return permissions\n    } else {\n      Vue.prototype.$baseMessage('用户信息接口异常', 'error')\n      return false\n    }\n  },\n  async logout({ dispatch }) {\n    await logout(state.accessToken)\n    await dispatch('resetAccessToken')\n    await resetRouter()\n  },\n  resetAccessToken({ commit }) {\n    commit('setPermissions', [])\n    commit('setAccessToken', '')\n    removeAccessToken()\n  },\n}\nexport default { state, getters, mutations, actions }\n"
  },
  {
    "path": "src/styles/element-variables.scss",
    "content": "@charset \"utf-8\";\n\n/* Transition\n-------------------------- */\n$--all-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);\n$--fade-transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);\n$--fade-linear-transition: opacity 200ms linear;\n$--md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1),\n  opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);\n$--border-transition-base: border-color 0.2s\n  cubic-bezier(0.645, 0.045, 0.355, 1);\n$--color-transition-base: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);\n\n/* Color\n-------------------------- */\n/// color|1|Brand Color|0\n$--color-primary: $base-color-blue;\n/// color|1|Background Color|4\n$--color-white: #fff;\n/// color|1|Background Color|4\n$--color-black: #000;\n$--color-primary-light-1: mix($--color-white, $--color-primary, 10%);\n\n/* 53a8ff */\n$--color-primary-light-2: mix($--color-white, $--color-primary, 20%);\n\n/* 66b1ff */\n$--color-primary-light-3: mix($--color-white, $--color-primary, 30%);\n\n/* 79bbff */\n$--color-primary-light-4: mix($--color-white, $--color-primary, 40%);\n\n/* 8cc5ff */\n$--color-primary-light-5: mix($--color-white, $--color-primary, 50%);\n\n/* a0cfff */\n$--color-primary-light-6: mix($--color-white, $--color-primary, 60%);\n\n/* b3d8ff */\n$--color-primary-light-7: mix($--color-white, $--color-primary, 70%);\n\n/* c6e2ff */\n$--color-primary-light-8: mix($--color-white, $--color-primary, 80%);\n\n/* d9ecff */\n$--color-primary-light-9: mix($--color-white, $--color-primary, 90%);\n\n/* ecf5ff */\n/// color|1|Functional Color|1\n$--color-success: $base-color-green;\n/// color|1|Functional Color|1\n$--color-warning: $base-color-yellow;\n/// color|1|Functional Color|1\n$--color-danger: $base-color-red;\n/// color|1|Functional Color|1\n$--color-info: #909399;\n\n$--color-success-light: mix($--color-white, $--color-success, 80%);\n$--color-warning-light: mix($--color-white, $--color-warning, 80%);\n$--color-danger-light: mix($--color-white, $--color-danger, 80%);\n$--color-info-light: mix($--color-white, $--color-info, 80%);\n\n$--color-success-lighter: mix($--color-white, $--color-success, 90%);\n$--color-warning-lighter: mix($--color-white, $--color-warning, 90%);\n$--color-danger-lighter: mix($--color-white, $--color-danger, 90%);\n$--color-info-lighter: mix($--color-white, $--color-info, 90%);\n/// color|1|Font Color|2\n$--color-text-primary: #303133;\n/// color|1|Font Color|2\n$--color-text-regular: #606266;\n/// color|1|Font Color|2\n$--color-text-secondary: #909399;\n/// color|1|Font Color|2\n$--color-text-placeholder: #c0c4cc;\n/// color|1|Border Color|3\n$--border-color-base: #dcdfe6;\n/// color|1|Border Color|3\n$--border-color-light: #e4e7ed;\n/// color|1|Border Color|3\n$--border-color-lighter: #ebeef5;\n/// color|1|Border Color|3\n$--border-color-extra-light: #f2f6fc;\n\n// Background\n/// color|1|Background Color|4\n$--background-color-base: #f5f7fa;\n\n/* Link\n-------------------------- */\n$--link-color: $--color-primary-light-2;\n$--link-hover-color: $--color-primary;\n\n/* Border\n-------------------------- */\n$--border-width-base: 1px;\n$--border-style-base: solid;\n$--border-color-hover: $--color-text-placeholder;\n$--border-base: $--border-width-base $--border-style-base $--border-color-base;\n/// borderRadius|1|Radius|0\n$--border-radius-base: $base-border-radius;\n/// borderRadius|1|Radius|0\n$--border-radius-small: $base-border-radius;\n/// borderRadius|1|Radius|0\n$--border-radius-circle: 100%;\n/// borderRadius|1|Radius|0\n$--border-radius-zero: 0;\n\n// Box-shadow\n/// boxShadow|1|Shadow|1\n$--box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);\n// boxShadow|1|Shadow|1\n$--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.12);\n/// boxShadow|1|Shadow|1\n$--box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n\n/* Fill\n-------------------------- */\n$--fill-base: $--color-white;\n\n/* Typography\n-------------------------- */\n$--font-path: \"~element-ui/lib/theme-chalk/fonts\";\n$--font-display: \"auto\";\n/// fontSize|1|Font Size|0\n$--font-size-extra-large: 20px;\n/// fontSize|1|Font Size|0\n$--font-size-large: 18px;\n/// fontSize|1|Font Size|0\n$--font-size-medium: 16px;\n/// fontSize|1|Font Size|0\n$--font-size-base: 14px;\n/// fontSize|1|Font Size|0\n$--font-size-small: 13px;\n/// fontSize|1|Font Size|0\n$--font-size-extra-small: 12px;\n/// fontWeight|1|Font Weight|1\n$--font-weight-primary: 500;\n/// fontWeight|1|Font Weight|1\n$--font-weight-secondary: 100;\n/// fontLineHeight|1|Line Height|2\n$--font-line-height-primary: 24px;\n/// fontLineHeight|1|Line Height|2\n$--font-line-height-secondary: 16px;\n$--font-color-disabled-base: #bbb;\n\n/* Size\n-------------------------- */\n$--size-base: 14px;\n\n/* z-index\n-------------------------- */\n$--index-normal: 1;\n$--index-top: 1000;\n$--index-popper: 2000;\n\n/* Disable base\n-------------------------- */\n$--disabled-fill-base: $--background-color-base;\n$--disabled-color-base: $--color-text-placeholder;\n$--disabled-border-base: $--border-color-light;\n\n/* Icon\n-------------------------- */\n$--icon-color: #666;\n$--icon-color-base: $--color-info;\n\n/* Checkbox\n-------------------------- */\n/// fontSize||Font|1\n$--checkbox-font-size: 14px;\n/// fontWeight||Font|1\n$--checkbox-font-weight: $--font-weight-primary;\n/// color||Color|0\n$--checkbox-font-color: $--color-text-regular;\n$--checkbox-input-height: 14px;\n$--checkbox-input-width: 14px;\n/// borderRadius||Border|2\n$--checkbox-border-radius: $--border-radius-small;\n/// color||Color|0\n$--checkbox-background-color: $--color-white;\n$--checkbox-input-border: $--border-base;\n\n/// color||Color|0\n$--checkbox-disabled-border-color: $--border-color-base;\n$--checkbox-disabled-input-fill: #edf2fc;\n$--checkbox-disabled-icon-color: $--color-text-placeholder;\n\n$--checkbox-disabled-checked-input-fill: $--border-color-extra-light;\n$--checkbox-disabled-checked-input-border-color: $--border-color-base;\n$--checkbox-disabled-checked-icon-color: $--color-text-placeholder;\n\n/// color||Color|0\n$--checkbox-checked-font-color: $--color-primary;\n$--checkbox-checked-input-border-color: $--color-primary;\n/// color||Color|0\n$--checkbox-checked-background-color: $--color-primary;\n$--checkbox-checked-icon-color: $--fill-base;\n\n$--checkbox-input-border-color-hover: $--color-primary;\n/// height||Other|4\n$--checkbox-bordered-height: 40px;\n/// padding||Spacing|3\n$--checkbox-bordered-padding: 9px 20px 9px 10px;\n/// padding||Spacing|3\n$--checkbox-bordered-medium-padding: 7px 20px 7px 10px;\n/// padding||Spacing|3\n$--checkbox-bordered-small-padding: 5px 15px 5px 10px;\n/// padding||Spacing|3\n$--checkbox-bordered-mini-padding: 3px 15px 3px 10px;\n$--checkbox-bordered-medium-input-height: 14px;\n$--checkbox-bordered-medium-input-width: 14px;\n/// height||Other|4\n$--checkbox-bordered-medium-height: 36px;\n$--checkbox-bordered-small-input-height: 12px;\n$--checkbox-bordered-small-input-width: 12px;\n/// height||Other|4\n$--checkbox-bordered-small-height: 32px;\n$--checkbox-bordered-mini-input-height: 12px;\n$--checkbox-bordered-mini-input-width: 12px;\n/// height||Other|4\n$--checkbox-bordered-mini-height: 28px;\n\n/// color||Color|0\n$--checkbox-button-checked-background-color: $--color-primary;\n/// color||Color|0\n$--checkbox-button-checked-font-color: $--color-white;\n/// color||Color|0\n$--checkbox-button-checked-border-color: $--color-primary;\n\n/* Radio\n-------------------------- */\n/// fontSize||Font|1\n$--radio-font-size: $--font-size-base;\n/// fontWeight||Font|1\n$--radio-font-weight: $--font-weight-primary;\n/// color||Color|0\n$--radio-font-color: $--color-text-regular;\n$--radio-input-height: 14px;\n$--radio-input-width: 14px;\n/// borderRadius||Border|2\n$--radio-input-border-radius: $--border-radius-circle;\n/// color||Color|0\n$--radio-input-background-color: $--color-white;\n$--radio-input-border: $--border-base;\n/// color||Color|0\n$--radio-input-border-color: $--border-color-base;\n/// color||Color|0\n$--radio-icon-color: $--color-white;\n\n$--radio-disabled-input-border-color: $--disabled-border-base;\n$--radio-disabled-input-fill: $--disabled-fill-base;\n$--radio-disabled-icon-color: $--disabled-fill-base;\n\n$--radio-disabled-checked-input-border-color: $--disabled-border-base;\n$--radio-disabled-checked-input-fill: $--disabled-fill-base;\n$--radio-disabled-checked-icon-color: $--color-text-placeholder;\n\n/// color||Color|0\n$--radio-checked-font-color: $--color-primary;\n/// color||Color|0\n$--radio-checked-input-border-color: $--color-primary;\n/// color||Color|0\n$--radio-checked-input-background-color: $--color-white;\n/// color||Color|0\n$--radio-checked-icon-color: $--color-primary;\n\n$--radio-input-border-color-hover: $--color-primary;\n\n$--radio-bordered-height: 40px;\n$--radio-bordered-padding: 12px 20px 0 10px;\n$--radio-bordered-medium-padding: 10px 20px 0 10px;\n$--radio-bordered-small-padding: 8px 15px 0 10px;\n$--radio-bordered-mini-padding: 6px 15px 0 10px;\n$--radio-bordered-medium-input-height: 14px;\n$--radio-bordered-medium-input-width: 14px;\n$--radio-bordered-medium-height: 36px;\n$--radio-bordered-small-input-height: 12px;\n$--radio-bordered-small-input-width: 12px;\n$--radio-bordered-small-height: 32px;\n$--radio-bordered-mini-input-height: 12px;\n$--radio-bordered-mini-input-width: 12px;\n$--radio-bordered-mini-height: 28px;\n\n/// fontSize||Font|1\n$--radio-button-font-size: $--font-size-base;\n/// color||Color|0\n$--radio-button-checked-background-color: $--color-primary;\n/// color||Color|0\n$--radio-button-checked-font-color: $--color-white;\n/// color||Color|0\n$--radio-button-checked-border-color: $--color-primary;\n$--radio-button-disabled-checked-fill: $--border-color-extra-light;\n\n/* Select\n-------------------------- */\n$--select-border-color-hover: $--border-color-hover;\n$--select-disabled-border: $--disabled-border-base;\n/// fontSize||Font|1\n$--select-font-size: $--font-size-base;\n$--select-close-hover-color: $--color-text-secondary;\n\n$--select-input-color: $--color-text-placeholder;\n$--select-multiple-input-color: #666;\n/// color||Color|0\n$--select-input-focus-border-color: $--color-primary;\n/// fontSize||Font|1\n$--select-input-font-size: 14px;\n\n$--select-option-color: $--color-text-regular;\n$--select-option-disabled-color: $--color-text-placeholder;\n$--select-option-disabled-background: $--color-white;\n/// height||Other|4\n$--select-option-height: 34px;\n$--select-option-hover-background: $--background-color-base;\n/// color||Color|0\n$--select-option-selected-font-color: $--color-primary;\n$--select-option-selected-hover: $--background-color-base;\n\n$--select-group-color: $--color-info;\n$--select-group-height: 30px;\n$--select-group-font-size: 12px;\n\n$--select-dropdown-background: $--color-white;\n$--select-dropdown-shadow: $--box-shadow-light;\n$--select-dropdown-empty-color: #999;\n/// height||Other|4\n$--select-dropdown-max-height: 274px;\n$--select-dropdown-padding: 6px 0;\n$--select-dropdown-empty-padding: 10px 0;\n$--select-dropdown-border: solid 1px $--border-color-light;\n\n/* Alert\n-------------------------- */\n$--alert-padding: 8px 16px;\n/// borderRadius||Border|2\n$--alert-border-radius: $--border-radius-base;\n/// fontSize||Font|1\n$--alert-title-font-size: 13px;\n/// fontSize||Font|1\n$--alert-description-font-size: 12px;\n/// fontSize||Font|1\n$--alert-close-font-size: 12px;\n/// fontSize||Font|1\n$--alert-close-customed-font-size: 13px;\n\n$--alert-success-color: $--color-success-lighter;\n$--alert-info-color: $--color-info-lighter;\n$--alert-warning-color: $--color-warning-lighter;\n$--alert-danger-color: $--color-danger-lighter;\n\n/// height||Other|4\n$--alert-icon-size: 16px;\n/// height||Other|4\n$--alert-icon-large-size: 28px;\n\n/* MessageBox\n-------------------------- */\n/// color||Color|0\n$--messagebox-title-color: $--color-text-primary;\n$--msgbox-width: 420px;\n$--msgbox-border-radius: $--border-radius-base;\n/// fontSize||Font|1\n$--messagebox-font-size: $--font-size-large;\n/// fontSize||Font|1\n$--messagebox-content-font-size: $--font-size-base;\n/// color||Color|0\n$--messagebox-content-color: $--color-text-regular;\n/// fontSize||Font|1\n$--messagebox-error-font-size: 12px;\n$--msgbox-padding-primary: 15px;\n/// color||Color|0\n$--messagebox-success-color: $--color-success;\n/// color||Color|0\n$--messagebox-info-color: $--color-info;\n/// color||Color|0\n$--messagebox-warning-color: $--color-warning;\n/// color||Color|0\n$--messagebox-danger-color: $--color-danger;\n\n/* Message\n-------------------------- */\n$--message-shadow: $--box-shadow-base;\n$--message-min-width: 380px;\n$--message-background-color: #edf2fc;\n$--message-padding: 15px 15px 15px 20px;\n/// color||Color|0\n$--message-close-icon-color: $--color-text-placeholder;\n/// height||Other|4\n$--message-close-size: 16px;\n/// color||Color|0\n$--message-close-hover-color: $--color-text-secondary;\n\n/// color||Color|0\n$--message-success-font-color: $--color-success;\n/// color||Color|0\n$--message-info-font-color: $--color-info;\n/// color||Color|0\n$--message-warning-font-color: $--color-warning;\n/// color||Color|0\n$--message-danger-font-color: $--color-danger;\n\n/* Notification\n-------------------------- */\n$--notification-width: 330px;\n/// padding||Spacing|3\n$--notification-padding: 14px 26px 14px 13px;\n$--notification-radius: 8px;\n$--notification-shadow: $--box-shadow-light;\n/// color||Color|0\n$--notification-border-color: $--border-color-lighter;\n$--notification-icon-size: 24px;\n$--notification-close-font-size: $--message-close-size;\n$--notification-group-margin-left: 13px;\n$--notification-group-margin-right: 8px;\n/// fontSize||Font|1\n$--notification-content-font-size: $--font-size-base;\n/// color||Color|0\n$--notification-content-color: $--color-text-regular;\n/// fontSize||Font|1\n$--notification-title-font-size: 16px;\n/// color||Color|0\n$--notification-title-color: $--color-text-primary;\n\n/// color||Color|0\n$--notification-close-color: $--color-text-secondary;\n/// color||Color|0\n$--notification-close-hover-color: $--color-text-regular;\n\n/// color||Color|0\n$--notification-success-icon-color: $--color-success;\n/// color||Color|0\n$--notification-info-icon-color: $--color-info;\n/// color||Color|0\n$--notification-warning-icon-color: $--color-warning;\n/// color||Color|0\n$--notification-danger-icon-color: $--color-danger;\n\n/* Input\n-------------------------- */\n$--input-font-size: $--font-size-base;\n/// color||Color|0\n$--input-font-color: $--color-text-regular;\n/// height||Other|4\n$--input-width: 140px;\n/// height||Other|4\n$--input-height: 40px;\n$--input-border: $--border-base;\n$--input-border-color: $--border-color-base;\n/// borderRadius||Border|2\n$--input-border-radius: $--border-radius-base;\n$--input-border-color-hover: $--border-color-hover;\n/// color||Color|0\n$--input-background-color: $--color-white;\n$--input-fill-disabled: $--disabled-fill-base;\n$--input-color-disabled: $--font-color-disabled-base;\n/// color||Color|0\n$--input-icon-color: $--color-text-placeholder;\n/// color||Color|0\n$--input-placeholder-color: $--color-text-placeholder;\n$--input-max-width: 314px;\n\n$--input-hover-border: $--border-color-hover;\n$--input-clear-hover-color: $--color-text-secondary;\n\n$--input-focus-border: $--color-primary;\n$--input-focus-fill: $--color-white;\n\n$--input-disabled-fill: $--disabled-fill-base;\n$--input-disabled-border: $--disabled-border-base;\n$--input-disabled-color: $--disabled-color-base;\n$--input-disabled-placeholder-color: $--color-text-placeholder;\n\n/// fontSize||Font|1\n$--input-medium-font-size: 14px;\n/// height||Other|4\n$--input-medium-height: 36px;\n/// fontSize||Font|1\n$--input-small-font-size: 13px;\n/// height||Other|4\n$--input-small-height: 32px;\n/// fontSize||Font|1\n$--input-mini-font-size: 12px;\n/// height||Other|4\n$--input-mini-height: 28px;\n\n/* Cascader\n-------------------------- */\n/// color||Color|0\n$--cascader-menu-font-color: $--color-text-regular;\n/// color||Color|0\n$--cascader-menu-selected-font-color: $--color-primary;\n$--cascader-menu-fill: $--fill-base;\n$--cascader-menu-font-size: $--font-size-base;\n$--cascader-menu-radius: $--border-radius-base;\n$--cascader-menu-border: solid 1px $--border-color-light;\n$--cascader-menu-shadow: $--box-shadow-light;\n$--cascader-node-background-hover: $--background-color-base;\n$--cascader-node-color-disabled: $--color-text-placeholder;\n$--cascader-color-empty: $--color-text-placeholder;\n$--cascader-tag-background: #f0f2f5;\n\n/* Group\n-------------------------- */\n$--group-option-flex: 0 0 (1/5) * 100%;\n$--group-option-offset-bottom: 12px;\n$--group-option-fill-hover: rgba($--color-black, 0.06);\n$--group-title-color: $--color-black;\n$--group-title-font-size: $--font-size-base;\n$--group-title-width: 66px;\n\n/* Tab\n-------------------------- */\n$--tab-font-size: $--font-size-base;\n$--tab-border-line: 1px solid #e4e4e4;\n$--tab-header-color-active: $--color-text-secondary;\n$--tab-header-color-hover: $--color-text-regular;\n$--tab-header-color: $--color-text-regular;\n$--tab-header-fill-active: rgba($--color-black, 0.06);\n$--tab-header-fill-hover: rgba($--color-black, 0.06);\n$--tab-vertical-header-width: 90px;\n$--tab-vertical-header-count-color: $--color-white;\n$--tab-vertical-header-count-fill: $--color-text-secondary;\n\n/* Button\n-------------------------- */\n/// fontSize||Font|1\n$--button-font-size: $--font-size-base;\n/// fontWeight||Font|1\n$--button-font-weight: $--font-weight-primary;\n/// borderRadius||Border|2\n$--button-border-radius: $--border-radius-base;\n/// padding||Spacing|3\n$--button-padding-vertical: 12px;\n/// padding||Spacing|3\n$--button-padding-horizontal: 20px;\n\n/// fontSize||Font|1\n$--button-medium-font-size: $--font-size-base;\n/// borderRadius||Border|2\n$--button-medium-border-radius: $--border-radius-base;\n/// padding||Spacing|3\n$--button-medium-padding-vertical: 10px;\n/// padding||Spacing|3\n$--button-medium-padding-horizontal: 20px;\n\n/// fontSize||Font|1\n$--button-small-font-size: 12px;\n$--button-small-border-radius: $--border-radius-base;\n/// padding||Spacing|3\n$--button-small-padding-vertical: 9px;\n/// padding||Spacing|3\n$--button-small-padding-horizontal: 15px;\n/// fontSize||Font|1\n$--button-mini-font-size: 12px;\n$--button-mini-border-radius: $--border-radius-base;\n/// padding||Spacing|3\n$--button-mini-padding-vertical: 7px;\n/// padding||Spacing|3\n$--button-mini-padding-horizontal: 15px;\n\n/// color||Color|0\n$--button-default-font-color: $--color-text-regular;\n/// color||Color|0\n$--button-default-background-color: $--color-white;\n/// color||Color|0\n$--button-default-border-color: $--border-color-base;\n\n/// color||Color|0\n$--button-disabled-font-color: $--color-text-placeholder;\n/// color||Color|0\n$--button-disabled-background-color: $--color-white;\n/// color||Color|0\n$--button-disabled-border-color: $--border-color-lighter;\n\n/// color||Color|0\n$--button-primary-border-color: $--color-primary;\n/// color||Color|0\n$--button-primary-font-color: $--color-white;\n/// color||Color|0\n$--button-primary-background-color: $--color-primary;\n/// color||Color|0\n$--button-success-border-color: $--color-success;\n/// color||Color|0\n$--button-success-font-color: $--color-white;\n/// color||Color|0\n$--button-success-background-color: $--color-success;\n/// color||Color|0\n$--button-warning-border-color: $--color-warning;\n/// color||Color|0\n$--button-warning-font-color: $--color-white;\n/// color||Color|0\n$--button-warning-background-color: $--color-warning;\n/// color||Color|0\n$--button-danger-border-color: $--color-danger;\n/// color||Color|0\n$--button-danger-font-color: $--color-white;\n/// color||Color|0\n$--button-danger-background-color: $--color-danger;\n/// color||Color|0\n$--button-info-border-color: $--color-info;\n/// color||Color|0\n$--button-info-font-color: $--color-white;\n/// color||Color|0\n$--button-info-background-color: $--color-info;\n\n$--button-hover-tint-percent: 20%;\n$--button-active-shade-percent: 10%;\n\n/* cascader\n-------------------------- */\n$--cascader-height: 200px;\n\n/* Switch\n-------------------------- */\n/// color||Color|0\n$--switch-on-color: $--color-primary;\n/// color||Color|0\n$--switch-off-color: $--border-color-base;\n/// fontSize||Font|1\n$--switch-font-size: $--font-size-base;\n$--switch-core-border-radius: 10px;\n// height||Other|4 TODO: width 代码写死的40px 所以下面这三个属性都没意义\n$--switch-width: 40px;\n// height||Other|4\n$--switch-height: 20px;\n// height||Other|4\n$--switch-button-size: 16px;\n\n/* Dialog\n-------------------------- */\n$--dialog-background-color: $--color-white;\n$--dialog-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n/// fontSize||Font|1\n$--dialog-title-font-size: $--font-size-large;\n/// fontSize||Font|1\n$--dialog-content-font-size: 14px;\n/// fontLineHeight||LineHeight|2\n$--dialog-font-line-height: $--font-line-height-primary;\n/// padding||Spacing|3\n$--dialog-padding-primary: 20px;\n\n/* Table\n-------------------------- */\n/// color||Color|0\n$--table-border-color: $--border-color-lighter;\n$--table-border: 1px solid $--table-border-color;\n/// color||Color|0\n$--table-font-color: $--color-text-regular;\n/// color||Color|0\n$--table-header-font-color: $--color-text-secondary;\n/// color||Color|0\n$--table-row-hover-background-color: $--background-color-base;\n$--table-current-row-background-color: $--color-primary-light-9;\n/// color||Color|0\n$--table-header-background-color: $--color-white;\n$--table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, 0.12);\n\n/* Pagination\n-------------------------- */\n/// fontSize||Font|1\n$--pagination-font-size: 13px;\n/// color||Color|0\n$--pagination-background-color: $--color-white;\n/// color||Color|0\n$--pagination-font-color: $--color-text-primary;\n$--pagination-border-radius: $--border-radius-base;\n/// color||Color|0\n$--pagination-button-color: $--color-text-primary;\n/// height||Other|4\n$--pagination-button-width: 35.5px;\n/// height||Other|4\n$--pagination-button-height: 28px;\n/// color||Color|0\n$--pagination-button-disabled-color: $--color-text-placeholder;\n/// color||Color|0\n$--pagination-button-disabled-background-color: $--color-white;\n/// color||Color|0\n$--pagination-hover-color: $--color-primary;\n\n/* Popup\n-------------------------- */\n/// color||Color|0\n$--popup-modal-background-color: $--color-black;\n/// opacity||Other|1\n$--popup-modal-opacity: 0.5;\n\n/* Popover\n-------------------------- */\n/// color||Color|0\n$--popover-background-color: $--color-white;\n/// fontSize||Font|1\n$--popover-font-size: $--font-size-base;\n/// color||Color|0\n$--popover-border-color: $--border-color-lighter;\n$--popover-arrow-size: 6px;\n/// padding||Spacing|3\n$--popover-padding: 12px;\n$--popover-padding-large: 18px 20px;\n/// fontSize||Font|1\n$--popover-title-font-size: 16px;\n/// color||Color|0\n$--popover-title-font-color: $--color-text-primary;\n\n/* Tooltip\n-------------------------- */\n/// color|1|Color|0\n$--tooltip-fill: $--color-text-primary;\n/// color|1|Color|0\n$--tooltip-color: $--color-white;\n/// fontSize||Font|1\n$--tooltip-font-size: 12px;\n/// color||Color|0\n$--tooltip-border-color: $--color-text-primary;\n$--tooltip-arrow-size: 6px;\n/// padding||Spacing|3\n$--tooltip-padding: 10px;\n\n/* Tag\n-------------------------- */\n/// color||Color|0\n$--tag-info-color: $--color-info;\n/// color||Color|0\n$--tag-primary-color: $--color-primary;\n/// color||Color|0\n$--tag-success-color: $--color-success;\n/// color||Color|0\n$--tag-warning-color: $--color-warning;\n/// color||Color|0\n$--tag-danger-color: $--color-danger;\n/// fontSize||Font|1\n$--tag-font-size: 12px;\n$--tag-border-radius: $--border-radius-base;\n$--tag-padding: 0 10px;\n\n/* Tree\n-------------------------- */\n/// color||Color|0\n$--tree-node-hover-background-color: $--background-color-base;\n/// color||Color|0\n$--tree-font-color: $--color-text-regular;\n/// color||Color|0\n$--tree-expand-icon-color: $--color-text-placeholder;\n\n/* Dropdown\n-------------------------- */\n$--dropdown-menu-box-shadow: $--box-shadow-light;\n$--dropdown-menuItem-hover-fill: $--color-primary-light-9;\n$--dropdown-menuItem-hover-color: $--link-color;\n\n/* Badge\n-------------------------- */\n/// color||Color|0\n$--badge-background-color: $--color-danger;\n$--badge-radius: 10px;\n/// fontSize||Font|1\n$--badge-font-size: 12px;\n/// padding||Spacing|3\n$--badge-padding: 6px;\n/// height||Other|4\n$--badge-size: 18px;\n\n/* Card\n-------------------------- */\n/// color||Color|0\n$--card-border-color: $--border-color-lighter;\n$--card-border-radius: $--border-radius-base;\n/// padding||Spacing|3\n$--card-padding: 20px;\n\n/* Slider\n-------------------------- */\n/// color||Color|0\n$--slider-main-background-color: $--color-primary;\n/// color||Color|0\n$--slider-runway-background-color: $--border-color-light;\n$--slider-button-hover-color: mix($--color-primary, black, 97%);\n$--slider-stop-background-color: $--color-white;\n$--slider-disable-color: $--color-text-placeholder;\n$--slider-margin: 16px 0;\n$--slider-border-radius: $--border-radius-base;\n/// height|1|Other|4\n$--slider-height: 6px;\n/// height||Other|4\n$--slider-button-size: 16px;\n$--slider-button-wrapper-size: 36px;\n$--slider-button-wrapper-offset: -15px;\n\n/* Steps\n-------------------------- */\n$--steps-border-color: $--disabled-border-base;\n$--steps-border-radius: $--border-radius-base;\n$--steps-padding: 20px;\n\n/* Menu\n-------------------------- */\n/// fontSize||Font|1\n$--menu-item-font-size: $--font-size-base;\n/// color||Color|0\n$--menu-item-font-color: $--color-text-primary;\n/// color||Color|0\n$--menu-background-color: $--color-white;\n$--menu-item-hover-fill: $--color-primary-light-9;\n\n/* Rate\n-------------------------- */\n$--rate-height: 20px;\n/// fontSize||Font|1\n$--rate-font-size: $--font-size-base;\n/// height||Other|3\n$--rate-icon-size: 18px;\n/// margin||Spacing|2\n$--rate-icon-margin: 6px;\n$--rate-icon-color: $--color-text-placeholder;\n\n/* DatePicker\n-------------------------- */\n$--datepicker-font-color: $--color-text-regular;\n/// color|1|Color|0\n$--datepicker-off-font-color: $--color-text-placeholder;\n/// color||Color|0\n$--datepicker-header-font-color: $--color-text-regular;\n$--datepicker-icon-color: $--color-text-primary;\n$--datepicker-border-color: $--disabled-border-base;\n$--datepicker-inner-border-color: #e4e4e4;\n/// color||Color|0\n$--datepicker-inrange-background-color: $--border-color-extra-light;\n/// color||Color|0\n$--datepicker-inrange-hover-background-color: $--border-color-extra-light;\n/// color||Color|0\n$--datepicker-active-color: $--color-primary;\n/// color||Color|0\n$--datepicker-hover-font-color: $--color-primary;\n$--datepicker-cell-hover-color: #fff;\n\n/* Loading\n-------------------------- */\n/// height||Other|4\n$--loading-spinner-size: 42px;\n/// height||Other|4\n$--loading-fullscreen-spinner-size: 50px;\n\n/* Scrollbar\n-------------------------- */\n$--scrollbar-background-color: rgba($--color-text-secondary, 0.3);\n$--scrollbar-hover-background-color: rgba($--color-text-secondary, 0.5);\n\n/* Carousel\n-------------------------- */\n/// fontSize||Font|1\n$--carousel-arrow-font-size: 12px;\n$--carousel-arrow-size: 36px;\n$--carousel-arrow-background: rgba(31, 45, 61, 0.11);\n$--carousel-arrow-hover-background: rgba(31, 45, 61, 0.23);\n/// width||Other|4\n$--carousel-indicator-width: 30px;\n/// height||Other|4\n$--carousel-indicator-height: 2px;\n$--carousel-indicator-padding-horizontal: 4px;\n$--carousel-indicator-padding-vertical: 12px;\n$--carousel-indicator-out-color: $--border-color-hover;\n\n/* Collapse\n-------------------------- */\n/// color||Color|0\n$--collapse-border-color: $--border-color-lighter;\n/// height||Other|4\n$--collapse-header-height: 48px;\n/// color||Color|0\n$--collapse-header-background-color: $--color-white;\n/// color||Color|0\n$--collapse-header-font-color: $--color-text-primary;\n/// fontSize||Font|1\n$--collapse-header-font-size: 13px;\n/// color||Color|0\n$--collapse-content-background-color: $--color-white;\n/// fontSize||Font|1\n$--collapse-content-font-size: 13px;\n/// color||Color|0\n$--collapse-content-font-color: $--color-text-primary;\n\n/* Transfer\n-------------------------- */\n$--transfer-border-color: $--border-color-lighter;\n$--transfer-border-radius: $--border-radius-base;\n/// height||Other|4\n$--transfer-panel-width: 200px;\n/// height||Other|4\n$--transfer-panel-header-height: 40px;\n/// color||Color|0\n$--transfer-panel-header-background-color: $--background-color-base;\n/// height||Other|4\n$--transfer-panel-footer-height: 40px;\n/// height||Other|4\n$--transfer-panel-body-height: 246px;\n/// height||Other|4\n$--transfer-item-height: 30px;\n/// height||Other|4\n$--transfer-filter-height: 32px;\n\n/* Header\n  -------------------------- */\n$--header-padding: 0 20px;\n\n/* Footer\n-------------------------- */\n$--footer-padding: 0 20px;\n\n/* Main\n-------------------------- */\n$--main-padding: 20px;\n\n/* Timeline\n-------------------------- */\n$--timeline-node-size-normal: 12px;\n$--timeline-node-size-large: 14px;\n$--timeline-node-color: $--border-color-light;\n\n/* Backtop\n-------------------------- */\n/// color||Color|0\n$--backtop-background-color: $--color-white;\n/// color||Color|0\n$--backtop-font-color: $--color-primary;\n/// color||Color|0\n$--backtop-hover-background-color: $--border-color-extra-light;\n\n/* Link\n-------------------------- */\n/// fontSize||Font|1\n$--link-font-size: $--font-size-base;\n/// fontWeight||Font|1\n$--link-font-weight: $--font-weight-primary;\n/// color||Color|0\n$--link-default-font-color: $--color-text-regular;\n/// color||Color|0\n$--link-default-active-color: $--color-primary;\n/// color||Color|0\n$--link-disabled-font-color: $--color-text-placeholder;\n/// color||Color|0\n$--link-primary-font-color: $--color-primary;\n/// color||Color|0\n$--link-success-font-color: $--color-success;\n/// color||Color|0\n$--link-warning-font-color: $--color-warning;\n/// color||Color|0\n$--link-danger-font-color: $--color-danger;\n/// color||Color|0\n$--link-info-font-color: $--color-info;\n\n/* Calendar\n-------------------------- */\n/// border||Other|4\n$--calendar-border: $--table-border;\n/// color||Other|4\n$--calendar-selected-background-color: #f2f8fe;\n$--calendar-cell-width: 85px;\n\n/* Form\n-------------------------- */\n/// fontSize||Font|1\n$--form-label-font-size: $--font-size-base;\n\n/* Avatar\n-------------------------- */\n/// color||Color|0\n$--avatar-font-color: #fff;\n/// color||Color|0\n$--avatar-background-color: #c0c4cc;\n/// fontSize||Font Size|1\n$--avatar-text-font-size: 14px;\n/// fontSize||Font Size|1\n$--avatar-icon-font-size: 18px;\n/// borderRadius||Border|2\n$--avatar-border-radius: $--border-radius-base;\n/// size|1|Avatar Size|3\n$--avatar-large-size: 40px;\n/// size|1|Avatar Size|3\n$--avatar-medium-size: 36px;\n/// size|1|Avatar Size|3\n$--avatar-small-size: 28px;\n\n/* Break-point\n-------------------------- */\n$--sm: 768px;\n$--md: 992px;\n$--lg: 1200px;\n$--xl: 1920px;\n\n$--breakpoints: (\n  \"xs\": (\n    max-width: $--sm - 1,\n  ),\n  \"sm\": (\n    min-width: $--sm,\n  ),\n  \"md\": (\n    min-width: $--md,\n  ),\n  \"lg\": (\n    min-width: $--lg,\n  ),\n  \"xl\": (\n    min-width: $--xl,\n  ),\n);\n\n$--breakpoints-spec: (\n  \"xs-only\": (\n    max-width: $--sm - 1,\n  ),\n  \"sm-and-up\": (\n    min-width: $--sm,\n  ),\n  \"sm-only\": \"(min-width: #{$--sm}) and (max-width: #{$--md - 1})\",\n  \"sm-and-down\": (\n    max-width: $--md - 1,\n  ),\n  \"md-and-up\": (\n    min-width: $--md,\n  ),\n  \"md-only\": \"(min-width: #{$--md}) and (max-width: #{$--lg - 1})\",\n  \"md-and-down\": (\n    max-width: $--lg - 1,\n  ),\n  \"lg-and-up\": (\n    min-width: $--lg,\n  ),\n  \"lg-only\": \"(min-width: #{$--lg}) and (max-width: #{$--xl - 1})\",\n  \"lg-and-down\": (\n    max-width: $--xl - 1,\n  ),\n  \"xl-only\": (\n    min-width: $--xl,\n  ),\n);\n\n@import \"~element-ui/packages/theme-chalk/src/index\";\n"
  },
  {
    "path": "src/styles/loading.scss",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 全局加载动画\n */\n\n@charset \"utf-8\";\n\n@import \"./spinner/dots.css\";\n@import \"./spinner/gauge.css\";\n@import \"./spinner/inner-circles.css\";\n@import \"./spinner/plus.css\";\n\n$base-loading: \".vab-loading-type\";\n\n/* 自定义loading开始 */\n#{$base-loading}1 {\n  display: flex;\n  width: 36px;\n  height: 36px;\n  margin: 0 auto 15px;\n  border: 3px solid transparent;\n  border-top-color: $base-color-blue;\n  border-bottom-color: $base-color-blue;\n  border-radius: 50%;\n  animation: vabLoading1-0 0.8s linear infinite;\n}\n\n#{$base-loading}1::before {\n  display: block;\n  width: 8px;\n  height: 8px;\n  margin: auto;\n  content: \"\";\n  border: 3px solid $base-color-blue;\n  border-radius: 50%;\n  animation: vabLoading1 0.5s alternate ease-in infinite;\n}\n\n@keyframes vabLoading1-0 {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes vabLoading1 {\n  from {\n    transform: scale(0.5);\n  }\n\n  to {\n    transform: scale(1.2);\n  }\n}\n\n#{$base-loading}2 {\n  width: 20px;\n  height: 20px;\n  margin-top: -40px;\n  margin-left: -10px;\n  animation: vabLoading2 1s linear reverse infinite;\n}\n\n#{$base-loading}2::before {\n  display: block;\n  width: 36px;\n  height: 36px;\n  margin-top: -17px;\n  margin-left: -18px;\n  content: \"\";\n  animation: vabLoading2 0.4s linear infinite;\n}\n\n#{$base-loading}2::after {\n  display: block;\n  width: 8px;\n  height: 8px;\n  margin-top: -3px;\n  margin-left: -4px;\n  content: \"\";\n  animation: vabLoading2 0.4s linear infinite;\n}\n\n#{$base-loading}2::before,\n#{$base-loading}2,\n#{$base-loading}2::after {\n  position: absolute;\n  top: 40%;\n  left: 50%;\n  border: 3px solid transparent;\n  border-top-color: $base-color-blue;\n  border-right-color: $base-color-blue;\n  border-radius: 50%;\n}\n\n@keyframes vabLoading2 {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n#{$base-loading}3 {\n  display: inline-block;\n  width: 2.5em;\n  height: 3em;\n  margin-bottom: 15px;\n  border: 3px solid transparent;\n  border-top-color: $base-color-blue;\n  border-bottom-color: $base-color-blue;\n  border-radius: 50%;\n  animation: vabLoading3 2s ease infinite;\n}\n\n@keyframes vabLoading3 {\n  50% {\n    border-width: 8px;\n    transform: rotate(360deg) scale(0.4, 0.33);\n  }\n\n  100% {\n    border-width: 3px;\n    transform: rotate(720deg) scale(1, 1);\n  }\n}\n\n#{$base-loading}4 {\n  display: inline-block;\n  width: 30px;\n  height: 30px;\n  margin: 0 auto 10px;\n  border: 8px solid transparent;\n  border-bottom-color: $base-color-blue;\n  border-left-color: $base-color-blue;\n  border-radius: 50%;\n  animation: vabLoading4 1s linear infinite normal;\n}\n\n#{$base-loading}4::after {\n  display: block;\n  width: 15px;\n  height: 15px;\n  margin: 0;\n  content: \" \";\n  border: 6px solid $base-color-blue;\n  border-bottom-color: transparent;\n  border-left-color: transparent;\n  border-radius: 50%;\n}\n\n@keyframes vabLoading4 {\n  0% {\n    opacity: 0.2;\n    transform: rotate(0deg);\n  }\n\n  50% {\n    opacity: 1;\n    transform: rotate(180deg);\n  }\n\n  100% {\n    opacity: 0.2;\n    transform: rotate(360deg);\n  }\n}\n\n#{$base-loading}5 {\n  display: block;\n  width: 0;\n  height: 0;\n  margin: 0 auto 15px;\n  border: solid 1.5em $base-color-blue;\n  border-right: solid 1.5em transparent;\n  border-left: solid 1.5em transparent;\n  border-radius: 100%;\n  animation: vabLoading5 1s linear infinite;\n}\n\n@keyframes vabLoading5 {\n  0% {\n    transform: rotate(0deg);\n  }\n\n  50% {\n    transform: rotate(60deg);\n  }\n\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n#{$base-loading}6 {\n  display: block;\n  width: 0;\n  height: 0;\n  margin: 0 auto 25px auto;\n  perspective: 200px;\n}\n\n#{$base-loading}6::before,\n#{$base-loading}6::after {\n  position: absolute;\n  width: 20px;\n  height: 20px;\n  content: \"\";\n  background: rgba(0, 0, 0, 0);\n  animation: vabLoading6 0.5s infinite alternate;\n}\n\n#{$base-loading}6::before {\n  left: 0;\n}\n\n#{$base-loading}6::after {\n  right: 0;\n  animation-delay: 0.15s;\n}\n\n@keyframes vabLoading6 {\n  0% {\n    box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n    transform: scale(1) translateY(0) rotateX(0deg);\n  }\n\n  100% {\n    background: $base-color-blue;\n    box-shadow: 0 25px 40px rgba($base-color-blue, 0.5);\n    transform: scale(1.2) translateY(-25px) rotateX(45deg);\n  }\n}\n\n#{$base-loading}7 {\n  display: block;\n  width: 25px;\n  height: 25px;\n  margin: 0 auto 15px auto;\n  border: 2px solid $base-color-blue;\n  border-top-color: rgba($base-color-blue, 0.2);\n  border-right-color: rgba($base-color-blue, 0.2);\n  border-bottom-color: rgba($base-color-blue, 0.2);\n  border-radius: 100%;\n  animation: vabLoading7 infinite 0.75s linear;\n}\n\n@keyframes vabLoading7 {\n  0% {\n    transform: rotate(0);\n  }\n\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n#{$base-loading}8 {\n  position: relative;\n  box-sizing: border-box;\n  display: block;\n  width: 20px;\n  height: 20px;\n  margin: 0 auto 15px auto;\n  background-color: $base-color-blue;\n  border-radius: 50%;\n  box-shadow: 30px 0 0 0 $base-color-blue;\n  transform: translateX(-15px);\n}\n\n#{$base-loading}8::after {\n  position: absolute;\n  top: 8px;\n  left: 9px;\n  width: 10px;\n  height: 10px;\n  content: \"\";\n  background-color: $base-color-white;\n  border-radius: 50%;\n  box-shadow: 30px 0 0 0 $base-color-white;\n  animation: vabLoading8 2s ease-in-out infinite alternate;\n}\n\n@keyframes vabLoading8 {\n  0% {\n    left: 9px;\n  }\n\n  100% {\n    left: 1px;\n  }\n}\n\n#{$base-loading}9 {\n  position: relative;\n  box-sizing: border-box;\n  display: block;\n  width: 20px;\n  height: 20px;\n  margin: 0 auto 15px auto;\n  border: 1px $base-color-blue solid;\n  animation: vabLoading9 5s linear infinite;\n}\n\n#{$base-loading}9::after {\n  position: absolute;\n  top: -8px;\n  left: 0;\n  width: 4px;\n  height: 4px;\n  content: \"\";\n  background-color: $base-color-blue;\n  animation: vabLoading9_check 1s ease-in-out infinite;\n}\n\n@keyframes vabLoading9_check {\n  25% {\n    top: -8px;\n    left: 22px;\n  }\n\n  50% {\n    top: 22px;\n    left: 22px;\n  }\n\n  75% {\n    top: 22px;\n    left: -9px;\n  }\n\n  100% {\n    top: -7px;\n    left: -9px;\n  }\n}\n\n@keyframes vabLoading9 {\n  0% {\n    box-shadow: inset 0 0 0 0 rgba($base-color-blue, 0.5);\n    opacity: 0.5;\n  }\n\n  100% {\n    box-shadow: inset 0 -20px 0 0 $base-color-blue;\n  }\n}\n\n/* 自定义loading结束 */\n"
  },
  {
    "path": "src/styles/normalize.scss",
    "content": "@charset \"utf-8\";\n\n/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n   ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n  line-height: 1.15; /* 1 */\n  -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n   ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n  margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n  display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n  margin: 0.67em 0;\n  font-size: 2em;\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n  box-sizing: content-box; /* 1 */\n  height: 0; /* 1 */\n  overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n  font-family: monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n  background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n  text-decoration: underline; /* 2 */\n  text-decoration: underline dotted; /* 2 */\n  border-bottom: none; /* 1 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n  font-family: monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n  font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n  position: relative;\n  font-size: 75%;\n  line-height: 0;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\n/* Embedded content\n   ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n  border-style: none;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  margin: 0; /* 2 */\n  font-family: inherit; /* 1 */\n  font-size: 100%; /* 1 */\n  line-height: 1.15; /* 1 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput {\n  /* 1 */\n  overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect {\n  /* 1 */\n  text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n  -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n  padding: 0;\n  border-style: none;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n  outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n  padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n *    `fieldset` elements in all browsers.\n */\n\nlegend {\n  box-sizing: border-box; /* 1 */\n  display: table; /* 1 */\n  max-width: 100%; /* 1 */\n  padding: 0; /* 3 */\n  color: inherit; /* 2 */\n  white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n  vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n  overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n  box-sizing: border-box; /* 1 */\n  padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n  -webkit-appearance: textfield; /* 1 */\n  outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n  -webkit-appearance: button; /* 1 */\n  font: inherit; /* 2 */\n}\n\n/* Interactive\n   ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n  display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n  display: list-item;\n}\n\n/* Misc\n   ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n  display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n  display: none;\n}\n"
  },
  {
    "path": "src/styles/spinner/dots.css",
    "content": ".dots-loader:not(:required) {\n  position: relative;\n  display: inline-block;\n  width: 7px;\n  height: 7px;\n  margin-bottom: 30px;\n  overflow: hidden;\n  text-indent: -9999px;\n  background: transparent;\n  border-radius: 100%;\n  box-shadow: #f86 -14px -14px 0 7px,\n    #fc6 14px -14px 0 7px,\n    #6d7 14px 14px 0 7px,\n    #4ae -14px 14px 0 7px;\n  transform-origin: 50% 50%;\n  animation: dots-loader 5s infinite ease-in-out;\n}\n\n@keyframes dots-loader {\n  0% {\n    box-shadow: #f86 -14px -14px 0 7px,\n      #fc6 14px -14px 0 7px,\n      #6d7 14px 14px 0 7px,\n      #4ae -14px 14px 0 7px;\n  }\n\n  8.33% {\n    box-shadow: #f86 14px -14px 0 7px,\n      #fc6 14px -14px 0 7px,\n      #6d7 14px 14px 0 7px,\n      #4ae -14px 14px 0 7px;\n  }\n\n  16.67% {\n    box-shadow: #f86 14px 14px 0 7px,\n      #fc6 14px 14px 0 7px,\n      #6d7 14px 14px 0 7px,\n      #4ae -14px 14px 0 7px;\n  }\n\n  25% {\n    box-shadow: #f86 -14px 14px 0 7px,\n      #fc6 -14px 14px 0 7px,\n      #6d7 -14px 14px 0 7px,\n      #4ae -14px 14px 0 7px;\n  }\n\n  33.33% {\n    box-shadow: #f86 -14px -14px 0 7px,\n      #fc6 -14px 14px 0 7px,\n      #6d7 -14px -14px 0 7px,\n      #4ae -14px -14px 0 7px;\n  }\n\n  41.67% {\n    box-shadow: #f86 14px -14px 0 7px,\n      #fc6 -14px 14px 0 7px,\n      #6d7 -14px -14px 0 7px,\n      #4ae 14px -14px 0 7px;\n  }\n\n  50% {\n    box-shadow: #f86 14px 14px 0 7px,\n      #fc6 -14px 14px 0 7px,\n      #6d7 -14px -14px 0 7px,\n      #4ae 14px -14px 0 7px;\n  }\n\n  58.33% {\n    box-shadow: #f86 -14px 14px 0 7px,\n      #fc6 -14px 14px 0 7px,\n      #6d7 -14px -14px 0 7px,\n      #4ae 14px -14px 0 7px;\n  }\n\n  66.67% {\n    box-shadow: #f86 -14px -14px 0 7px,\n      #fc6 -14px -14px 0 7px,\n      #6d7 -14px -14px 0 7px,\n      #4ae 14px -14px 0 7px;\n  }\n\n  75% {\n    box-shadow: #f86 14px -14px 0 7px,\n      #fc6 14px -14px 0 7px,\n      #6d7 14px -14px 0 7px,\n      #4ae 14px -14px 0 7px;\n  }\n\n  83.33% {\n    box-shadow: #f86 14px 14px 0 7px,\n      #fc6 14px -14px 0 7px,\n      #6d7 14px 14px 0 7px,\n      #4ae 14px 14px 0 7px;\n  }\n\n  91.67% {\n    box-shadow: #f86 -14px 14px 0 7px,\n      #fc6 14px -14px 0 7px,\n      #6d7 14px 14px 0 7px,\n      #4ae -14px 14px 0 7px;\n  }\n\n  100% {\n    box-shadow: #f86 -14px -14px 0 7px,\n      #fc6 14px -14px 0 7px,\n      #6d7 14px 14px 0 7px,\n      #4ae -14px 14px 0 7px;\n  }\n}\n"
  },
  {
    "path": "src/styles/spinner/gauge.css",
    "content": ".gauge-loader:not(:required) {\n  position: relative;\n  display: inline-block;\n  width: 64px;\n  height: 32px;\n  margin-bottom: 10px;\n  overflow: hidden;\n  text-indent: -9999px;\n  background: #6ca;\n  border-top-left-radius: 32px;\n  border-top-right-radius: 32px;\n}\n\n.gauge-loader:not(:required)::before {\n  position: absolute;\n  top: 5px;\n  left: 30px;\n  width: 4px;\n  height: 27px;\n  content: \"\";\n  background: white;\n  border-radius: 2px;\n  transform-origin: 50% 100%;\n  animation: gauge-loader 4000ms infinite ease;\n}\n\n.gauge-loader:not(:required)::after {\n  position: absolute;\n  top: 26px;\n  left: 26px;\n  width: 13px;\n  height: 13px;\n  content: \"\";\n  background: white;\n  -moz-border-radius: 8px;\n  -webkit-border-radius: 8px;\n  border-radius: 8px;\n}\n\n@keyframes gauge-loader {\n  0% {\n    transform: rotate(-50deg);\n  }\n\n  10% {\n    transform: rotate(20deg);\n  }\n\n  20% {\n    transform: rotate(60deg);\n  }\n\n  24% {\n    transform: rotate(60deg);\n  }\n\n  40% {\n    transform: rotate(-20deg);\n  }\n\n  54% {\n    transform: rotate(70deg);\n  }\n\n  56% {\n    transform: rotate(78deg);\n  }\n\n  58% {\n    transform: rotate(73deg);\n  }\n\n  60% {\n    transform: rotate(75deg);\n  }\n\n  62% {\n    transform: rotate(70deg);\n  }\n\n  70% {\n    transform: rotate(-20deg);\n  }\n\n  80% {\n    transform: rotate(20deg);\n  }\n\n  83% {\n    transform: rotate(25deg);\n  }\n\n  86% {\n    transform: rotate(20deg);\n  }\n\n  89% {\n    transform: rotate(25deg);\n  }\n\n  100% {\n    transform: rotate(-50deg);\n  }\n}\n"
  },
  {
    "path": "src/styles/spinner/inner-circles.css",
    "content": ".inner-circles-loader:not(:required) {\n  position: relative;\n  display: inline-block;\n  width: 50px;\n  height: 50px;\n  margin-bottom: 10px;\n  overflow: hidden;\n  text-indent: -9999px;\n  background: rgba(25, 165, 152, 0.5);\n  border-radius: 50%;\n  transform: translate3d(0, 0, 0);\n}\n\n.inner-circles-loader:not(:required)::before,\n.inner-circles-loader:not(:required)::after {\n  position: absolute;\n  top: 0;\n  display: inline-block;\n  width: 50px;\n  height: 50px;\n  content: \"\";\n  border-radius: 50%;\n}\n\n.inner-circles-loader:not(:required)::before {\n  left: 0;\n  background: #c7efcf;\n  transform-origin: 0 50%;\n  animation: inner-circles-loader 3s infinite;\n}\n\n.inner-circles-loader:not(:required)::after {\n  right: 0;\n  background: #eef5db;\n  transform-origin: 100% 50%;\n  animation: inner-circles-loader 3s 0.2s reverse infinite;\n}\n\n@keyframes inner-circles-loader {\n  0% {\n    transform: rotate(0deg);\n  }\n\n  50% {\n    transform: rotate(360deg);\n  }\n\n  100% {\n    transform: rotate(0deg);\n  }\n}\n"
  },
  {
    "path": "src/styles/spinner/plus.css",
    "content": ".plus-loader:not(:required) {\n  position: relative;\n  display: inline-block;\n  width: 48px;\n  height: 48px;\n  margin-bottom: 10px;\n  overflow: hidden;\n  text-indent: -9999px;\n  background: #f86;\n  -moz-border-radius: 24px;\n  -webkit-border-radius: 24px;\n  border-radius: 24px;\n  -moz-transform: rotateZ(90deg);\n  -ms-transform: rotateZ(90deg);\n  -webkit-transform: rotateZ(90deg);\n  transform: rotateZ(90deg);\n  -moz-transform-origin: 50% 50%;\n  -ms-transform-origin: 50% 50%;\n  -webkit-transform-origin: 50% 50%;\n  transform-origin: 50% 50%;\n  -moz-animation: plus-loader-background 3s infinite ease-in-out;\n  -webkit-animation: plus-loader-background 3s infinite ease-in-out;\n  animation: plus-loader-background 3s infinite ease-in-out;\n}\n\n.plus-loader:not(:required)::after {\n  position: absolute;\n  top: 0;\n  right: 50%;\n  width: 50%;\n  height: 100%;\n  content: \"\";\n  background: #f86;\n  -moz-border-radius: 24px 0 0 24px;\n  -webkit-border-radius: 24px;\n  border-radius: 24px 0 0 24px;\n  -moz-transform-origin: 100% 50%;\n  -ms-transform-origin: 100% 50%;\n  -webkit-transform-origin: 100% 50%;\n  transform-origin: 100% 50%;\n  -moz-animation: plus-loader-top 3s infinite linear;\n  -webkit-animation: plus-loader-top 3s infinite linear;\n  animation: plus-loader-top 3s infinite linear;\n}\n\n.plus-loader:not(:required)::before {\n  position: absolute;\n  top: 0;\n  right: 50%;\n  width: 50%;\n  height: 100%;\n  content: \"\";\n  background: #fc6;\n  -moz-border-radius: 24px 0 0 24px;\n  -webkit-border-radius: 24px;\n  border-radius: 24px 0 0 24px;\n  -moz-transform-origin: 100% 50%;\n  -ms-transform-origin: 100% 50%;\n  -webkit-transform-origin: 100% 50%;\n  transform-origin: 100% 50%;\n  -moz-animation: plus-loader-bottom 3s infinite linear;\n  -webkit-animation: plus-loader-bottom 3s infinite linear;\n  animation: plus-loader-bottom 3s infinite linear;\n}\n\n@keyframes plus-loader-top {\n  2.5% {\n    background: #f86;\n    -moz-transform: rotateY(0deg);\n    -ms-transform: rotateY(0deg);\n    -webkit-transform: rotateY(0deg);\n    transform: rotateY(0deg);\n    -moz-animation-timing-function: ease-in;\n    -webkit-animation-timing-function: ease-in;\n    animation-timing-function: ease-in;\n  }\n\n  13.75% {\n    background: #ff430d;\n    -moz-transform: rotateY(90deg);\n    -ms-transform: rotateY(90deg);\n    -webkit-transform: rotateY(90deg);\n    transform: rotateY(90deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  13.76% {\n    background: #ffae0d;\n    -moz-transform: rotateY(90deg);\n    -ms-transform: rotateY(90deg);\n    -webkit-transform: rotateY(90deg);\n    transform: rotateY(90deg);\n    -moz-animation-timing-function: ease-out;\n    -webkit-animation-timing-function: ease-out;\n    animation-timing-function: ease-out;\n  }\n\n  25% {\n    background: #fc6;\n    -moz-transform: rotateY(180deg);\n    -ms-transform: rotateY(180deg);\n    -webkit-transform: rotateY(180deg);\n    transform: rotateY(180deg);\n  }\n\n  27.5% {\n    background: #fc6;\n    -moz-transform: rotateY(180deg);\n    -ms-transform: rotateY(180deg);\n    -webkit-transform: rotateY(180deg);\n    transform: rotateY(180deg);\n    -moz-animation-timing-function: ease-in;\n    -webkit-animation-timing-function: ease-in;\n    animation-timing-function: ease-in;\n  }\n\n  41.25% {\n    background: #ffae0d;\n    -moz-transform: rotateY(90deg);\n    -ms-transform: rotateY(90deg);\n    -webkit-transform: rotateY(90deg);\n    transform: rotateY(90deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  41.26% {\n    background: #2cc642;\n    -moz-transform: rotateY(90deg);\n    -ms-transform: rotateY(90deg);\n    -webkit-transform: rotateY(90deg);\n    transform: rotateY(90deg);\n    -moz-animation-timing-function: ease-out;\n    -webkit-animation-timing-function: ease-out;\n    animation-timing-function: ease-out;\n  }\n\n  50% {\n    background: #6d7;\n    -moz-transform: rotateY(0deg);\n    -ms-transform: rotateY(0deg);\n    -webkit-transform: rotateY(0deg);\n    transform: rotateY(0deg);\n  }\n\n  52.5% {\n    background: #6d7;\n    -moz-transform: rotateY(0deg);\n    -ms-transform: rotateY(0deg);\n    -webkit-transform: rotateY(0deg);\n    transform: rotateY(0deg);\n    -moz-animation-timing-function: ease-in;\n    -webkit-animation-timing-function: ease-in;\n    animation-timing-function: ease-in;\n  }\n\n  63.75% {\n    background: #2cc642;\n    -moz-transform: rotateY(90deg);\n    -ms-transform: rotateY(90deg);\n    -webkit-transform: rotateY(90deg);\n    transform: rotateY(90deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  63.76% {\n    background: #1386d2;\n    -moz-transform: rotateY(90deg);\n    -ms-transform: rotateY(90deg);\n    -webkit-transform: rotateY(90deg);\n    transform: rotateY(90deg);\n    -moz-animation-timing-function: ease-out;\n    -webkit-animation-timing-function: ease-out;\n    animation-timing-function: ease-out;\n  }\n\n  75% {\n    background: #4ae;\n    -moz-transform: rotateY(180deg);\n    -ms-transform: rotateY(180deg);\n    -webkit-transform: rotateY(180deg);\n    transform: rotateY(180deg);\n  }\n\n  77.5% {\n    background: #4ae;\n    -moz-transform: rotateY(180deg);\n    -ms-transform: rotateY(180deg);\n    -webkit-transform: rotateY(180deg);\n    transform: rotateY(180deg);\n    -moz-animation-timing-function: ease-in;\n    -webkit-animation-timing-function: ease-in;\n    animation-timing-function: ease-in;\n  }\n\n  91.25% {\n    background: #1386d2;\n    -moz-transform: rotateY(90deg);\n    -ms-transform: rotateY(90deg);\n    -webkit-transform: rotateY(90deg);\n    transform: rotateY(90deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  91.26% {\n    background: #ff430d;\n    -moz-transform: rotateY(90deg);\n    -ms-transform: rotateY(90deg);\n    -webkit-transform: rotateY(90deg);\n    transform: rotateY(90deg);\n    -moz-animation-timing-function: ease-in;\n    -webkit-animation-timing-function: ease-in;\n    animation-timing-function: ease-in;\n  }\n\n  100% {\n    background: #f86;\n    -moz-transform: rotateY(0deg);\n    -ms-transform: rotateY(0deg);\n    -webkit-transform: rotateY(0deg);\n    transform: rotateY(0deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n}\n\n@keyframes plus-loader-bottom {\n  0% {\n    background: #fc6;\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  50% {\n    background: #fc6;\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  75% {\n    background: #4ae;\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  100% {\n    background: #4ae;\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n}\n\n@keyframes plus-loader-background {\n  0% {\n    background: #f86;\n    -moz-transform: rotateZ(180deg);\n    -ms-transform: rotateZ(180deg);\n    -webkit-transform: rotateZ(180deg);\n    transform: rotateZ(180deg);\n  }\n\n  25% {\n    background: #f86;\n    -moz-transform: rotateZ(180deg);\n    -ms-transform: rotateZ(180deg);\n    -webkit-transform: rotateZ(180deg);\n    transform: rotateZ(180deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  27.5% {\n    background: #6d7;\n    -moz-transform: rotateZ(90deg);\n    -ms-transform: rotateZ(90deg);\n    -webkit-transform: rotateZ(90deg);\n    transform: rotateZ(90deg);\n  }\n\n  50% {\n    background: #6d7;\n    -moz-transform: rotateZ(90deg);\n    -ms-transform: rotateZ(90deg);\n    -webkit-transform: rotateZ(90deg);\n    transform: rotateZ(90deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  52.5% {\n    background: #6d7;\n    -moz-transform: rotateZ(0deg);\n    -ms-transform: rotateZ(0deg);\n    -webkit-transform: rotateZ(0deg);\n    transform: rotateZ(0deg);\n  }\n\n  75% {\n    background: #6d7;\n    -moz-transform: rotateZ(0deg);\n    -ms-transform: rotateZ(0deg);\n    -webkit-transform: rotateZ(0deg);\n    transform: rotateZ(0deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n\n  77.5% {\n    background: #f86;\n    -moz-transform: rotateZ(270deg);\n    -ms-transform: rotateZ(270deg);\n    -webkit-transform: rotateZ(270deg);\n    transform: rotateZ(270deg);\n  }\n\n  100% {\n    background: #f86;\n    -moz-transform: rotateZ(270deg);\n    -ms-transform: rotateZ(270deg);\n    -webkit-transform: rotateZ(270deg);\n    transform: rotateZ(270deg);\n    -moz-animation-timing-function: step-start;\n    -webkit-animation-timing-function: step-start;\n    animation-timing-function: step-start;\n  }\n}\n"
  },
  {
    "path": "src/styles/themes/default.scss",
    "content": "/* 绿荫草场主题、荣耀典藏主题、暗黑之子主题加QQ讨论群972435319、1139183756后私聊群主获取，获取后将主题放到themes文件夹根目录即可 */\n"
  },
  {
    "path": "src/styles/transition.scss",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description vue过渡动画\n */\n\n@charset \"utf-8\";\n\n.fade-transform-leave-active,\n.fade-transform-enter-active {\n  transition: $base-transition;\n}\n\n.fade-transform-enter {\n  opacity: 0;\n}\n\n.fade-transform-leave-to {\n  opacity: 0;\n}\n"
  },
  {
    "path": "src/styles/vab.scss",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 全局样式\n */\n\n@charset \"utf-8\";\n\n@import './normalize.scss';\n@import './transition.scss';\n@import './loading.scss';\n$base: '.vab';\n\n@mixin scrollbar {\n  max-height: 88vh;\n  margin-bottom: 0.5vh;\n  overflow-y: auto;\n\n  &::-webkit-scrollbar {\n    width: 0;\n    height: 0;\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: rgba(144, 147, 153, 0.3);\n    border-radius: 10px;\n  }\n\n  &::-webkit-scrollbar-thumb:hover {\n    background-color: rgba(144, 147, 153, 0.3);\n  }\n}\n\n@mixin base-scrollbar {\n  &::-webkit-scrollbar {\n    width: 13px;\n    height: 13px;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background-color: rgba(0, 0, 0, 0.4);\n    background-clip: padding-box;\n    border: 3px solid transparent;\n    border-radius: 7px;\n  }\n\n  &::-webkit-scrollbar-thumb:hover {\n    background-color: rgba(0, 0, 0, 0.5);\n  }\n\n  &::-webkit-scrollbar-track {\n    background-color: transparent;\n  }\n\n  &::-webkit-scrollbar-track:hover {\n    background-color: #f8fafc;\n  }\n}\n\nimg {\n  object-fit: cover;\n}\n\na {\n  color: $base-color-blue;\n  text-decoration: none;\n  cursor: pointer;\n}\n\n* {\n  transition: $base-transition;\n}\nsvg {\n  transition: none;\n  * {\n    transition: none;\n  }\n}\n\nhtml {\n  body {\n    position: relative;\n    height: 100vh;\n    padding: 0;\n    margin: 0;\n    font-family: Avenir, Helvetica, Arial, sans-serif;\n    font-size: $base-font-size-default;\n    color: #2c3e50;\n    background: #f6f8f9;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n\n    @include base-scrollbar;\n\n    div {\n      @include base-scrollbar;\n    }\n\n    svg,\n    i {\n      &:hover {\n        opacity: 0.8;\n      }\n    }\n\n    .v-modal {\n      backdrop-filter: blur(10px);\n    }\n\n    /* el-tag开始 */\n    .el-tag + .el-tag {\n      margin-left: 10px;\n    }\n\n    /* el-tag结束 */\n\n    /* markdown编辑器开始 */\n    .editor-toolbar {\n      .no-mobile,\n      .fa-question-circle {\n        display: none;\n      }\n    }\n\n    /* markdown编辑器结束 */\n\n    /* 间隔线开始 */\n    .el-divider--horizontal {\n      margin: 10px 0 25px 0;\n\n      .el-divider__text {\n        display: -webkit-box;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        -webkit-line-clamp: 1;\n        -webkit-box-orient: vertical;\n      }\n    }\n\n    /* 间隔线结束 */\n\n    /* 大图展示开始 */\n    .el-image-viewer {\n      &__close {\n        .el-icon-circle-close {\n          color: $base-color-white;\n        }\n      }\n    }\n\n    /* 大图展示结束 */\n\n    .vue-admin-better-wrapper {\n      .app-main-container {\n        @include base-scrollbar;\n\n        > [class*='-container'] {\n          * {\n            transition: none;\n          }\n          padding: $base-padding;\n          background: $base-color-white;\n        }\n      }\n    }\n\n    /* 进度条开始 */\n    #nprogress {\n      position: fixed;\n      z-index: $base-z-index;\n\n      .bar {\n        background: $base-color-blue !important;\n      }\n\n      .peg {\n        box-shadow: 0 0 10px $base-color-blue, 0 0 5px $base-color-blue !important;\n      }\n    }\n\n    /* 进度条结束 */\n\n    /* 表格开始 */\n\n    .el-table {\n      .el-table__body-wrapper {\n        @include base-scrollbar;\n      }\n\n      th {\n        background: #f5f7fa;\n      }\n\n      td,\n      th {\n        position: relative;\n        box-sizing: border-box;\n        padding: 7.5px 0;\n\n        .cell {\n          font-size: $base-font-size-default;\n          font-weight: normal;\n          color: #606266;\n\n          .el-image {\n            width: 50px;\n            height: 50px;\n            border-radius: $base-border-radius;\n          }\n        }\n      }\n    }\n\n    /* 表格结束 */\n\n    /* 分页开始 */\n    .el-pagination {\n      padding: 2px 5px;\n      margin: 15px 0 0 0;\n      font-weight: normal;\n      color: $base-color-black;\n      text-align: center;\n    }\n\n    /* 分页结束 */\n\n    /* 菜单开始 */\n    .el-menu.el-menu--popup.el-menu--popup-right-start {\n      @include scrollbar;\n    }\n\n    .el-menu.el-menu--popup.el-menu--popup-bottom-start {\n      @include scrollbar;\n    }\n\n    .el-submenu__title i {\n      color: $base-color-white;\n    }\n\n    /* 菜单结束 */\n\n    /* 弹窗开始 */\n\n    .el-dialog,\n    .el-message-box {\n      &__body {\n        border-top: 1px solid $base-border-color;\n\n        .el-form {\n          padding-right: 30px;\n        }\n      }\n\n      &__footer {\n        padding: $base-padding;\n        text-align: right;\n        border-top: 1px solid $base-border-color;\n      }\n\n      &__content {\n        padding: 20px 20px 20px 20px;\n      }\n    }\n\n    /* 弹窗结束 */\n\n    /* 卡片开始 */\n    .el-card {\n      margin-bottom: 15px;\n\n      &__body {\n        padding: $base-padding;\n      }\n    }\n\n    /* 卡片结束 */\n\n    /* 下拉树样式-----------开始 */\n    .select-tree-popper {\n      .el-scrollbar {\n        .el-scrollbar__view {\n          .el-select-dropdown__item {\n            height: auto;\n            max-height: 274px;\n            padding: 0;\n            overflow-y: auto;\n            line-height: 26px;\n          }\n        }\n      }\n    }\n\n    /* 下拉树样式-----------结束 */\n  }\n}\n"
  },
  {
    "path": "src/styles/variables.scss",
    "content": "/**\n * @author https://vue-admin-better.com （不想保留author可删除）\n * @description 全局主题变量配置\n */\n/* stylelint-disable */\n@charset \"utf-8\";\n//框架默认主题色\n$base-color-default: #409eff;\n//默认层级\n$base-z-index: 999;\n//横向布局纵向布局时菜单背景色\n$base-menu-background: #21252b;\n//菜单文字颜色\n$base-menu-color: hsla(0, 0%, 100%, 0.95);\n//菜单选中文字颜色\n$base-menu-color-active: hsla(0, 0%, 100%, 0.95);\n//菜单选中背景色\n$base-menu-background-active: $base-color-default;\n//标题颜色\n$base-title-color: #fff;\n//字体大小配置\n$base-font-size-small: 12px;\n$base-font-size-default: 14px;\n$base-font-size-big: 16px;\n$base-font-size-bigger: 18px;\n$base-font-size-max: 22px;\n$base-font-color: #606266;\n$base-color-blue: $base-color-default;\n$base-color-green: #41b882;\n$base-color-white: #fff;\n$base-color-black: #000;\n$base-color-yellow: #ffa91b;\n$base-color-orange: #ff6700;\n$base-color-red: #f34d37;\n$base-color-gray: rgba(0, 0, 0, 0.65);\n$base-main-width: 1279px;\n$base-border-radius: 4px;\n$base-border-color: #dcdfe6;\n//输入框高度\n$base-input-height: 32px;\n//默认paddiing\n$base-padding: 20px;\n//默认阴影\n$base-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);\n//横向布局时top-bar、logo、一级菜单的高度\n$base-top-bar-height: 65px;\n//纵向布局时logo的高度\n$base-logo-height: 75px;\n//顶部nav-bar的高度\n$base-nav-bar-height: 60px;\n//顶部多标签页tabs-bar的高度\n$base-tabs-bar-height: 55px;\n//顶部多标签页tabs-bar中每一个item的高度\n$base-tag-item-height: 34px;\n//菜单li标签的高度\n$base-menu-item-height: 50px;\n//app-main的高度\n$base-app-main-height: calc(\n  100vh - #{$base-nav-bar-height} - #{$base-tabs-bar-height} - #{$base-padding} -\n    #{$base-padding} - 55px - 55px\n);\n//纵向布局时左侧导航未折叠时的宽度\n$base-left-menu-width: 256px;\n//纵向布局时左侧导航未折叠时右侧内容的宽度\n$base-right-content-width: calc(100% - #{$base-left-menu-width});\n//纵向布局时左侧导航已折叠时的宽度\n$base-left-menu-width-min: 65px;\n//纵向布局时左侧导航已折叠时右侧内容的宽度\n$base-right-content-width-min: calc(100% - #{$base-left-menu-width-min});\n//默认动画\n$base-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border 0s,\n  background 0s, color 0s, font-size 0s;\n//默认动画长\n$base-transition-time: 0.3s;\n\n:export {\n  //菜单文字颜色变量导出\n  menu-color: $base-menu-color;\n  //菜单选中文字颜色变量导出\n  menu-color-active: $base-menu-color-active;\n  //菜单背景色变量导出\n  menu-background: $base-menu-background;\n}\n"
  },
  {
    "path": "src/utils/accessToken.js",
    "content": "import { storage, tokenTableName } from '@/config'\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 获取accessToken\n * @returns {string|ActiveX.IXMLDOMNode|Promise<any>|any|IDBRequest<any>|MediaKeyStatus|FormDataEntryValue|Function|Promise<Credential | null>}\n */\nexport function getAccessToken() {\n  if (storage) {\n    if ('localStorage' === storage) {\n      return localStorage.getItem(tokenTableName)\n    } else if ('sessionStorage' === storage) {\n      return sessionStorage.getItem(tokenTableName)\n    } else {\n      return localStorage.getItem(tokenTableName)\n    }\n  } else {\n    return localStorage.getItem(tokenTableName)\n  }\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 存储accessToken\n * @param accessToken\n * @returns {void|*}\n */\nexport function setAccessToken(accessToken) {\n  if (storage) {\n    if ('localStorage' === storage) {\n      return localStorage.setItem(tokenTableName, accessToken)\n    } else if ('sessionStorage' === storage) {\n      return sessionStorage.setItem(tokenTableName, accessToken)\n    } else {\n      return localStorage.setItem(tokenTableName, accessToken)\n    }\n  } else {\n    return localStorage.setItem(tokenTableName, accessToken)\n  }\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 移除accessToken\n * @returns {void|Promise<void>}\n */\nexport function removeAccessToken() {\n  if (storage) {\n    if ('localStorage' === storage) {\n      return localStorage.removeItem(tokenTableName)\n    } else if ('sessionStorage' === storage) {\n      return sessionStorage.clear()\n    } else {\n      return localStorage.removeItem(tokenTableName)\n    }\n  } else {\n    return localStorage.removeItem(tokenTableName)\n  }\n}\n"
  },
  {
    "path": "src/utils/encrypt.js",
    "content": "import { JSEncrypt } from 'jsencrypt'\nimport { getPublicKey } from '@/api/publicKey'\n\nconst privateKey =\n  'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMFPa+v52FkSUXvcUnrGI/XzW3EpZRI0s9BCWJ3oNQmEYA5luWW5p8h0uadTIoTyYweFPdH4hveyxlwmS7oefvbIdiP+o+QIYW/R4Wjsb4Yl8MhR4PJqUE3RCy6IT9fM8ckG4kN9ECs6Ja8fQFc6/mSl5dJczzJO3k1rWMBhKJD/AgMBAAECgYEAucMakH9dWeryhrYoRHcXo4giPVJsH9ypVt4KzmOQY/7jV7KFQK3x//27UoHfUCak51sxFw9ek7UmTPM4HjikA9LkYeE7S381b4QRvFuf3L6IbMP3ywJnJ8pPr2l5SqQ00W+oKv+w/VmEsyUHr+k4Z+4ik+FheTkVWp566WbqFsECQQDjYaMcaKw3j2Zecl8T6eUe7fdaRMIzp/gcpPMfT/9rDzIQk+7ORvm1NI9AUmFv/FAlfpuAMrdL2n7p9uznWb7RAkEA2aP934kbXg5bdV0R313MrL+7WTK/qdcYxATUbMsMuWWQBoS5irrt80WCZbG48hpocJavLNjbtrjmUX3CuJBmzwJAOJg8uP10n/+ZQzjEYXh+BszEHDuw+pp8LuT/fnOy5zrJA0dO0RjpXijO3vuiNPVgHXT9z1LQPJkNrb5ACPVVgQJBALPeb4uV0bNrJDUb5RB4ghZnIxv18CcaqNIft7vuGCcFBAIPIRTBprR+RuVq+xHDt3sNXdsvom4h49+Hky1b0ksCQBBwUtVaqH6ztCtwUF1j2c/Zcrt5P/uN7IHAd44K0gIJc1+Csr3qPG+G2yoqRM8KVqLI8Z2ZYn9c+AvEE+L9OQY='\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description RSA加密\n * @param data\n * @returns {Promise<{param: PromiseLike<ArrayBuffer>}|*>}\n */\nexport async function encryptedData(data) {\n  let publicKey = ''\n  const res = await getPublicKey()\n  publicKey = res.data.publicKey\n  if (res.data.mockServer) {\n    publicKey = ''\n  }\n  if (publicKey == '') {\n    return data\n  }\n  const encrypt = new JSEncrypt()\n  encrypt.setPublicKey(\n    `-----BEGIN PUBLIC KEY-----${publicKey}-----END PUBLIC KEY-----`\n  )\n  data = encrypt.encrypt(JSON.stringify(data))\n  return {\n    param: data,\n  }\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description RSA解密\n * @param data\n * @returns {PromiseLike<ArrayBuffer>}\n */\nexport function decryptedData(data) {\n  const decrypt = new JSEncrypt()\n  decrypt.setPrivateKey(\n    `-----BEGIN RSA PRIVATE KEY-----${privateKey}-----END RSA PRIVATE KEY-----`\n  )\n  data = decrypt.decrypt(JSON.stringify(data))\n  return data\n}\n"
  },
  {
    "path": "src/utils/errorLog.js",
    "content": "import Vue from 'vue'\nimport store from '@/store'\nimport { isArray, isString } from '@/utils/validate'\nimport { errorLog } from '@/config'\n\nconst needErrorLog = errorLog\nconst checkNeed = () => {\n  const env = process.env.NODE_ENV\n  if (isString(needErrorLog)) {\n    return env === needErrorLog\n  }\n  if (isArray(needErrorLog)) {\n    return needErrorLog.includes(env)\n  }\n  return false\n}\nif (checkNeed()) {\n  Vue.config.errorHandler = (err, vm, info) => {\n    console.error('vue-admin-better错误拦截:', err, vm, info)\n    const url = window.location.href\n    Vue.nextTick(() => {\n      store.dispatch('errorLog/addErrorLog', { err, vm, info, url })\n    })\n  }\n}\n"
  },
  {
    "path": "src/utils/handleRoutes.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description all模式渲染后端返回路由\n * @param constantRoutes\n * @returns {*}\n */\nexport function convertRouter(asyncRoutes) {\n  return asyncRoutes.map((route) => {\n    if (route.component) {\n      if (route.component === 'Layout') {\n        route.component = (resolve) => require(['@/layouts'], resolve)\n      } else if (route.component === 'EmptyLayout') {\n        route.component = (resolve) =>\n          require(['@/layouts/EmptyLayout'], resolve)\n      } else {\n        const index = route.component.indexOf('views')\n        const path =\n          index > 0 ? route.component.slice(index) : `views/${route.component}`\n        route.component = (resolve) => require([`@/${path}`], resolve)\n      }\n    }\n    if (route.children && route.children.length)\n      route.children = convertRouter(route.children)\n    if (route.children && route.children.length === 0) delete route.children\n    return route\n  })\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断当前路由是否包含权限\n * @param permissions\n * @param route\n * @returns {boolean|*}\n */\nfunction hasPermission(permissions, route) {\n  if (route.meta && route.meta.permissions) {\n    return permissions.some((role) => route.meta.permissions.includes(role))\n  } else {\n    return true\n  }\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description intelligence模式根据permissions数组拦截路由\n * @param routes\n * @param permissions\n * @returns {[]}\n */\nexport function filterAsyncRoutes(routes, permissions) {\n  const finallyRoutes = []\n  routes.forEach((route) => {\n    const item = { ...route }\n    if (hasPermission(permissions, item)) {\n      if (item.children) {\n        item.children = filterAsyncRoutes(item.children, permissions)\n      }\n      finallyRoutes.push(item)\n    }\n  })\n  return finallyRoutes\n}\n"
  },
  {
    "path": "src/utils/index.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 格式化时间\n * @param time\n * @param cFormat\n * @returns {string|null}\n */\nexport function parseTime(time, cFormat) {\n  if (arguments.length === 0) {\n    return null\n  }\n  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'\n  let date\n  if (typeof time === 'object') {\n    date = time\n  } else {\n    if (typeof time === 'string' && /^[0-9]+$/.test(time)) {\n      time = parseInt(time)\n    }\n    if (typeof time === 'number' && time.toString().length === 10) {\n      time = time * 1000\n    }\n    date = new Date(time)\n  }\n  const formatObj = {\n    y: date.getFullYear(),\n    m: date.getMonth() + 1,\n    d: date.getDate(),\n    h: date.getHours(),\n    i: date.getMinutes(),\n    s: date.getSeconds(),\n    a: date.getDay(),\n  }\n  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {\n    let value = formatObj[key]\n    if (key === 'a') {\n      return ['日', '一', '二', '三', '四', '五', '六'][value]\n    }\n    if (result.length > 0 && value < 10) {\n      value = '0' + value\n    }\n    return value || 0\n  })\n  return time_str\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 格式化时间\n * @param time\n * @param option\n * @returns {string}\n */\nexport function formatTime(time, option) {\n  if (('' + time).length === 10) {\n    time = parseInt(time) * 1000\n  } else {\n    time = +time\n  }\n  const d = new Date(time)\n  const now = Date.now()\n\n  const diff = (now - d) / 1000\n\n  if (diff < 30) {\n    return '刚刚'\n  } else if (diff < 3600) {\n    // less 1 hour\n    return Math.ceil(diff / 60) + '分钟前'\n  } else if (diff < 3600 * 24) {\n    return Math.ceil(diff / 3600) + '小时前'\n  } else if (diff < 3600 * 24 * 2) {\n    return '1天前'\n  }\n  if (option) {\n    return parseTime(time, option)\n  } else {\n    return (\n      d.getMonth() +\n      1 +\n      '月' +\n      d.getDate() +\n      '日' +\n      d.getHours() +\n      '时' +\n      d.getMinutes() +\n      '分'\n    )\n  }\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 将url请求参数转为json格式\n * @param url\n * @returns {{}|any}\n */\nexport function paramObj(url) {\n  const search = url.split('?')[1]\n  if (!search) {\n    return {}\n  }\n  return JSON.parse(\n    '{\"' +\n      decodeURIComponent(search)\n        .replace(/\"/g, '\\\\\"')\n        .replace(/&/g, '\",\"')\n        .replace(/=/g, '\":\"')\n        .replace(/\\+/g, ' ') +\n      '\"}'\n  )\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 父子关系的数组转换成树形结构数据\n * @param data\n * @returns {*}\n */\nexport function translateDataToTree(data) {\n  const parent = data.filter(\n    (value) => value.parentId === 'undefined' || value.parentId == null\n  )\n  const children = data.filter(\n    (value) => value.parentId !== 'undefined' && value.parentId != null\n  )\n  const translator = (parent, children) => {\n    parent.forEach((parent) => {\n      children.forEach((current, index) => {\n        if (current.parentId === parent.id) {\n          const temp = JSON.parse(JSON.stringify(children))\n          temp.splice(index, 1)\n          translator([current], temp)\n          typeof parent.children !== 'undefined'\n            ? parent.children.push(current)\n            : (parent.children = [current])\n        }\n      })\n    })\n  }\n  translator(parent, children)\n  return parent\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 树形结构数据转换成父子关系的数组\n * @param data\n * @returns {[]}\n */\nexport function translateTreeToData(data) {\n  const result = []\n  data.forEach((item) => {\n    const loop = (data) => {\n      result.push({\n        id: data.id,\n        name: data.name,\n        parentId: data.parentId,\n      })\n      const child = data.children\n      if (child) {\n        for (let i = 0; i < child.length; i++) {\n          loop(child[i])\n        }\n      }\n    }\n    loop(item)\n  })\n  return result\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 10位时间戳转换\n * @param time\n * @returns {string}\n */\nexport function tenBitTimestamp(time) {\n  const date = new Date(time * 1000)\n  const y = date.getFullYear()\n  let m = date.getMonth() + 1\n  m = m < 10 ? '' + m : m\n  let d = date.getDate()\n  d = d < 10 ? '' + d : d\n  let h = date.getHours()\n  h = h < 10 ? '0' + h : h\n  let minute = date.getMinutes()\n  let second = date.getSeconds()\n  minute = minute < 10 ? '0' + minute : minute\n  second = second < 10 ? '0' + second : second\n  return y + '年' + m + '月' + d + '日 ' + h + ':' + minute + ':' + second //组合\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 13位时间戳转换\n * @param time\n * @returns {string}\n */\nexport function thirteenBitTimestamp(time) {\n  const date = new Date(time / 1)\n  const y = date.getFullYear()\n  let m = date.getMonth() + 1\n  m = m < 10 ? '' + m : m\n  let d = date.getDate()\n  d = d < 10 ? '' + d : d\n  let h = date.getHours()\n  h = h < 10 ? '0' + h : h\n  let minute = date.getMinutes()\n  let second = date.getSeconds()\n  minute = minute < 10 ? '0' + minute : minute\n  second = second < 10 ? '0' + second : second\n  return y + '年' + m + '月' + d + '日 ' + h + ':' + minute + ':' + second //组合\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 获取随机id\n * @param length\n * @returns {string}\n */\nexport function uuid(length = 32) {\n  const num = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'\n  let str = ''\n  for (let i = 0; i < length; i++) {\n    str += num.charAt(Math.floor(Math.random() * num.length))\n  }\n  return str\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description m到n的随机数\n * @param m\n * @param n\n * @returns {number}\n */\nexport function random(m, n) {\n  return Math.floor(Math.random() * (m - n) + n)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description addEventListener\n * @type {function(...[*]=)}\n */\nexport const on = (function () {\n  return function (element, event, handler, useCapture = false) {\n    if (element && event && handler) {\n      element.addEventListener(event, handler, useCapture)\n    }\n  }\n})()\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description removeEventListener\n * @type {function(...[*]=)}\n */\nexport const off = (function () {\n  return function (element, event, handler, useCapture = false) {\n    if (element && event) {\n      element.removeEventListener(event, handler, useCapture)\n    }\n  }\n})()\n"
  },
  {
    "path": "src/utils/pageTitle.js",
    "content": "import { title } from '@/config'\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 设置标题\n * @param pageTitle\n * @returns {string}\n */\nexport default function getPageTitle(pageTitle) {\n  if (pageTitle) {\n    return `${pageTitle}-${title}`\n  }\n  return `${title}`\n}\n"
  },
  {
    "path": "src/utils/permission.js",
    "content": "import store from '@/store'\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 检查权限\n * @param value\n * @returns {boolean}\n */\nexport default function checkPermission(value) {\n  if (value && value instanceof Array && value.length > 0) {\n    const permissions = store.getters['user/permissions']\n    const permissionPermissions = value\n\n    return permissions.some((role) => {\n      return permissionPermissions.includes(role)\n    })\n  } else {\n    return false\n  }\n}\n"
  },
  {
    "path": "src/utils/request.js",
    "content": "import Vue from 'vue'\nimport axios from 'axios'\nimport {\n  baseURL,\n  contentType,\n  debounce,\n  invalidCode,\n  noPermissionCode,\n  requestTimeout,\n  successCode,\n  tokenName,\n  loginInterception,\n} from '@/config'\nimport store from '@/store'\nimport qs from 'qs'\nimport router from '@/router'\nimport { isArray } from '@/utils/validate'\n\nlet loadingInstance\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 处理code异常\n * @param {*} code\n * @param {*} msg\n */\nconst handleCode = (code, msg) => {\n  switch (code) {\n    case invalidCode:\n      Vue.prototype.$baseMessage(msg || `后端接口${code}异常`, 'error')\n      store.dispatch('user/resetAccessToken').catch(() => {})\n      if (loginInterception) {\n        location.reload()\n      }\n      break\n    case noPermissionCode:\n      router.push({ path: '/401' }).catch(() => {})\n      break\n    default:\n      Vue.prototype.$baseMessage(msg || `后端接口${code}异常`, 'error')\n      break\n  }\n}\n\nconst instance = axios.create({\n  baseURL,\n  timeout: requestTimeout,\n  headers: {\n    'Content-Type': contentType,\n  },\n})\n\ninstance.interceptors.request.use(\n  (config) => {\n    if (store.getters['user/accessToken']) {\n      config.headers[tokenName] = store.getters['user/accessToken']\n    }\n    //这里会过滤所有为空、0、false的key，如果不需要请自行注释\n    if (config.data)\n      config.data = Vue.prototype.$baseLodash.pickBy(\n        config.data,\n        Vue.prototype.$baseLodash.identity\n      )\n    if (\n      config.data &&\n      config.headers['Content-Type'] ===\n        'application/x-www-form-urlencoded;charset=UTF-8'\n    )\n      config.data = qs.stringify(config.data)\n    if (debounce.some((item) => config.url.includes(item)))\n      loadingInstance = Vue.prototype.$baseLoading()\n    return config\n  },\n  (error) => {\n    return Promise.reject(error)\n  }\n)\n\ninstance.interceptors.response.use(\n  (response) => {\n    if (loadingInstance) loadingInstance.close()\n\n    const { data, config } = response\n    const { code, msg } = data\n    // 操作正常Code数组\n    const codeVerificationArray = isArray(successCode)\n      ? [...successCode]\n      : [...[successCode]]\n    // 是否操作正常\n    if (codeVerificationArray.includes(code)) {\n      return data\n    } else {\n      handleCode(code, msg)\n      return Promise.reject(\n        'vue-admin-better请求异常拦截:' +\n          JSON.stringify({ url: config.url, code, msg }) || 'Error'\n      )\n    }\n  },\n  (error) => {\n    if (loadingInstance) loadingInstance.close()\n    const { response, message } = error\n    if (error.response && error.response.data) {\n      const { status, data } = response\n      handleCode(status, data.msg || message)\n      return Promise.reject(error)\n    } else {\n      let { message } = error\n      if (message === 'Network Error') {\n        message = '后端接口连接异常'\n      }\n      if (message.includes('timeout')) {\n        message = '后端接口请求超时'\n      }\n      if (message.includes('Request failed with status code')) {\n        const code = message.substr(message.length - 3)\n        message = '后端接口' + code + '异常'\n      }\n      Vue.prototype.$baseMessage(message || `后端接口未知异常`, 'error')\n      return Promise.reject(error)\n    }\n  }\n)\n\nexport default instance\n"
  },
  {
    "path": "src/utils/static.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com\n * @description 导入所有 controller 模块，浏览器环境中自动输出controller文件夹下Mock接口，请勿修改。\n */\nimport Mock from 'mockjs'\nimport { paramObj } from '@/utils/index'\n\nconst mocks = []\nconst files = require.context('../../mock/controller', false, /\\.js$/)\n\nfiles.keys().forEach((key) => {\n  mocks.push(...files(key))\n})\n\nexport function mockXHR() {\n  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send\n  Mock.XHR.prototype.send = function () {\n    if (this.custom.xhr) {\n      this.custom.xhr.withCredentials = this.withCredentials || false\n\n      if (this.responseType) {\n        this.custom.xhr.responseType = this.responseType\n      }\n    }\n    this.proxy_send(...arguments)\n  }\n\n  function XHRHttpRequst(respond) {\n    return function (options) {\n      let result\n      if (respond instanceof Function) {\n        const { body, type, url } = options\n        result = respond({\n          method: type,\n          body: JSON.parse(body),\n          query: paramObj(url),\n        })\n      } else {\n        result = respond\n      }\n      return Mock.mock(result)\n    }\n  }\n\n  mocks.forEach((item) => {\n    Mock.mock(\n      new RegExp(item.url),\n      item.type || 'get',\n      XHRHttpRequst(item.response)\n    )\n  })\n}\n"
  },
  {
    "path": "src/utils/vab.js",
    "content": "import { loadingText, messageDuration, title } from '@/config'\nimport * as lodash from 'lodash'\nimport { Loading, Message, MessageBox, Notification } from 'element-ui'\nimport store from '@/store'\nimport { getAccessToken } from '@/utils/accessToken'\n\nconst accessToken = store.getters['user/accessToken']\nconst layout = store.getters['settings/layout']\n\nconst install = (Vue, opts = {}) => {\n  /* 全局accessToken */\n  Vue.prototype.$baseAccessToken = () => {\n    return accessToken || getAccessToken()\n  }\n  /* 全局标题 */\n  Vue.prototype.$baseTitle = (() => {\n    return title\n  })()\n  /* 全局加载层 */\n  Vue.prototype.$baseLoading = (index, text) => {\n    let loading\n    if (!index) {\n      loading = Loading.service({\n        lock: true,\n        text: text || loadingText,\n        background: 'hsla(0,0%,100%,.8)',\n      })\n    } else {\n      loading = Loading.service({\n        lock: true,\n        text: text || loadingText,\n        spinner: 'vab-loading-type' + index,\n        background: 'hsla(0,0%,100%,.8)',\n      })\n    }\n    return loading\n  }\n  /* 全局多彩加载层 */\n  Vue.prototype.$baseColorfullLoading = (index, text) => {\n    let loading\n    if (!index) {\n      loading = Loading.service({\n        lock: true,\n        text: text || loadingText,\n        spinner: 'dots-loader',\n        background: 'hsla(0,0%,100%,.8)',\n      })\n    } else {\n      switch (index) {\n        case 1:\n          index = 'dots'\n          break\n        case 2:\n          index = 'gauge'\n          break\n        case 3:\n          index = 'inner-circles'\n          break\n        case 4:\n          index = 'plus'\n          break\n      }\n      loading = Loading.service({\n        lock: true,\n        text: text || loadingText,\n        spinner: index + '-loader',\n        background: 'hsla(0,0%,100%,.8)',\n      })\n    }\n    return loading\n  }\n  /* 全局Message */\n  Vue.prototype.$baseMessage = (message, type) => {\n    Message({\n      offset: 60,\n      showClose: true,\n      message: message,\n      type: type,\n      dangerouslyUseHTMLString: true,\n      duration: messageDuration,\n    })\n  }\n\n  /* 全局Alert */\n  Vue.prototype.$baseAlert = (content, title, callback) => {\n    MessageBox.alert(content, title || '温馨提示', {\n      confirmButtonText: '确定',\n      dangerouslyUseHTMLString: true,\n      callback: (action) => {\n        if (callback) {\n          callback()\n        }\n      },\n    })\n  }\n\n  /* 全局Confirm */\n  Vue.prototype.$baseConfirm = (content, title, callback1, callback2) => {\n    MessageBox.confirm(content, title || '温馨提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      closeOnClickModal: false,\n      type: 'warning',\n    })\n      .then(() => {\n        if (callback1) {\n          callback1()\n        }\n      })\n      .catch(() => {\n        if (callback2) {\n          callback2()\n        }\n      })\n  }\n\n  /* 全局Notification */\n  Vue.prototype.$baseNotify = (message, title, type, position) => {\n    Notification({\n      title: title,\n      message: message,\n      position: position || 'top-right',\n      type: type || 'success',\n      duration: messageDuration,\n    })\n  }\n\n  /* 全局TableHeight */\n  Vue.prototype.$baseTableHeight = (formType) => {\n    let height = window.innerHeight\n    let paddingHeight = 400\n    const formHeight = 50\n\n    if (layout === 'vertical') {\n      paddingHeight = 340\n    }\n\n    if ('number' == typeof formType) {\n      height = height - paddingHeight - formHeight * formType\n    } else {\n      height = height - paddingHeight\n    }\n    return height\n  }\n\n  /* 全局lodash */\n  Vue.prototype.$baseLodash = lodash\n  /* 全局事件总线 */\n  Vue.prototype.$baseEventBus = new Vue()\n}\n\nif (typeof window !== 'undefined' && window.Vue) {\n  install(window.Vue)\n}\n\nexport default install\n"
  },
  {
    "path": "src/utils/validate.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判读是否为外链\n * @param path\n * @returns {boolean}\n */\nexport function isExternal(path) {\n  return /^(https?:|mailto:|tel:)/.test(path)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 校验密码是否小于6位\n * @param str\n * @returns {boolean}\n */\nexport function isPassword(str) {\n  return str.length >= 6\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否为数字\n * @param value\n * @returns {boolean}\n */\nexport function isNumber(value) {\n  const reg = /^[0-9]*$/\n  return reg.test(value)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是名称\n * @param value\n * @returns {boolean}\n */\nexport function isName(value) {\n  const reg = /^[\\u4e00-\\u9fa5a-zA-Z0-9]+$/\n  return reg.test(value)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否为IP\n * @param ip\n * @returns {boolean}\n */\nexport function isIP(ip) {\n  const reg =\n    /^(\\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])$/\n  return reg.test(ip)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是传统网站\n * @param url\n * @returns {boolean}\n */\nexport function isUrl(url) {\n  const reg =\n    /^(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.,?'\\\\+&%$#=~_-]+))*$/\n  return reg.test(url)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是小写字母\n * @param str\n * @returns {boolean}\n */\nexport function isLowerCase(str) {\n  const reg = /^[a-z]+$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是大写字母\n * @param str\n * @returns {boolean}\n */\nexport function isUpperCase(str) {\n  const reg = /^[A-Z]+$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是大写字母开头\n * @param str\n * @returns {boolean}\n */\nexport function isAlphabets(str) {\n  const reg = /^[A-Za-z]+$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是字符串\n * @param str\n * @returns {boolean}\n */\nexport function isString(str) {\n  return typeof str === 'string' || str instanceof String\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是数组\n * @param arg\n * @returns {arg is any[]|boolean}\n */\nexport function isArray(arg) {\n  if (typeof Array.isArray === 'undefined') {\n    return Object.prototype.toString.call(arg) === '[object Array]'\n  }\n  return Array.isArray(arg)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是端口号\n * @param str\n * @returns {boolean}\n */\nexport function isPort(str) {\n  const reg =\n    /^([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])$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是手机号\n * @param str\n * @returns {boolean}\n */\nexport function isPhone(str) {\n  const reg = /^1\\d{10}$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是身份证号(第二代)\n * @param str\n * @returns {boolean}\n */\nexport function isIdCard(str) {\n  const reg =\n    /^[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]$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否是邮箱\n * @param str\n * @returns {boolean}\n */\nexport function isEmail(str) {\n  const reg = /^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否中文\n * @param str\n * @returns {boolean}\n */\nexport function isChina(str) {\n  const reg = /^[\\u4E00-\\u9FA5]{2,4}$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否为空\n * @param str\n * @returns {boolean}\n */\nexport function isBlank(str) {\n  return (\n    str == null ||\n    false ||\n    str === '' ||\n    str.trim() === '' ||\n    str.toLocaleLowerCase().trim() === 'null'\n  )\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否为固话\n * @param str\n * @returns {boolean}\n */\nexport function isTel(str) {\n  const reg =\n    /^(400|800)([0-9\\\\-]{7,10})|(([0-9]{4}|[0-9]{3})(-| )?)?([0-9]{7,8})((-| |转)*([0-9]{1,4}))?$/\n  return reg.test(str)\n}\n\n/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description 判断是否为数字且最多两位小数\n * @param str\n * @returns {boolean}\n */\nexport function isNum(str) {\n  const reg = /^\\d+(\\.\\d{1,2})?$/\n  return reg.test(str)\n}\n"
  },
  {
    "path": "src/views/401.vue",
    "content": "<template>\n  <div class=\"error-container\">\n    <div class=\"error-content\">\n      <el-row :gutter=\"20\">\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"12\" :lg=\"12\" :xl=\"12\">\n          <div class=\"pic-error\">\n            <img\n              alt=\"401\"\n              class=\"pic-error-parent\"\n              src=\"@/assets/error_images/401.png\"\n            />\n            <img\n              alt=\"401\"\n              class=\"pic-error-child left\"\n              src=\"@/assets/error_images/cloud.png\"\n            />\n            <img\n              alt=\"401\"\n              class=\"pic-error-child\"\n              src=\"@/assets/error_images/cloud.png\"\n            />\n            <img\n              alt=\"401\"\n              class=\"pic-error-child\"\n              src=\"@/assets/error_images/cloud.png\"\n            />\n          </div>\n        </el-col>\n\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"12\" :lg=\"12\" :xl=\"12\">\n          <div class=\"bullshit\">\n            <div class=\"bullshit-oops\">{{ oops }}</div>\n            <div class=\"bullshit-headline\">{{ headline }}</div>\n            <div class=\"bullshit-info\">{{ info }}</div>\n            <a class=\"bullshit-return-home\" href=\"#/index\">\n              {{ jumpTime }}s&nbsp;{{ btn }}\n            </a>\n          </div>\n        </el-col>\n      </el-row>\n    </div>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'Page401',\n    data() {\n      return {\n        jumpTime: 5,\n        oops: '抱歉!',\n        headline: '您没有操作权限...',\n        info: '当前帐号没有操作权限,请联系管理员。',\n        btn: '返回',\n        timer: 0,\n      }\n    },\n    mounted() {\n      this.timeChange()\n    },\n    beforeDestroy() {\n      clearInterval(this.timer)\n    },\n    methods: {\n      timeChange() {\n        this.timer = setInterval(() => {\n          if (this.jumpTime) {\n            this.jumpTime--\n          } else {\n            this.$router.push({ path: '/' })\n            this.$store.dispatch('tagsBar/delOthersRoutes', {\n              path: '/',\n            })\n            clearInterval(this.timer)\n          }\n        }, 1000)\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .error-container {\n    position: absolute;\n    top: 40%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n\n    .error-content {\n      .pic-error {\n        position: relative;\n        float: left;\n        width: 120%;\n        overflow: hidden;\n\n        &-parent {\n          width: 100%;\n        }\n\n        &-child {\n          position: absolute;\n\n          &.left {\n            top: 17px;\n            left: 220px;\n            width: 80px;\n            opacity: 0;\n            animation-name: cloudLeft;\n            animation-duration: 2s;\n            animation-timing-function: linear;\n            animation-delay: 1s;\n            animation-fill-mode: forwards;\n          }\n\n          &.mid {\n            top: 10px;\n            left: 420px;\n            width: 46px;\n            opacity: 0;\n            animation-name: cloudMid;\n            animation-duration: 2s;\n            animation-timing-function: linear;\n            animation-delay: 1.2s;\n            animation-fill-mode: forwards;\n          }\n\n          &.right {\n            top: 100px;\n            left: 500px;\n            width: 62px;\n            opacity: 0;\n            animation-name: cloudRight;\n            animation-duration: 2s;\n            animation-timing-function: linear;\n            animation-delay: 1s;\n            animation-fill-mode: forwards;\n          }\n\n          @keyframes cloudLeft {\n            0% {\n              top: 17px;\n              left: 220px;\n              opacity: 0;\n            }\n\n            20% {\n              top: 33px;\n              left: 188px;\n              opacity: 1;\n            }\n\n            80% {\n              top: 81px;\n              left: 92px;\n              opacity: 1;\n            }\n\n            100% {\n              top: 97px;\n              left: 60px;\n              opacity: 0;\n            }\n          }\n\n          @keyframes cloudMid {\n            0% {\n              top: 10px;\n              left: 420px;\n              opacity: 0;\n            }\n\n            20% {\n              top: 40px;\n              left: 360px;\n              opacity: 1;\n            }\n\n            70% {\n              top: 130px;\n              left: 180px;\n              opacity: 1;\n            }\n\n            100% {\n              top: 160px;\n              left: 120px;\n              opacity: 0;\n            }\n          }\n\n          @keyframes cloudRight {\n            0% {\n              top: 100px;\n              left: 500px;\n              opacity: 0;\n            }\n\n            20% {\n              top: 120px;\n              left: 460px;\n              opacity: 1;\n            }\n\n            80% {\n              top: 180px;\n              left: 340px;\n              opacity: 1;\n            }\n\n            100% {\n              top: 200px;\n              left: 300px;\n              opacity: 0;\n            }\n          }\n        }\n      }\n\n      .bullshit {\n        position: relative;\n        float: left;\n        width: 300px;\n        padding: 30px 0;\n        overflow: hidden;\n\n        &-oops {\n          margin-bottom: 20px;\n          font-size: 32px;\n          font-weight: bold;\n          line-height: 40px;\n          color: $base-color-blue;\n          opacity: 0;\n          animation-name: slideUp;\n          animation-duration: 0.5s;\n          animation-fill-mode: forwards;\n        }\n\n        &-headline {\n          margin-bottom: 10px;\n          font-size: 20px;\n          font-weight: bold;\n          line-height: 24px;\n          color: #222;\n          opacity: 0;\n          animation-name: slideUp;\n          animation-duration: 0.5s;\n          animation-delay: 0.1s;\n          animation-fill-mode: forwards;\n        }\n\n        &-info {\n          margin-bottom: 30px;\n          font-size: 13px;\n          line-height: 21px;\n          color: $base-color-gray;\n          opacity: 0;\n          animation-name: slideUp;\n          animation-duration: 0.5s;\n          animation-delay: 0.2s;\n          animation-fill-mode: forwards;\n        }\n\n        &-return-home {\n          display: block;\n          float: left;\n          width: 110px;\n          height: 36px;\n          font-size: 14px;\n          line-height: 36px;\n          color: #fff;\n          text-align: center;\n          cursor: pointer;\n          background: $base-color-blue;\n          border-radius: 100px;\n          opacity: 0;\n          animation-name: slideUp;\n          animation-duration: 0.5s;\n          animation-delay: 0.3s;\n          animation-fill-mode: forwards;\n        }\n\n        @keyframes slideUp {\n          0% {\n            opacity: 0;\n            transform: translateY(60px);\n          }\n\n          100% {\n            opacity: 1;\n            transform: translateY(0);\n          }\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/views/404.vue",
    "content": "<template>\n  <div class=\"error-container\">\n    <div class=\"error-content\">\n      <el-row :gutter=\"20\">\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"12\" :lg=\"12\" :xl=\"12\">\n          <div class=\"pic-error\">\n            <img\n              alt=\"401\"\n              class=\"pic-error-parent\"\n              src=\"@/assets/error_images/404.png\"\n            />\n            <img\n              alt=\"401\"\n              class=\"pic-error-child left\"\n              src=\"@/assets/error_images/cloud.png\"\n            />\n            <img\n              alt=\"401\"\n              class=\"pic-error-child\"\n              src=\"@/assets/error_images/cloud.png\"\n            />\n            <img\n              alt=\"401\"\n              class=\"pic-error-child\"\n              src=\"@/assets/error_images/cloud.png\"\n            />\n          </div>\n        </el-col>\n\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"12\" :lg=\"12\" :xl=\"12\">\n          <div class=\"bullshit\">\n            <div class=\"bullshit-oops\">{{ oops }}</div>\n            <div class=\"bullshit-headline\">{{ headline }}</div>\n            <div class=\"bullshit-info\">{{ info }}</div>\n            <a class=\"bullshit-return-home\" href=\"#/index\">\n              {{ jumpTime }}s&nbsp;{{ btn }}\n            </a>\n          </div>\n        </el-col>\n      </el-row>\n    </div>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'Page404',\n    data() {\n      return {\n        jumpTime: 5,\n        oops: '抱歉!',\n        headline: '当前页面不存在...',\n        info: '请检查您输入的网址是否正确，或点击下面的按钮返回首页。',\n        btn: '返回首页',\n        timer: 0,\n      }\n    },\n    mounted() {\n      this.timeChange()\n    },\n    beforeDestroy() {\n      clearInterval(this.timer)\n    },\n    methods: {\n      timeChange() {\n        this.timer = setInterval(() => {\n          if (this.jumpTime) {\n            this.jumpTime--\n          } else {\n            this.$router.push({ path: '/' })\n            this.$store.dispatch('tagsBar/delOthersRoutes', {\n              path: '/',\n            })\n            clearInterval(this.timer)\n          }\n        }, 1000)\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .error-container {\n    position: absolute;\n    top: 40%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n\n    .error-content {\n      .pic-error {\n        position: relative;\n        float: left;\n        width: 120%;\n        overflow: hidden;\n\n        &-parent {\n          width: 100%;\n        }\n\n        &-child {\n          position: absolute;\n\n          &.left {\n            top: 17px;\n            left: 220px;\n            width: 80px;\n            opacity: 0;\n            animation-name: cloudLeft;\n            animation-duration: 2s;\n            animation-timing-function: linear;\n            animation-delay: 1s;\n            animation-fill-mode: forwards;\n          }\n\n          &.mid {\n            top: 10px;\n            left: 420px;\n            width: 46px;\n            opacity: 0;\n            animation-name: cloudMid;\n            animation-duration: 2s;\n            animation-timing-function: linear;\n            animation-delay: 1.2s;\n            animation-fill-mode: forwards;\n          }\n\n          &.right {\n            top: 100px;\n            left: 500px;\n            width: 62px;\n            opacity: 0;\n            animation-name: cloudRight;\n            animation-duration: 2s;\n            animation-timing-function: linear;\n            animation-delay: 1s;\n            animation-fill-mode: forwards;\n          }\n\n          @keyframes cloudLeft {\n            0% {\n              top: 17px;\n              left: 220px;\n              opacity: 0;\n            }\n\n            20% {\n              top: 33px;\n              left: 188px;\n              opacity: 1;\n            }\n\n            80% {\n              top: 81px;\n              left: 92px;\n              opacity: 1;\n            }\n\n            100% {\n              top: 97px;\n              left: 60px;\n              opacity: 0;\n            }\n          }\n\n          @keyframes cloudMid {\n            0% {\n              top: 10px;\n              left: 420px;\n              opacity: 0;\n            }\n\n            20% {\n              top: 40px;\n              left: 360px;\n              opacity: 1;\n            }\n\n            70% {\n              top: 130px;\n              left: 180px;\n              opacity: 1;\n            }\n\n            100% {\n              top: 160px;\n              left: 120px;\n              opacity: 0;\n            }\n          }\n\n          @keyframes cloudRight {\n            0% {\n              top: 100px;\n              left: 500px;\n              opacity: 0;\n            }\n\n            20% {\n              top: 120px;\n              left: 460px;\n              opacity: 1;\n            }\n\n            80% {\n              top: 180px;\n              left: 340px;\n              opacity: 1;\n            }\n\n            100% {\n              top: 200px;\n              left: 300px;\n              opacity: 0;\n            }\n          }\n        }\n      }\n\n      .bullshit {\n        position: relative;\n        float: left;\n        width: 300px;\n        padding: 30px 0;\n        overflow: hidden;\n\n        &-oops {\n          margin-bottom: 20px;\n          font-size: 32px;\n          font-weight: bold;\n          line-height: 40px;\n          color: $base-color-blue;\n          opacity: 0;\n          animation-name: slideUp;\n          animation-duration: 0.5s;\n          animation-fill-mode: forwards;\n        }\n\n        &-headline {\n          margin-bottom: 10px;\n          font-size: 20px;\n          font-weight: bold;\n          line-height: 24px;\n          color: #222;\n          opacity: 0;\n          animation-name: slideUp;\n          animation-duration: 0.5s;\n          animation-delay: 0.1s;\n          animation-fill-mode: forwards;\n        }\n\n        &-info {\n          margin-bottom: 30px;\n          font-size: 13px;\n          line-height: 21px;\n          color: $base-color-gray;\n          opacity: 0;\n          animation-name: slideUp;\n          animation-duration: 0.5s;\n          animation-delay: 0.2s;\n          animation-fill-mode: forwards;\n        }\n\n        &-return-home {\n          display: block;\n          float: left;\n          width: 110px;\n          height: 36px;\n          font-size: 14px;\n          line-height: 36px;\n          color: #fff;\n          text-align: center;\n          cursor: pointer;\n          background: $base-color-blue;\n          border-radius: 100px;\n          opacity: 0;\n          animation-name: slideUp;\n          animation-duration: 0.5s;\n          animation-delay: 0.3s;\n          animation-fill-mode: forwards;\n        }\n\n        @keyframes slideUp {\n          0% {\n            opacity: 0;\n            transform: translateY(60px);\n          }\n\n          100% {\n            opacity: 1;\n            transform: translateY(0);\n          }\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/views/index/index.vue",
    "content": "<template>\n  <div class=\"more-container\">\n    <el-row :gutter=\"20\">\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"8\" :xl=\"8\">\n        <el-card>\n          <div slot=\"header\">\n            开源版本\n            <el-button style=\"float: right; padding: 3px 0\" type=\"text\">\n              永久免费 个人/商业使用\n            </el-button>\n          </div>\n          <div>\n            <ul>\n              <li>永久开源免费，支持横纵布局切换</li>\n              <li>\n                保留浏览器控制台打印即可免费商用，页面中的作者信息可全部去除，无需保留\n              </li>\n              <li>\n                开源地址\n                <a\n                  target=\"_blank\"\n                  href=\"https://github.com/chuzhixin/vue-admin-better\"\n                >\n                  如果有幸帮到了你，麻烦给个star\n                </a>\n              </li>\n              <li>提供讨论群专属文档，QQ群 972435319</li>\n            </ul>\n          </div>\n        </el-card>\n      </el-col>\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"8\" :xl=\"8\">\n        <el-card>\n          <div slot=\"header\">\n            VIP群（自愿原则）\n            <el-button style=\"float: right; padding: 3px 0\" type=\"text\">\n              ￥100（不再提供）\n            </el-button>\n          </div>\n          <div>\n            <ul>\n              <li>为防止不必要的误解，开源版不再提供vip群</li>\n            </ul>\n          </div>\n        </el-card>\n      </el-col>\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"8\" :xl=\"8\">\n        <el-card>\n          <div slot=\"header\">\n            开源版不保留MIT协议，变更作者（自愿原则）\n            <el-button style=\"float: right; padding: 3px 0\" type=\"text\">\n              ￥299\n            </el-button>\n          </div>\n          <div>\n            <ul>\n              <li>支持以上所有特权，不包含VIP群</li>\n              <li>包含开源基础版授权与开源集成版授权</li>\n              <li>永久更新</li>\n              <li>提供低价外包合作机会</li>\n            </ul>\n          </div>\n        </el-card>\n      </el-col>\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"8\" :xl=\"8\">\n        <el-card>\n          <div slot=\"header\">\n            PRO版\n            <el-button style=\"float: right; padding: 3px 0\" type=\"text\">\n              ￥699\n            </el-button>\n          </div>\n          <div>\n            <ul>\n              <li>\n                演示地址：\n                <a\n                  target=\"_blank\"\n                  href=\"https://chu1204505056.gitee.io/admin-pro\"\n                >\n                  点我\n                </a>\n              </li>\n            </ul>\n          </div>\n        </el-card>\n      </el-col>\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"8\" :xl=\"8\">\n        <el-card>\n          <div slot=\"header\">\n            Plus版\n            <el-button style=\"float: right; padding: 3px 0\" type=\"text\">\n              ￥799\n            </el-button>\n          </div>\n          <div>\n            <ul>\n              <li>\n                演示地址：\n                <a\n                  target=\"_blank\"\n                  href=\"https://chu1204505056.gitee.io/admin-plus\"\n                >\n                  点我\n                </a>\n              </li>\n            </ul>\n          </div>\n        </el-card>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'More',\n    components: {},\n    data() {\n      return { nodeEnv: process.env.NODE_ENV }\n    },\n    created() {},\n    mounted() {},\n    methods: {},\n  }\n</script>\n<style lang=\"scss\" scoped>\n  .more-container {\n    ::v-deep {\n      .el-card__body {\n        > div {\n          min-height: 220px;\n          padding: 20px;\n\n          > ul {\n            > li {\n              line-height: 30px;\n            }\n          }\n\n          > img {\n            display: block;\n            margin: 40px auto;\n            border: 1px solid #dedede;\n          }\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/views/login/index.vue",
    "content": "<template>\n  <div class=\"login-container\">\n    <el-alert\n      v-if=\"nodeEnv !== 'development'\"\n      title=\"beautiful boys and girls欢迎加入vue-admin-betterQQ群：972435319\"\n      type=\"success\"\n      :closable=\"false\"\n      style=\"position: fixed\"\n    ></el-alert>\n    <el-row>\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"12\" :lg=\"16\" :xl=\"16\">\n        <div style=\"color: transparent\">占位符</div>\n      </el-col>\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"12\" :lg=\"8\" :xl=\"8\">\n        <el-form\n          ref=\"form\"\n          :model=\"form\"\n          :rules=\"rules\"\n          class=\"login-form\"\n          label-position=\"left\"\n        >\n          <div class=\"title\">hello !</div>\n          <div class=\"title-tips\">欢迎来到{{ title }}！</div>\n          <el-form-item style=\"margin-top: 40px\" prop=\"username\">\n            <span class=\"svg-container svg-container-admin\">\n              <vab-icon :icon=\"['fas', 'user']\" />\n            </span>\n            <el-input\n              v-model.trim=\"form.username\"\n              v-focus\n              placeholder=\"请输入用户名\"\n              tabindex=\"1\"\n              type=\"text\"\n            />\n          </el-form-item>\n          <el-form-item prop=\"password\">\n            <span class=\"svg-container\">\n              <vab-icon :icon=\"['fas', 'lock']\" />\n            </span>\n            <el-input\n              :key=\"passwordType\"\n              ref=\"password\"\n              v-model.trim=\"form.password\"\n              :type=\"passwordType\"\n              tabindex=\"2\"\n              placeholder=\"请输入密码\"\n              @keyup.enter.native=\"handleLogin\"\n            />\n            <span\n              v-if=\"passwordType === 'password'\"\n              class=\"show-password\"\n              @click=\"handlePassword\"\n            >\n              <vab-icon :icon=\"['fas', 'eye-slash']\"></vab-icon>\n            </span>\n            <span v-else class=\"show-password\" @click=\"handlePassword\">\n              <vab-icon :icon=\"['fas', 'eye']\"></vab-icon>\n            </span>\n          </el-form-item>\n          <el-button\n            :loading=\"loading\"\n            class=\"login-btn\"\n            type=\"primary\"\n            @click=\"handleLogin\"\n          >\n            登录\n          </el-button>\n          <router-link to=\"/register\">\n            <div style=\"margin-top: 20px\">注册</div>\n          </router-link>\n        </el-form>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n\n<script>\n  import { isPassword } from '@/utils/validate'\n\n  export default {\n    name: 'Login',\n    directives: {\n      focus: {\n        inserted(el) {\n          el.querySelector('input').focus()\n        },\n      },\n    },\n    data() {\n      const validateusername = (rule, value, callback) => {\n        if ('' == value) {\n          callback(new Error('用户名不能为空'))\n        } else {\n          callback()\n        }\n      }\n      const validatePassword = (rule, value, callback) => {\n        if (!isPassword(value)) {\n          callback(new Error('密码不能少于6位'))\n        } else {\n          callback()\n        }\n      }\n      return {\n        nodeEnv: process.env.NODE_ENV,\n        title: this.$baseTitle,\n        form: {\n          username: '',\n          password: '',\n        },\n        rules: {\n          username: [\n            {\n              required: true,\n              trigger: 'blur',\n              validator: validateusername,\n            },\n          ],\n          password: [\n            {\n              required: true,\n              trigger: 'blur',\n              validator: validatePassword,\n            },\n          ],\n        },\n        loading: false,\n        passwordType: 'password',\n        redirect: undefined,\n      }\n    },\n    watch: {\n      $route: {\n        handler(route) {\n          this.redirect = (route.query && route.query.redirect) || '/'\n        },\n        immediate: true,\n      },\n    },\n    created() {\n      document.body.style.overflow = 'hidden'\n    },\n    beforeDestroy() {\n      document.body.style.overflow = 'auto'\n    },\n    mounted() {\n      this.form.username = 'admin'\n      this.form.password = '123456'\n    },\n    methods: {\n      handlePassword() {\n        this.passwordType === 'password'\n          ? (this.passwordType = '')\n          : (this.passwordType = 'password')\n        this.$nextTick(() => {\n          this.$refs.password.focus()\n        })\n      },\n      handleLogin() {\n        this.$refs.form.validate((valid) => {\n          if (valid) {\n            this.loading = true\n            this.$store\n              .dispatch('user/login', this.form)\n              .then(() => {\n                const routerPath =\n                  this.redirect === '/404' || this.redirect === '/401'\n                    ? '/'\n                    : this.redirect\n                this.$router.push(routerPath).catch(() => {})\n                this.loading = false\n              })\n              .catch(() => {\n                this.loading = false\n              })\n          } else {\n            return false\n          }\n        })\n        /* setTimeout(() => {\n          window.open('https://github.com/chuzhixin/vue-admin-better')\n        }, 100000) */\n      },\n    },\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .login-container {\n    height: 100vh;\n    background: url('~@/assets/login_images/background.jpg') center center fixed\n      no-repeat;\n    background-size: cover;\n\n    .title {\n      font-size: 54px;\n      font-weight: 500;\n      color: rgba(14, 18, 26, 1);\n    }\n\n    .title-tips {\n      margin-top: 29px;\n      font-size: 26px;\n      font-weight: 400;\n      color: rgba(14, 18, 26, 1);\n      text-overflow: ellipsis;\n      white-space: nowrap;\n    }\n\n    .login-btn {\n      display: inherit;\n      width: 220px;\n      height: 60px;\n      margin-top: 5px;\n      border: 0;\n\n      &:hover {\n        opacity: 0.9;\n      }\n    }\n\n    .login-form {\n      position: relative;\n      max-width: 100%;\n      margin: calc((100vh - 425px) / 2) 10% 10%;\n      overflow: hidden;\n\n      .forget-password {\n        width: 100%;\n        margin-top: 40px;\n        text-align: left;\n\n        .forget-pass {\n          width: 129px;\n          height: 19px;\n          font-size: 20px;\n          font-weight: 400;\n          color: rgba(92, 102, 240, 1);\n        }\n      }\n    }\n\n    .tips {\n      margin-bottom: 10px;\n      font-size: $base-font-size-default;\n      color: $base-color-white;\n\n      span {\n        &:first-of-type {\n          margin-right: 16px;\n        }\n      }\n    }\n\n    .title-container {\n      position: relative;\n\n      .title {\n        margin: 0 auto 40px auto;\n        font-size: 34px;\n        font-weight: bold;\n        color: $base-color-blue;\n        text-align: center;\n      }\n    }\n\n    .svg-container {\n      position: absolute;\n      top: 14px;\n      left: 15px;\n      z-index: $base-z-index;\n      font-size: 16px;\n      color: #d7dee3;\n      cursor: pointer;\n      user-select: none;\n    }\n\n    .show-password {\n      position: absolute;\n      top: 14px;\n      right: 25px;\n      font-size: 16px;\n      color: #d7dee3;\n      cursor: pointer;\n      user-select: none;\n    }\n\n    ::v-deep {\n      .el-form-item {\n        padding-right: 0;\n        margin: 20px 0;\n        color: #454545;\n        background: transparent;\n        border: 1px solid transparent;\n        border-radius: 2px;\n\n        &__content {\n          min-height: $base-input-height;\n          line-height: $base-input-height;\n        }\n\n        &__error {\n          position: absolute;\n          top: 100%;\n          left: 18px;\n          font-size: $base-font-size-small;\n          line-height: 18px;\n          color: $base-color-red;\n        }\n      }\n\n      .el-input {\n        box-sizing: border-box;\n\n        input {\n          height: 58px;\n          padding-left: 45px;\n          font-size: $base-font-size-default;\n          line-height: 58px;\n          color: $base-font-color;\n          background: #f6f4fc;\n          border: 0;\n          caret-color: $base-font-color;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/views/register/index.vue",
    "content": "<template>\n  <div class=\"register-container\">\n    <el-alert\n      v-if=\"nodeEnv !== 'development'\"\n      title=\"beautiful boys and girls欢迎加入vue-admin-betterQQ群：972435319\"\n      type=\"success\"\n      :closable=\"false\"\n      style=\"position: fixed\"\n    ></el-alert>\n    <el-row>\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"12\" :lg=\"16\" :xl=\"16\">\n        <div style=\"color: transparent\">占位符</div>\n      </el-col>\n      <el-col :xs=\"24\" :sm=\"24\" :md=\"12\" :lg=\"8\" :xl=\"8\">\n        <el-form\n          ref=\"registerForm\"\n          :model=\"form\"\n          class=\"register-form\"\n          :rules=\"registerRules\"\n          size=\"mini\"\n        >\n          <el-form-item prop=\"username\">\n            <el-input\n              v-model.trim=\"form.username\"\n              v-focus\n              style=\"margin-top: 20px\"\n              type=\"text\"\n              placeholder=\"请输入用户名\"\n              auto-complete=\"off\"\n            >\n              <vab-icon slot=\"prefix\" :icon=\"['fas', 'user-alt']\"></vab-icon>\n            </el-input>\n          </el-form-item>\n          <el-form-item prop=\"phone\">\n            <el-input\n              v-model.trim=\"form.phone\"\n              type=\"text\"\n              placeholder=\"请输入手机号\"\n              maxlength=\"11\"\n              show-word-limit\n              autocomplete=\"off\"\n            >\n              <vab-icon slot=\"prefix\" :icon=\"['fas', 'mobile-alt']\"></vab-icon>\n            </el-input>\n          </el-form-item>\n          <el-form-item prop=\"phoneCode\" style=\"position: relative\">\n            <el-input\n              v-model.trim=\"form.phoneCode\"\n              type=\"text\"\n              placeholder=\"手机验证码\"\n            >\n              <vab-icon\n                slot=\"prefix\"\n                :icon=\"['fas', 'envelope-open']\"\n              ></vab-icon>\n            </el-input>\n            <el-button\n              type=\"primary\"\n              class=\"show-pwd phone-code\"\n              :disabled=\"isGetphone\"\n              @click=\"getPhoneCode\"\n            >\n              {{ phoneCode }}\n            </el-button>\n          </el-form-item>\n          <el-form-item prop=\"password\">\n            <el-input\n              v-model.trim=\"form.password\"\n              type=\"password\"\n              placeholder=\"设置密码\"\n              autocomplete=\"new-password\"\n            >\n              <vab-icon slot=\"prefix\" :icon=\"['fas', 'unlock']\"></vab-icon>\n            </el-input>\n          </el-form-item>\n          <el-form-item>\n            <el-button\n              class=\"register-btn\"\n              type=\"primary\"\n              @click.native.prevent=\"handleReister\"\n            >\n              注册\n            </el-button>\n            <router-link to=\"/login\">\n              <div style=\"margin-top: 20px\">登录</div>\n            </router-link>\n          </el-form-item>\n        </el-form>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n<script>\n  import { isPassword, isPhone } from '@/utils/validate'\n  import { register } from '@/api/user'\n  export default {\n    username: 'Register',\n    directives: {\n      focus: {\n        inserted(el) {\n          el.querySelector('input').focus()\n        },\n      },\n    },\n    data() {\n      const validateusername = (rule, value, callback) => {\n        if ('' == value) {\n          callback(new Error('用户名不能为空'))\n        } else {\n          callback()\n        }\n      }\n      const validatePassword = (rule, value, callback) => {\n        if (!isPassword(value)) {\n          callback(new Error('密码不能少于6位'))\n        } else {\n          callback()\n        }\n      }\n      const validatePhone = (rule, value, callback) => {\n        if (!isPhone(value)) {\n          callback(new Error('请输入正确的手机号'))\n        } else {\n          callback()\n        }\n      }\n      return {\n        isGetphone: false,\n        getPhoneIntval: null,\n        phoneCode: '获取验证码',\n        showRegister: false,\n        nodeEnv: process.env.NODE_ENV,\n        title: this.$baseTitle,\n        form: {},\n        registerRules: {\n          username: [\n            { required: true, trigger: 'blur', message: '请输入用户名' },\n            { max: 20, trigger: 'blur', message: '最多不能超过20个字' },\n            { validator: validateusername, trigger: 'blur' },\n          ],\n          phone: [\n            { required: true, trigger: 'blur', message: '请输入手机号码' },\n            { validator: validatePhone, trigger: 'blur' },\n          ],\n          password: [\n            { required: true, trigger: 'blur', message: '请输入密码' },\n            { validator: validatePassword, trigger: 'blur' },\n          ],\n          phoneCode: [\n            { required: true, trigger: 'blur', message: '请输入手机验证码' },\n          ],\n        },\n        loading: false,\n        passwordType: 'password',\n      }\n    },\n    created() {\n      document.body.style.overflow = 'hidden'\n    },\n    beforeDestroy() {\n      document.body.style.overflow = 'auto'\n      this.getPhoneIntval = null\n      clearInterval(this.getPhoneIntval)\n    },\n    methods: {\n      getPhoneCode() {\n        this.isGetphone = true\n        let n = 60\n        this.getPhoneIntval = setInterval(() => {\n          if (n > 0) {\n            n--\n            this.phoneCode = '重新获取(' + n + 's)'\n          } else {\n            this.getPhoneIntval = null\n            clearInterval(this.getPhoneIntval)\n            this.phoneCode = '获取验证码'\n            this.isGetphone = false\n          }\n        }, 1000)\n      },\n      handleReister() {\n        this.$refs['registerForm'].validate(async (valid) => {\n          if (valid) {\n            const param = {\n              username: this.form.username,\n              phone: this.form.phone,\n              password: this.form.password,\n              phoneCode: this.form.phoneCode,\n            }\n            const { msg } = await register(param)\n            this.$baseMessage(msg, 'success')\n          }\n        })\n      },\n    },\n  }\n</script>\n<style lang=\"scss\" scoped>\n  .register-container {\n    height: 100vh;\n    background: url('~@/assets/login_images/background.jpg') center center fixed\n      no-repeat;\n    background-size: cover;\n\n    .title {\n      font-size: 54px;\n      font-weight: 500;\n      color: rgba(14, 18, 26, 1);\n    }\n\n    .title-tips {\n      margin-top: 29px;\n      font-size: 26px;\n      font-weight: 400;\n      color: rgba(14, 18, 26, 1);\n      text-overflow: ellipsis;\n      white-space: nowrap;\n    }\n\n    .register-btn {\n      display: inherit;\n      width: 220px;\n      height: 60px;\n      margin-top: 5px;\n      border: 0;\n\n      &:hover {\n        opacity: 0.9;\n      }\n    }\n\n    .register-form {\n      position: relative;\n      max-width: 100%;\n      margin: calc((100vh - 499px) / 2) 10% 10%;\n      overflow: hidden;\n\n      .forget-password {\n        width: 100%;\n        margin-top: 40px;\n        text-align: left;\n\n        .forget-password {\n          width: 129px;\n          height: 19px;\n          font-size: 20px;\n          font-weight: 400;\n          color: rgba(92, 102, 240, 1);\n        }\n      }\n\n      .per-code {\n        width: 100px;\n        height: 36px;\n        font-size: 20px;\n        line-height: 36px;\n        color: #fff;\n        text-align: center;\n        cursor: pointer;\n        background: #bbc1ce;\n      }\n\n      .phone-code {\n        width: 120px;\n        height: 36px;\n        font-size: 14px;\n        color: #fff;\n        border-radius: 3px;\n      }\n    }\n\n    .tips {\n      margin-bottom: 10px;\n      font-size: $base-font-size-default;\n      color: $base-color-white;\n\n      span {\n        &:first-of-type {\n          margin-right: 16px;\n        }\n      }\n    }\n\n    .title-container {\n      position: relative;\n\n      .title {\n        margin: 0 auto 40px auto;\n        font-size: 34px;\n        font-weight: bold;\n        color: $base-color-blue;\n        text-align: center;\n      }\n    }\n\n    .svg-container {\n      position: absolute;\n      top: 14px;\n      left: 15px;\n      z-index: $base-z-index;\n      font-size: 16px;\n      color: #d7dee3;\n      cursor: pointer;\n      user-select: none;\n    }\n\n    .show-pwd {\n      position: absolute;\n      top: 14px;\n      right: 25px;\n      font-size: 16px;\n      color: $base-font-color;\n      cursor: pointer;\n      user-select: none;\n    }\n\n    ::v-deep {\n      .el-form-item {\n        padding-right: 0;\n        margin: 20px 0;\n        color: #454545;\n        background: transparent;\n        border: 1px solid transparent;\n        border-radius: 2px;\n\n        &__content {\n          min-height: $base-input-height;\n          line-height: $base-input-height;\n        }\n\n        &__error {\n          position: absolute;\n          top: 100%;\n          left: 18px;\n          font-size: $base-font-size-small;\n          line-height: 18px;\n          color: $base-color-red;\n        }\n      }\n\n      .el-input {\n        box-sizing: border-box;\n\n        .el-input__count {\n          .el-input__count-inner {\n            background: transparent;\n          }\n        }\n\n        .el-input__prefix {\n          left: 15px;\n          line-height: 56px;\n\n          .svg-inline--fa {\n            width: 20px;\n          }\n        }\n\n        input {\n          height: 58px;\n          padding-left: 45px;\n          font-size: $base-font-size-default;\n          line-height: 58px;\n          color: $base-font-color;\n          background: #f6f4fc;\n          border: 0;\n          caret-color: $base-font-color;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "vab-icon/package.json",
    "content": "{\n  \"name\": \"vab-icon\",\n  \"version\": \"0.0.1\",\n  \"main\": \"lib/vab-icon.umd.min.js\"\n}\n"
  },
  {
    "path": "vue.config.js",
    "content": "/**\n * @author chuzhixin 1204505056@qq.com （不想保留author可删除）\n * @description cli配置\n */\n\nconst path = require('path')\nconst {\n  publicPath,\n  assetsDir,\n  outputDir,\n  lintOnSave,\n  transpileDependencies,\n  title,\n  abbreviation,\n  devPort,\n  providePlugin,\n  build7z,\n  donation,\n} = require('./src/config')\nconst { webpackBarName, webpackBanner, donationConsole } = require('layouts')\n\nif (donation) donationConsole()\nconst { version, author } = require('./package.json')\nconst Webpack = require('webpack')\nconst WebpackBar = require('webpackbar')\nconst FileManagerPlugin = require('filemanager-webpack-plugin')\nconst dayjs = require('dayjs')\nconst date = dayjs().format('YYYY_M_D')\nconst time = dayjs().format('YYYY-M-D HH:mm:ss')\nconst productionGzipExtensions = ['html', 'js', 'css', 'svg']\nprocess.env.VUE_APP_TITLE = title || 'vue-admin-better'\nprocess.env.VUE_APP_AUTHOR = author || 'chuzhixin 1204505056@qq.com'\nprocess.env.VUE_APP_UPDATE_TIME = time\nprocess.env.VUE_APP_VERSION = version\n\nconst resolve = (dir) => path.join(__dirname, dir)\nconst mockServer = () => {\n  if (process.env.NODE_ENV === 'development')\n    return require('./mock/mockServer.js')\n  else return ''\n}\n\nmodule.exports = {\n  publicPath,\n  assetsDir,\n  outputDir,\n  lintOnSave,\n  transpileDependencies,\n  devServer: {\n    hot: true,\n    port: devPort,\n    open: true,\n    noInfo: false,\n    overlay: {\n      warnings: true,\n      errors: true,\n    },\n    after: mockServer(),\n  },\n  configureWebpack() {\n    return {\n      resolve: {\n        alias: {\n          '@': resolve('src'),\n        },\n      },\n      plugins: [\n        new Webpack.ProvidePlugin(providePlugin),\n        new WebpackBar({\n          name: webpackBarName,\n        }),\n      ],\n    }\n  },\n  chainWebpack(config) {\n    config.plugins.delete('preload')\n    config.plugins.delete('prefetch')\n    config.module\n      .rule('svg')\n      .exclude.add(resolve('src/remixIcon'))\n      .add(resolve('src/colorfulIcon'))\n      .end()\n\n    config.module\n      .rule('remixIcon')\n      .test(/\\.svg$/)\n      .include.add(resolve('src/remixIcon'))\n      .end()\n      .use('svg-sprite-loader')\n      .loader('svg-sprite-loader')\n      .options({ symbolId: 'remix-icon-[name]' })\n      .end()\n\n    config.module\n      .rule('colorfulIcon')\n      .test(/\\.svg$/)\n      .include.add(resolve('src/colorfulIcon'))\n      .end()\n      .use('svg-sprite-loader')\n      .loader('svg-sprite-loader')\n      .options({ symbolId: 'colorful-icon-[name]' })\n      .end()\n\n    /*  config.when(process.env.NODE_ENV === \"development\", (config) => {\n      config.devtool(\"source-map\");\n    }); */\n    config.when(process.env.NODE_ENV !== 'development', (config) => {\n      config.performance.set('hints', false)\n      config.devtool('none')\n      config.optimization.splitChunks({\n        chunks: 'all',\n        cacheGroups: {\n          libs: {\n            name: 'chunk-libs',\n            test: /[\\\\/]node_modules[\\\\/]/,\n            priority: 10,\n            chunks: 'initial',\n          },\n          elementUI: {\n            name: 'chunk-elementUI',\n            priority: 20,\n            test: /[\\\\/]node_modules[\\\\/]_?element-ui(.*)/,\n          },\n          fortawesome: {\n            name: 'chunk-fortawesome',\n            priority: 20,\n            test: /[\\\\/]node_modules[\\\\/]_?@fortawesome(.*)/,\n          },\n        },\n      })\n      config\n        .plugin('banner')\n        .use(Webpack.BannerPlugin, [`${webpackBanner}${time}`])\n        .end()\n      config.module\n        .rule('images')\n        .use('image-webpack-loader')\n        .loader('image-webpack-loader')\n        .options({\n          bypassOnDebug: true,\n        })\n        .end()\n    })\n\n    if (build7z) {\n      config.when(process.env.NODE_ENV === 'production', (config) => {\n        config\n          .plugin('fileManager')\n          .use(FileManagerPlugin, [\n            {\n              onEnd: {\n                delete: [`./${outputDir}/video`, `./${outputDir}/data`],\n                archive: [\n                  {\n                    source: `./${outputDir}`,\n                    destination: `./${outputDir}/${abbreviation}_${outputDir}_${date}.7z`,\n                  },\n                ],\n              },\n            },\n          ])\n          .end()\n      })\n    }\n  },\n  runtimeCompiler: true,\n  productionSourceMap: false,\n  css: {\n    requireModuleExtension: true,\n    sourceMap: true,\n    loaderOptions: {\n      scss: {\n        /*sass-loader 8.0语法 */\n        //prependData: '@import \"~@/styles/variables.scss\";',\n\n        /*sass-loader 9.0写法，感谢github用户 shaonialife*/\n        additionalData(content, loaderContext) {\n          const { resourcePath, rootContext } = loaderContext\n          const relativePath = path.relative(rootContext, resourcePath)\n          if (\n            relativePath.replace(/\\\\/g, '/') !== 'src/styles/variables.scss'\n          ) {\n            return '@import \"~@/styles/variables.scss\";' + content\n          }\n          return content\n        },\n      },\n    },\n  },\n}\n"
  },
  {
    "path": "webstorm.config.js",
    "content": "'use strict'\nconst webpackConfig = require('@vue/cli-service/webpack.config.js')\nmodule.exports = webpackConfig\n"
  }
]