[
  {
    "path": ".gitattributes",
    "content": ""
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: 报告bug\ndescription: 创建一个报告，请详细描述，来帮助我们改进\ntitle: '[bug]: '\nlabels: [ \"bug\" ]\n\nbody:\n  # User's README and agreement\n  - type: markdown\n    attributes:\n      value: |\n        ## 感谢您愿意填写错误回报！\n        ## 以下是一些注意事项，请务必阅读让我们能够更容易处理\n\n        ### ❗ | 查看[文档](https://oddfar.github.io/campus-doc/campus-imaotai)里是否有解决方案 \n        ### ❗ | 确定没有相同问题的 ISSUE 已被提出. \n        ### ❗ | 如果是部署问题，在[讨论区](https://github.com/oddfar/campus-imaotai/discussions/categories/show-and-tell)里搜索是否存在相似的部署方案\n\n\n        ## 如果您不知道如何有效、精准地表述，我们建议您先阅读《提问的智慧》\n        链接: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)\n        ---\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: 请确保您已阅读以上注意事项，并勾选下方的确认框。\n      options:\n        - label: \"我已确认我已升级到最新版本（最新代码）测试过，问题依旧存在。\"\n          required: true\n        - label: \"我已经在 [Issue Tracker](https://github.com/oddfar/campus-imaotai/issues) 中找过我要提出的问题，没有找到相同问题的ISSUE。\"\n          required: true\n        - label: \"我理解并认可上述内容，并理解项目维护者精力有限，不遵循规则的 issue 可能会被无视或直接关闭\"\n          required: true\n\n  # User's data\n  - type: markdown\n    attributes:\n      value: |\n        ## 环境信息\n        请根据实际使用环境修改以下信息。\n\n  # Env | Version\n  - type: input\n    id: env-campus-version\n    attributes:\n      label: campus框架版本\n\n  - type: input\n    id: env-campus-imaotai-version\n    attributes:\n      label: 本项目 campus-imaotai 版本\n    validations:\n      required: true\n\n\n  # Env | VM Version\n  - type: dropdown\n    id: env-vm-ver\n    attributes:\n      label: 运行环境\n      description: 选择运行 campus-imaotai 的系统版本\n      options:\n        - Windows (64)\n        - Windows (32/x84)\n        - MacOS\n        - Linux\n        - Ubuntu\n        - CentOS\n        - ArchLinux\n        - UNIX (Android)\n        - 其它（请在下方说明）\n    validations:\n      required: true\n\n  # Env | VM Arch\n  - type: dropdown\n    id: env-vm-arch\n    attributes:\n      label: 运行架构\n      description: (可选) 选择运行 campus-imaotai 的系统架构\n      options:\n        - AMD64\n        - x86\n        - ARM [32] (别名：AArch32 / ARMv7）\n        - ARM [64] (别名：AArch64 / ARMv8）\n        - 其它\n\n  # Input | Reproduce\n  - type: textarea\n    id: problem-description\n    attributes:\n      label: 问题描述\n      description: |\n        1. 使用的什么功能，遇到什么问题\n        2. 返回的实际结果是什么\n    validations:\n      required: true\n\n  # Input | Expected result\n  - type: textarea\n    id: expected\n    attributes:\n      label: 期望的结果是什么？\n    validations:\n      required: true\n\n  # Optional | Reproduce code\n  - type: textarea\n    id: reproduce-code\n    attributes:\n      label: 简单的复现代码/链接（可选）\n      render: Java\n\n  # Optional | Logging\n  - type: textarea\n    id: logging\n    attributes:\n      label: 日志记录（可选）\n      description: |\n        截图或者日志记录都可以，比如 Docker 容器的日志。\n        Docker中查看容器的日志，可以使用 `docker logs container_id` 命令。\n         `container_id` 为目标容器的ID或名称\n\n  # Optional | Extra description\n  - type: textarea\n    id: extra-desc\n    attributes:\n      label: 补充说明（可选）"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: ⭐ 功能请求 —— Feature Request\nabout: 使用简练详细的语言描述希望加入的新功能\ntitle: '[Feature] 请填写标题'\nlabels: enhancement\nassignees: ''\n\n---\n\n## 例行检查\n\n+ [ ] 我已确认目前没有类似 issue\n+ [ ] 我已确认我已升级到最新版本\n+ [ ] 我理解并愿意跟进此 issue，协助测试和提供反馈\n+ [ ] 我理解并认可上述内容，并理解项目维护者精力有限，不遵循规则的 issue 可能会被无视或直接关闭\n\n## 功能请求\n<!--写一个清晰简洁的描述来表明功能或问题是什么。-->\n\n## 解决方案和应用场景\n<!-- 新功能的解决方案和应用场景。-->\n"
  },
  {
    "path": ".github/workflows/api.yml",
    "content": "name: api\non:\n  # 手动构建\n  workflow_dispatch: \n  push:\n    branches: [ \"master\",\"dev\" ]\n    tags: [ 'v*','V*' ]\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n  IMAGE_SUBNAME: api\n  WORKING_DIR: campus-modular\n\njobs:\n  java:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n      packages: write\n      id-token: write\n\n    steps:\n      - name: Checkout 🛎️\n        uses: actions/checkout@v3\n\n      - name: Set up JDK 8\n        uses: actions/setup-java@v3\n        with:\n          java-version: '8'\n          distribution: 'temurin'\n          cache: maven\n\n      - name: Build with Maven\n        working-directory: ./\n        run: mvn -B package -P prod  --file pom.xml\n        \n      - name: Login to GHCR\n        if: github.event_name != 'pull_request'\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata (tags, labels) for Docker\n        id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ env.IMAGE_SUBNAME }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=pr\n            type=semver,pattern={{version}}\n            type=semver,pattern={{major}}.{{minor}}\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n     \n      - name: Build and push\n        uses: docker/build-push-action@v5\n        with:\n          context: ${{ env.WORKING_DIR }}\n          push: ${{ github.event_name != 'pull_request' }}\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n          platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: release\non:\n  # 手动构建\n  workflow_dispatch: \n    inputs:\n        version_name:\n          description: '版本号'\n          required: false\n          type: string\n\nenv:\n  API_WORKING_DIR: campus-modular\n  WEB_WORKING_DIR: vue_campus_admin\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n\n    defaults:\n      run:\n        working-directory: ./\n\n    permissions: write-all\n\n    strategy:\n      matrix:\n        node-version: [16.x]\n\n    steps:\n      - name: Checkout 🛎️\n        uses: actions/checkout@v3\n\n      #--------------------构建jar--------------------\n      - name: Set up JDK 8\n        uses: actions/setup-java@v3\n        with:\n          java-version: '8'\n          distribution: 'temurin'\n          cache: maven\n\n      - name: Build with Maven\n        working-directory: ./\n        run: mvn -B package -P prod  --file pom.xml\n\n      # 设置 Maven pom 版本环境变量\n      - name: Set Release version env variable\n        run: |\n          if [ ${{ github.event.inputs.version_name }} != \"\" ]; then\n            echo \"RELEASE_VERSION=${{ github.event.inputs.version_name }}\" >> $GITHUB_ENV\n          else\n            # 获取maven项目里的版本号\n            echo \"RELEASE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)\" >> $GITHUB_ENV\n          fi\n          \n      - name: Remane jar\n        working-directory: ${{ env.API_WORKING_DIR }}\n        run: |\n          cd target\n          # rename 's/(.*)\\.jar/$1_${{ env.RELEASE_VERSION }}.jar/' *.jar\n          for file in *.jar; do mv \"$file\" \"${file%.jar}_${{ env.RELEASE_VERSION }}.jar\"; done\n      #--------------------构建前端--------------------\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Cache ☕\n        id: cache\n        uses: actions/cache@v3\n        with:\n          path: vue_campus_admin/node_modules\n          key: ${{runner.os}}-npm-caches-${{ hashfiles('package-lock.json') }}\n\n      - name: Install 🔧\n        working-directory: ${{ env.WEB_WORKING_DIR }}\n        if: steps.cache.outputs.cache-hit != 'true'\n        run: npm install\n\n      - name: Build 🔧\n        working-directory: ${{ env.WEB_WORKING_DIR }}\n        run: |\n          npm run build:prod\n\n      - name: Tar dist file\n        working-directory: ${{ env.WEB_WORKING_DIR }}\n        run: |\n          tar -czvf dist_${{ env.RELEASE_VERSION }}.tar.gz dist\n\n      \n      # 上传文件并发布 Release\n      - name: Create GitHub release\n        uses: marvinpinto/action-automatic-releases@latest\n        with:\n          repo_token: \"${{ secrets.GITHUB_TOKEN }}\"\n          automatic_release_tag: \"v${{ env.RELEASE_VERSION }}\"\n          prerelease: false\n          title: \"v${{ env.RELEASE_VERSION }}\"\n          files: |\n            ${{ env.API_WORKING_DIR }}/target/*.jar\n            ${{ env.WEB_WORKING_DIR }}/dist_*.tar.gz\n"
  },
  {
    "path": ".github/workflows/web.yml",
    "content": "name: web\non:\n  # 手动构建\n  workflow_dispatch:\n  push:\n    branches: [ \"master\",\"dev\" ]\n    tags: [ 'v*','V*' ]\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n  IMAGE_SUBNAME: web\n  WORKING_DIR: vue_campus_admin\n\njobs:\n  node:\n\n    runs-on: ubuntu-latest\n\n    defaults:\n      run:\n        working-directory: ${{ env.WORKING_DIR }}\n\n    permissions:\n      contents: read\n      packages: write\n      id-token: write\n\n    strategy:\n      matrix:\n        node-version: [16.x]\n\n    steps:\n      - name: Checkout 🛎️\n        uses: actions/checkout@v3\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Cache ☕\n        id: cache\n        uses: actions/cache@v3\n        with:\n          path: vue_campus_admin/node_modules\n          key: ${{runner.os}}-npm-caches-${{ hashfiles('package-lock.json') }}\n\n      - name: Install 🔧\n        if: steps.cache.outputs.cache-hit != 'true'\n        run: npm install\n\n      - name: Build 🔧\n        run: |\n          npm run build:prod\n\n      - name: Login to GHCR\n        if: github.event_name != 'pull_request'\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata (tags, labels) for Docker\n        id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ env.IMAGE_SUBNAME }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=pr\n            type=semver,pattern={{version}}\n            type=semver,pattern={{major}}.{{minor}}\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n\n      - name: CP Docker conf\n        run: |\n           cp -r ../doc doc\n\n      - name: Build and push\n        uses: docker/build-push-action@v5\n        with:\n          context: ${{ env.WORKING_DIR }}\n          push: ${{ github.event_name != 'pull_request' }}\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n          platforms: linux/386,linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le,linux/s390x"
  },
  {
    "path": ".gitignore",
    "content": "HELP.md\n\n*.log\n\n# Build Tools\ntarget/\n!.mvn/wrapper/maven-wrapper.jar\n!**/src/main/**/target/\n!**/src/test/**/target/\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n.sts4-cache\n\n### IntelliJ IDEA ###\n.idea\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\n/nbproject/private/\n/nbbuild/\n/dist/\n/nbdist/\n/.nb-gradle/\nbuild/\n!**/src/main/**/build/\n!**/src/test/**/build/\n\n### VS Code ###\n.vscode/\n\n~/\n### mac ###\n.DS_Store\n\n.flattened-pom.xml"
  },
  {
    "path": ".run/campus-server.run.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"campus-server\" type=\"docker-deploy\" factoryName=\"dockerfile\" server-name=\"Docker\">\n    <deployment type=\"dockerfile\">\n      <settings>\n        <option name=\"imageTag\" value=\"campus/campus-imaotai:1.0.13\" />\n        <option name=\"buildOnly\" value=\"true\" />\n        <option name=\"sourceFilePath\" value=\"campus-modular/Dockerfile\" />\n      </settings>\n    </deployment>\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><a href=\"https://oddfar.com/\" target=\"_blank\" rel=\"noopener noreferrer\"><img width=\"180\" src=\"https://note.oddfar.com/img/web.png\" alt=\"logo\"></a></p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/oddfar/campus-imaotai/stargazers\"><img src=\"https://img.shields.io/github/stars/oddfar/campus-imaotai.svg\"></a>\n\t<a href=\"https://github.com/oddfar/campus-imaotai/blob/master/LICENSE\"><img src=\"https://img.shields.io/github/license/oddfar/campus-imaotai.svg\"></a>\n</p>\n\n<p align=\"center\"> i茅台app自动预约，每日自动预约，支持docker一键部署</p>\n\n<h2 align=\"center\">Campus-imaotai</h2>\n\n  [笔记仓库](https://github.com/oddfar/notes)  |  [我的博客](https://oddfar.com)  \n\n## 项目介绍\n\ni茅台app，每日自动预约茅台\n\n- [x] 平台注册账号\n- [x] 添加多个用户\n- [x] 自动预约\n- [x] 类型选择（本市出货量最大的门店，或位置附近门店）\n- [x] 自动旅行\n- [x] 首次旅行分享\n- [x] 获取申购耐力值\n- [x] 自定义时间/随机时间预约或旅行\n- [x] 申购结果消息推送\n\n此项目使用 **Campus** 进行编写：<https://github.com/oddfar/campus>\n\n## 文档\n\n- 文档：https://oddfar.github.io/campus-doc/campus-imaotai\n\n- 视频：https://www.bilibili.com/video/BV1dj411H7oT\n\n\n## 演示图\n\n\n\n| i茅台预约                                                    |                                                              |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| ![image-20230707144241399](https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/image-20230707144241399.png) | ![image-20230707144404638](https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/image-20230707144404638.png) |\n|                                                              |                                                              |\n| ![image-20230707144703842](https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/image-20230707144703842.png) | ![image-20230707145525709](https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/image-20230707145525709.png) |\n\n\n\n## 贡献代码\n\n若您有好的想法，发现一些 **BUG** 并修复了，欢迎提交 **Pull Request** 参与开源贡献\n\n发起 pull request 请求，提交到 master 分支，等待作者合并\n\n**感谢为这个项目贡献代码的朋友**\n\n<a href=\"https://github.com/oddfar/campus-imaotai/graphs/contributors\">\n<img src=\"https://contrib.rocks/image?repo=oddfar/campus-imaotai\" />\n</a>\n\n## star 趋势图\n\n![Stargazers over time](https://starchart.cc/oddfar/campus-imaotai.svg)\n\n## 友情链接\n\n- 本项目其他版\n\n  C#：<https://github.com/lisongkun/hygge-imaotai>\n\n- 葫芦娃项目\n\n  yize8888-maotai：https://github.com/yize8888/maotai\n\n## 声明\n\n- 本项目涉及的数据由使用的个人或组织自行填写，本项目不对数据内容负责，包括但不限于数据的真实性、准确性、合法性。使用本项目所造成的一切后果，与本项目的所有贡献者无关，由使用的个人或组织完全承担。\n- 本项目中涉及的第三方硬件、软件等，与本项目没有任何直接或间接的关系。本项目仅对部署和使用过程进行客观描述，不代表支持使用任何第三方硬件、软件。使用任何第三方硬件、软件，所造成的一切后果由使用的个人或组织承担，与本项目无关。\n- 本项目中所有内容只供学习和研究使用，不得将本项目中任何内容用于违反国家/地区/组织等的法律法规或相关规定的其他用途。\n- 所有基于本项目源代码，进行的任何修改，为其他个人或组织的自发行为，与本项目没有任何直接或间接的关系，所造成的一切后果亦与本项目无关。\n- 所有直接或间接使用本项目的个人和组织，应24小时内完成学习和研究，并及时删除本项目中的所有内容。如对本项目的功能有需求，应自行开发相关功能。\n- 本项目保留随时对免责声明进行补充或更改的权利，直接或间接使用本项目内容的个人或组织，视为接受本项目的特别声明。\n\n## 鸣谢\n\n> [IntelliJ IDEA](https://zh.wikipedia.org/zh-hans/IntelliJ_IDEA) 是一个在各个方面都最大程度地提高开发人员的生产力的 IDE，适用于 JVM 平台语言。\n\n特别感谢 [JetBrains](https://www.jetbrains.com/?from=campus) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=campus) 等 IDE 的授权    \n[<img src=\"https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/jetbrains-variant.png\" width=\"200\"/>](https://www.jetbrains.com/?from=campus)\n\n\n[<img src=\"https://api.gitsponsors.com/api/badge/img?id=663458108\" height=\"20\">](https://api.gitsponsors.com/api/badge/link?p=B5hGpnclTmBJ3UZkEvdPGMFIr6mP8gfAX5zs5E7fvFS73izvYiiomLvUJSqDJ1PcIBTj9m7RAfJ+YSorEaTfDOXnqL5OsV3x5taooWf6/7j78cFmcDrgKyQYgKj41O17r5xkL3Nfi2mfpMBe5pGLpg==)\n\n"
  },
  {
    "path": "campus-admin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>campus</artifactId>\n        <groupId>com.oddfar.campus</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>campus-admin</artifactId>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>com.oddfar.campus</groupId>\n            <artifactId>campus-framework</artifactId>\n        </dependency>\n        <!-- swagger3-->\n        <dependency>\n            <groupId>io.springfox</groupId>\n            <artifactId>springfox-boot-starter</artifactId>\n        </dependency>\n        <!-- 防止进入swagger页面报类型转换错误，排除3.0.0中的引用，手动增加1.6.2版本 -->\n        <dependency>\n            <groupId>io.swagger</groupId>\n            <artifactId>swagger-models</artifactId>\n            <version>1.6.2</version>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/config/CaptchaConfig.java",
    "content": "package com.oddfar.campus.admin.config;\n\nimport com.google.code.kaptcha.impl.DefaultKaptcha;\nimport com.google.code.kaptcha.util.Config;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.util.Properties;\n\nimport static com.google.code.kaptcha.Constants.*;\n\n/**\n * 验证码配置\n *\n * @author ruoyi\n */\n@Configuration\npublic class CaptchaConfig {\n    @Bean(name = \"captchaProducer\")\n    public DefaultKaptcha getKaptchaBean() {\n        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();\n        Properties properties = new Properties();\n        // 是否有边框 默认为true 我们可以自己设置yes，no\n        properties.setProperty(KAPTCHA_BORDER, \"yes\");\n        // 验证码文本字符颜色 默认为Color.BLACK\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, \"black\");\n        // 验证码图片宽度 默认为200\n        properties.setProperty(KAPTCHA_IMAGE_WIDTH, \"160\");\n        // 验证码图片高度 默认为50\n        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, \"60\");\n        // 验证码文本字符大小 默认为40\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, \"38\");\n        // KAPTCHA_SESSION_KEY\n        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, \"kaptchaCode\");\n        // 验证码文本字符长度 默认为5\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, \"4\");\n        // 验证码文本字体样式 默认为new Font(\"Arial\", 1, fontSize), new Font(\"Courier\", 1, fontSize)\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, \"Arial,Courier\");\n        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy\n        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, \"com.google.code.kaptcha.impl.ShadowGimpy\");\n        Config config = new Config(properties);\n        defaultKaptcha.setConfig(config);\n        return defaultKaptcha;\n    }\n\n    @Bean(name = \"captchaProducerMath\")\n    public DefaultKaptcha getKaptchaBeanMath() {\n        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();\n        Properties properties = new Properties();\n        // 是否有边框 默认为true 我们可以自己设置yes，no\n        properties.setProperty(KAPTCHA_BORDER, \"yes\");\n        // 边框颜色 默认为Color.BLACK\n        properties.setProperty(KAPTCHA_BORDER_COLOR, \"105,179,90\");\n        // 验证码文本字符颜色 默认为Color.BLACK\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, \"blue\");\n        // 验证码图片宽度 默认为200\n        properties.setProperty(KAPTCHA_IMAGE_WIDTH, \"160\");\n        // 验证码图片高度 默认为50\n        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, \"60\");\n        // 验证码文本字符大小 默认为40\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, \"35\");\n        // KAPTCHA_SESSION_KEY\n        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, \"kaptchaCodeMath\");\n        // 验证码文本生成器\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, \"com.oddfar.campus.framework.config.KaptchaTextCreator\");\n        // 验证码文本字符间距 默认为2\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, \"3\");\n        // 验证码文本字符长度 默认为5\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, \"6\");\n        // 验证码文本字体样式 默认为new Font(\"Arial\", 1, fontSize), new Font(\"Courier\", 1, fontSize)\n        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, \"Arial,Courier\");\n        // 验证码噪点颜色 默认为Color.BLACK\n        properties.setProperty(KAPTCHA_NOISE_COLOR, \"white\");\n        // 干扰实现类\n        properties.setProperty(KAPTCHA_NOISE_IMPL, \"com.google.code.kaptcha.impl.NoNoise\");\n        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy\n        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, \"com.google.code.kaptcha.impl.ShadowGimpy\");\n        Config config = new Config(properties);\n        defaultKaptcha.setConfig(config);\n        return defaultKaptcha;\n    }\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/config/SwaggerConfig.java",
    "content": "package com.oddfar.campus.admin.config;\n\n\nimport io.swagger.models.auth.In;\nimport io.swagger.v3.oas.annotations.Operation;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport springfox.documentation.builders.ApiInfoBuilder;\nimport springfox.documentation.builders.PathSelectors;\nimport springfox.documentation.builders.RequestHandlerSelectors;\nimport springfox.documentation.service.*;\nimport springfox.documentation.spi.DocumentationType;\nimport springfox.documentation.spi.service.contexts.SecurityContext;\nimport springfox.documentation.spring.web.plugins.Docket;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Swagger2的接口配置\n *\n * @author ruoyi\n */\n@Configuration\npublic class SwaggerConfig {\n\n    /**\n     * 是否开启swagger\n     */\n    @Value(\"${swagger.enabled}\")\n    private boolean enabled;\n\n    /**\n     * 版本情况\n     */\n    @Value(\"${campus.version}\")\n    private String version;\n\n    /**\n     * 创建API\n     */\n    @Bean\n    public Docket createRestApi() {\n        return new Docket(DocumentationType.OAS_30)\n                // 是否启用Swagger\n                .enable(enabled)\n                // 用来创建该API的基本信息，展示在文档的页面中（自定义展示的信息）\n                .apiInfo(apiInfo())\n                // 设置哪些接口暴露给Swagger展示\n                .select()\n                // 扫描所有有注解的api，用这种方式更灵活\n                .apis(RequestHandlerSelectors.withMethodAnnotation(Operation.class))\n                // 扫描指定包中的swagger注解\n                // .apis(RequestHandlerSelectors.basePackage(\"com.ruoyi.project.tool.swagger\"))\n                // 扫描所有 .apis(RequestHandlerSelectors.any())\n                .paths(PathSelectors.any())\n                .build()\n                /* 设置安全模式，swagger可以设置访问token */\n                .securitySchemes(securitySchemes())\n                .securityContexts(securityContexts());\n    }\n\n    /**\n     * 安全模式，这里指定token通过Authorization头请求头传递\n     */\n    private List<SecurityScheme> securitySchemes() {\n        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();\n        apiKeyList.add(new ApiKey(\"Authorization\", \"Authorization\", In.HEADER.toValue()));\n        return apiKeyList;\n    }\n\n    /**\n     * 安全上下文\n     */\n    private List<SecurityContext> securityContexts() {\n        List<SecurityContext> securityContexts = new ArrayList<>();\n        securityContexts.add(\n                SecurityContext.builder()\n                        .securityReferences(defaultAuth())\n                        .operationSelector(o -> o.requestMappingPattern().matches(\"/.*\"))\n                        .build());\n        return securityContexts;\n    }\n\n    /**\n     * 默认的安全上引用\n     */\n    private List<SecurityReference> defaultAuth() {\n        AuthorizationScope authorizationScope = new AuthorizationScope(\"global\", \"accessEverything\");\n        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];\n        authorizationScopes[0] = authorizationScope;\n        List<SecurityReference> securityReferences = new ArrayList<>();\n        securityReferences.add(new SecurityReference(\"Authorization\", authorizationScopes));\n        return securityReferences;\n    }\n\n    /**\n     * 添加摘要信息\n     */\n    private ApiInfo apiInfo() {\n        // 用ApiInfoBuilder进行定制\n        return new ApiInfoBuilder()\n                // 设置标题\n                .title(\"标题：Campus_接口文档\")\n                // 描述\n                .description(\"描述：Campus的接口文档列表\")\n                // 作者信息\n                .contact(new Contact(\"oddfar\", null, null))\n                // 版本\n                .version(\"版本号:\" + version)\n                .build();\n    }\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/monitor/SysLogininforController.java",
    "content": "package com.oddfar.campus.admin.controller.monitor;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.annotation.Log;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysLoginLogEntity;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.framework.service.SysLoginLogService;\nimport com.oddfar.campus.framework.web.service.SysPasswordService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\n\n/**\n * 系统访问记录\n *\n * @author ruoyi\n */\n@RestController\n@RequestMapping(\"/monitor/logininfor\")\n@Log(openLog = false)\n@ApiResource(name = \"登录日志管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysLogininforController {\n    @Autowired\n    private SysLoginLogService logininforService;\n\n    @Autowired\n    private SysPasswordService passwordService;\n\n    @PreAuthorize(\"@ss.hasPermi('monitor:logininfor:list')\")\n    @GetMapping(value = \"/list\",name = \"登录日志-分类列表\")\n    public R list(SysLoginLogEntity logininfor) {\n        PageResult<SysLoginLogEntity> page = logininforService.selectLogininforPage(logininfor);\n        return R.ok().put(page);\n    }\n\n    @PreAuthorize(\"@ss.hasPermi('monitor:logininfor:remove')\")\n    @DeleteMapping(value = \"/{infoIds}\",name = \"登录日志-删除\")\n    public R remove(@PathVariable Long[] infoIds) {\n        return R.ok(logininforService.deleteLogininforByIds(infoIds));\n    }\n\n    @PreAuthorize(\"@ss.hasPermi('monitor:logininfor:remove')\")\n    @DeleteMapping(value = \"/clean\",name = \"登录日志-清空\")\n    public R clean() {\n        logininforService.cleanLogininfor();\n        return R.ok();\n    }\n\n    @PreAuthorize(\"@ss.hasPermi('monitor:logininfor:unlock')\")\n    @GetMapping(value = \"/unlock/{userName}\",name = \"登录日志-解锁\")\n    public R unlock(@PathVariable(\"userName\") String userName) {\n        passwordService.clearLoginRecordCache(userName);\n        return R.ok();\n    }\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/monitor/SysOperlogController.java",
    "content": "package com.oddfar.campus.admin.controller.monitor;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.annotation.Log;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysOperLogEntity;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.framework.service.SysOperLogService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * 操作日志记录\n */\n@RestController\n@RequestMapping(\"/monitor/operlog\")\n@Log(openLog = false)\n@ApiResource(name = \"操作日志管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysOperlogController {\n\n    @Autowired\n    private SysOperLogService operLogService;\n\n    @PreAuthorize(\"@ss.hasPermi('monitor:operlog:list')\")\n    @GetMapping(value = \"/list\", name = \"操作日志-分页\")\n    public R list(SysOperLogEntity operLog) {\n        PageResult<SysOperLogEntity> page = operLogService.selectOperLogPage(operLog);\n        return R.ok().put(page);\n    }\n\n\n    @PreAuthorize(\"@ss.hasPermi('monitor:operlog:remove')\")\n    @DeleteMapping(value = \"/{operIds}\", name = \"操作日志-删除\")\n    public R remove(@PathVariable Long[] operIds) {\n        return R.ok(operLogService.deleteOperLogByIds(operIds));\n    }\n\n    @PreAuthorize(\"@ss.hasPermi('monitor:operlog:remove')\")\n    @DeleteMapping(value = \"/clean\", name = \"操作日志-清空\")\n    public R clean() {\n        operLogService.cleanOperLog();\n        return R.ok();\n    }\n\n    /**\n     * @author jamin\n     * 接下来要改造的是，将日志系统升级为elk 2025-03-21\n     */\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/CaptchaController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport cn.hutool.core.codec.Base64;\nimport com.google.code.kaptcha.Producer;\nimport com.oddfar.campus.common.annotation.Log;\nimport com.oddfar.campus.common.constant.CacheConstants;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.utils.uuid.IdUtils;\nimport com.oddfar.campus.framework.api.sysconfig.ConfigExpander;\nimport com.oddfar.campus.framework.service.SysConfigService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.util.FastByteArrayOutputStream;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.annotation.Resource;\nimport javax.imageio.ImageIO;\nimport javax.servlet.http.HttpServletResponse;\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 验证码操作处理\n *\n * @author ruoyi\n */\n@RestController\n@Log(openLog = false)\npublic class CaptchaController {\n    @Resource(name = \"captchaProducer\")\n    private Producer captchaProducer;\n\n    @Resource(name = \"captchaProducerMath\")\n    private Producer captchaProducerMath;\n\n    @Autowired\n    private RedisCache redisCache;\n    @Autowired\n    private SysConfigService configService;\n\n    /**\n     * 生成验证码\n     */\n    @GetMapping(value = \"/captchaImage\", name = \"生产验证码\")\n    public R getCode(HttpServletResponse response) throws IOException {\n        R ajax = R.ok();\n        boolean captchaEnabled = configService.selectCaptchaEnabled();\n        ajax.put(\"captchaEnabled\", captchaEnabled);\n        if (!captchaEnabled) {\n            return ajax;\n        }\n\n        // 保存验证码信息\n        String uuid = IdUtils.simpleUUID();\n        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;\n\n        String capStr = null, code = null;\n        BufferedImage image = null;\n\n        String captchaType = ConfigExpander.getLoginCaptchaType();\n        if (\"math\".equals(captchaType)) {\n            String capText = captchaProducerMath.createText();\n            capStr = capText.substring(0, capText.lastIndexOf(\"@\"));\n            code = capText.substring(capText.lastIndexOf(\"@\") + 1);\n            image = captchaProducerMath.createImage(capStr);\n        } else if (\"char\".equals(captchaType)) {\n            capStr = code = captchaProducer.createText();\n            image = captchaProducer.createImage(capStr);\n        }\n\n        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);\n        // 转换流信息写出\n        FastByteArrayOutputStream os = new FastByteArrayOutputStream();\n        try {\n            ImageIO.write(image, \"jpg\", os);\n        } catch (IOException e) {\n            return R.error(e.getMessage());\n        }\n\n        ajax.put(\"uuid\", uuid);\n        ajax.put(\"img\", Base64.encode(os.toByteArray()));\n        return ajax;\n    }\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysApiResourceController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.framework.service.SysResourceService;\nimport com.oddfar.campus.framework.web.service.SysPermissionService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\nimport static com.oddfar.campus.common.utils.SecurityUtils.getUserId;\n\n@RestController\n@RequestMapping(\"/system/resource\")\n@ApiResource(name = \"资源管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysApiResourceController {\n    @Autowired\n    private SysResourceService resourceService;\n    @Autowired\n    private SysPermissionService permissionService;\n\n    /**\n     * 加载对应角色资源列表树\n     */\n    @GetMapping(value = \"/roleApiTreeselect/{roleId}\", name = \"资源管理-加载对应角色资源列表树\")\n    public R roleMenuTreeSelect(@PathVariable(\"roleId\") Long roleId) {\n        List<SysResourceEntity> resources = resourceService.selectApiResourceList(getUserId());\n        R ajax = R.ok();\n        ajax.put(\"checkedKeys\", resourceService.selectResourceListByRoleId(roleId));\n        ajax.put(\"resources\", resourceService.buildResourceTreeSelect(resources));\n        return ajax;\n    }\n\n    /**\n     * 修改对应角色api资源\n     */\n    @PutMapping(value = \"/roleApi\", name = \"修改对应角色api资源\")\n    public R editRoleResource(Long roleId, Long[] resourceIds) {\n        resourceService.editRoleResource(roleId, resourceIds);\n        //更新redis缓存权限数据\n        permissionService.resetLoginUserRoleCache(roleId);\n        return R.ok();\n    }\n\n\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysConfigController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysConfigEntity;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.framework.service.SysConfigService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * 配置管理\n */\n@RestController\n@RequestMapping(\"/system/config\")\n@ApiResource(name = \"参数配置管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysConfigController {\n    @Autowired\n    private SysConfigService configService;\n\n    @GetMapping(value = \"page\", name = \"参数配置管理-分页\")\n    @PreAuthorize(\"@ss.hasPermi('system:config:list')\")\n    public R page(SysConfigEntity sysConfigEntity) {\n        PageResult<SysConfigEntity> page = configService.page(sysConfigEntity);\n\n        return R.ok().put(page);\n    }\n\n    @GetMapping(value = \"{id}\", name = \"参数配置管理-查询id信息\")\n    @PreAuthorize(\"@ss.hasPermi('system:config:query')\")\n    public R getInfo(@PathVariable(\"id\") Long id) {\n        SysConfigEntity entity = configService.selectConfigById(id);\n\n        return R.ok().put(entity);\n    }\n\n    /**\n     * 根据参数键名查询参数值\n     */\n    @GetMapping(value = \"/configKey/{configKey:.+}\")\n    public R getConfigKey(@PathVariable String configKey) {\n        return R.ok(configService.selectConfigByKey(configKey));\n    }\n\n    @PostMapping(name = \"参数配置管理-新增\")\n    @PreAuthorize(\"@ss.hasPermi('system:config:add')\")\n    public R add(@Validated @RequestBody SysConfigEntity config) {\n        if (!configService.checkConfigKeyUnique(config)) {\n            return R.error(\"新增参数'\" + config.getConfigName() + \"'失败，参数键名已存在\");\n        }\n        return R.ok(configService.insertConfig(config));\n\n    }\n\n    @PutMapping(name = \"参数配置管理-修改\")\n    @PreAuthorize(\"@ss.hasPermi('system:config:edit')\")\n    public R edit(@Validated @RequestBody SysConfigEntity config) {\n        if (!configService.checkConfigKeyUnique(config)) {\n            return R.error(\"修改参数'\" + config.getConfigName() + \"'失败，参数键名已存在\");\n        }\n        return R.ok(configService.updateConfig(config));\n\n    }\n\n    @DeleteMapping(value = \"/{configIds}\", name = \"参数配置管理-删除\")\n    @PreAuthorize(\"@ss.hasPermi('system:config:remove')\")\n    public R remove(@PathVariable Long[] configIds) {\n        configService.deleteConfigByIds(configIds);\n\n        return R.ok();\n    }\n\n    /**\n     * 刷新参数缓存\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:config:remove')\")\n    @DeleteMapping(value = \"/refreshCache\", name = \"参数配置管理-刷新缓存\")\n    public R refreshCache() {\n        configService.resetConfigCache();\n        return R.ok();\n    }\n}"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysDictDataController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.Anonymous;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysDictDataEntity;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.framework.service.SysDictDataService;\nimport com.oddfar.campus.framework.service.SysDictTypeService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.util.StringUtils;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@RestController\n@RequestMapping(\"/system/dict/data\")\n@ApiResource(name = \"字典数据管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysDictDataController {\n    @Autowired\n    private SysDictDataService dictDataService;\n    @Autowired\n    private SysDictTypeService dictTypeService;\n\n\n    @PreAuthorize(\"@ss.hasPermi('system:dict:list')\")\n    @GetMapping(value = \"/list\", name = \"字典数据管理-分页\")\n    public R page(SysDictDataEntity dictData) {\n        PageResult<SysDictDataEntity> page = dictDataService.page(dictData);\n        return R.ok().put(page);\n    }\n\n    /**\n     * 根据字典类型查询字典数据信息\n     */\n    @GetMapping(value = \"/type/{dictType}\", name = \"字典数据管理-根据字典类型查询字典数据信息\")\n    @Anonymous\n    public R dictType(@PathVariable String dictType) {\n\n        List<SysDictDataEntity> data = dictTypeService.selectDictDataByType(dictType);\n        if (StringUtils.isEmpty(data)) {\n            data = new ArrayList<SysDictDataEntity>();\n        }\n        return R.ok().put(data);\n    }\n\n\n    /**\n     * 查询字典数据详细\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:query')\")\n    @GetMapping(value = \"/{dictCode}\", name = \"字典数据管理-查询\")\n    public R getInfo(@PathVariable Long dictCode) {\n        return R.ok(dictDataService.selectDictDataById(dictCode));\n    }\n\n\n    /**\n     * 新增字典类型\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:add')\")\n    @PostMapping(name = \"字典数据管理-新增\")\n    public R add(@Validated @RequestBody SysDictDataEntity dict) {\n        return R.ok(dictDataService.insertDictData(dict));\n    }\n\n    /**\n     * 修改保存字典类型\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:edit')\")\n    @PutMapping(name = \"字典数据管理-修改\")\n    public R edit(@Validated @RequestBody SysDictDataEntity dict) {\n        return R.ok(dictDataService.updateDictData(dict));\n    }\n\n    /**\n     * 删除字典类型\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:remove')\")\n    @DeleteMapping(value = \"/{dictCodes}\", name = \"字典数据管理-删除\")\n    public R remove(@PathVariable Long[] dictCodes) {\n        dictDataService.deleteDictDataByIds(dictCodes);\n        return R.ok();\n    }\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysDictTypeController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysDictTypeEntity;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.framework.service.SysDictTypeService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n\n@RestController\n@RequestMapping(\"/system/dict/type\")\n@ApiResource(name = \"字典类型管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysDictTypeController {\n\n    @Autowired\n    private SysDictTypeService dictTypeService;\n\n    @PreAuthorize(\"@ss.hasPermi('system:dict:list')\")\n    @GetMapping(value = \"/list\", name = \"字典类型管理-分页\")\n    public R list(SysDictTypeEntity sysDictTypeEntity) {\n        PageResult<SysDictTypeEntity> page = dictTypeService.page(sysDictTypeEntity);\n        return R.ok().put(page);\n    }\n\n    /**\n     * 查询字典类型详细\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:query')\")\n    @GetMapping(value = \"/{dictId}\", name = \"字典类型管理-查询\")\n    public R getInfo(@PathVariable Long dictId) {\n        return R.ok(dictTypeService.selectDictTypeById(dictId));\n    }\n\n    /**\n     * 新增字典类型\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:add')\")\n    @PostMapping(name = \"字典类型管理-新增\")\n    public R add(@Validated @RequestBody SysDictTypeEntity dict) {\n        if (!dictTypeService.checkDictTypeUnique(dict)) {\n            return R.error(\"新增字典'\" + dict.getDictName() + \"'失败，字典类型已存在\");\n        }\n\n        return R.ok(dictTypeService.insertDictType(dict));\n    }\n\n    /**\n     * 修改字典类型\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:edit')\")\n    @PutMapping(name = \"字典类型管理-修改\")\n    public R edit(@Validated @RequestBody SysDictTypeEntity dict) {\n        if (!dictTypeService.checkDictTypeUnique(dict)) {\n            return R.error(\"修改字典'\" + dict.getDictName() + \"'失败，字典类型已存在\");\n        }\n        return R.ok(dictTypeService.updateDictType(dict));\n    }\n\n    /**\n     * 删除字典类型\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:remove')\")\n    @DeleteMapping(value = \"/{dictIds}\", name = \"字典类型管理-删除\")\n    public R remove(@PathVariable Long[] dictIds) {\n        dictTypeService.deleteDictTypeByIds(dictIds);\n        return R.ok();\n    }\n\n    /**\n     * 刷新字典缓存\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:dict:remove')\")\n    @DeleteMapping(value = \"/refreshCache\", name = \"字典类型管理-刷新\")\n    public R refreshCache() {\n        dictTypeService.resetDictCache();\n        return R.ok();\n    }\n\n    /**\n     * 获取字典选择框列表\n     */\n    @GetMapping(value = \"/optionselect\", name = \"字典类型管理-获取字典选择框列表\")\n    public R optionselect() {\n        List<SysDictTypeEntity> dictTypes = dictTypeService.selectDictTypeAll();\n        return R.ok(dictTypes);\n    }\n\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysIndexController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.config.CampusConfig;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.HashMap;\n\n/**\n * 首页\n *\n * @author oddfar\n */\n@RestController\npublic class SysIndexController {\n    /**\n     * 系统基础配置\n     */\n    @Autowired\n    private CampusConfig campusConfig;\n\n    /**\n     * 访问首页，提示语\n     */\n    @RequestMapping(\"/\")\n    public String index() {\n        return StringUtils.format(\"欢迎使用{}后台管理框架，当前版本：v{}，请通过前端地址访问。\", campusConfig.getName(), campusConfig.getVersion());\n    }\n\n    @Value(\"${campus.version}\")\n    private String version;\n\n    @Value(\"${campus.frameworkVersion}\")\n    private String frameworkVersion;\n\n    /**\n     * 版本情况\n     */\n    @RequestMapping(\"/version\")\n    public R version() {\n        HashMap<String, String> map = new HashMap<>();\n        map.put(\"version\", version);\n        map.put(\"frameworkVersion\", frameworkVersion);\n        return R.ok(map);\n    }\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysLoginController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysMenuEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.LoginBody;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.framework.service.SysMenuService;\nimport com.oddfar.campus.framework.web.service.SysLoginService;\nimport com.oddfar.campus.framework.web.service.SysPermissionService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\nimport java.util.Set;\n\n@RestController\n@RequestMapping\n@ApiResource(name = \"登录路由\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysLoginController {\n\n    @Autowired\n    private SysMenuService menuService;\n    @Autowired\n    private SysLoginService loginService;\n\n    @Autowired\n    private SysPermissionService permissionService;\n\n    /**\n     * 登录方法\n     *\n     * @param loginBody 登录信息\n     * @return 结果\n     */\n    @PostMapping(value = \"/login\", name = \"登录方法\")\n    public R login(@RequestBody LoginBody loginBody) {\n        R r = R.ok();\n        // 生成令牌\n        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),\n                loginBody.getUuid());\n        r.put(Constants.TOKEN, token);\n        return r;\n    }\n\n    /**\n     * 获取用户信息\n     *\n     * @return 用户信息\n     */\n    @GetMapping(value = \"getInfo\", name = \"获取用户信息\")\n    public R getInfo() {\n        SysUserEntity user = SecurityUtils.getLoginUser().getUser();\n        // 角色集合\n        Set<String> roles = permissionService.getRolePermission(user);\n        // 权限集合\n        Set<String> permissions = permissionService.getMenuPermission(user);\n        R ajax = R.ok();\n        ajax.put(\"user\", user);\n        ajax.put(\"roles\", roles);\n        ajax.put(\"permissions\", permissions);\n        return ajax;\n    }\n\n    /**\n     * 获取路由信息\n     *\n     * @return 路由信息\n     */\n    @GetMapping(value = \"getRouters\", name = \"获取路由信息\")\n    public R getRouters() {\n        Long userId = SecurityUtils.getUserId();\n        List<SysMenuEntity> menus = menuService.selectMenuTreeByUserId(userId);\n        return R.ok(menuService.buildMenus(menus));\n    }\n\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysMenuController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.constant.UserConstants;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysMenuEntity;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.service.SysMenuService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\nimport static com.oddfar.campus.common.utils.SecurityUtils.getUserId;\n\n@RestController\n@RequestMapping(\"/system/menu\")\n@ApiResource(name = \"菜单管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysMenuController {\n\n    @Autowired\n    private SysMenuService menuService;\n\n    /**\n     * 获取菜单列表\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:menu:list')\")\n    @GetMapping(value = \"/list\", name = \"菜单管理-分页\")\n    public R list(SysMenuEntity menu) {\n        List<SysMenuEntity> menus = menuService.selectMenuList(menu, getUserId());\n        return R.ok(menus);\n    }\n\n    /**\n     * 根据菜单编号获取详细信息\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:menu:query')\")\n    @GetMapping(value = \"/{menuId}\", name = \"菜单管理-查询\")\n    public R getInfo(@PathVariable Long menuId) {\n        return R.ok(menuService.selectMenuById(menuId));\n    }\n\n    /**\n     * 获取菜单下拉树列表\n     */\n    @GetMapping(value = \"/treeselect\", name = \"菜单管理-获取菜单下拉树列表\")\n    public R treeSelect(SysMenuEntity menu) {\n        List<SysMenuEntity> menus = menuService.selectMenuList(menu, getUserId());\n        return R.ok(menuService.buildMenuTreeSelect(menus));\n    }\n\n    /**\n     * 加载对应角色菜单列表树\n     */\n    @GetMapping(value = \"/roleMenuTreeselect/{roleId}\", name = \"菜单管理-加载对应角色菜单列表树\")\n    public R roleMenuTreeselect(@PathVariable(\"roleId\") Long roleId) {\n        List<SysMenuEntity> menus = menuService.selectMenuList(getUserId());\n        R ajax = R.ok();\n        ajax.put(\"checkedKeys\", menuService.selectMenuListByRoleId(roleId));\n        ajax.put(\"menus\", menuService.buildMenuTreeSelect(menus));\n        return ajax;\n    }\n\n    /**\n     * 新增菜单\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:menu:add')\")\n    @PostMapping(name = \"菜单管理-新增\")\n    public R add(@Validated @RequestBody SysMenuEntity menu) {\n        if (!menuService.checkMenuNameUnique(menu)) {\n            return R.error(\"新增菜单'\" + menu.getMenuName() + \"'失败，菜单名称已存在\");\n        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {\n            return R.error(\"新增菜单'\" + menu.getMenuName() + \"'失败，地址必须以http(s)://开头\");\n        }\n        return R.ok(menuService.insertMenu(menu));\n    }\n\n    /**\n     * 修改菜单\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:menu:edit')\")\n    @PutMapping(name = \"菜单管理-修改\")\n    public R edit(@Validated @RequestBody SysMenuEntity menu) {\n        if (!menuService.checkMenuNameUnique(menu)) {\n            return R.error(\"修改菜单'\" + menu.getMenuName() + \"'失败，菜单名称已存在\");\n        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {\n            return R.error(\"修改菜单'\" + menu.getMenuName() + \"'失败，地址必须以http(s)://开头\");\n        } else if (menu.getMenuId().equals(menu.getParentId())) {\n            return R.error(\"修改菜单'\" + menu.getMenuName() + \"'失败，上级菜单不能选择自己\");\n        }\n        return R.ok(menuService.updateMenu(menu));\n    }\n\n    /**\n     * 删除菜单\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:menu:remove')\")\n    @DeleteMapping(value = \"/{menuId}\", name = \"菜单管理-删除\")\n    public R remove(@PathVariable(\"menuId\") Long menuId) {\n        if (menuService.hasChildByMenuId(menuId)) {\n            return R.error(\"存在子菜单,不允许删除\");\n        }\n        if (menuService.checkMenuExistRole(menuId)) {\n            return R.error(\"菜单已分配,不允许删除\");\n        }\n        return R.ok(menuService.deleteMenuById(menuId));\n    }\n\n\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysProfileController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.api.file.FileUploadUtils;\nimport com.oddfar.campus.framework.api.file.MimeTypeUtils;\nimport com.oddfar.campus.framework.api.sysconfig.ConfigExpander;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport com.oddfar.campus.framework.web.service.TokenService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport static com.oddfar.campus.common.utils.SecurityUtils.getLoginUser;\n\n/**\n * 个人信息 业务处理\n */\n@RestController\n@RequestMapping(\"/system/user/profile\")\n@ApiResource(name = \"个人信息管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysProfileController {\n    @Autowired\n    private SysUserService userService;\n\n    @Autowired\n    private TokenService tokenService;\n\n    /**\n     * 个人信息\n     */\n    @GetMapping(name = \"个人信息管理-查询\")\n    public R profile() {\n        LoginUser loginUser = getLoginUser();\n        SysUserEntity user = loginUser.getUser();\n        R ajax = R.ok(user);\n        ajax.put(\"roleGroup\", userService.selectUserRoleGroup(loginUser.getUsername()));\n        return ajax;\n    }\n\n    /**\n     * 修改用户\n     */\n    @PutMapping(name = \"个人信息管理-修改\")\n    public R updateProfile(@RequestBody SysUserEntity user) {\n        LoginUser loginUser = getLoginUser();\n        SysUserEntity sysUser = loginUser.getUser();\n        user.setUserName(sysUser.getUserName());\n        if (StringUtils.isNotEmpty(user.getPhonenumber())\n                && !(userService.checkPhoneUnique(user))) {\n            return R.error(\"修改用户'\" + user.getUserName() + \"'失败，手机号码已存在\");\n        }\n        if (StringUtils.isNotEmpty(user.getEmail())\n                && !(userService.checkEmailUnique(user))) {\n            return R.error(\"修改用户'\" + user.getUserName() + \"'失败，邮箱账号已存在\");\n        }\n        user.setUserId(sysUser.getUserId());\n        user.setPassword(null);\n        user.setAvatar(null);\n        if (userService.updateUserProfile(user) > 0) {\n            // 更新缓存用户信息\n            sysUser.setNickName(user.getNickName());\n            sysUser.setPhonenumber(user.getPhonenumber());\n            sysUser.setEmail(user.getEmail());\n            sysUser.setSex(user.getSex());\n            tokenService.setLoginUser(loginUser);\n            return R.ok();\n        }\n        return R.error(\"修改个人信息异常，请联系管理员\");\n    }\n\n    /**\n     * 重置密码\n     */\n    @PutMapping(value = \"/updatePwd\", name = \"个人信息管理-重置密码\")\n    public R updatePwd(String oldPassword, String newPassword) {\n        SysUserEntity user = userService.selectUserById(SecurityUtils.getUserId());\n//        LoginUser loginUser = getLoginUser();\n        String userName = user.getUserName();\n        String password = user.getPassword();\n        if (!SecurityUtils.matchesPassword(oldPassword, password)) {\n            return R.error(\"修改密码失败，旧密码错误\");\n        }\n        if (SecurityUtils.matchesPassword(newPassword, password)) {\n            return R.error(\"新密码不能与旧密码相同\");\n        }\n        if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) {\n//            // 更新缓存用户密码\n//            loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword));\n//            tokenService.setLoginUser(loginUser);\n            return R.ok();\n        }\n        return R.error(\"修改密码异常，请联系管理员\");\n    }\n\n    /**\n     * 头像上传\n     */\n    @PostMapping(value = \"/avatar\", name = \"个人信息管理-头像上次\")\n    public R avatar(@RequestParam(\"avatarfile\") MultipartFile file) throws Exception {\n        if (!file.isEmpty()) {\n            LoginUser loginUser = getLoginUser();\n            String avatar = FileUploadUtils.upload(ConfigExpander.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);\n            if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) {\n                R ajax = R.ok();\n                ajax.put(\"imgUrl\", avatar);\n                // 更新缓存用户头像\n                loginUser.getUser().setAvatar(avatar);\n                tokenService.setLoginUser(loginUser);\n                return ajax;\n            }\n        }\n        return R.error(\"上传图片异常，请联系管理员\");\n    }\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysRegisterController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.Anonymous;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.RegisterBody;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.service.SysConfigService;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport com.oddfar.campus.framework.web.service.SysRegisterService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * 注册验证\n */\n@RestController\npublic class SysRegisterController {\n    @Autowired\n    private SysRegisterService registerService;\n\n    @Autowired\n    private SysConfigService configService;\n\n    @Autowired\n    private SysUserService userService;\n\n    @PostMapping(\"/register\")\n    public R register(@RequestBody RegisterBody user) {\n        if (!(\"true\".equals(configService.selectConfigByKey(\"sys.account.registerUser\")))) {\n            return R.error(\"当前系统没有开启注册功能！\");\n        }\n        String msg = registerService.register(user);\n        return StringUtils.isEmpty(msg) ? R.ok() : R.error(msg);\n    }\n\n    @Anonymous\n    @GetMapping(\"/userNameUnique\")\n    public R userNameUnique(String userName) {\n        SysUserEntity userEntity = new SysUserEntity();\n        userEntity.setUserName(userName);\n        return R.ok(userService.checkUserNameUnique(userEntity));\n    }\n\n    @Anonymous\n    @GetMapping(\"/emailUnique\")\n    public R emailUnique(String email) {\n        SysUserEntity userEntity = new SysUserEntity();\n        userEntity.setEmail(email);\n        return R.ok(userService.checkEmailUnique(userEntity));\n    }\n\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysRoleController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserRoleEntity;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.service.SysRoleService;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport com.oddfar.campus.framework.web.service.SysPermissionService;\nimport com.oddfar.campus.framework.web.service.TokenService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.Arrays;\n\n@RestController\n@RequestMapping(\"/system/role\")\n@ApiResource(name = \"角色管理\" , resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysRoleController {\n\n    @Autowired\n    private SysRoleService roleService;\n    @Autowired\n    private SysUserService userService;\n    @Autowired\n    private SysPermissionService permissionService;\n    @Autowired\n    private TokenService tokenService;\n\n    @PreAuthorize(\"@ss.hasPermi('system:role:list')\")\n    @GetMapping(\"/list\")\n    public R list(SysRoleEntity role) {\n        PageResult<SysRoleEntity> list = roleService.page(role);\n        return R.ok().put(list);\n    }\n\n    /**\n     * 根据角色编号获取详细信息\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:query')\")\n    @GetMapping(value = \"/{roleId}\")\n    public R getInfo(@PathVariable Long roleId) {\n        return R.ok(roleService.selectRoleById(roleId));\n    }\n\n    /**\n     * 新增角色\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:add')\")\n    @PostMapping\n    public R add(@Validated @RequestBody SysRoleEntity role) {\n        if (!roleService.checkRoleNameUnique(role)) {\n            return R.error(\"新增角色'\" + role.getRoleName() + \"'失败，角色名称已存在\");\n        } else if (!roleService.checkRoleKeyUnique(role)) {\n            return R.error(\"新增角色'\" + role.getRoleName() + \"'失败，角色权限已存在\");\n        }\n        return R.ok(roleService.insertRole(role));\n\n    }\n\n    /**\n     * 修改保存角色\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:edit')\")\n    @PutMapping\n    public R edit(@Validated @RequestBody SysRoleEntity role) {\n        roleService.checkRoleAllowed(role);\n        if (!roleService.checkRoleNameUnique(role)) {\n            return R.error(\"修改角色'\" + role.getRoleName() + \"'失败，角色名称已存在\");\n        } else if (!roleService.checkRoleKeyUnique(role)) {\n            return R.error(\"修改角色'\" + role.getRoleName() + \"'失败，角色权限已存在\");\n        }\n\n        if (roleService.updateRole(role) > 0) {\n            // 更新缓存用户权限\n            LoginUser loginUser = SecurityUtils.getLoginUser();\n            if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) {\n                loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));\n                loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));\n                tokenService.setLoginUser(loginUser);\n            }\n            permissionService.resetLoginUserRoleCache(role.getRoleId());\n            return R.ok();\n        }\n        return R.error(\"修改角色'\" + role.getRoleName() + \"'失败，请联系管理员\");\n    }\n\n    /**\n     * 状态修改\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:edit')\")\n    @PutMapping(\"/changeStatus\")\n    public R changeStatus(@RequestBody SysRoleEntity role) {\n        roleService.checkRoleAllowed(role);\n        roleService.updateRoleStatus(role);\n        //更新redis缓存权限数据\n        permissionService.resetLoginUserRoleCache(role.getRoleId());\n        return R.ok();\n    }\n\n    /**\n     * 删除角色\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:remove')\")\n    @DeleteMapping(\"/{roleIds}\")\n    public R remove(@PathVariable Long[] roleIds) {\n        roleService.deleteRoleByIds(roleIds);\n        //更新redis缓存权限数据\n        Arrays.stream(roleIds).forEach(id -> permissionService.resetLoginUserRoleCache(id));\n\n        return R.ok();\n    }\n\n\n    /**\n     * 查询已分配用户角色列表\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:list')\")\n    @GetMapping(\"/authUser/allocatedList\")\n    public R allocatedList(SysUserEntity user) {\n        Page<SysUserEntity> page = userService.selectAllocatedList(user);\n        return R.ok().put(page);\n    }\n\n    /**\n     * 查询未分配用户角色列表\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:list')\")\n    @GetMapping(\"/authUser/unallocatedList\")\n    public R unallocatedList(SysUserEntity user) {\n        Page<SysUserEntity> page = userService.selectUnallocatedList(user);\n        return R.ok().put(page);\n    }\n\n    /**\n     * 取消授权用户\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:edit')\")\n    @PutMapping(\"/authUser/cancel\")\n    public R cancelAuthUser(@RequestBody SysUserRoleEntity userRole) {\n        int i = roleService.deleteAuthUser(userRole);\n        //更新redis缓存权限数据\n        permissionService.resetLoginUserRoleCache(userRole.getRoleId());\n        return R.ok(i);\n    }\n\n    /**\n     * 批量取消授权用户\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:edit')\")\n    @PutMapping(\"/authUser/cancelAll\")\n    public R cancelAuthUserAll(Long roleId, Long[] userIds) {\n        int i = roleService.deleteAuthUsers(roleId, userIds);\n        //更新redis缓存权限数据\n        permissionService.resetLoginUserRoleCache(roleId);\n\n        return R.ok(i);\n    }\n\n    /**\n     * 批量选择用户授权\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:role:edit')\")\n    @PutMapping(\"/authUser/selectAll\")\n    public R selectAuthUserAll(Long roleId, Long[] userIds) {\n        return R.ok(roleService.insertAuthUsers(roleId, userIds));\n    }\n\n}\n"
  },
  {
    "path": "campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysUserController.java",
    "content": "package com.oddfar.campus.admin.controller.system;\n\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.service.SysRoleService;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport com.oddfar.campus.framework.web.service.SysPermissionService;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\n/**\n * 用户管理\n *\n * @author oddfar\n */\n@RestController\n@RequestMapping(\"/system/user\")\n@ApiResource(name = \"用户管理\", resBizType = ResBizTypeEnum.SYSTEM)\npublic class SysUserController {\n    @Autowired\n    private SysUserService userService;\n    @Autowired\n    private SysRoleService roleService;\n    @Autowired\n    private SysPermissionService permissionService;\n\n    /**\n     * 分页\n     */\n    @GetMapping(\"list\")\n    @PreAuthorize(\"@ss.hasPermi('system:user:list')\")\n    public R page(SysUserEntity sysUserEntity) {\n        PageResult<SysUserEntity> page = userService.page(sysUserEntity);\n\n        return R.ok().put(page);\n    }\n\n    /**\n     * 信息\n     */\n    @GetMapping({\"{userId}\", \"/\"})\n    @PreAuthorize(\"@ss.hasPermi('system:user:query')\")\n    public R getInfo(@PathVariable(value = \"userId\", required = false) Long userId) {\n        R res = R.ok();\n        List<SysRoleEntity> roles = roleService.selectRoleAll();\n        res.put(\"roles\", SysUserEntity.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));\n        if (StringUtils.isNotNull(userId)) {\n            SysUserEntity sysUser = userService.selectUserById(userId);\n            res.put(\"data\", sysUser);\n            res.put(\"roleIds\", sysUser.getRoles().stream().map(SysRoleEntity::getRoleId).filter(Objects::nonNull).collect(Collectors.toList()));\n        }\n\n        return res;\n    }\n\n    /**\n     * 新增用户\n     */\n    @PostMapping\n    @PreAuthorize(\"@ss.hasPermi('system:user:add')\")\n    public R add(@Validated @RequestBody SysUserEntity sysUserEntity) {\n        userService.insertUser(sysUserEntity);\n\n        return R.ok();\n    }\n\n    /**\n     * 修改\n     */\n    @PutMapping\n    @PreAuthorize(\"@ss.hasPermi('system:user:edit')\")\n    public R update(@Validated @RequestBody SysUserEntity user) {\n        userService.checkUserAllowed(user);\n        if (!(userService.checkUserNameUnique(user))) {\n            return R.error(\"修改用户'\" + user.getUserName() + \"'失败，登录账号已存在\");\n        } else if (StringUtils.isNotEmpty(user.getPhonenumber())\n                && !(userService.checkPhoneUnique(user))) {\n            return R.error(\"修改用户'\" + user.getUserName() + \"'失败，手机号码已存在\");\n        } else if (StringUtils.isNotEmpty(user.getEmail())\n                && !(userService.checkEmailUnique(user))) {\n            return R.error(\"修改用户'\" + user.getUserName() + \"'失败，邮箱账号已存在\");\n        }\n        user.setPassword(null);\n\n        return R.ok(userService.updateUser(user));\n    }\n\n    /**\n     * 删除\n     */\n    @DeleteMapping(\"/{userIds}\")\n    @PreAuthorize(\"@ss.hasPermi('system:user:remove')\")\n    public R remove(@PathVariable Long[] userIds) {\n        if (ArrayUtils.contains(userIds, SecurityUtils.getUserId())) {\n            return R.error(\"当前用户不能删除\");\n        }\n        return R.ok(userService.deleteUserByIds(userIds));\n    }\n\n    /**\n     * 根据用户编号获取授权角色\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:user:query')\")\n    @GetMapping(\"/authRole/{userId}\")\n    public R authRole(@PathVariable(\"userId\") Long userId) {\n        R res = R.ok();\n        SysUserEntity user = userService.selectUserById(userId);\n        List<SysRoleEntity> roles = roleService.selectRolesByUserId(userId);\n        res.put(\"user\", user);\n        res.put(\"roles\", SysUserEntity.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));\n        return res;\n    }\n\n    /**\n     * 用户授权角色\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:user:edit')\")\n    @PutMapping(\"/authRole\")\n    public R insertAuthRole(Long userId, Long[] roleIds) {\n        if (!SysUserEntity.isAdmin(userId)) {\n            userService.insertUserAuth(userId, roleIds);\n            return R.ok();\n        } else {\n            return R.error(\"不可操作超级管理员\");\n        }\n\n\n    }\n\n    /**\n     * 重置密码\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:user:resetPwd')\")\n    @PutMapping(\"/resetPwd\")\n    public R resetPwd(@RequestBody SysUserEntity user) {\n\n        userService.checkUserAllowed(user);\n        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));\n        return R.ok(userService.resetPwd(user));\n    }\n\n    /**\n     * 状态修改\n     */\n    @PreAuthorize(\"@ss.hasPermi('system:user:edit')\")\n    @PutMapping(\"/changeStatus\")\n    public R changeStatus(@RequestBody SysUserEntity user) {\n\n        userService.checkUserAllowed(user);\n        userService.updateUserStatus(user);\n        permissionService.resetUserRoleAuthCache(user.getUserId());\n        return R.ok();\n    }\n\n\n}"
  },
  {
    "path": "campus-common/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>campus</artifactId>\n        <groupId>com.oddfar.campus</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>campus-common</artifactId>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n    </properties>\n    <dependencies>\n        <!-- Token生成与解析-->\n        <dependency>\n            <groupId>io.jsonwebtoken</groupId>\n            <artifactId>jjwt</artifactId>\n        </dependency>\n        <!--常用工具类 -->\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-lang3</artifactId>\n        </dependency>\n        <!-- 文件上传工具类 -->\n        <dependency>\n            <groupId>commons-fileupload</groupId>\n            <artifactId>commons-fileupload</artifactId>\n        </dependency>\n        <!-- redis 缓存操作 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-redis</artifactId>\n        </dependency>\n        <!-- spring2.X集成redis所需common-pool2-->\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-pool2</artifactId>\n        </dependency>\n        <!-- spring security 安全认证 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <!--validation校验-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <!-- excel工具 -->\n        <dependency>\n            <groupId>org.apache.poi</groupId>\n            <artifactId>poi-ooxml</artifactId>\n        </dependency>\n        <!--lombk-->\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n        </dependency>\n        <!--mybatisPlus依赖-->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-boot-starter</artifactId>\n        </dependency>\n\n        <!-- sql性能分析插件 -->\n        <dependency>\n            <groupId>p6spy</groupId>\n            <artifactId>p6spy</artifactId>\n        </dependency>\n\n        <!-- dynamic-datasource 多数据源-->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>\n        </dependency>\n\n        <!--fastjson-->\n        <dependency>\n            <groupId>com.alibaba.fastjson2</groupId>\n            <artifactId>fastjson2</artifactId>\n        </dependency>\n        <!--validation-->\n        <dependency>\n            <groupId>jakarta.validation</groupId>\n            <artifactId>jakarta.validation-api</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，主要是 PageParam 使用到 -->\n        </dependency>\n        <!--hutool-->\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n        </dependency>\n        <!--mapstruct-->\n        <dependency>\n            <groupId>org.mapstruct</groupId>\n            <artifactId>mapstruct</artifactId>\n        </dependency>\n        <!-- 解析客户端操作系统、浏览器等 -->\n        <dependency>\n            <groupId>eu.bitwalker</groupId>\n            <artifactId>UserAgentUtils</artifactId>\n        </dependency>\n        <!-- servlet包 -->\n        <dependency>\n            <groupId>javax.servlet</groupId>\n            <artifactId>javax.servlet-api</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/annotation/Anonymous.java",
    "content": "package com.oddfar.campus.common.annotation;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 匿名访问不鉴权注解\n *\n * @author ruoyi\n */\n@Target({ElementType.METHOD, ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Anonymous {\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/annotation/ApiResource.java",
    "content": "package com.oddfar.campus.common.annotation;\n\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\n\nimport java.lang.annotation.*;\n\n/**\n * 资源标识\n */\n@Target({ElementType.METHOD, ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface ApiResource {\n\n    /**\n     * 资源编码唯一标识\n     * 可不填写此注解属性，默认生成的编码标识为: 控制器类名称 + 分隔符 + 方法名称\n     */\n    String code() default \"\";\n\n    /**\n     * 应用编码\n     * 用于区分不同业务\n     */\n    String appCode() default \"\";\n\n    /**\n     * 资源名称(必填项)\n     */\n    String name() default \"\";\n\n    /**\n     * 资源的类型，系统类还是业务类资源\n     */\n    ResBizTypeEnum resBizType() default ResBizTypeEnum.BUSINESS;\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/annotation/Log.java",
    "content": "package com.oddfar.campus.common.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 用来标记在控制器类或方法上，进行判断是否需要对接口进行日志记录\n */\n@Target({ElementType.METHOD, ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Log {\n\n    /**\n     * 是否进行日志记录，默认开启\n     */\n    boolean openLog() default true;\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/annotation/RepeatSubmit.java",
    "content": "package com.oddfar.campus.common.annotation;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Inherited;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 自定义注解防止表单重复提交\n *\n * @author ruoyi\n */\n@Inherited\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface RepeatSubmit {\n    /**\n     * 间隔时间(ms)，小于此时间视为重复提交\n     */\n    public int interval() default 5000;\n\n    /**\n     * 提示消息\n     */\n    public String message() default \"不允许重复提交，请稍候再试\";\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/annotation/Sensitive.java",
    "content": "package com.oddfar.campus.common.annotation;\n\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.oddfar.campus.common.config.SensitiveSerializer;\nimport com.oddfar.campus.common.enums.SensitiveStrategy;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 数据脱敏注解\n *\n * @author zhujie\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.FIELD)\n@JacksonAnnotationsInside\n@JsonSerialize(using = SensitiveSerializer.class)\npublic @interface Sensitive {\n    SensitiveStrategy strategy();\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/config/CampusConfig.java",
    "content": "package com.oddfar.campus.common.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\n/**\n * 首页\n *\n * @author oddfar\n */\n\n@Data\n@Component\n@ConfigurationProperties(prefix = \"campus\")\npublic class CampusConfig {\n\n    /**\n     * 项目名称\n     */\n    private String name;\n\n    /**\n     * 版本\n     */\n    private String version;\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/config/FastJson2JsonRedisSerializer.java",
    "content": "package com.oddfar.campus.common.config;\n\nimport com.alibaba.fastjson2.JSON;\nimport com.alibaba.fastjson2.JSONReader;\nimport com.alibaba.fastjson2.JSONWriter;\nimport org.springframework.data.redis.serializer.RedisSerializer;\nimport org.springframework.data.redis.serializer.SerializationException;\n\nimport java.nio.charset.Charset;\n\n/**\n * Redis使用FastJson序列化\n * \n * @author ruoyi\n */\npublic class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>\n{\n    public static final Charset DEFAULT_CHARSET = Charset.forName(\"UTF-8\");\n\n    private Class<T> clazz;\n\n    public FastJson2JsonRedisSerializer(Class<T> clazz)\n    {\n        super();\n        this.clazz = clazz;\n    }\n\n    @Override\n    public byte[] serialize(T t) throws SerializationException\n    {\n        if (t == null)\n        {\n            return new byte[0];\n        }\n        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);\n    }\n\n    @Override\n    public T deserialize(byte[] bytes) throws SerializationException\n    {\n        if (bytes == null || bytes.length <= 0)\n        {\n            return null;\n        }\n        String str = new String(bytes, DEFAULT_CHARSET);\n\n        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/config/RedisConfig.java",
    "content": "package com.oddfar.campus.common.config;\n\nimport org.springframework.cache.annotation.CachingConfigurerSupport;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.core.script.DefaultRedisScript;\nimport org.springframework.data.redis.serializer.StringRedisSerializer;\n\n/**\n * redis配置\n *\n * @author ruoyi\n */\n@Configuration\n@EnableCaching\npublic class RedisConfig extends CachingConfigurerSupport {\n    @Bean\n    @SuppressWarnings(value = {\"unchecked\", \"rawtypes\"})\n    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {\n        RedisTemplate<Object, Object> template = new RedisTemplate<>();\n        template.setConnectionFactory(connectionFactory);\n\n        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);\n\n        // 使用StringRedisSerializer来序列化和反序列化redis的key值\n        template.setKeySerializer(new StringRedisSerializer());\n        template.setValueSerializer(serializer);\n\n        // Hash的key也采用StringRedisSerializer的序列化方式\n        template.setHashKeySerializer(new StringRedisSerializer());\n        template.setHashValueSerializer(serializer);\n\n        template.afterPropertiesSet();\n        return template;\n    }\n\n    @Bean\n    public DefaultRedisScript<Long> limitScript() {\n        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();\n        redisScript.setScriptText(limitScriptText());\n        redisScript.setResultType(Long.class);\n        return redisScript;\n    }\n\n    /**\n     * 限流脚本\n     */\n    private String limitScriptText() {\n        return \"local key = KEYS[1]\\n\" +\n                \"local count = tonumber(ARGV[1])\\n\" +\n                \"local time = tonumber(ARGV[2])\\n\" +\n                \"local current = redis.call('get', key);\\n\" +\n                \"if current and tonumber(current) > count then\\n\" +\n                \"    return tonumber(current);\\n\" +\n                \"end\\n\" +\n                \"current = redis.call('incr', key)\\n\" +\n                \"if tonumber(current) == 1 then\\n\" +\n                \"    redis.call('expire', key, time)\\n\" +\n                \"end\\n\" +\n                \"return tonumber(current);\";\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/config/SensitiveSerializer.java",
    "content": "package com.oddfar.campus.common.config;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.BeanProperty;\nimport com.fasterxml.jackson.databind.JsonMappingException;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.fasterxml.jackson.databind.ser.ContextualSerializer;\nimport com.oddfar.campus.common.annotation.Sensitive;\nimport com.oddfar.campus.common.enums.SensitiveStrategy;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.BeansException;\n\nimport java.io.IOException;\nimport java.util.Objects;\n\n/**\n * 数据脱敏json序列化工具\n *\n * @author Yjoioooo\n */\n@Slf4j\npublic class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {\n\n    private SensitiveStrategy strategy;\n\n    @Override\n    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {\n        try {\n            gen.writeString(strategy.desensitizer().apply(value));\n        } catch (BeansException e) {\n            log.error(\"脱敏实现不存在, 采用默认处理 => {}\", e.getMessage());\n            gen.writeString(value);\n        }\n    }\n\n    @Override\n    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {\n        Sensitive annotation = property.getAnnotation(Sensitive.class);\n        if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {\n            this.strategy = annotation.strategy();\n            return this;\n        }\n        return prov.findValueSerializer(property.getType(), property);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/constant/CacheConstants.java",
    "content": "package com.oddfar.campus.common.constant;\n\n/**\n * 缓存的key 常量\n */\npublic class CacheConstants {\n    /**\n     * 登录用户 redis token key\n     */\n    public static final String LOGIN_TOKEN_KEY = \"login_tokens:\";\n\n    /**\n     * 登录用户 redis userId key\n     */\n    public static final String LOGIN_USER_KEY = \"login_user:\";\n    /**\n     * 验证码 redis key\n     */\n    public static final String CAPTCHA_CODE_KEY = \"captcha_codes:\";\n\n\n    /**\n     * 参数管理 cache key\n     */\n    public static final String SYS_CONFIG_KEY = \"sys_config:\";\n\n    /**\n     * 字典管理 cache key\n     */\n    public static final String SYS_DICT_KEY = \"sys_dict:\";\n\n    /**\n     * 防重提交 redis key\n     */\n    public static final String REPEAT_SUBMIT_KEY = \"repeat_submit:\";\n\n    /**\n     * 限流 redis key\n     */\n    public static final String RATE_LIMIT_KEY = \"rate_limit:\";\n\n    /**\n     * 登录账户密码错误次数 redis key\n     */\n    public static final String PWD_ERR_CNT_KEY = \"pwd_err_cnt:\";\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/constant/Constants.java",
    "content": "package com.oddfar.campus.common.constant;\n\n/**\n * 通用常量信息\n *\n * @author ruoyi\n */\npublic class Constants {\n    /**\n     * UTF-8 字符集\n     */\n    public static final String UTF8 = \"UTF-8\";\n\n    /**\n     * GBK 字符集\n     */\n    public static final String GBK = \"GBK\";\n\n    /**\n     * www主域\n     */\n    public static final String WWW = \"www.\";\n\n    /**\n     * http请求\n     */\n    public static final String HTTP = \"http://\";\n\n    /**\n     * https请求\n     */\n    public static final String HTTPS = \"https://\";\n\n    /**\n     * 通用成功标识\n     */\n    public static final String SUCCESS = \"0\";\n\n    /**\n     * 通用失败标识\n     */\n    public static final String FAIL = \"1\";\n\n    /**\n     * 是\n     */\n    public static final String YES = \"Y\";\n\n    /**\n     * 否\n     */\n    public static final String NO = \"N\";\n\n    /**\n     * 登录成功\n     */\n    public static final String LOGIN_SUCCESS = \"Success\";\n\n    /**\n     * 注销\n     */\n    public static final String LOGOUT = \"Logout\";\n\n    /**\n     * 注册\n     */\n    public static final String REGISTER = \"Register\";\n\n    /**\n     * 登录失败\n     */\n    public static final String LOGIN_FAIL = \"Error\";\n\n    /**\n     * 验证码有效期（分钟）\n     */\n    public static final Integer CAPTCHA_EXPIRATION = 2;\n\n    /**\n     * 令牌\n     */\n    public static final String TOKEN = \"token\";\n\n    /**\n     * 用户id\n     */\n    public static final String USER_ID = \"userId\";\n\n    /**\n     * 令牌前缀\n     */\n    public static final String TOKEN_PREFIX = \"Bearer \";\n\n    /**\n     * 令牌前缀\n     */\n    public static final String LOGIN_USER_KEY = \"login_user_key\";\n\n    /**\n     * 用户ID\n     */\n    public static final String JWT_USERID = \"userid\";\n\n    /**\n     * 用户名称\n     */\n    public static final String JWT_USERNAME = \"sub\";\n\n    /**\n     * 用户头像\n     */\n    public static final String JWT_AVATAR = \"avatar\";\n\n    /**\n     * 创建时间\n     */\n    public static final String JWT_CREATED = \"created\";\n\n    /**\n     * 用户权限\n     */\n    public static final String JWT_AUTHORITIES = \"authorities\";\n\n    /**\n     * 资源映射路径 前缀\n     */\n    public static final String RESOURCE_PREFIX = \"/profile\";\n\n    /**\n     * RMI 远程方法调用\n     */\n    public static final String LOOKUP_RMI = \"rmi:\";\n\n    /**\n     * LDAP 远程方法调用\n     */\n    public static final String LOOKUP_LDAP = \"ldap:\";\n\n    /**\n     * LDAPS 远程方法调用\n     */\n    public static final String LOOKUP_LDAPS = \"ldaps:\";\n\n    /**\n     * 定时任务白名单配置（仅允许访问的包名，如其他需要可以自行添加）\n     */\n    public static final String[] JOB_WHITELIST_STR = {\"com.ruoyi\"};\n\n    /**\n     * 定时任务违规的字符\n     */\n    public static final String[] JOB_ERROR_STR = {\"java.net.URL\", \"javax.naming.InitialContext\", \"org.yaml.snakeyaml\",\n            \"org.springframework\", \"org.apache\", \"com.ruoyi.common.utils.file\"};\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/constant/HttpStatus.java",
    "content": "package com.oddfar.campus.common.constant;\n\n/**\n * 返回状态码\n *\n * @author ruoyi\n */\npublic class HttpStatus {\n    /**\n     * 操作成功\n     */\n    public static final int SUCCESS = 200;\n\n    /**\n     * 对象创建成功\n     */\n    public static final int CREATED = 201;\n\n    /**\n     * 请求已经被接受\n     */\n    public static final int ACCEPTED = 202;\n\n    /**\n     * 操作已经执行成功，但是没有返回数据\n     */\n    public static final int NO_CONTENT = 204;\n\n    /**\n     * 资源已被移除\n     */\n    public static final int MOVED_PERM = 301;\n\n    /**\n     * 重定向\n     */\n    public static final int SEE_OTHER = 303;\n\n    /**\n     * 资源没有被修改\n     */\n    public static final int NOT_MODIFIED = 304;\n\n    /**\n     * 参数列表错误（缺少，格式不匹配）\n     */\n    public static final int BAD_REQUEST = 400;\n\n    /**\n     * 未授权\n     */\n    public static final int UNAUTHORIZED = 401;\n\n    /**\n     * 访问受限，授权过期\n     */\n    public static final int FORBIDDEN = 403;\n\n    /**\n     * 资源，服务未找到\n     */\n    public static final int NOT_FOUND = 404;\n\n    /**\n     * 不允许的http方法\n     */\n    public static final int BAD_METHOD = 405;\n\n    /**\n     * 资源冲突，或者资源被锁\n     */\n    public static final int CONFLICT = 409;\n\n    /**\n     * 不支持的数据，媒体类型\n     */\n    public static final int UNSUPPORTED_TYPE = 415;\n\n    /**\n     * 系统内部错误\n     */\n    public static final int ERROR = 500;\n\n    /**\n     * 接口未实现\n     */\n    public static final int NOT_IMPLEMENTED = 501;\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/constant/UserConstants.java",
    "content": "package com.oddfar.campus.common.constant;\n\n/**\n * 用户常量信息\n *\n * @author ruoyi\n */\npublic class UserConstants {\n    /**\n     * 平台内系统用户的唯一标志\n     */\n    public static final String SYS_USER = \"SYS_USER\";\n\n\n    /**\n     * 正常状态\n     */\n    public static final String NORMAL = \"0\";\n\n    /**\n     * 异常状态\n     */\n    public static final String EXCEPTION = \"1\";\n\n    /**\n     * 用户封禁状态\n     */\n    public static final String USER_DISABLE = \"1\";\n\n    /**\n     * 角色封禁状态\n     */\n    public static final String ROLE_DISABLE = \"1\";\n\n\n    /**\n     * 字典正常状态\n     */\n    public static final String DICT_NORMAL = \"0\";\n\n    /**\n     * 是否为系统默认（是）\n     */\n    public static final String YES = \"Y\";\n\n    /**\n     * 是否菜单外链（是）\n     */\n    public static final String YES_FRAME = \"0\";\n\n    /**\n     * 是否菜单外链（否）\n     */\n    public static final String NO_FRAME = \"1\";\n\n    /**\n     * 菜单类型（目录）\n     */\n    public static final String TYPE_DIR = \"M\";\n\n    /**\n     * 菜单类型（菜单）\n     */\n    public static final String TYPE_MENU = \"C\";\n\n    /**\n     * 菜单类型（按钮）\n     */\n    public static final String TYPE_BUTTON = \"F\";\n\n    /**\n     * Layout组件标识\n     */\n    public final static String LAYOUT = \"Layout\";\n\n    /**\n     * ParentView组件标识\n     */\n    public final static String PARENT_VIEW = \"ParentView\";\n\n    /**\n     * InnerLink组件标识\n     */\n    public final static String INNER_LINK = \"InnerLink\";\n\n    /**\n     * 校验返回结果码\n     */\n    public final static String UNIQUE = \"0\";\n    public final static String NOT_UNIQUE = \"1\";\n\n    /**\n     * 用户名长度限制\n     */\n    public static final int USERNAME_MIN_LENGTH = 2;\n    public static final int USERNAME_MAX_LENGTH = 20;\n\n    /**\n     * 密码长度限制\n     */\n    public static final int PASSWORD_MIN_LENGTH = 5;\n    public static final int PASSWORD_MAX_LENGTH = 20;\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/core/BaseMapperX.java",
    "content": "package com.oddfar.campus.common.core;\n\n\nimport com.baomidou.mybatisplus.core.conditions.Wrapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.toolkit.support.SFunction;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.oddfar.campus.common.core.page.PageQuery;\nimport com.oddfar.campus.common.domain.PageResult;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 在 MyBatis Plus 的 BaseMapper 的基础上拓展，提供更多的能力\n */\npublic interface BaseMapperX<T> extends BaseMapper<T> {\n\n    default PageResult<T> selectPage(@Param(\"ew\") Wrapper<T> queryWrapper) {\n        PageQuery pageQuery = new PageQuery();\n        Page<T> page = pageQuery.buildPage();\n        selectPage(page, queryWrapper);\n        // 转换返回\n        return new PageResult(page.getRecords(), page.getTotal());\n    }\n\n    default T selectOne(String field, Object value) {\n        return selectOne(new QueryWrapper<T>().eq(field, value));\n    }\n\n    default T selectOne(SFunction<T, ?> field, Object value) {\n        return selectOne(new LambdaQueryWrapper<T>().eq(field, value));\n    }\n\n    default T selectOne(String field1, Object value1, String field2, Object value2) {\n        return selectOne(new QueryWrapper<T>().eq(field1, value1).eq(field2, value2));\n    }\n\n    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {\n        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));\n    }\n\n    default Long selectCount() {\n        return selectCount(new QueryWrapper<T>());\n    }\n\n    default Long selectCount(String field, Object value) {\n        return selectCount(new QueryWrapper<T>().eq(field, value));\n    }\n\n    default Long selectCount(SFunction<T, ?> field, Object value) {\n        return selectCount(new LambdaQueryWrapper<T>().eq(field, value));\n    }\n\n    default List<T> selectList() {\n        return selectList(new QueryWrapper<>());\n    }\n\n    default List<T> selectList(String field, Object value) {\n        return selectList(new QueryWrapper<T>().eq(field, value));\n    }\n\n    default List<T> selectList(SFunction<T, ?> field, Object value) {\n        return selectList(new LambdaQueryWrapper<T>().eq(field, value));\n    }\n\n    default List<T> selectList(String field, Collection<?> values) {\n        return selectList(new QueryWrapper<T>().in(field, values));\n    }\n\n    default List<T> selectList(SFunction<T, ?> field, Collection<?> values) {\n        return selectList(new LambdaQueryWrapper<T>().in(field, values));\n    }\n\n    /**\n     * 逐条插入，适合少量数据插入，或者对性能要求不高的场景\n     * <p>\n     * 如果大量，请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法\n     *\n     * @param entities 实体们\n     */\n    default void insertBatch(Collection<T> entities) {\n        entities.forEach(this::insert);\n    }\n\n    default void updateBatch(T update) {\n        update(update, new QueryWrapper<>());\n    }\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/core/LambdaQueryWrapperX.java",
    "content": "package com.oddfar.campus.common.core;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.CollectionUtils;\nimport com.baomidou.mybatisplus.core.toolkit.support.SFunction;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Collection;\nimport java.util.Map;\n\n/**\n * 拓展 MyBatis Plus QueryWrapper 类，主要增加如下功能：\n * <p>\n * 1. 拼接条件的方法，增加 xxxIfPresent 方法，用于判断值不存在的时候，不要拼接到条件中。\n *\n * @param <T> 数据类型\n */\npublic class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {\n\n    public LambdaQueryWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {\n        if (StringUtils.hasText(val)) {\n            return (LambdaQueryWrapperX<T>) super.like(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {\n        if (!CollectionUtils.isEmpty(values)) {\n            return (LambdaQueryWrapperX<T>) super.in(column, values);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {\n        if (!ArrayUtil.isEmpty(values)) {\n            return (LambdaQueryWrapperX<T>) super.in(column, values);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.eq(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.ne(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.gt(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.ge(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.lt(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.le(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {\n        if (val1 != null && val2 != null) {\n            return (LambdaQueryWrapperX<T>) super.between(column, val1, val2);\n        }\n        if (val1 != null) {\n            return (LambdaQueryWrapperX<T>) ge(column, val1);\n        }\n        if (val2 != null) {\n            return (LambdaQueryWrapperX<T>) le(column, val2);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {\n\n        Object val1 = ArrayUtil.get(values, 0);\n        Object val2 = ArrayUtil.get(values, 1);\n        return betweenIfPresent(column, val1, val2);\n    }\n\n    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Map<String, Object> values) {\n\n        String val1 = (String) values.get(\"beginTime\");\n        String val2 = (String) values.get(\"endTime\");\n        return betweenIfPresent(column, val1, val2);\n    }\n\n    // ========== 重写父类方法，方便链式调用 ==========\n\n    @Override\n    public LambdaQueryWrapperX<T> eq(boolean condition, SFunction<T, ?> column, Object val) {\n        super.eq(condition, column, val);\n        return this;\n    }\n\n    @Override\n    public LambdaQueryWrapperX<T> eq(SFunction<T, ?> column, Object val) {\n        super.eq(column, val);\n        return this;\n    }\n\n    @Override\n    public LambdaQueryWrapperX<T> orderByDesc(SFunction<T, ?> column) {\n        super.orderByDesc(true, column);\n        return this;\n    }\n\n    @Override\n    public LambdaQueryWrapperX<T> last(String lastSql) {\n        super.last(lastSql);\n        return this;\n    }\n\n    @Override\n    public LambdaQueryWrapperX<T> in(SFunction<T, ?> column, Collection<?> coll) {\n        super.in(column, coll);\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/core/RedisCache.java",
    "content": "package com.oddfar.campus.common.core;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.data.redis.core.BoundSetOperations;\nimport org.springframework.data.redis.core.HashOperations;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.core.ValueOperations;\nimport org.springframework.stereotype.Component;\n\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * spring redis 工具类\n *\n * @author ruoyi\n **/\n@SuppressWarnings(value = {\"unchecked\", \"rawtypes\"})\n@Component\npublic class RedisCache {\n    @Autowired\n    public RedisTemplate redisTemplate;\n\n    /**\n     * 缓存基本的对象，Integer、String、实体类等\n     *\n     * @param key   缓存的键值\n     * @param value 缓存的值\n     */\n    public <T> void setCacheObject(final String key, final T value) {\n        redisTemplate.opsForValue().set(key, value);\n    }\n\n    /**\n     * 缓存基本的对象，Integer、String、实体类等\n     *\n     * @param key      缓存的键值\n     * @param value    缓存的值\n     * @param timeout  时间\n     * @param timeUnit 时间颗粒度\n     */\n    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {\n        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);\n    }\n\n    /**\n     * 设置有效时间\n     *\n     * @param key     Redis键\n     * @param timeout 超时时间\n     * @return true=设置成功；false=设置失败\n     */\n    public boolean expire(final String key, final long timeout) {\n        return expire(key, timeout, TimeUnit.SECONDS);\n    }\n\n    /**\n     * 设置有效时间\n     *\n     * @param key     Redis键\n     * @param timeout 超时时间\n     * @param unit    时间单位\n     * @return true=设置成功；false=设置失败\n     */\n    public boolean expire(final String key, final long timeout, final TimeUnit unit) {\n        return redisTemplate.expire(key, timeout, unit);\n    }\n\n    /**\n     * 获取有效时间\n     *\n     * @param key Redis键\n     * @return 有效时间\n     */\n    public long getExpire(final String key) {\n        return redisTemplate.getExpire(key);\n    }\n\n    /**\n     * 判断 key是否存在\n     *\n     * @param key 键\n     * @return true 存在 false不存在\n     */\n    public Boolean hasKey(String key) {\n        return redisTemplate.hasKey(key);\n    }\n\n    /**\n     * 获得缓存的基本对象。\n     *\n     * @param key 缓存键值\n     * @return 缓存键值对应的数据\n     */\n    public <T> T getCacheObject(final String key) {\n        ValueOperations<String, T> operation = redisTemplate.opsForValue();\n        return operation.get(key);\n    }\n\n    /**\n     * 删除单个对象\n     *\n     * @param key\n     */\n    public boolean deleteObject(final String key) {\n        return redisTemplate.delete(key);\n    }\n\n    /**\n     * 删除集合对象\n     *\n     * @param collection 多个对象\n     * @return\n     */\n    public boolean deleteObject(final Collection collection) {\n        return redisTemplate.delete(collection) > 0;\n    }\n\n    /**\n     * 缓存List数据\n     *\n     * @param key      缓存的键值\n     * @param dataList 待缓存的List数据\n     * @return 缓存的对象\n     */\n    public <T> long setCacheList(final String key, final List<T> dataList) {\n        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);\n        return count == null ? 0 : count;\n    }\n\n    /**\n     * 重新set缓存List数据\n     *\n     * @param key      缓存的键值\n     * @param dataList 待缓存的List数据\n     * @return 缓存的对象\n     */\n    public <T> long reSetCacheList(final String key, final List<T> dataList) {\n        this.deleteObject(key);\n        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);\n        return count == null ? 0 : count;\n    }\n\n    /**\n     * 获得缓存的list对象\n     *\n     * @param key 缓存的键值\n     * @return 缓存键值对应的数据\n     */\n    public <T> List<T> getCacheList(final String key) {\n        return redisTemplate.opsForList().range(key, 0, -1);\n    }\n\n    /**\n     * 缓存Set\n     *\n     * @param key     缓存键值\n     * @param dataSet 缓存的数据\n     * @return 缓存数据的对象\n     */\n    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {\n        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);\n        Iterator<T> it = dataSet.iterator();\n        while (it.hasNext()) {\n            setOperation.add(it.next());\n        }\n        return setOperation;\n    }\n\n    /**\n     * 获得缓存的set\n     *\n     * @param key\n     * @return\n     */\n    public <T> Set<T> getCacheSet(final String key) {\n        return redisTemplate.opsForSet().members(key);\n    }\n\n    /**\n     * 缓存Map\n     *\n     * @param key\n     * @param dataMap\n     */\n    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {\n        if (dataMap != null) {\n            redisTemplate.opsForHash().putAll(key, dataMap);\n        }\n    }\n\n    /**\n     * 获得缓存的Map\n     *\n     * @param key\n     * @return\n     */\n    public <T> Map<String, T> getCacheMap(final String key) {\n        return redisTemplate.opsForHash().entries(key);\n    }\n\n    /**\n     * 往Hash中存入数据\n     *\n     * @param key   Redis键\n     * @param hKey  Hash键\n     * @param value 值\n     */\n    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {\n        redisTemplate.opsForHash().put(key, hKey, value);\n    }\n\n    /**\n     * 获取Hash中的数据\n     *\n     * @param key  Redis键\n     * @param hKey Hash键\n     * @return Hash中的对象\n     */\n    public <T> T getCacheMapValue(final String key, final String hKey) {\n        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();\n        return opsForHash.get(key, hKey);\n    }\n\n    /**\n     * 获取多个Hash中的数据\n     *\n     * @param key   Redis键\n     * @param hKeys Hash键集合\n     * @return Hash对象集合\n     */\n    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {\n        return redisTemplate.opsForHash().multiGet(key, hKeys);\n    }\n\n    /**\n     * 删除Hash中的某条数据\n     *\n     * @param key  Redis键\n     * @param hKey Hash键\n     * @return 是否成功\n     */\n    public boolean deleteCacheMapValue(final String key, final String hKey) {\n        return redisTemplate.opsForHash().delete(key, hKey) > 0;\n    }\n\n    /**\n     * 获得缓存的基本对象列表\n     *\n     * @param pattern 字符串前缀\n     * @return 对象列表\n     */\n    public Collection<String> keys(final String pattern) {\n        return redisTemplate.keys(pattern);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/core/page/PageQuery.java",
    "content": "package com.oddfar.campus.common.core.page;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.baomidou.mybatisplus.core.metadata.OrderItem;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.oddfar.campus.common.core.text.Convert;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.sql.SqlUtil;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 分页查询实体类\n *\n * @author oddfar\n * @date 2023/11/26\n */\n@Data\npublic class PageQuery implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 当前记录起始索引\n     */\n    private static final String PAGE_NUM = \"pageNum\";\n\n    /**\n     * 每页显示记录数\n     */\n    private static final String PAGE_SIZE = \"pageSize\";\n\n    /**\n     * 分页大小\n     */\n    private Integer pageSize;\n\n    /**\n     * 当前页数\n     */\n    private Integer pageNum;\n\n    /**\n     * 排序列\n     */\n    private String orderByColumn;\n\n    /**\n     * 排序的方向desc或者asc\n     */\n    private String isAsc;\n\n    /**\n     * 当前记录起始索引 默认值\n     */\n    public static final int DEFAULT_PAGE_NUM = 1;\n\n    /**\n     * 每页显示记录数 默认值 10\n     */\n    public static final int DEFAULT_PAGE_SIZE = 10;\n\n    public final static String COMMA = \",\";\n\n    public <T> Page<T> build() {\n        Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);\n        Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);\n        if (pageNum <= 0) {\n            pageNum = DEFAULT_PAGE_NUM;\n        }\n        Page<T> page = new Page<>(pageNum, pageSize);\n        List<OrderItem> orderItems = buildOrderItem();\n        if (CollUtil.isNotEmpty(orderItems)) {\n            page.addOrder(orderItems);\n        }\n        return page;\n    }\n\n    public <T> Page<T> buildPage() {\n        Integer pageNum = Convert.toInt(ServletUtils.getParameter(PAGE_NUM), DEFAULT_PAGE_NUM);\n        Integer pageSize = Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), DEFAULT_PAGE_SIZE);\n        if (pageNum <= 0) {\n            pageNum = DEFAULT_PAGE_NUM;\n        }\n        Page<T> page = new Page<>(pageNum, pageSize);\n        List<OrderItem> orderItems = buildOrderItem();\n        if (CollUtil.isNotEmpty(orderItems)) {\n            page.addOrder(orderItems);\n        }\n        return page;\n    }\n\n    /**\n     * 构建排序\n     * <p>\n     * 支持的用法如下:\n     * {isAsc:\"asc\",orderByColumn:\"id\"} order by id asc\n     * {isAsc:\"asc\",orderByColumn:\"id,createTime\"} order by id asc,create_time asc\n     * {isAsc:\"desc\",orderByColumn:\"id,createTime\"} order by id desc,create_time desc\n     * {isAsc:\"asc,desc\",orderByColumn:\"id,createTime\"} order by id asc,create_time desc\n     */\n    private List<OrderItem> buildOrderItem() {\n        if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {\n            return null;\n        }\n        String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);\n        orderBy = StringUtils.toUnderScoreCase(orderBy);\n\n        // 兼容前端排序类型\n        isAsc = StringUtils.replaceEach(isAsc, new String[]{\"ascending\", \"descending\"}, new String[]{\"asc\", \"desc\"});\n\n        String[] orderByArr = orderBy.split(COMMA);\n        String[] isAscArr = isAsc.split(COMMA);\n        if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {\n            throw new ServiceException(\"排序参数有误\");\n        }\n\n        List<OrderItem> list = new ArrayList<>();\n        // 每个字段各自排序\n        for (int i = 0; i < orderByArr.length; i++) {\n            String orderByStr = orderByArr[i];\n            String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];\n            if (\"asc\".equals(isAscStr)) {\n                list.add(OrderItem.asc(orderByStr));\n            } else if (\"desc\".equals(isAscStr)) {\n                list.add(OrderItem.desc(orderByStr));\n            } else {\n                throw new ServiceException(\"排序参数有误\");\n            }\n        }\n        return list;\n    }\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/core/page/TableDataInfo.java",
    "content": "package com.oddfar.campus.common.core.page;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * 表格分页数据对象\n *\n * @author ruoyi\n */\npublic class TableDataInfo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 总记录数\n     */\n    private long total;\n\n    /**\n     * 列表数据\n     */\n    private List<?> rows;\n\n    /**\n     * 消息状态码\n     */\n    private int code;\n\n    /**\n     * 消息内容\n     */\n    private String msg;\n\n    /**\n     * 表格数据对象\n     */\n    public TableDataInfo() {\n    }\n\n    /**\n     * 分页\n     *\n     * @param list  列表数据\n     * @param total 总记录数\n     */\n    public TableDataInfo(List<?> list, int total) {\n        this.rows = list;\n        this.total = total;\n    }\n\n    public long getTotal() {\n        return total;\n    }\n\n    public void setTotal(long total) {\n        this.total = total;\n    }\n\n    public List<?> getRows() {\n        return rows;\n    }\n\n    public void setRows(List<?> rows) {\n        this.rows = rows;\n    }\n\n    public int getCode() {\n        return code;\n    }\n\n    public void setCode(int code) {\n        this.code = code;\n    }\n\n    public String getMsg() {\n        return msg;\n    }\n\n    public void setMsg(String msg) {\n        this.msg = msg;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/core/text/CharsetKit.java",
    "content": "package com.oddfar.campus.common.core.text;\n\nimport com.oddfar.campus.common.utils.StringUtils;\n\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * 字符集工具类\n * \n * @author ruoyi\n */\npublic class CharsetKit\n{\n    /** ISO-8859-1 */\n    public static final String ISO_8859_1 = \"ISO-8859-1\";\n    /** UTF-8 */\n    public static final String UTF_8 = \"UTF-8\";\n    /** GBK */\n    public static final String GBK = \"GBK\";\n\n    /** ISO-8859-1 */\n    public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1);\n    /** UTF-8 */\n    public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8);\n    /** GBK */\n    public static final Charset CHARSET_GBK = Charset.forName(GBK);\n\n    /**\n     * 转换为Charset对象\n     * \n     * @param charset 字符集，为空则返回默认字符集\n     * @return Charset\n     */\n    public static Charset charset(String charset)\n    {\n        return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);\n    }\n\n    /**\n     * 转换字符串的字符集编码\n     * \n     * @param source 字符串\n     * @param srcCharset 源字符集，默认ISO-8859-1\n     * @param destCharset 目标字符集，默认UTF-8\n     * @return 转换后的字符集\n     */\n    public static String convert(String source, String srcCharset, String destCharset)\n    {\n        return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));\n    }\n\n    /**\n     * 转换字符串的字符集编码\n     * \n     * @param source 字符串\n     * @param srcCharset 源字符集，默认ISO-8859-1\n     * @param destCharset 目标字符集，默认UTF-8\n     * @return 转换后的字符集\n     */\n    public static String convert(String source, Charset srcCharset, Charset destCharset)\n    {\n        if (null == srcCharset)\n        {\n            srcCharset = StandardCharsets.ISO_8859_1;\n        }\n\n        if (null == destCharset)\n        {\n            destCharset = StandardCharsets.UTF_8;\n        }\n\n        if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset))\n        {\n            return source;\n        }\n        return new String(source.getBytes(srcCharset), destCharset);\n    }\n\n    /**\n     * @return 系统字符集编码\n     */\n    public static String systemCharset()\n    {\n        return Charset.defaultCharset().name();\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/core/text/Convert.java",
    "content": "package com.oddfar.campus.common.core.text;\n\nimport com.oddfar.campus.common.utils.StringUtils;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.Charset;\nimport java.text.NumberFormat;\nimport java.util.Set;\n\n/**\n * 类型转换器\n *\n * @author ruoyi\n */\npublic class Convert {\n    /**\n     * 转换为字符串<br>\n     * 如果给定的值为null，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static String toStr(Object value, String defaultValue) {\n        if (null == value) {\n            return defaultValue;\n        }\n        if (value instanceof String) {\n            return (String) value;\n        }\n        return value.toString();\n    }\n\n    /**\n     * 转换为字符串<br>\n     * 如果给定的值为<code>null</code>，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static String toStr(Object value) {\n        return toStr(value, null);\n    }\n\n    /**\n     * 转换为字符<br>\n     * 如果给定的值为null，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Character toChar(Object value, Character defaultValue) {\n        if (null == value) {\n            return defaultValue;\n        }\n        if (value instanceof Character) {\n            return (Character) value;\n        }\n\n        final String valueStr = toStr(value, null);\n        return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0);\n    }\n\n    /**\n     * 转换为字符<br>\n     * 如果给定的值为<code>null</code>，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Character toChar(Object value) {\n        return toChar(value, null);\n    }\n\n    /**\n     * 转换为byte<br>\n     * 如果给定的值为<code>null</code>，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Byte toByte(Object value, Byte defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof Byte) {\n            return (Byte) value;\n        }\n        if (value instanceof Number) {\n            return ((Number) value).byteValue();\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            return Byte.parseByte(valueStr);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为byte<br>\n     * 如果给定的值为<code>null</code>，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Byte toByte(Object value) {\n        return toByte(value, null);\n    }\n\n    /**\n     * 转换为Short<br>\n     * 如果给定的值为<code>null</code>，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Short toShort(Object value, Short defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof Short) {\n            return (Short) value;\n        }\n        if (value instanceof Number) {\n            return ((Number) value).shortValue();\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            return Short.parseShort(valueStr.trim());\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为Short<br>\n     * 如果给定的值为<code>null</code>，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Short toShort(Object value) {\n        return toShort(value, null);\n    }\n\n    /**\n     * 转换为Number<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Number toNumber(Object value, Number defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof Number) {\n            return (Number) value;\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            return NumberFormat.getInstance().parse(valueStr);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为Number<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Number toNumber(Object value) {\n        return toNumber(value, null);\n    }\n\n    /**\n     * 转换为int<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Integer toInt(Object value, Integer defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof Integer) {\n            return (Integer) value;\n        }\n        if (value instanceof Number) {\n            return ((Number) value).intValue();\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            return Integer.parseInt(valueStr.trim());\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为int<br>\n     * 如果给定的值为<code>null</code>，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Integer toInt(Object value) {\n        return toInt(value, null);\n    }\n\n    /**\n     * 转换为Integer数组<br>\n     *\n     * @param str 被转换的值\n     * @return 结果\n     */\n    public static Integer[] toIntArray(String str) {\n        return toIntArray(\",\", str);\n    }\n\n    /**\n     * 转换为Long数组<br>\n     *\n     * @param str 被转换的值\n     * @return 结果\n     */\n    public static Long[] toLongArray(String str) {\n        return toLongArray(\",\", str);\n    }\n\n    /**\n     * 转换为Integer数组<br>\n     *\n     * @param split 分隔符\n     * @param split 被转换的值\n     * @return 结果\n     */\n    public static Integer[] toIntArray(String split, String str) {\n        if (StringUtils.isEmpty(str)) {\n            return new Integer[]{};\n        }\n        String[] arr = str.split(split);\n        final Integer[] ints = new Integer[arr.length];\n        for (int i = 0; i < arr.length; i++) {\n            final Integer v = toInt(arr[i], 0);\n            ints[i] = v;\n        }\n        return ints;\n    }\n\n    /**\n     * 转换为Long数组<br>\n     *\n     * @param split 分隔符\n     * @param str   被转换的值\n     * @return 结果\n     */\n    public static Long[] toLongArray(String split, String str) {\n        if (StringUtils.isEmpty(str)) {\n            return new Long[]{};\n        }\n        String[] arr = str.split(split);\n        final Long[] longs = new Long[arr.length];\n        for (int i = 0; i < arr.length; i++) {\n            final Long v = toLong(arr[i], null);\n            longs[i] = v;\n        }\n        return longs;\n    }\n\n    /**\n     * 转换为String数组<br>\n     *\n     * @param str 被转换的值\n     * @return 结果\n     */\n    public static String[] toStrArray(String str) {\n        return toStrArray(\",\", str);\n    }\n\n    /**\n     * 转换为String数组<br>\n     *\n     * @param split 分隔符\n     * @param split 被转换的值\n     * @return 结果\n     */\n    public static String[] toStrArray(String split, String str) {\n        return str.split(split);\n    }\n\n    /**\n     * 转换为long<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Long toLong(Object value, Long defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof Long) {\n            return (Long) value;\n        }\n        if (value instanceof Number) {\n            return ((Number) value).longValue();\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            // 支持科学计数法\n            return new BigDecimal(valueStr.trim()).longValue();\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为long<br>\n     * 如果给定的值为<code>null</code>，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Long toLong(Object value) {\n        return toLong(value, null);\n    }\n\n    /**\n     * 转换为double<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Double toDouble(Object value, Double defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof Double) {\n            return (Double) value;\n        }\n        if (value instanceof Number) {\n            return ((Number) value).doubleValue();\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            // 支持科学计数法\n            return new BigDecimal(valueStr.trim()).doubleValue();\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为double<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Double toDouble(Object value) {\n        return toDouble(value, null);\n    }\n\n    /**\n     * 转换为Float<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Float toFloat(Object value, Float defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof Float) {\n            return (Float) value;\n        }\n        if (value instanceof Number) {\n            return ((Number) value).floatValue();\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            return Float.parseFloat(valueStr.trim());\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为Float<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Float toFloat(Object value) {\n        return toFloat(value, null);\n    }\n\n    /**\n     * 转换为boolean<br>\n     * String支持的值为：true、false、yes、ok、no，1,0 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static Boolean toBool(Object value, Boolean defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof Boolean) {\n            return (Boolean) value;\n        }\n        String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        valueStr = valueStr.trim().toLowerCase();\n        switch (valueStr) {\n            case \"true\":\n            case \"yes\":\n            case \"ok\":\n            case \"1\":\n                return true;\n            case \"false\":\n            case \"no\":\n            case \"0\":\n                return false;\n            default:\n                return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为boolean<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static Boolean toBool(Object value) {\n        return toBool(value, null);\n    }\n\n    /**\n     * 转换为Enum对象<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     *\n     * @param clazz        Enum的Class\n     * @param value        值\n     * @param defaultValue 默认值\n     * @return Enum\n     */\n    public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value, E defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (clazz.isAssignableFrom(value.getClass())) {\n            @SuppressWarnings(\"unchecked\")\n            E myE = (E) value;\n            return myE;\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            return Enum.valueOf(clazz, valueStr);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为Enum对象<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<code>null</code><br>\n     *\n     * @param clazz Enum的Class\n     * @param value 值\n     * @return Enum\n     */\n    public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value) {\n        return toEnum(clazz, value, null);\n    }\n\n    /**\n     * 转换为BigInteger<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static BigInteger toBigInteger(Object value, BigInteger defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof BigInteger) {\n            return (BigInteger) value;\n        }\n        if (value instanceof Long) {\n            return BigInteger.valueOf((Long) value);\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            return new BigInteger(valueStr);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为BigInteger<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<code>null</code><br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static BigInteger toBigInteger(Object value) {\n        return toBigInteger(value, null);\n    }\n\n    /**\n     * 转换为BigDecimal<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value        被转换的值\n     * @param defaultValue 转换错误时的默认值\n     * @return 结果\n     */\n    public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) {\n        if (value == null) {\n            return defaultValue;\n        }\n        if (value instanceof BigDecimal) {\n            return (BigDecimal) value;\n        }\n        if (value instanceof Long) {\n            return new BigDecimal((Long) value);\n        }\n        if (value instanceof Double) {\n            return new BigDecimal((Double) value);\n        }\n        if (value instanceof Integer) {\n            return new BigDecimal((Integer) value);\n        }\n        final String valueStr = toStr(value, null);\n        if (StringUtils.isEmpty(valueStr)) {\n            return defaultValue;\n        }\n        try {\n            return new BigDecimal(valueStr);\n        } catch (Exception e) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * 转换为BigDecimal<br>\n     * 如果给定的值为空，或者转换失败，返回默认值<br>\n     * 转换失败不会报错\n     *\n     * @param value 被转换的值\n     * @return 结果\n     */\n    public static BigDecimal toBigDecimal(Object value) {\n        return toBigDecimal(value, null);\n    }\n\n    /**\n     * 将对象转为字符串<br>\n     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法\n     *\n     * @param obj 对象\n     * @return 字符串\n     */\n    public static String utf8Str(Object obj) {\n        return str(obj, CharsetKit.CHARSET_UTF_8);\n    }\n\n    /**\n     * 将对象转为字符串<br>\n     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法\n     *\n     * @param obj         对象\n     * @param charsetName 字符集\n     * @return 字符串\n     */\n    public static String str(Object obj, String charsetName) {\n        return str(obj, Charset.forName(charsetName));\n    }\n\n    /**\n     * 将对象转为字符串<br>\n     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法\n     *\n     * @param obj     对象\n     * @param charset 字符集\n     * @return 字符串\n     */\n    public static String str(Object obj, Charset charset) {\n        if (null == obj) {\n            return null;\n        }\n\n        if (obj instanceof String) {\n            return (String) obj;\n        } else if (obj instanceof byte[]) {\n            return str((byte[]) obj, charset);\n        } else if (obj instanceof Byte[]) {\n            byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj);\n            return str(bytes, charset);\n        } else if (obj instanceof ByteBuffer) {\n            return str((ByteBuffer) obj, charset);\n        }\n        return obj.toString();\n    }\n\n    /**\n     * 将byte数组转为字符串\n     *\n     * @param bytes   byte数组\n     * @param charset 字符集\n     * @return 字符串\n     */\n    public static String str(byte[] bytes, String charset) {\n        return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset));\n    }\n\n    /**\n     * 解码字节码\n     *\n     * @param data    字符串\n     * @param charset 字符集，如果此字段为空，则解码的结果取决于平台\n     * @return 解码后的字符串\n     */\n    public static String str(byte[] data, Charset charset) {\n        if (data == null) {\n            return null;\n        }\n\n        if (null == charset) {\n            return new String(data);\n        }\n        return new String(data, charset);\n    }\n\n    /**\n     * 将编码的byteBuffer数据转换为字符串\n     *\n     * @param data    数据\n     * @param charset 字符集，如果为空使用当前系统字符集\n     * @return 字符串\n     */\n    public static String str(ByteBuffer data, String charset) {\n        if (data == null) {\n            return null;\n        }\n\n        return str(data, Charset.forName(charset));\n    }\n\n    /**\n     * 将编码的byteBuffer数据转换为字符串\n     *\n     * @param data    数据\n     * @param charset 字符集，如果为空使用当前系统字符集\n     * @return 字符串\n     */\n    public static String str(ByteBuffer data, Charset charset) {\n        if (null == charset) {\n            charset = Charset.defaultCharset();\n        }\n        return charset.decode(data).toString();\n    }\n\n    // ----------------------------------------------------------------------- 全角半角转换\n\n    /**\n     * 半角转全角\n     *\n     * @param input String.\n     * @return 全角字符串.\n     */\n    public static String toSBC(String input) {\n        return toSBC(input, null);\n    }\n\n    /**\n     * 半角转全角\n     *\n     * @param input         String\n     * @param notConvertSet 不替换的字符集合\n     * @return 全角字符串.\n     */\n    public static String toSBC(String input, Set<Character> notConvertSet) {\n        char[] c = input.toCharArray();\n        for (int i = 0; i < c.length; i++) {\n            if (null != notConvertSet && notConvertSet.contains(c[i])) {\n                // 跳过不替换的字符\n                continue;\n            }\n\n            if (c[i] == ' ') {\n                c[i] = '\\u3000';\n            } else if (c[i] < '\\177') {\n                c[i] = (char) (c[i] + 65248);\n\n            }\n        }\n        return new String(c);\n    }\n\n    /**\n     * 全角转半角\n     *\n     * @param input String.\n     * @return 半角字符串\n     */\n    public static String toDBC(String input) {\n        return toDBC(input, null);\n    }\n\n    /**\n     * 替换全角为半角\n     *\n     * @param text          文本\n     * @param notConvertSet 不替换的字符集合\n     * @return 替换后的字符\n     */\n    public static String toDBC(String text, Set<Character> notConvertSet) {\n        char[] c = text.toCharArray();\n        for (int i = 0; i < c.length; i++) {\n            if (null != notConvertSet && notConvertSet.contains(c[i])) {\n                // 跳过不替换的字符\n                continue;\n            }\n\n            if (c[i] == '\\u3000') {\n                c[i] = ' ';\n            } else if (c[i] > '\\uFF00' && c[i] < '\\uFF5F') {\n                c[i] = (char) (c[i] - 65248);\n            }\n        }\n        String returnString = new String(c);\n\n        return returnString;\n    }\n\n    /**\n     * 数字金额大写转换 先写个完整的然后将如零拾替换成零\n     *\n     * @param n 数字\n     * @return 中文大写数字\n     */\n    public static String digitUppercase(double n) {\n        String[] fraction = {\"角\", \"分\"};\n        String[] digit = {\"零\", \"壹\", \"贰\", \"叁\", \"肆\", \"伍\", \"陆\", \"柒\", \"捌\", \"玖\"};\n        String[][] unit = {{\"元\", \"万\", \"亿\"}, {\"\", \"拾\", \"佰\", \"仟\"}};\n\n        String head = n < 0 ? \"负\" : \"\";\n        n = Math.abs(n);\n\n        String s = \"\";\n        for (int i = 0; i < fraction.length; i++) {\n            s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll(\"(零.)+\", \"\");\n        }\n        if (s.length() < 1) {\n            s = \"整\";\n        }\n        int integerPart = (int) Math.floor(n);\n\n        for (int i = 0; i < unit[0].length && integerPart > 0; i++) {\n            String p = \"\";\n            for (int j = 0; j < unit[1].length && n > 0; j++) {\n                p = digit[integerPart % 10] + unit[1][j] + p;\n                integerPart = integerPart / 10;\n            }\n            s = p.replaceAll(\"(零.)*零$\", \"\").replaceAll(\"^$\", \"零\") + unit[0][i] + s;\n        }\n        return head + s.replaceAll(\"(零.)*零元\", \"元\").replaceFirst(\"(零.)+\", \"\").replaceAll(\"(零.)+\", \"零\").replaceAll(\"^整$\", \"零元整\");\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/BaseEntity.java",
    "content": "package com.oddfar.campus.common.domain;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableLogic;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.io.Serializable;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Entity基类\n *\n * @author oddfar\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class BaseEntity implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n\n    @TableField(fill = FieldFill.INSERT)\n    private Date createTime;\n\n    @TableField(fill = FieldFill.INSERT)\n    private Long createUser;\n\n    @TableField(fill = FieldFill.UPDATE)\n    private Date updateTime;\n\n    @TableField(fill = FieldFill.UPDATE)\n    private Long updateUser;\n\n    @TableLogic\n    @TableField(fill = FieldFill.INSERT)\n    @JsonIgnore\n    private Integer delFlag;\n\n    @TableField(exist = false)\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private Map<String, Object> params = new HashMap<>();;\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/PageResult.java",
    "content": "package com.oddfar.campus.common.domain;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Data\npublic final class PageResult<T> implements Serializable {\n\n    private List<T> rows;\n\n    private long total;\n\n    public PageResult() {\n    }\n\n    public PageResult(List<T> rows, long total) {\n        this.rows = rows;\n        this.total = total;\n    }\n\n    public PageResult(int total) {\n        this.rows = new ArrayList<>();\n        this.total = total;\n    }\n\n    public static <T> PageResult<T> empty() {\n        return new PageResult<>(0);\n    }\n\n    public static <T> PageResult<T> empty(int total) {\n        return new PageResult<>(total);\n    }\n\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/R.java",
    "content": "package com.oddfar.campus.common.domain;\n\n\nimport com.alibaba.fastjson2.JSON;\nimport com.alibaba.fastjson2.TypeReference;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.oddfar.campus.common.enums.BizCodeEnum;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 返回数据\n */\npublic class R extends HashMap<String, Object> {\n    private static final long serialVersionUID = 1L;\n\n    public R setData(Object data) {\n        put(\"data\", data);\n        return this;\n    }\n\n    //利用fastjson进行逆转\n    public <T> T getData(String key, TypeReference<T> typeReference) {\n        Object data = get(key);//默认是map\n        String s = JSON.toJSONString(data);\n        T t = JSON.parseObject(s, typeReference);\n        return t;\n    }\n\n    //利用fastjson进行逆转\n    public <T> T getData(TypeReference<T> typeReference) {\n        Object data = get(\"data\");\n        String s = JSON.toJSONString(data);\n        T t = JSON.parseObject(s, typeReference);\n        return t;\n    }\n\n\n    public R() {\n        put(\"code\", 200);\n        put(\"msg\", \"success\");\n    }\n\n    public static R error() {\n        return error(500, \"未知异常，请联系管理员\");\n    }\n\n    public static R error(String msg) {\n        return error(500, msg);\n    }\n\n    public static R error(int code, String msg) {\n        R r = new R();\n        r.put(\"code\", code);\n        r.put(\"msg\", msg);\n        return r;\n    }\n\n    public static R error(BizCodeEnum bizCodeEnum) {\n        R r = new R();\n        r.put(\"code\", bizCodeEnum.getCode());\n        r.put(\"msg\", bizCodeEnum.getMsg());\n        return r;\n    }\n\n    public static R ok(String msg) {\n        R r = new R();\n        r.put(\"msg\", msg);\n        return r;\n    }\n\n    public static R ok(Object data) {\n        R r = new R();\n        r.put(\"data\", data);\n        return r;\n    }\n\n    public static R ok(Map<String, Object> map) {\n        R r = new R();\n        r.putAll(map);\n        return r;\n    }\n\n    public static R ok() {\n        return new R();\n    }\n\n    public R put(String key, Object value) {\n        super.put(key, value);\n        return this;\n    }\n\n    public R put(Object value) {\n        super.put(\"data\", value);\n        return this;\n    }\n\n    public R put(PageResult pageResult) {\n        super.put(\"rows\", pageResult.getRows());\n        super.put(\"total\", pageResult.getTotal());\n        return this;\n    }\n\n    public R put(IPage page) {\n        super.put(\"rows\", page.getRecords());\n        super.put(\"total\", page.getTotal());\n        return this;\n    }\n\n\n    public Integer getCode() {\n        return (Integer) this.get(\"code\");\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/TreeSelect.java",
    "content": "package com.oddfar.campus.common.domain;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.oddfar.campus.common.domain.entity.SysMenuEntity;\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\n\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Treeselect树结构实体类\n *\n * @author ruoyi\n */\npublic class TreeSelect implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 节点ID\n     */\n    private Long id;\n\n    /**\n     * 节点名称\n     */\n    private String label;\n\n    /**\n     * 子节点\n     */\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private List<TreeSelect> children;\n\n    public TreeSelect() {\n    }\n\n    public TreeSelect(Long id, String label) {\n        this.id = id;\n        this.label = label;\n    }\n\n\n    public TreeSelect(Long id, String label, List<SysResourceEntity> resources) {\n        this.id = id;\n        this.label = label;\n        this.children = resources.stream().map(TreeSelect::new).collect(Collectors.toList());\n    }\n\n    public TreeSelect(SysMenuEntity menu) {\n        this.id = menu.getMenuId();\n        this.label = menu.getMenuName();\n        this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());\n    }\n\n    public TreeSelect(SysResourceEntity resource) {\n        this.id = resource.getResourceId();\n        this.label = resource.getResourceName();\n    }\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getLabel() {\n        return label;\n    }\n\n    public void setLabel(String label) {\n        this.label = label;\n    }\n\n    public List<TreeSelect> getChildren() {\n        return children;\n    }\n\n    public void setChildren(List<TreeSelect> children) {\n        this.children = children;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysConfigEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Size;\n\n/**\n * 配置实体类\n *\n * @author oddfar\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@TableName(\"sys_config\")\npublic class SysConfigEntity extends BaseEntity {\n    private static final long serialVersionUID = 1L;\n\n    //    @JsonFormat(shape = JsonFormat.Shape.STRING)\n    @TableId(\"config_id\")\n    private Long configId;\n\n    /**\n     * 参数名称\n     */\n    @NotBlank(message = \"参数名称不能为空\")\n    @Size(min = 0, max = 100, message = \"参数名称不能超过100个字符\")\n    private String configName;\n\n    /**\n     * 参数键名\n     */\n    @NotBlank(message = \"参数键名不能为空\")\n    @Size(min = 0, max = 100, message = \"参数键名长度不能超过100个字符\")\n    private String configKey;\n\n    /**\n     * 参数键值\n     */\n    @NotBlank(message = \"参数键值不能为空\")\n    @Size(min = 0, max = 500, message = \"参数键值长度不能超过500个字符\")\n    private String configValue;\n\n    /**\n     * 系统内置（Y是 N否）\n     */\n    @NotBlank(message = \"系统内置不能为空\")\n    private String configType;\n\n    /**\n     * 所属分类的编码\n     */\n    @NotBlank(message = \"所属分类的编码不能为空\")\n    private String groupCode;\n\n    /**\n     * 备注\n     */\n    private String remark;\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysDictDataEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.oddfar.campus.common.constant.UserConstants;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Size;\n\n/**\n * 字典数据表 sys_dict_data\n *\n * @author oddfar\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@TableName(\"sys_dict_data\")\npublic class SysDictDataEntity extends BaseEntity {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 字典编码\n     */\n    @TableId(\"dict_code\")\n    private Long dictCode;\n\n    /**\n     * 字典排序\n     */\n    private Long dictSort;\n\n    /**\n     * 字典标签\n     */\n    @NotBlank(message = \"字典标签不能为空\")\n    @Size(min = 0, max = 100, message = \"字典标签长度不能超过100个字符\")\n    private String dictLabel;\n\n    /**\n     * 字典键值\n     */\n    @NotBlank(message = \"字典键值不能为空\")\n    @Size(min = 0, max = 100, message = \"字典键值长度不能超过100个字符\")\n    private String dictValue;\n\n    /**\n     * 字典类型\n     */\n    @NotBlank(message = \"字典类型不能为空\")\n    @Size(min = 0, max = 100, message = \"字典类型长度不能超过100个字符\")\n    private String dictType;\n\n    /**\n     * 样式属性（其他样式扩展）\n     */\n    @Size(min = 0, max = 100, message = \"样式属性长度不能超过100个字符\")\n    private String cssClass;\n\n    /**\n     * 表格字典样式\n     */\n    private String listClass;\n\n    /**\n     * 是否默认（Y是 N否）\n     */\n    private String isDefault;\n\n    /**\n     * 状态（0正常 1停用）\n     */\n    private String status;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    public boolean getDefault() {\n        return UserConstants.YES.equals(this.isDefault);\n    }\n\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysDictTypeEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Pattern;\nimport javax.validation.constraints.Size;\n\n/**\n * 字典类型表 sys_dict_type\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@TableName(\"sys_dict_type\")\npublic class SysDictTypeEntity extends BaseEntity {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 字典主键\n     */\n    @TableId(\"dict_id\")\n    private Long dictId;\n\n    /**\n     * 字典名称\n     */\n    @NotBlank(message = \"字典名称不能为空\")\n    @Size(min = 0, max = 100, message = \"字典类型名称长度不能超过100个字符\")\n    private String dictName;\n\n    /**\n     * 字典类型\n     */\n    @NotBlank(message = \"字典类型不能为空\")\n    @Size(min = 0, max = 100, message = \"字典类型类型长度不能超过100个字符\")\n    @Pattern(regexp = \"^[a-z][a-z0-9_]*$\", message = \"字典类型必须以字母开头，且只能为（小写字母，数字，下滑线）\")\n    private String dictType;\n\n\n    /**\n     * 状态（0正常 1停用）\n     */\n    private String status;\n\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysLoginLogEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport lombok.Data;\n\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\nimport javax.validation.constraints.NotNull;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 系统访问记录表 sys_logininfor\n *\n * @author ruoyi\n */\n@Data\n@TableName(\"sys_log_login\")\npublic class SysLoginLogEntity {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * ID\n     */\n    @TableId(\"info_id\")\n    private Long infoId;\n\n    /**\n     * 登录成功的用户id\n     */\n    private Long userId;\n\n    /**\n     * 用户账号\n     */\n    private String userName;\n\n    /**\n     * 登录状态 0成功 1失败\n     */\n    private String status;\n\n    /**\n     * 登录IP地址\n     */\n    private String ipaddr;\n\n    /**\n     * 登录地点\n     */\n    private String loginLocation;\n\n    /**\n     * 浏览器类型\n     */\n    private String browser;\n\n    /**\n     * 操作系统\n     */\n    private String os;\n\n    /**\n     * 提示消息\n     */\n    private String msg;\n\n    /**\n     * 访问时间\n     */\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n    private Date loginTime;\n\n\n    private static final Integer PAGE_NUM = 1;\n    private static final Integer PAGE_SIZE = 10;\n\n    @TableField(exist = false)\n    private Map<String, Object> params;\n\n    @NotNull(message = \"页码不能为空\")\n    @Min(value = 1, message = \"页码最小值为 1\")\n    @TableField(exist = false)\n    @JsonIgnore\n    private Integer pageNum = PAGE_NUM;\n\n    @NotNull(message = \"每页条数不能为空\")\n    @Min(value = 1, message = \"每页条数最小值为 1\")\n    @Max(value = 100, message = \"每页条数最大值为 100\")\n    @TableField(exist = false)\n    @JsonIgnore\n    private Integer pageSize = PAGE_SIZE;\n\n    public Map<String, Object> getParams() {\n        if (params == null) {\n            params = new HashMap<>();\n        }\n        return params;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysMenuEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\nimport javax.validation.constraints.Size;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * 菜单权限表 sys_menu\n * （根据若依项目修改）\n *\n * @author oddfar\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@TableName(\"sys_menu\")\npublic class SysMenuEntity extends BaseEntity {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 菜单ID\n     */\n    @TableId(\"menu_id\")\n    private Long menuId;\n\n    /**\n     * 菜单名称\n     */\n    @NotBlank(message = \"菜单名称不能为空\")\n    @Size(min = 0, max = 50, message = \"菜单名称长度不能超过50个字符\")\n    private String menuName;\n\n    /**\n     * 父菜单名称\n     */\n    @TableField(exist = false)\n    private String parentName;\n\n    /**\n     * 父菜单ID\n     */\n    private Long parentId;\n\n    /**\n     * 显示顺序\n     */\n    @NotNull(message = \"显示顺序不能为空\")\n    private Integer orderNum;\n\n    /**\n     * 路由地址\n     */\n    @Size(min = 0, max = 200, message = \"路由地址不能超过200个字符\")\n    private String path;\n\n    /**\n     * 组件路径\n     */\n    @Size(min = 0, max = 200, message = \"组件路径不能超过255个字符\")\n    private String component;\n\n    /**\n     * 路由参数\n     */\n    private String query;\n\n    /**\n     * 是否为外链（0是 1否）\n     */\n    private String isFrame;\n\n    /**\n     * 是否缓存（0缓存 1不缓存）\n     */\n    private String isCache;\n\n    /**\n     * 类型（M目录 C菜单 F按钮）\n     */\n    @NotBlank(message = \"菜单类型不能为空\")\n    private String menuType;\n\n    /**\n     * 显示状态（0显示 1隐藏）\n     */\n    private String visible;\n\n    /**\n     * 菜单状态（0显示 1隐藏）\n     */\n    private String status;\n\n    /**\n     * 权限字符串\n     */\n    @Size(min = 0, max = 100, message = \"权限标识长度不能超过100个字符\")\n    private String perms;\n\n    /**\n     * 菜单图标\n     */\n    private String icon;\n\n    /** 备注 */\n    private String remark;\n\n    /**\n     * 子菜单\n     */\n    @TableField(exist = false)\n    private List<SysMenuEntity> children = new ArrayList<SysMenuEntity>();\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysOperLogEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport lombok.Data;\n\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\nimport javax.validation.constraints.NotNull;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 日志实体类 sys_log\n */\n@Data\n@TableName(\"sys_log_oper\")\npublic class SysOperLogEntity {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 日志主键\n     */\n    @TableId(\"oper_id\")\n    private Long operId;\n\n    /**\n     * 服务名称，一般为spring.application.name\n     */\n    private String appName;\n\n    /**\n     * 操作模块\n     */\n    private String logName;\n\n    /**\n     * 日志记录内容\n     */\n    private String logContent;\n\n    /**\n     * 请求方法\n     */\n    private String method;\n\n    /**\n     * 请求方式\n     */\n    private String requestMethod;\n\n    /**\n     * 操作人员 id\n     */\n    private Long operUserId;\n\n    /**\n     * 请求url\n     */\n    private String operUrl;\n\n    /**\n     * 操作地址\n     */\n    private String operIp;\n\n\n    /**\n     * 请求参数\n     */\n    private String operParam;\n\n    /**\n     * 返回参数\n     */\n    private String jsonResult;\n\n\n    /**\n     * 操作状态（0正常 1异常）\n     */\n    private Integer status;\n\n    /**\n     * 错误消息\n     */\n    private String errorMsg;\n\n    /**\n     * 操作时间\n     */\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n    private Date operTime;\n\n\n    private static final Integer PAGE_NUM = 1;\n    private static final Integer PAGE_SIZE = 10;\n\n    @TableField(exist = false)\n    private Map<String, Object> params;\n\n    @NotNull(message = \"页码不能为空\")\n    @Min(value = 1, message = \"页码最小值为 1\")\n    @TableField(exist = false)\n    @JsonIgnore\n    private Integer pageNum = PAGE_NUM;\n\n    @NotNull(message = \"每页条数不能为空\")\n    @Min(value = 1, message = \"每页条数最小值为 1\")\n    @Max(value = 100, message = \"每页条数最大值为 100\")\n    @TableField(exist = false)\n    @JsonIgnore\n    private Integer pageSize = PAGE_SIZE;\n\n    public Map<String, Object> getParams() {\n        if (params == null) {\n            params = new HashMap<>();\n        }\n        return params;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysResourceEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Data\n@EqualsAndHashCode(callSuper = true)\n@TableName(\"sys_resource\")\npublic class SysResourceEntity extends BaseEntity {\n    private static final long serialVersionUID = 1L;\n\n    @TableId(\"resource_id\")\n    private Long resourceId;\n\n    /**\n     * 应用编码\n     */\n    private String appCode;\n\n    /**\n     * 资源编码\n     */\n    private String resourceCode;\n\n    /**\n     * 资源名称\n     */\n    private String resourceName;\n\n    /**\n     * 类名称\n     */\n    private String className;\n\n    /**\n     * 方法名称\n     */\n    private String methodName;\n\n    /**\n     * 资源模块编码，一般为控制器类名排除Controller\n     */\n    @TableField(exist = false)\n    private String  modular_code;\n\n    /**\n     * 资源模块名称，一般为控制器名称\n     */\n    private String modularName;\n\n    /**\n     * 资源url\n     */\n    private String url;\n\n    /**\n     * http请求方法\n     */\n    private String httpMethod;\n\n    /**\n     * 资源的业务类型：1-业务类，2-系统类\n     */\n    private Integer resourceBizType;\n\n    /**\n     * 是否需要鉴权：Y-是，N-否\n     */\n    private String requiredPermissionFlag;\n\n    /**\n     * 子菜单\n     */\n    @TableField(exist = false)\n    private List<SysResourceEntity> children = new ArrayList<SysResourceEntity>();\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysRoleEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\n\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Size;\nimport java.util.Set;\n\n\n/**\n * 角色表 sys_role\n *\n * @author ruoyi\n */\n@TableName(\"sys_role\")\n@EqualsAndHashCode(callSuper = true)\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class SysRoleEntity extends BaseEntity {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 角色ID\n     */\n    @TableId(\"role_id\")\n    private Long roleId;\n\n    /**\n     * 角色名称\n     */\n    @NotBlank(message = \"角色名称不能为空\")\n    @Size(min = 0, max = 30, message = \"角色名称长度不能超过30个字符\")\n    private String roleName;\n\n    /**\n     * 角色权限\n     */\n    @NotBlank(message = \"权限字符不能为空\")\n    @Size(min = 0, max = 100, message = \"权限字符长度不能超过100个字符\")\n    private String roleKey;\n\n    /**\n     * 角色排序\n     */\n    @NotBlank(message = \"显示顺序不能为空\")\n    private String roleSort;\n\n\n    /**\n     * 菜单树选择项是否关联显示（ 0：父子不互相关联显示 1：父子互相关联显示）\n     */\n    private boolean menuCheckStrictly;\n\n\n    /**\n     * 角色状态（0正常 1停用）\n     */\n    private String status;\n\n    /**\n     * 用户是否存在此角色标识 默认不存在\n     */\n    @TableField(exist = false)\n    private boolean flag = false;\n\n    /**\n     * 菜单组\n     */\n    @TableField(exist = false)\n    private Long[] menuIds;\n\n    /**\n     * 角色菜单权限\n     */\n    @TableField(exist = false)\n    private Set<String> permissions;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    public SysRoleEntity(Long roleId) {\n        this.roleId = roleId;\n    }\n\n    public boolean isAdmin() {\n        return isAdmin(this.roleId);\n    }\n\n    public static boolean isAdmin(Long roleId) {\n        return roleId != null && 1L == roleId;\n    }\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysRoleMenuEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * 角色和菜单关联 sys_role_menu\n */\n@Data\n@TableName(\"sys_role_menu\")\npublic class SysRoleMenuEntity implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 角色ID\n     */\n    @TableId(type = IdType.INPUT)\n    private Long roleId;\n\n    /**\n     * 菜单ID\n     */\n    private Long menuId;\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysRoleResourceEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * 角色与接口资源关系表\n */\n@Data\n@TableName(\"sys_role_resource\")\npublic class SysRoleResourceEntity implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 资源编码\n     */\n    private String resourceCode;\n\n    /**\n     * 角色ID\n     */\n    @TableId(type = IdType.INPUT)\n    private Long roleId;\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysUserEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport com.oddfar.campus.common.validator.Xss;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\n\nimport javax.validation.constraints.Email;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Size;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * 用户对象 sys_user\n *\n * @author oddfar\n * @since 1.0.0 2022-09-24\n */\n\n@TableName(\"sys_user\")\n@EqualsAndHashCode(callSuper = true)\n@Data\n@NoArgsConstructor\npublic class SysUserEntity extends BaseEntity  {\n    private static final long serialVersionUID = 1L;\n\n    /** 用户ID */\n    @TableId(\"user_id\")\n    private Long userId;\n\n    /** 用户账号 */\n    @Xss(message = \"用户账号不能包含脚本字符\")\n    @NotBlank(message = \"用户账号不能为空\")\n    @Size(min = 0, max = 30, message = \"用户账号长度不能超过30个字符\")\n    private String userName;\n\n    /** 用户昵称 */\n    @Xss(message = \"用户昵称不能包含脚本字符\")\n    @Size(min = 0, max = 30, message = \"用户昵称长度不能超过30个字符\")\n    private String nickName;\n\n    /** 用户邮箱 */\n    @Email(message = \"邮箱格式不正确\")\n    @Size(min = 0, max = 50, message = \"邮箱长度不能超过50个字符\")\n    private String email;\n\n    /** 手机号码 */\n    @Size(min = 0, max = 11, message = \"手机号码长度不能超过11个字符\")\n    private String phonenumber;\n\n    /** 用户性别 */\n    private String sex;\n\n    /** 用户头像 */\n    private String avatar;\n\n    /** 密码 */\n    private String password;\n\n    /** 帐号状态（0正常 1停用） */\n    private String status;\n\n    /** 最后登录IP */\n    private String loginIp;\n\n    /** 最后登录时间 */\n    private Date loginDate;\n\n    /** 备注 */\n    private String remark;\n\n    /** 角色对象 */\n    @TableField(exist = false)\n    private List<SysRoleEntity> roles;\n\n    /** 资源对象 */\n//    @TableField(exist = false)\n//    private List<SysResourceEntity> resources;\n\n    /** 角色组 */\n    @TableField(exist = false)\n    private Long[] roleIds;\n\n    /** 岗位组 */\n    @TableField(exist = false)\n    private Long[] postIds;\n\n    /** 角色ID */\n    @TableField(exist = false)\n    private Long roleId;\n\n    public SysUserEntity(Long userId){\n        this.userId = userId;\n    }\n\n    public boolean isAdmin()\n    {\n        return isAdmin(this.userId);\n    }\n\n    public static boolean isAdmin(Long userId)\n    {\n        return userId != null && 1L == userId;\n    }\n\n}"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysUserRoleEntity.java",
    "content": "package com.oddfar.campus.common.domain.entity;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * 用户和角色关联 sys_user_role\n */\n@Data\n@TableName(\"sys_user_role\")\npublic class SysUserRoleEntity implements Serializable {\n    private static final long serialVersionUID = 1L;\n    /**\n     * 用户ID\n     */\n    @TableId(type = IdType.INPUT)\n    private Long userId;\n\n    /**\n     * 角色ID\n     */\n    private Long roleId;\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/model/LoginBody.java",
    "content": "package com.oddfar.campus.common.domain.model;\n\n/**\n * 用户登录对象\n *\n * @author ruoyi\n */\npublic class LoginBody {\n    /**\n     * 用户名\n     */\n    private String username;\n\n    /**\n     * 用户密码\n     */\n    private String password;\n\n    /**\n     * 验证码\n     */\n    private String code;\n\n    /**\n     * 唯一标识\n     */\n    private String uuid;\n\n    public String getUsername() {\n        return username;\n    }\n\n    public void setUsername(String username) {\n        this.username = username;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    public String getCode() {\n        return code;\n    }\n\n    public void setCode(String code) {\n        this.code = code;\n    }\n\n    public String getUuid() {\n        return uuid;\n    }\n\n    public void setUuid(String uuid) {\n        this.uuid = uuid;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/model/LoginUser.java",
    "content": "package com.oddfar.campus.common.domain.model;\n\nimport com.alibaba.fastjson2.annotation.JSONField;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport org.springframework.security.core.GrantedAuthority;\nimport org.springframework.security.core.userdetails.UserDetails;\n\nimport java.util.Collection;\nimport java.util.Set;\n\n\n/**\n * 登录用户身份权限\n *\n * @author ruoyi\n */\n@Data\n@AllArgsConstructor\npublic class LoginUser implements UserDetails {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * 用户唯一标识\n     */\n    private String token;\n\n\n    /**\n     * 过期时间\n     */\n    private Long expireTime;\n\n    /**\n     * 登录IP地址\n     */\n    private String ipaddr;\n\n\n    /**\n     * 权限列表\n     */\n    private Set<String> permissions;\n\n    /**\n     * 资源列表\n     */\n    private Set<String> resources;\n\n    /**\n     * 用户信息\n     */\n    private SysUserEntity user;\n\n    public LoginUser(Long userId, SysUserEntity user, Set<String> permissions, Set<String> resources) {\n        this.userId = userId;\n        this.user = user;\n        this.permissions = permissions;\n        this.resources = resources;\n    }\n\n\n    @JSONField(serialize = false)\n    @Override\n    public String getPassword() {\n        return user.getPassword();\n    }\n\n    @Override\n    public String getUsername() {\n        return user.getUserName();\n    }\n\n    /**\n     * 账户是否未过期,过期无法验证\n     */\n    @JSONField(serialize = false)\n    @Override\n    public boolean isAccountNonExpired() {\n        return true;\n    }\n\n    /**\n     * 指定用户是否解锁,锁定的用户无法进行身份验证\n     *\n     * @return\n     */\n    @JSONField(serialize = false)\n    @Override\n    public boolean isAccountNonLocked() {\n        return true;\n    }\n\n    /**\n     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证\n     *\n     * @return\n     */\n    @JSONField(serialize = false)\n    @Override\n    public boolean isCredentialsNonExpired() {\n        return true;\n    }\n\n    /**\n     * 是否可用 ,禁用的用户不能身份验证\n     *\n     * @return\n     */\n    @JSONField(serialize = false)\n    @Override\n    public boolean isEnabled() {\n        return true;\n    }\n\n\n    @Override\n    public Collection<? extends GrantedAuthority> getAuthorities() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/model/LoginUserToken.java",
    "content": "package com.oddfar.campus.common.domain.model;\n\nimport lombok.Data;\n\n/**\n * 登录用户身份信息\n *\n */\n@Data\npublic class LoginUserToken {\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * 用户唯一标识\n     */\n    private String token;\n\n    /**\n     * 登录时间\n     */\n    private Long loginTime;\n\n    /**\n     * 过期时间\n     */\n    private Long expireTime;\n\n    /**\n     * 登录IP地址\n     */\n    private String ipaddr;\n\n    /**\n     * 登录地点\n     */\n    private String loginLocation;\n\n    /**\n     * 浏览器类型\n     */\n    private String browser;\n\n    /**\n     * 操作系统\n     */\n    private String os;\n\n    public LoginUserToken() {\n    }\n\n    public LoginUserToken(LoginUser loginUser) {\n        this.userId = loginUser.getUserId();\n        this.token = loginUser.getToken();\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/model/RegisterBody.java",
    "content": "package com.oddfar.campus.common.domain.model;\n\nimport lombok.Data;\n\n/**\n * 用户注册对象\n */\n@Data\npublic class RegisterBody extends LoginBody {\n\n    /**\n     * 邮箱\n     */\n    private String email;\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/model/SysRoleAuth.java",
    "content": "package com.oddfar.campus.common.domain.model;\n\nimport com.oddfar.campus.common.domain.entity.SysRoleResourceEntity;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class SysRoleAuth {\n\n    /**\n     * 角色id\n     */\n    Long roleID;\n\n    /**\n     * 权限字符\n     */\n    String perms;\n\n    /**\n     * 资源编码\n     */\n   String resourceCode;\n\n    public SysRoleAuth(SysRoleResourceEntity roleResourceEntity) {\n        this.roleID = roleResourceEntity.getRoleId();\n        this.resourceCode = roleResourceEntity.getResourceCode();\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/model/SysRoleAuthList.java",
    "content": "package com.oddfar.campus.common.domain.model;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.Set;\n\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class SysRoleAuthList {\n\n    /**\n     * 角色id\n     */\n    Long roleID;\n\n    /**\n     * 权限字符\n     */\n    Set<String> perms;\n\n    /**\n     * 资源编码\n     */\n    Set<String> resourceCode;\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/domain/vo/RouterVo.java",
    "content": "package com.oddfar.campus.common.domain.vo;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.oddfar.campus.common.utils.MetaVo;\n\nimport java.util.List;\n\n/**\n * 路由配置信息\n *\n * @author ruoyi\n */\n@JsonInclude(JsonInclude.Include.NON_EMPTY)\npublic class RouterVo {\n    /**\n     * 路由名字\n     */\n    private String name;\n\n    /**\n     * 路由地址\n     */\n    private String path;\n\n    /**\n     * 是否隐藏路由，当设置 true 的时候该路由不会再侧边栏出现\n     */\n    private boolean hidden;\n\n    /**\n     * 重定向地址，当设置 noRedirect 的时候该路由在面包屑导航中不可被点击\n     */\n    private String redirect;\n\n    /**\n     * 组件地址\n     */\n    private String component;\n\n    /**\n     * 路由参数：如 {\"id\": 1, \"name\": \"ry\"}\n     */\n    private String query;\n\n    /**\n     * 当你一个路由下面的 children 声明的路由大于1个时，自动会变成嵌套的模式--如组件页面\n     */\n    private Boolean alwaysShow;\n\n    /**\n     * 其他元素\n     */\n    private MetaVo meta;\n\n    /**\n     * 子路由\n     */\n    private List<RouterVo> children;\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public boolean getHidden() {\n        return hidden;\n    }\n\n    public void setHidden(boolean hidden) {\n        this.hidden = hidden;\n    }\n\n    public String getRedirect() {\n        return redirect;\n    }\n\n    public void setRedirect(String redirect) {\n        this.redirect = redirect;\n    }\n\n    public String getComponent() {\n        return component;\n    }\n\n    public void setComponent(String component) {\n        this.component = component;\n    }\n\n    public String getQuery() {\n        return query;\n    }\n\n    public void setQuery(String query) {\n        this.query = query;\n    }\n\n    public Boolean getAlwaysShow() {\n        return alwaysShow;\n    }\n\n    public void setAlwaysShow(Boolean alwaysShow) {\n        this.alwaysShow = alwaysShow;\n    }\n\n    public MetaVo getMeta() {\n        return meta;\n    }\n\n    public void setMeta(MetaVo meta) {\n        this.meta = meta;\n    }\n\n    public List<RouterVo> getChildren() {\n        return children;\n    }\n\n    public void setChildren(List<RouterVo> children) {\n        this.children = children;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/enums/BizCodeEnum.java",
    "content": "package com.oddfar.campus.common.enums;\n\n/***\n * 错误码和错误信息定义类\n * 1. 错误码定义规则为5为数字\n * 2. 前两位表示业务场景，最后三位表示错误码。例如：100001。10:通用 001:系统未知异常\n * 3. 维护错误码后需要维护错误描述，将他们定义为枚举形式\n * 错误码列表：\n *  10: 通用\n *      001：参数格式校验\n *\n *\n */\npublic enum BizCodeEnum {\n    UNKNOWN_EXCEPTION(10000, \"系统未知错误\"),\n    VALID_EXCEPTION(10001, \"参数校验异常\");\n\n    private Integer code;\n    private String msg;\n\n    private BizCodeEnum(Integer code, String msg) {\n        this.code = code;\n        this.msg = msg;\n    }\n\n    public Integer getCode() {\n        return code;\n    }\n\n    public String getMsg() {\n        return msg;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/enums/BusinessStatus.java",
    "content": "package com.oddfar.campus.common.enums;\n\n/**\n * 操作状态\n *\n * @author ruoyi\n */\npublic enum BusinessStatus {\n    /**\n     * 成功\n     */\n    SUCCESS,\n\n    /**\n     * 失败\n     */\n    FAIL,\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/enums/ResBizTypeEnum.java",
    "content": "package com.oddfar.campus.common.enums;\n\nimport lombok.Getter;\n\n/**\n * 接口资源的类别\n */\n@Getter\npublic enum ResBizTypeEnum {\n\n    /**\n     * 业务类\n     */\n    BUSINESS(1, \"业务类\"),\n\n    /**\n     * 系统类\n     */\n    SYSTEM(2, \"系统类\");\n\n    private final Integer code;\n\n    private final String message;\n\n    ResBizTypeEnum(Integer code, String message) {\n        this.code = code;\n        this.message = message;\n    }\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/enums/SensitiveStrategy.java",
    "content": "package com.oddfar.campus.common.enums;\n\nimport cn.hutool.core.util.DesensitizedUtil;\nimport lombok.AllArgsConstructor;\n\nimport java.util.function.Function;\n\n/**\n * 脱敏策略\n *\n * @author Yjoioooo\n * @version 3.6.0\n */\n@AllArgsConstructor\npublic enum SensitiveStrategy {\n\n    /**\n     * 身份证脱敏\n     */\n    ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)),\n\n    /**\n     * 手机号脱敏\n     */\n    PHONE(DesensitizedUtil::mobilePhone),\n\n    /**\n     * 地址脱敏\n     */\n    ADDRESS(s -> DesensitizedUtil.address(s, 8)),\n\n    /**\n     * 邮箱脱敏\n     */\n    EMAIL(DesensitizedUtil::email),\n\n    /**\n     * 银行卡\n     */\n    BANK_CARD(DesensitizedUtil::bankCard);\n\n    //可自行添加其他脱敏策略\n    private final Function<String, String> desensitizer;\n\n    public Function<String, String> desensitizer() {\n        return desensitizer;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/enums/UserStatusEnum.java",
    "content": "package com.oddfar.campus.common.enums;\n\n/**\n * 用户状态\n *\n * @author ruoyi\n */\npublic enum UserStatusEnum {\n    OK(\"0\", \"正常\"), DISABLE(\"1\", \"停用\"), DELETED(\"2\", \"删除\");\n\n    private final String code;\n    private final String info;\n\n    UserStatusEnum(String code, String info) {\n        this.code = code;\n        this.info = info;\n    }\n\n    public String getCode() {\n        return code;\n    }\n\n    public String getInfo() {\n        return info;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/ServiceException.java",
    "content": "package com.oddfar.campus.common.exception;\n\n/**\n * 业务异常\n *\n * @author ruoyi\n */\npublic final class ServiceException extends RuntimeException {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 错误码\n     */\n    private Integer code;\n\n    /**\n     * 错误提示\n     */\n    private String message;\n\n    /**\n     * 错误明细，内部调试错误\n     */\n    private String detailMessage;\n\n    /**\n     * 空构造方法，避免反序列化问题\n     */\n    public ServiceException() {\n    }\n\n    public ServiceException(String message) {\n        this.message = message;\n    }\n\n\n    public ServiceException(String message, Integer code) {\n        this.message = message;\n        this.code = code;\n    }\n\n    public String getDetailMessage() {\n        return detailMessage;\n    }\n\n    @Override\n    public String getMessage() {\n        return message;\n    }\n\n    public Integer getCode() {\n        return code;\n    }\n\n    public ServiceException setMessage(String message) {\n        this.message = message;\n        return this;\n    }\n\n    public ServiceException setDetailMessage(String detailMessage) {\n        this.detailMessage = detailMessage;\n        return this;\n    }\n}"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/UtilException.java",
    "content": "package com.oddfar.campus.common.exception;\n\n/**\n * 工具类异常\n *\n * @author ruoyi\n */\npublic class UtilException extends RuntimeException {\n    private static final long serialVersionUID = 8247610319171014183L;\n\n    public UtilException(Throwable e) {\n        super(e.getMessage(), e);\n    }\n\n    public UtilException(String message) {\n        super(message);\n    }\n\n    public UtilException(String message, Throwable throwable) {\n        super(message, throwable);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/base/BaseException.java",
    "content": "package com.oddfar.campus.common.exception.base;\n\n\nimport com.oddfar.campus.common.utils.MessageUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n/**\n * 基础异常\n *\n * @author ruoyi\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class BaseException extends RuntimeException {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 所属模块\n     */\n    private String module;\n\n    /**\n     * 错误码\n     */\n    private String code;\n\n    /**\n     * 错误码对应的参数\n     */\n    private Object[] args;\n\n    /**\n     * 错误消息\n     */\n    private String defaultMessage;\n\n\n    @Override\n    public String getMessage() {\n        String message = null;\n        if (!StringUtils.isEmpty(code)) {\n            message = MessageUtils.message(code, args);\n        }\n        if (message == null) {\n            message = defaultMessage;\n        }\n        return message;\n    }\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/file/FileException.java",
    "content": "package com.oddfar.campus.common.exception.file;\n\nimport com.oddfar.campus.common.exception.base.BaseException;\n\n/**\n * 文件信息异常类\n *\n * @author ruoyi\n */\npublic class FileException extends BaseException {\n    private static final long serialVersionUID = 1L;\n\n    public FileException(String code, Object[] args) {\n        super(\"file\", code, args, null);\n    }\n\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/file/FileNameLengthLimitExceededException.java",
    "content": "package com.oddfar.campus.common.exception.file;\n\n/**\n * 文件名称超长限制异常类\n * \n * @author ruoyi\n */\npublic class FileNameLengthLimitExceededException extends FileException\n{\n    private static final long serialVersionUID = 1L;\n\n    public FileNameLengthLimitExceededException(int defaultFileNameLength)\n    {\n        super(\"upload.filename.exceed.length\", new Object[] { defaultFileNameLength });\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/file/FileSizeLimitExceededException.java",
    "content": "package com.oddfar.campus.common.exception.file;\n\n/**\n * 文件名大小限制异常类\n * \n * @author ruoyi\n */\npublic class FileSizeLimitExceededException extends FileException\n{\n    private static final long serialVersionUID = 1L;\n\n    public FileSizeLimitExceededException(long defaultMaxSize)\n    {\n        super(\"upload.exceed.maxSize\", new Object[] { defaultMaxSize });\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/file/InvalidExtensionException.java",
    "content": "package com.oddfar.campus.common.exception.file;\n\nimport org.apache.commons.fileupload.FileUploadException;\n\nimport java.util.Arrays;\n\n/**\n * 文件上传 误异常类\n *\n * @author ruoyi\n */\npublic class InvalidExtensionException extends FileUploadException {\n    private static final long serialVersionUID = 1L;\n\n    private String[] allowedExtension;\n    private String extension;\n    private String filename;\n\n    public InvalidExtensionException(String[] allowedExtension, String extension, String filename) {\n        super(\"文件[\" + filename + \"]后缀[\" + extension + \"]不正确，请上传\" + Arrays.toString(allowedExtension) + \"格式\");\n        this.allowedExtension = allowedExtension;\n        this.extension = extension;\n        this.filename = filename;\n    }\n\n    public String[] getAllowedExtension() {\n        return allowedExtension;\n    }\n\n    public String getExtension() {\n        return extension;\n    }\n\n    public String getFilename() {\n        return filename;\n    }\n\n    public static class InvalidImageExtensionException extends InvalidExtensionException {\n        private static final long serialVersionUID = 1L;\n\n        public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) {\n            super(allowedExtension, extension, filename);\n        }\n    }\n\n    public static class InvalidFlashExtensionException extends InvalidExtensionException {\n        private static final long serialVersionUID = 1L;\n\n        public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) {\n            super(allowedExtension, extension, filename);\n        }\n    }\n\n    public static class InvalidMediaExtensionException extends InvalidExtensionException {\n        private static final long serialVersionUID = 1L;\n\n        public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) {\n            super(allowedExtension, extension, filename);\n        }\n    }\n\n    public static class InvalidVideoExtensionException extends InvalidExtensionException {\n        private static final long serialVersionUID = 1L;\n\n        public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) {\n            super(allowedExtension, extension, filename);\n        }\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/user/CaptchaException.java",
    "content": "package com.oddfar.campus.common.exception.user;\n\n/**\n * 验证码错误异常类\n *\n * @author ruoyi\n */\npublic class CaptchaException extends UserException {\n    private static final long serialVersionUID = 1L;\n\n    public CaptchaException() {\n        super(\"user.jcaptcha.error\", null);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/user/CaptchaExpireException.java",
    "content": "package com.oddfar.campus.common.exception.user;\n\n/**\n * 验证码失效异常类\n *\n * @author ruoyi\n */\npublic class CaptchaExpireException extends UserException {\n    private static final long serialVersionUID = 1L;\n\n    public CaptchaExpireException() {\n        super(\"user.jcaptcha.expire\", null);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/user/UserException.java",
    "content": "package com.oddfar.campus.common.exception.user;\n\nimport com.oddfar.campus.common.exception.base.BaseException;\n\n/**\n * 用户信息异常类\n *\n * @author ruoyi\n */\npublic class UserException extends BaseException {\n    private static final long serialVersionUID = 1L;\n\n    public UserException(String code, Object[] args) {\n        super(\"user\", code, args, null);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/user/UserPasswordNotMatchException.java",
    "content": "package com.oddfar.campus.common.exception.user;\n\n/**\n * 用户密码不正确或不符合规范异常类\n *\n * @author ruoyi\n */\npublic class UserPasswordNotMatchException extends UserException {\n    private static final long serialVersionUID = 1L;\n\n    public UserPasswordNotMatchException() {\n        super(\"user.password.not.match\", null);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/exception/user/UserPasswordRetryLimitExceedException.java",
    "content": "package com.oddfar.campus.common.exception.user;\n\n/**\n * 用户错误最大次数异常类\n * \n * @author ruoyi\n */\npublic class UserPasswordRetryLimitExceedException extends UserException\n{\n    private static final long serialVersionUID = 1L;\n\n    public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime)\n    {\n        super(\"user.password.retry.limit.exceed\", new Object[] { retryLimitCount, lockTime });\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/filter/PropertyPreExcludeFilter.java",
    "content": "package com.oddfar.campus.common.filter;\n\nimport com.alibaba.fastjson2.filter.SimplePropertyPreFilter;\n\n/**\n * 排除JSON敏感属性\n *\n * @author ruoyi\n */\npublic class PropertyPreExcludeFilter extends SimplePropertyPreFilter {\n    public PropertyPreExcludeFilter() {\n    }\n\n    public PropertyPreExcludeFilter addExcludes(String... filters) {\n        for (int i = 0; i < filters.length; i++) {\n            this.getExcludes().add(filters[i]);\n        }\n        return this;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/filter/RepeatedlyRequestWrapper.java",
    "content": "package com.oddfar.campus.common.filter;\n\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.utils.http.HttpHelper;\n\nimport javax.servlet.ReadListener;\nimport javax.servlet.ServletInputStream;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletRequestWrapper;\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\n\n/**\n * 构建可重复读取inputStream的request\n * \n * @author ruoyi\n */\npublic class RepeatedlyRequestWrapper extends HttpServletRequestWrapper\n{\n    private final byte[] body;\n\n    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException\n    {\n        super(request);\n        request.setCharacterEncoding(Constants.UTF8);\n        response.setCharacterEncoding(Constants.UTF8);\n\n        body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8);\n    }\n\n    @Override\n    public BufferedReader getReader() throws IOException\n    {\n        return new BufferedReader(new InputStreamReader(getInputStream()));\n    }\n\n    @Override\n    public ServletInputStream getInputStream() throws IOException\n    {\n        final ByteArrayInputStream bais = new ByteArrayInputStream(body);\n        return new ServletInputStream()\n        {\n            @Override\n            public int read() throws IOException\n            {\n                return bais.read();\n            }\n\n            @Override\n            public int available() throws IOException\n            {\n                return body.length;\n            }\n\n            @Override\n            public boolean isFinished()\n            {\n                return false;\n            }\n\n            @Override\n            public boolean isReady()\n            {\n                return false;\n            }\n\n            @Override\n            public void setReadListener(ReadListener readListener)\n            {\n\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/DateUtils.java",
    "content": "package com.oddfar.campus.common.utils;\n\nimport org.apache.commons.lang3.time.DateFormatUtils;\n\nimport java.lang.management.ManagementFactory;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.time.*;\nimport java.util.Date;\n\n/**\n * 时间工具类\n *\n * @author ruoyi\n */\npublic class DateUtils extends org.apache.commons.lang3.time.DateUtils {\n    public static String YYYY = \"yyyy\";\n\n    public static String YYYY_MM = \"yyyy-MM\";\n\n    public static String YYYY_MM_DD = \"yyyy-MM-dd\";\n\n    public static String YYYYMMDDHHMMSS = \"yyyyMMddHHmmss\";\n\n    public static String YYYY_MM_DD_HH_MM_SS = \"yyyy-MM-dd HH:mm:ss\";\n\n    private static String[] parsePatterns = {\n            \"yyyy-MM-dd\", \"yyyy-MM-dd HH:mm:ss\", \"yyyy-MM-dd HH:mm\", \"yyyy-MM\",\n            \"yyyy/MM/dd\", \"yyyy/MM/dd HH:mm:ss\", \"yyyy/MM/dd HH:mm\", \"yyyy/MM\",\n            \"yyyy.MM.dd\", \"yyyy.MM.dd HH:mm:ss\", \"yyyy.MM.dd HH:mm\", \"yyyy.MM\"};\n\n    /**\n     * 获取当前Date型日期\n     *\n     * @return Date() 当前日期\n     */\n    public static Date getNowDate() {\n        return new Date();\n    }\n\n    /**\n     * 获取当前日期, 默认格式为yyyy-MM-dd\n     *\n     * @return String\n     */\n    public static String getDate() {\n        return dateTimeNow(YYYY_MM_DD);\n    }\n\n    public static final String getTime() {\n        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);\n    }\n\n    public static final String dateTimeNow() {\n        return dateTimeNow(YYYYMMDDHHMMSS);\n    }\n\n    public static final String dateTimeNow(final String format) {\n        return parseDateToStr(format, new Date());\n    }\n\n    public static final String dateTime(final Date date) {\n        return parseDateToStr(YYYY_MM_DD, date);\n    }\n\n    public static final String parseDateToStr(final String format, final Date date) {\n        return new SimpleDateFormat(format).format(date);\n    }\n\n    public static final Date dateTime(final String format, final String ts) {\n        try {\n            return new SimpleDateFormat(format).parse(ts);\n        } catch (ParseException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 日期路径 即年/月/日 如2018/08/08\n     */\n    public static final String datePath() {\n        Date now = new Date();\n        return DateFormatUtils.format(now, \"yyyy/MM/dd\");\n    }\n\n    /**\n     * 日期路径 即年/月/日 如20180808\n     */\n    public static final String dateTime() {\n        Date now = new Date();\n        return DateFormatUtils.format(now, \"yyyyMMdd\");\n    }\n\n    /**\n     * 日期型字符串转化为日期 格式\n     */\n    public static Date parseDate(Object str) {\n        if (str == null) {\n            return null;\n        }\n        try {\n            return parseDate(str.toString(), parsePatterns);\n        } catch (ParseException e) {\n            return null;\n        }\n    }\n\n    /**\n     * 获取服务器启动时间\n     */\n    public static Date getServerStartDate() {\n        long time = ManagementFactory.getRuntimeMXBean().getStartTime();\n        return new Date(time);\n    }\n\n    /**\n     * 计算相差天数\n     */\n    public static int differentDaysByMillisecond(Date date1, Date date2) {\n        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));\n    }\n\n    /**\n     * 计算两个时间差\n     */\n    public static String getDatePoor(Date endDate, Date nowDate) {\n        long nd = 1000 * 24 * 60 * 60;\n        long nh = 1000 * 60 * 60;\n        long nm = 1000 * 60;\n        // long ns = 1000;\n        // 获得两个时间的毫秒时间差异\n        long diff = endDate.getTime() - nowDate.getTime();\n        // 计算差多少天\n        long day = diff / nd;\n        // 计算差多少小时\n        long hour = diff % nd / nh;\n        // 计算差多少分钟\n        long min = diff % nd % nh / nm;\n        // 计算差多少秒//输出结果\n        // long sec = diff % nd % nh % nm / ns;\n        return day + \"天\" + hour + \"小时\" + min + \"分钟\";\n    }\n\n    /**\n     * 增加 LocalDateTime ==> Date\n     */\n    public static Date toDate(LocalDateTime temporalAccessor) {\n        ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());\n        return Date.from(zdt.toInstant());\n    }\n\n    /**\n     * 增加 LocalDate ==> Date\n     */\n    public static Date toDate(LocalDate temporalAccessor) {\n        LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));\n        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());\n        return Date.from(zdt.toInstant());\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/DictUtils.java",
    "content": "package com.oddfar.campus.common.utils;\n\nimport com.alibaba.fastjson2.JSONArray;\nimport com.oddfar.campus.common.constant.CacheConstants;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.domain.entity.SysDictDataEntity;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 字典工具类\n *\n */\npublic class DictUtils {\n    /**\n     * 分隔符\n     */\n    public static final String SEPARATOR = \",\";\n\n    /**\n     * 设置字典缓存\n     *\n     * @param key       参数键\n     * @param dictDatas 字典数据列表\n     */\n    public static void setDictCache(String key, List<SysDictDataEntity> dictDatas) {\n        SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);\n    }\n\n    /**\n     * 获取字典缓存\n     *\n     * @param key 参数键\n     * @return dictDatas 字典数据列表\n     */\n    public static List<SysDictDataEntity> getDictCache(String key) {\n        JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));\n        if (StringUtils.isNotNull(arrayCache)) {\n            return arrayCache.toList(SysDictDataEntity.class);\n        }\n        return null;\n    }\n\n    /**\n     * 根据字典类型和字典值获取字典标签\n     *\n     * @param dictType  字典类型\n     * @param dictValue 字典值\n     * @return 字典标签\n     */\n    public static String getDictLabel(String dictType, String dictValue) {\n        return getDictLabel(dictType, dictValue, SEPARATOR);\n    }\n\n    /**\n     * 根据字典类型和字典标签获取字典值\n     *\n     * @param dictType  字典类型\n     * @param dictLabel 字典标签\n     * @return 字典值\n     */\n    public static String getDictValue(String dictType, String dictLabel) {\n        return getDictValue(dictType, dictLabel, SEPARATOR);\n    }\n\n    /**\n     * 根据字典类型和字典值获取字典标签\n     *\n     * @param dictType  字典类型\n     * @param dictValue 字典值\n     * @param separator 分隔符\n     * @return 字典标签\n     */\n    public static String getDictLabel(String dictType, String dictValue, String separator) {\n        StringBuilder propertyString = new StringBuilder();\n        List<SysDictDataEntity> datas = getDictCache(dictType);\n\n        if (StringUtils.isNotNull(datas)) {\n            if (StringUtils.containsAny(separator, dictValue)) {\n                for (SysDictDataEntity dict : datas) {\n                    for (String value : dictValue.split(separator)) {\n                        if (value.equals(dict.getDictValue())) {\n                            propertyString.append(dict.getDictLabel()).append(separator);\n                            break;\n                        }\n                    }\n                }\n            } else {\n                for (SysDictDataEntity dict : datas) {\n                    if (dictValue.equals(dict.getDictValue())) {\n                        return dict.getDictLabel();\n                    }\n                }\n            }\n        }\n        return StringUtils.stripEnd(propertyString.toString(), separator);\n    }\n\n    /**\n     * 根据字典类型和字典标签获取字典值\n     *\n     * @param dictType  字典类型\n     * @param dictLabel 字典标签\n     * @param separator 分隔符\n     * @return 字典值\n     */\n    public static String getDictValue(String dictType, String dictLabel, String separator) {\n        StringBuilder propertyString = new StringBuilder();\n        List<SysDictDataEntity> datas = getDictCache(dictType);\n\n        if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) {\n            for (SysDictDataEntity dict : datas) {\n                for (String label : dictLabel.split(separator)) {\n                    if (label.equals(dict.getDictLabel())) {\n                        propertyString.append(dict.getDictValue()).append(separator);\n                        break;\n                    }\n                }\n            }\n        } else {\n            for (SysDictDataEntity dict : datas) {\n                if (dictLabel.equals(dict.getDictLabel())) {\n                    return dict.getDictValue();\n                }\n            }\n        }\n        return StringUtils.stripEnd(propertyString.toString(), separator);\n    }\n\n    /**\n     * 删除指定字典缓存\n     *\n     * @param key 字典键\n     */\n    public static void removeDictCache(String key) {\n        SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key));\n    }\n\n    /**\n     * 清空字典缓存\n     */\n    public static void clearDictCache() {\n        Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + \"*\");\n        SpringUtils.getBean(RedisCache.class).deleteObject(keys);\n    }\n\n    /**\n     * 设置cache key\n     *\n     * @param configKey 参数键\n     * @return 缓存键key\n     */\n    public static String getCacheKey(String configKey) {\n        return CacheConstants.SYS_DICT_KEY + configKey;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/LogUtils.java",
    "content": "package com.oddfar.campus.common.utils;\n\n/**\n * 处理并记录日志文件\n * \n * @author ruoyi\n */\npublic class LogUtils\n{\n    public static String getBlock(Object msg)\n    {\n        if (msg == null)\n        {\n            msg = \"\";\n        }\n        return \"[\" + msg.toString() + \"]\";\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/MessageUtils.java",
    "content": "package com.oddfar.campus.common.utils;\n\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.i18n.LocaleContextHolder;\n\n/**\n * 获取i18n资源文件\n *\n * @author ruoyi\n */\npublic class MessageUtils {\n    /**\n     * 根据消息键和参数 获取消息 委托给spring messageSource\n     *\n     * @param code 消息键\n     * @param args 参数\n     * @return 获取国际化翻译值\n     */\n    public static String message(String code, Object... args) {\n        MessageSource messageSource = SpringUtils.getBean(MessageSource.class);\n        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/MetaVo.java",
    "content": "package com.oddfar.campus.common.utils;\n\n\n\n/**\n * 路由显示信息\n * \n * @author ruoyi\n */\npublic class MetaVo\n{\n    /**\n     * 设置该路由在侧边栏和面包屑中展示的名字\n     */\n    private String title;\n\n    /**\n     * 设置该路由的图标，对应路径src/assets/icons/svg\n     */\n    private String icon;\n\n    /**\n     * 设置为true，则不会被 <keep-alive>缓存\n     */\n    private boolean noCache;\n\n    /**\n     * 内链地址（http(s)://开头）\n     */\n    private String link;\n\n    public MetaVo()\n    {\n    }\n\n    public MetaVo(String title, String icon)\n    {\n        this.title = title;\n        this.icon = icon;\n    }\n\n    public MetaVo(String title, String icon, boolean noCache)\n    {\n        this.title = title;\n        this.icon = icon;\n        this.noCache = noCache;\n    }\n\n    public MetaVo(String title, String icon, String link)\n    {\n        this.title = title;\n        this.icon = icon;\n        this.link = link;\n    }\n\n    public MetaVo(String title, String icon, boolean noCache, String link)\n    {\n        this.title = title;\n        this.icon = icon;\n        this.noCache = noCache;\n        if (StringUtils.ishttp(link))\n        {\n            this.link = link;\n        }\n    }\n\n    public boolean isNoCache()\n    {\n        return noCache;\n    }\n\n    public void setNoCache(boolean noCache)\n    {\n        this.noCache = noCache;\n    }\n\n    public String getTitle()\n    {\n        return title;\n    }\n\n    public void setTitle(String title)\n    {\n        this.title = title;\n    }\n\n    public String getIcon()\n    {\n        return icon;\n    }\n\n    public void setIcon(String icon)\n    {\n        this.icon = icon;\n    }\n\n    public String getLink()\n    {\n        return link;\n    }\n\n    public void setLink(String link)\n    {\n        this.link = link;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/SecurityUtils.java",
    "content": "package com.oddfar.campus.common.utils;\n\nimport com.oddfar.campus.common.constant.HttpStatus;\nimport com.oddfar.campus.common.constant.UserConstants;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport org.springframework.security.authentication.AnonymousAuthenticationToken;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\n\n/**\n * 安全服务工具类\n *\n * @author ruoyi\n */\npublic class SecurityUtils {\n    /**\n     * 用户ID\n     **/\n    public static Long getUserId() {\n        try {\n            return getLoginUser().getUserId();\n        } catch (Exception e) {\n            throw new ServiceException(\"获取用户ID异常\", HttpStatus.UNAUTHORIZED);\n        }\n    }\n\n\n    /**\n     * 获取用户账户\n     **/\n    public static String getUsername() {\n        try {\n            return getLoginUser().getUsername();\n        } catch (Exception e) {\n            throw new ServiceException(\"获取用户账户异常\", HttpStatus.UNAUTHORIZED);\n        }\n    }\n\n    /**\n     * 获取用户\n     **/\n    public static LoginUser getLoginUser() {\n        try {\n            LoginUser loginUser = (LoginUser) getAuthentication().getPrincipal();\n            //用户不正常\n            if (!loginUser.getUser().getStatus().equals(UserConstants.NORMAL)) {\n                throw new ServiceException(\"用户被禁止\", HttpStatus.FORBIDDEN);\n            }\n            return loginUser;\n        } catch (Exception e) {\n            throw new ServiceException(\"获取用户信息异常\", HttpStatus.UNAUTHORIZED);\n        }\n    }\n\n    /**\n     * 是否登录，true登录\n     *\n     * @return\n     */\n    public static boolean isLogin() {\n        Authentication auth = getAuthentication();\n        if (auth instanceof AnonymousAuthenticationToken) {\n            return false;\n        } else {\n            return true;\n        }\n\n    }\n\n    /**\n     * 获取Authentication\n     */\n    public static Authentication getAuthentication() {\n        return SecurityContextHolder.getContext().getAuthentication();\n    }\n\n    /**\n     * 生成BCryptPasswordEncoder密码\n     *\n     * @param password 密码\n     * @return 加密字符串\n     */\n    public static String encryptPassword(String password) {\n        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();\n        return passwordEncoder.encode(password);\n    }\n\n    /**\n     * 判断密码是否相同\n     *\n     * @param rawPassword     真实密码\n     * @param encodedPassword 加密后字符\n     * @return 结果\n     */\n    public static boolean matchesPassword(String rawPassword, String encodedPassword) {\n        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();\n        return passwordEncoder.matches(rawPassword, encodedPassword);\n    }\n\n    /**\n     * 是否为管理员\n     *\n     * @param userId 用户ID\n     * @return 结果\n     */\n    public static boolean isAdmin(Long userId) {\n        return userId != null && 1L == userId;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/ServletUtils.java",
    "content": "package com.oddfar.campus.common.utils;\n\nimport cn.hutool.core.convert.Convert;\nimport com.oddfar.campus.common.constant.Constants;\nimport org.springframework.web.context.request.RequestAttributes;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\n\n/**\n * 客户端工具类\n *\n * @author ruoyi\n */\npublic class ServletUtils {\n    /**\n     * 获取String参数\n     */\n    public static String getParameter(String name) {\n        return getRequest().getParameter(name);\n    }\n\n    /**\n     * 获取String参数\n     */\n    public static String getParameter(String name, String defaultValue) {\n        return Convert.toStr(getRequest().getParameter(name), defaultValue);\n    }\n\n    /**\n     * 获取Integer参数\n     */\n    public static Integer getParameterToInt(String name) {\n        return Convert.toInt(getRequest().getParameter(name));\n    }\n\n    /**\n     * 获取Integer参数\n     */\n    public static Integer getParameterToInt(String name, Integer defaultValue) {\n        return Convert.toInt(getRequest().getParameter(name), defaultValue);\n    }\n\n    /**\n     * 获取Boolean参数\n     */\n    public static Boolean getParameterToBool(String name) {\n        return Convert.toBool(getRequest().getParameter(name));\n    }\n\n    /**\n     * 获取Boolean参数\n     */\n    public static Boolean getParameterToBool(String name, Boolean defaultValue) {\n        return Convert.toBool(getRequest().getParameter(name), defaultValue);\n    }\n\n    /**\n     * 获取request\n     */\n    public static HttpServletRequest getRequest() {\n        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();\n        if (!(requestAttributes instanceof ServletRequestAttributes)) {\n            return null;\n        }\n        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;\n        return servletRequestAttributes.getRequest();\n    }\n\n    /**\n     * 获取request\n     */\n    public static HttpServletRequest getRequest2()\n    {\n        return getRequestAttributes().getRequest();\n    }\n\n    /**\n     * 获取response\n     */\n    public static HttpServletResponse getResponse() {\n        return getRequestAttributes().getResponse();\n    }\n\n    /**\n     * 获取session\n     */\n    public static HttpSession getSession() {\n        return getRequest().getSession();\n    }\n\n    public static ServletRequestAttributes getRequestAttributes() {\n        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();\n        return (ServletRequestAttributes) attributes;\n    }\n\n    /**\n     * 将字符串渲染到客户端\n     *\n     * @param response 渲染对象\n     * @param string   待渲染的字符串\n     */\n    public static void renderString(HttpServletResponse response, String string) {\n        try {\n            response.setStatus(200);\n            response.setContentType(\"application/json\");\n            response.setCharacterEncoding(\"utf-8\");\n            response.getWriter().print(string);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * 是否是Ajax异步请求\n     *\n     * @param request\n     */\n    public static boolean isAjaxRequest(HttpServletRequest request) {\n        String accept = request.getHeader(\"accept\");\n        if (accept != null && accept.contains(\"application/json\")) {\n            return true;\n        }\n\n        String xRequestedWith = request.getHeader(\"X-Requested-With\");\n        if (xRequestedWith != null && xRequestedWith.contains(\"XMLHttpRequest\")) {\n            return true;\n        }\n\n        String uri = request.getRequestURI();\n        if (StringUtils.inStringIgnoreCase(uri, \".json\", \".xml\")) {\n            return true;\n        }\n\n        String ajax = request.getParameter(\"__ajax\");\n        return StringUtils.inStringIgnoreCase(ajax, \"json\", \"xml\");\n    }\n\n    /**\n     * 内容编码\n     *\n     * @param str 内容\n     * @return 编码后的内容\n     */\n    public static String urlEncode(String str) {\n        try {\n            return URLEncoder.encode(str, Constants.UTF8);\n        } catch (UnsupportedEncodingException e) {\n            return StringUtils.EMPTY;\n        }\n    }\n\n    /**\n     * 内容解码\n     *\n     * @param str 内容\n     * @return 解码后的内容\n     */\n    public static String urlDecode(String str) {\n        try {\n            return URLDecoder.decode(str, Constants.UTF8);\n        } catch (UnsupportedEncodingException e) {\n            return StringUtils.EMPTY;\n        }\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/SpringUtils.java",
    "content": "package com.oddfar.campus.common.utils;\n\nimport org.springframework.aop.framework.AopContext;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.NoSuchBeanDefinitionException;\nimport org.springframework.beans.factory.config.BeanFactoryPostProcessor;\nimport org.springframework.beans.factory.config.ConfigurableListableBeanFactory;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.stereotype.Component;\n\n/**\n * spring工具类 方便在非spring管理环境中获取bean\n *\n * @author ruoyi\n */\n@Component\npublic final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {\n    /**\n     * Spring应用上下文环境\n     */\n    private static ConfigurableListableBeanFactory beanFactory;\n\n    private static ApplicationContext applicationContext;\n\n    @Override\n    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {\n        SpringUtils.beanFactory = beanFactory;\n    }\n\n    @Override\n    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n        SpringUtils.applicationContext = applicationContext;\n    }\n\n    /**\n     * 获取对象\n     *\n     * @param name\n     * @return Object 一个以所给名字注册的bean的实例\n     * @throws org.springframework.beans.BeansException\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T getBean(String name) throws BeansException {\n        return (T) beanFactory.getBean(name);\n    }\n\n    /**\n     * 获取类型为requiredType的对象\n     *\n     * @param clz\n     * @return\n     * @throws org.springframework.beans.BeansException\n     */\n    public static <T> T getBean(Class<T> clz) throws BeansException {\n        T result = (T) beanFactory.getBean(clz);\n        return result;\n    }\n\n    /**\n     * 如果BeanFactory包含一个与所给名称匹配的bean定义，则返回true\n     *\n     * @param name\n     * @return boolean\n     */\n    public static boolean containsBean(String name) {\n        return beanFactory.containsBean(name);\n    }\n\n    /**\n     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到，将会抛出一个异常（NoSuchBeanDefinitionException）\n     *\n     * @param name\n     * @return boolean\n     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException\n     */\n    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {\n        return beanFactory.isSingleton(name);\n    }\n\n    /**\n     * @param name\n     * @return Class 注册对象的类型\n     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException\n     */\n    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {\n        return beanFactory.getType(name);\n    }\n\n    /**\n     * 如果给定的bean名字在bean定义中有别名，则返回这些别名\n     *\n     * @param name\n     * @return\n     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException\n     */\n    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {\n        return beanFactory.getAliases(name);\n    }\n\n    /**\n     * 获取aop代理对象\n     *\n     * @param invoker\n     * @return\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T getAopProxy(T invoker) {\n        return (T) AopContext.currentProxy();\n    }\n\n    /**\n     * 获取当前的环境配置，无配置返回null\n     *\n     * @return 当前的环境配置\n     */\n    public static String[] getActiveProfiles() {\n        return applicationContext.getEnvironment().getActiveProfiles();\n    }\n\n    /**\n     * 获取当前的环境配置，当有多个环境配置时，只获取第一个\n     *\n     * @return 当前的环境配置\n     */\n    public static String getActiveProfile() {\n        final String[] activeProfiles = getActiveProfiles();\n        return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;\n    }\n\n    /**\n     * 获取配置文件中的值\n     *\n     * @param key 配置文件的key\n     * @return 当前的配置文件的值\n     */\n    public static String getRequiredProperty(String key) {\n        return applicationContext.getEnvironment().getRequiredProperty(key);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/StringUtils.java",
    "content": "package com.oddfar.campus.common.utils;\n\nimport cn.hutool.core.text.StrFormatter;\nimport com.oddfar.campus.common.constant.Constants;\nimport org.springframework.util.AntPathMatcher;\n\nimport java.util.*;\n\n/**\n * 字符串工具类\n *\n * @author ruoyi\n */\npublic class StringUtils extends org.apache.commons.lang3.StringUtils {\n    /**\n     * 空字符串\n     */\n    private static final String NULLSTR = \"\";\n\n    /**\n     * 下划线\n     */\n    private static final char SEPARATOR = '_';\n\n    /**\n     * 获取参数不为空值\n     *\n     * @param value defaultValue 要判断的value\n     * @return value 返回值\n     */\n    public static <T> T nvl(T value, T defaultValue) {\n        return value != null ? value : defaultValue;\n    }\n\n    /**\n     * * 判断一个Collection是否为空， 包含List，Set，Queue\n     *\n     * @param coll 要判断的Collection\n     * @return true：为空 false：非空\n     */\n    public static boolean isEmpty(Collection<?> coll) {\n        return isNull(coll) || coll.isEmpty();\n    }\n\n    /**\n     * * 判断一个Collection是否非空，包含List，Set，Queue\n     *\n     * @param coll 要判断的Collection\n     * @return true：非空 false：空\n     */\n    public static boolean isNotEmpty(Collection<?> coll) {\n        return !isEmpty(coll);\n    }\n\n    /**\n     * * 判断一个对象数组是否为空\n     *\n     * @param objects 要判断的对象数组\n     *                * @return true：为空 false：非空\n     */\n    public static boolean isEmpty(Object[] objects) {\n        return isNull(objects) || (objects.length == 0);\n    }\n\n    /**\n     * * 判断一个对象数组是否非空\n     *\n     * @param objects 要判断的对象数组\n     * @return true：非空 false：空\n     */\n    public static boolean isNotEmpty(Object[] objects) {\n        return !isEmpty(objects);\n    }\n\n    /**\n     * * 判断一个Map是否为空\n     *\n     * @param map 要判断的Map\n     * @return true：为空 false：非空\n     */\n    public static boolean isEmpty(Map<?, ?> map) {\n        return isNull(map) || map.isEmpty();\n    }\n\n    /**\n     * * 判断一个Map是否为空\n     *\n     * @param map 要判断的Map\n     * @return true：非空 false：空\n     */\n    public static boolean isNotEmpty(Map<?, ?> map) {\n        return !isEmpty(map);\n    }\n\n    /**\n     * * 判断一个字符串是否为空串\n     *\n     * @param str String\n     * @return true：为空 false：非空\n     */\n    public static boolean isEmpty(String str) {\n        return isNull(str) || NULLSTR.equals(str.trim());\n    }\n\n    /**\n     * * 判断一个字符串是否为非空串\n     *\n     * @param str String\n     * @return true：非空串 false：空串\n     */\n    public static boolean isNotEmpty(String str) {\n        return !isEmpty(str);\n    }\n\n    /**\n     * * 判断一个对象是否为空\n     *\n     * @param object Object\n     * @return true：为空 false：非空\n     */\n    public static boolean isNull(Object object) {\n        return object == null;\n    }\n\n    /**\n     * * 判断一个对象是否非空\n     *\n     * @param object Object\n     * @return true：非空 false：空\n     */\n    public static boolean isNotNull(Object object) {\n        return !isNull(object);\n    }\n\n    /**\n     * * 判断一个对象是否是数组类型（Java基本型别的数组）\n     *\n     * @param object 对象\n     * @return true：是数组 false：不是数组\n     */\n    public static boolean isArray(Object object) {\n        return isNotNull(object) && object.getClass().isArray();\n    }\n\n    /**\n     * 去空格\n     */\n    public static String trim(String str) {\n        return (str == null ? \"\" : str.trim());\n    }\n\n    /**\n     * 截取字符串\n     *\n     * @param str   字符串\n     * @param start 开始\n     * @return 结果\n     */\n    public static String substring(final String str, int start) {\n        if (str == null) {\n            return NULLSTR;\n        }\n\n        if (start < 0) {\n            start = str.length() + start;\n        }\n\n        if (start < 0) {\n            start = 0;\n        }\n        if (start > str.length()) {\n            return NULLSTR;\n        }\n\n        return str.substring(start);\n    }\n\n    /**\n     * 截取字符串\n     *\n     * @param str   字符串\n     * @param start 开始\n     * @param end   结束\n     * @return 结果\n     */\n    public static String substring(final String str, int start, int end) {\n        if (str == null) {\n            return NULLSTR;\n        }\n\n        if (end < 0) {\n            end = str.length() + end;\n        }\n        if (start < 0) {\n            start = str.length() + start;\n        }\n\n        if (end > str.length()) {\n            end = str.length();\n        }\n\n        if (start > end) {\n            return NULLSTR;\n        }\n\n        if (start < 0) {\n            start = 0;\n        }\n        if (end < 0) {\n            end = 0;\n        }\n\n        return str.substring(start, end);\n    }\n\n    /**\n     * 格式化文本, {} 表示占位符<br>\n     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>\n     * 如果想输出 {} 使用 \\\\转义 { 即可，如果想输出 {} 之前的 \\ 使用双转义符 \\\\\\\\ 即可<br>\n     * 例：<br>\n     * 通常使用：format(\"this is {} for {}\", \"a\", \"b\") -> this is a for b<br>\n     * 转义{}： format(\"this is \\\\{} for {}\", \"a\", \"b\") -> this is \\{} for a<br>\n     * 转义\\： format(\"this is \\\\\\\\{} for {}\", \"a\", \"b\") -> this is \\a for b<br>\n     *\n     * @param template 文本模板，被替换的部分用 {} 表示\n     * @param params   参数值\n     * @return 格式化后的文本\n     */\n    public static String format(String template, Object... params) {\n        if (isEmpty(params) || isEmpty(template)) {\n            return template;\n        }\n        return StrFormatter.format(template, params);\n    }\n\n    /**\n     * 是否为http(s)://开头\n     *\n     * @param link 链接\n     * @return 结果\n     */\n    public static boolean ishttp(String link) {\n        return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);\n    }\n\n    /**\n     * 字符串转set\n     *\n     * @param str 字符串\n     * @param sep 分隔符\n     * @return set集合\n     */\n    public static final Set<String> str2Set(String str, String sep) {\n        return new HashSet<String>(str2List(str, sep, true, false));\n    }\n\n    /**\n     * 字符串转list\n     *\n     * @param str         字符串\n     * @param sep         分隔符\n     * @param filterBlank 过滤纯空白\n     * @param trim        去掉首尾空白\n     * @return list集合\n     */\n    public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) {\n        List<String> list = new ArrayList<String>();\n        if (StringUtils.isEmpty(str)) {\n            return list;\n        }\n\n        // 过滤空白字符串\n        if (filterBlank && StringUtils.isBlank(str)) {\n            return list;\n        }\n        String[] split = str.split(sep);\n        for (String string : split) {\n            if (filterBlank && StringUtils.isBlank(string)) {\n                continue;\n            }\n            if (trim) {\n                string = string.trim();\n            }\n            list.add(string);\n        }\n\n        return list;\n    }\n\n    /**\n     * 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value\n     *\n     * @param collection 给定的集合\n     * @param array      给定的数组\n     * @return boolean 结果\n     */\n    public static boolean containsAny(Collection<String> collection, String... array) {\n        if (isEmpty(collection) || isEmpty(array)) {\n            return false;\n        } else {\n            for (String str : array) {\n                if (collection.contains(str)) {\n                    return true;\n                }\n            }\n            return false;\n        }\n    }\n\n    /**\n     * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写\n     *\n     * @param cs                  指定字符串\n     * @param searchCharSequences 需要检查的字符串数组\n     * @return 是否包含任意一个字符串\n     */\n    public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) {\n        if (isEmpty(cs) || isEmpty(searchCharSequences)) {\n            return false;\n        }\n        for (CharSequence testStr : searchCharSequences) {\n            if (containsIgnoreCase(cs, testStr)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 驼峰转下划线命名\n     */\n    public static String toUnderScoreCase(String str) {\n        if (str == null) {\n            return null;\n        }\n        StringBuilder sb = new StringBuilder();\n        // 前置字符是否大写\n        boolean preCharIsUpperCase = true;\n        // 当前字符是否大写\n        boolean curreCharIsUpperCase = true;\n        // 下一字符是否大写\n        boolean nexteCharIsUpperCase = true;\n        for (int i = 0; i < str.length(); i++) {\n            char c = str.charAt(i);\n            if (i > 0) {\n                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));\n            } else {\n                preCharIsUpperCase = false;\n            }\n\n            curreCharIsUpperCase = Character.isUpperCase(c);\n\n            if (i < (str.length() - 1)) {\n                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));\n            }\n\n            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {\n                sb.append(SEPARATOR);\n            } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {\n                sb.append(SEPARATOR);\n            }\n            sb.append(Character.toLowerCase(c));\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * 是否包含字符串\n     *\n     * @param str  验证字符串\n     * @param strs 字符串组\n     * @return 包含返回true\n     */\n    public static boolean inStringIgnoreCase(String str, String... strs) {\n        if (str != null && strs != null) {\n            for (String s : strs) {\n                if (str.equalsIgnoreCase(trim(s))) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空，则返回空字符串。 例如：HELLO_WORLD->HelloWorld\n     *\n     * @param name 转换前的下划线大写方式命名的字符串\n     * @return 转换后的驼峰式命名的字符串\n     */\n    public static String convertToCamelCase(String name) {\n        StringBuilder result = new StringBuilder();\n        // 快速检查\n        if (name == null || name.isEmpty()) {\n            // 没必要转换\n            return \"\";\n        } else if (!name.contains(\"_\")) {\n            // 不含下划线，仅将首字母大写\n            return name.substring(0, 1).toUpperCase() + name.substring(1);\n        }\n        // 用下划线将原始字符串分割\n        String[] camels = name.split(\"_\");\n        for (String camel : camels) {\n            // 跳过原始字符串中开头、结尾的下换线或双重下划线\n            if (camel.isEmpty()) {\n                continue;\n            }\n            // 首字母大写\n            result.append(camel.substring(0, 1).toUpperCase());\n            result.append(camel.substring(1).toLowerCase());\n        }\n        return result.toString();\n    }\n\n    /**\n     * 驼峰式命名法 例如：user_name->userName\n     */\n    public static String toCamelCase(String s) {\n        if (s == null) {\n            return null;\n        }\n        s = s.toLowerCase();\n        StringBuilder sb = new StringBuilder(s.length());\n        boolean upperCase = false;\n        for (int i = 0; i < s.length(); i++) {\n            char c = s.charAt(i);\n\n            if (c == SEPARATOR) {\n                upperCase = true;\n            } else if (upperCase) {\n                sb.append(Character.toUpperCase(c));\n                upperCase = false;\n            } else {\n                sb.append(c);\n            }\n        }\n        return sb.toString();\n    }\n\n    /**\n     * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串\n     *\n     * @param str  指定字符串\n     * @param strs 需要检查的字符串数组\n     * @return 是否匹配\n     */\n    public static boolean matches(String str, List<String> strs) {\n        if (isEmpty(str) || isEmpty(strs)) {\n            return false;\n        }\n        for (String pattern : strs) {\n            if (isMatch(pattern, str)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 判断url是否与规则配置:\n     * ? 表示单个字符;\n     * * 表示一层路径内的任意字符串，不可跨层级;\n     * ** 表示任意层路径;\n     *\n     * @param pattern 匹配规则\n     * @param url     需要匹配的url\n     * @return\n     */\n    public static boolean isMatch(String pattern, String url) {\n        AntPathMatcher matcher = new AntPathMatcher();\n        return matcher.match(pattern, url);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T cast(Object obj) {\n        return (T) obj;\n    }\n\n    /**\n     * 数字左边补齐0，使之达到指定长度。注意，如果数字转换为字符串后，长度大于size，则只保留 最后size个字符。\n     *\n     * @param num  数字对象\n     * @param size 字符串指定长度\n     * @return 返回数字的字符串格式，该字符串为指定长度。\n     */\n    public static final String padl(final Number num, final int size) {\n        return padl(num.toString(), size, '0');\n    }\n\n    /**\n     * 字符串左补齐。如果原始字符串s长度大于size，则只保留最后size个字符。\n     *\n     * @param s    原始字符串\n     * @param size 字符串指定长度\n     * @param c    用于补齐的字符\n     * @return 返回指定长度的字符串，由原字符串左补齐或截取得到。\n     */\n    public static final String padl(final String s, final int size, final char c) {\n        final StringBuilder sb = new StringBuilder(size);\n        if (s != null) {\n            final int len = s.length();\n            if (s.length() <= size) {\n                for (int i = size - len; i > 0; i--) {\n                    sb.append(c);\n                }\n                sb.append(s);\n            } else {\n                return s.substring(len - size, len);\n            }\n        } else {\n            for (int i = size; i > 0; i--) {\n                sb.append(c);\n            }\n        }\n        return sb.toString();\n    }\n}"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/Threads.java",
    "content": "package com.oddfar.campus.common.utils;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.*;\n\n/**\n * 线程相关工具类.\n *\n * @author ruoyi\n */\npublic class Threads {\n    private static final Logger logger = LoggerFactory.getLogger(Threads.class);\n\n    /**\n     * sleep等待,单位为毫秒\n     */\n    public static void sleep(long milliseconds) {\n        try {\n            Thread.sleep(milliseconds);\n        } catch (InterruptedException e) {\n            return;\n        }\n    }\n\n    /**\n     * 停止线程池\n     * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.\n     * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.\n     * 如果仍然超時，則強制退出.\n     * 另对在shutdown时线程本身被调用中断做了处理.\n     */\n    public static void shutdownAndAwaitTermination(ExecutorService pool) {\n        if (pool != null && !pool.isShutdown()) {\n            pool.shutdown();\n            try {\n                if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {\n                    pool.shutdownNow();\n                    if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {\n                        logger.info(\"Pool did not terminate\");\n                    }\n                }\n            } catch (InterruptedException ie) {\n                pool.shutdownNow();\n                Thread.currentThread().interrupt();\n            }\n        }\n    }\n\n    /**\n     * 打印线程异常信息\n     */\n    public static void printException(Runnable r, Throwable t) {\n        if (t == null && r instanceof Future<?>) {\n            try {\n                Future<?> future = (Future<?>) r;\n                if (future.isDone()) {\n                    future.get();\n                }\n            } catch (CancellationException ce) {\n                t = ce;\n            } catch (ExecutionException ee) {\n                t = ee.getCause();\n            } catch (InterruptedException ie) {\n                Thread.currentThread().interrupt();\n            }\n        }\n        if (t != null) {\n            logger.error(t.getMessage(), t);\n        }\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/http/HttpHelper.java",
    "content": "package com.oddfar.campus.common.utils.http;\n\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.servlet.ServletRequest;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * 通用http工具封装\n * \n * @author ruoyi\n */\npublic class HttpHelper\n{\n    private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);\n\n    public static String getBodyString(ServletRequest request)\n    {\n        StringBuilder sb = new StringBuilder();\n        BufferedReader reader = null;\n        try (InputStream inputStream = request.getInputStream())\n        {\n            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));\n            String line = \"\";\n            while ((line = reader.readLine()) != null)\n            {\n                sb.append(line);\n            }\n        }\n        catch (IOException e)\n        {\n            LOGGER.warn(\"getBodyString出现问题！\");\n        }\n        finally\n        {\n            if (reader != null)\n            {\n                try\n                {\n                    reader.close();\n                }\n                catch (IOException e)\n                {\n                    LOGGER.error(ExceptionUtils.getMessage(e));\n                }\n            }\n        }\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/http/HttpUtils.java",
    "content": "package com.oddfar.campus.common.utils.http;\n\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.net.ssl.*;\nimport java.io.*;\nimport java.net.ConnectException;\nimport java.net.SocketTimeoutException;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.nio.charset.StandardCharsets;\nimport java.security.cert.X509Certificate;\n\n/**\n * 通用http发送方法\n * \n * @author ruoyi\n */\npublic class HttpUtils\n{\n    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);\n\n    /**\n     * 向指定 URL 发送GET方法的请求\n     *\n     * @param url 发送请求的 URL\n     * @return 所代表远程资源的响应结果\n     */\n    public static String sendGet(String url)\n    {\n        return sendGet(url, StringUtils.EMPTY);\n    }\n\n    /**\n     * 向指定 URL 发送GET方法的请求\n     *\n     * @param url 发送请求的 URL\n     * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。\n     * @return 所代表远程资源的响应结果\n     */\n    public static String sendGet(String url, String param)\n    {\n        return sendGet(url, param, Constants.UTF8);\n    }\n\n    /**\n     * 向指定 URL 发送GET方法的请求\n     *\n     * @param url 发送请求的 URL\n     * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。\n     * @param contentType 编码类型\n     * @return 所代表远程资源的响应结果\n     */\n    public static String sendGet(String url, String param, String contentType)\n    {\n        StringBuilder result = new StringBuilder();\n        BufferedReader in = null;\n        try\n        {\n            String urlNameString = StringUtils.isNotBlank(param) ? url + \"?\" + param : url;\n            log.info(\"sendGet - {}\", urlNameString);\n            URL realUrl = new URL(urlNameString);\n            URLConnection connection = realUrl.openConnection();\n            connection.setRequestProperty(\"accept\", \"*/*\");\n            connection.setRequestProperty(\"connection\", \"Keep-Alive\");\n            connection.setRequestProperty(\"user-agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\");\n            connection.connect();\n            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));\n            String line;\n            while ((line = in.readLine()) != null)\n            {\n                result.append(line);\n            }\n            log.info(\"recv - {}\", result);\n        }\n        catch (ConnectException e)\n        {\n            log.error(\"调用HttpUtils.sendGet ConnectException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (SocketTimeoutException e)\n        {\n            log.error(\"调用HttpUtils.sendGet SocketTimeoutException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (IOException e)\n        {\n            log.error(\"调用HttpUtils.sendGet IOException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (Exception e)\n        {\n            log.error(\"调用HttpsUtil.sendGet Exception, url=\" + url + \",param=\" + param, e);\n        }\n        finally\n        {\n            try\n            {\n                if (in != null)\n                {\n                    in.close();\n                }\n            }\n            catch (Exception ex)\n            {\n                log.error(\"调用in.close Exception, url=\" + url + \",param=\" + param, ex);\n            }\n        }\n        return result.toString();\n    }\n\n    /**\n     * 向指定 URL 发送POST方法的请求\n     *\n     * @param url 发送请求的 URL\n     * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。\n     * @return 所代表远程资源的响应结果\n     */\n    public static String sendPost(String url, String param)\n    {\n        PrintWriter out = null;\n        BufferedReader in = null;\n        StringBuilder result = new StringBuilder();\n        try\n        {\n            log.info(\"sendPost - {}\", url);\n            URL realUrl = new URL(url);\n            URLConnection conn = realUrl.openConnection();\n            conn.setRequestProperty(\"accept\", \"*/*\");\n            conn.setRequestProperty(\"connection\", \"Keep-Alive\");\n            conn.setRequestProperty(\"user-agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\");\n            conn.setRequestProperty(\"Accept-Charset\", \"utf-8\");\n            conn.setRequestProperty(\"contentType\", \"utf-8\");\n            conn.setDoOutput(true);\n            conn.setDoInput(true);\n            out = new PrintWriter(conn.getOutputStream());\n            out.print(param);\n            out.flush();\n            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));\n            String line;\n            while ((line = in.readLine()) != null)\n            {\n                result.append(line);\n            }\n            log.info(\"recv - {}\", result);\n        }\n        catch (ConnectException e)\n        {\n            log.error(\"调用HttpUtils.sendPost ConnectException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (SocketTimeoutException e)\n        {\n            log.error(\"调用HttpUtils.sendPost SocketTimeoutException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (IOException e)\n        {\n            log.error(\"调用HttpUtils.sendPost IOException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (Exception e)\n        {\n            log.error(\"调用HttpsUtil.sendPost Exception, url=\" + url + \",param=\" + param, e);\n        }\n        finally\n        {\n            try\n            {\n                if (out != null)\n                {\n                    out.close();\n                }\n                if (in != null)\n                {\n                    in.close();\n                }\n            }\n            catch (IOException ex)\n            {\n                log.error(\"调用in.close Exception, url=\" + url + \",param=\" + param, ex);\n            }\n        }\n        return result.toString();\n    }\n\n    public static String sendSSLPost(String url, String param)\n    {\n        StringBuilder result = new StringBuilder();\n        String urlNameString = url + \"?\" + param;\n        try\n        {\n            log.info(\"sendSSLPost - {}\", urlNameString);\n            SSLContext sc = SSLContext.getInstance(\"SSL\");\n            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());\n            URL console = new URL(urlNameString);\n            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();\n            conn.setRequestProperty(\"accept\", \"*/*\");\n            conn.setRequestProperty(\"connection\", \"Keep-Alive\");\n            conn.setRequestProperty(\"user-agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\");\n            conn.setRequestProperty(\"Accept-Charset\", \"utf-8\");\n            conn.setRequestProperty(\"contentType\", \"utf-8\");\n            conn.setDoOutput(true);\n            conn.setDoInput(true);\n\n            conn.setSSLSocketFactory(sc.getSocketFactory());\n            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());\n            conn.connect();\n            InputStream is = conn.getInputStream();\n            BufferedReader br = new BufferedReader(new InputStreamReader(is));\n            String ret = \"\";\n            while ((ret = br.readLine()) != null)\n            {\n                if (ret != null && !\"\".equals(ret.trim()))\n                {\n                    result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));\n                }\n            }\n            log.info(\"recv - {}\", result);\n            conn.disconnect();\n            br.close();\n        }\n        catch (ConnectException e)\n        {\n            log.error(\"调用HttpUtils.sendSSLPost ConnectException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (SocketTimeoutException e)\n        {\n            log.error(\"调用HttpUtils.sendSSLPost SocketTimeoutException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (IOException e)\n        {\n            log.error(\"调用HttpUtils.sendSSLPost IOException, url=\" + url + \",param=\" + param, e);\n        }\n        catch (Exception e)\n        {\n            log.error(\"调用HttpsUtil.sendSSLPost Exception, url=\" + url + \",param=\" + param, e);\n        }\n        return result.toString();\n    }\n\n    private static class TrustAnyTrustManager implements X509TrustManager\n    {\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String authType)\n        {\n        }\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String authType)\n        {\n        }\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers()\n        {\n            return new X509Certificate[] {};\n        }\n    }\n\n    private static class TrustAnyHostnameVerifier implements HostnameVerifier\n    {\n        @Override\n        public boolean verify(String hostname, SSLSession session)\n        {\n            return true;\n        }\n    }\n}"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/ip/AddressUtils.java",
    "content": "package com.oddfar.campus.common.utils.ip;\n\nimport com.alibaba.fastjson2.JSON;\nimport com.alibaba.fastjson2.JSONObject;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.http.HttpUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * 获取地址类\n *\n * @author ruoyi\n */\npublic class AddressUtils {\n    private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);\n\n    // IP地址查询\n    public static final String IP_URL = \"http://whois.pconline.com.cn/ipJson.jsp\";\n\n    // 未知地址\n    public static final String UNKNOWN = \"XX XX\";\n\n    public static String getRealAddressByIP(String ip) {\n        // 内网不查询\n        if (IpUtils.internalIp(ip)) {\n            return \"内网IP\";\n        }\n//        if (RuoYiConfig.isAddressEnabled())\n        {\n            try {\n                String rspStr = HttpUtils.sendGet(IP_URL, \"ip=\" + ip + \"&json=true\", Constants.GBK);\n                if (StringUtils.isEmpty(rspStr)) {\n                    log.error(\"获取地理位置异常 {}\", ip);\n                    return UNKNOWN;\n                }\n                JSONObject obj = JSON.parseObject(rspStr);\n                String region = obj.getString(\"pro\");\n                String city = obj.getString(\"city\");\n                return String.format(\"%s %s\", region, city);\n            } catch (Exception e) {\n                log.error(\"获取地理位置异常 {}\", ip);\n            }\n        }\n        return UNKNOWN;\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/ip/IpUtils.java",
    "content": "package com.oddfar.campus.common.utils.ip;\n\n\nimport com.oddfar.campus.common.utils.StringUtils;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\n/**\n * 获取IP方法\n * \n * @author ruoyi\n */\npublic class IpUtils\n{\n    /**\n     * 获取客户端IP\n     * \n     * @param request 请求对象\n     * @return IP地址\n     */\n    public static String getIpAddr(HttpServletRequest request)\n    {\n        if (request == null)\n        {\n            return \"unknown\";\n        }\n        String ip = request.getHeader(\"x-forwarded-for\");\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip))\n        {\n            ip = request.getHeader(\"Proxy-Client-IP\");\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip))\n        {\n            ip = request.getHeader(\"X-Forwarded-For\");\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip))\n        {\n            ip = request.getHeader(\"WL-Proxy-Client-IP\");\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip))\n        {\n            ip = request.getHeader(\"X-Real-IP\");\n        }\n\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip))\n        {\n            ip = request.getRemoteAddr();\n        }\n\n        return \"0:0:0:0:0:0:0:1\".equals(ip) ? \"127.0.0.1\" : getMultistageReverseProxyIp(ip);\n    }\n\n    /**\n     * 检查是否为内部IP地址\n     * \n     * @param ip IP地址\n     * @return 结果\n     */\n    public static boolean internalIp(String ip)\n    {\n        byte[] addr = textToNumericFormatV4(ip);\n        return internalIp(addr) || \"127.0.0.1\".equals(ip);\n    }\n\n    /**\n     * 检查是否为内部IP地址\n     * \n     * @param addr byte地址\n     * @return 结果\n     */\n    private static boolean internalIp(byte[] addr)\n    {\n        if (StringUtils.isNull(addr) || addr.length < 2)\n        {\n            return true;\n        }\n        final byte b0 = addr[0];\n        final byte b1 = addr[1];\n        // 10.x.x.x/8\n        final byte SECTION_1 = 0x0A;\n        // 172.16.x.x/12\n        final byte SECTION_2 = (byte) 0xAC;\n        final byte SECTION_3 = (byte) 0x10;\n        final byte SECTION_4 = (byte) 0x1F;\n        // 192.168.x.x/16\n        final byte SECTION_5 = (byte) 0xC0;\n        final byte SECTION_6 = (byte) 0xA8;\n        switch (b0)\n        {\n            case SECTION_1:\n                return true;\n            case SECTION_2:\n                if (b1 >= SECTION_3 && b1 <= SECTION_4)\n                {\n                    return true;\n                }\n            case SECTION_5:\n                switch (b1)\n                {\n                    case SECTION_6:\n                        return true;\n                }\n            default:\n                return false;\n        }\n    }\n\n    /**\n     * 将IPv4地址转换成字节\n     * \n     * @param text IPv4地址\n     * @return byte 字节\n     */\n    public static byte[] textToNumericFormatV4(String text)\n    {\n        if (text.length() == 0)\n        {\n            return null;\n        }\n\n        byte[] bytes = new byte[4];\n        String[] elements = text.split(\"\\\\.\", -1);\n        try\n        {\n            long l;\n            int i;\n            switch (elements.length)\n            {\n                case 1:\n                    l = Long.parseLong(elements[0]);\n                    if ((l < 0L) || (l > 4294967295L))\n                    {\n                        return null;\n                    }\n                    bytes[0] = (byte) (int) (l >> 24 & 0xFF);\n                    bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);\n                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);\n                    bytes[3] = (byte) (int) (l & 0xFF);\n                    break;\n                case 2:\n                    l = Integer.parseInt(elements[0]);\n                    if ((l < 0L) || (l > 255L))\n                    {\n                        return null;\n                    }\n                    bytes[0] = (byte) (int) (l & 0xFF);\n                    l = Integer.parseInt(elements[1]);\n                    if ((l < 0L) || (l > 16777215L))\n                    {\n                        return null;\n                    }\n                    bytes[1] = (byte) (int) (l >> 16 & 0xFF);\n                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);\n                    bytes[3] = (byte) (int) (l & 0xFF);\n                    break;\n                case 3:\n                    for (i = 0; i < 2; ++i)\n                    {\n                        l = Integer.parseInt(elements[i]);\n                        if ((l < 0L) || (l > 255L))\n                        {\n                            return null;\n                        }\n                        bytes[i] = (byte) (int) (l & 0xFF);\n                    }\n                    l = Integer.parseInt(elements[2]);\n                    if ((l < 0L) || (l > 65535L))\n                    {\n                        return null;\n                    }\n                    bytes[2] = (byte) (int) (l >> 8 & 0xFF);\n                    bytes[3] = (byte) (int) (l & 0xFF);\n                    break;\n                case 4:\n                    for (i = 0; i < 4; ++i)\n                    {\n                        l = Integer.parseInt(elements[i]);\n                        if ((l < 0L) || (l > 255L))\n                        {\n                            return null;\n                        }\n                        bytes[i] = (byte) (int) (l & 0xFF);\n                    }\n                    break;\n                default:\n                    return null;\n            }\n        }\n        catch (NumberFormatException e)\n        {\n            return null;\n        }\n        return bytes;\n    }\n\n    /**\n     * 获取IP地址\n     * \n     * @return 本地IP地址\n     */\n    public static String getHostIp()\n    {\n        try\n        {\n            return InetAddress.getLocalHost().getHostAddress();\n        }\n        catch (UnknownHostException e)\n        {\n        }\n        return \"127.0.0.1\";\n    }\n\n    /**\n     * 获取主机名\n     * \n     * @return 本地主机名\n     */\n    public static String getHostName()\n    {\n        try\n        {\n            return InetAddress.getLocalHost().getHostName();\n        }\n        catch (UnknownHostException e)\n        {\n        }\n        return \"未知\";\n    }\n\n    /**\n     * 从多级反向代理中获得第一个非unknown IP地址\n     *\n     * @param ip 获得的IP地址\n     * @return 第一个非unknown IP地址\n     */\n    public static String getMultistageReverseProxyIp(String ip)\n    {\n        // 多级反向代理检测\n        if (ip != null && ip.indexOf(\",\") > 0)\n        {\n            final String[] ips = ip.trim().split(\",\");\n            for (String subIp : ips)\n            {\n                if (false == isUnknown(subIp))\n                {\n                    ip = subIp;\n                    break;\n                }\n            }\n        }\n        return ip;\n    }\n\n    /**\n     * 检测给定字符串是否为未知，多用于检测HTTP请求相关\n     *\n     * @param checkString 被检测的字符串\n     * @return 是否未知\n     */\n    public static boolean isUnknown(String checkString)\n    {\n        return StringUtils.isBlank(checkString) || \"unknown\".equalsIgnoreCase(checkString);\n    }\n}"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/pol/ExcelHandlerAdapter.java",
    "content": "package com.oddfar.campus.common.utils.pol;\n\n/**\n * Excel数据格式处理适配器\n *\n * @author ruoyi\n */\npublic interface ExcelHandlerAdapter {\n    /**\n     * 格式化\n     *\n     * @param value 单元格数据值\n     * @param args  excel注解args参数组\n     * @return 处理后的值\n     */\n    Object format(Object value, String[] args);\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/reflect/ReflectUtils.java",
    "content": "package com.oddfar.campus.common.utils.reflect;\n\nimport com.oddfar.campus.common.core.text.Convert;\nimport com.oddfar.campus.common.utils.DateUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Validate;\nimport org.apache.poi.ss.usermodel.DateUtil;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect.*;\nimport java.util.Date;\n\n/**\n * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.\n *\n * @author ruoyi\n */\n@SuppressWarnings(\"rawtypes\")\npublic class ReflectUtils {\n    private static final String SETTER_PREFIX = \"set\";\n\n    private static final String GETTER_PREFIX = \"get\";\n\n    private static final String CGLIB_CLASS_SEPARATOR = \"$$\";\n\n    private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class);\n\n    /**\n     * 调用Getter方法.\n     * 支持多级，如：对象名.对象名.方法\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <E> E invokeGetter(Object obj, String propertyName) {\n        Object object = obj;\n        for (String name : StringUtils.split(propertyName, \".\")) {\n            String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);\n            object = invokeMethod(object, getterMethodName, new Class[]{}, new Object[]{});\n        }\n        return (E) object;\n    }\n\n    /**\n     * 调用Setter方法, 仅匹配方法名。\n     * 支持多级，如：对象名.对象名.方法\n     */\n    public static <E> void invokeSetter(Object obj, String propertyName, E value) {\n        Object object = obj;\n        String[] names = StringUtils.split(propertyName, \".\");\n        for (int i = 0; i < names.length; i++) {\n            if (i < names.length - 1) {\n                String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);\n                object = invokeMethod(object, getterMethodName, new Class[]{}, new Object[]{});\n            } else {\n                String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);\n                invokeMethodByName(object, setterMethodName, new Object[]{value});\n            }\n        }\n    }\n\n    /**\n     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <E> E getFieldValue(final Object obj, final String fieldName) {\n        Field field = getAccessibleField(obj, fieldName);\n        if (field == null) {\n            logger.debug(\"在 [\" + obj.getClass() + \"] 中，没有找到 [\" + fieldName + \"] 字段 \");\n            return null;\n        }\n        E result = null;\n        try {\n            result = (E) field.get(obj);\n        } catch (IllegalAccessException e) {\n            logger.error(\"不可能抛出的异常{}\", e.getMessage());\n        }\n        return result;\n    }\n\n    /**\n     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.\n     */\n    public static <E> void setFieldValue(final Object obj, final String fieldName, final E value) {\n        Field field = getAccessibleField(obj, fieldName);\n        if (field == null) {\n            // throw new IllegalArgumentException(\"在 [\" + obj.getClass() + \"] 中，没有找到 [\" + fieldName + \"] 字段 \");\n            logger.debug(\"在 [\" + obj.getClass() + \"] 中，没有找到 [\" + fieldName + \"] 字段 \");\n            return;\n        }\n        try {\n            field.set(obj, value);\n        } catch (IllegalAccessException e) {\n            logger.error(\"不可能抛出的异常: {}\", e.getMessage());\n        }\n    }\n\n    /**\n     * 直接调用对象方法, 无视private/protected修饰符.\n     * 用于一次性调用的情况，否则应使用getAccessibleMethod()函数获得Method后反复调用.\n     * 同时匹配方法名+参数类型，\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,\n                                     final Object[] args) {\n        if (obj == null || methodName == null) {\n            return null;\n        }\n        Method method = getAccessibleMethod(obj, methodName, parameterTypes);\n        if (method == null) {\n            logger.debug(\"在 [\" + obj.getClass() + \"] 中，没有找到 [\" + methodName + \"] 方法 \");\n            return null;\n        }\n        try {\n            return (E) method.invoke(obj, args);\n        } catch (Exception e) {\n            String msg = \"method: \" + method + \", obj: \" + obj + \", args: \" + args + \"\";\n            throw convertReflectionExceptionToUnchecked(msg, e);\n        }\n    }\n\n    /**\n     * 直接调用对象方法, 无视private/protected修饰符，\n     * 用于一次性调用的情况，否则应使用getAccessibleMethodByName()函数获得Method后反复调用.\n     * 只匹配函数名，如果有多个同名函数调用第一个。\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <E> E invokeMethodByName(final Object obj, final String methodName, final Object[] args) {\n        Method method = getAccessibleMethodByName(obj, methodName, args.length);\n        if (method == null) {\n            // 如果为空不报错，直接返回空。\n            logger.debug(\"在 [\" + obj.getClass() + \"] 中，没有找到 [\" + methodName + \"] 方法 \");\n            return null;\n        }\n        try {\n            // 类型转换（将参数数据类型转换为目标方法参数类型）\n            Class<?>[] cs = method.getParameterTypes();\n            for (int i = 0; i < cs.length; i++) {\n                if (args[i] != null && !args[i].getClass().equals(cs[i])) {\n                    if (cs[i] == String.class) {\n                        args[i] = Convert.toStr(args[i]);\n                        if (StringUtils.endsWith((String) args[i], \".0\")) {\n                            args[i] = StringUtils.substringBefore((String) args[i], \".0\");\n                        }\n                    } else if (cs[i] == Integer.class) {\n                        args[i] = Convert.toInt(args[i]);\n                    } else if (cs[i] == Long.class) {\n                        args[i] = Convert.toLong(args[i]);\n                    } else if (cs[i] == Double.class) {\n                        args[i] = Convert.toDouble(args[i]);\n                    } else if (cs[i] == Float.class) {\n                        args[i] = Convert.toFloat(args[i]);\n                    } else if (cs[i] == Date.class) {\n                        if (args[i] instanceof String) {\n                            args[i] = DateUtils.parseDate(args[i]);\n                        } else {\n                            args[i] = DateUtil.getJavaDate((Double) args[i]);\n                        }\n                    } else if (cs[i] == boolean.class || cs[i] == Boolean.class) {\n                        args[i] = Convert.toBool(args[i]);\n                    }\n                }\n            }\n            return (E) method.invoke(obj, args);\n        } catch (Exception e) {\n            String msg = \"method: \" + method + \", obj: \" + obj + \", args: \" + args + \"\";\n            throw convertReflectionExceptionToUnchecked(msg, e);\n        }\n    }\n\n    /**\n     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.\n     * 如向上转型到Object仍无法找到, 返回null.\n     */\n    public static Field getAccessibleField(final Object obj, final String fieldName) {\n        // 为空不报错。直接返回 null\n        if (obj == null) {\n            return null;\n        }\n        Validate.notBlank(fieldName, \"fieldName can't be blank\");\n        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {\n            try {\n                Field field = superClass.getDeclaredField(fieldName);\n                makeAccessible(field);\n                return field;\n            } catch (NoSuchFieldException e) {\n                continue;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.\n     * 如向上转型到Object仍无法找到, 返回null.\n     * 匹配函数名+参数类型。\n     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)\n     */\n    public static Method getAccessibleMethod(final Object obj, final String methodName,\n                                             final Class<?>... parameterTypes) {\n        // 为空不报错。直接返回 null\n        if (obj == null) {\n            return null;\n        }\n        Validate.notBlank(methodName, \"methodName can't be blank\");\n        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {\n            try {\n                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);\n                makeAccessible(method);\n                return method;\n            } catch (NoSuchMethodException e) {\n                continue;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.\n     * 如向上转型到Object仍无法找到, 返回null.\n     * 只匹配函数名。\n     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)\n     */\n    public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) {\n        // 为空不报错。直接返回 null\n        if (obj == null) {\n            return null;\n        }\n        Validate.notBlank(methodName, \"methodName can't be blank\");\n        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {\n            Method[] methods = searchType.getDeclaredMethods();\n            for (Method method : methods) {\n                if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) {\n                    makeAccessible(method);\n                    return method;\n                }\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 改变private/protected的方法为public，尽量不调用实际改动的语句，避免JDK的SecurityManager抱怨。\n     */\n    public static void makeAccessible(Method method) {\n        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))\n                && !method.isAccessible()) {\n            method.setAccessible(true);\n        }\n    }\n\n    /**\n     * 改变private/protected的成员变量为public，尽量不调用实际改动的语句，避免JDK的SecurityManager抱怨。\n     */\n    public static void makeAccessible(Field field) {\n        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())\n                || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {\n            field.setAccessible(true);\n        }\n    }\n\n    /**\n     * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处\n     * 如无法找到, 返回Object.class.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> Class<T> getClassGenricType(final Class clazz) {\n        return getClassGenricType(clazz, 0);\n    }\n\n    /**\n     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.\n     * 如无法找到, 返回Object.class.\n     */\n    public static Class getClassGenricType(final Class clazz, final int index) {\n        Type genType = clazz.getGenericSuperclass();\n\n        if (!(genType instanceof ParameterizedType)) {\n            logger.debug(clazz.getSimpleName() + \"'s superclass not ParameterizedType\");\n            return Object.class;\n        }\n\n        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();\n\n        if (index >= params.length || index < 0) {\n            logger.debug(\"Index: \" + index + \", Size of \" + clazz.getSimpleName() + \"'s Parameterized Type: \"\n                    + params.length);\n            return Object.class;\n        }\n        if (!(params[index] instanceof Class)) {\n            logger.debug(clazz.getSimpleName() + \" not set the actual class on superclass generic parameter\");\n            return Object.class;\n        }\n\n        return (Class) params[index];\n    }\n\n    public static Class<?> getUserClass(Object instance) {\n        if (instance == null) {\n            throw new RuntimeException(\"Instance must not be null\");\n        }\n        Class clazz = instance.getClass();\n        if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {\n            Class<?> superClass = clazz.getSuperclass();\n            if (superClass != null && !Object.class.equals(superClass)) {\n                return superClass;\n            }\n        }\n        return clazz;\n\n    }\n\n    /**\n     * 将反射时的checked exception转换为unchecked exception.\n     */\n    public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) {\n        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException\n                || e instanceof NoSuchMethodException) {\n            return new IllegalArgumentException(msg, e);\n        } else if (e instanceof InvocationTargetException) {\n            return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());\n        }\n        return new RuntimeException(msg, e);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/spring/AopTargetUtils.java",
    "content": "/*\n * Copyright [2020-2030] [https://www.stylefeng.cn]\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Guns采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：\n *\n * 1.请不要删除和修改根目录下的LICENSE文件。\n * 2.请不要删除和修改Guns源码头部的版权声明。\n * 3.请保留源码和相关描述文件的项目出处，作者声明等。\n * 4.分发源码时候，请注明软件出处 https://gitee.com/stylefeng/guns\n * 5.在修改包名，模块名称，项目代码等时，请注明软件出处 https://gitee.com/stylefeng/guns\n * 6.若您的项目无法满足以上几点，可申请商业授权\n */\npackage com.oddfar.campus.common.utils.spring;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.aop.framework.AdvisedSupport;\nimport org.springframework.aop.framework.AopProxy;\nimport org.springframework.aop.support.AopUtils;\n\nimport java.lang.reflect.Field;\n\n/**\n * 获取代理原始对象的工具\n *\n * @author fengshuonan\n * @date 2020/10/19 16:20\n */\n@Slf4j\npublic class AopTargetUtils {\n\n    /**\n     * 获取代理对象的原始对象\n     *\n     * @author fengshuonan\n     * @date 2020/10/19 16:21\n     */\n    public static Object getTarget(Object proxy) {\n\n        // 不是代理对象，直接返回参数对象\n        if (!AopUtils.isAopProxy(proxy)) {\n            return proxy;\n        }\n\n        // 判断是否是jdk还是cglib代理的对象\n        try {\n            if (AopUtils.isJdkDynamicProxy(proxy)) {\n                return getJdkDynamicProxyTargetObject(proxy);\n            } else {\n                return getCglibProxyTargetObject(proxy);\n            }\n        } catch (Exception e) {\n            log.error(\"获取代理对象异常\", e);\n            return null;\n        }\n    }\n\n    /**\n     * 获取cglib代理的对象\n     *\n     * @author fengshuonan\n     * @date 2020/10/19 16:21\n     */\n    private static Object getCglibProxyTargetObject(Object proxy) throws Exception {\n        Field h = proxy.getClass().getDeclaredField(\"CGLIB$CALLBACK_0\");\n        h.setAccessible(true);\n        Object dynamicAdvisedInterceptor = h.get(proxy);\n        Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField(\"advised\");\n        advised.setAccessible(true);\n        return ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();\n    }\n\n    /**\n     * 获取jdk代理的对象\n     *\n     * @author fengshuonan\n     * @date 2020/10/19 16:22\n     */\n    private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {\n        Field h = proxy.getClass().getSuperclass().getDeclaredField(\"h\");\n        h.setAccessible(true);\n        AopProxy aopProxy = (AopProxy) h.get(proxy);\n        Field advised = aopProxy.getClass().getDeclaredField(\"advised\");\n        advised.setAccessible(true);\n        return ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();\n    }\n\n}  "
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/sql/SqlUtil.java",
    "content": "package com.oddfar.campus.common.utils.sql;\n\nimport cn.hutool.core.exceptions.UtilException;\nimport com.oddfar.campus.common.utils.StringUtils;\n\n/**\n * sql操作工具类\n * \n * @author ruoyi\n */\npublic class SqlUtil\n{\n    /**\n     * 定义常用的 sql关键字\n     */\n    public static String SQL_REGEX = \"select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare \";\n\n    /**\n     * 仅支持字母、数字、下划线、空格、逗号、小数点（支持多个字段排序）\n     */\n    public static String SQL_PATTERN = \"[a-zA-Z0-9_\\\\ \\\\,\\\\.]+\";\n\n    /**\n     * 检查字符，防止注入绕过\n     */\n    public static String escapeOrderBySql(String value)\n    {\n        if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))\n        {\n            throw new UtilException(\"参数不符合规范，不能进行查询\");\n        }\n        return value;\n    }\n\n    /**\n     * 验证 order by 语法是否符合规范\n     */\n    public static boolean isValidOrderBySql(String value)\n    {\n        return value.matches(SQL_PATTERN);\n    }\n\n    /**\n     * SQL关键字检查\n     */\n    public static void filterKeyword(String value)\n    {\n        if (StringUtils.isEmpty(value))\n        {\n            return;\n        }\n        String[] sqlKeywords = StringUtils.split(SQL_REGEX, \"\\\\|\");\n        for (String sqlKeyword : sqlKeywords)\n        {\n            if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1)\n            {\n                throw new UtilException(\"参数存在SQL注入风险\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/uuid/IdUtils.java",
    "content": "package com.oddfar.campus.common.utils.uuid;\n\nimport cn.hutool.core.lang.UUID;\n\n/**\n * ID生成器工具类\n * \n * @author ruoyi\n */\npublic class IdUtils\n{\n    /**\n     * 获取随机UUID\n     * \n     * @return 随机UUID\n     */\n    public static String randomUUID()\n    {\n        return UUID.randomUUID().toString();\n    }\n\n    /**\n     * 简化的UUID，去掉了横线\n     * \n     * @return 简化的UUID，去掉了横线\n     */\n    public static String simpleUUID()\n    {\n        return UUID.randomUUID().toString(true);\n    }\n\n    /**\n     * 获取随机UUID，使用性能更好的ThreadLocalRandom生成UUID\n     * \n     * @return 随机UUID\n     */\n    public static String fastUUID()\n    {\n        return UUID.fastUUID().toString();\n    }\n\n    /**\n     * 简化的UUID，去掉了横线，使用性能更好的ThreadLocalRandom生成UUID\n     * \n     * @return 简化的UUID，去掉了横线\n     */\n    public static String fastSimpleUUID()\n    {\n        return UUID.fastUUID().toString(true);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/uuid/Seq.java",
    "content": "package com.oddfar.campus.common.utils.uuid;\n\nimport com.oddfar.campus.common.utils.DateUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author ruoyi 序列生成类\n */\npublic class Seq {\n    // 通用序列类型\n    public static final String commSeqType = \"COMMON\";\n\n    // 上传序列类型\n    public static final String uploadSeqType = \"UPLOAD\";\n\n    // 通用接口序列数\n    private static AtomicInteger commSeq = new AtomicInteger(1);\n\n    // 上传接口序列数\n    private static AtomicInteger uploadSeq = new AtomicInteger(1);\n\n    // 机器标识\n    private static String machineCode = \"A\";\n\n    /**\n     * 获取通用序列号\n     *\n     * @return 序列值\n     */\n    public static String getId() {\n        return getId(commSeqType);\n    }\n\n    /**\n     * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串\n     *\n     * @return 序列值\n     */\n    public static String getId(String type) {\n        AtomicInteger atomicInt = commSeq;\n        if (uploadSeqType.equals(type)) {\n            atomicInt = uploadSeq;\n        }\n        return getId(atomicInt, 3);\n    }\n\n    /**\n     * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串\n     *\n     * @param atomicInt 序列数\n     * @param length    数值长度\n     * @return 序列值\n     */\n    public static String getId(AtomicInteger atomicInt, int length) {\n        String result = DateUtils.dateTimeNow();\n        result += machineCode;\n        result += getSeq(atomicInt, length);\n        return result;\n    }\n\n    /**\n     * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数\n     *\n     * @return 序列值\n     */\n    private synchronized static String getSeq(AtomicInteger atomicInt, int length) {\n        // 先取值再+1\n        int value = atomicInt.getAndIncrement();\n\n        // 如果更新后值>=10 的 (length)幂次方则重置为1\n        int maxSeq = (int) Math.pow(10, length);\n        if (atomicInt.get() >= maxSeq) {\n            atomicInt.set(1);\n        }\n        // 转字符串，用0左补齐\n        return StringUtils.padl(value, length);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/utils/web/WebFrameworkUtils.java",
    "content": "package com.oddfar.campus.common.utils.web;\n\nimport javax.servlet.ServletRequest;\nimport javax.servlet.http.HttpServletRequest;\n\n/**\n * 专属于 web 包的工具类\n * 参考 ruoyi-pro 芋道源码\n *\n * @author oddfar\n */\npublic class WebFrameworkUtils {\n\n    private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = \"login_user_id\";\n\n    public static void setLoginUserId(ServletRequest request, Long userId) {\n        request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);\n    }\n\n    /**\n     * 获得当前用户的编号，从请求中\n     * 注意：该方法仅限于 framework 框架使用！！！ @SecurityUtils.getUserId()\n     *\n     * @param request 请求\n     * @return 用户编号\n     */\n    public static Long getLoginUserId(HttpServletRequest request) {\n        if (request == null) {\n            return null;\n        }\n        return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID);\n    }\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/validator/Xss.java",
    "content": "package com.oddfar.campus.common.validator;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 自定义xss校验注解\n * \n * @author ruoyi\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER })\n@Constraint(validatedBy = { XssValidator.class })\npublic @interface Xss\n{\n    String message()\n\n    default \"不允许任何脚本运行\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n}\n"
  },
  {
    "path": "campus-common/src/main/java/com/oddfar/campus/common/validator/XssValidator.java",
    "content": "package com.oddfar.campus.common.validator;\n\nimport com.oddfar.campus.common.utils.StringUtils;\n\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * 自定义xss校验注解实现\n * \n * @author ruoyi\n */\npublic class XssValidator implements ConstraintValidator<Xss, String>\n{\n    private static final String HTML_PATTERN = \"<(\\\\S*?)[^>]*>.*?|<.*? />\";\n\n    @Override\n    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext)\n    {\n        if (StringUtils.isBlank(value))\n        {\n            return true;\n        }\n        return !containsHtml(value);\n    }\n\n    public static boolean containsHtml(String value)\n    {\n        Pattern pattern = Pattern.compile(HTML_PATTERN);\n        Matcher matcher = pattern.matcher(value);\n        return matcher.matches();\n    }\n}"
  },
  {
    "path": "campus-framework/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>campus</artifactId>\n        <groupId>com.oddfar.campus</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>campus-framework</artifactId>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n    </properties>\n\n    <dependencies>\n\n        <dependency>\n            <groupId>com.oddfar.campus</groupId>\n            <artifactId>campus-common</artifactId>\n        </dependency>\n\n        <!-- SpringWeb模块 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n\n        <!-- SpringBoot 拦截器 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-aop</artifactId>\n        </dependency>\n\n        <!-- 验证码 -->\n        <dependency>\n            <groupId>com.github.penggle</groupId>\n            <artifactId>kaptcha</artifactId>\n            <exclusions>\n                <exclusion>\n                    <artifactId>javax.servlet-api</artifactId>\n                    <groupId>javax.servlet</groupId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <!-- Mysql驱动包 -->\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n        </dependency>\n\n        <!-- 邮件服务-->\n        <dependency>\n            <groupId>com.sun.mail</groupId>\n            <artifactId>javax.mail</artifactId>\n        </dependency>\n\n    </dependencies>\n</project>"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/file/FileOperatorApi.java",
    "content": "package com.oddfar.campus.framework.api.file;\n\nimport org.springframework.web.multipart.MultipartFile;\n\n/**\n * 文件操作api\n * （后期整合一下阿里云 腾讯云）\n *\n * @author oddfar\n */\npublic interface FileOperatorApi {\n\n    void storageFile(String bucketName, MultipartFile file, String[] allowedExtension);\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/file/FileUploadUtils.java",
    "content": "package com.oddfar.campus.framework.api.file;\n\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.exception.file.FileNameLengthLimitExceededException;\nimport com.oddfar.campus.common.exception.file.FileSizeLimitExceededException;\nimport com.oddfar.campus.common.exception.file.InvalidExtensionException;\nimport com.oddfar.campus.common.utils.DateUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.uuid.Seq;\nimport com.oddfar.campus.framework.api.sysconfig.ConfigExpander;\nimport org.apache.commons.io.FilenameUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport java.util.Objects;\n\n/**\n * 文件上传工具类\n *\n * @author ruoyi\n */\npublic class FileUploadUtils {\n    private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class);\n\n    /**\n     * 默认大小 50M\n     */\n    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;\n\n    /**\n     * 默认的文件名最大长度 100\n     */\n    public static final int DEFAULT_FILE_NAME_LENGTH = 100;\n\n    /**\n     * 默认上传的地址\n     */\n    private static String defaultBaseDir = ConfigExpander.getFileProfile();\n\n    public static void setDefaultBaseDir(String defaultBaseDir) {\n        FileUploadUtils.defaultBaseDir = defaultBaseDir;\n    }\n\n    public static String getDefaultBaseDir() {\n        return defaultBaseDir;\n    }\n\n    /**\n     * 以默认配置进行文件上传\n     *\n     * @param file 上传的文件\n     * @return 文件名称\n     * @throws Exception\n     */\n    public static final String upload(MultipartFile file) throws IOException {\n        try {\n            return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);\n        } catch (Exception e) {\n            throw new IOException(e.getMessage(), e);\n        }\n    }\n\n    /**\n     * 根据文件路径上传\n     *\n     * @param baseDir 相对应用的基目录\n     * @param file    上传的文件\n     * @return 文件名称\n     * @throws IOException\n     */\n    public static final String upload(String baseDir, MultipartFile file) throws IOException {\n        try {\n            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);\n        } catch (Exception e) {\n            throw new IOException(e.getMessage(), e);\n        }\n    }\n\n    /**\n     * 文件上传\n     *\n     * @param baseDir          相对应用的基目录\n     * @param file             上传的文件\n     * @param allowedExtension 上传文件类型\n     * @return 返回上传成功的文件名\n     * @throws FileSizeLimitExceededException       如果超出最大大小\n     * @throws FileNameLengthLimitExceededException 文件名太长\n     * @throws IOException                          比如读写文件出错时\n     * @throws InvalidExtensionException            文件校验异常\n     */\n    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)\n            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,\n            InvalidExtensionException {\n        int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();\n        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {\n            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);\n        }\n\n        assertAllowed(file, allowedExtension);\n\n        String fileName = extractFilename(file);\n\n        String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();\n        file.transferTo(Paths.get(absPath));\n        return getPathFileName(baseDir, fileName);\n    }\n\n    /**\n     * 编码文件名\n     */\n    public static final String extractFilename(MultipartFile file) {\n\n        return StringUtils.format(\"{}/{}_{}.{}\", DateUtils.datePath(),\n                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));\n    }\n\n    public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException {\n        File desc = new File(uploadDir + File.separator + fileName);\n\n        if (!desc.exists()) {\n            if (!desc.getParentFile().exists()) {\n                desc.getParentFile().mkdirs();\n            }\n        }\n        return desc;\n    }\n\n    public static final String getPathFileName(String uploadDir, String fileName) throws IOException {\n        int dirLastIndex = ConfigExpander.getFileProfile().length() + 1;\n        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);\n        return Constants.RESOURCE_PREFIX + \"/\" + currentDir + \"/\" + fileName;\n    }\n\n    /**\n     * 文件大小校验\n     *\n     * @param file 上传的文件\n     * @return\n     * @throws FileSizeLimitExceededException 如果超出最大大小\n     * @throws InvalidExtensionException\n     */\n    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)\n            throws FileSizeLimitExceededException, InvalidExtensionException {\n        long size = file.getSize();\n        if (size > DEFAULT_MAX_SIZE) {\n            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);\n        }\n\n        String fileName = file.getOriginalFilename();\n        String extension = getExtension(file);\n        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {\n            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {\n                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,\n                        fileName);\n            } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {\n                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,\n                        fileName);\n            } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {\n                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,\n                        fileName);\n            } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) {\n                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,\n                        fileName);\n            } else {\n                throw new InvalidExtensionException(allowedExtension, extension, fileName);\n            }\n        }\n    }\n\n    /**\n     * 判断MIME类型是否是允许的MIME类型\n     *\n     * @param extension\n     * @param allowedExtension\n     * @return\n     */\n    public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {\n        for (String str : allowedExtension) {\n            if (str.equalsIgnoreCase(extension)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 获取文件名的后缀\n     *\n     * @param file 表单文件\n     * @return 后缀名\n     */\n    public static final String getExtension(MultipartFile file) {\n        String extension = FilenameUtils.getExtension(file.getOriginalFilename());\n        if (StringUtils.isEmpty(extension)) {\n            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));\n        }\n        return extension;\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/file/LocalFileOperator.java",
    "content": "package com.oddfar.campus.framework.api.file;\n\nimport org.springframework.web.multipart.MultipartFile;\n\npublic class LocalFileOperator implements FileOperatorApi {\n\n    private String currentSavePath;\n\n    public LocalFileOperator(String currentSavePath) {\n        this.currentSavePath = currentSavePath;\n//        initClient();\n    }\n\n    @Override\n    public void storageFile(String bucketName, MultipartFile file, String[] allowedExtension) {\n\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/file/MimeTypeUtils.java",
    "content": "package com.oddfar.campus.framework.api.file;\n\n/**\n * 媒体类型工具类\n *\n * @author ruoyi\n */\npublic class MimeTypeUtils {\n    public static final String IMAGE_PNG = \"image/png\";\n\n    public static final String IMAGE_JPG = \"image/jpg\";\n\n    public static final String IMAGE_JPEG = \"image/jpeg\";\n\n    public static final String IMAGE_BMP = \"image/bmp\";\n\n    public static final String IMAGE_GIF = \"image/gif\";\n\n    public static final String[] IMAGE_EXTENSION = {\"bmp\", \"gif\", \"jpg\", \"jpeg\", \"png\"};\n\n    public static final String[] FLASH_EXTENSION = {\"swf\", \"flv\"};\n\n    public static final String[] MEDIA_EXTENSION = {\"swf\", \"flv\", \"mp3\", \"wav\", \"wma\", \"wmv\", \"mid\", \"avi\", \"mpg\",\n            \"asf\", \"rm\", \"rmvb\"};\n\n    public static final String[] VIDEO_EXTENSION = {\"mp4\", \"avi\", \"rmvb\"};\n\n    public static final String[] IMAGE_VIDEO_EXTENSION = {\n            // 图片\n            \"bmp\", \"gif\", \"jpg\", \"jpeg\", \"png\",\n            // 视频格式\n            \"mp4\", \"avi\", \"rmvb\",\n    };\n\n    public static final String[] DEFAULT_ALLOWED_EXTENSION = {\n            // 图片\n            \"bmp\", \"gif\", \"jpg\", \"jpeg\", \"png\",\n            // word excel powerpoint\n            \"doc\", \"docx\", \"xls\", \"xlsx\", \"ppt\", \"pptx\", \"html\", \"htm\", \"txt\",\n            // 压缩文件\n            \"rar\", \"zip\", \"gz\", \"bz2\",\n            // 视频格式\n            \"mp4\", \"avi\", \"rmvb\",\n            // pdf\n            \"pdf\"};\n\n    public static String getExtension(String prefix) {\n        switch (prefix) {\n            case IMAGE_PNG:\n                return \"png\";\n            case IMAGE_JPG:\n                return \"jpg\";\n            case IMAGE_JPEG:\n                return \"jpeg\";\n            case IMAGE_BMP:\n                return \"bmp\";\n            case IMAGE_GIF:\n                return \"gif\";\n            default:\n                return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/file/ZyFileAutoConfiguration.java",
    "content": "package com.oddfar.campus.framework.api.file;\n\n\nimport com.oddfar.campus.framework.api.sysconfig.ConfigExpander;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 文件的自动配置\n */\n@Configuration\npublic class ZyFileAutoConfiguration {\n\n    /**\n     * 本地文件操作\n     */\n    @Bean\n    @ConditionalOnMissingBean(FileOperatorApi.class)\n    public FileOperatorApi fileOperatorApi() {\n        return new LocalFileOperator(ConfigExpander.getFileProfile());\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/Impl/MailServiceImpl.java",
    "content": "package com.oddfar.campus.framework.api.mail.Impl;\n\nimport cn.hutool.extra.mail.MailAccount;\nimport cn.hutool.extra.mail.MailUtil;\nimport com.oddfar.campus.framework.api.mail.MailConfigRead;\nimport com.oddfar.campus.framework.api.mail.MailSendApi;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\n\n@Service\npublic class MailServiceImpl implements MailSendApi {\n\n    @Async//异步发送邮件\n    @Override\n    public void sendQQMail(List<String> tos, String subject, String content, Boolean isHtml) {\n\n        String host = MailConfigRead.getSmtpHost();\n        String port = MailConfigRead.getSmtpPort();\n        String account = MailConfigRead.getSendAccount();\n        String password = MailConfigRead.getPassword();\n\n        MailAccount mailAccount = new MailAccount();\n        mailAccount.setHost(host);\n        mailAccount.setPort(Integer.parseInt(port));\n        mailAccount.setAuth(true);\n        mailAccount.setFrom(account);\n        mailAccount.setPass(password);\n        //在使用QQ或Gmail邮箱时，需要强制开启SSL支持\n        mailAccount.setSslEnable(true);\n\n        MailUtil.send(mailAccount, tos, subject, content, isHtml);\n\n    }\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/MailConfigRead.java",
    "content": "package com.oddfar.campus.framework.api.mail;\n\nimport com.oddfar.campus.framework.api.sysconfig.ConfigContext;\n\n/**\n * 读取邮箱的配置\n *\n * @author oddfar\n */\npublic class MailConfigRead {\n\n    public static String getSmtpHost() {\n        return ConfigContext.me().selectConfigByKey(\"sys.email.smtp.host\", String.class);\n    }\n\n    public static String getSmtpPort() {\n        return ConfigContext.me().selectConfigByKey(\"sys.email.smtp.port\", String.class);\n    }\n\n    public static String getSendAccount() {\n        return ConfigContext.me().selectConfigByKey(\"sys.email.send.account\", String.class);\n    }\n\n    public static String getPassword() {\n        return ConfigContext.me().selectConfigByKey(\"sys.email.password\", String.class);\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/MailSendApi.java",
    "content": "package com.oddfar.campus.framework.api.mail;\n\nimport java.util.List;\n\n/**\n * 发送邮件Api\n * （后期整合下阿里云 腾讯云）\n *\n * @author oddfar\n */\npublic interface MailSendApi {\n\n    /**\n     * 以发送QQ邮箱为例子\n     *\n     * @param tos     对方的邮箱地址，可以是单个，也可以是多个（Collection表示）\n     * @param subject 标题\n     * @param content 邮件正文，可以是文本，也可以是HTML内容\n     * @param isHtml  是否为HTML，如果是，那参数content识别为HTML内容\n     */\n    void sendQQMail(List<String> tos, String subject, String content, Boolean isHtml);\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/MailSendContext.java",
    "content": "package com.oddfar.campus.framework.api.mail;\n\nimport cn.hutool.extra.spring.SpringUtil;\n\n/**\n * 邮件发送的api上下文\n *\n * @author oddfar\n */\npublic class MailSendContext {\n\n    /**\n     * 获取邮件发送的接口\n     */\n    public static MailSendApi me() {\n        return SpringUtil.getBean(MailSendApi.class);\n    }\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/ZyMailAutoConfig.java",
    "content": "package com.oddfar.campus.framework.api.mail;\n\nimport com.oddfar.campus.framework.api.mail.Impl.MailServiceImpl;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class ZyMailAutoConfig {\n\n\n    /**\n     * mail发送邮件接口\n     */\n    @Bean\n    @ConditionalOnMissingBean(MailSendApi.class)\n    public MailSendApi mailSenderApi() {\n        return new MailServiceImpl();\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/resource/ApiResourceAutoConfig.java",
    "content": "package com.oddfar.campus.framework.api.resource;\n\n\nimport com.oddfar.campus.framework.api.resource.impl.DefaultResourceCollector;\nimport com.oddfar.campus.framework.listener.ApiResourceScanner;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 资源的自动配置\n */\n@Configuration\npublic class ApiResourceAutoConfig {\n\n    /**\n     * 资源搜集器\n     */\n    @Bean\n    @ConditionalOnMissingBean(ApiResourceScanner.class)\n    public ApiResourceScanner apiResourceScanner(ResourceCollectorApi resourceCollectorApi) {\n        return new ApiResourceScanner(resourceCollectorApi);\n    }\n\n\n    /**\n     * 资源搜集api\n     */\n    @Bean\n    @ConditionalOnMissingBean(ResourceCollectorApi.class)\n    public ResourceCollectorApi resourceCollectorApi() {\n        return new DefaultResourceCollector();\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/resource/ResourceCollectorApi.java",
    "content": "package com.oddfar.campus.framework.api.resource;\n\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport com.oddfar.campus.common.domain.model.SysRoleAuth;\nimport com.oddfar.campus.common.domain.model.SysRoleAuthList;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 权限资源收集器，搜集本项目中的资源，仅搜集并缓存起来，不持久化\n * 参考 https://gitee.com/stylefeng/guns 项目\n */\npublic interface ResourceCollectorApi {\n\n\n    /**\n     * 保存所有扫描到的资源\n     *\n     */\n    void collectResources(List<SysResourceEntity> apiResource);\n\n\n    /**\n     * 获取当前运行项目的所有资源\n     *\n     */\n    List<SysResourceEntity> getAllResources();\n\n    /**\n     * 设置角色的资源和菜单权限缓存\n     * @param rolePermsMap\n     * @param roleResourceMap\n     */\n    void setRoleAuthCache(Map<Long, List<SysRoleAuth>> rolePermsMap, Map<Long, List<SysRoleAuth>> roleResourceMap);\n\n    /**\n     * 获取缓存的角色的资源和菜单权限\n     */\n    Map<Long, SysRoleAuthList> getRoleListMap();\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/resource/impl/DefaultResourceCollector.java",
    "content": "package com.oddfar.campus.framework.api.resource.impl;\n\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport com.oddfar.campus.common.domain.model.SysRoleAuth;\nimport com.oddfar.campus.common.domain.model.SysRoleAuthList;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.api.resource.ResourceCollectorApi;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class DefaultResourceCollector implements ResourceCollectorApi {\n\n    /**\n     * 以资源编码为key，存放资源集合\n     */\n    private final Map<String, SysResourceEntity> resourceDefinitions = new ConcurrentHashMap<>();\n\n    private final Map<Long, SysRoleAuthList> roleListMap = new ConcurrentHashMap<>();\n\n    @Override\n    public void collectResources(List<SysResourceEntity> apiResource) {\n        if (apiResource != null && apiResource.size() > 0) {\n            for (SysResourceEntity resourceEntity : apiResource) {\n                SysResourceEntity alreadyFlag = resourceDefinitions.get(resourceEntity.getResourceCode());\n                if (alreadyFlag != null) {\n                    throw new RuntimeException(\"资源扫描过程中存在重复资源！\\n已存在资源：\" + alreadyFlag + \"\\n新资源为： \" + resourceEntity);\n                }\n                resourceDefinitions.put(resourceEntity.getResourceCode(), resourceEntity);\n            }\n        }\n    }\n\n    @Override\n    public List<SysResourceEntity> getAllResources() {\n        Set<Map.Entry<String, SysResourceEntity>> entries = resourceDefinitions.entrySet();\n        ArrayList<SysResourceEntity> resourceDefinitions = new ArrayList<>();\n        for (Map.Entry<String, SysResourceEntity> entry : entries) {\n            resourceDefinitions.add(entry.getValue());\n        }\n        return resourceDefinitions;\n    }\n\n    @Override\n    public void setRoleAuthCache(Map<Long, List<SysRoleAuth>> rolePermsMap, Map<Long, List<SysRoleAuth>> roleResourceMap) {\n        roleListMap.clear();\n\n        for (Long roleId : rolePermsMap.keySet()) {\n\n            List<SysRoleAuth> list = rolePermsMap.get(roleId);\n            //把关于roleId的perms数据建立成set集合\n            Set<String> perms = new HashSet<>();\n            list.stream().forEach(r -> {\n                if (StringUtils.isNotEmpty(r.getPerms())) {\n                    perms.add(r.getPerms());\n                }\n            });\n\n            //如果resMap包含roleId\n            if (roleListMap.containsKey(roleId)) {\n                SysRoleAuthList sysRoleList = roleListMap.get(roleId);\n                if (sysRoleList.getPerms() != null) {\n                    //如果存在perms，则添加数据\n                    sysRoleList.getPerms().addAll(perms);\n                } else {\n                    //无数据则直接set\n                    sysRoleList.setPerms(perms);\n                }\n            } else {\n                //不包含roleId重新生成\n                SysRoleAuthList sysRoleList = new SysRoleAuthList(roleId, perms, null);\n                roleListMap.put(roleId, sysRoleList);\n            }\n\n        }\n\n        for (Long roleId : roleResourceMap.keySet()) {\n            List<SysRoleAuth> list = roleResourceMap.get(roleId);\n            //把关于roleId的resource数据建立成set集合\n            Set<String> resourceSet = new HashSet<>();\n\n\n            list.stream().forEach(r -> {\n                if (StringUtils.isNotEmpty(r.getResourceCode())) {\n                    resourceSet.add(r.getResourceCode());\n                }\n            });\n\n\n            //如果map包含roleId\n            if (roleListMap.containsKey(roleId)) {\n                SysRoleAuthList sysRoleList = roleListMap.get(roleId);\n                if (sysRoleList.getResourceCode() != null) {\n                    sysRoleList.getResourceCode().addAll(resourceSet);\n                } else {\n                    sysRoleList.setResourceCode(resourceSet);\n                }\n\n            } else {\n                SysRoleAuthList sysRoleList = new SysRoleAuthList(roleId, null, resourceSet);\n                roleListMap.put(roleId, sysRoleList);\n            }\n\n        }\n\n    }\n\n    @Override\n    public Map<Long, SysRoleAuthList> getRoleListMap() {\n        return roleListMap;\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/sysconfig/ConfigContext.java",
    "content": "package com.oddfar.campus.framework.api.sysconfig;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.oddfar.campus.framework.service.SysConfigService;\n\n/**\n * 系统配置\n */\npublic class ConfigContext {\n\n    /**\n     * 获取系统配置操作接口\n     */\n    public static SysConfigService me() {\n        return SpringUtil.getBean(SysConfigService.class);\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/sysconfig/ConfigExpander.java",
    "content": "package com.oddfar.campus.framework.api.sysconfig;\n\nimport cn.hutool.core.convert.Convert;\n\npublic class ConfigExpander {\n    /**\n     * 用户默认头像url\n     */\n    public static String getUserDefaultAvatar() {\n        return ConfigContext.me().selectConfigByKey(\"sys.user.default.avatar\");\n    }\n\n    /**\n     * 验证码类型\n     */\n    public static String getLoginCaptchaType() {\n        return ConfigContext.me().selectConfigByKey(\"sys.login.captchaType\", String.class, \"math\");\n    }\n\n    /**\n     * 获取文件保存目录\n     */\n    public static String getFileProfile() {\n\n        String osName = System.getProperty(\"os.name\").toLowerCase();\n        if (osName.contains(\"win\")) {\n            return ConfigContext.me().selectConfigByKey(\"sys.local.profile.win\", String.class, \"D:\\\\uploadPath\");\n        }\n        if (osName.contains(\"mac\")) {\n            return ConfigContext.me().selectConfigByKey(\"sys.local.profile.mac\", String.class, \"~/uploadPath\");\n        }\n        if (osName.contains(\"linux\")) {\n            return ConfigContext.me().selectConfigByKey(\"sys.local.profile.linux\", String.class, \"/data/uploadPath\");\n        }\n        return null;\n    }\n\n    /**\n     * 获取头像上传路径\n     */\n    public static String getAvatarPath() {\n        return getFileProfile() + \"/avatar\";\n    }\n\n\n    /**\n     * 全局日志记录，开启则所有请求都将记录日志\n     */\n    public static Boolean getGlobalControllerOpenFlag() {\n        String flag = ConfigContext.me().selectConfigByKey(\"sys.log.global.flag\", String.class, \"false\");\n\n        return Convert.toBool(flag);\n\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/api/sysconfig/ZyConfigAutoConfiguration.java",
    "content": "package com.oddfar.campus.framework.api.sysconfig;\n\nimport com.oddfar.campus.framework.service.SysConfigService;\nimport com.oddfar.campus.framework.service.impl.SysConfigServiceImpl;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 系统配置自动配置\n */\n@Configuration\npublic class ZyConfigAutoConfiguration {\n\n    @Bean\n    @ConditionalOnMissingBean(SysConfigService.class)\n    public SysConfigService configService() {\n        return new SysConfigServiceImpl();\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/aspectj/LogAspect.java",
    "content": "package com.oddfar.campus.framework.aspectj;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.alibaba.fastjson2.JSON;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.annotation.Log;\nimport com.oddfar.campus.common.domain.entity.SysOperLogEntity;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.enums.BusinessStatus;\nimport com.oddfar.campus.common.filter.PropertyPreExcludeFilter;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.ip.IpUtils;\nimport com.oddfar.campus.framework.api.sysconfig.ConfigExpander;\nimport com.oddfar.campus.framework.manager.AsyncFactory;\nimport com.oddfar.campus.framework.manager.AsyncManager;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.AfterReturning;\nimport org.aspectj.lang.annotation.AfterThrowing;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.stereotype.Component;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.multipart.MultipartFile;\nimport org.springframework.web.servlet.HandlerMapping;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Collection;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 操作日志记录处理\n *\n * @author oddfar\n */\n@Aspect\n@Component\npublic class LogAspect {\n\n    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);\n\n    /**\n     * 排除敏感属性字段\n     */\n    public static final String[] EXCLUDE_PROPERTIES = {\"password\", \"oldPassword\", \"newPassword\", \"confirmPassword\"};\n\n\n    @Value(\"${spring.application.name:}\")\n    private String springApplicationName;\n\n\n    /**\n     * 切所有的controller包\n     */\n    @Pointcut(\"execution(* *..controller..*(..))\")\n    public void webLog() {\n\n    }\n\n    /**\n     * 处理完请求后执行\n     *\n     * @param joinPoint 切点\n     */\n    @AfterReturning(pointcut = \"webLog()\", returning = \"jsonResult\")\n    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {\n        boolean ensureMakeLog = this.ensureMakeLog(joinPoint);\n        if (!ensureMakeLog) {\n            return;\n        }\n        // 获取接口上@GetMapper等的name属性\n        Map<String, Object> annotationProp = getAnnotationProp(joinPoint);\n\n        handleLog(joinPoint, annotationProp, null, jsonResult);\n    }\n\n    /**\n     * 拦截异常操作\n     *\n     * @param joinPoint 切点\n     * @param e         异常\n     */\n    @AfterThrowing(pointcut = \"webLog()\", throwing = \"e\")\n    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {\n        boolean ensureMakeLog = this.ensureMakeLog(joinPoint);\n        if (!ensureMakeLog) {\n            return;\n        }\n        // 获取接口上@GetMapper等的name属性\n        Map<String, Object> annotationProp = getAnnotationProp(joinPoint);\n\n        handleLog(joinPoint, annotationProp, e, null);\n    }\n\n    /**\n     * AOP获取 @GetMapping等 的属性信息\n     *\n     * @param joinPoint joinPoint对象\n     * @return 返回K, V格式的参数，key是参数名称，v是参数值\n     */\n    private Map<String, Object> getAnnotationProp(JoinPoint joinPoint) {\n        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();\n        Method method = methodSignature.getMethod();\n\n        // 通过map封装参数和参数值，key参数名，value是参数值\n        Map<String, Object> propMap = new HashMap<>(2);\n\n        // 获取接口上的@GetMapping等的name属性 填充到map\n        ApiResource apiResource = method.getDeclaringClass().getAnnotation(ApiResource.class);\n\n        for (Annotation annotation : method.getAnnotations()) {\n            //若是 spring 的请求注解\n            if (annotation.toString().contains(\"Mapping(\")) {\n                // 填充其他属性\n                String name = invokeAnnotationMethod(annotation, \"name\", String.class);\n                propMap.put(\"log_content\", StringUtils.isNull(name) ? \"\" : name);\n            }\n        }\n\n        propMap.put(\"app_name\", apiResource != null && StrUtil.isNotBlank(apiResource.appCode()) ? apiResource.appCode()\n                : springApplicationName);\n\n        /**\n         * 以下是只填充 GetMapping 和 PostMapping\n         */\n//        GetMapping getResource = method.getAnnotation(GetMapping.class);\n//        PostMapping postResource = method.getAnnotation(PostMapping.class);\n//        if (getResource != null) {\n//            propMap.put(\"log_content\", getResource.name());\n//        }\n//\n//        if (postResource != null) {\n//            propMap.put(\"log_content\", postResource.name());\n//        }\n\n        return propMap;\n    }\n\n\n    protected void handleLog(final JoinPoint joinPoint, Map<String, Object> annotationProp, final Exception e, Object jsonResult) {\n\n        try {\n            // *========数据库日志=========*//\n            SysOperLogEntity operLog = new SysOperLogEntity();\n            //设置appcode\n            operLog.setAppName(annotationProp.get(\"app_name\").toString());\n            operLog.setLogName(\"API接口日志记录\");\n            operLog.setLogContent(annotationProp.get(\"log_content\").toString());\n\n            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());\n            // 请求的地址\n            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());\n            operLog.setOperIp(ip);\n            operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));\n\n            if (SecurityUtils.isLogin()) {\n                // 获取当前的用户\n                LoginUser loginUser = SecurityUtils.getLoginUser();\n                operLog.setOperUserId(loginUser.getUserId());\n            }\n\n            if (e != null) {\n                operLog.setStatus(BusinessStatus.FAIL.ordinal());\n                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));\n            }\n            // 设置方法名称\n            String className = joinPoint.getTarget().getClass().getName();\n            String methodName = joinPoint.getSignature().getName();\n            operLog.setMethod(className + \".\" + methodName + \"()\");\n            // 设置请求方式\n            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());\n            // 处理设置注解上的参数 app name那些\n            getControllerMethodDescription(joinPoint, operLog, jsonResult);\n            operLog.setOperTime(new Date());\n            // 保存数据库\n            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));\n        } catch (Exception exp) {\n            // 记录本地异常日志\n            log.error(\"==前置通知异常==\");\n            log.error(\"异常信息:{}\", exp.getMessage());\n            exp.printStackTrace();\n        }\n    }\n\n    /**\n     * 获取注解中对方法的描述信息 用于Controller层注解\n     *\n     * @param operLog 操作日志\n     * @throws Exception\n     */\n    public void getControllerMethodDescription(JoinPoint joinPoint, SysOperLogEntity operLog, Object jsonResult) throws Exception {\n\n        // 保存request，参数和值,获取参数的信息，传入到数据库中。\n        setRequestValue(joinPoint, operLog);\n\n        //保存response，参数和值\n        if (StringUtils.isNotNull(jsonResult)) {\n            operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));\n        }\n    }\n\n    /**\n     * 获取请求的参数，放到log中\n     *\n     * @param operLog 操作日志\n     * @throws Exception 异常\n     */\n    private void setRequestValue(JoinPoint joinPoint, SysOperLogEntity operLog) throws Exception {\n        String requestMethod = operLog.getRequestMethod();\n        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {\n            String params = argsArrayToString(joinPoint.getArgs());\n            operLog.setOperParam(StringUtils.substring(params, 0, 2000));\n        } else {\n            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);\n            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));\n        }\n    }\n\n    /**\n     * 参数拼装\n     */\n    private String argsArrayToString(Object[] paramsArray) {\n        String params = \"\";\n        if (paramsArray != null && paramsArray.length > 0) {\n            for (Object o : paramsArray) {\n                if (StringUtils.isNotNull(o) && !isFilterObject(o)) {\n                    try {\n                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());\n                        params += jsonObj.toString() + \" \";\n                    } catch (Exception e) {\n                    }\n                }\n            }\n        }\n        return params.trim();\n    }\n\n    /**\n     * 确定当前接口是否需要记录日志\n     * 参考：https://gitee.com/stylefeng/guns\n     */\n    private boolean ensureMakeLog(JoinPoint point) {\n        // 判断是否需要记录日志，如果不需要直接返回\n        Boolean openFlag = ConfigExpander.getGlobalControllerOpenFlag();\n\n        // 获取类上的业务日志开关注解\n        Class<?> controllerClass = point.getTarget().getClass();\n        Log businessLog = controllerClass.getAnnotation(Log.class);\n\n        // 获取方法上的业务日志开关注解\n        Log methodBusinessLog = null;\n        MethodSignature methodSignature = null;\n        if (!(point.getSignature() instanceof MethodSignature)) {\n            return false;\n        }\n        methodSignature = (MethodSignature) point.getSignature();\n        Object target = point.getTarget();\n        try {\n            Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());\n            methodBusinessLog = currentMethod.getAnnotation(Log.class);\n        } catch (NoSuchMethodException e) {\n            return false;\n        }\n\n        // 如果开关开启\n        if (openFlag) {\n            // 如果控制器类上特意标明不做日志，则不记录日志\n            if (businessLog != null && !businessLog.openLog()) {\n                return false;\n            }\n            // 如果方法上标明不记录日志，则不记录日志\n            return methodBusinessLog == null || methodBusinessLog.openLog();\n        } else {\n            // 如果全局开关没开启，但是类上有特殊标记开启日志，则以类上标注为准\n            if (businessLog != null && businessLog.openLog()) {\n                return true;\n            }\n            // 如果方法上标明不记录日志，则不记录日志\n            return methodBusinessLog != null && methodBusinessLog.openLog();\n        }\n\n    }\n\n    /**\n     * 调用注解上的某个方法，并获取结果\n     */\n    private <T> T invokeAnnotationMethod(Annotation apiResource, String methodName, Class<T> resultType) {\n        try {\n            Class<? extends Annotation> annotationType = apiResource.annotationType();\n            Method method = annotationType.getMethod(methodName);\n            return (T) method.invoke(apiResource);\n        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {\n\n        }\n        return null;\n    }\n\n    /**\n     * 忽略敏感属性\n     */\n    public PropertyPreExcludeFilter excludePropertyPreFilter() {\n        return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);\n    }\n\n    /**\n     * 判断是否需要过滤的对象。\n     *\n     * @param o 对象信息。\n     * @return 如果是需要过滤的对象，则返回true；否则返回false。\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public boolean isFilterObject(final Object o) {\n        Class<?> clazz = o.getClass();\n        if (clazz.isArray()) {\n            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);\n        } else if (Collection.class.isAssignableFrom(clazz)) {\n            Collection collection = (Collection) o;\n            for (Object value : collection) {\n                return value instanceof MultipartFile;\n            }\n        } else if (Map.class.isAssignableFrom(clazz)) {\n            Map map = (Map) o;\n            for (Object value : map.entrySet()) {\n                Map.Entry entry = (Map.Entry) value;\n                return entry.getValue() instanceof MultipartFile;\n            }\n        }\n        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse\n                || o instanceof BindingResult;\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/config/ApplicationConfig.java",
    "content": "package com.oddfar.campus.framework.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.EnableAspectJAutoProxy;\n\n/**\n * 程序注解配置\n */\n@Configuration\n// 表示通过aop框架暴露该代理对象,AopContext能够访问\n@EnableAspectJAutoProxy(exposeProxy = true)\npublic class ApplicationConfig {\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/config/AsyncConfig.java",
    "content": "package com.oddfar.campus.framework.config;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.AsyncConfigurer;\nimport org.springframework.scheduling.annotation.EnableAsync;\n\nimport java.util.Arrays;\nimport java.util.concurrent.Executor;\n\n/**\n * 异步配置\n *\n * @author Lion Li\n */\n@EnableAsync(proxyTargetClass = true)\n@Configuration\npublic class AsyncConfig implements AsyncConfigurer {\n\n    /**\n     * 自定义 @Async 注解使用系统线程池\n     */\n    @Override\n    public Executor getAsyncExecutor() {\n        return SpringUtil.getBean(\"scheduledExecutorService\");\n    }\n\n    /**\n     * 异步执行异常处理\n     */\n    @Override\n    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {\n        return (throwable, method, objects) -> {\n            throwable.printStackTrace();\n            StringBuilder sb = new StringBuilder();\n            sb.append(\"Exception message - \").append(throwable.getMessage())\n                    .append(\", Method name - \").append(method.getName());\n            if (ArrayUtil.isNotEmpty(objects)) {\n                sb.append(\", Parameter value - \").append(Arrays.toString(objects));\n            }\n            throw new ServiceException(sb.toString());\n        };\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/config/JacksonConfig.java",
    "content": "package com.oddfar.campus.framework.config;\n\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;\nimport com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;\nimport com.oddfar.campus.framework.handler.BigNumberSerializer;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;\nimport org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.TimeZone;\n\n/**\n * jackson 配置\n *\n * @author Lion Li\n */\n@Slf4j\n@Configuration\n@AutoConfigureBefore(JacksonAutoConfiguration.class)\npublic class JacksonConfig {\n\n    @Bean\n    public Jackson2ObjectMapperBuilderCustomizer customizer() {\n        return builder -> {\n            // 全局配置序列化返回 JSON 处理\n            JavaTimeModule javaTimeModule = new JavaTimeModule();\n            javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);\n            javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);\n            javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);\n            javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);\n            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\");\n            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));\n            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));\n            builder.modules(javaTimeModule);\n            //时区配置\n            builder.timeZone(TimeZone.getDefault());\n            log.info(\"初始化 jackson 配置\");\n        };\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/config/KaptchaTextCreator.java",
    "content": "package com.oddfar.campus.framework.config;\n\nimport com.google.code.kaptcha.text.impl.DefaultTextCreator;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.util.Random;\n\n/**\n * 验证码文本生成器\n *\n * @author ruoyi\n */\npublic class KaptchaTextCreator extends DefaultTextCreator {\n    private static final String[] CNUMBERS = \"0,1,2,3,4,5,6,7,8,9,10\".split(\",\");\n\n    @Override\n    public String getText() {\n        Integer result = 0;\n        Random random = new Random();\n        int x = random.nextInt(10);\n        int y = random.nextInt(10);\n        StringBuilder suChinese = new StringBuilder();\n        int randomoperands = random.nextInt(3);\n        if (randomoperands == 0) {\n            result = x * y;\n            suChinese.append(CNUMBERS[x]);\n            suChinese.append(\"*\");\n            suChinese.append(CNUMBERS[y]);\n        } else if (randomoperands == 1) {\n            if ((x != 0) && y % x == 0) {\n                result = y / x;\n                suChinese.append(CNUMBERS[y]);\n                suChinese.append(\"/\");\n                suChinese.append(CNUMBERS[x]);\n            } else {\n                result = x + y;\n                suChinese.append(CNUMBERS[x]);\n                suChinese.append(\"+\");\n                suChinese.append(CNUMBERS[y]);\n            }\n        } else {\n            if (x >= y) {\n                result = x - y;\n                suChinese.append(CNUMBERS[x]);\n                suChinese.append(\"-\");\n                suChinese.append(CNUMBERS[y]);\n            } else {\n                result = y - x;\n                suChinese.append(CNUMBERS[y]);\n                suChinese.append(\"-\");\n                suChinese.append(CNUMBERS[x]);\n            }\n        }\n        suChinese.append(\"=?@\" + result);\n        return suChinese.toString();\n    }\n}"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/config/MyWebMvcConfig.java",
    "content": "package com.oddfar.campus.framework.config;\n\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.framework.api.sysconfig.ConfigExpander;\nimport com.oddfar.campus.framework.interceptor.RepeatSubmitInterceptor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.context.request.RequestContextListener;\nimport org.springframework.web.cors.CorsConfiguration;\nimport org.springframework.web.cors.UrlBasedCorsConfigurationSource;\nimport org.springframework.web.filter.CorsFilter;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n/**\n * 通用配置\n * (根据若依修改)\n */\n@Configuration\npublic class MyWebMvcConfig implements WebMvcConfigurer {\n    @Autowired\n    private RepeatSubmitInterceptor repeatSubmitInterceptor;\n\n\n    /**\n     * 映射到访问本地的资源文件\n     *\n     * @param registry\n     */\n    @Override\n    public void addResourceHandlers(ResourceHandlerRegistry registry) {\n\n        /** 本地文件上传路径 */\n        registry.addResourceHandler(Constants.RESOURCE_PREFIX + \"/**\")\n                .addResourceLocations(\"file:\" + ConfigExpander.getFileProfile() + \"/\");\n\n        /** swagger配置 */\n        registry.addResourceHandler(\"/swagger-ui/**\")\n                .addResourceLocations(\"classpath:/META-INF/resources/webjars/springfox-swagger-ui/\");\n    }\n\n\n    /**\n     * 自定义拦截规则\n     */\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns(\"/**\");\n    }\n\n    /**\n     * 跨域配置\n     */\n    @Bean\n    public CorsFilter corsFilter() {\n        CorsConfiguration config = new CorsConfiguration();\n        config.setAllowCredentials(true);\n        // 设置访问源地址\n        config.addAllowedOriginPattern(\"*\");\n        // 设置访问源请求头\n        config.addAllowedHeader(\"*\");\n        // 设置访问源请求方法\n        config.addAllowedMethod(\"*\");\n        // 有效期 1800秒\n        config.setMaxAge(1800L);\n        // 添加映射路径，拦截一切请求\n        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();\n        source.registerCorsConfiguration(\"/**\", config);\n        // 返回新的CorsFilter\n        return new CorsFilter(source);\n    }\n\n    /**\n     * RequestContextListener监听器\n     * bug：https://blog.csdn.net/qq_39575279/article/details/86562195\n     *\n     * @return\n     */\n    @Bean\n    public RequestContextListener requestContextListenerBean() {\n        return new RequestContextListener();\n    }\n}"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/config/MybatisPlusConfig.java",
    "content": "package com.oddfar.campus.framework.config;\n\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;\nimport com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;\nimport com.oddfar.campus.framework.handler.MyDBFieldHandler;\nimport org.mybatis.spring.annotation.MapperScan;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\n@MapperScan(\"${mybatis-plus.mapperPackage}\")\npublic class MybatisPlusConfig {\n\n    /**\n     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)\n     */\n    @Bean\n    public MybatisPlusInterceptor mybatisPlusInterceptor() {\n        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();\n        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));\n        return interceptor;\n    }\n\n\n    /**\n     * 自动填充参数类\n     *\n     * @return\n     */\n    @Bean\n    public MetaObjectHandler defaultMetaObjectHandler() {\n        return new MyDBFieldHandler();\n    }\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/config/SecurityConfig.java",
    "content": "package com.oddfar.campus.framework.config;\n\nimport com.oddfar.campus.framework.security.filter.JwtAuthenticationTokenFilter;\nimport com.oddfar.campus.framework.security.handle.AuthenticationEntryPointImpl;\nimport com.oddfar.campus.framework.security.handle.LogoutSuccessHandlerImpl;\nimport com.oddfar.campus.framework.security.properties.PermitAllUrlProperties;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.security.authentication.AuthenticationManager;\nimport org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;\nimport org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;\nimport org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;\nimport org.springframework.security.config.http.SessionCreationPolicy;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;\nimport org.springframework.security.web.authentication.logout.LogoutFilter;\nimport org.springframework.web.filter.CorsFilter;\n\n/**\n * spring security配置\n *\n * @author ruoyi\n */\n@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n    /**\n     * 自定义用户认证逻辑\n     */\n    @Autowired\n    private UserDetailsService userDetailsService;\n\n    /**\n     * 认证失败处理类\n     */\n    @Autowired\n    private AuthenticationEntryPointImpl unauthorizedHandler;\n\n\n    /**\n     * 退出处理类\n     */\n    @Autowired\n    private LogoutSuccessHandlerImpl logoutSuccessHandler;\n\n    /**\n     * token认证过滤器\n     */\n    @Autowired\n    private JwtAuthenticationTokenFilter authenticationTokenFilter;\n\n\n    /**\n     * 跨域过滤器\n     */\n    @Autowired\n    private CorsFilter corsFilter;\n\n    /**\n     * 允许匿名访问的地址\n     */\n    @Autowired\n    private PermitAllUrlProperties permitAllUrl;\n\n    /**\n     * 解决 无法直接注入 AuthenticationManager\n     *\n     * @return\n     * @throws Exception\n     */\n    @Bean\n    @Override\n    public AuthenticationManager authenticationManagerBean() throws Exception {\n        return super.authenticationManagerBean();\n    }\n\n    /**\n     * anyRequest          |   匹配所有请求路径\n     * access              |   SpringEl表达式结果为true时可以访问\n     * anonymous           |   匿名可以访问\n     * denyAll             |   用户不能访问\n     * fullyAuthenticated  |   用户完全认证可以访问（非remember-me下自动登录）\n     * hasAnyAuthority     |   如果有参数，参数表示权限，则其中任何一个权限可以访问\n     * hasAnyRole          |   如果有参数，参数表示角色，则其中任何一个角色可以访问\n     * hasAuthority        |   如果有参数，参数表示权限，则其权限可以访问\n     * hasIpAddress        |   如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问\n     * hasRole             |   如果有参数，参数表示角色，则其角色可以访问\n     * permitAll           |   用户可以任意访问\n     * rememberMe          |   允许通过remember-me登录的用户访问\n     * authenticated       |   用户登录后可访问\n     */\n    @Override\n    protected void configure(HttpSecurity httpSecurity) throws Exception {\n        // 注解标记允许匿名访问的url\n        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();\n        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());\n\n        httpSecurity\n                // CSRF禁用，因为不使用session\n                .csrf().disable()\n                // 认证失败处理类\n                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()\n                // 基于token，所以不需要session\n                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()\n                // 过滤请求\n                .authorizeRequests()\n                // 对于登录login 注册register 验证码captchaImage 允许匿名访问\n                .antMatchers(\"/login\", \"/register\", \"/captchaImage\").anonymous()\n                // 静态资源，可匿名访问\n                .antMatchers(HttpMethod.GET, \"/\", \"/*.html\", \"/**/*.html\", \"/**/*.css\", \"/**/*.js\", \"/profile/**\").permitAll()\n                .antMatchers(\"/swagger-ui.html\", \"/swagger-resources/**\", \"/webjars/**\", \"/*/api-docs\", \"/druid/**\").permitAll()\n                // 除上面外的所有请求全部需要鉴权认证\n                .anyRequest().authenticated()\n                .and()\n                .headers().frameOptions().disable();\n        // 添加Logout filter\n        httpSecurity.logout().logoutUrl(\"/logout\").logoutSuccessHandler(logoutSuccessHandler);\n        // 添加JWT filter\n        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);\n        // 添加CORS filter\n        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);\n        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);\n    }\n\n    /**\n     * 强散列哈希加密实现\n     */\n    @Bean\n    public BCryptPasswordEncoder bCryptPasswordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n\n    /**\n     * 身份认证接口\n     */\n    @Override\n    protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/config/ThreadPoolConfig.java",
    "content": "package com.oddfar.campus.framework.config;\n\nimport com.oddfar.campus.common.utils.Threads;\nimport org.apache.commons.lang3.concurrent.BasicThreadFactory;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\n\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n/**\n * 线程池配置\n *\n * @author ruoyi\n **/\n@Configuration\npublic class ThreadPoolConfig {\n    // 核心线程池大小\n    private int corePoolSize = 50;\n\n    // 最大可创建的线程数\n    private int maxPoolSize = 200;\n\n    // 队列最大长度\n    private int queueCapacity = 1000;\n\n    // 线程池维护线程所允许的空闲时间\n    private int keepAliveSeconds = 300;\n\n    @Bean(name = \"threadPoolTaskExecutor\")\n    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {\n        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\n        executor.setMaxPoolSize(maxPoolSize);\n        executor.setCorePoolSize(corePoolSize);\n        executor.setQueueCapacity(queueCapacity);\n        executor.setKeepAliveSeconds(keepAliveSeconds);\n        // 线程池对拒绝任务(无线程可用)的处理策略\n        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());\n        return executor;\n    }\n\n    /**\n     * 执行周期性或定时任务\n     */\n    @Bean(name = \"scheduledExecutorService\")\n    protected ScheduledExecutorService scheduledExecutorService() {\n        return new ScheduledThreadPoolExecutor(corePoolSize,\n                new BasicThreadFactory.Builder().namingPattern(\"schedule-pool-%d\").daemon(true).build(),\n                new ThreadPoolExecutor.CallerRunsPolicy()) {\n            @Override\n            protected void afterExecute(Runnable r, Throwable t) {\n                super.afterExecute(r, t);\n                Threads.printException(r, t);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/expander/SysConfigExpander.java",
    "content": "package com.oddfar.campus.framework.expander;\n\nimport com.oddfar.campus.framework.service.SysConfigService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.PostConstruct;\n\n/**\n * 系统配置读取\n */\n@Component\npublic class SysConfigExpander {\n\n    private static SysConfigService configService;\n\n    @Autowired\n    private SysConfigService sysConfigService;\n\n    @PostConstruct\n    public void init() {\n        configService = sysConfigService;\n    }\n\n\n    /**\n     * 用户默认头像url\n     */\n    public static String getUserDefaultAvatar() {\n        return configService.selectConfigByKey(\"sys.user.default.avatar\");\n    }\n\n    /**\n     * 验证码类型\n     *\n     * @return\n     */\n    public static String getLoginCaptchaType() {\n        return configService.selectConfigByKey(\"sys.login.captchaType\", String.class, \"math\");\n    }\n\n\n    /**\n     * 获取文件保存目录\n     *\n     * @return\n     */\n    public static String getFileProfile() {\n\n        String osName = System.getProperty(\"os.name\").toLowerCase();\n        if (osName.contains(\"win\")) {\n            return configService.selectConfigByKey(\"sys.local.profile.win\", String.class, \"D:\\\\uploadPath\");\n        }\n        if (osName.contains(\"mac\")) {\n            return configService.selectConfigByKey(\"sys.local.profile.mac\", String.class, \"~/uploadPath\");\n        }\n        if (osName.contains(\"linux\")) {\n            return configService.selectConfigByKey(\"sys.local.profile.linux\", String.class, \"/data/uploadPath\");\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/expander/SysFileConfigExpander.java",
    "content": "package com.oddfar.campus.framework.expander;\n\nimport com.oddfar.campus.framework.api.sysconfig.ConfigContext;\nimport org.springframework.stereotype.Component;\n\n/**\n * 系统文件配置读取\n */\n@Component\npublic class SysFileConfigExpander {\n    /**\n     * 获取文件保存目录\n     */\n    public static String getProfile() {\n\n        String osName = System.getProperty(\"os.name\").toLowerCase();\n        if (osName.contains(\"win\")) {\n            return ConfigContext.me().selectConfigByKey(\"sys.local.profile.win\", String.class, \"D:\\\\uploadPath\");\n        }\n        if (osName.contains(\"mac\")) {\n            return ConfigContext.me().selectConfigByKey(\"sys.local.profile.mac\", String.class, \"~/uploadPath\");\n        }\n        if (osName.contains(\"linux\")) {\n            return ConfigContext.me().selectConfigByKey(\"sys.local.profile.linux\", String.class, \"/data/uploadPath\");\n        }\n        return null;\n    }\n\n    /**\n     * 获取导入上传路径\n     */\n    public static String getImportPath()\n    {\n        return getProfile() + \"/import\";\n    }\n    /**\n     * 获取头像上传路径\n     */\n    public static String getAvatarPath()\n    {\n        return getProfile() + \"/avatar\";\n    }\n\n    /**\n     * 获取下载路径\n     */\n    public static String getDownloadPath()\n    {\n        return getProfile() + \"/download/\";\n    }\n\n    /**\n     * 获取上传路径\n     */\n    public static String getUploadPath()\n    {\n        return getProfile() + \"/upload\";\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/handler/BigNumberSerializer.java",
    "content": "package com.oddfar.campus.framework.handler;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.fasterxml.jackson.databind.annotation.JacksonStdImpl;\nimport com.fasterxml.jackson.databind.ser.std.NumberSerializer;\n\nimport java.io.IOException;\n\n/**\n * 超出 JS 最大最小值 处理\n *\n * @author Lion Li\n */\n@JacksonStdImpl\npublic class BigNumberSerializer extends NumberSerializer {\n\n    /**\n     * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来\n     */\n    private static final long MAX_SAFE_INTEGER = 9007199254740991L;\n    private static final long MIN_SAFE_INTEGER = -9007199254740991L;\n\n    /**\n     * 提供实例\n     */\n    public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);\n\n    public BigNumberSerializer(Class<? extends Number> rawType) {\n        super(rawType);\n    }\n\n    @Override\n    public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {\n        // 超出范围 序列化位字符串\n        if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {\n            super.serialize(value, gen, provider);\n        } else {\n            gen.writeString(value.toString());\n        }\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/handler/MyDBFieldHandler.java",
    "content": "package com.oddfar.campus.framework.handler;\n\nimport com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.web.WebFrameworkUtils;\nimport org.apache.ibatis.reflection.MetaObject;\n\nimport java.util.Date;\nimport java.util.Objects;\n\n/**\n * mybatis-plus 通用参数填充实现类\n */\npublic class MyDBFieldHandler implements MetaObjectHandler {\n\n    @Override\n    public void insertFill(MetaObject metaObject) {\n//        this.strictInsertFill(metaObject, \"createTime\", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)\n\n        if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity) {\n            BaseEntity baseEntity = (BaseEntity) metaObject.getOriginalObject();\n\n            Date current = new Date();\n            // 创建时间为空，则以当前时间为插入时间\n            if (Objects.isNull(baseEntity.getCreateTime())) {\n                baseEntity.setCreateTime(current);\n            }\n            // 更新时间为空，则以当前时间为更新时间\n            if (Objects.isNull(baseEntity.getUpdateTime())) {\n                baseEntity.setUpdateTime(current);\n            }\n            //TODO getRequest2\n            Long userId = WebFrameworkUtils.getLoginUserId(ServletUtils.getRequest());\n\n            // 当前登录用户不为空，创建人为空，则当前登录用户为创建人\n            if (Objects.nonNull(userId) && Objects.isNull(baseEntity.getCreateUser())) {\n                baseEntity.setCreateUser(userId);\n            }\n            // 当前登录用户不为空，更新人为空，则当前登录用户为更新人\n            if (Objects.nonNull(userId) && Objects.isNull(baseEntity.getUpdateUser())) {\n                baseEntity.setUpdateUser(userId);\n            }\n            if (Objects.isNull(baseEntity.getDelFlag())) {\n                baseEntity.setDelFlag(0);\n            }\n        }\n    }\n\n    @Override\n    public void updateFill(MetaObject metaObject) {\n//        this.strictUpdateFill(metaObject, \"updateTime\", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)\n\n        // 更新时间为空，则以当前时间为更新时间\n        Object modifyTime = getFieldValByName(\"updateTime\", metaObject);\n        if (Objects.isNull(modifyTime)) {\n            setFieldValByName(\"updateTime\", new Date(), metaObject);\n        }\n\n        // 当前登录用户不为空，更新人为空，则当前登录用户为更新人\n        Object modifier = getFieldValByName(\"updateUser\", metaObject);\n        Long userId = WebFrameworkUtils.getLoginUserId(ServletUtils.getRequest());\n        if (Objects.nonNull(userId) && Objects.isNull(modifier)) {\n            setFieldValByName(\"updateUser\", userId, metaObject);\n        }\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/interceptor/RepeatSubmitInterceptor.java",
    "content": "package com.oddfar.campus.framework.interceptor;\n\nimport com.alibaba.fastjson2.JSON;\nimport com.oddfar.campus.common.annotation.RepeatSubmit;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.lang.reflect.Method;\n\n/**\n * 防止重复提交拦截器\n *\n * @author ruoyi\n */\n@Component\npublic abstract class RepeatSubmitInterceptor implements HandlerInterceptor {\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        if (handler instanceof HandlerMethod) {\n            HandlerMethod handlerMethod = (HandlerMethod) handler;\n            Method method = handlerMethod.getMethod();\n            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);\n            if (annotation != null) {\n                if (this.isRepeatSubmit(request, annotation)) {\n                    R r = R.error(annotation.message());\n                    ServletUtils.renderString(response, JSON.toJSONString(r));\n                    return false;\n                }\n            }\n            return true;\n        } else {\n            return true;\n        }\n    }\n\n    /**\n     * 验证是否重复提交由子类实现具体的防重复提交的规则\n     *\n     * @param request\n     * @return\n     * @throws Exception\n     */\n    public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/interceptor/impl/SameUrlDataInterceptor.java",
    "content": "package com.oddfar.campus.framework.interceptor.impl;\n\nimport com.alibaba.fastjson2.JSON;\nimport com.oddfar.campus.common.annotation.RepeatSubmit;\nimport com.oddfar.campus.common.constant.CacheConstants;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.filter.RepeatedlyRequestWrapper;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.http.HttpHelper;\nimport com.oddfar.campus.framework.interceptor.RepeatSubmitInterceptor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 判断请求url和数据是否和上一次相同，\n * 如果和上次相同，则是重复提交表单。 有效时间为10秒内。\n * \n * @author ruoyi\n */\n@Component\npublic class SameUrlDataInterceptor extends RepeatSubmitInterceptor\n{\n    public final String REPEAT_PARAMS = \"repeatParams\";\n\n    public final String REPEAT_TIME = \"repeatTime\";\n\n    // 令牌自定义标识\n    @Value(\"${token.header}\")\n    private String header;\n\n    @Autowired\n    private RedisCache redisCache;\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation)\n    {\n        String nowParams = \"\";\n        if (request instanceof RepeatedlyRequestWrapper)\n        {\n            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;\n            nowParams = HttpHelper.getBodyString(repeatedlyRequest);\n        }\n\n        // body参数为空，获取Parameter的数据\n        if (StringUtils.isEmpty(nowParams))\n        {\n            nowParams = JSON.toJSONString(request.getParameterMap());\n        }\n        Map<String, Object> nowDataMap = new HashMap<String, Object>();\n        nowDataMap.put(REPEAT_PARAMS, nowParams);\n        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());\n\n        // 请求地址（作为存放cache的key值）\n        String url = request.getRequestURI();\n\n        // 唯一值（没有消息头则使用请求地址）\n        String submitKey = StringUtils.trimToEmpty(request.getHeader(header));\n\n        // 唯一标识（指定key + url + 消息头）\n        String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;\n\n        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);\n        if (sessionObj != null)\n        {\n            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;\n            if (sessionMap.containsKey(url))\n            {\n                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);\n                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval()))\n                {\n                    return true;\n                }\n            }\n        }\n        Map<String, Object> cacheMap = new HashMap<String, Object>();\n        cacheMap.put(url, nowDataMap);\n        redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);\n        return false;\n    }\n\n    /**\n     * 判断参数是否相同\n     */\n    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)\n    {\n        String nowParams = (String) nowMap.get(REPEAT_PARAMS);\n        String preParams = (String) preMap.get(REPEAT_PARAMS);\n        return nowParams.equals(preParams);\n    }\n\n    /**\n     * 判断两次间隔时间\n     */\n    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)\n    {\n        long time1 = (Long) nowMap.get(REPEAT_TIME);\n        long time2 = (Long) preMap.get(REPEAT_TIME);\n        if ((time1 - time2) < interval)\n        {\n            return true;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/listener/ApiResourceScanner.java",
    "content": "package com.oddfar.campus.framework.listener;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport com.oddfar.campus.common.utils.spring.AopTargetUtils;\nimport com.oddfar.campus.framework.api.resource.ResourceCollectorApi;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.beans.factory.config.BeanPostProcessor;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * api接口资源扫描\n * 参考 https://gitee.com/stylefeng/guns 项目\n *\n * @author oddfar\n */\npublic class ApiResourceScanner implements BeanPostProcessor {\n\n    @Value(\"${spring.application.name:}\")\n    private String springApplicationName;\n\n\n    /**\n     * 权限资源收集接口\n     */\n    private final ResourceCollectorApi resourceCollectorApi;\n\n    public ApiResourceScanner(ResourceCollectorApi resourceCollectorApi) {\n        this.resourceCollectorApi = resourceCollectorApi;\n    }\n\n    @Override\n    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {\n        return bean;\n    }\n\n    @Override\n    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {\n        Object aopTarget = AopTargetUtils.getTarget(bean);\n\n        if (aopTarget == null) {\n            aopTarget = bean;\n        }\n\n        Class<?> clazz = aopTarget.getClass();\n        // 判断是不是控制器,不是控制器就略过\n        boolean controllerFlag = getControllerFlag(clazz);\n        if (!controllerFlag) {\n            return bean;\n        }\n\n        ApiResource classApiAnnotation = clazz.getAnnotation(ApiResource.class);\n        if (classApiAnnotation != null) {\n            // 扫描控制器的所有带ApiResource注解的方法\n            List<SysResourceEntity> apiResources = doScan(clazz);\n            // 将扫描到的注解转化为资源实体存储到缓存\n            resourceCollectorApi.collectResources(apiResources);\n        }\n\n        return bean;\n    }\n\n\n    /**\n     * 判断一个类是否是控制器\n     */\n    private boolean getControllerFlag(Class<?> clazz) {\n        Annotation[] annotations = clazz.getAnnotations();\n        for (Annotation annotation : annotations) {\n            if (RestController.class.equals(annotation.annotationType())) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n\n    /**\n     * 扫描整个类中包含的所有@ApiResource资源\n     */\n    private List<SysResourceEntity> doScan(Class<?> clazz) {\n\n        ArrayList<SysResourceEntity> apiResources = new ArrayList<>();\n        Method[] declaredMethods = clazz.getDeclaredMethods();\n        if (declaredMethods.length > 0) {\n            for (Method declaredMethod : declaredMethods) {\n                Annotation annotation = null;\n\n                Annotation[] annotations = declaredMethod.getAnnotations();\n                for (Annotation a : annotations) {\n                    //若是 spring 的请求注解\n                    if (a.toString().contains(\"Mapping(\")) {\n                        annotation = a;\n                    }\n                }\n\n                if (annotation != null) {\n                    SysResourceEntity definition = createDefinition(clazz, declaredMethod, annotation);\n                    apiResources.add(definition);\n                }\n            }\n        }\n        return apiResources;\n    }\n\n\n    /**\n     * 根据类信息，方法信息，注解信息创建 SysResourceEntity 对象\n     */\n    private SysResourceEntity createDefinition(Class<?> controllerClass, Method method, Annotation annotation) {\n        SysResourceEntity resource = new SysResourceEntity();\n\n        //设置类和方法名称\n        resource.setClassName(controllerClass.getSimpleName());\n        resource.setMethodName(method.getName());\n\n        // 填充模块编码，模块编码就是控制器名称截取Controller关键字前边的字符串\n        String className = resource.getClassName();\n\n        int controllerIndex = className.indexOf(\"Controller\");\n        if (controllerIndex == -1) {\n            throw new IllegalArgumentException(\"controller class name is not illegal, it should end with Controller!\");\n        }\n        //去掉Controller\n        String modular = className.substring(0, controllerIndex);\n        resource.setModular_code(modular);\n\n        // 填充模块的中文名称\n        ApiResource apiResource = controllerClass.getAnnotation(ApiResource.class);\n        // 接口资源的类别\n        resource.setResourceBizType(apiResource.resBizType().getCode());\n        resource.setModularName(apiResource.name());\n        // 设置appCode\n        if (StrUtil.isNotBlank(apiResource.appCode())) {\n            resource.setAppCode(apiResource.appCode());\n        }else {\n            resource.setAppCode(springApplicationName);\n        }\n        //资源唯一编码\n        String resourceCode = StrUtil.toUnderlineCase(resource.getAppCode()) + \".\" + StrUtil.toUnderlineCase(modular) + \".\" + StrUtil.toUnderlineCase(resource.getMethodName());\n        resource.setResourceCode(resourceCode);\n\n        //是否需要鉴权\n        PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);\n        resource.setRequiredPermissionFlag(Constants.NO);\n        if (preAuthorize != null) {\n            if (preAuthorize.value().contains(\"@ss.resourceAuth\")) {\n                resource.setRequiredPermissionFlag(Constants.YES);\n            }\n        }\n\n        // 填充其他属性\n        String name = invokeAnnotationMethod(annotation, \"name\", String.class);\n        //不存在则资源名称为方法名\n        if (StringUtils.isNotEmpty(name)) {\n            resource.setResourceName(name);\n        } else {\n            resource.setResourceName(resource.getMethodName());\n        }\n\n        String[] value = invokeAnnotationMethod(annotation, \"value\", String[].class);\n        String controllerMethodPath = createControllerPath(controllerClass, value);\n        resource.setUrl(controllerMethodPath);\n        //填充请求方法\n        resource.setHttpMethod(StringUtils.substringBetween(annotation.toString(), \"annotation.\", \"Mapping\").toLowerCase());\n\n        return resource;\n    }\n\n\n    /**\n     * 根据控制器类上的RequestMapping注解的映射路径，以及方法上的路径，拼出整个接口的路径\n     */\n    private String createControllerPath(Class<?> clazz, String[] paths) {\n        String path = \"\";\n        if (paths.length > 0) {\n            path = \"/\" + paths[0];\n        }\n        String controllerPath;\n\n        RequestMapping controllerRequestMapping = clazz.getDeclaredAnnotation(RequestMapping.class);\n        if (controllerRequestMapping == null) {\n            controllerPath = \"\";\n        } else {\n            String[] values = controllerRequestMapping.value();\n            if (values.length > 0) {\n                controllerPath = values[0];\n            } else {\n                controllerPath = \"\";\n            }\n        }\n\n        // 控制器上的path要以/开头\n        if (!controllerPath.startsWith(\"/\")) {\n            controllerPath = \"/\" + controllerPath;\n        }\n\n        // 前缀多个左斜杠替换为一个\n        return (controllerPath + path).replaceAll(\"/+\", \"/\");\n    }\n\n\n    /**\n     * 调用注解上的某个方法，并获取结果\n     *\n     * @author fengshuonan\n     * @date 2020/12/8 17:13\n     */\n    private <T> T invokeAnnotationMethod(Annotation apiResource, String methodName, Class<T> resultType) {\n        try {\n            Class<? extends Annotation> annotationType = apiResource.annotationType();\n            Method method = annotationType.getMethod(methodName);\n            return (T) method.invoke(apiResource);\n        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {\n\n        }\n        throw new RuntimeException(\"扫描api资源时出错!\");\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/listener/ReadyEventListener.java",
    "content": "package com.oddfar.campus.framework.listener;\n\n\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport com.oddfar.campus.framework.api.resource.ResourceCollectorApi;\nimport com.oddfar.campus.framework.service.SysResourceService;\nimport com.oddfar.campus.framework.service.SysRoleService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.ApplicationListener;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.stereotype.Component;\n\nimport java.util.List;\n\n/**\n * 监听项目初始化完毕，执行的操作\n *\n * @author oddfar\n */\n@Component\npublic class ReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {\n\n    @Autowired\n    private SysResourceService resourceService;\n    @Autowired\n    private SysRoleService roleService;\n\n    @Override\n    public void onApplicationEvent(ApplicationReadyEvent event) {\n        //导入资源数据\n        ConfigurableApplicationContext applicationContext = event.getApplicationContext();\n\n        //清空数据\n        resourceService.truncateResource();\n\n        // 获取当前系统的所有资源\n        ResourceCollectorApi resourceCollectorApi = applicationContext.getBean(ResourceCollectorApi.class);\n        List<SysResourceEntity> allResources = resourceCollectorApi.getAllResources();\n        //添加api接口资源到数据库\n        resourceService.saveBatch(allResources);\n\n        //把用户资源和权限缓存\n        roleService.resetRoleAuthCache();\n    }\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/listener/ResourceReportListener.java",
    "content": "package com.oddfar.campus.framework.listener;\n\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport com.oddfar.campus.framework.service.SysResourceService;\n//import io.swagger.annotations.Api;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.core.annotation.AnnotationUtils;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.context.WebApplicationContext;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.mvc.method.RequestMappingInfo;\nimport org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Method;\nimport java.util.Map;\nimport java.util.Optional;\n\n/**\n * 监听项目初始化完毕，导入资源信息(没用，暂存下代码)\n *\n * @author oddfar\n */\n//@Component  implements ApplicationListener<ApplicationReadyEvent>\npublic class ResourceReportListener {\n\n\n    @Autowired\n    WebApplicationContext applicationContext;\n    @Autowired\n    SysResourceService resourceService;\n\n    //    @Override\n    public void onApplicationEvent(ApplicationReadyEvent event) {\n        //清空表\n        resourceService.truncateResource();\n\n        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);\n        // 获取所有controller方法\n        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();\n\n        map.keySet().forEach(info -> {\n            HandlerMethod handlerMethod = map.get(info);\n            PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), PreAuthorize.class);\n            //如果方法上含有  @PreAuthorize 注解\n            Optional.ofNullable(preAuthorize).ifPresent(anonymous -> {\n                if (preAuthorize.value().contains(\"@ss.test\")) {\n                    Method m = handlerMethod.getMethod();\n                    setSource(m);\n                }\n            });\n        });\n\n    }\n\n\n    /**\n     * 根据 Method 设置其内容\n     *\n     * @param m\n     */\n    private void setSource(Method m) {\n\n        Class<?> aClass = m.getDeclaringClass();\n        Annotation[] annotations = m.getDeclaredAnnotations();\n\n        for (Annotation a : annotations) {\n            //若是 spring 的请求注解\n            if (a.toString().contains(\"Mapping(\")) {\n                SysResourceEntity resource = new SysResourceEntity();\n\n                resource.setClassName(aClass.getSimpleName());\n                //获取注解@Api，并设置模块名称\n//                Api api = AnnotationUtils.getAnnotation(aClass, Api.class);\n//                resource.setModularName(api.value());\n\n                resource.setMethodName(m.getName());\n                //设置注解其他内容\n                setSource(resource, a);\n                //保存到数据库\n                resourceService.insertResource(resource);\n            }\n        }\n\n    }\n\n    /**\n     * 设置 SysResourceEntity 的其他内容\n     *\n     * @param resource\n     * @param a        例如 @GetMapping，设置其参数的内容\n     */\n    private void setSource(SysResourceEntity resource, Annotation a) {\n        String s = a.toString();\n        resource.setResourceName(StringUtils.substringBetween(s, \", name=\", \", \"));\n        resource.setUrl(StringUtils.substringBetween(s, \", value=\", \", \"));\n        resource.setHttpMethod(StringUtils.substringBetween(s, \"annotation.\", \"Mapping\"));\n\n        resource.setAppCode(\"zhiyuan\");\n        //ClassName+MethodName(替换)\n        resource.setResourceCode(resource.getClassName().toLowerCase().replace(\"controller\", \"\") + \":\" + resource.getMethodName());\n\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/manager/AsyncFactory.java",
    "content": "package com.oddfar.campus.framework.manager;\n\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.domain.entity.SysLoginLogEntity;\nimport com.oddfar.campus.common.domain.entity.SysOperLogEntity;\nimport com.oddfar.campus.common.utils.LogUtils;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.SpringUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.ip.AddressUtils;\nimport com.oddfar.campus.common.utils.ip.IpUtils;\nimport com.oddfar.campus.framework.service.SysLoginLogService;\nimport com.oddfar.campus.framework.service.SysOperLogService;\nimport eu.bitwalker.useragentutils.UserAgent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Date;\nimport java.util.TimerTask;\n\n/**\n * 异步工厂（产生任务用）\n *\n * @author ruoyi\n */\npublic class AsyncFactory {\n    private static final Logger sys_user_logger = LoggerFactory.getLogger(\"sys-user\");\n\n    /**\n     * 记录登录信息\n     *\n     * @param userName 用户名\n     * @param status   状态\n     * @param message  消息\n     * @param args     列表\n     * @return 任务task\n     */\n    public static TimerTask recordLogininfor(final String userName, final Long userId, final String status,\n                                             final String message, final Object... args) {\n        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader(\"User-Agent\"));\n        final String ip = IpUtils.getIpAddr(ServletUtils.getRequest2());\n        return new TimerTask() {\n            @Override\n            public void run() {\n                String address = AddressUtils.getRealAddressByIP(ip);\n                StringBuilder s = new StringBuilder();\n                s.append(LogUtils.getBlock(ip));\n                s.append(address);\n                s.append(LogUtils.getBlock(userName));\n                s.append(LogUtils.getBlock(userId));\n                s.append(LogUtils.getBlock(status));\n                s.append(LogUtils.getBlock(message));\n                // 打印信息到日志\n                sys_user_logger.info(s.toString(), args);\n                // 获取客户端操作系统\n                String os = userAgent.getOperatingSystem().getName();\n                // 获取客户端浏览器\n                String browser = userAgent.getBrowser().getName();\n                // 封装对象\n                SysLoginLogEntity logininfor = new SysLoginLogEntity();\n                logininfor.setLoginTime(new Date());\n                logininfor.setUserName(userName);\n                logininfor.setUserId(userId);\n                logininfor.setIpaddr(ip);\n                logininfor.setLoginLocation(address);\n                logininfor.setBrowser(browser);\n                logininfor.setOs(os);\n                logininfor.setMsg(message);\n                // 日志状态\n                if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {\n                    logininfor.setStatus(Constants.SUCCESS);\n                } else if (Constants.LOGIN_FAIL.equals(status)) {\n                    logininfor.setStatus(Constants.FAIL);\n                }\n                // 插入数据\n                SpringUtils.getBean(SysLoginLogService.class).insertLogininfor(logininfor);\n            }\n        };\n    }\n\n    /**\n     * 操作日志记录\n     *\n     * @param operLog 操作日志信息\n     * @return 任务task\n     */\n    public static TimerTask recordOper(final SysOperLogEntity operLog) {\n        return new TimerTask() {\n            @Override\n            public void run() {\n                SpringUtils.getBean(SysOperLogService.class).insertOperlog(operLog);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/manager/AsyncManager.java",
    "content": "package com.oddfar.campus.framework.manager;\n\nimport com.oddfar.campus.common.utils.SpringUtils;\nimport com.oddfar.campus.common.utils.Threads;\n\nimport java.util.TimerTask;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 异步任务管理器\n *\n * @author ruoyi\n */\npublic class AsyncManager {\n    /**\n     * 操作延迟10毫秒\n     */\n    private final int OPERATE_DELAY_TIME = 10;\n\n    /**\n     * 异步操作任务调度线程池\n     */\n    private ScheduledExecutorService executor = SpringUtils.getBean(\"scheduledExecutorService\");\n\n    /**\n     * 单例模式\n     */\n    private AsyncManager() {\n    }\n\n    private static AsyncManager me = new AsyncManager();\n\n    public static AsyncManager me() {\n        return me;\n    }\n\n    /**\n     * 执行任务\n     *\n     * @param task 任务\n     */\n    public void execute(TimerTask task) {\n        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);\n    }\n\n    /**\n     * 停止任务线程池\n     */\n    public void shutdown() {\n        Threads.shutdownAndAwaitTermination(executor);\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysConfigMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysConfigEntity;\n\npublic interface SysConfigMapper extends BaseMapperX<SysConfigEntity> {\n    default PageResult<SysConfigEntity> selectPage(SysConfigEntity config) {\n\n        return selectPage(new LambdaQueryWrapperX<SysConfigEntity>()\n                .likeIfPresent(SysConfigEntity::getConfigName, config.getConfigName())\n                .likeIfPresent(SysConfigEntity::getConfigKey, config.getConfigKey())\n                .eqIfPresent(SysConfigEntity::getGroupCode, config.getGroupCode())\n                .betweenIfPresent(SysConfigEntity::getCreateTime, config.getParams())\n        );\n\n    }\n\n\n    /**\n     * 查询参数配置信息\n     *\n     * @param config 参数配置信息\n     * @return 参数配置信息\n     */\n    default SysConfigEntity selectConfig(SysConfigEntity config) {\n\n        return selectOne(new LambdaQueryWrapperX<SysConfigEntity>()\n                .eq(ObjectUtil.isNotEmpty(config.getConfigId()), SysConfigEntity::getConfigId, config.getConfigId())\n                .eq(ObjectUtil.isNotEmpty(config.getConfigName()), SysConfigEntity::getConfigName, config.getConfigName())\n                .eq(ObjectUtil.isNotEmpty(config.getConfigKey()), SysConfigEntity::getConfigKey, config.getConfigKey()));\n    }\n\n    /**\n     * 校验参数键名是否唯一\n     */\n    default SysConfigEntity checkConfigKeyUnique(SysConfigEntity config) {\n        return selectOne(new LambdaQueryWrapperX<SysConfigEntity>()\n                .eq(SysConfigEntity::getConfigKey, config.getConfigKey())\n        );\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysDictDataMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysDictDataEntity;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\npublic interface SysDictDataMapper extends BaseMapperX<SysDictDataEntity> {\n    default PageResult<SysDictDataEntity> selectPage(SysDictDataEntity dictData) {\n\n        return selectPage( new LambdaQueryWrapperX<SysDictDataEntity>()\n                .likeIfPresent(SysDictDataEntity::getDictType, dictData.getDictType())\n                .likeIfPresent(SysDictDataEntity::getDictLabel, dictData.getDictLabel())\n                .eqIfPresent(SysDictDataEntity::getStatus, \"0\")\n                .betweenIfPresent(SysDictDataEntity::getCreateTime, dictData.getParams())\n                .orderByAsc(SysDictDataEntity::getDictSort));\n    }\n\n    default List<SysDictDataEntity> selectDictDataByType(String dictType) {\n        return selectList(new LambdaQueryWrapperX<SysDictDataEntity>()\n                .eq(SysDictDataEntity::getDictType, dictType)\n                .eq(SysDictDataEntity::getStatus, \"0\")\n                .orderByAsc(SysDictDataEntity::getDictSort));\n    }\n\n    /**\n     * 同步修改字典类型\n     *\n     * @param oldDictType 旧字典类型\n     * @param newDictType 新旧字典类型\n     * @return 结果\n     */\n//    @Update(\"update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType}\")\n    default int updateDictDataType(@Param(\"oldDictType\") String oldDictType, @Param(\"newDictType\") String newDictType) {\n        SysDictDataEntity dictData = new SysDictDataEntity();\n        dictData.setDictType(newDictType);\n        return update(dictData, new UpdateWrapper<SysDictDataEntity>().eq(\"dict_type\", oldDictType));\n    }\n\n    /**\n     * 查询字典数据\n     *\n     * @param dictType 字典类型\n     * @return 字典数据\n     */\n    default Long countDictDataByType(String dictType) {\n//       select count(1) from sys_dict_data where dict_type=#{dictType}\n        return selectCount(\"dict_type\", dictType);\n\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysDictTypeMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysDictTypeEntity;\n\nimport java.util.Map;\n\npublic interface SysDictTypeMapper extends BaseMapperX<SysDictTypeEntity> {\n    default PageResult<SysDictTypeEntity> selectPage(SysDictTypeEntity dictType) {\n        return selectPage(new LambdaQueryWrapperX<SysDictTypeEntity>()\n                .likeIfPresent(SysDictTypeEntity::getDictName, dictType.getDictName())\n                .likeIfPresent(SysDictTypeEntity::getDictType, dictType.getDictType())\n                .eqIfPresent(SysDictTypeEntity::getStatus, dictType.getStatus())\n                .betweenIfPresent(SysDictTypeEntity::getCreateTime, dictType.getParams()));\n    }\n\n\n    default LambdaQueryWrapper<SysDictTypeEntity> creatWrapper(SysDictTypeEntity dictType) {\n        Map<String, Object> params = dictType.getParams();\n        String beginTime = (String) params.get(\"beginTime\");\n        String endTime = (String) params.get(\"endTime\");\n        return new LambdaQueryWrapperX<SysDictTypeEntity>()\n                .ge(ObjectUtil.isNotEmpty(beginTime), SysDictTypeEntity::getCreateTime, beginTime)\n                .le(ObjectUtil.isNotEmpty(endTime), SysDictTypeEntity::getCreateTime, endTime);\n\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysLoginLogMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysLoginLogEntity;\n\nimport java.util.List;\n\n/**\n * 系统访问日志情况信息 数据层\n *\n * @author ruoyi\n */\npublic interface SysLoginLogMapper extends BaseMapperX<SysLoginLogEntity> {\n\n    default PageResult<SysLoginLogEntity> selectLogininforPage(SysLoginLogEntity logininfor) {\n        return selectPage(new LambdaQueryWrapperX<SysLoginLogEntity>()\n                .eqIfPresent(SysLoginLogEntity::getUserId, logininfor.getUserId())\n                .eqIfPresent(SysLoginLogEntity::getUserName,logininfor.getUserName())\n                .eqIfPresent(SysLoginLogEntity::getStatus, logininfor.getStatus())\n                .betweenIfPresent(SysLoginLogEntity::getLoginTime, logininfor.getParams())\n                .orderByDesc(SysLoginLogEntity::getInfoId)\n        );\n    }\n\n\n    /**\n     * 查询系统登录日志集合\n     *\n     * @param logininfor 访问日志对象\n     * @return 登录记录集合\n     */\n    default List<SysLoginLogEntity> selectLogininforList(SysLoginLogEntity logininfor) {\n        return selectList(new LambdaQueryWrapperX<SysLoginLogEntity>()\n                .eqIfPresent(SysLoginLogEntity::getStatus, logininfor.getStatus())\n                .likeIfPresent(SysLoginLogEntity::getUserName, logininfor.getUserName())\n                .eqIfPresent(SysLoginLogEntity::getUserId, logininfor.getUserId()));\n    }\n\n\n    /**\n     * 清空系统登录日志\n     *\n     * @return 结果\n     */\n    int cleanLogininfor();\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysMenuMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysMenuEntity;\nimport com.oddfar.campus.common.domain.model.SysRoleAuth;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\n\npublic interface SysMenuMapper extends BaseMapperX<SysMenuEntity> {\n    default PageResult<SysMenuEntity> selectPage(SysMenuEntity sysMenuEntity) {\n\n        return selectPage(new LambdaQueryWrapperX<SysMenuEntity>());\n    }\n\n    /**\n     * 查询系统菜单列表\n     *\n     * @param menu 菜单信息\n     * @return 菜单列表\n     */\n    default List<SysMenuEntity> selectMenuList(SysMenuEntity menu) {\n\n        return selectList(new LambdaQueryWrapperX<SysMenuEntity>()\n                .like(ObjectUtil.isNotNull(menu.getMenuName()), SysMenuEntity::getMenuName, menu.getMenuName())\n                .eq(ObjectUtil.isNotNull(menu.getVisible()), SysMenuEntity::getVisible, menu.getVisible())\n                .eq(ObjectUtil.isNotNull(menu.getStatus()), SysMenuEntity::getStatus, menu.getStatus()));\n    }\n\n    /**\n     * 根据用户查询系统菜单列表\n     *\n     * @param menu 菜单信息\n     * @return 菜单列表\n     */\n    List<SysMenuEntity> selectMenuListByUserId(SysMenuEntity menu);\n\n    /**\n     * 根据用户ID查询菜单\n     *\n     * @return 菜单列表\n     */\n    List<SysMenuEntity> selectMenuTreeAll();\n\n    /**\n     * 根据用户ID查询菜单\n     *\n     * @param userId 用户ID\n     * @return 菜单列表\n     */\n    List<SysMenuEntity> selectMenuTreeByUserId(Long userId);\n\n\n    /**\n     * 根据角色ID查询权限\n     *\n     * @param roleId 角色ID\n     * @return 权限列表\n     */\n    List<String> selectMenuPermsByRoleId(Long roleId);\n\n    /**\n     * 查询所有角色的权限列表\n     *\n     * @return SysRolePerms\n     */\n    List<SysRoleAuth> getMenuPermsAll();\n\n    /**\n     * 根据用户ID查询权限\n     *\n     * @param userId 用户ID\n     * @return 权限列表\n     */\n    List<String> selectMenuPermsByUserId(Long userId);\n\n\n    /**\n     * 根据角色ID查询菜单树信息\n     *\n     * @param roleId            角色ID\n     * @param menuCheckStrictly 菜单树选择项是否关联显示\n     * @return 选中菜单列表\n     */\n    List<Long> selectMenuListByRoleId(@Param(\"roleId\") Long roleId, @Param(\"menuCheckStrictly\") boolean menuCheckStrictly);\n\n    /**\n     * 校验菜单名称是否唯一\n     */\n    default SysMenuEntity checkMenuNameUnique(SysMenuEntity menu) {\n        return selectOne(new LambdaQueryWrapperX<SysMenuEntity>()\n                .eq(SysMenuEntity::getMenuName, menu.getMenuName())\n                .eq(SysMenuEntity::getParentId, menu.getParentId())\n        );\n    }\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysOperLogMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysOperLogEntity;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * 操作日志 数据层\n */\npublic interface SysOperLogMapper extends BaseMapperX<SysOperLogEntity> {\n\n\n    /**\n     * 分页查询系统操作日志集合\n     *\n     * @param operLog 操作日志对象\n     * @return 操作日志集合\n     */\n    default PageResult<SysOperLogEntity> selectOperLogPage(SysOperLogEntity operLog) {\n\n        return selectPage(new LambdaQueryWrapperX<SysOperLogEntity>()\n                .likeIfPresent(SysOperLogEntity::getAppName, operLog.getAppName())\n                .likeIfPresent(SysOperLogEntity::getLogName, operLog.getLogName())\n                .eqIfPresent(SysOperLogEntity::getStatus,operLog.getStatus())\n                .eqIfPresent(SysOperLogEntity::getOperIp, operLog.getOperIp())\n                .eqIfPresent(SysOperLogEntity::getOperId,operLog.getOperId())\n                .betweenIfPresent(SysOperLogEntity::getOperTime, operLog.getParams())\n                .orderByDesc(SysOperLogEntity::getOperId)\n        );\n    }\n\n    /**\n     * 批量删除系统操作日志\n     *\n     * @param operIds 需要删除的操作日志ID\n     * @return 结果\n     */\n    default int deleteOperLogByIds(Long[] operIds) {\n\n        return deleteBatchIds(Arrays.asList(operIds));\n    }\n\n\n    /**\n     * 清空操作日志\n     */\n    public void cleanOperLog();\n\n    /**\n     * 查询系统操作日志集合\n     * @return\n     */\n    default List<SysOperLogEntity> selectOperLogList(SysOperLogEntity operLog){\n        return selectList(new LambdaQueryWrapperX<SysOperLogEntity>()\n                .likeIfPresent(SysOperLogEntity::getAppName, operLog.getAppName())\n                .likeIfPresent(SysOperLogEntity::getLogName, operLog.getLogName())\n                .eqIfPresent(SysOperLogEntity::getOperIp, operLog.getOperIp())\n                .betweenIfPresent(SysOperLogEntity::getOperTime, operLog.getParams())\n        );\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysResourceMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport org.apache.ibatis.annotations.Update;\n\nimport java.util.List;\nimport java.util.Set;\n\npublic interface SysResourceMapper extends BaseMapperX<SysResourceEntity> {\n    default PageResult<SysResourceEntity> selectPage(SysResourceEntity resource) {\n\n        return selectPage(new LambdaQueryWrapperX<SysResourceEntity>()\n                .betweenIfPresent(SysResourceEntity::getCreateTime, resource.getParams()));\n    }\n\n\n    /**\n     * 清空 sys_resource 数据库\n     */\n    @Update(\"truncate table sys_resource\")\n    void truncateResource();\n\n    /**\n     * 根据角色ID查询资源编码列表\n     *\n     * @param roleId 角色ID\n     * @return 权限列表\n     */\n    Set<String> selectResourceCodeByRoleId(Long roleId);\n\n    /**\n     * 根据角色ID查询资源树信息\n     *\n     * @param roleId 角色ID\n     * @return 选中接口资源列表\n     */\n    List<Long> selectResourceListByRoleId(Long roleId);\n\n    /**\n     * 根据用户查询api资源列表\n     *\n     * @param resource\n     * @return\n     */\n    List<SysResourceEntity> selectResourceListByUserId(SysResourceEntity resource);\n\n    /**\n     * 查询资源列表\n     *\n     * @param resource\n     * @return\n     */\n    List<SysResourceEntity> selectResourceList(SysResourceEntity resource);\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysRoleMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.List;\nimport java.util.Set;\n\npublic interface SysRoleMapper extends BaseMapperX<SysRoleEntity> {\n    default PageResult<SysRoleEntity> selectPage(SysRoleEntity role) {\n\n        return selectPage(new LambdaQueryWrapperX<SysRoleEntity>()\n                .likeIfPresent(SysRoleEntity::getRoleName, role.getRoleName())\n                .likeIfPresent(SysRoleEntity::getRoleKey, role.getRoleKey())\n                .eqIfPresent(SysRoleEntity::getStatus, role.getStatus())\n        );\n    }\n\n    /**\n     * 根据用户ID查询角色\n     *\n     * @param userId 用户ID\n     * @return 角色列表\n     */\n    List<SysRoleEntity> selectRolePermissionByUserId(Long userId);\n\n    /**\n     * 通过角色ID查询角色\n     *\n     * @param roleId 角色ID\n     * @return 角色对象信息\n     */\n    SysRoleEntity selectRoleById(Long roleId);\n\n    /**\n     * 根据条件分页查询角色数据\n     *\n     * @param role 角色信息\n     * @return 角色数据集合信息\n     */\n    List<SysRoleEntity> selectRoleList(SysRoleEntity role);\n\n    /**\n     * 根据角色权限字符串查询角色数据\n     * @param RoleKeys\n     * @return\n     */\n    List<SysRoleEntity> selectRoleListByKey(@Param(\"RoleKeys\") Set<String> RoleKeys);\n\n    /**\n     * 校验角色名称是否唯一\n     *\n     * @param roleName 角色名称\n     * @return 角色信息\n     */\n    SysRoleEntity checkRoleNameUnique(String roleName);\n\n    /**\n     * 校验角色权限是否唯一\n     *\n     * @param roleKey 角色权限\n     * @return 角色信息\n     */\n    SysRoleEntity checkRoleKeyUnique(String roleKey);\n\n    /**\n     * 根据用户ID查询角色\n     *\n     * @param userName 用户名\n     * @return 角色列表\n     */\n    public List<SysRoleEntity> selectRolesByUserName(String userName);\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysRoleMenuMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.domain.entity.SysRoleMenuEntity;\n\nimport java.util.List;\n\npublic interface SysRoleMenuMapper extends BaseMapperX<SysRoleMenuEntity> {\n\n    /**\n     * 查询菜单使用数量\n     *\n     * @param menuId 菜单ID\n     * @return 结果\n     */\n    public int checkMenuExistRole(Long menuId);\n\n    /**\n     * 通过角色ID删除角色和菜单关联\n     *\n     * @param roleId 角色ID\n     * @return 结果\n     */\n    public int deleteRoleMenuByRoleId(Long roleId);\n\n    /**\n     * 批量删除角色菜单关联信息\n     *\n     * @param ids 需要删除的数据ID\n     * @return 结果\n     */\n    public int deleteRoleMenu(Long[] ids);\n\n    /**\n     * 批量新增角色菜单信息\n     *\n     * @param roleMenuList 角色菜单列表\n     * @return 结果\n     */\n    public int batchRoleMenu(List<SysRoleMenuEntity> roleMenuList);\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysRoleResourceMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.domain.entity.SysRoleResourceEntity;\n\nimport java.util.List;\n\npublic interface SysRoleResourceMapper extends BaseMapperX<SysRoleResourceEntity> {\n\n\n    /**\n     * 删除角色与资源关联\n     *\n     * @param roleId 角色id\n     */\n    default int deleteRoleResourceByRoleId(Long roleId) {\n        return delete(new QueryWrapper<SysRoleResourceEntity>().eq(\"role_id\", roleId));\n    }\n\n    /**\n     * 批量保存角色与资源关系\n     *\n     * @param rrList\n     */\n    int saveBatch(List<SysRoleResourceEntity> rrList);\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysUserMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.baomidou.mybatisplus.core.conditions.Wrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Constants;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport org.apache.ibatis.annotations.Param;\n\npublic interface SysUserMapper extends BaseMapperX<SysUserEntity> {\n\n    default PageResult<SysUserEntity> selectPage(SysUserEntity user) {\n\n        return selectPage(new LambdaQueryWrapperX<SysUserEntity>()\n                .likeIfPresent(SysUserEntity::getUserName, user.getUserName())\n                .likeIfPresent(SysUserEntity::getPhonenumber, user.getPhonenumber())\n                .eqIfPresent(SysUserEntity::getStatus, user.getStatus())\n                .betweenIfPresent(SysUserEntity::getCreateTime, user.getParams())\n        );\n    }\n\n\n    /**\n     * 通过用户名查询用户\n     *\n     * @param userName\n     * @return\n     */\n    SysUserEntity selectUserByUserName(String userName);\n\n    /**\n     * 通过用户ID查询用户\n     *\n     * @param userId 用户ID\n     * @return 用户对象信息\n     */\n    SysUserEntity selectUserById(Long userId);\n\n\n    /**\n     * 根据条件分页查询已配用户角色列表\n     *\n     * @return 用户信息集合信息\n     */\n    Page<SysUserEntity> selectAllocatedList(@Param(\"page\") Page<SysUserEntity> page, @Param(Constants.WRAPPER) Wrapper<SysUserEntity> queryWrapper);\n\n    /**\n     * 根据条件分页查询未分配用户角色列表\n     *\n     * @param user 用户信息\n     * @return 用户信息集合信息\n     */\n    Page<SysUserEntity> selectUnallocatedList(@Param(\"page\") Page<SysUserEntity> page, @Param(\"user\") SysUserEntity user);\n\n\n    /**\n     * 修改用户头像\n     *\n     * @param userName 用户名\n     * @param avatar   头像地址\n     * @return 结果\n     */\n    int updateUserAvatar(@Param(\"userName\") String userName, @Param(\"avatar\") String avatar);\n\n    /**\n     * 校验email是否唯一\n     *\n     * @param email 用户邮箱\n     * @return 结果\n     */\n    default SysUserEntity checkEmailUnique(String email) {\n\n        return selectOne(new LambdaQueryWrapperX<SysUserEntity>().eq(SysUserEntity::getEmail, email));\n    }\n\n    /**\n     * 校验手机号码是否唯一\n     *\n     * @param phonenumber 手机号码\n     * @return 结果\n     */\n    default SysUserEntity checkPhoneUnique(String phonenumber) {\n        return selectOne(new LambdaQueryWrapperX<SysUserEntity>().eq(SysUserEntity::getPhonenumber, phonenumber));\n    }\n\n    /**\n     * 重置用户密码\n     *\n     * @param userName 用户名\n     * @param password 密码\n     * @return 结果\n     */\n    int resetUserPwd(@Param(\"userName\") String userName, @Param(\"password\") String password);\n\n\n    /**\n     * 校验用户名称是否唯一\n     *\n     * @param userName 用户名称\n     * @return 结果\n     */\n    default SysUserEntity checkUserNameUnique(String userName) {\n        return selectOne(new LambdaQueryWrapperX<SysUserEntity>().eq(SysUserEntity::getUserName, userName));\n    }\n\n\n}"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysUserRoleMapper.java",
    "content": "package com.oddfar.campus.framework.mapper;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.domain.entity.SysUserRoleEntity;\nimport org.apache.ibatis.annotations.Param;\n\npublic interface SysUserRoleMapper extends BaseMapperX<SysUserRoleEntity> {\n\n    /**\n     * 通过用户ID删除用户和角色关联\n     *\n     * @param userId 用户ID\n     * @return 结果\n     */\n    default int deleteUserRoleByUserId(Long userId) {\n        return delete(new QueryWrapper<SysUserRoleEntity>()\n                .eq(\"user_id\" , userId));\n    }\n\n    /**\n     * 删除用户和角色关联信息\n     *\n     * @param userRole 用户和角色关联信息\n     * @return 结果\n     */\n    default int deleteUserRoleInfo(SysUserRoleEntity userRole) {\n        return delete(new QueryWrapper<SysUserRoleEntity>()\n                .eq(\"user_id\" , userRole.getUserId())\n                .eq(\"role_id\" , userRole.getRoleId()));\n    }\n\n    /**\n     * 批量取消授权用户角色\n     *\n     * @param roleId  角色ID\n     * @param userIds 需要删除的用户数据ID\n     * @return 结果\n     */\n    default int deleteUserRoleInfos(@Param(\"roleId\") Long roleId, @Param(\"userIds\") Long[] userIds) {\n        return delete(new QueryWrapper<SysUserRoleEntity>()\n                .eq(\"role_id\" , roleId)\n                .in(\"user_id\" , userIds));\n    }\n\n    /**\n     * 通过角色ID查询角色使用数量\n     *\n     * @param roleId 角色ID\n     * @return 结果\n     */\n    default int countUserRoleByRoleId(Long roleId) {\n\n        return selectCount(\"role_id\" , roleId).intValue();\n    }\n\n    /**\n     * 批量删除用户和角色关联\n     *\n     * @param userIds 需要删除的数据ID\n     * @return 结果\n     */\n    void deleteUserRole(Long[] userIds);\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/security/context/AuthenticationContextHolder.java",
    "content": "package com.oddfar.campus.framework.security.context;\n\nimport org.springframework.security.core.Authentication;\n\n/**\n * 身份验证信息\n *\n * @author ruoyi\n */\npublic class AuthenticationContextHolder {\n    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();\n\n    public static Authentication getContext() {\n        return contextHolder.get();\n    }\n\n    public static void setContext(Authentication context) {\n        contextHolder.set(context);\n    }\n\n    public static void clearContext() {\n        contextHolder.remove();\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/security/context/PermissionContextHolder.java",
    "content": "package com.oddfar.campus.framework.security.context;\n\n\nimport cn.hutool.core.convert.Convert;\nimport org.springframework.web.context.request.RequestAttributes;\nimport org.springframework.web.context.request.RequestContextHolder;\n\n/**\n * 权限信息\n *\n * @author ruoyi\n */\npublic class PermissionContextHolder {\n    private static final String PERMISSION_CONTEXT_ATTRIBUTES = \"PERMISSION_CONTEXT\";\n\n    public static void setContext(String permission) {\n        RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,\n                RequestAttributes.SCOPE_REQUEST);\n    }\n\n    public static String getContext() {\n        return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,\n                RequestAttributes.SCOPE_REQUEST));\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/security/filter/JwtAuthenticationTokenFilter.java",
    "content": "package com.oddfar.campus.framework.security.filter;\n\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.web.WebFrameworkUtils;\nimport com.oddfar.campus.framework.web.service.TokenService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.security.web.authentication.WebAuthenticationDetailsSource;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport javax.servlet.FilterChain;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * token过滤器 验证token有效性\n *\n * @author ruoyi\n */\n@Component\npublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter {\n    @Autowired\n    private TokenService tokenService;\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)\n            throws ServletException, IOException {\n        LoginUser loginUser = tokenService.getLoginUser(request);\n        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {\n            // 设置当前用户\n            setLoginUser(loginUser, request);\n\n            tokenService.verifyToken(loginUser);\n            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());\n            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));\n            SecurityContextHolder.getContext().setAuthentication(authenticationToken);\n\n        }\n        chain.doFilter(request, response);\n    }\n\n    /**\n     * 设置当前用户\n     *\n     * @param loginUser 登录用户\n     * @param request   请求\n     */\n    public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {\n\n        // 额外设置到 request 中，有的过滤器在 Spring Security 之前\n        WebFrameworkUtils.setLoginUserId(request, loginUser.getUserId());\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/security/handle/AuthenticationEntryPointImpl.java",
    "content": "package com.oddfar.campus.framework.security.handle;\n\nimport com.alibaba.fastjson2.JSON;\nimport com.oddfar.campus.common.constant.HttpStatus;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.web.AuthenticationEntryPoint;\nimport org.springframework.stereotype.Component;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.io.Serializable;\n\n/**\n * 认证失败处理类 返回未授权\n *\n * @author ruoyi\n */\n@Component\npublic class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {\n    private static final long serialVersionUID = -8970718410437077606L;\n\n    @Override\n    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)\n            throws IOException {\n        int code = HttpStatus.UNAUTHORIZED;\n        String msg = StringUtils.format(\"请求访问：{}，认证失败，无法访问系统资源\", request.getRequestURI());\n        ServletUtils.renderString(response, JSON.toJSONString(R.error(code, msg)));\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/security/handle/LogoutSuccessHandlerImpl.java",
    "content": "package com.oddfar.campus.framework.security.handle;\n\nimport com.alibaba.fastjson2.JSON;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.constant.HttpStatus;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.utils.MessageUtils;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.manager.AsyncFactory;\nimport com.oddfar.campus.framework.manager.AsyncManager;\nimport com.oddfar.campus.framework.web.service.TokenService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.web.authentication.logout.LogoutSuccessHandler;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * 自定义退出处理类 返回成功\n *\n * @author ruoyi\n */\n@Configuration\npublic class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {\n    @Autowired\n    private TokenService tokenService;\n\n    /**\n     * 退出处理\n     *\n     */\n    @Override\n    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)\n            throws IOException, ServletException {\n        LoginUser loginUser = tokenService.getLoginUser(request);\n        if (StringUtils.isNotNull(loginUser)) {\n            // 删除用户缓存记录\n            tokenService.delLoginUser(loginUser.getToken());\n            // 记录用户退出日志\n            AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getUsername(), loginUser.getUserId(), Constants.LOGOUT, MessageUtils.message(\"user.logout.success\")));\n        }\n        ServletUtils.renderString(response, JSON.toJSONString(R.error(HttpStatus.SUCCESS, MessageUtils.message(\"user.logout.success\"))));\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/security/properties/PermitAllUrlProperties.java",
    "content": "package com.oddfar.campus.framework.security.properties;\n\nimport com.oddfar.campus.common.annotation.Anonymous;\nimport org.apache.commons.lang3.RegExUtils;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.annotation.AnnotationUtils;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.mvc.method.RequestMappingInfo;\nimport org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.regex.Pattern;\n\n/**\n * 设置Anonymous注解允许匿名访问的url\n * \n * @author ruoyi\n */\n@Configuration\npublic class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware\n{\n    private static final Pattern PATTERN = Pattern.compile(\"\\\\{(.*?)\\\\}\");\n\n    private ApplicationContext applicationContext;\n\n    private List<String> urls = new ArrayList<>();\n\n    public String ASTERISK = \"*\";\n\n    @Override\n    public void afterPropertiesSet()\n    {\n        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);\n        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();\n\n        map.keySet().forEach(info -> {\n            HandlerMethod handlerMethod = map.get(info);\n\n            // 获取方法上边的注解 替代path variable 为 *\n            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);\n            Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()\n                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));\n\n            // 获取类上边的注解, 替代path variable 为 *\n            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);\n            Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()\n                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));\n        });\n    }\n\n    @Override\n    public void setApplicationContext(ApplicationContext context) throws BeansException\n    {\n        this.applicationContext = context;\n    }\n\n    public List<String> getUrls()\n    {\n        return urls;\n    }\n\n    public void setUrls(List<String> urls)\n    {\n        this.urls = urls;\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysConfigService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysConfigEntity;\n\npublic interface SysConfigService {\n\n    PageResult<SysConfigEntity> page(SysConfigEntity sysConfigEntity);\n\n    /**\n     * 查询参数配置信息\n     *\n     * @param configId 参数配置ID\n     * @return 参数配置信息\n     */\n    SysConfigEntity selectConfigById(Long configId);\n\n    /**\n     * 根据键名查询参数配置信息\n     *\n     * @param configKey 参数键名\n     * @return 参数键值\n     */\n    String selectConfigByKey(String configKey);\n\n    /**\n     * 根据键名查询参数配置信息\n     *\n     * @param configKey\n     * @param clazz     转换的类\n     * @param <T>\n     * @return\n     */\n    <T> T selectConfigByKey(String configKey, Class<T> clazz);\n\n    /**\n     * 根据键名查询参数配置信息\n     * 查询的值为null则返回defaultValue\n     *\n     * @param configKey\n     * @param clazz\n     * @param defaultValue 默认的内容\n     * @param <T>\n     * @return\n     */\n    <T> T selectConfigByKey(String configKey, Class<T> clazz, T defaultValue);\n\n\n    /**\n     * 获取验证码开关\n     *\n     * @return true开启，false关闭\n     */\n    boolean selectCaptchaEnabled();\n\n    /**\n     * 新增参数配置\n     *\n     * @param config 参数配置信息\n     * @return 结果\n     */\n    int insertConfig(SysConfigEntity config);\n\n    /**\n     * 修改参数配置\n     *\n     * @param config 参数配置信息\n     * @return 结果\n     */\n    int updateConfig(SysConfigEntity config);\n\n    /**\n     * 批量删除参数信息\n     *\n     * @param configIds 需要删除的参数ID\n     */\n    void deleteConfigByIds(Long[] configIds);\n\n    /**\n     * 加载参数缓存数据\n     */\n    void loadingConfigCache();\n\n    /**\n     * 校验参数键名是否唯一\n     *\n     * @param config 参数配置信息\n     * @return 结果 true为唯一\n     */\n    boolean checkConfigKeyUnique(SysConfigEntity config);\n\n    /**\n     * 清空参数缓存数据\n     */\n    void clearConfigCache();\n\n    /**\n     * 重置参数缓存数据\n     */\n    void resetConfigCache();\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysDictDataService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysDictDataEntity;\n\npublic interface SysDictDataService {\n\n    PageResult<SysDictDataEntity> page(SysDictDataEntity dictDataEntity);\n\n    /**\n     * 新增保存字典数据信息\n     *\n     * @param dictData 字典数据信息\n     * @return 结果\n     */\n    int insertDictData(SysDictDataEntity dictData);\n\n    /**\n     * 根据字典数据ID查询信息\n     *\n     * @param dictCode 字典数据ID\n     * @return 字典数据\n     */\n    SysDictDataEntity selectDictDataById(Long dictCode);\n\n    /**\n     * 修改保存字典数据信息\n     *\n     * @param dictData 字典数据信息\n     * @return 结果\n     */\n     int updateDictData(SysDictDataEntity dictData);\n\n    /**\n     * 批量删除字典数据信息\n     *\n     * @param dictCodes 需要删除的字典数据ID\n     */\n     void deleteDictDataByIds(Long[] dictCodes);\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysDictTypeService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysDictDataEntity;\nimport com.oddfar.campus.common.domain.entity.SysDictTypeEntity;\n\nimport java.util.List;\n\npublic interface SysDictTypeService  {\n\n    PageResult<SysDictTypeEntity> page(SysDictTypeEntity sysDictTypeEntity);\n\n\n    /**\n     * 根据字典类型查询字典数据\n     *\n     * @param dictType 字典类型\n     * @return 字典数据集合信息\n     */\n    List<SysDictDataEntity> selectDictDataByType(String dictType);\n\n    /**\n     * 根据字典类型ID查询信息\n     *\n     * @param dictId 字典类型ID\n     * @return 字典类型\n     */\n    SysDictTypeEntity selectDictTypeById(Long dictId);\n\n    /**\n     * 查询所有字典类型\n     *\n     * @return 字典类型集合信息\n     */\n     List<SysDictTypeEntity> selectDictTypeAll();\n\n    /**\n     * 修改保存字典类型信息\n     *\n     * @param dictType 字典类型信息\n     * @return 结果\n     */\n     int updateDictType(SysDictTypeEntity dictType);\n\n    /**\n     * 新增保存字典类型信息\n     *\n     * @param dictType 字典类型信息\n     * @return 结果\n     */\n     int insertDictType(SysDictTypeEntity dictType);\n\n    /**\n     * 批量删除字典信息\n     *\n     * @param dictIds 需要删除的字典ID\n     */\n     void deleteDictTypeByIds(Long[] dictIds);\n\n    /**\n     * 重置字典缓存数据\n     */\n    void resetDictCache();\n\n    /**\n     * 加载字典缓存数据\n     */\n     void loadingDictCache();\n\n    /**\n     * 校验字典类型称是否唯一\n     *\n     * @param dictType 字典类型\n     * @return 结果\n     */\n    boolean checkDictTypeUnique(SysDictTypeEntity dictType);\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysLoginLogService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysLoginLogEntity;\n\nimport java.util.List;\n\n/**\n * 系统访问日志情况信息 服务层\n */\npublic interface SysLoginLogService {\n    /**\n     * 查询系统登录日志分页数据\n     *\n     * @param logininfor 访问日志对象\n     * @return 登录记录分页数据\n     */\n    public PageResult<SysLoginLogEntity> selectLogininforPage(SysLoginLogEntity logininfor);\n\n    /**\n     * 新增系统登录日志\n     *\n     * @param logininfor 访问日志对象\n     */\n    public void insertLogininfor(SysLoginLogEntity logininfor);\n\n    /**\n     * 查询系统登录日志集合\n     *\n     * @param logininfor 访问日志对象\n     * @return 登录记录集合\n     */\n    public List<SysLoginLogEntity> selectLogininforList(SysLoginLogEntity logininfor);\n\n    /**\n     * 批量删除系统登录日志\n     *\n     * @param infoIds 需要删除的登录日志ID\n     * @return 结果\n     */\n    public int deleteLogininforByIds(Long[] infoIds);\n\n    /**\n     * 清空系统登录日志\n     */\n    public void cleanLogininfor();\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysMenuService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.oddfar.campus.common.domain.TreeSelect;\nimport com.oddfar.campus.common.domain.entity.SysMenuEntity;\nimport com.oddfar.campus.common.domain.model.SysRoleAuth;\nimport com.oddfar.campus.common.domain.vo.RouterVo;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\npublic interface SysMenuService {\n\n    /**\n     * 根据用户查询系统菜单列表\n     *\n     * @param userId 用户ID\n     * @return 菜单列表\n     */\n    List<SysMenuEntity> selectMenuList(Long userId);\n\n\n    /**\n     * 根据用户查询系统菜单列表\n     *\n     * @param menu   菜单信息\n     * @param userId 用户ID\n     * @return 菜单列表\n     */\n    List<SysMenuEntity> selectMenuList(SysMenuEntity menu, Long userId);\n\n    /**\n     * 根据用户ID查询菜单树信息\n     *\n     * @param userId 用户ID\n     * @return 菜单列表\n     */\n    List<SysMenuEntity> selectMenuTreeByUserId(Long userId);\n\n    /**\n     * 根据角色ID查询菜单树信息\n     *\n     * @param roleId 角色ID\n     * @return 选中菜单列表\n     */\n    List<Long> selectMenuListByRoleId(Long roleId);\n\n    /**\n     * 根据菜单ID查询信息\n     *\n     * @param menuId 菜单ID\n     * @return 菜单信息\n     */\n    SysMenuEntity selectMenuById(Long menuId);\n\n    /**\n     * 构建前端路由所需要的菜单\n     *\n     * @param menus 菜单列表\n     * @return 路由列表\n     */\n    List<RouterVo> buildMenus(List<SysMenuEntity> menus);\n\n    /**\n     * 构建前端所需要树结构\n     *\n     * @param menus 菜单列表\n     * @return 树结构列表\n     */\n    List<SysMenuEntity> buildMenuTree(List<SysMenuEntity> menus);\n\n    /**\n     * 构建前端所需要下拉树结构\n     *\n     * @param menus 菜单列表\n     * @return 下拉树结构列表\n     */\n    List<TreeSelect> buildMenuTreeSelect(List<SysMenuEntity> menus);\n\n    /**\n     * 根据角色ID查询权限\n     *\n     * @param roleId 角色ID\n     * @return 权限列表\n     */\n    Set<String> selectMenuPermsByRoleId(Long roleId);\n\n    /**\n     * 所有权限\n     *\n     * @return 根据角色id分组的权限列表\n     */\n    Map<Long, List<SysRoleAuth>> selectMenuPermsAll();\n\n    /**\n     * 根据用户ID查询权限\n     *\n     * @param userId 用户ID\n     * @return 权限列表\n     */\n    Collection<String> selectMenuPermsByUserId(Long userId);\n\n    /**\n     * 新增保存菜单信息\n     *\n     * @param menu 菜单信息\n     * @return 结果\n     */\n    int insertMenu(SysMenuEntity menu);\n\n    /**\n     * 修改保存菜单信息\n     *\n     * @param menu 菜单信息\n     * @return 结果\n     */\n    int updateMenu(SysMenuEntity menu);\n\n    /**\n     * 删除菜单管理信息\n     *\n     * @param menuId 菜单ID\n     * @return 结果\n     */\n     int deleteMenuById(Long menuId);\n\n    /**\n     * 查询菜单是否存在角色\n     *\n     * @param menuId 菜单ID\n     * @return 结果 true 存在 false 不存在\n     */\n    boolean checkMenuExistRole(Long menuId);\n\n    /**\n     * 是否存在菜单子节点\n     *\n     * @param menuId 菜单ID\n     * @return 结果 true 存在 false 不存在\n     */\n    boolean hasChildByMenuId(Long menuId);\n\n    /**\n     * 校验菜单名称是否唯一\n     *\n     * @param menu 菜单信息\n     * @return 结果 true为唯一\n     */\n    boolean checkMenuNameUnique(SysMenuEntity menu);\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysOperLogService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysOperLogEntity;\n\nimport java.util.List;\n\n/**\n * 操作日志 服务层\n */\npublic interface SysOperLogService {\n    /**\n     * 新增操作日志\n     *\n     * @param operLog 操作日志对象\n     */\n    public void insertOperlog(SysOperLogEntity operLog);\n\n    /**\n     * 查询系统操作日志集合\n     *\n     * @param operLog 操作日志对象\n     * @return 操作日志集合\n     */\n    PageResult<SysOperLogEntity> selectOperLogPage(SysOperLogEntity operLog);\n\n    /**\n     * 批量删除系统操作日志\n     *\n     * @param operIds 需要删除的操作日志ID\n     * @return 结果\n     */\n    public int deleteOperLogByIds(Long[] operIds);\n\n    /**\n     * 查询操作日志详细\n     *\n     * @param operId 操作ID\n     * @return 操作日志对象\n     */\n    public SysOperLogEntity selectOperLogById(Long operId);\n\n    /**\n     * 清空操作日志\n     */\n    public void cleanOperLog();\n\n    /**\n     * 查询操作日志列表\n     *\n     * @param operLog 操作日志对象\n     * @return\n     */\n    List<SysOperLogEntity> selectOperLogList(SysOperLogEntity operLog);\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysResourceService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.TreeSelect;\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport com.oddfar.campus.common.domain.model.SysRoleAuth;\n\nimport java.util.List;\nimport java.util.Set;\n\npublic interface SysResourceService extends IService<SysResourceEntity> {\n\n    PageResult<SysResourceEntity> page(SysResourceEntity sysResourceEntity);\n\n\n    /**\n     * 新增接口资源信息\n     *\n     * @param resource\n     * @return\n     */\n    int insertResource(SysResourceEntity resource);\n\n    /**\n     * 清空 sys_resource 数据库\n     */\n    void truncateResource();\n\n    /**\n     * 根据角色ID查询资源编码列表\n     *\n     * @param roleId 角色ID\n     * @return 权限列表\n     */\n    Set<String> selectResourceCodeByRoleId(Long roleId);\n\n\n    /**\n     * 根据用户id查询api资源列表\n     *\n     * @param userId\n     * @return\n     */\n    List<SysResourceEntity> selectApiResourceList(Long userId);\n\n    /**\n     * 查询所有SysRoleAuth关系,关于resource的\n     */\n    List<SysRoleAuth> selectSysRoleAuthAll();\n\n    /**\n     * 根据用户id查询api资源列表\n     *\n     * @param userId\n     * @return\n     */\n    List<SysResourceEntity> selectApiResourceList(SysResourceEntity resource, Long userId);\n\n    /**\n     * 根据角色ID查询资源树信息\n     *\n     * @param roleId 角色ID\n     * @return 选中接口资源列表\n     */\n    List<Long> selectResourceListByRoleId(Long roleId);\n\n    /**\n     * 构建前端所需要下拉树结构\n     *\n     * @param resources 资源列表\n     * @return 下拉树结构列表\n     */\n    List<TreeSelect> buildResourceTreeSelect(List<SysResourceEntity> resources);\n\n    /**\n     * 修改角色\n     *\n     * @param roleId\n     * @param resourceIds\n     */\n    void editRoleResource(Long roleId, Long[] resourceIds);\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysRoleService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserRoleEntity;\n\nimport java.util.List;\nimport java.util.Set;\n\npublic interface SysRoleService extends IService<SysUserRoleEntity> {\n\n\n    PageResult<SysRoleEntity> page(SysRoleEntity sysRoleEntity);\n\n    /**\n     * 根据条件分页查询角色数据\n     *\n     * @param role 角色信息\n     * @return 角色数据集合信息\n     */\n    List<SysRoleEntity> selectRoleList(SysRoleEntity role);\n\n    /**\n     * 根据用户ID查询角色权限\n     *\n     * @param userId 用户ID\n     * @return 权限列表\n     */\n    Set<String> selectRolePermissionByUserId(Long userId);\n\n    /**\n     * 通过角色ID查询角色\n     *\n     * @param roleId 角色ID\n     * @return 角色对象信息\n     */\n    SysRoleEntity selectRoleById(Long roleId);\n\n    /**\n     * 查询所有角色\n     *\n     * @return 角色列表\n     */\n    List<SysRoleEntity> selectRoleAll();\n\n    /**\n     * 根据用户ID查询角色列表\n     *\n     * @param userId 用户ID\n     * @return 角色列表\n     */\n    List<SysRoleEntity> selectRolesByUserId(Long userId);\n\n\n    /**\n     * 新增保存角色信息\n     *\n     * @param role 角色信息\n     * @return 结果\n     */\n    int insertRole(SysRoleEntity role);\n\n    /**\n     * 修改保存角色信息\n     *\n     * @param role 角色信息\n     * @return 结果\n     */\n    int updateRole(SysRoleEntity role);\n\n    /**\n     * 修改角色状态\n     *\n     * @param role 角色信息\n     * @return 结果\n     */\n    int updateRoleStatus(SysRoleEntity role);\n\n    /**\n     * 批量删除角色信息\n     *\n     * @param roleIds 需要删除的角色ID\n     * @return 结果\n     */\n    int deleteRoleByIds(Long[] roleIds);\n\n    /**\n     * 取消授权用户角色\n     *\n     * @param userRole 用户和角色关联信息\n     * @return 结果\n     */\n    int deleteAuthUser(SysUserRoleEntity userRole);\n\n    /**\n     * 批量取消授权用户角色\n     *\n     * @param roleId  角色ID\n     * @param userIds 需要取消授权的用户数据ID\n     * @return 结果\n     */\n    int deleteAuthUsers(Long roleId, Long[] userIds);\n\n    /**\n     * 批量选择授权用户角色\n     *\n     * @param roleId  角色ID\n     * @param userIds 需要删除的用户数据ID\n     * @return 结果\n     */\n    boolean insertAuthUsers(Long roleId, Long[] userIds);\n\n    /**\n     * 通过角色ID查询角色使用数量\n     *\n     * @param roleId 角色ID\n     * @return 结果\n     */\n    int countUserRoleByRoleId(Long roleId);\n\n    /**\n     * 校验角色名称是否唯一\n     *\n     * @param role 角色信息\n     * @return 结果\n     */\n    boolean checkRoleNameUnique(SysRoleEntity role);\n\n    /**\n     * 校验角色权限是否唯一\n     *\n     * @param role 角色信息\n     * @return 结果\n     */\n    boolean checkRoleKeyUnique(SysRoleEntity role);\n\n    /**\n     * 校验角色是否允许操作\n     *\n     * @param role 角色信息\n     */\n    void checkRoleAllowed(SysRoleEntity role);\n\n    /**\n     * 重置角色的资源和菜单权限缓存\n     */\n    void resetRoleAuthCache();\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/SysUserService.java",
    "content": "package com.oddfar.campus.framework.service;\n\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\n\nimport java.util.Set;\n\n\npublic interface SysUserService {\n\n    PageResult<SysUserEntity> page(SysUserEntity sysUserEntity);\n\n\n    /**\n     * 通过用户名查询用户\n     *\n     * @param userName 用户名\n     * @return 用户对象信息\n     */\n    SysUserEntity selectUserByUserName(String userName);\n\n    /**\n     * 通过用户ID查询用户\n     *\n     * @param userId 用户ID\n     * @return 用户对象信息\n     */\n    SysUserEntity selectUserById(Long userId);\n\n    /**\n     * 根据条件分页查询已分配用户角色列表\n     *\n     * @param user 用户信息\n     * @return 用户信息集合信息\n     */\n    Page<SysUserEntity> selectAllocatedList(SysUserEntity user);\n\n    /**\n     * 根据条件分页查询未分配用户角色列表\n     *\n     * @param user 用户信息\n     * @return 用户信息集合信息\n     */\n    Page<SysUserEntity> selectUnallocatedList(SysUserEntity user);\n\n    /**\n     * 根据用户ID查询用户所属角色组\n     *\n     * @param userName 用户名\n     * @return 结果\n     */\n    String selectUserRoleGroup(String userName);\n\n    /**\n     * 注册用户信息\n     *\n     * @param user 用户信息\n     * @return 结果\n     */\n     boolean registerUser(SysUserEntity user);\n\n    /**\n     * 新增用户信息\n     *\n     * @param user 用户信息\n     * @return 结果\n     */\n    int insertUser(SysUserEntity user);\n\n    /**\n     * 修改用户信息\n     *\n     * @param user 用户信息\n     * @return 结果\n     */\n    int updateUser(SysUserEntity user);\n\n    /**\n     * 批量删除用户信息\n     *\n     * @param userIds 需要删除的用户ID\n     * @return 结果\n     */\n    int deleteUserByIds(Long[] userIds);\n\n    /**\n     * 修改用户基本信息\n     *\n     * @param user 用户信息\n     * @return 结果\n     */\n    int updateUserProfile(SysUserEntity user);\n\n    /**\n     * 修改用户状态\n     *\n     * @param user 用户信息\n     * @return 结果\n     */\n    int updateUserStatus(SysUserEntity user);\n\n    /**\n     * 修改用户头像\n     *\n     * @param userName 用户名\n     * @param avatar 头像地址\n     * @return 结果\n     */\n     boolean updateUserAvatar(String userName, String avatar);\n\n    /**\n     * 重置用户密码\n     *\n     * @param user 用户信息\n     * @return 结果\n     */\n    int resetPwd(SysUserEntity user);\n\n    /**\n     * 校验用户是否允许操作\n     *\n     * @param user 用户信息\n     */\n    void checkUserAllowed(SysUserEntity user);\n\n    /**\n     * 用户授权角色(先删除再添加)\n     *\n     * @param userId  用户ID\n     * @param roleIds 角色组\n     */\n    void insertUserAuth(Long userId, Long[] roleIds);\n\n    /**\n     * 用户授权角色\n     *\n     * @param userId  用户ID\n     * @param roleKey 角色权限字符串\n     */\n    void insertUserAuth(Long userId, Set<String> roleKey);\n\n    /**\n     * 重置用户密码\n     *\n     * @param userName 用户名\n     * @param password 密码\n     * @return 结果\n     */\n     int resetUserPwd(String userName, String password);\n\n    /**\n     * 校验用户名称是否唯一\n     *\n     * @param user 用户信息\n     * @return 结果\n     */\n     boolean checkUserNameUnique(SysUserEntity user);\n\n    /**\n     * 校验手机号码是否唯一\n     *\n     * @param user 用户信息\n     * @return 结果 true为唯一\n     */\n    boolean checkPhoneUnique(SysUserEntity user);\n\n    /**\n     * 校验email是否唯一\n     *\n     * @param user 用户信息\n     * @return 结果 true为唯一\n     */\n    boolean checkEmailUnique(SysUserEntity user);\n\n\n}"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysConfigServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.oddfar.campus.common.constant.CacheConstants;\nimport com.oddfar.campus.common.constant.UserConstants;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysConfigEntity;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.mapper.SysConfigMapper;\nimport com.oddfar.campus.framework.service.SysConfigService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.PostConstruct;\nimport javax.annotation.Resource;\nimport java.util.Collection;\nimport java.util.List;\n\n@Service\npublic class SysConfigServiceImpl implements SysConfigService {\n\n    @Resource\n    private SysConfigMapper configMapper;\n\n    @Autowired\n    private RedisCache redisCache;\n\n    /**\n     * 项目启动时，初始化参数到缓存\n     */\n    @PostConstruct\n    public void init() {\n        loadingConfigCache();\n    }\n\n    @Override\n    public PageResult<SysConfigEntity> page(SysConfigEntity sysConfigEntity) {\n        return configMapper.selectPage(sysConfigEntity);\n    }\n\n    @Override\n    public SysConfigEntity selectConfigById(Long configId) {\n        SysConfigEntity config = new SysConfigEntity();\n        config.setConfigId(configId);\n        return configMapper.selectById(config);\n    }\n\n    /**\n     * 根据键名查询参数配置信息\n     *\n     * @param configKey 参数key\n     * @return 参数键值\n     */\n    @Override\n    public String selectConfigByKey(String configKey) {\n        String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));\n        if (StringUtils.isNotEmpty(configValue)) {\n            return configValue;\n        }\n        SysConfigEntity config = new SysConfigEntity();\n        config.setConfigKey(configKey);\n        SysConfigEntity retConfig = configMapper.selectConfig(config);\n        if (StringUtils.isNotNull(retConfig)) {\n            redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());\n            return retConfig.getConfigValue();\n        }\n        return StringUtils.EMPTY;\n    }\n\n    @Override\n    public <T> T selectConfigByKey(String configKey, Class<T> clazz) {\n        T configValue = redisCache.getCacheObject(getCacheKey(configKey));\n        if (ObjectUtil.isNotEmpty(configValue)) {\n            return configValue;\n        }\n        SysConfigEntity config = new SysConfigEntity();\n        config.setConfigKey(configKey);\n        SysConfigEntity retConfig = configMapper.selectConfig(config);\n        if (ObjectUtil.isNotNull(retConfig)) {\n            redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());\n            return Convert.convert(clazz, retConfig.getConfigValue());\n        }\n        return null;\n    }\n\n    @Override\n    public <T> T selectConfigByKey(String configKey, Class<T> clazz, T defaultValue) {\n        T value = this.selectConfigByKey(configKey, clazz);\n        return value == null ? defaultValue : value;\n    }\n\n    /**\n     * 获取验证码开关\n     *\n     * @return true开启，false关闭\n     */\n    @Override\n    public boolean selectCaptchaEnabled() {\n        String captchaEnabled = selectConfigByKey(\"sys.account.captchaEnabled\");\n        if (StringUtils.isEmpty(captchaEnabled)) {\n            return true;\n        }\n        return Convert.toBool(captchaEnabled);\n    }\n\n    @Override\n    public int insertConfig(SysConfigEntity config) {\n        int row = configMapper.insert(config);\n        if (row > 0) {\n            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());\n        }\n        return row;\n    }\n\n    @Override\n    public int updateConfig(SysConfigEntity config) {\n        int row = configMapper.updateById(config);\n        if (row > 0) {\n            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());\n        }\n        return row;\n    }\n\n    @Override\n    public void deleteConfigByIds(Long[] configIds) {\n        for (Long configId : configIds) {\n            SysConfigEntity config = selectConfigById(configId);\n            if (StringUtils.equals(UserConstants.YES, config.getConfigType())) {\n                throw new ServiceException(String.format(\"内置参数【%1$s】不能删除 \", config.getConfigKey()));\n            }\n            configMapper.deleteById(configId);\n            redisCache.deleteObject(getCacheKey(config.getConfigKey()));\n        }\n    }\n\n    /**\n     * 加载参数缓存数据\n     */\n    @Override\n    public void loadingConfigCache() {\n        List<SysConfigEntity> configsList = configMapper.selectList();\n        for (SysConfigEntity config : configsList) {\n            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());\n        }\n    }\n\n\n    @Override\n    public boolean checkConfigKeyUnique(SysConfigEntity config) {\n        Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId();\n        SysConfigEntity info = configMapper.checkConfigKeyUnique(config);\n        if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public void clearConfigCache() {\n        Collection<String> keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + \"*\");\n        redisCache.deleteObject(keys);\n    }\n\n    @Override\n    public void resetConfigCache() {\n        clearConfigCache();\n        loadingConfigCache();\n    }\n\n    /**\n     * 设置cache key\n     *\n     * @param configKey 参数键\n     * @return 缓存键key\n     */\n    private String getCacheKey(String configKey) {\n        return CacheConstants.SYS_CONFIG_KEY + configKey;\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysDictDataServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysDictDataEntity;\nimport com.oddfar.campus.common.utils.DictUtils;\nimport com.oddfar.campus.framework.mapper.SysDictDataMapper;\nimport com.oddfar.campus.framework.service.SysDictDataService;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\nimport java.util.List;\n\n@Service\npublic class SysDictDataServiceImpl implements SysDictDataService {\n\n    @Resource\n    private SysDictDataMapper dictDataMapper;\n\n\n    @Override\n    public PageResult<SysDictDataEntity> page(SysDictDataEntity dictDataEntity) {\n        return dictDataMapper.selectPage(dictDataEntity);\n    }\n\n    @Override\n    public int insertDictData(SysDictDataEntity dictData) {\n        int row = dictDataMapper.insert(dictData);\n        if (row > 0) {\n            List<SysDictDataEntity> dictDatas = dictDataMapper.selectDictDataByType(dictData.getDictType());\n            DictUtils.setDictCache(dictData.getDictType(), dictDatas);\n        }\n        return row;\n    }\n\n    @Override\n    public SysDictDataEntity selectDictDataById(Long dictCode) {\n        return dictDataMapper.selectById(dictCode);\n    }\n\n\n    @Override\n    public int updateDictData(SysDictDataEntity data) {\n        int row = dictDataMapper.updateById(data);\n        if (row > 0) {\n            List<SysDictDataEntity> dictDatas = dictDataMapper.selectDictDataByType(data.getDictType());\n            DictUtils.setDictCache(data.getDictType(), dictDatas);\n        }\n        return row;\n    }\n\n    @Override\n    public void deleteDictDataByIds(Long[] dictCodes) {\n        for (Long dictCode : dictCodes) {\n            SysDictDataEntity data = dictDataMapper.selectById(dictCode);\n            dictDataMapper.deleteById(dictCode);\n            List<SysDictDataEntity> dictDatas = dictDataMapper.selectDictDataByType(data.getDictType());\n            DictUtils.setDictCache(data.getDictType(), dictDatas);\n        }\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysDictTypeServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysDictDataEntity;\nimport com.oddfar.campus.common.domain.entity.SysDictTypeEntity;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.DictUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.mapper.SysDictDataMapper;\nimport com.oddfar.campus.framework.mapper.SysDictTypeMapper;\nimport com.oddfar.campus.framework.service.SysDictTypeService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport javax.annotation.Resource;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport static com.oddfar.campus.common.utils.DictUtils.clearDictCache;\n\n@Service\npublic class SysDictTypeServiceImpl implements SysDictTypeService {\n    @Resource\n    private SysDictTypeMapper dictTypeMapper;\n    @Resource\n    private SysDictDataMapper dictDataMapper;\n\n    @Override\n    public PageResult<SysDictTypeEntity> page(SysDictTypeEntity sysDictTypeEntity) {\n        return dictTypeMapper.selectPage(sysDictTypeEntity);\n    }\n\n    /**\n     * 根据字典类型查询字典数据\n     *\n     * @param dictType 字典类型\n     * @return 字典数据集合信息\n     */\n    @Override\n    public List<SysDictDataEntity> selectDictDataByType(String dictType) {\n        List<SysDictDataEntity> dictDatas = DictUtils.getDictCache(dictType);\n        if (StringUtils.isNotEmpty(dictDatas)) {\n            return dictDatas;\n        }\n\n        dictDatas = dictDataMapper.selectDictDataByType(dictType);\n\n        if (StringUtils.isNotEmpty(dictDatas)) {\n            DictUtils.setDictCache(dictType, dictDatas);\n            return dictDatas;\n        }\n        return null;\n\n\n    }\n\n    @Override\n    public SysDictTypeEntity selectDictTypeById(Long dictId) {\n        return dictTypeMapper.selectById(dictId);\n    }\n\n    @Override\n    public List<SysDictTypeEntity> selectDictTypeAll() {\n        return dictTypeMapper.selectList();\n    }\n\n    @Override\n    @Transactional\n    public int updateDictType(SysDictTypeEntity dictType) {\n        SysDictTypeEntity oldDict = dictTypeMapper.selectById(dictType.getDictId());\n        dictDataMapper.updateDictDataType(oldDict.getDictType(), dictType.getDictType());\n        //把要更新的dictType的内容赋值到oldDict\n//        BeanUtil.copyProperties(dictType, oldDict);\n        int row = dictTypeMapper.updateById(dictType);\n\n        if (row > 0) {\n            List<SysDictDataEntity> dictDatas = dictDataMapper.selectDictDataByType(dictType.getDictType());\n            DictUtils.setDictCache(dictType.getDictType(), dictDatas);\n        }\n        return row;\n    }\n\n    @Override\n    public int insertDictType(SysDictTypeEntity dictType) {\n        int row = dictTypeMapper.insert(dictType);\n        if (row > 0) {\n            DictUtils.setDictCache(dictType.getDictType(), null);\n        }\n        return row;\n    }\n\n    @Override\n    public void deleteDictTypeByIds(Long[] dictIds) {\n        for (Long dictId : dictIds) {\n            SysDictTypeEntity dictType = selectDictTypeById(dictId);\n            if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0) {\n                throw new ServiceException(String.format(\"%1$s已分配,不能删除\", dictType.getDictName()));\n            }\n//            this.removeById(dictId);\n            dictTypeMapper.deleteById(dictId);\n            DictUtils.removeDictCache(dictType.getDictType());\n        }\n    }\n\n    @Override\n    public void resetDictCache() {\n        clearDictCache();\n        loadingDictCache();\n    }\n\n    /**\n     * 加载字典缓存数据\n     */\n    @Override\n    public void loadingDictCache() {\n        Map<String, List<SysDictDataEntity>> dictDataMap = dictDataMapper.selectList(\"status\", \"0\").stream().collect(Collectors.groupingBy(SysDictDataEntity::getDictType));\n        for (Map.Entry<String, List<SysDictDataEntity>> entry : dictDataMap.entrySet()) {\n            DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictDataEntity::getDictSort)).collect(Collectors.toList()));\n        }\n    }\n\n    @Override\n    public boolean checkDictTypeUnique(SysDictTypeEntity dictType) {\n        Long dictId = StringUtils.isNull(dictType.getDictId()) ? -1L : dictType.getDictId();\n        SysDictTypeEntity info = dictTypeMapper.selectOne(new LambdaQueryWrapperX<SysDictTypeEntity>()\n                .eq(SysDictTypeEntity::getDictType, dictType.getDictType()));\n        if (StringUtils.isNotNull(info) && info.getDictId().longValue() != dictId.longValue()) {\n            return false;\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysLoginLogServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysLoginLogEntity;\nimport com.oddfar.campus.framework.mapper.SysLoginLogMapper;\nimport com.oddfar.campus.framework.service.SysLoginLogService;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * 系统访问日志情况信息 服务层处理\n *\n * @author ruoyi\n */\n@Service\npublic class SysLoginLogServiceImpl implements SysLoginLogService {\n\n    @Resource\n    private SysLoginLogMapper loginLogMapper;\n\n    /**\n     * 查询系统登录日志分页数据\n     *\n     * @param logininfor 访问日志对象\n     * @return 登录记录分页数据\n     */\n    @Override\n    public PageResult<SysLoginLogEntity> selectLogininforPage(SysLoginLogEntity logininfor) {\n        return loginLogMapper.selectLogininforPage(logininfor);\n    }\n\n    /**\n     * 新增系统登录日志\n     *\n     * @param logininfor 访问日志对象\n     */\n    @Override\n    public void insertLogininfor(SysLoginLogEntity logininfor) {\n        loginLogMapper.insert(logininfor);\n    }\n\n    /**\n     * 查询系统登录日志集合\n     *\n     * @param logininfor 访问日志对象\n     * @return 登录记录集合\n     */\n    @Override\n    public List<SysLoginLogEntity> selectLogininforList(SysLoginLogEntity logininfor) {\n        return loginLogMapper.selectLogininforList(logininfor);\n    }\n\n    /**\n     * 批量删除系统登录日志\n     *\n     * @param infoIds 需要删除的登录日志ID\n     * @return 结果\n     */\n    @Override\n    public int deleteLogininforByIds(Long[] infoIds) {\n        return loginLogMapper.deleteBatchIds(Arrays.asList(infoIds));\n    }\n\n    /**\n     * 清空系统登录日志\n     */\n    @Override\n    public void cleanLogininfor() {\n        loginLogMapper.cleanLogininfor();\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysMenuServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.constant.UserConstants;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.TreeSelect;\nimport com.oddfar.campus.common.domain.entity.SysMenuEntity;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport com.oddfar.campus.common.domain.entity.SysRoleMenuEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.SysRoleAuth;\nimport com.oddfar.campus.common.domain.vo.RouterVo;\nimport com.oddfar.campus.common.utils.MetaVo;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.mapper.SysMenuMapper;\nimport com.oddfar.campus.framework.mapper.SysRoleMapper;\nimport com.oddfar.campus.framework.mapper.SysRoleMenuMapper;\nimport com.oddfar.campus.framework.service.SysMenuService;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * 菜单权限表(Menu)表服务实现类\n */\n@Service\npublic class SysMenuServiceImpl implements SysMenuService {\n\n    @Resource\n    private SysMenuMapper menuMapper;\n    @Resource\n    private SysRoleMapper roleMapper;\n    @Resource\n    private SysRoleMenuMapper roleMenuMapper;\n\n    /**\n     * 根据用户查询系统菜单列表\n     *\n     * @param userId 用户ID\n     * @return 菜单列表\n     */\n    @Override\n    public List<SysMenuEntity> selectMenuList(Long userId) {\n        return selectMenuList(new SysMenuEntity(), userId);\n    }\n\n    /**\n     * 查询系统菜单列表\n     *\n     * @param menu 菜单信息\n     * @return 菜单列表\n     */\n    @Override\n    public List<SysMenuEntity> selectMenuList(SysMenuEntity menu, Long userId) {\n        List<SysMenuEntity> menuList = null;\n        // 管理员显示所有菜单信息\n        if (SysUserEntity.isAdmin(userId)) {\n            menuList = menuMapper.selectMenuList(menu);\n        } else {\n            menu.getParams().put(\"userId\", userId);\n            menuList = menuMapper.selectMenuListByUserId(menu);\n        }\n        return menuList;\n    }\n\n    /**\n     * 根据用户ID查询菜单\n     *\n     * @param userId 用户名称\n     * @return 菜单列表\n     */\n    @Override\n    public List<SysMenuEntity> selectMenuTreeByUserId(Long userId) {\n        List<SysMenuEntity> menus = null;\n        if (SecurityUtils.isAdmin(userId)) {\n            menus = menuMapper.selectMenuTreeAll();\n        } else {\n            menus = menuMapper.selectMenuTreeByUserId(userId);\n        }\n        return getChildPerms(menus, 0);\n    }\n\n    /**\n     * 根据角色ID查询菜单树信息\n     *\n     * @param roleId 角色ID\n     * @return 选中菜单列表\n     */\n    @Override\n    public List<Long> selectMenuListByRoleId(Long roleId) {\n        SysRoleEntity role = roleMapper.selectRoleById(roleId);\n        return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly());\n    }\n\n    @Override\n    public SysMenuEntity selectMenuById(Long menuId) {\n        return menuMapper.selectById(menuId);\n    }\n\n    /**\n     * 构建前端路由所需要的菜单\n     *\n     * @param menus 菜单列表\n     * @return 路由列表\n     */\n    @Override\n    public List<RouterVo> buildMenus(List<SysMenuEntity> menus) {\n        List<RouterVo> routers = new LinkedList<RouterVo>();\n        for (SysMenuEntity menu : menus) {\n            RouterVo router = new RouterVo();\n            router.setHidden(\"1\".equals(menu.getVisible()));\n            router.setName(getRouteName(menu));\n            router.setPath(getRouterPath(menu));\n            router.setComponent(getComponent(menu));\n            router.setQuery(menu.getQuery());\n            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals(\"1\", menu.getIsCache()), menu.getPath()));\n            List<SysMenuEntity> cMenus = menu.getChildren();\n            if (!cMenus.isEmpty() && cMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {\n                router.setAlwaysShow(true);\n                router.setRedirect(\"noRedirect\");\n                router.setChildren(buildMenus(cMenus));\n            } else if (isMenuFrame(menu)) {\n                router.setMeta(null);\n                List<RouterVo> childrenList = new ArrayList<RouterVo>();\n                RouterVo children = new RouterVo();\n                children.setPath(menu.getPath());\n                children.setComponent(menu.getComponent());\n                children.setName(StringUtils.capitalize(menu.getPath()));\n                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals(\"1\", menu.getIsCache()), menu.getPath()));\n                children.setQuery(menu.getQuery());\n                childrenList.add(children);\n                router.setChildren(childrenList);\n            } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) {\n                router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));\n                router.setPath(\"/\");\n                List<RouterVo> childrenList = new ArrayList<RouterVo>();\n                RouterVo children = new RouterVo();\n                String routerPath = innerLinkReplaceEach(menu.getPath());\n                children.setPath(routerPath);\n                children.setComponent(UserConstants.INNER_LINK);\n                children.setName(StringUtils.capitalize(routerPath));\n                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));\n                childrenList.add(children);\n                router.setChildren(childrenList);\n            }\n            routers.add(router);\n        }\n        return routers;\n    }\n\n    /**\n     * 构建前端所需要下拉树结构\n     *\n     * @param menus 菜单列表\n     * @return 下拉树结构列表\n     */\n    @Override\n    public List<TreeSelect> buildMenuTreeSelect(List<SysMenuEntity> menus) {\n        List<SysMenuEntity> menuTrees = buildMenuTree(menus);\n        return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList());\n    }\n\n    /**\n     * 构建前端所需要树结构\n     *\n     * @param menus 菜单列表\n     * @return 树结构列表\n     */\n    @Override\n    public List<SysMenuEntity> buildMenuTree(List<SysMenuEntity> menus) {\n        List<SysMenuEntity> returnList = new ArrayList<SysMenuEntity>();\n        List<Long> tempList = new ArrayList<Long>();\n        for (SysMenuEntity dept : menus) {\n            tempList.add(dept.getMenuId());\n        }\n        for (Iterator<SysMenuEntity> iterator = menus.iterator(); iterator.hasNext(); ) {\n            SysMenuEntity menu = (SysMenuEntity) iterator.next();\n            // 如果是顶级节点, 遍历该父节点的所有子节点\n            if (!tempList.contains(menu.getParentId())) {\n                recursionFn(menus, menu);\n                returnList.add(menu);\n            }\n        }\n        if (returnList.isEmpty()) {\n            returnList = menus;\n        }\n        return returnList;\n    }\n\n    /**\n     * 根据角色ID查询权限\n     *\n     * @param roleId 角色ID\n     * @return 权限列表\n     */\n    @Override\n    public Set<String> selectMenuPermsByRoleId(Long roleId) {\n        List<String> perms = menuMapper.selectMenuPermsByRoleId(roleId);\n        Set<String> permsSet = new HashSet<>();\n        for (String perm : perms) {\n            if (StringUtils.isNotEmpty(perm)) {\n                permsSet.addAll(Arrays.asList(perm.trim().split(\",\")));\n            }\n        }\n        return permsSet;\n    }\n\n    @Override\n    public Map<Long, List<SysRoleAuth>> selectMenuPermsAll() {\n        List<SysRoleAuth> sysRolePerms = menuMapper.getMenuPermsAll();\n        //根据roleId分组\n        return sysRolePerms.stream().collect(Collectors.groupingBy(SysRoleAuth::getRoleID));\n    }\n\n    /**\n     * 根据用户ID查询权限\n     *\n     * @param userId 用户ID\n     * @return 权限列表\n     */\n    @Override\n    public Set<String> selectMenuPermsByUserId(Long userId) {\n        List<String> perms = menuMapper.selectMenuPermsByUserId(userId);\n        Set<String> permsSet = new HashSet<>();\n        for (String perm : perms) {\n            if (StringUtils.isNotEmpty(perm)) {\n                permsSet.addAll(Arrays.asList(perm.trim().split(\",\")));\n            }\n        }\n        return permsSet;\n    }\n\n    @Override\n    public int insertMenu(SysMenuEntity menu) {\n        return menuMapper.insert(menu);\n    }\n\n    @Override\n    public int updateMenu(SysMenuEntity menu) {\n        return menuMapper.updateById(menu);\n    }\n\n    @Override\n    public int deleteMenuById(Long menuId) {\n        return menuMapper.deleteById(menuId);\n    }\n\n    @Override\n    public boolean checkMenuExistRole(Long menuId) {\n        Long result = roleMenuMapper.selectCount(new QueryWrapper<SysRoleMenuEntity>().eq(\"menu_id\", menuId)   );\n        return result > 0;\n    }\n\n    @Override\n    public boolean hasChildByMenuId(Long menuId) {\n        Long result = menuMapper.selectCount(\n                new LambdaQueryWrapperX<SysMenuEntity>().eq(SysMenuEntity::getParentId, menuId));\n\n        return result > 0;\n    }\n\n    @Override\n    public boolean checkMenuNameUnique(SysMenuEntity menu) {\n        Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();\n        SysMenuEntity info = menuMapper.checkMenuNameUnique(menu);\n        if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * 获取路由地址\n     *\n     * @param menu 菜单信息\n     * @return 路由地址\n     */\n    public String getRouterPath(SysMenuEntity menu) {\n        String routerPath = menu.getPath();\n        // 内链打开外网方式\n        if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) {\n            routerPath = innerLinkReplaceEach(routerPath);\n        }\n        // 非外链并且是一级目录（类型为目录）\n        if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())\n                && UserConstants.NO_FRAME.equals(menu.getIsFrame())) {\n            routerPath = \"/\" + menu.getPath();\n        }\n        // 非外链并且是一级目录（类型为菜单）\n        else if (isMenuFrame(menu)) {\n            routerPath = \"/\";\n        }\n        return routerPath;\n    }\n\n    /**\n     * 是否为内链组件\n     *\n     * @param menu 菜单信息\n     * @return 结果\n     */\n    public boolean isInnerLink(SysMenuEntity menu) {\n        return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath());\n    }\n\n    /**\n     * 获取组件信息\n     *\n     * @param menu 菜单信息\n     * @return 组件信息\n     */\n    public String getComponent(SysMenuEntity menu) {\n        String component = UserConstants.LAYOUT;\n        if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) {\n            component = menu.getComponent();\n        } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) {\n            component = UserConstants.INNER_LINK;\n        } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) {\n            component = UserConstants.PARENT_VIEW;\n        }\n        return component;\n    }\n\n    /**\n     * 是否为parent_view组件\n     *\n     * @param menu 菜单信息\n     * @return 结果\n     */\n    public boolean isParentView(SysMenuEntity menu) {\n        return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType());\n    }\n\n    /**\n     * 获取路由名称\n     *\n     * @param menu 菜单信息\n     * @return 路由名称\n     */\n    public String getRouteName(SysMenuEntity menu) {\n        String routerName = StringUtils.capitalize(menu.getPath());\n        // 非外链并且是一级目录（类型为目录）\n        if (isMenuFrame(menu)) {\n            routerName = StringUtils.EMPTY;\n        }\n        return routerName;\n    }\n\n    /**\n     * 是否为菜单内部跳转\n     *\n     * @param menu 菜单信息\n     * @return 结果\n     */\n    public boolean isMenuFrame(SysMenuEntity menu) {\n        return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType())\n                && menu.getIsFrame().equals(UserConstants.NO_FRAME);\n    }\n\n    /**\n     * 根据父节点的ID获取所有子节点\n     *\n     * @param list     分类表\n     * @param parentId 传入的父节点ID\n     * @return String\n     */\n    public List<SysMenuEntity> getChildPerms(List<SysMenuEntity> list, int parentId) {\n        List<SysMenuEntity> returnList = new ArrayList<SysMenuEntity>();\n        for (Iterator<SysMenuEntity> iterator = list.iterator(); iterator.hasNext(); ) {\n            SysMenuEntity t = (SysMenuEntity) iterator.next();\n            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点\n            if (t.getParentId() == parentId) {\n                recursionFn(list, t);\n                returnList.add(t);\n            }\n        }\n        return returnList;\n    }\n\n    /**\n     * 递归列表\n     *\n     * @param list\n     * @param t\n     */\n    private void recursionFn(List<SysMenuEntity> list, SysMenuEntity t) {\n        // 得到子节点列表\n        List<SysMenuEntity> childList = getChildList(list, t);\n        t.setChildren(childList);\n        for (SysMenuEntity tChild : childList) {\n            if (hasChild(list, tChild)) {\n                recursionFn(list, tChild);\n            }\n        }\n    }\n\n    /**\n     * 判断是否有子节点\n     */\n    private boolean hasChild(List<SysMenuEntity> list, SysMenuEntity t) {\n        return getChildList(list, t).size() > 0;\n    }\n\n    /**\n     * 得到子节点列表\n     */\n    private List<SysMenuEntity> getChildList(List<SysMenuEntity> list, SysMenuEntity t) {\n        List<SysMenuEntity> tlist = new ArrayList<SysMenuEntity>();\n        Iterator<SysMenuEntity> it = list.iterator();\n        while (it.hasNext()) {\n            SysMenuEntity n = (SysMenuEntity) it.next();\n            if (n.getParentId().longValue() == t.getMenuId().longValue()) {\n                tlist.add(n);\n            }\n        }\n        return tlist;\n    }\n\n    /**\n     * 内链域名特殊字符替换\n     *\n     * @return\n     */\n    public String innerLinkReplaceEach(String path) {\n        return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS, Constants.WWW, \".\"},\n                new String[]{\"\", \"\", \"\", \"/\"});\n    }\n}\n\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysOperLogServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysOperLogEntity;\nimport com.oddfar.campus.framework.mapper.SysOperLogMapper;\nimport com.oddfar.campus.framework.service.SysOperLogService;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\nimport java.util.List;\n\n/**\n * 操作日志 服务层处理\n */\n@Service\npublic class SysOperLogServiceImpl implements SysOperLogService {\n    @Resource\n    private SysOperLogMapper operLogMapper;\n\n    /**\n     * 新增操作日志\n     *\n     * @param operLog 操作日志对象\n     */\n    @Override\n    public void insertOperlog(SysOperLogEntity operLog) {\n        operLogMapper.insert(operLog);\n    }\n\n    /**\n     * 查询系统操作日志集合\n     *\n     * @param operLog 操作日志对象\n     * @return 操作日志集合\n     */\n    @Override\n    public PageResult<SysOperLogEntity> selectOperLogPage(SysOperLogEntity operLog) {\n        return operLogMapper.selectOperLogPage(operLog);\n    }\n\n    /**\n     * 批量删除系统操作日志\n     *\n     * @param operIds 需要删除的操作日志ID\n     * @return 结果\n     */\n    @Override\n    public int deleteOperLogByIds(Long[] operIds) {\n        return operLogMapper.deleteOperLogByIds(operIds);\n    }\n\n    /**\n     * 查询操作日志详细\n     *\n     * @param operId 操作ID\n     * @return 操作日志对象\n     */\n    @Override\n    public SysOperLogEntity selectOperLogById(Long operId) {\n        return operLogMapper.selectById(operId);\n    }\n\n    /**\n     * 清空操作日志\n     */\n    @Override\n    public void cleanOperLog() {\n        operLogMapper.cleanOperLog();\n    }\n\n    @Override\n    public List<SysOperLogEntity> selectOperLogList(SysOperLogEntity operLog) {\n        return operLogMapper.selectOperLogList(operLog);\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysResourceServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.TreeSelect;\nimport com.oddfar.campus.common.domain.entity.SysResourceEntity;\nimport com.oddfar.campus.common.domain.entity.SysRoleResourceEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.SysRoleAuth;\nimport com.oddfar.campus.framework.mapper.SysResourceMapper;\nimport com.oddfar.campus.framework.mapper.SysRoleResourceMapper;\nimport com.oddfar.campus.framework.service.SysResourceService;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n@Service\npublic class SysResourceServiceImpl extends ServiceImpl<SysResourceMapper, SysResourceEntity> implements SysResourceService {\n\n    @Resource\n    private SysResourceMapper resourceMapper;\n    @Resource\n    private SysRoleResourceMapper roleResourceMapper;\n\n    @Override\n    public PageResult<SysResourceEntity> page(SysResourceEntity sysResourceEntity) {\n        return resourceMapper.selectPage(sysResourceEntity);\n    }\n\n    /**\n     * 新增接口资源信息\n     *\n     * @param resource\n     * @return\n     */\n    @Override\n    public int insertResource(SysResourceEntity resource) {\n\n        int row = resourceMapper.insert(resource);\n        return row;\n    }\n\n    /**\n     * 清空 sys_resource 数据库\n     */\n    @Override\n    public void truncateResource() {\n        resourceMapper.truncateResource();\n    }\n\n    /**\n     * 根据角色ID查询资源编码列表\n     *\n     * @param roleId 角色ID\n     * @return 权限列表\n     */\n    @Override\n    public Set<String> selectResourceCodeByRoleId(Long roleId) {\n        return resourceMapper.selectResourceCodeByRoleId(roleId);\n    }\n\n    @Override\n    public List<SysResourceEntity> selectApiResourceList(Long userId) {\n        SysResourceEntity resourceEntity = new SysResourceEntity();\n        resourceEntity.setRequiredPermissionFlag(Constants.YES);\n        return selectApiResourceList(resourceEntity, userId);\n    }\n\n    @Override\n    public List<SysRoleAuth> selectSysRoleAuthAll() {\n        return roleResourceMapper.selectList().stream()\n                .map(SysRoleAuth::new).collect(Collectors.toList());\n    }\n\n    @Override\n    public List<SysResourceEntity> selectApiResourceList(SysResourceEntity resource, Long userId) {\n        List<SysResourceEntity> resourceList = null;\n        // 管理员显示所有资源信息\n        if (SysUserEntity.isAdmin(userId)) {\n            resourceList = resourceMapper.selectResourceList(resource);\n        } else {\n            resource.getParams().put(\"userId\", userId);\n            resourceList = resourceMapper.selectResourceListByUserId(resource);\n        }\n        return resourceList;\n    }\n\n    @Override\n    public List<Long> selectResourceListByRoleId(Long roleId) {\n        return resourceMapper.selectResourceListByRoleId(roleId);\n    }\n\n    @Override\n    public List<TreeSelect> buildResourceTreeSelect(List<SysResourceEntity> resources) {\n\n        List<TreeSelect> treeSelects = new ArrayList<>();\n\n        Map<String, List<SysResourceEntity>> map = resources.stream().collect(\n                Collectors.groupingBy(SysResourceEntity::getClassName));\n        long size = 0L;\n        for (String key : map.keySet()) {\n            String modularName = map.get(key).get(0).getModularName();\n            TreeSelect treeSelect = new TreeSelect(++size, modularName, map.get(key));\n            treeSelects.add(treeSelect);\n        }\n        return treeSelects;\n    }\n\n    @Override\n    public void editRoleResource(Long roleId, Long[] resourceIds) {\n        // 删除角色与api资源关联\n        roleResourceMapper.deleteRoleResourceByRoleId(roleId);\n        //添加角色与api资源管理\n        if (resourceIds.length > 0) {\n            List<SysResourceEntity> resourceEntities = resourceMapper.selectBatchIds(Arrays.asList(resourceIds));\n            insertRoleMenu(roleId, resourceEntities);\n        }\n    }\n\n    public int insertRoleMenu(Long roleId, List<SysResourceEntity> resourceEntities) {\n\n        List<SysRoleResourceEntity> rrList = new ArrayList<>();\n\n        for (SysResourceEntity resourceEntity : resourceEntities) {\n            SysRoleResourceEntity rr = new SysRoleResourceEntity();\n            rr.setRoleId(roleId);\n            rr.setResourceCode(resourceEntity.getResourceCode());\n            rrList.add(rr);\n        }\n\n        return roleResourceMapper.saveBatch(rrList);\n\n    }\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysRoleServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport com.oddfar.campus.common.domain.entity.SysRoleMenuEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserRoleEntity;\nimport com.oddfar.campus.common.domain.model.SysRoleAuth;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.api.resource.ResourceCollectorApi;\nimport com.oddfar.campus.framework.mapper.SysRoleMapper;\nimport com.oddfar.campus.framework.mapper.SysRoleMenuMapper;\nimport com.oddfar.campus.framework.mapper.SysUserRoleMapper;\nimport com.oddfar.campus.framework.service.SysMenuService;\nimport com.oddfar.campus.framework.service.SysResourceService;\nimport com.oddfar.campus.framework.service.SysRoleService;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport javax.annotation.Resource;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n@Service\npublic class SysRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUserRoleEntity> implements SysRoleService {\n\n    @Resource\n    private SysRoleMapper roleMapper;\n    @Resource\n    private SysUserRoleMapper userRoleMapper;\n    @Resource\n    private SysRoleMenuMapper roleMenuMapper;\n    @Resource\n    private SysMenuService menuService;\n    @Resource\n    private SysResourceService resourceService;\n\n    @Resource\n    ApplicationContext applicationContext;\n\n    @Override\n    public PageResult<SysRoleEntity> page(SysRoleEntity sysRoleEntity) {\n        return roleMapper.selectPage(sysRoleEntity);\n    }\n\n    @Override\n    public List<SysRoleEntity> selectRoleList(SysRoleEntity role) {\n        return roleMapper.selectRoleList(role);\n    }\n\n    /**\n     * 根据用户ID查询权限\n     *\n     * @param userId 用户ID\n     * @return 权限列表\n     */\n    @Override\n    public Set<String> selectRolePermissionByUserId(Long userId) {\n        List<SysRoleEntity> perms = roleMapper.selectRolePermissionByUserId(userId);\n        Set<String> permsSet = new HashSet<>();\n        for (SysRoleEntity perm : perms) {\n            if (StringUtils.isNotNull(perm)) {\n                permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(\",\")));\n            }\n        }\n        return permsSet;\n    }\n\n\n    /**\n     * 通过角色ID查询角色\n     *\n     * @param roleId 角色ID\n     * @return 角色对象信息\n     */\n    @Override\n    public SysRoleEntity selectRoleById(Long roleId) {\n        return roleMapper.selectRoleById(roleId);\n    }\n\n    @Override\n    public List<SysRoleEntity> selectRoleAll() {\n        return this.selectRoleList(new SysRoleEntity());\n//        return SpringUtils.getAopProxy(this).selectRoleList(new SysRoleEntity());\n    }\n\n    @Override\n    public List<SysRoleEntity> selectRolesByUserId(Long userId) {\n        List<SysRoleEntity> userRoles = roleMapper.selectRolePermissionByUserId(userId);\n        List<SysRoleEntity> roles = selectRoleAll();\n        for (SysRoleEntity role : roles) {\n            for (SysRoleEntity userRole : userRoles) {\n                if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) {\n                    role.setFlag(true);\n                    break;\n                }\n            }\n        }\n        return roles;\n    }\n\n\n    @Override\n    @Transactional\n    public int insertRole(SysRoleEntity role) {\n        // 新增角色信息\n        roleMapper.insert(role);\n        return insertRoleMenu(role);\n    }\n\n    @Override\n    public int updateRole(SysRoleEntity role) {\n        // 修改角色信息\n        roleMapper.updateById(role);\n        // 删除角色与菜单关联\n        roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId());\n\n        return insertRoleMenu(role);\n    }\n\n    @Override\n    public int updateRoleStatus(SysRoleEntity role) {\n        return roleMapper.updateById(role);\n    }\n\n    @Override\n    @Transactional\n    public int deleteRoleByIds(Long[] roleIds) {\n        for (Long roleId : roleIds) {\n            checkRoleAllowed(new SysRoleEntity(roleId));\n            SysRoleEntity role = selectRoleById(roleId);\n            if (countUserRoleByRoleId(roleId) > 0) {\n                throw new ServiceException(String.format(\"%1$s已分配,不能删除\", role.getRoleName()));\n            }\n        }\n        // 删除角色与菜单关联\n        roleMenuMapper.deleteRoleMenu(roleIds);\n        // 删除角色\n        return roleMapper.deleteBatchIds(Arrays.asList(roleIds));\n    }\n\n    @Override\n    public int deleteAuthUser(SysUserRoleEntity userRole) {\n        return userRoleMapper.deleteUserRoleInfo(userRole);\n    }\n\n    @Override\n    public int deleteAuthUsers(Long roleId, Long[] userIds) {\n        return userRoleMapper.deleteUserRoleInfos(roleId, userIds);\n    }\n\n    @Override\n    public boolean insertAuthUsers(Long roleId, Long[] userIds) {\n        // 新增用户与角色管理\n        List<SysUserRoleEntity> list = new ArrayList<SysUserRoleEntity>();\n        for (Long userId : userIds) {\n            SysUserRoleEntity ur = new SysUserRoleEntity();\n            ur.setUserId(userId);\n            ur.setRoleId(roleId);\n            list.add(ur);\n        }\n\n        return this.saveBatch(list);\n    }\n\n    @Override\n    public int countUserRoleByRoleId(Long roleId) {\n        return userRoleMapper.countUserRoleByRoleId(roleId);\n    }\n\n    @Override\n    public boolean checkRoleNameUnique(SysRoleEntity role) {\n        Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId();\n        SysRoleEntity info = roleMapper.checkRoleNameUnique(role.getRoleName());\n        if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public boolean checkRoleKeyUnique(SysRoleEntity role) {\n        Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId();\n        SysRoleEntity info = roleMapper.checkRoleKeyUnique(role.getRoleKey());\n        if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public void checkRoleAllowed(SysRoleEntity role) {\n        if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) {\n            throw new ServiceException(\"不允许操作超级管理员角色\");\n        }\n    }\n\n    @Override\n    public void resetRoleAuthCache() {\n        //把用户资源和权限缓存\n        Map<Long, List<SysRoleAuth>> rolePermsMap = menuService.selectMenuPermsAll();\n        Map<Long, List<SysRoleAuth>> roleResourceMap = resourceService.selectSysRoleAuthAll().stream().collect(Collectors.groupingBy(SysRoleAuth::getRoleID));\n\n        ResourceCollectorApi resourceCollectorApi = applicationContext.getBean(ResourceCollectorApi.class);\n        resourceCollectorApi.setRoleAuthCache(rolePermsMap, roleResourceMap);\n    }\n\n    /**\n     * 新增角色菜单信息\n     *\n     * @param role 角色对象\n     */\n    public int insertRoleMenu(SysRoleEntity role) {\n        int rows = 1;\n        // 新增用户与角色管理\n        List<SysRoleMenuEntity> list = new ArrayList<SysRoleMenuEntity>();\n        for (Long menuId : role.getMenuIds()) {\n            SysRoleMenuEntity rm = new SysRoleMenuEntity();\n            rm.setRoleId(role.getRoleId());\n            rm.setMenuId(menuId);\n            list.add(rm);\n        }\n        if (list.size() > 0) {\n            rows = roleMenuMapper.batchRoleMenu(list);\n        }\n        return rows;\n    }\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysUserServiceImpl.java",
    "content": "package com.oddfar.campus.framework.service.impl;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.oddfar.campus.common.constant.UserConstants;\nimport com.oddfar.campus.common.core.page.PageQuery;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserRoleEntity;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.api.sysconfig.ConfigExpander;\nimport com.oddfar.campus.framework.mapper.SysRoleMapper;\nimport com.oddfar.campus.framework.mapper.SysUserMapper;\nimport com.oddfar.campus.framework.mapper.SysUserRoleMapper;\nimport com.oddfar.campus.framework.service.SysRoleService;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.CollectionUtils;\n\nimport javax.annotation.Resource;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n@Service\npublic class SysUserServiceImpl implements SysUserService {\n\n    @Resource\n    private SysUserMapper userMapper;\n    @Resource\n    private SysUserRoleMapper userRoleMapper;\n    @Resource\n    private SysRoleMapper roleMapper;\n    @Resource\n    private SysRoleService roleService;\n\n    @Override\n    public PageResult<SysUserEntity> page(SysUserEntity sysUserEntity) {\n        return userMapper.selectPage(sysUserEntity);\n    }\n\n    @Override\n    public SysUserEntity selectUserByUserName(String userName) {\n        SysUserEntity userEntity = userMapper.selectUserByUserName(userName);\n        if (userEntity != null && StringUtils.isEmpty(userEntity.getAvatar())) {\n            userEntity.setAvatar(ConfigExpander.getUserDefaultAvatar());\n        }\n        return userEntity;\n    }\n\n    @Override\n    public SysUserEntity selectUserById(Long userId) {\n        SysUserEntity userEntity = userMapper.selectUserById(userId);\n        if (userEntity != null && StringUtils.isEmpty(userEntity.getAvatar())) {\n            userEntity.setAvatar(ConfigExpander.getUserDefaultAvatar());\n        }\n        return userEntity;\n    }\n\n    @Override\n    public Page<SysUserEntity> selectAllocatedList(SysUserEntity user) {\n        Page<SysUserEntity> page = new PageQuery().buildPage();\n\n        QueryWrapper<SysUserEntity> wrapper = Wrappers.query();\n        wrapper.eq(\"u.del_flag\", UserConstants.NORMAL)\n                .eq(\"r.role_id\", user.getRoleId())\n                .eq(ObjectUtil.isNotNull(user.getPhonenumber()), \"u.phonenumber\", user.getPhonenumber())\n                .like(ObjectUtil.isNotNull(user.getUserName()), \"u.user_name\", user.getUserName());\n        Page<SysUserEntity> sysUserPage = userMapper.selectAllocatedList(page, wrapper);\n        return sysUserPage;\n    }\n\n    @Override\n    public Page<SysUserEntity> selectUnallocatedList(SysUserEntity user) {\n        Page<SysUserEntity> page = new PageQuery().buildPage();\n        return userMapper.selectUnallocatedList(page, user);\n    }\n\n    @Override\n    public String selectUserRoleGroup(String userName) {\n        List<SysRoleEntity> list = roleMapper.selectRolesByUserName(userName);\n        if (CollectionUtils.isEmpty(list)) {\n            return StringUtils.EMPTY;\n        }\n        return list.stream().map(SysRoleEntity::getRoleName).collect(Collectors.joining(\",\"));\n    }\n\n    @Override\n    public boolean registerUser(SysUserEntity user) {\n        return userMapper.insert(user) > 0;\n    }\n\n    @Override\n    @Transactional\n    public int insertUser(SysUserEntity user) {\n        if (StringUtils.isNotEmpty(user.getUserName())\n                && !checkUserNameUnique(user)) {\n            throw new ServiceException(\"新增用户'\" + user.getUserName() + \"'失败，登录账号已存在\");\n        }\n        if (StringUtils.isNotEmpty(user.getPhonenumber())\n                && !(checkPhoneUnique(user))) {\n            throw new ServiceException(\"新增用户'\" + user.getUserName() + \"'失败，手机号码已存在\");\n        }\n        if (StringUtils.isNotEmpty(user.getEmail())\n                && !(checkEmailUnique(user))) {\n            throw new ServiceException(\"新增用户'\" + user.getUserName() + \"'失败，邮箱账号已存在\");\n        }\n        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));\n        // 新增用户信息\n        int rows = userMapper.insert(user);\n        // 新增用户与角色管理\n        insertUserRole(user);\n        return rows;\n    }\n\n    @Override\n    @Transactional\n    public int updateUser(SysUserEntity user) {\n\n        Long userId = user.getUserId();\n        // 删除用户与角色关联\n        userRoleMapper.deleteUserRoleByUserId(userId);\n        // 新增用户与角色管理\n        insertUserRole(user);\n        return userMapper.updateById(user);\n    }\n\n    @Override\n    @Transactional\n    public int deleteUserByIds(Long[] userIds) {\n        for (Long userId : userIds) {\n            checkUserAllowed(new SysUserEntity(userId));\n        }\n        // 删除用户与角色关联\n        userRoleMapper.deleteUserRole(userIds);\n        return userMapper.deleteBatchIds(Arrays.asList(userIds));\n    }\n\n\n    @Override\n    public int updateUserProfile(SysUserEntity user) {\n        return userMapper.updateById(user);\n    }\n\n    @Override\n    public int updateUserStatus(SysUserEntity user) {\n        return userMapper.updateById(user);\n    }\n\n    @Override\n    public boolean updateUserAvatar(String userName, String avatar) {\n        return userMapper.updateUserAvatar(userName, avatar) > 0;\n    }\n\n    @Override\n    public int resetPwd(SysUserEntity user) {\n        return userMapper.updateById(user);\n    }\n\n    @Override\n    public void checkUserAllowed(SysUserEntity user) {\n        if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) {\n            throw new ServiceException(\"不允许操作超级管理员用户\");\n        }\n    }\n\n    @Override\n    @Transactional\n    public void insertUserAuth(Long userId, Long[] roleIds) {\n        userRoleMapper.deleteUserRoleByUserId(userId);\n        insertUserRole(userId, roleIds);\n    }\n\n    @Override\n    public void insertUserAuth(Long userId, Set<String> roleKey) {\n        //查询现有的权限字符\n        Set<String> roleSet = roleService.selectRolePermissionByUserId(userId);\n        roleKey.addAll(roleSet);\n\n        List<SysRoleEntity> sysRoleList = roleMapper.selectRoleListByKey(roleKey);\n        List<Long> roleIds = sysRoleList.stream().map(SysRoleEntity::getRoleId).collect(Collectors.toList());\n\n        userRoleMapper.deleteUserRoleByUserId(userId);\n        insertUserRole(userId, roleIds.toArray(new Long[0]));\n\n    }\n\n    @Override\n    public int resetUserPwd(String userName, String password) {\n        return userMapper.resetUserPwd(userName, password);\n    }\n\n    @Override\n    public boolean checkUserNameUnique(SysUserEntity user) {\n        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();\n        SysUserEntity info = userMapper.checkUserNameUnique(user.getUserName());\n        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public boolean checkPhoneUnique(SysUserEntity user) {\n        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();\n        SysUserEntity info = userMapper.checkPhoneUnique(user.getPhonenumber());\n        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public boolean checkEmailUnique(SysUserEntity user) {\n        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();\n        SysUserEntity info = userMapper.checkEmailUnique(user.getEmail());\n        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * 新增用户角色信息\n     *\n     * @param user 用户对象\n     */\n    public void insertUserRole(SysUserEntity user) {\n        this.insertUserRole(user.getUserId(), user.getRoleIds());\n    }\n\n\n    /**\n     * 新增用户角色信息\n     *\n     * @param userId  用户ID\n     * @param roleIds 角色组\n     */\n    public void insertUserRole(Long userId, Long[] roleIds) {\n        if (StringUtils.isNotEmpty(roleIds)) {\n            // 新增用户与角色管理\n            List<SysUserRoleEntity> list = new ArrayList<SysUserRoleEntity>(roleIds.length);\n            for (Long roleId : roleIds) {\n                if (roleId != null) {\n                    SysUserRoleEntity ur = new SysUserRoleEntity();\n                    ur.setUserId(userId);\n                    ur.setRoleId(roleId);\n                    list.add(ur);\n                }\n            }\n            userRoleMapper.insertBatch(list);\n        }\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/web/exception/GlobalExceptionHandler.java",
    "content": "package com.oddfar.campus.framework.web.exception;\n\nimport com.oddfar.campus.common.constant.HttpStatus;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.security.access.AccessDeniedException;\nimport org.springframework.validation.BindException;\nimport org.springframework.web.HttpRequestMethodNotSupportedException;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.servlet.http.HttpServletRequest;\n\n/**\n * 全局异常处理器\n *\n * @author ruoyi\n */\n@RestControllerAdvice\npublic class GlobalExceptionHandler {\n    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);\n\n    /**\n     * 权限校验异常\n     */\n    @ExceptionHandler(AccessDeniedException.class)\n    public R handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {\n        String requestURI = request.getRequestURI();\n        log.error(\"请求地址'{}',权限校验失败'{}'\", requestURI, e.getMessage());\n        return R.error(HttpStatus.FORBIDDEN, \"没有权限，请联系管理员授权\");\n    }\n\n    /**\n     * 请求方式不支持\n     */\n    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)\n    public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,\n                                                          HttpServletRequest request) {\n        String requestURI = request.getRequestURI();\n        log.error(\"请求地址'{}',不支持'{}'请求\", requestURI, e.getMethod());\n        return R.error(e.getMessage());\n    }\n\n    /**\n     * 业务异常\n     */\n    @ExceptionHandler(ServiceException.class)\n    public R handleServiceException(ServiceException e, HttpServletRequest request) {\n        log.error(e.getMessage(), e);\n        Integer code = e.getCode();\n        return StringUtils.isNotNull(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage());\n    }\n\n    /**\n     * 拦截未知的运行时异常\n     */\n    @ExceptionHandler(RuntimeException.class)\n    public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {\n        String requestURI = request.getRequestURI();\n        log.error(\"请求地址'{}',发生未知异常.\", requestURI, e);\n        return R.error(e.getMessage());\n    }\n\n    /**\n     * 系统异常\n     */\n    @ExceptionHandler(Exception.class)\n    public R handleException(Exception e, HttpServletRequest request) {\n        String requestURI = request.getRequestURI();\n        log.error(\"请求地址'{}',发生系统异常.\", requestURI, e);\n        return R.error(e.getMessage());\n    }\n\n    /**\n     * 自定义验证异常\n     */\n    @ExceptionHandler(BindException.class)\n    public R handleBindException(BindException e) {\n        log.error(e.getMessage(), e);\n        String message = e.getAllErrors().get(0).getDefaultMessage();\n        return R.error(message);\n    }\n\n    /**\n     * 自定义验证异常\n     */\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {\n        log.error(e.getMessage(), e);\n        String message = e.getBindingResult().getFieldError().getDefaultMessage();\n        return R.error(message);\n    }\n\n\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/web/service/PermissionService.java",
    "content": "package com.oddfar.campus.framework.web.service;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.security.context.PermissionContextHolder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.web.context.WebApplicationContext;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.HandlerExecutionChain;\nimport org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.lang.reflect.Method;\nimport java.util.Set;\n\n/**\n * 自定义权限实现，及自定义controller接口资源权限，ss取自SpringSecurity首字母\n * （根据若依修改）\n *\n * @author oddfar\n */\n@Service(\"ss\")\npublic class PermissionService {\n    private static final Logger log = LoggerFactory.getLogger(PermissionService.class);\n\n    @Value(\"${spring.application.name:}\")\n    private String springApplicationName;\n\n    @Autowired\n    WebApplicationContext applicationContext;\n\n    /**\n     * 所有权限标识\n     */\n    private static final String ALL_PERMISSION = \"*:*:*\";\n\n    /**\n     * 管理员角色权限标识\n     */\n    private static final String SUPER_ADMIN = \"admin\";\n\n    private static final String ROLE_DELIMETER = \",\";\n\n    private static final String PERMISSION_DELIMETER = \",\";\n\n    /**\n     * 验证用户是否具备某权限\n     *\n     * @param permission 权限字符串\n     * @return 用户是否具备某权限\n     */\n    public boolean hasPermi(String permission) {\n        if (StringUtils.isEmpty(permission)) {\n            return false;\n        }\n        LoginUser loginUser = SecurityUtils.getLoginUser();\n        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {\n            return false;\n        }\n        PermissionContextHolder.setContext(permission);\n        return hasPermissions(loginUser.getPermissions(), permission);\n    }\n\n    /**\n     * 验证用户是否具备某接口\n     * @return 用户是否具备某接口\n     */\n    public boolean resourceAuth() {\n        LoginUser loginUser = SecurityUtils.getLoginUser();\n        if (StringUtils.isNull(loginUser)) {\n            return false;\n        }\n\n        HttpServletRequest request = ServletUtils.getRequest();\n        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);\n        HandlerExecutionChain handlerChain = null;\n        try {\n            handlerChain = mapping.getHandler(request);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        //通过处理链找到对应的HandlerMethod类\n        HandlerMethod handler = (HandlerMethod) handlerChain.getHandler();\n        String resourceCode = getResourceCode(handler);\n\n        return hasResources(loginUser.getResources(), resourceCode);\n    }\n\n\n    /**\n     * 获取controller方法的编码\n     *\n     * @param handler\n     * @return\n     */\n    private String getResourceCode(HandlerMethod handler) {\n        Object bean = handler.getBean();//处理请求的类\n        Class<?> aClass = bean.getClass();\n        Method method = handler.getMethod();//处理请求的方法\n        //获取@ApiResource接口\n        ApiResource apiResource =  method.getDeclaringClass().getAnnotation(ApiResource.class);\n        //资源唯一编码\n        StringBuffer resourceCode = new StringBuffer();\n        //应用编码\n        String appCode = springApplicationName;\n        if (apiResource != null) {\n            if (StringUtils.isNotEmpty(apiResource.appCode())) {\n                appCode = apiResource.appCode();\n            }\n        }\n        String className = StrUtil.toUnderlineCase(StringUtils.substringBefore(aClass.getSimpleName(), \"$$\")\n                .replace(\"Controller\", \"\"));\n\n        String methodName = StrUtil.toUnderlineCase(method.getName());\n\n        return resourceCode.append(appCode).append(\".\").append(className).append(\".\").append(methodName).toString();\n\n    }\n\n    /**\n     * 判断是否包含接口\n     *\n     * @param resources 资源列表\n     * @param resource  资源接口字符串\n     * @return 用户是否具备某权限\n     */\n    private boolean hasResources(Set<String> resources, String resource) {\n        return resources.contains(ALL_PERMISSION) || resources.contains(StringUtils.trim(resource));\n    }\n\n    /**\n     * 验证用户是否不具备某权限，与 hasPermi逻辑相反\n     *\n     * @param permission 权限字符串\n     * @return 用户是否不具备某权限\n     */\n    public boolean lacksPermi(String permission) {\n        return hasPermi(permission) != true;\n    }\n\n    /**\n     * 验证用户是否具有以下任意一个权限\n     *\n     * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表\n     * @return 用户是否具有以下任意一个权限\n     */\n    public boolean hasAnyPermi(String permissions) {\n        if (StringUtils.isEmpty(permissions)) {\n            return false;\n        }\n        LoginUser loginUser = SecurityUtils.getLoginUser();\n        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {\n            return false;\n        }\n        PermissionContextHolder.setContext(permissions);\n        Set<String> authorities = loginUser.getPermissions();\n        for (String permission : permissions.split(PERMISSION_DELIMETER)) {\n            if (permission != null && hasPermissions(authorities, permission)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 判断用户是否拥有某个角色\n     *\n     * @param role 角色字符串\n     * @return 用户是否具备某角色\n     */\n    public boolean hasRole(String role) {\n        if (StringUtils.isEmpty(role)) {\n            return false;\n        }\n        LoginUser loginUser = SecurityUtils.getLoginUser();\n        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {\n            return false;\n        }\n        for (SysRoleEntity sysRole : loginUser.getUser().getRoles()) {\n            String roleKey = sysRole.getRoleKey();\n            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 验证用户是否不具备某角色，与 isRole逻辑相反。\n     *\n     * @param role 角色名称\n     * @return 用户是否不具备某角色\n     */\n    public boolean lacksRole(String role) {\n        return hasRole(role) != true;\n    }\n\n    /**\n     * 验证用户是否具有以下任意一个角色\n     *\n     * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表\n     * @return 用户是否具有以下任意一个角色\n     */\n    public boolean hasAnyRoles(String roles) {\n        if (StringUtils.isEmpty(roles)) {\n            return false;\n        }\n        LoginUser loginUser = SecurityUtils.getLoginUser();\n        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {\n            return false;\n        }\n        for (String role : roles.split(ROLE_DELIMETER)) {\n            if (hasRole(role)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 判断是否包含权限\n     *\n     * @param permissions 权限列表\n     * @param permission  权限字符串\n     * @return 用户是否具备某权限\n     */\n    private boolean hasPermissions(Set<String> permissions, String permission) {\n        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/web/service/SysLoginService.java",
    "content": "package com.oddfar.campus.framework.web.service;\n\nimport cn.hutool.core.date.DateUtil;\nimport com.oddfar.campus.common.constant.CacheConstants;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.MessageUtils;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.ip.IpUtils;\nimport com.oddfar.campus.common.utils.web.WebFrameworkUtils;\nimport com.oddfar.campus.framework.manager.AsyncFactory;\nimport com.oddfar.campus.framework.manager.AsyncManager;\nimport com.oddfar.campus.framework.security.context.AuthenticationContextHolder;\nimport com.oddfar.campus.framework.service.SysConfigService;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.authentication.AuthenticationManager;\nimport org.springframework.security.authentication.BadCredentialsException;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.Resource;\n\n/**\n * 登录校验方法\n *\n * @author ruoyi\n */\n@Component\npublic class SysLoginService {\n    @Autowired\n    private TokenService tokenService;\n\n    @Resource\n    private AuthenticationManager authenticationManager;\n\n    @Autowired\n    private RedisCache redisCache;\n\n    @Autowired\n    private SysUserService userService;\n\n    @Autowired\n    private SysConfigService configService;\n\n    /**\n     * 登录验证\n     *\n     * @param username 用户名\n     * @param password 密码\n     * @param code     验证码\n     * @param uuid     唯一标识\n     * @return 结果\n     */\n    public String login(String username, String password, String code, String uuid) {\n        boolean captchaEnabled = configService.selectCaptchaEnabled();\n        // 验证码开关\n        if (captchaEnabled) {\n            validateCaptcha(username, code, uuid);\n        }\n        // 用户验证\n        Authentication authentication = null;\n        try {\n            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);\n            AuthenticationContextHolder.setContext(authenticationToken);\n            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername\n            authentication = authenticationManager.authenticate(authenticationToken);\n        } catch (Exception e) {\n            if (e instanceof BadCredentialsException) {\n                //异步执行->保存登录信息\n                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.LOGIN_FAIL, MessageUtils.message(\"user.password.not.match\")));\n                throw new ServiceException(\"账号或密码错误\");\n            } else {\n                //异步执行->登录信息\n                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.LOGIN_FAIL, e.getMessage()));\n                e.printStackTrace();\n                throw new ServiceException(e.getMessage());\n            }\n        } finally {\n            AuthenticationContextHolder.clearContext();\n        }\n        LoginUser loginUser = (LoginUser) authentication.getPrincipal();\n        //异步记录登录成功日志\n        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, loginUser.getUserId(), Constants.LOGIN_SUCCESS, MessageUtils.message(\"user.login.success\")));\n        // 额外设置到 request 中，有的过滤器在 Spring Security 之前\n        WebFrameworkUtils.setLoginUserId(ServletUtils.getRequest(), loginUser.getUserId());\n        //记录登录信息\n        recordLoginInfo(loginUser.getUserId());\n        // 生成token\n        return tokenService.createToken(loginUser);\n    }\n\n    /**\n     * 校验验证码\n     *\n     * @param username 用户名\n     * @param code     验证码\n     * @param uuid     唯一标识\n     * @return 结果\n     */\n    public void validateCaptcha(String username, String code, String uuid) {\n        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, \"\");\n        String captcha = redisCache.getCacheObject(verifyKey);\n        redisCache.deleteObject(verifyKey);\n        if (captcha == null) {\n//            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message(\"user.jcaptcha.expire\")));\n            throw new ServiceException(\"验证码失效\");\n        }\n        if (!code.equalsIgnoreCase(captcha)) {\n//            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message(\"user.jcaptcha.error\")));\n            throw new ServiceException(\"验证码错误\");\n        }\n    }\n\n    /**\n     * 记录登录信息\n     *\n     * @param userId 用户ID\n     */\n    public void recordLoginInfo(Long userId) {\n        SysUserEntity sysUser = new SysUserEntity();\n        sysUser.setUserId(userId);\n        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));\n        sysUser.setLoginDate(DateUtil.date());\n        userService.updateUserProfile(sysUser);\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/web/service/SysPasswordService.java",
    "content": "package com.oddfar.campus.framework.web.service;\n\nimport com.oddfar.campus.common.constant.CacheConstants;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.exception.user.UserPasswordNotMatchException;\nimport com.oddfar.campus.common.exception.user.UserPasswordRetryLimitExceedException;\nimport com.oddfar.campus.common.utils.MessageUtils;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.framework.manager.AsyncFactory;\nimport com.oddfar.campus.framework.manager.AsyncManager;\nimport com.oddfar.campus.framework.security.context.AuthenticationContextHolder;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.stereotype.Component;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 登录密码方法\n *\n * @author ruoyi\n */\n@Component\npublic class SysPasswordService {\n    @Autowired\n    private RedisCache redisCache;\n\n    @Value(value = \"${user.password.maxRetryCount}\")\n    private int maxRetryCount;\n\n    @Value(value = \"${user.password.lockTime}\")\n    private int lockTime;\n\n    /**\n     * 登录账户密码错误次数缓存键名\n     *\n     * @param username 用户名\n     * @return 缓存键key\n     */\n    private String getCacheKey(String username) {\n        return CacheConstants.PWD_ERR_CNT_KEY + username;\n    }\n\n    public void validate(SysUserEntity user) {\n        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();\n        String username = usernamePasswordAuthenticationToken.getName();\n        String password = usernamePasswordAuthenticationToken.getCredentials().toString();\n\n        Integer retryCount = redisCache.getCacheObject(getCacheKey(username));\n\n        if (retryCount == null) {\n            retryCount = 0;\n        }\n\n        if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) {\n            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.LOGIN_FAIL,\n                    MessageUtils.message(\"user.password.retry.limit.exceed\", maxRetryCount, lockTime)));\n            throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);\n        }\n\n        if (!matches(user, password)) {\n            retryCount = retryCount + 1;\n            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.LOGIN_FAIL,\n                    MessageUtils.message(\"user.password.retry.limit.count\", retryCount)));\n            redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);\n            throw new UserPasswordNotMatchException();\n        } else {\n            clearLoginRecordCache(username);\n        }\n    }\n\n    public boolean matches(SysUserEntity user, String rawPassword) {\n        return SecurityUtils.matchesPassword(rawPassword, user.getPassword());\n    }\n\n    public void clearLoginRecordCache(String loginName) {\n        if (redisCache.hasKey(getCacheKey(loginName))) {\n            redisCache.deleteObject(getCacheKey(loginName));\n        }\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/web/service/SysPermissionService.java",
    "content": "package com.oddfar.campus.framework.web.service;\n\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.domain.entity.SysRoleEntity;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.framework.service.SysMenuService;\nimport com.oddfar.campus.framework.service.SysResourceService;\nimport com.oddfar.campus.framework.service.SysRoleService;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 用户权限处理\n *\n * @author ruoyi\n */\n@Component\npublic class SysPermissionService {\n    @Autowired\n    private SysRoleService roleService;\n    @Autowired\n    private SysMenuService menuService;\n    @Autowired\n    private SysResourceService resourceService;\n    @Autowired\n    private SysUserService userService;\n    @Autowired\n    private TokenService tokenService;\n\n    @Autowired\n    private RedisCache redisCache;\n\n    /**\n     * 获取角色数据权限\n     *\n     * @param user 用户信息\n     * @return 角色权限信息\n     */\n    public Set<String> getRolePermission(SysUserEntity user) {\n        Set<String> roles = new HashSet<String>();\n        // 管理员拥有所有权限\n        if (user.isAdmin()) {\n            roles.add(\"admin\");\n        } else {\n            roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));\n        }\n        return roles;\n    }\n\n    /**\n     * 获取菜单数据权限\n     *\n     * @param user 用户信息\n     * @return 菜单权限信息\n     */\n    public Set<String> getMenuPermission(SysUserEntity user) {\n        Set<String> perms = new HashSet<String>();\n        // 管理员拥有所有权限\n        if (user.isAdmin()) {\n            perms.add(\"*:*:*\");\n        } else {\n            List<SysRoleEntity> roles = user.getRoles();\n            if (!roles.isEmpty() && roles.size() > 1) {\n                // 多角色设置permissions属性，以便数据权限匹配权限\n                for (SysRoleEntity role : roles) {\n                    Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());\n                    role.setPermissions(rolePerms);\n                    perms.addAll(rolePerms);\n                }\n            } else {\n                perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));\n            }\n        }\n        return perms;\n    }\n\n    /**\n     * 获取菜单数据权限\n     *\n     * @param roleID 角色id\n     * @return 菜单权限信息\n     */\n    public Set<String> getMenuPermissionByRoleId(Long roleID) {\n        Set<String> perms = new HashSet<String>();\n        // 管理员拥有所有权限\n        if (roleID == 1) {\n            perms.add(\"*:*:*\");\n        } else {\n            perms = menuService.selectMenuPermsByRoleId(roleID);\n        }\n        return perms;\n    }\n\n    /**\n     * 获取接口资源数据权限\n     *\n     * @param user 用户信息\n     * @return 资源信息\n     */\n    public Set<String> getResources(SysUserEntity user) {\n        Set<String> res = new HashSet<String>();\n        // 超级管理员拥有所有权限\n        if (user.isAdmin()) {\n            res.add(\"*:*:*\");\n        } else {\n            List<SysRoleEntity> roles = user.getRoles();\n            if (roles != null && !roles.isEmpty()) {\n                for (SysRoleEntity role : roles) {\n                    Set<String> code = resourceService.selectResourceCodeByRoleId(role.getRoleId());\n                    res.addAll(code);\n                }\n            }\n        }\n        return res;\n    }\n\n\n    /**\n     * 重置登录用户的权限缓存\n     *\n     * @param roleId 角色id\n     */\n    public void resetLoginUserRoleCache(long roleId) {\n//        Collection<String> keys = redisCache.keys(CacheConstants.LOGIN_USER_KEY + \"*\");\n        SysUserEntity user = new SysUserEntity();\n        user.setRoleId(roleId);\n        List<SysUserEntity> sysUserEntities = userService.selectAllocatedList(user).getRecords();\n\n        sysUserEntities.forEach(u -> resetUserRoleAuthCache(u.getUserId()));\n    }\n\n    /**\n     * 重置redis里用户权限的缓存\n     *\n     * @param userId 用户id\n     */\n    public void resetUserRoleAuthCache(long userId) {\n\n        LoginUser loginUser = tokenService.getLoginUserByUserId(userId);\n        if (loginUser != null) {\n            loginUser.setPermissions(getMenuPermission(loginUser.getUser()));\n            loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));\n            loginUser.setResources(getResources(loginUser.getUser()));\n            tokenService.setLoginUser(loginUser);\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/web/service/SysRegisterService.java",
    "content": "package com.oddfar.campus.framework.web.service;\n\nimport com.oddfar.campus.common.constant.CacheConstants;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.constant.UserConstants;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.RegisterBody;\nimport com.oddfar.campus.common.exception.user.CaptchaException;\nimport com.oddfar.campus.common.exception.user.CaptchaExpireException;\nimport com.oddfar.campus.common.utils.MessageUtils;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.manager.AsyncFactory;\nimport com.oddfar.campus.framework.manager.AsyncManager;\nimport com.oddfar.campus.framework.service.SysConfigService;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\n/**\n * 注册校验方法\n */\n@Component\npublic class SysRegisterService {\n    @Autowired\n    private SysUserService userService;\n\n    @Autowired\n    private SysConfigService configService;\n\n    @Autowired\n    private RedisCache redisCache;\n\n    /**\n     * 注册\n     */\n    public String register(RegisterBody registerBody) {\n        String msg = \"\", username = registerBody.getUsername(), password = registerBody.getPassword();\n        SysUserEntity sysUser = new SysUserEntity();\n        sysUser.setUserName(username);\n\n        // 验证码开关\n        boolean captchaEnabled = configService.selectCaptchaEnabled();\n        if (captchaEnabled) {\n            validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());\n        }\n\n        if (StringUtils.isEmpty(username)) {\n            msg = \"用户名不能为空\";\n        } else if (StringUtils.isEmpty(password)) {\n            msg = \"用户密码不能为空\";\n        } else if (username.length() < UserConstants.USERNAME_MIN_LENGTH\n                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {\n            msg = \"账户长度必须在2到20个字符之间\";\n        } else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH\n                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {\n            msg = \"密码长度必须在5到20个字符之间\";\n        } else if (!(userService.checkUserNameUnique(sysUser))) {\n            msg = \"保存用户'\" + username + \"'失败，注册账号已存在\";\n        } else {\n            sysUser.setNickName(username);\n            sysUser.setPassword(SecurityUtils.encryptPassword(password));\n            boolean regFlag = userService.registerUser(sysUser);\n            if (!regFlag) {\n                msg = \"注册失败,请联系系统管理人员\";\n            } else {\n                //注册失败异步记录信息\n                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.REGISTER, MessageUtils.message(\"user.register.success\")));\n            }\n        }\n        return msg;\n    }\n\n    /**\n     * 校验验证码\n     *\n     * @param username 用户名\n     * @param code     验证码\n     * @param uuid     唯一标识\n     * @return 结果\n     */\n    public void validateCaptcha(String username, String code, String uuid) {\n        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, \"\");\n        String captcha = redisCache.getCacheObject(verifyKey);\n        redisCache.deleteObject(verifyKey);\n        if (captcha == null) {\n            throw new CaptchaExpireException();\n        }\n        if (!code.equalsIgnoreCase(captcha)) {\n            throw new CaptchaException();\n        }\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/web/service/TokenService.java",
    "content": "package com.oddfar.campus.framework.web.service;\n\nimport com.oddfar.campus.common.constant.CacheConstants;\nimport com.oddfar.campus.common.constant.Constants;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.domain.model.LoginUserToken;\nimport com.oddfar.campus.common.utils.ServletUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.common.utils.ip.AddressUtils;\nimport com.oddfar.campus.common.utils.ip.IpUtils;\nimport com.oddfar.campus.common.utils.uuid.IdUtils;\nimport eu.bitwalker.useragentutils.UserAgent;\nimport io.jsonwebtoken.Claims;\nimport io.jsonwebtoken.Jwts;\nimport io.jsonwebtoken.SignatureAlgorithm;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * token验证处理\n * (根据若依修改，增加LoginUser)\n *\n * @author oddfar\n */\n@Component\npublic class TokenService {\n    // 令牌自定义标识\n    @Value(\"${token.header}\")\n    private String header;\n\n    // 令牌秘钥\n    @Value(\"${token.secret}\")\n    private String secret;\n\n    // 令牌有效期（默认30分钟）\n    @Value(\"${token.expireTime}\")\n    private int expireTime;\n\n    protected static final long MILLIS_SECOND = 1000;\n\n    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;\n\n    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;\n\n    @Autowired\n    private RedisCache redisCache;\n\n    /**\n     * 获取用户身份信息\n     *\n     * @return 用户信息\n     */\n    public LoginUser getLoginUser(HttpServletRequest request) {\n        LoginUserToken loginUserToken = getLoginUserToken(request);\n        if (StringUtils.isNotNull(loginUserToken)) {\n            LoginUser user = redisCache.getCacheObject(getLoginKey(loginUserToken.getUserId()));\n            return user;\n        }\n        return null;\n    }\n\n    /**\n     * 获取用户身份信息\n     *\n     * @return 用户信息\n     */\n    public LoginUserToken getLoginUserToken(HttpServletRequest request) {\n        // 获取请求携带的令牌\n        String token = getToken(request);\n        if (StringUtils.isNotEmpty(token)) {\n            try {\n                Claims claims = parseToken(token);\n                // 解析对应的权限以及用户信息\n                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);\n                String userKey = getTokenKey(uuid);\n                //获取用户id\n                LoginUserToken loginUserToken = redisCache.getCacheObject(userKey);\n\n\n                return loginUserToken;\n            } catch (Exception e) {\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 获取用户身份信息\n     *\n     * @return 用户信息\n     */\n    public LoginUser getLoginUserByUserId(long userId) {\n\n        String loginKey = getLoginKey(userId);\n        if (redisCache.hasKey(loginKey)) {\n            try {\n                LoginUser user = redisCache.getCacheObject(getLoginKey(userId));\n                return user;\n            } catch (Exception e) {\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 设置用户身份信息\n     */\n    public void setLoginUser(LoginUser loginUser) {\n        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {\n            refreshToken(loginUser);\n        }\n    }\n\n    /**\n     * 删除用户身份信息\n     */\n    public void delLoginUser(String token) {\n        if (StringUtils.isNotEmpty(token)) {\n            String userKey = getTokenKey(token);\n            redisCache.deleteObject(userKey);\n        }\n    }\n\n    /**\n     * 创建令牌\n     *\n     * @param loginUser 用户信息\n     * @return 令牌\n     */\n    public String createToken(LoginUser loginUser) {\n        String token = IdUtils.fastUUID();\n        loginUser.setToken(token);\n        //设置登录信息\n        LoginUserToken loginUserToken = new LoginUserToken(loginUser);\n        setUserAgent(loginUserToken);\n        refreshToken(loginUser, loginUserToken);\n\n        Map<String, Object> claims = new HashMap<>();\n        claims.put(Constants.LOGIN_USER_KEY, token);\n\n        return createToken(claims);\n    }\n\n    /**\n     * 验证令牌有效期，相差不足20分钟，自动刷新缓存\n     *\n     * @param loginUser\n     * @return 令牌\n     */\n    public void verifyToken(LoginUser loginUser) {\n        long expireTime = loginUser.getExpireTime();\n        long currentTime = System.currentTimeMillis();\n        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {\n            refreshToken(loginUser);\n        }\n    }\n\n    /**\n     * 刷新令牌有效期\n     *\n     * @param loginUser 登录信息\n     */\n    public void refreshToken(LoginUser loginUser) {\n        String userKey = getTokenKey(loginUser.getToken());\n        LoginUserToken loginUserToken = redisCache.getCacheObject(userKey);\n        refreshToken(loginUser, loginUserToken);\n    }\n\n    /**\n     * 刷新令牌有效期\n     *\n     * @param loginUser 登录信息\n     */\n    public void refreshToken(LoginUser loginUser, LoginUserToken loginUserToken) {\n        loginUserToken.setLoginTime(System.currentTimeMillis());\n        loginUser.setExpireTime(loginUserToken.getLoginTime() + expireTime * MILLIS_MINUTE);\n        loginUserToken.setExpireTime(loginUserToken.getLoginTime() + expireTime * MILLIS_MINUTE);\n        // 根据uuid将loginUser登录的缓存\n        String userKey = getTokenKey(loginUser.getToken());\n        redisCache.setCacheObject(userKey, loginUserToken, expireTime, TimeUnit.MINUTES);\n        //设置loginUser缓存\n        redisCache.setCacheObject(getLoginKey(loginUser.getUserId()), loginUser, expireTime, TimeUnit.MINUTES);\n    }\n\n    /**\n     * 设置用户代理信息\n     *\n     * @param loginUserToken 登录信息\n     */\n    public void setUserAgent(LoginUserToken loginUserToken) {\n        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader(\"User-Agent\"));\n        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());\n        loginUserToken.setIpaddr(ip);\n        loginUserToken.setLoginLocation(AddressUtils.getRealAddressByIP(ip));\n        loginUserToken.setBrowser(userAgent.getBrowser().getName());\n        loginUserToken.setOs(userAgent.getOperatingSystem().getName());\n\n    }\n\n    /**\n     * 从数据声明生成令牌\n     *\n     * @param claims 数据声明\n     * @return 令牌\n     */\n    private String createToken(Map<String, Object> claims) {\n        String token = Jwts.builder()\n                .setClaims(claims)\n                .signWith(SignatureAlgorithm.HS512, secret).compact();\n        return token;\n    }\n\n    /**\n     * 从令牌中获取数据声明\n     *\n     * @param token 令牌\n     * @return 数据声明\n     */\n    private Claims parseToken(String token) {\n        return Jwts.parser()\n                .setSigningKey(secret)\n                .parseClaimsJws(token)\n                .getBody();\n    }\n\n    /**\n     * 从令牌中获取用户名\n     *\n     * @param token 令牌\n     * @return 用户名\n     */\n    public String getUsernameFromToken(String token) {\n        Claims claims = parseToken(token);\n        return claims.getSubject();\n    }\n\n    /**\n     * 获取请求token\n     *\n     * @param request\n     * @return token\n     */\n    private String getToken(HttpServletRequest request) {\n        String token = request.getHeader(header);\n        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {\n            token = token.replace(Constants.TOKEN_PREFIX, \"\");\n        }\n        return token;\n    }\n\n    private String getTokenKey(String uuid) {\n        return CacheConstants.LOGIN_TOKEN_KEY + uuid;\n    }\n\n    private String getLoginKey(Long userId) {\n        return CacheConstants.LOGIN_USER_KEY + userId;\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/java/com/oddfar/campus/framework/web/service/UserDetailsServiceImpl.java",
    "content": "package com.oddfar.campus.framework.web.service;\n\nimport com.oddfar.campus.common.domain.entity.SysUserEntity;\nimport com.oddfar.campus.common.domain.model.LoginUser;\nimport com.oddfar.campus.common.enums.UserStatusEnum;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.exception.user.UserPasswordNotMatchException;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.service.SysUserService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.security.core.userdetails.UsernameNotFoundException;\nimport org.springframework.stereotype.Service;\n\n/**\n * 用户验证处理\n *\n * @author ruoyi\n */\n@Service\npublic class UserDetailsServiceImpl implements UserDetailsService {\n    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);\n\n    @Autowired\n    private SysUserService userService;\n\n    @Autowired\n    private SysPasswordService passwordService;\n\n    @Autowired\n    private SysPermissionService permissionService;\n\n    @Override\n    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n        SysUserEntity user = userService.selectUserByUserName(username);\n        if (StringUtils.isNull(user)) {\n            log.info(\"登录用户：{} 不存在.\", username);\n            throw new UserPasswordNotMatchException();\n        } else if (UserStatusEnum.DELETED.getCode().equals(user.getStatus())) {\n            log.info(\"登录用户：{} 已被删除.\", username);\n            throw new ServiceException(\"对不起，您的账号：\" + username + \" 已被删除\");\n        } else if (UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) {\n            log.info(\"登录用户：{} 已被停用.\", username);\n            throw new ServiceException(\"对不起，您的账号：\" + username + \" 已停用\");\n        }\n\n        passwordService.validate(user);\n\n        return createLoginUser(user);\n    }\n\n    public UserDetails createLoginUser(SysUserEntity user) {\n        return new LoginUser(user.getUserId(), user,\n                permissionService.getMenuPermission(user), permissionService.getResources(user));\n    }\n}\n"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysConfigMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysConfigMapper\">\n\n</mapper>\n"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysDictDataMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysDictDataMapper\">\n\n</mapper>\n"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysDictTypeMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysDictTypeMapper\">\n\n</mapper>\n"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysLoginLogMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysLoginLogMapper\">\n\n    <resultMap type=\"com.oddfar.campus.common.domain.entity.SysLoginLogEntity\" id=\"SysLogininforResult\">\n        <id property=\"infoId\" column=\"info_id\"/>\n        <result property=\"userId\" column=\"user_id\"/>\n        <result property=\"userName\" column=\"user_name\"/>\n        <result property=\"status\" column=\"status\"/>\n        <result property=\"ipaddr\" column=\"ipaddr\"/>\n        <result property=\"loginLocation\" column=\"login_location\"/>\n        <result property=\"browser\" column=\"browser\"/>\n        <result property=\"os\" column=\"os\"/>\n        <result property=\"msg\" column=\"msg\"/>\n        <result property=\"loginTime\" column=\"login_time\"/>\n    </resultMap>\n\n\n    <update id=\"cleanLogininfor\">\n        truncate table sys_log_login\n    </update>\n\n</mapper>"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysMenuMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysMenuMapper\">\n\n    <resultMap type=\"com.oddfar.campus.common.domain.entity.SysMenuEntity\" id=\"SysMenuResult\">\n        <id property=\"menuId\" column=\"menu_id\"/>\n        <result property=\"menuName\" column=\"menu_name\"/>\n        <result property=\"parentName\" column=\"parent_name\"/>\n        <result property=\"parentId\" column=\"parent_id\"/>\n        <result property=\"orderNum\" column=\"order_num\"/>\n        <result property=\"path\" column=\"path\"/>\n        <result property=\"component\" column=\"component\"/>\n        <result property=\"query\" column=\"query\"/>\n        <result property=\"isFrame\" column=\"is_frame\"/>\n        <result property=\"isCache\" column=\"is_cache\"/>\n        <result property=\"menuType\" column=\"menu_type\"/>\n        <result property=\"visible\" column=\"visible\"/>\n        <result property=\"status\" column=\"status\"/>\n        <result property=\"perms\" column=\"perms\"/>\n        <result property=\"icon\" column=\"icon\"/>\n        <result property=\"createUser\" column=\"create_user\"/>\n        <result property=\"createTime\" column=\"create_time\"/>\n        <result property=\"updateTime\" column=\"update_time\"/>\n        <result property=\"updateUser\" column=\"update_by\"/>\n        <result property=\"remark\" column=\"remark\"/>\n    </resultMap>\n\n    <resultMap id=\"RoleAuthResult\" type=\"com.oddfar.campus.common.domain.model.SysRoleAuth\">\n        <result property=\"roleID\" column=\"role_id\"/>\n        <result property=\"perms\" column=\"perms\"/>\n        <result property=\"resourceCode\" column=\"resource_code\"/>\n    </resultMap>\n\n    <select id=\"selectMenuTreeAll\" resultType=\"com.oddfar.campus.common.domain.entity.SysMenuEntity\">\n        select distinct m.menu_id,\n                        m.parent_id,\n                        m.menu_name,\n                        m.path,\n                        m.component,\n                        m.`query`,\n                        m.visible,\n                        m.status,\n                        ifnull(m.perms, '') as perms,\n                        m.is_frame,\n                        m.is_cache,\n                        m.menu_type,\n                        m.icon,\n                        m.order_num,\n                        m.create_time\n        from sys_menu m\n        where m.menu_type in ('M', 'C')\n          and m.status = 0\n          and m.del_flag = '0'\n        order by m.parent_id, m.order_num\n    </select>\n\n    <select id=\"selectMenuTreeByUserId\" parameterType=\"Long\" resultMap=\"SysMenuResult\">\n        select distinct m.menu_id,\n                        m.parent_id,\n                        m.menu_name,\n                        m.path,\n                        m.component,\n                        m.`query`,\n                        m.visible,\n                        m.status,\n                        ifnull(m.perms, '') as perms,\n                        m.is_frame,\n                        m.is_cache,\n                        m.menu_type,\n                        m.icon,\n                        m.order_num,\n                        m.create_time\n        from sys_menu m\n                 left join sys_role_menu rm on m.menu_id = rm.menu_id\n                 left join sys_user_role ur on rm.role_id = ur.role_id\n                 left join sys_role ro on ur.role_id = ro.role_id\n                 left join sys_user u on ur.user_id = u.user_id\n        where u.user_id = #{userId}\n          and m.menu_type in ('M', 'C')\n          and m.status = 0\n          and m.del_flag = '0'\n          AND ro.status = 0\n          and ro.del_flag = '0'\n        order by m.parent_id, m.order_num\n    </select>\n\n    <select id=\"selectMenuPermsByRoleId\" parameterType=\"Long\" resultType=\"String\">\n        select distinct m.perms\n        from sys_menu m\n                 left join sys_role_menu rm on m.menu_id = rm.menu_id\n        where m.status = '0'\n          and m.del_flag = '0'\n          and rm.role_id = #{roleId}\n    </select>\n\n    <select id=\"getMenuPermsAll\" resultMap=\"RoleAuthResult\">\n        select m.perms, rm.role_id\n        from sys_menu m\n                 left join sys_role_menu rm on m.menu_id = rm.menu_id\n        where m.status = '0'\n          AND m.del_flag = '0'\n          AND rm.role_id IS NOT NULL\n    </select>\n\n    <select id=\"selectMenuPermsByUserId\" parameterType=\"Long\" resultType=\"String\">\n        select distinct m.perms\n        from sys_menu m\n                 left join sys_role_menu rm on m.menu_id = rm.menu_id\n                 left join sys_user_role ur on rm.role_id = ur.role_id\n                 left join sys_role r on r.role_id = ur.role_id\n        where m.status = '0'\n          and r.status = '0'\n          and r.del_flag = '0'\n          and m.del_flag = '0'\n          and ur.user_id = #{userId}\n    </select>\n\n    <select id=\"selectMenuListByUserId\" parameterType=\"com.oddfar.campus.common.domain.entity.SysMenuEntity\" resultMap=\"SysMenuResult\">\n        select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.visible, m.status,\n        ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time\n        from sys_menu m\n        left join sys_role_menu rm on m.menu_id = rm.menu_id\n        left join sys_user_role ur on rm.role_id = ur.role_id\n        left join sys_role ro on ur.role_id = ro.role_id\n        where ur.user_id = #{params.userId}\n        and m.del_flag = '0'\n        and ro.del_flag = '0'\n        <if test=\"menuName != null and menuName != ''\">\n            AND m.menu_name like concat('%', #{menuName}, '%')\n        </if>\n        <if test=\"visible != null and visible != ''\">\n            AND m.visible = #{visible}\n        </if>\n        <if test=\"status != null and status != ''\">\n            AND m.status = #{status}\n        </if>\n        order by m.parent_id, m.order_num\n    </select>\n\n    <select id=\"selectMenuListByRoleId\" resultType=\"Long\">\n        select m.menu_id\n        from sys_menu m\n        left join sys_role_menu rm on m.menu_id = rm.menu_id\n        where rm.role_id = #{roleId}\n        and m.del_flag = '0'\n        <if test=\"menuCheckStrictly\">\n            and m.menu_id not in (select m.parent_id from sys_menu m inner join sys_role_menu rm on m.menu_id =\n            rm.menu_id and rm.role_id = #{roleId})\n        </if>\n        order by m.parent_id, m.order_num\n    </select>\n\n\n</mapper>\n"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysOperLogMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysOperLogMapper\">\n\n    <resultMap type=\"com.oddfar.campus.common.domain.entity.SysOperLogEntity\" id=\"SysOperLogResult\">\n        <id     property=\"operId\"         column=\"oper_id\"        />\n        <result property=\"appName\"          column=\"app_name\"          />\n        <result property=\"logName\"          column=\"log_name\"          />\n        <result property=\"logContent\"          column=\"log_content\"          />\n        <result property=\"method\"         column=\"method\"         />\n        <result property=\"requestMethod\"  column=\"request_method\" />\n        <result property=\"operUrl\"        column=\"oper_url\"       />\n        <result property=\"operIp\"         column=\"oper_ip\"        />\n        <result property=\"operParam\"      column=\"oper_param\"     />\n        <result property=\"jsonResult\"     column=\"json_result\"    />\n        <result property=\"status\"         column=\"status\"         />\n        <result property=\"errorMsg\"       column=\"error_msg\"      />\n        <result property=\"operTime\"       column=\"oper_time\"      />\n    </resultMap>\n\n    <update id=\"cleanOperLog\">\n        truncate table sys_log_oper\n    </update>\n\n</mapper>"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysResourceMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysResourceMapper\">\n\n    <select id=\"selectResourceCodeByRoleId\" resultType=\"java.lang.String\">\n        select sr.resource_code\n        from sys_role_resource sr\n                 left join sys_role r on r.role_id = sr.role_id\n        where sr.role_id = #{roleId}\n          AND r.status = 0\n    </select>\n\n\n    <select id=\"selectResourceListByRoleId\" resultType=\"java.lang.Long\">\n        select r.resource_id\n        from sys_resource r\n                 left join sys_role_resource rr on r.resource_code = rr.resource_code\n        where rr.role_id = #{roleId}\n    </select>\n\n    <sql id=\"selectResourceVo\">\n        select resource_id,\n               app_code,\n               resource_code,\n               resource_name,\n               class_name,\n               method_name,\n               modular_name,\n               url,\n               http_method,\n               resource_biz_type,\n               required_permission_flag,\n               create_time\n        from sys_resource\n    </sql>\n\n    <select id=\"selectResourceList\" resultType=\"com.oddfar.campus.common.domain.entity.SysResourceEntity\">\n        <include refid=\"selectResourceVo\"/>\n        <where>\n            <if test=\"resourceCode != null and resourceCode != ''\">\n                AND resource_code = #{resourceCode}\n            </if>\n            <if test=\"className != null and className != ''\">\n                AND class_name = #{className}\n            </if>\n            <if test=\"requiredPermissionFlag != null and requiredPermissionFlag != ''\">\n                AND required_permission_flag = #{requiredPermissionFlag}\n            </if>\n        </where>\n    </select>\n\n    <select id=\"selectResourceListByUserId\" resultType=\"com.oddfar.campus.common.domain.entity.SysResourceEntity\">\n        select distinct r.resource_id, r.app_code, r.resource_code, r.resource_name, r.class_name, r.method_name,\n        r.modular_name, r.url, r.http_method, r.resource_biz_type, r.required_permission_flag,r.create_time\n        from sys_resource r\n        left join sys_role_resource rr on r.resource_code = rr.resource_code\n        left join sys_user_role ur on rr.role_id = ur.role_id\n        left join sys_role ro on ur.role_id = ro.role_id\n        where ur.user_id = #{params.userId}\n        <if test=\"resourceCode != null and resourceCode != ''\">\n            AND resource_code = #{resourceCode}\n        </if>\n        <if test=\"className != null and className != ''\">\n            AND class_name = #{className}\n        </if>\n        <if test=\"requiredPermissionFlag != null and requiredPermissionFlag != ''\">\n            AND required_permission_flag = #{requiredPermissionFlag}\n        </if>\n\n    </select>\n\n\n</mapper>\n"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysRoleMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysRoleMapper\">\n\n    <resultMap type=\"com.oddfar.campus.common.domain.entity.SysRoleEntity\" id=\"SysRoleResult\">\n        <id property=\"roleId\" column=\"role_id\"/>\n        <result property=\"roleName\" column=\"role_name\"/>\n        <result property=\"roleKey\" column=\"role_key\"/>\n        <result property=\"roleSort\" column=\"role_sort\"/>\n        <result property=\"menuCheckStrictly\" column=\"menu_check_strictly\"/>\n        <result property=\"status\" column=\"status\"/>\n        <result property=\"delFlag\" column=\"del_flag\"/>\n        <result property=\"createUser\" column=\"create_user\"/>\n        <result property=\"createTime\" column=\"create_time\"/>\n        <result property=\"updateUser\" column=\"update_user\"/>\n        <result property=\"updateTime\" column=\"update_time\"/>\n    </resultMap>\n\n    <sql id=\"selectRoleVo\">\n        select distinct r.role_id,\n                        r.role_name,\n                        r.role_key,\n                        r.role_sort,\n                        r.menu_check_strictly,\n                        r.status,\n                        r.del_flag,\n                        r.create_time,\n                        r.remark\n        from sys_role r\n                 left join sys_user_role ur on ur.role_id = r.role_id\n                 left join sys_user u on u.user_id = ur.user_id\n    </sql>\n\n\n\n    <select id=\"selectRolePermissionByUserId\" resultType=\"com.oddfar.campus.common.domain.entity.SysRoleEntity\">\n        <include refid=\"selectRoleVo\"/>\n        WHERE r.del_flag = '0' and ur.user_id = #{userId}\n    </select>\n\n\n    <select id=\"selectRoleById\" parameterType=\"Long\" resultType=\"com.oddfar.campus.common.domain.entity.SysRoleEntity\">\n        <include refid=\"selectRoleVo\"/>\n        where r.role_id = #{roleId}\n    </select>\n\n\n    <select id=\"selectRoleList\" parameterType=\"com.oddfar.campus.common.domain.entity.SysRoleEntity\" resultMap=\"SysRoleResult\">\n        <include refid=\"selectRoleVo\"/>\n        where r.del_flag = '0'\n        <if test=\"roleId != null and roleId != 0\">\n            AND r.role_id = #{roleId}\n        </if>\n        <if test=\"roleName != null and roleName != ''\">\n            AND r.role_name like concat('%', #{roleName}, '%')\n        </if>\n        <if test=\"status != null and status != ''\">\n            AND r.status = #{status}\n        </if>\n        <if test=\"roleKey != null and roleKey != ''\">\n            AND r.role_key like concat('%', #{roleKey}, '%')\n        </if>\n        <if test=\"params.beginTime != null and params.beginTime != ''\"><!-- 开始时间检索 -->\n            and date_format(r.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')\n        </if>\n        <if test=\"params.endTime != null and params.endTime != ''\"><!-- 结束时间检索 -->\n            and date_format(r.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')\n        </if>\n        order by r.role_sort\n    </select>\n\n    <select id=\"selectRoleListByKey\" resultMap=\"SysRoleResult\">\n        <include refid=\"selectRoleVo\"/>\n        where r.del_flag = '0'\n        and r.role_key in  <foreach item=\"roleKey\"  collection=\"RoleKeys\" open=\"(\" separator=\",\" close=\")\">\n                              #{roleKey}\n                           </foreach>\n    </select>\n\n    <select id=\"checkRoleNameUnique\" parameterType=\"String\" resultMap=\"SysRoleResult\">\n        <include refid=\"selectRoleVo\"/>\n        where r.role_name=#{roleName} and r.del_flag = '0' limit 1\n    </select>\n\n    <select id=\"checkRoleKeyUnique\" parameterType=\"String\" resultMap=\"SysRoleResult\">\n        <include refid=\"selectRoleVo\"/>\n        where r.role_key=#{roleKey} and r.del_flag = '0' limit 1\n    </select>\n\n    <select id=\"selectRolesByUserName\" parameterType=\"String\" resultMap=\"SysRoleResult\">\n        <include refid=\"selectRoleVo\"/>\n        WHERE r.del_flag = '0' and u.user_name = #{userName}\n    </select>\n\n\n</mapper>\n"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysRoleMenuMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\" >\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysRoleMenuMapper\">\n\n    <resultMap type=\"com.oddfar.campus.common.domain.entity.SysRoleMenuEntity\" id=\"SysRoleMenuResult\">\n        <result property=\"roleId\" column=\"role_id\"/>\n        <result property=\"menuId\" column=\"menu_id\"/>\n    </resultMap>\n\n    <select id=\"checkMenuExistRole\" resultType=\"Integer\">\n        select count(1)\n        from sys_role_menu\n        where menu_id = #{menuId}\n    </select>\n\n    <delete id=\"deleteRoleMenuByRoleId\" parameterType=\"Long\">\n        delete\n        from sys_role_menu\n        where role_id = #{roleId}\n    </delete>\n\n    <delete id=\"deleteRoleMenu\" parameterType=\"Long\">\n        delete from sys_role_menu where role_id in\n        <foreach collection=\"array\" item=\"roleId\" open=\"(\" separator=\",\" close=\")\">\n            #{roleId}\n        </foreach>\n    </delete>\n\n    <insert id=\"batchRoleMenu\">\n        insert into sys_role_menu(role_id, menu_id) values\n        <foreach item=\"item\" index=\"index\" collection=\"list\" separator=\",\">\n            (#{item.roleId},#{item.menuId})\n        </foreach>\n    </insert>\n\n\n</mapper>"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysRoleResourceMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\" >\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysRoleResourceMapper\">\n\n\n    <insert id=\"saveBatch\">\n        insert into sys_role_resource(role_id, resource_code) values\n        <foreach item=\"item\" index=\"index\" collection=\"list\" separator=\",\">\n            (#{item.roleId},#{item.resourceCode})\n        </foreach>\n    </insert>\n</mapper>"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysUserMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysUserMapper\">\n\n    <resultMap type=\"com.oddfar.campus.common.domain.entity.SysUserEntity\" id=\"SysUserResult\">\n        <id     property=\"userId\"       column=\"user_id\"      />\n        <result property=\"userName\"     column=\"user_name\"    />\n        <result property=\"nickName\"     column=\"nick_name\"    />\n        <result property=\"email\"        column=\"email\"        />\n        <result property=\"phonenumber\"  column=\"phonenumber\"  />\n        <result property=\"sex\"          column=\"sex\"          />\n        <result property=\"avatar\"       column=\"avatar\"       />\n        <result property=\"password\"     column=\"password\"     />\n        <result property=\"status\"       column=\"status\"       />\n        <result property=\"delFlag\"      column=\"del_flag\"     />\n        <result property=\"loginIp\"      column=\"login_ip\"     />\n        <result property=\"loginDate\"    column=\"login_date\"   />\n        <result property=\"createUser\"     column=\"create_user\"    />\n        <result property=\"createTime\"   column=\"create_time\"  />\n        <result property=\"updateUser\"     column=\"update_user\"    />\n        <result property=\"updateTime\"   column=\"update_time\"  />\n        <result property=\"remark\"       column=\"remark\"       />\n        <collection  property=\"roles\"   javaType=\"java.util.List\"   resultMap=\"RoleResult\" />\n    </resultMap>\n\n    <resultMap id=\"RoleResult\" type=\"com.oddfar.campus.common.domain.entity.SysRoleEntity\">\n        <id     property=\"roleId\"       column=\"role_id\"        />\n        <result property=\"roleName\"     column=\"role_name\"      />\n        <result property=\"roleKey\"      column=\"role_key\"       />\n        <result property=\"roleSort\"     column=\"role_sort\"      />\n        <result property=\"status\"       column=\"role_status\"    />\n    </resultMap>\n\n\n\n    <sql id=\"selectUserVo\">\n        select u.user_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_user, u.create_time, u.remark,\n               r.role_id, r.role_name, r.role_key, r.role_sort, r.status as role_status\n        from sys_user u\n                 left join sys_user_role ur on u.user_id = ur.user_id\n                 left join sys_role r on r.role_id = ur.role_id\n    </sql>\n\n\n    <update id=\"resetUserPwd\" parameterType=\"com.oddfar.campus.common.domain.entity.SysUserEntity\">\n        update sys_user set password = #{password} where user_name = #{userName}\n    </update>\n\n    <update id=\"updateUserAvatar\" parameterType=\"com.oddfar.campus.common.domain.entity.SysUserEntity\">\n        update sys_user set avatar = #{avatar} where user_name = #{userName}\n    </update>\n\n\n    <select id=\"selectUserByUserName\" parameterType=\"String\" resultMap=\"SysUserResult\">\n        <include refid=\"selectUserVo\"/>\n        where u.user_name = #{userName} and u.del_flag = '0'\n    </select>\n\n    <select id=\"selectUserById\" parameterType=\"Long\" resultMap=\"SysUserResult\">\n        <include refid=\"selectUserVo\"/>\n        where u.user_id = #{userId}\n    </select>\n\n    <select id=\"selectAllocatedList\"  resultMap=\"SysUserResult\">\n        select distinct u.user_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time\n        from sys_user u\n            left join sys_user_role ur on u.user_id = ur.user_id\n            left join sys_role r on r.role_id = ur.role_id\n        ${ew.getCustomSqlSegment}\n    </select>\n\n    <select id=\"selectUnallocatedList\" resultMap=\"SysUserResult\">\n        select distinct u.user_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time\n        from sys_user u\n        left join sys_user_role ur on u.user_id = ur.user_id\n        left join sys_role r on r.role_id = ur.role_id\n        where u.del_flag = '0' and (r.role_id != #{user.roleId} or r.role_id IS NULL)\n        and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and ur.role_id = #{user.roleId})\n        <if test=\"user.userName != null and user.userName != ''\">\n            AND u.user_name like concat('%', #{user.userName}, '%')\n        </if>\n        <if test=\"user.phonenumber != null and user.phonenumber != ''\">\n            AND u.phonenumber like concat('%', #{user.phonenumber}, '%')\n        </if>\n    </select>\n\n\n</mapper>\n"
  },
  {
    "path": "campus-framework/src/main/resources/mapper/SysUserRoleMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\" >\n<mapper namespace=\"com.oddfar.campus.framework.mapper.SysUserRoleMapper\">\n\n    <delete id=\"deleteUserRole\" parameterType=\"Long\">\n        delete from sys_user_role where user_id in\n        <foreach collection=\"array\" item=\"userId\" open=\"(\" separator=\",\" close=\")\">\n            #{userId}\n        </foreach>\n    </delete>\n\n</mapper>"
  },
  {
    "path": "campus-modular/Dockerfile",
    "content": "FROM adoptopenjdk/openjdk8\n#FROM java:8\n#FROM openjdk:8\n\nMAINTAINER oddfar\n\n# 创建目录\nRUN mkdir -p /home/campus/conf\n\nWORKDIR /home/campus\n\nENV SERVER_PORT=8160\n\nEXPOSE ${SERVER_PORT}\n\nADD ./target/campus-modular.jar ./app.jar\n\nENTRYPOINT [\"java\", \\\n            \"-Djava.security.egd=file:/dev/./urandom\", \\\n            \"-Dserver.port=${SERVER_PORT}\", \\\n            \"-jar\", \"app.jar\"]\n"
  },
  {
    "path": "campus-modular/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>campus</artifactId>\n        <groupId>com.oddfar.campus</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>campus-modular</artifactId>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.oddfar.campus</groupId>\n            <artifactId>campus-admin</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <version>2.5.15</version>\n                <configuration>\n                    <fork>true</fork> <!-- 如果没有该配置，devtools不会生效 -->\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-war-plugin</artifactId>\n                <version>3.0.0</version>\n                <configuration>\n                    <failOnMissingWebXml>false</failOnMissingWebXml>\n                    <warName>${project.artifactId}</warName>\n                </configuration>\n            </plugin>\n        </plugins>\n        <finalName>${project.artifactId}</finalName>\n    </build>\n\n</project>"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/CampusApplication.java",
    "content": "package com.oddfar.campus;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\nimport java.time.ZoneOffset;\nimport java.util.TimeZone;\n\n/**\n * GitHub: https://github.com/oddfar/campus-imaotai\n * @author oddfar\n */\n@SpringBootApplication\npublic class CampusApplication {\n\n    public static void main(String[] args) {\n        //设置+8时区，避免因为时区问题导致预约时间不正确\n        TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.of(\"+8\")));\n        SpringApplication.run(CampusApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/api/PushPlusApi.java",
    "content": "package com.oddfar.campus.business.api;\n\nimport cn.hutool.http.HttpUtil;\nimport com.oddfar.campus.business.entity.ILog;\nimport com.oddfar.campus.business.entity.IUser;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport com.oddfar.campus.framework.manager.AsyncManager;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TimerTask;\n\n/**\n * @author zhiyuan\n */\npublic class PushPlusApi {\n\n\n    public static void sendNotice(IUser iUser, ILog operLog) {\n        String token = iUser.getPushPlusToken();\n        if (StringUtils.isEmpty(token)) {\n            return;\n        }\n        String title, content;\n        if (operLog.getStatus() == 0) {\n            //预约成功\n            title = iUser.getRemark() + \"-i茅台执行成功\";\n            content = iUser.getMobile() + System.lineSeparator() + operLog.getLogContent();\n            AsyncManager.me().execute(sendNotice(token, title, content, \"txt\"));\n        } else {\n            //预约失败\n            title = iUser.getRemark() + \"-i茅台执行失败\";\n            content = iUser.getMobile() + System.lineSeparator() + operLog.getLogContent();\n            AsyncManager.me().execute(sendNotice(token, title, content, \"txt\"));\n        }\n\n\n    }\n\n    /**\n     * push推送\n     *\n     * @param token    token\n     * @param title    消息标题\n     * @param content  具体消息内容\n     * @param template 发送消息模板\n     */\n    public static TimerTask sendNotice(String token, String title, String content, String template) {\n        return new TimerTask() {\n            @Override\n            public void run() {\n                String url = \"http://www.pushplus.plus/send\";\n                Map<String, Object> map = new HashMap<>();\n                map.put(\"token\", token);\n                map.put(\"title\", title);\n                map.put(\"content\", content);\n                if (StringUtils.isEmpty(template)) {\n                    map.put(\"template\", \"html\");\n                }\n                HttpUtil.post(url, map);\n            }\n        };\n    }\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/controller/IItemController.java",
    "content": "package com.oddfar.campus.business.controller;\n\nimport com.oddfar.campus.business.entity.IItem;\nimport com.oddfar.campus.business.mapper.IItemMapper;\nimport com.oddfar.campus.business.service.IShopService;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.R;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\n\n/**\n * I茅台预约商品列表Controller\n *\n * @author oddfar\n * @date 2023-07-05\n */\n@RestController\n@RequestMapping(\"/imt/item\")\n@ApiResource(name = \"I茅台预约商品列Controller\")\n@RequiredArgsConstructor\npublic class IItemController {\n\n    private final IItemMapper iItemMapper;\n    private final IShopService iShopService;\n\n    /**\n     * 查询I茅台预约商品列列表\n     */\n    @GetMapping(value = \"/list\", name = \"查询I茅台预约商品列列表\")\n    public R list() {\n        List<IItem> iItems = iItemMapper.selectList();\n\n        return R.ok(iItems);\n    }\n\n    /**\n     * 刷新i茅台预约商品列表\n     */\n    @GetMapping(value = \"/refresh\", name = \"刷新i茅台预约商品列表\")\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    public R refreshItem() {\n        iShopService.refreshItem();\n        return R.ok();\n    }\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/controller/ILogController.java",
    "content": "package com.oddfar.campus.business.controller;\n\nimport com.oddfar.campus.business.entity.ILog;\nimport com.oddfar.campus.business.service.IMTLogService;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * I茅台日志Controller\n *\n * @author oddfar\n * @date 2023-08-01\n */\n@RestController\n@RequestMapping(\"/imt/log\")\n@ApiResource(name = \"I茅台日志Controller\")\n@RequiredArgsConstructor\npublic class ILogController {\n    private final IMTLogService logService;\n\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    @GetMapping(value = \"/list\", name = \"操作日志-分页\")\n    public R list(ILog log) {\n        PageResult<ILog> page = logService.page(log);\n        return R.ok().put(page);\n    }\n\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    @DeleteMapping(value = \"/{operIds}\", name = \"操作日志-删除\")\n    public R remove(@PathVariable Long[] operIds) {\n        return R.ok(logService.deleteLogByIds(operIds));\n    }\n\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    @DeleteMapping(value = \"/clean\", name = \"操作日志-清空\")\n    public R clean() {\n        logService.cleanLog();\n        return R.ok();\n    }\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/controller/IShopController.java",
    "content": "package com.oddfar.campus.business.controller;\n\nimport com.oddfar.campus.business.entity.IShop;\nimport com.oddfar.campus.business.mapper.IShopMapper;\nimport com.oddfar.campus.business.service.IShopService;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n\n/**\n * i茅台商品Controller\n *\n * @author oddfar\n * @date 2023-07-05\n */\n@RestController\n@RequestMapping(\"/imt/shop\")\n@ApiResource(name = \"i茅台商品Controller\")\n@RequiredArgsConstructor\npublic class IShopController {\n    private final IShopService iShopService;\n    private final IShopMapper iShopMapper;\n\n    /**\n     * 查询i茅台商品列表\n     */\n    @GetMapping(\"/list\")\n    public R list(IShop iShop) {\n        PageResult<IShop> page = iShopMapper.selectPage(iShop);\n\n        return R.ok().put(page);\n    }\n\n\n    /**\n     * 刷新i茅台商品列表\n     */\n    @GetMapping(value = \"/refresh\", name = \"刷新i茅台商品列表\")\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    public R refreshShop() {\n        iShopService.refreshShop();\n        return R.ok();\n    }\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/controller/IUserController.java",
    "content": "package com.oddfar.campus.business.controller;\n\nimport com.oddfar.campus.business.entity.IShop;\nimport com.oddfar.campus.business.entity.IUser;\nimport com.oddfar.campus.business.mapper.IUserMapper;\nimport com.oddfar.campus.business.service.IMTService;\nimport com.oddfar.campus.business.service.IShopService;\nimport com.oddfar.campus.business.service.IUserService;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\n/**\n * I茅台用户Controller\n *\n * @author oddfar\n * @date 2023-07-06\n */\n@RestController\n@RequestMapping(\"/imt/user\")\n@ApiResource(name = \"I茅台用户Controller\")\n@RequiredArgsConstructor\npublic class IUserController {\n\n    private final IUserService iUserService;\n    private final IUserMapper iUserMapper;\n    private final IMTService imtService;\n    private final IShopService iShopService;\n\n    /**\n     * 查询I茅台用户列表\n     */\n    @GetMapping(value = \"/list\", name = \"查询I茅台用户列表\")\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    public R list(IUser iUser) {\n        PageResult<IUser> page = iUserService.page(iUser);\n\n        return R.ok().put(page);\n    }\n\n    /**\n     * 发送验证码\n     */\n    @GetMapping(value = \"/sendCode\", name = \"发送验证码\")\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    public R sendCode(String mobile, String deviceId) {\n        imtService.sendCode(mobile, deviceId);\n\n        return R.ok();\n    }\n\n    /**\n     * 预约\n     */\n    @GetMapping(value = \"/reservation\", name = \"预约\")\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    public R reservation(String mobile) {\n        IUser user = iUserMapper.selectById(mobile);\n        if (user == null) {\n            return R.error(\"用户不存在\");\n        }\n        if (StringUtils.isEmpty(user.getItemCode())) {\n            return R.error(\"商品预约code为空\");\n        }\n\n        imtService.reservation(user);\n        return R.ok();\n    }\n\n    /**\n     * 小茅运旅行活动\n     */\n    @GetMapping(value = \"/travelReward\", name = \"旅行\")\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    public R travelReward(String mobile) {\n        IUser user = iUserMapper.selectById(mobile);\n        if (user == null) {\n            return R.error(\"用户不存在\");\n        } else {\n            imtService.getTravelReward(user);\n            return R.ok();\n        }\n    }\n\n    /**\n     * 登录\n     */\n    @GetMapping(value = \"/login\", name = \"登录\")\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    public R login(String mobile, String code, String deviceId) {\n        imtService.login(mobile, code, deviceId);\n\n        return R.ok();\n    }\n\n\n    /**\n     * 获取I茅台用户详细信息\n     */\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    @GetMapping(value = \"/{mobile}\", name = \"获取I茅台用户详细信息\")\n    public R getInfo(@PathVariable(\"mobile\") Long mobile) {\n        return R.ok(iUserMapper.selectById(mobile));\n    }\n\n    /**\n     * 新增I茅台用户\n     */\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    @PostMapping(name = \"新增I茅台用户\")\n    public R add(@RequestBody IUser iUser) {\n\n        IShop iShop = iShopService.selectByIShopId(iUser.getIshopId());\n        if (iShop == null) {\n            throw new ServiceException(\"门店商品id不存在\");\n        }\n        iUser.setLng(iShop.getLng());\n        iUser.setLat(iShop.getLat());\n        iUser.setProvinceName(iShop.getProvinceName());\n        iUser.setCityName(iShop.getCityName());\n\n        return R.ok(iUserService.insertIUser(iUser));\n    }\n\n    /**\n     * 修改I茅台用户\n     */\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    @PutMapping(name = \"修改I茅台用户\")\n    public R edit(@RequestBody IUser iUser) {\n        IShop iShop = iShopService.selectByIShopId(iUser.getIshopId());\n        if (iShop == null) {\n            throw new ServiceException(\"门店商品id不存在\");\n        }\n        iUser.setLng(iShop.getLng());\n        iUser.setLat(iShop.getLat());\n        iUser.setProvinceName(iShop.getProvinceName());\n        iUser.setCityName(iShop.getCityName());\n        return R.ok(iUserService.updateIUser(iUser));\n    }\n\n    /**\n     * 删除I茅台用户\n     */\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    @DeleteMapping(value = \"/{mobiles}\", name = \"删除I茅台用户\")\n    public R remove(@PathVariable Long[] mobiles) {\n        return R.ok(iUserMapper.deleteIUser(mobiles));\n    }\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/controller/TestController.java",
    "content": "package com.oddfar.campus.business.controller;\n\nimport com.oddfar.campus.business.mapper.IUserMapper;\nimport com.oddfar.campus.business.service.IMTService;\nimport com.oddfar.campus.business.service.IShopService;\nimport com.oddfar.campus.common.annotation.Anonymous;\nimport com.oddfar.campus.common.annotation.ApiResource;\nimport com.oddfar.campus.common.annotation.Log;\nimport com.oddfar.campus.common.domain.R;\nimport com.oddfar.campus.common.enums.ResBizTypeEnum;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RequestMapping(\"/test\")\n@ApiResource(name = \"测试\", appCode = \"test\", resBizType = ResBizTypeEnum.BUSINESS)\n@Log(openLog = false)\npublic class TestController {\n\n    @Autowired\n    private IUserMapper iUserMapper;\n    @Autowired\n    private IMTService imtService;\n    @Autowired\n    private IShopService iShopService;\n\n    /**\n     * 需要接口权限\n     */\n    @PreAuthorize(\"@ss.resourceAuth()\")\n    @GetMapping(value = \"/1\", name = \"测试1的接口\")\n    public R test1() {\n\n        return R.ok();\n    }\n\n    /**\n     * 需要 'campus:test' 权限字符串\n     */\n    @PreAuthorize(\"@ss.hasPermi('campus:test')\")\n    @GetMapping(value = \"/2\", name = \"测试2的接口\")\n    public R test2() {\n\n        return R.ok();\n    }\n\n    /**\n     * 匿名接口，不需要认证，所有人都可访问\n     */\n    @Anonymous\n    @GetMapping(value = \"/3\", name = \"测试3的接口\")\n    public R test3() {\n        iShopService.selectShopList();\n        return R.ok();\n    }\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/domain/IMTCacheConstants.java",
    "content": "package com.oddfar.campus.business.domain;\n\n/**\n * i茅台缓存常量\n *\n * @author oddfar\n * @date 2024/4/12\n */\npublic interface IMTCacheConstants {\n    String MT_VERSION = \"mt_version\";\n\n    String MT_SESSION_ID = \"mt_session_id\";\n\n    String MT_SHOP_LIST = \"mt_shop_list\";\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/domain/IMTItemInfo.java",
    "content": "package com.oddfar.campus.business.domain;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n/**\n * i茅台预约商品信息\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class IMTItemInfo {\n\n    private String shopId;\n\n    private int count;\n\n    private String itemId;\n\n    /**\n     * 库存\n     */\n    private int inventory;\n\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/domain/IUserRequest.java",
    "content": "package com.oddfar.campus.business.domain;\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport lombok.Data;\n\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * i茅台用户请求对象\n */\n@Data\npublic class IUserRequest {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 手机号\n     */\n    private Long mobile;\n\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * token\n     */\n    private String token;\n\n    /**\n     * cookie\n     */\n    private String cookie;\n\n    /**\n     * 设备id\n     */\n    private String deviceId;\n\n    /**\n     * 商品预约code，用@间隔\n     */\n    private String itemCode;\n\n    /**\n     * 完整地址\n     */\n    private String address;\n\n    /**\n     * 预约的分钟（1-59）\n     */\n    private int minute;\n\n    /**\n     * 随机分钟预约，9点取一个时间（0:随机，1:不随机）\n     */\n    private String randomMinute;\n\n    /**\n     * 类型\n     */\n    private int shopType;\n\n    /**\n     * 门店商品ID\n     */\n    private String ishopId;\n\n    /**\n     * push_plus_token\n     */\n    private String pushPlusToken;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * token过期时间\n     */\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n    private Date expireTime;\n\n    @TableField(exist = false)\n    private Map<String, Object> params;\n\n    public Map<String, Object> getParams() {\n        if (params == null) {\n            params = new HashMap<>();\n        }\n        return params;\n    }\n\n    public int getMinute() {\n        if (minute > 59 || minute < 1) {\n            this.minute = 5;\n        }\n        return minute;\n    }\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/domain/MapPoint.java",
    "content": "package com.oddfar.campus.business.domain;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class MapPoint {\n\n  /**\n   * 纬度\n   */\n  private Double latitude;\n\n  /**\n   * 经度\n   */\n  private Double longitude;\n\n}"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/entity/IItem.java",
    "content": "package com.oddfar.campus.business.entity;\n\nimport com.alibaba.fastjson2.JSONObject;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport lombok.Data;\n\nimport java.util.Date;\n\n/**\n * I茅台预约商品对象 i_item\n *\n * @author oddfar\n * @date 2023-07-02\n */\n@Data\n@TableName(\"i_item\")\npublic class IItem {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * ID\n     */\n    @TableId\n    private Long itemId;\n\n    /**\n     * 商品code\n     */\n    private String itemCode;\n\n    /**\n     * 标题\n     */\n    private String title;\n\n    /**\n     * 内容\n     */\n    private String content;\n\n    /**\n     * 图片\n     */\n    private String picture;\n\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n    private Date createTime;\n\n    public IItem() {\n    }\n\n    public IItem(JSONObject item) {\n        this.itemCode = item.getString(\"itemCode\");\n        this.title =  item.getString(\"title\");;\n        this.content =  item.getString(\"content\");;\n        this.picture =  item.getString(\"picture\");;\n        this.createTime = new Date();\n    }\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/entity/ILog.java",
    "content": "package com.oddfar.campus.business.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport lombok.Data;\n\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\nimport javax.validation.constraints.NotNull;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * I茅台日志 i_log\n *\n * @author oddfar\n * @date 2023-08-01\n */\n@Data\n@TableName(\"i_log\")\npublic class ILog {\n\n    private static final long serialVersionUID = 1L;\n\n    private static final Integer PAGE_NUM = 1;\n    private static final Integer PAGE_SIZE = 10;\n\n    /**\n     * 日志主键\n     */\n    @TableId(\"log_id\")\n    private Long logId;\n\n    /**\n     * 日志记录内容\n     */\n    private String logContent;\n\n    /**\n     * 操作人员手机号\n     */\n    private Long mobile;\n\n    /**\n     * 操作状态（0正常 1异常）\n     */\n    private Integer status;\n\n    /**\n     * 操作时间\n     */\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n    private Date operTime;\n\n    /**\n     * 上级人\n     */\n    private Long createUser;\n\n    @TableField(exist = false)\n    private Map<String, Object> params;\n\n    public Map<String, Object> getParams() {\n        if (params == null) {\n            params = new HashMap<>();\n        }\n        return params;\n    }\n\n    @NotNull(message = \"页码不能为空\")\n    @Min(value = 1, message = \"页码最小值为 1\")\n    @TableField(exist = false)\n    @JsonIgnore\n    private Integer pageNum = PAGE_NUM;\n\n    @NotNull(message = \"每页条数不能为空\")\n    @Min(value = 1, message = \"每页条数最小值为 1\")\n    @Max(value = 100, message = \"每页条数最大值为 100\")\n    @TableField(exist = false)\n    @JsonIgnore\n    private Integer pageSize = PAGE_SIZE;\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/entity/IShop.java",
    "content": "package com.oddfar.campus.business.entity;\n\nimport com.alibaba.fastjson2.JSONObject;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport lombok.Data;\n\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * I茅台商品对象 i_shop\n *\n * @author oddfar\n * @date 2023-07-02\n */\n@Data\n@TableName(\"i_shop\")\npublic class IShop {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * ID\n     */\n    @TableId\n    private Long shopId;\n\n    /**\n     * 商品ID\n     */\n    private String iShopId;\n\n    /**\n     * 省份\n     */\n    private String provinceName;\n\n    /**\n     * 城市\n     */\n    private String cityName;\n\n    /**\n     * 地区\n     */\n    private String districtName;\n\n    /**\n     * 完整地址\n     */\n    private String fullAddress;\n\n    /**\n     * 纬度\n     */\n    private String lat;\n\n    /**\n     * 经度\n     */\n    private String lng;\n\n    /**\n     * 名称\n     */\n    private String name;\n\n    /**\n     * 公司名称\n     */\n    private String tenantName;\n\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n    private Date createTime;\n\n    @TableField(exist = false)\n    private Map<String, Object> params;\n\n    /**\n     * 距离\n     */\n    @TableField(exist = false)\n    private Double distance;\n\n\n    public IShop() {\n    }\n\n    public IShop(String iShopId, JSONObject jsonObject) {\n        this.iShopId = iShopId;\n        this.provinceName = jsonObject.getString(\"provinceName\");\n        this.cityName = jsonObject.getString(\"cityName\");\n        this.districtName = jsonObject.getString(\"districtName\");\n        this.fullAddress = jsonObject.getString(\"fullAddress\");\n        this.lat = jsonObject.getString(\"lat\");\n        this.lng = jsonObject.getString(\"lng\");\n        this.name = jsonObject.getString(\"name\");\n        this.tenantName = jsonObject.getString(\"tenantName\");\n        this.createTime = new Date();\n\n    }\n\n    public Map<String, Object> getParams() {\n        if (params == null) {\n            params = new HashMap<>();\n        }\n        return params;\n    }\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/entity/IUser.java",
    "content": "package com.oddfar.campus.business.entity;\n\nimport com.alibaba.fastjson2.JSONObject;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.oddfar.campus.common.domain.BaseEntity;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.util.*;\n\n/**\n * I茅台用户对象 i_user\n *\n * @author oddfar\n * @date 2023-07-02\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@TableName(\"i_user\")\npublic class IUser extends BaseEntity {\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 手机号\n     */\n    @TableId\n    private Long mobile;\n\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * token\n     */\n    private String token;\n\n    /**\n     * cookie\n     */\n    private String cookie;\n\n    /**\n     * 设备id\n     */\n    private String deviceId;\n\n    /**\n     * 商品预约code，用@间隔\n     */\n    private String itemCode;\n\n    /**\n     * 门店商品ID\n     */\n    private String ishopId;\n\n    /**\n     * 省份\n     */\n    private String provinceName;\n\n    /**\n     * 城市\n     */\n    private String cityName;\n\n    /**\n     * 完整地址\n     */\n    private String address;\n\n    /**\n     * 纬度\n     */\n    private String lat;\n\n    /**\n     * 经度\n     */\n    private String lng;\n\n    /**\n     * 预约的分钟（1-59）\n     */\n    private int minute;\n\n    /**\n     * 随机分钟预约，9点取一个时间（0:随机，1:不随机）\n     */\n    private String randomMinute;\n\n    /**\n     * 类型(1：预约本市出货量最大的门店，2：预约你的位置附近门店)\n     */\n    private int shopType;\n\n    /**\n     * push_plus_token\n     */\n    private String pushPlusToken;\n\n    /**\n     * 返回参数\n     */\n    @JsonIgnore\n    private String jsonResult;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * token过期时间\n     */\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n    private Date expireTime;\n\n    @TableField(exist = false)\n    private Map<String, Object> params;\n\n    public IUser() {\n\n    }\n\n    public IUser(Long mobile, JSONObject jsonObject) {\n        JSONObject data = jsonObject.getJSONObject(\"data\");\n        this.userId = data.getLong(\"userId\");\n        this.mobile = mobile;\n        this.token = data.getString(\"token\");\n        this.cookie = data.getString(\"cookie\");\n        this.jsonResult = StringUtils.substring(jsonObject.toJSONString(), 0, 2000);\n\n//        if (StringUtils.isEmpty(this.remark)) {\n//            this.remark = data.getString(\"userName\");\n//        }\n\n        Calendar calendar = Calendar.getInstance();\n        calendar.add(Calendar.DAY_OF_MONTH, 30);\n        Date thirtyDaysLater = calendar.getTime();\n        this.expireTime = thirtyDaysLater;\n    }\n\n    public IUser(Long mobile, String deviceId, JSONObject jsonObject) {\n        JSONObject data = jsonObject.getJSONObject(\"data\");\n        this.userId = data.getLong(\"userId\");\n        this.mobile = mobile;\n        this.token = data.getString(\"token\");\n        this.cookie = data.getString(\"cookie\");\n        this.deviceId = deviceId.toLowerCase();\n        this.jsonResult = StringUtils.substring(jsonObject.toJSONString(), 0, 2000);\n\n        if (StringUtils.isEmpty(this.remark)) {\n            this.remark = data.getString(\"userName\");\n        }\n\n        Calendar calendar = Calendar.getInstance();\n        calendar.add(Calendar.DAY_OF_MONTH, 30);\n        Date thirtyDaysLater = calendar.getTime();\n        this.expireTime = thirtyDaysLater;\n    }\n\n    public Map<String, Object> getParams() {\n        if (params == null) {\n            params = new HashMap<>();\n        }\n        return params;\n    }\n\n    public int getMinute() {\n        if (minute > 59 || minute < 1) {\n            this.minute = 5;\n        }\n        return minute;\n    }\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/mapper/IItemMapper.java",
    "content": "package com.oddfar.campus.business.mapper;\n\nimport com.oddfar.campus.business.entity.IItem;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport org.apache.ibatis.annotations.Update;\n\n/**\n * I茅台预约商品Mapper接口\n *\n * @author oddfar\n * @date 2023-07-02\n */\npublic interface IItemMapper extends BaseMapperX<IItem> {\n    //清空指定表\n    @Update(\"truncate table i_item\")\n    void truncateItem();\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/mapper/ILogMapper.java",
    "content": "package com.oddfar.campus.business.mapper;\n\nimport com.oddfar.campus.business.entity.ILog;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport org.apache.ibatis.annotations.Select;\n\n/**\n * I茅台日志Mapper接口\n *\n * @author oddfar\n * @date 2023-08-01\n */\npublic interface ILogMapper extends BaseMapperX<ILog> {\n\n    default PageResult<ILog> selectPage(ILog iLog) {\n\n        return selectPage(new LambdaQueryWrapperX<ILog>()\n                .eqIfPresent(ILog::getMobile, iLog.getMobile())\n                .eqIfPresent(ILog::getStatus, iLog.getStatus())\n                .betweenIfPresent(ILog::getOperTime, iLog.getParams())\n                .orderByDesc(ILog::getLogId)\n        );\n\n    }\n\n    default PageResult<ILog> selectPage(ILog iLog, Long userId) {\n\n        return selectPage(new LambdaQueryWrapperX<ILog>()\n                .eqIfPresent(ILog::getMobile, iLog.getMobile())\n                .eqIfPresent(ILog::getStatus, iLog.getStatus())\n                .eq(ILog::getCreateUser, userId)\n                .betweenIfPresent(ILog::getOperTime, iLog.getParams())\n                .orderByDesc(ILog::getLogId)\n        );\n\n    }\n\n    @Select(\"truncate table i_log\")\n    void cleanLog();\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/mapper/IShopMapper.java",
    "content": "package com.oddfar.campus.business.mapper;\n\nimport com.oddfar.campus.business.entity.IShop;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport org.apache.ibatis.annotations.Update;\n\n/**\n * I茅台商品Mapper接口\n *\n * @author oddfar\n * @date 2023-07-02\n */\n\npublic interface IShopMapper extends BaseMapperX<IShop> {\n    //清空指定表\n    @Update(\"truncate table i_shop\")\n    void truncateShop();\n\n    default PageResult<IShop> selectPage(IShop iShop) {\n\n        return selectPage(new LambdaQueryWrapperX<IShop>()\n                .eqIfPresent(IShop::getIShopId,iShop.getIShopId())\n                .likeIfPresent(IShop::getProvinceName, iShop.getProvinceName())\n                .likeIfPresent(IShop::getDistrictName, iShop.getDistrictName())\n                .likeIfPresent(IShop::getCityName, iShop.getCityName())\n                .likeIfPresent(IShop::getTenantName, iShop.getTenantName())\n                .betweenIfPresent(IShop::getCreateTime, iShop.getParams())\n        );\n\n    }\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/mapper/IUserMapper.java",
    "content": "package com.oddfar.campus.business.mapper;\n\nimport com.oddfar.campus.business.entity.IUser;\nimport com.oddfar.campus.common.core.BaseMapperX;\nimport com.oddfar.campus.common.core.LambdaQueryWrapperX;\nimport com.oddfar.campus.common.domain.PageResult;\nimport org.apache.ibatis.annotations.Update;\n\nimport java.util.List;\n\n/**\n * I茅台用户Mapper接口\n *\n * @author oddfar\n * @date 2023-07-02\n */\npublic interface IUserMapper extends BaseMapperX<IUser> {\n    default PageResult<IUser> selectPage(IUser iUser) {\n\n        return selectPage(new LambdaQueryWrapperX<IUser>()\n                .eqIfPresent(IUser::getUserId, iUser.getUserId())\n                .eqIfPresent(IUser::getMobile, iUser.getMobile())\n                .eqIfPresent(IUser::getProvinceName, iUser.getProvinceName())\n                .betweenIfPresent(IUser::getExpireTime, iUser.getParams())\n                .orderByAsc(IUser::getCreateTime)\n        );\n\n    }\n\n    default PageResult<IUser> selectPage(IUser iUser, Long userId) {\n\n        return selectPage(new LambdaQueryWrapperX<IUser>()\n                .eqIfPresent(IUser::getUserId, iUser.getUserId())\n                .eqIfPresent(IUser::getMobile, iUser.getMobile())\n                .eqIfPresent(IUser::getProvinceName, iUser.getProvinceName())\n                .eq(IUser::getCreateUser, userId)\n                .betweenIfPresent(IUser::getExpireTime, iUser.getParams())\n                .orderByAsc(IUser::getCreateTime)\n        );\n\n    }\n\n    default List<IUser> selectReservationUser() {\n        return selectList(new LambdaQueryWrapperX<IUser>()\n//                      .gt(IUser::getExpireTime, new Date())\n                        .ne(IUser::getLat, \"\")\n                        .ne(IUser::getLng, \"\")\n                        .ne(IUser::getItemCode, \"\")\n                        .isNotNull(IUser::getItemCode)\n\n        );\n\n    }\n\n    /**\n     * 通过预约执行分钟查询预约用户列表\n     */\n    default List<IUser> selectReservationUserByMinute(int minute) {\n        return selectList(new LambdaQueryWrapperX<IUser>()\n                        .eq(IUser::getMinute, minute)\n//                      .gt(IUser::getExpireTime, new Date())\n                        .ne(IUser::getLat, \"\")\n                        .ne(IUser::getLng, \"\")\n                        .ne(IUser::getItemCode, \"\")\n                        .isNotNull(IUser::getItemCode)\n        );\n    }\n\n    @Update(\"UPDATE i_user SET `minute` = (SELECT FLOOR(RAND() * 50 + 1)) WHERE random_minute = \\\"0\\\"\")\n    void updateUserMinuteBatch();\n\n    @Update(\"SET @row_number = 0;\\n\" +\n            \"UPDATE i_user\\n\" +\n            \"SET `minute` = (@row_number := @row_number + 1) % 50 + 1\\n\" +\n            \"ORDER BY RAND();\")\n    void updateUserMinuteEven();\n\n    int deleteIUser(Long[] iUserId);\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/IMTLogFactory.java",
    "content": "package com.oddfar.campus.business.service;\n\nimport com.oddfar.campus.business.api.PushPlusApi;\nimport com.oddfar.campus.business.entity.ILog;\nimport com.oddfar.campus.business.entity.IUser;\nimport com.oddfar.campus.common.utils.SpringUtils;\nimport com.oddfar.campus.framework.manager.AsyncManager;\n\nimport java.util.Date;\nimport java.util.TimerTask;\n\n/**\n * i茅台日志记录\n */\npublic class IMTLogFactory {\n\n\n    public static void reservation(IUser iUser, String logContent) {\n        //{\"code\":2000,\"data\":{\"successDesc\":\"申购完成，请于7月6日18:00查看预约申购结果\",\"reservationList\":[{\"reservationId\":17053404357,\"sessionId\":678,\"shopId\":\"233331084001\",\"reservationTime\":1688608601720,\"itemId\":\"10214\",\"count\":1}],\"reservationDetail\":{\"desc\":\"申购成功后将以短信形式通知您，请您在申购成功次日18:00前确认支付方式，并在7天内完成提货。\",\"lotteryTime\":1688637600000,\"cacheValidTime\":1688637600000}}}\n        ILog operLog = new ILog();\n\n        operLog.setOperTime(new Date());\n\n        if (logContent.contains(\"报错\")) {\n            //失败\n            operLog.setStatus(1);\n        } else {\n            operLog.setStatus(0);\n        }\n        operLog.setMobile(iUser.getMobile());\n        operLog.setCreateUser(iUser.getCreateUser());\n        operLog.setLogContent(logContent);\n\n        AsyncManager.me().execute(recordOper(operLog));\n        //推送\n        PushPlusApi.sendNotice(iUser, operLog);\n    }\n\n    /**\n     * 操作日志记录\n     *\n     * @param operLog 操作日志信息\n     * @return 任务task\n     */\n    public static TimerTask recordOper(final ILog operLog) {\n        return new TimerTask() {\n            @Override\n            public void run() {\n                SpringUtils.getBean(IMTLogService.class).insertLog(operLog);\n            }\n        };\n    }\n\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/IMTLogService.java",
    "content": "package com.oddfar.campus.business.service;\n\nimport com.oddfar.campus.business.entity.ILog;\nimport com.oddfar.campus.common.domain.PageResult;\n\n/**\n * i茅台 日志\n */\npublic interface IMTLogService {\n\n    PageResult<ILog> page(ILog iLog);\n\n    /**\n     * 新增操作日志\n     *\n     * @param iLog 操作日志对象\n     */\n    public int insertLog(ILog iLog);\n\n    /**\n     * 批量删除系统操作日志\n     *\n     * @param operIds 需要删除的操作日志ID\n     * @return 结果\n     */\n    public int deleteLogByIds(Long[] operIds);\n\n    /**\n     * 清空操作日志\n     */\n    public void cleanLog();\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/IMTService.java",
    "content": "package com.oddfar.campus.business.service;\n\nimport com.oddfar.campus.business.entity.IUser;\n\npublic interface IMTService {\n    /**\n     * 获取i茅台app版本号\n     *\n     * @return\n     */\n    String getMTVersion();\n\n    /**\n     * 刷新i茅台app版本号\n     */\n    void refreshMTVersion();\n\n    /**\n     * 发送手机验证码\n     *\n     * @param mobile   手机号\n     * @param deviceId 设备id\n     */\n    Boolean sendCode(String mobile, String deviceId);\n\n    /**\n     * 登录i茅台\n     *\n     * @param mobile   手机号\n     * @param code     验证码\n     * @param deviceId 设备id\n     * @return\n     */\n    boolean login(String mobile, String code, String deviceId);\n\n    /**\n     * 预约\n     */\n    void reservation(IUser iUser);\n\n    /**\n     * 获取申购耐力值\n     */\n    String getEnergyAward(IUser iUser);\n\n    /**\n     * 获得旅行奖励\n     *\n     * @param iUser\n     * @return\n     */\n    void getTravelReward(IUser iUser);\n//\n//    /**\n//     * 获取累计申购奖励\n//     * @param iUser\n//     */\n//    void getCumulatively(IUser iUser);\n\n    /**\n     * 批量预约\n     */\n    void reservationBatch();\n\n    /**\n     * 批量获得旅行奖励\n     */\n    void getTravelRewardBatch();\n\n    /**\n     * 刷新版本号，预约item，门店shop列表，\n     */\n    void refreshAll();\n\n    /**\n     * 每日预约申购结果\n     */\n    void appointmentResults();\n\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/IShopService.java",
    "content": "package com.oddfar.campus.business.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.oddfar.campus.business.domain.IMTItemInfo;\nimport com.oddfar.campus.business.entity.IShop;\n\nimport java.util.List;\n\npublic interface IShopService extends IService<IShop> {\n\n    List<IShop> selectShopList();\n\n    /**\n     * 刷新数据库i茅台shop列表\n     */\n    void refreshShop();\n\n    /**\n     * 获取当天的sessionId\n     */\n    String getCurrentSessionId();\n\n    /**\n     * 刷新i茅台预约商品列表\n     */\n    void refreshItem();\n\n    /**\n     * 根据 iShopId 查询门店信息\n     * <p>\n     * 预约选择门店时，根据城市、经纬度选择门店\n     * 之前这些参数是在账号配置里手动填写\n     * 现在是查询门店的信息，把门店的这些信息填到账号配置里\n     * 主要是这样避免用户填错信息\n     *\n     * @param iShopId\n     * @return 门店信息\n     */\n    IShop selectByIShopId(String iShopId);\n\n    /**\n     * 查询所在省市的投放产品和数量\n     *\n     * @param province 省份，例如：河北省，北京市\n     * @param itemId   项目id即预约项目code\n     */\n    List<IMTItemInfo> getShopsByProvince(String province, String itemId);\n\n    /**\n     * @param shopType 1：预约本市出货量最大的门店，2：预约你的位置附近门店\n     * @param itemId   项目id即预约项目code\n     * @param province 省份，例如：河北省，北京市\n     * @param city     市：例如石家庄市\n     * @return\n     */\n    String getShopId(int shopType, String itemId, String province, String city, String lat, String lng);\n\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/IUserService.java",
    "content": "package com.oddfar.campus.business.service;\n\nimport com.alibaba.fastjson2.JSONObject;\nimport com.oddfar.campus.business.entity.IUser;\nimport com.oddfar.campus.common.domain.PageResult;\n\nimport java.util.List;\n\npublic interface IUserService {\n\n    PageResult<IUser> page(IUser iUser);\n\n    /**\n     * 添加i茅台用户\n     *\n     * @param mobile\n     * @param body\n     * @return\n     */\n    int insertIUser(Long mobile, String deviceId, JSONObject body);\n\n    /**\n     * 查询预约用户列表\n     *\n     * @return\n     */\n    List<IUser> selectReservationUser();\n\n    /**\n     * 通过预约执行分钟查询预约用户列表\n     *\n     * @return\n     */\n    List<IUser> selectReservationUserByMinute(int minute);\n\n    /**\n     * 添加i茅台用户\n     *\n     * @param iUser\n     * @return\n     */\n    int insertIUser(IUser iUser);\n\n    /**\n     * 修改I茅台用户\n     *\n     * @param iUser I茅台用户\n     * @return 结果\n     */\n    int updateIUser(IUser iUser);\n\n    /**\n     * 批量修改用户随机预约的时间\n     *\n     * @return\n     */\n    void updateUserMinuteBatch();\n\n    /**\n     * 删除用户\n     *\n     * @param iUserId id\n     * @return\n     */\n    int deleteIUser(Long[] iUserId);\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/impl/IMTLogServiceImpl.java",
    "content": "package com.oddfar.campus.business.service.impl;\n\nimport com.oddfar.campus.business.entity.ILog;\nimport com.oddfar.campus.business.mapper.ILogMapper;\nimport com.oddfar.campus.business.service.IMTLogService;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Arrays;\n\n@Service\npublic class IMTLogServiceImpl implements IMTLogService {\n\n    @Autowired\n    private ILogMapper logMapper;\n\n    @Override\n    public PageResult<ILog> page(ILog iLog) {\n        Long userId = SecurityUtils.getUserId();\n        if (userId != 1) {\n            return logMapper.selectPage(iLog, userId);\n        }\n        return logMapper.selectPage(iLog);\n    }\n\n\n    @Override\n    public int deleteLogByIds(Long[] operIds) {\n        return logMapper.deleteBatchIds(Arrays.asList(operIds));\n    }\n\n    @Override\n    public void cleanLog() {\n        logMapper.cleanLog();\n    }\n\n    @Override\n    public int insertLog(ILog iLog) {\n        return logMapper.insert(iLog);\n    }\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/impl/IMTServiceImpl.java",
    "content": "package com.oddfar.campus.business.service.impl;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.date.DateUnit;\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.crypto.Mode;\nimport cn.hutool.crypto.Padding;\nimport cn.hutool.crypto.symmetric.AES;\nimport cn.hutool.http.HttpRequest;\nimport cn.hutool.http.HttpResponse;\nimport cn.hutool.http.HttpUtil;\nimport cn.hutool.http.Method;\nimport com.alibaba.fastjson2.JSON;\nimport com.alibaba.fastjson2.JSONArray;\nimport com.alibaba.fastjson2.JSONObject;\nimport com.oddfar.campus.business.domain.IMTCacheConstants;\nimport com.oddfar.campus.business.entity.IUser;\nimport com.oddfar.campus.business.mapper.IUserMapper;\nimport com.oddfar.campus.business.service.IMTLogFactory;\nimport com.oddfar.campus.business.service.IMTService;\nimport com.oddfar.campus.business.service.IShopService;\nimport com.oddfar.campus.business.service.IUserService;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.PostConstruct;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.Random;\n\n@Service\npublic class IMTServiceImpl implements IMTService {\n\n    private static final Logger logger = LoggerFactory.getLogger(IMTServiceImpl.class);\n\n    @Autowired\n    private IUserMapper iUserMapper;\n\n    @Autowired\n    private RedisCache redisCache;\n\n    @Autowired\n    private IUserService iUserService;\n    @Autowired\n    private IShopService iShopService;\n\n    private final static String SALT = \"2af72f100c356273d46284f6fd1dfc08\";\n\n    private final static String AES_KEY = \"qbhajinldepmucsonaaaccgypwuvcjaa\";\n    private final static String AES_IV = \"2018534749963515\";\n\n    /**\n     * 项目启动时，初始化数据\n     */\n    @PostConstruct\n    public void init() {\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                refreshAll();\n            }\n        }).start();\n\n    }\n\n\n    @Override\n    public String getMTVersion() {\n        String mtVersion = Convert.toStr(redisCache.getCacheObject(IMTCacheConstants.MT_VERSION));\n        if (StringUtils.isNotEmpty(mtVersion)) {\n            return mtVersion;\n        }\n        String url = \"https://apps.apple.com/cn/app/i%E8%8C%85%E5%8F%B0/id1600482450\";\n        String htmlContent = HttpUtil.get(url);\n        Pattern pattern = Pattern.compile(\"new__latest__version\\\">(.*?)</p>\", Pattern.DOTALL);\n        Matcher matcher = pattern.matcher(htmlContent);\n        if (matcher.find()) {\n            mtVersion = matcher.group(1);\n            mtVersion = mtVersion.replace(\"版本 \", \"\");\n        }\n        redisCache.setCacheObject(IMTCacheConstants.MT_VERSION, mtVersion);\n\n        return mtVersion;\n\n    }\n\n    @Override\n    public void refreshMTVersion() {\n        redisCache.deleteObject(IMTCacheConstants.MT_VERSION);\n        getMTVersion();\n    }\n\n    @Override\n    public Boolean sendCode(String mobile, String deviceId) {\n        Map<String, Object> data = new HashMap<>();\n        data.put(\"mobile\", mobile);\n        final long curTime = System.currentTimeMillis();\n        data.put(\"md5\", signature(mobile, curTime));\n        data.put(\"timestamp\", String.valueOf(curTime));\n//        data.put(\"MT-APP-Version\", MT_VERSION);\n\n        HttpRequest request = HttpUtil.createRequest(Method.POST,\n                \"https://app.moutai519.com.cn/xhr/front/user/register/vcode\");\n\n\n        request.header(\"MT-Device-ID\", deviceId);\n        request.header(\"MT-APP-Version\", getMTVersion());\n        request.header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\");\n\n        request.header(\"Content-Type\", \"application/json\");\n\n        HttpResponse execute = request.body(JSONObject.toJSONString(data)).execute();\n        JSONObject jsonObject = JSONObject.parseObject(execute.body());\n        //成功返回 {\"code\":2000}\n        logger.info(\"「发送验证码返回」：\" + jsonObject.toJSONString());\n        if (jsonObject.getString(\"code\").equals(\"2000\")) {\n            return Boolean.TRUE;\n        } else {\n            logger.error(\"「发送验证码-失败」：\" + jsonObject.toJSONString());\n            throw new ServiceException(\"发送验证码错误\");\n//            return false;\n        }\n\n    }\n\n    @Override\n    public boolean login(String mobile, String code, String deviceId) {\n        Map<String, String> map = new HashMap<>();\n        map.put(\"mobile\", mobile);\n        map.put(\"vCode\", code);\n\n        final long curTime = System.currentTimeMillis();\n        map.put(\"md5\", signature(mobile + code + \"\" + \"\", curTime));\n\n        map.put(\"timestamp\", String.valueOf(curTime));\n        map.put(\"MT-APP-Version\", getMTVersion());\n\n        HttpRequest request = HttpUtil.createRequest(Method.POST,\n                \"https://app.moutai519.com.cn/xhr/front/user/register/login\");\n        IUser user = iUserMapper.selectById(mobile);\n        if (user != null) {\n            deviceId = user.getDeviceId();\n        }\n        request.header(\"MT-Device-ID\", deviceId);\n        request.header(\"MT-APP-Version\", getMTVersion());\n        request.header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\");\n        request.header(\"Content-Type\", \"application/json\");\n\n        HttpResponse execute = request.body(JSONObject.toJSONString(map)).execute();\n\n        JSONObject body = JSONObject.parseObject(execute.body());\n\n        if (body.getString(\"code\").equals(\"2000\")) {\n//            logger.info(\"「登录请求-成功」\" + body.toJSONString());\n            iUserService.insertIUser(Long.parseLong(mobile), deviceId, body);\n            return true;\n        } else {\n            logger.error(\"「登录请求-失败」\" + body.toJSONString());\n            throw new ServiceException(\"登录失败，本地错误日志已记录\");\n//            return false;\n        }\n\n    }\n\n\n    @Override\n    public void reservation(IUser iUser) {\n        if (StringUtils.isEmpty(iUser.getItemCode())) {\n            return;\n        }\n        String[] items = iUser.getItemCode().split(\"@\");\n\n        String logContent = \"\";\n        for (String itemId : items) {\n            try {\n                String shopId = iShopService.getShopId(iUser.getShopType(), itemId,\n                        iUser.getProvinceName(), iUser.getCityName(), iUser.getLat(), iUser.getLng());\n                //预约\n                JSONObject json = reservation(iUser, itemId, shopId);\n                logContent += String.format(\"[预约项目]：%s\\n[shopId]：%s\\n[结果返回]：%s\\n\\n\", itemId, shopId, json.toString());\n\n                //随机延迟3～5秒\n                Random random = new Random();\n                int sleepTime = random.nextInt(3) + 3;\n                Thread.sleep(sleepTime * 1000);\n            } catch (Exception e) {\n                logContent += String.format(\"执行报错--[预约项目]：%s\\n[结果返回]：%s\\n\\n\", itemId, e.getMessage());\n            }\n        }\n\n//        try {\n//            //预约后领取耐力值\n//            String energyAward = getEnergyAward(iUser);\n//            logContent += \"[申购耐力值]:\" + energyAward;\n//        } catch (Exception e) {\n//            logContent += \"执行报错--[申购耐力值]:\" + e.getMessage();\n//        }\n        //日志记录\n        IMTLogFactory.reservation(iUser, logContent);\n        //预约后延迟领取耐力值\n        getEnergyAwardDelay(iUser);\n    }\n\n    /**\n     * 延迟执行：获取申购耐力值，并记录日志\n     *\n     * @param iUser\n     */\n    public void getEnergyAwardDelay(IUser iUser) {\n        Runnable runnable = new Runnable() {\n            @Override\n            public void run() {\n                String logContent = \"\";\n                //sleep 10秒\n                try {\n                    Thread.sleep(10000);\n                    //预约后领取耐力值\n                    String energyAward = getEnergyAward(iUser);\n                    logContent += \"[申购耐力值]:\" + energyAward;\n                } catch (Exception e) {\n                    logContent += \"执行报错--[申购耐力值]:\" + e.getMessage();\n                }\n                //日志记录\n                IMTLogFactory.reservation(iUser, logContent);\n            }\n        };\n        new Thread(runnable).start();\n\n    }\n\n    // 领取小茅运\n    public void receiveReward(IUser iUser) {\n        String url = \"https://h5.moutai519.com.cn/game/xmTravel/receiveReward\";\n        HttpRequest request = HttpUtil.createRequest(Method.POST, url);\n\n        request.header(\"MT-Device-ID\", iUser.getDeviceId())\n                .header(\"MT-APP-Version\", getMTVersion())\n                .header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\")\n                .header(\"MT-Lat\", iUser.getLat())\n                .header(\"MT-Lng\", iUser.getLng())\n                .cookie(\"MT-Token-Wap=\" + iUser.getCookie() + \";MT-Device-ID-Wap=\" + iUser.getDeviceId() + \";\");\n\n        HttpResponse execute = request.execute();\n        JSONObject body = JSONObject.parseObject(execute.body());\n\n        if (body.getInteger(\"code\") != 2000) {\n            String message = \"领取小茅运失败\";\n            throw new ServiceException(message);\n        }\n    }\n\n    public void shareReward(IUser iUser) {\n        logger.info(\"「领取每日首次分享获取耐力」：\" + iUser.getMobile());\n        String url = \"https://h5.moutai519.com.cn/game/xmTravel/shareReward\";\n        HttpRequest request = HttpUtil.createRequest(Method.POST, url);\n\n        request.header(\"MT-Device-ID\", iUser.getDeviceId())\n                .header(\"MT-APP-Version\", getMTVersion())\n                .header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\")\n                .header(\"MT-Lat\", iUser.getLat())\n                .header(\"MT-Lng\", iUser.getLng())\n                .cookie(\"MT-Token-Wap=\" + iUser.getCookie() + \";MT-Device-ID-Wap=\" + iUser.getDeviceId() + \";\");\n\n        HttpResponse execute = request.execute();\n        JSONObject body = JSONObject.parseObject(execute.body());\n\n        if (body.getInteger(\"code\") != 2000) {\n            String message = \"领取每日首次分享获取耐力失败\";\n            throw new ServiceException(message);\n        }\n    }\n\n    //获取申购耐力值\n    @Override\n    public String getEnergyAward(IUser iUser) {\n        String url = \"https://h5.moutai519.com.cn/game/isolationPage/getUserEnergyAward\";\n        HttpRequest request = HttpUtil.createRequest(Method.POST, url);\n\n        request.header(\"MT-Device-ID\", iUser.getDeviceId())\n                .header(\"MT-APP-Version\", getMTVersion())\n                .header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\")\n                .header(\"MT-Lat\", iUser.getLat())\n                .header(\"MT-Lng\", iUser.getLng())\n                .cookie(\"MT-Token-Wap=\" + iUser.getCookie() + \";MT-Device-ID-Wap=\" + iUser.getDeviceId() + \";\");\n\n        String body = request.execute().body();\n\n        JSONObject jsonObject = JSONObject.parseObject(body);\n        if (jsonObject.getInteger(\"code\") != 200) {\n            String message = jsonObject.getString(\"message\");\n            throw new ServiceException(message);\n        }\n\n        return body;\n    }\n\n    @Override\n    public void getTravelReward(IUser iUser) {\n        String logContent = \"\";\n        try {\n            String s = travelReward(iUser);\n            logContent += \"[获得旅行奖励]:\" + s;\n        } catch (Exception e) {\n//            e.printStackTrace();\n            logContent += \"执行报错--[获得旅行奖励]:\" + e.getMessage();\n        }\n        //日志记录\n        IMTLogFactory.reservation(iUser, logContent);\n    }\n\n    /**\n     * 获得旅行奖励\n     *\n     * @param iUser\n     * @return\n     */\n    public String travelReward(IUser iUser) {\n        //9-20点才能领取旅行奖励\n        int hour = DateUtil.hour(new Date(), true);\n        if (!(9 <= hour && hour < 20)) {\n            String message = \"活动未开始，开始时间9点-20点\";\n            throw new ServiceException(message);\n        }\n        Map<String, Integer> pageData = getUserIsolationPageData(iUser);\n        Integer status = pageData.get(\"status\");\n        if (status == 3) {\n            Integer currentPeriodCanConvertXmyNum = pageData.get(\"currentPeriodCanConvertXmyNum\");\n            Double travelRewardXmy = getXmTravelReward(iUser);\n            // 获取小茅运\n            receiveReward(iUser);\n            //首次分享获取耐力\n            shareReward(iUser);\n            //本次旅行奖励领取后, 当月实际剩余旅行奖励\n            if (travelRewardXmy > currentPeriodCanConvertXmyNum) {\n                String message = \"当月无可领取奖励\";\n                throw new ServiceException(message);\n            }\n        }\n\n        Integer remainChance = pageData.get(\"remainChance\");\n        if (remainChance < 1) {\n            String message = \"当日旅行次数已耗尽\";\n            throw new ServiceException(message);\n        } else {\n            //小茅运旅行活动\n            return startTravel(iUser);\n        }\n    }\n\n    //小茅运旅行活动\n    public String startTravel(IUser iUser) {\n        String url = \"https://h5.moutai519.com.cn/game/xmTravel/startTravel\";\n        HttpRequest request = HttpUtil.createRequest(Method.POST, url);\n\n        request.header(\"MT-Device-ID\", iUser.getDeviceId())\n                .header(\"MT-APP-Version\", getMTVersion())\n                .header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\")\n                .cookie(\"MT-Token-Wap=\" + iUser.getCookie() + \";MT-Device-ID-Wap=\" + iUser.getDeviceId() + \";\");\n        String body = request.execute().body();\n        JSONObject jsonObject = JSONObject.parseObject(body);\n        if (jsonObject.getInteger(\"code\") != 2000) {\n            String message = \"开始旅行失败：\" + jsonObject.getString(\"message\");\n            throw new ServiceException(message);\n        }\n        JSONObject data = jsonObject.getJSONObject(\"data\");\n        //最后返回 {\"startTravelTs\":1690798861076}\n        return jsonObject.toString();\n    }\n\n\n    //查询 可获取小茅运\n    public Double getXmTravelReward(IUser iUser) {\n        //查询旅行奖励:\n        String url = \"https://h5.moutai519.com.cn/game/xmTravel/getXmTravelReward\";\n        HttpRequest request = HttpUtil.createRequest(Method.GET, url);\n\n        request.header(\"MT-Device-ID\", iUser.getDeviceId())\n                .header(\"MT-APP-Version\", getMTVersion())\n                .header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\")\n                .cookie(\"MT-Token-Wap=\" + iUser.getCookie() + \";MT-Device-ID-Wap=\" + iUser.getDeviceId() + \";\");\n        String body = request.execute().body();\n        JSONObject jsonObject = JSONObject.parseObject(body);\n        if (jsonObject.getInteger(\"code\") != 2000) {\n            String message = jsonObject.getString(\"message\");\n            throw new ServiceException(message);\n        }\n        JSONObject data = jsonObject.getJSONObject(\"data\");\n        Double travelRewardXmy = data.getDouble(\"travelRewardXmy\");\n        //例如 1.95\n        return travelRewardXmy;\n\n    }\n\n    //获取用户页面数据\n    public Map<String, Integer> getUserIsolationPageData(IUser iUser) {\n        //查询小茅运信息\n        String url = \"https://h5.moutai519.com.cn/game/isolationPage/getUserIsolationPageData\";\n        HttpRequest request = HttpUtil.createRequest(Method.GET, url);\n\n        request.header(\"MT-Device-ID\", iUser.getDeviceId())\n                .header(\"MT-APP-Version\", getMTVersion())\n                .header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\")\n                .cookie(\"MT-Token-Wap=\" + iUser.getCookie() + \";MT-Device-ID-Wap=\" + iUser.getDeviceId() + \";\");\n        String body = request.form(\"__timestamp\", DateUtil.currentSeconds()).execute().body();\n\n        JSONObject jsonObject = JSONObject.parseObject(body);\n        if (jsonObject.getInteger(\"code\") != 2000) {\n            String message = jsonObject.getString(\"message\");\n            throw new ServiceException(message);\n        }\n        JSONObject data = jsonObject.getJSONObject(\"data\");\n        //xmy: 小茅运值\n        String xmy = data.getString(\"xmy\");\n        //energy: 耐力值\n        int energy = data.getIntValue(\"energy\");\n        JSONObject xmTravel = data.getJSONObject(\"xmTravel\");\n        JSONObject energyReward = data.getJSONObject(\"energyReward\");\n        //status: 1. 未开始 2. 进行中 3. 已完成\n        Integer status = xmTravel.getInteger(\"status\");\n        // travelEndTime: 旅行结束时间\n        Long travelEndTime = xmTravel.getLong(\"travelEndTime\");\n        //remainChance 今日剩余旅行次数\n        int remainChance = xmTravel.getIntValue(\"remainChance\");\n\n        //可领取申购耐力值奖励\n        Integer energyValue = energyReward.getInteger(\"value\");\n\n        if (energyValue > 0) {\n            //获取申购耐力值\n            getEnergyAward(iUser);\n            energy += energyValue;\n        }\n\n//        本月剩余旅行奖励\n        int exchangeRateInfo = getExchangeRateInfo(iUser);\n        if (exchangeRateInfo <= 0) {\n            String message = \"当月无可领取奖励\";\n            throw new ServiceException(message);\n        }\n\n        Long endTime = travelEndTime * 1000;\n        // 未开始\n        if (status == 1) {\n            if (energy < 100) {\n                String message = String.format(\"耐力不足100, 当前耐力值:%s\", energy);\n                throw new ServiceException(message);\n            }\n        }\n        // 进行中\n        if (status == 2) {\n            Date date = new Date(endTime);\n            SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n            String formattedDate = sdf.format(date);\n            String message = String.format(\"旅行暂未结束,本次旅行结束时间:%s \", formattedDate);\n            throw new ServiceException(message);\n        }\n        Map<String, Integer> map = new HashMap<>();\n\n        map.put(\"remainChance\", remainChance);\n        map.put(\"status\", status);\n        map.put(\"currentPeriodCanConvertXmyNum\", getExchangeRateInfo(iUser));\n        return map;\n    }\n\n    // 获取本月剩余奖励耐力值\n    public int getExchangeRateInfo(IUser iUser) {\n        String url = \"https://h5.moutai519.com.cn/game/synthesize/exchangeRateInfo\";\n        HttpRequest request = HttpUtil.createRequest(Method.GET, url);\n\n        request.header(\"MT-Device-ID\", iUser.getDeviceId())\n                .header(\"MT-APP-Version\", getMTVersion())\n                .header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\")\n                .cookie(\"MT-Token-Wap=\" + iUser.getCookie() + \";MT-Device-ID-Wap=\" + iUser.getDeviceId() + \";\");\n\n        String body = request.form(\"__timestamp\", DateUtil.currentSeconds()).execute().body();\n        JSONObject jsonObject = JSONObject.parseObject(body);\n        if (jsonObject.getInteger(\"code\") != 2000) {\n            String message = jsonObject.getString(\"message\");\n            throw new ServiceException(message);\n        }\n        //获取本月剩余奖励耐力值\n        return jsonObject.getJSONObject(\"data\").getIntValue(\"currentPeriodCanConvertXmyNum\");\n\n    }\n\n    @Async\n    @Override\n    public void reservationBatch() {\n        int minute = DateUtil.minute(new Date());\n        List<IUser> iUsers = iUserService.selectReservationUserByMinute(minute);\n\n        for (IUser iUser : iUsers) {\n            logger.info(\"「开始预约用户」\" + iUser.getMobile());\n            //预约\n            reservation(iUser);\n            //延时3秒\n            try {\n                TimeUnit.SECONDS.sleep(3);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n\n        }\n    }\n\n    @Async\n    @Override\n    public void getTravelRewardBatch() {\n        try {\n            int minute = DateUtil.minute(new Date());\n            List<IUser> iUsers = iUserService.selectReservationUserByMinute(minute);\n//        List<IUser> iUsers = iUserService.selectReservationUser();\n\n            for (IUser iUser : iUsers) {\n                logger.info(\"「开始获得旅行奖励」\" + iUser.getMobile());\n                getTravelReward(iUser);\n                //延时3秒\n                TimeUnit.SECONDS.sleep(3);\n            }\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    @Override\n    public void refreshAll() {\n        refreshMTVersion();\n        iShopService.refreshShop();\n        iShopService.refreshItem();\n    }\n\n    @Override\n    public void appointmentResults() {\n        logger.info(\"申购结果查询开始=========================\");\n        List<IUser> iUsers = iUserService.selectReservationUser();\n        for (IUser iUser : iUsers) {\n            try {\n                String url = \"https://app.moutai519.com.cn/xhr/front/mall/reservation/list/pageOne/query\";\n                String body = HttpUtil.createRequest(Method.GET, url)\n                        .header(\"MT-Device-ID\", iUser.getDeviceId())\n                        .header(\"MT-APP-Version\", getMTVersion())\n                        .header(\"MT-Token\", iUser.getToken())\n                        .header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\").execute().body();\n                JSONObject jsonObject = JSONObject.parseObject(body);\n                logger.info(\"查询申购结果回调: user->{},response->{}\", iUser.getMobile(), body);\n                if (jsonObject.getInteger(\"code\") != 2000) {\n                    String message = jsonObject.getString(\"message\");\n                    throw new ServiceException(message);\n                }\n                JSONArray itemVOs = jsonObject.getJSONObject(\"data\").getJSONArray(\"reservationItemVOS\");\n                if (Objects.isNull(itemVOs) || itemVOs.isEmpty()) {\n                    logger.info(\"申购记录为空: user->{}\", iUser.getMobile());\n                    continue;\n                }\n                for (Object itemVO : itemVOs) {\n                    JSONObject item = JSON.parseObject(itemVO.toString());\n                    // 预约时间在24小时内的\n                    if (item.getInteger(\"status\") == 2 && DateUtil.between(item.getDate(\"reservationTime\"), new Date(), DateUnit.HOUR) < 24) {\n                        String logContent = DateUtil.formatDate(item.getDate(\"reservationTime\")) + \" 申购\" + item.getString(\"itemName\") + \"成功\";\n                        IMTLogFactory.reservation(iUser, logContent);\n                    }\n                }\n            } catch (Exception e) {\n                logger.error(\"查询申购结果失败:失败原因->{}\", e.getMessage(), e);\n            }\n\n        }\n        logger.info(\"申购结果查询结束=========================\");\n    }\n\n    public JSONObject reservation(IUser iUser, String itemId, String shopId) {\n        Map<String, Object> map = new HashMap<>();\n        JSONArray itemArray = new JSONArray();\n        Map<String, Object> info = new HashMap<>();\n        info.put(\"count\", 1);\n        info.put(\"itemId\", itemId);\n\n        itemArray.add(info);\n\n        map.put(\"itemInfoList\", itemArray);\n\n        map.put(\"sessionId\", iShopService.getCurrentSessionId());\n        map.put(\"userId\", iUser.getUserId().toString());\n        map.put(\"shopId\", shopId);\n\n        map.put(\"actParam\", AesEncrypt(JSON.toJSONString(map)));\n\n        HttpRequest request = HttpUtil.createRequest(Method.POST,\n                \"https://app.moutai519.com.cn/xhr/front/mall/reservation/add\");\n\n        request.header(\"MT-Lat\", iUser.getLat());\n        request.header(\"MT-Lng\", iUser.getLng());\n        request.header(\"MT-Token\", iUser.getToken());\n        request.header(\"MT-Info\", \"028e7f96f6369cafe1d105579c5b9377\");\n        request.header(\"MT-Device-ID\", iUser.getDeviceId());\n        request.header(\"MT-APP-Version\", getMTVersion());\n        request.header(\"User-Agent\", \"iOS;16.3;Apple;?unrecognized?\");\n        request.header(\"Content-Type\", \"application/json\");\n        request.header(\"userId\", iUser.getUserId().toString());\n\n        HttpResponse execute = request.body(JSONObject.toJSONString(map)).execute();\n\n        JSONObject body = JSONObject.parseObject(execute.body());\n        //{\"code\":2000,\"data\":{\"successDesc\":\"申购完成，请于7月6日18:00查看预约申购结果\",\"reservationList\":[{\"reservationId\":17053404357,\"sessionId\":678,\"shopId\":\"233331084001\",\"reservationTime\":1688608601720,\"itemId\":\"10214\",\"count\":1}],\"reservationDetail\":{\"desc\":\"申购成功后将以短信形式通知您，请您在申购成功次日18:00前确认支付方式，并在7天内完成提货。\",\"lotteryTime\":1688637600000,\"cacheValidTime\":1688637600000}}}\n        if (body.getInteger(\"code\") != 2000) {\n            String message = body.getString(\"message\");\n            throw new ServiceException(message);\n        }\n//        logger.info(body.toJSONString());\n        return body;\n    }\n\n    /**\n     * 加密\n     *\n     * @param params\n     * @return\n     */\n    public static String AesEncrypt(String params) {\n        AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, AES_KEY.getBytes(), AES_IV.getBytes());\n        return aes.encryptBase64(params);\n    }\n\n    /**\n     * 解密\n     *\n     * @param params\n     * @return\n     */\n    public static String AesDecrypt(String params) {\n        AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, AES_KEY.getBytes(), AES_IV.getBytes());\n        return aes.decryptStr(params);\n    }\n\n    /**\n     * 获取验证码的md5签名，密钥+手机号+时间\n     * 登录的md5签名：密钥+mobile+vCode+ydLogId+ydToken\n     *\n     * @param content\n     * @return\n     */\n    private static String signature(String content, long time) {\n\n        String text = SALT + content + time;\n        String md5 = \"\";\n        try {\n            MessageDigest md = MessageDigest.getInstance(\"MD5\");\n            byte[] hashBytes = md.digest(text.getBytes());\n            StringBuilder sb = new StringBuilder();\n            for (byte b : hashBytes) {\n                sb.append(String.format(\"%02x\", b));\n            }\n            md5 = sb.toString();\n        } catch (NoSuchAlgorithmException e) {\n            e.printStackTrace();\n        }\n        return md5;\n    }\n\n\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/impl/IShopServiceImpl.java",
    "content": "package com.oddfar.campus.business.service.impl;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.http.HttpRequest;\nimport cn.hutool.http.HttpUtil;\nimport cn.hutool.http.Method;\nimport com.alibaba.fastjson2.JSONArray;\nimport com.alibaba.fastjson2.JSONException;\nimport com.alibaba.fastjson2.JSONObject;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.oddfar.campus.business.domain.IMTCacheConstants;\nimport com.oddfar.campus.business.domain.IMTItemInfo;\nimport com.oddfar.campus.business.domain.MapPoint;\nimport com.oddfar.campus.business.entity.IItem;\nimport com.oddfar.campus.business.entity.IShop;\nimport com.oddfar.campus.business.mapper.IItemMapper;\nimport com.oddfar.campus.business.mapper.IShopMapper;\nimport com.oddfar.campus.business.service.IShopService;\nimport com.oddfar.campus.common.core.RedisCache;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\n\nimport java.time.LocalDate;\nimport java.time.ZoneOffset;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n@Service\n@Slf4j\npublic class IShopServiceImpl extends ServiceImpl<IShopMapper, IShop> implements IShopService {\n\n\n    @Autowired\n    IShopMapper iShopMapper;\n    @Autowired\n    IItemMapper iItemMapper;\n\n    @Autowired\n    RedisCache redisCache;\n\n    @Override\n    public List<IShop> selectShopList() {\n\n        List<IShop> shopList = redisCache.getCacheList(IMTCacheConstants.MT_SHOP_LIST);\n\n        if (shopList != null && shopList.size() > 0) {\n            return shopList;\n        } else {\n            refreshShop();\n        }\n\n        shopList = iShopMapper.selectList();\n\n\n        return shopList;\n    }\n\n    //    @Async\n    @Override\n    public void refreshShop() {\n\n        HttpRequest request = HttpUtil.createRequest(Method.GET,\n                \"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/resource/get\");\n\n        JSONObject body = JSONObject.parseObject(request.execute().body());\n        //获取shop的url\n        String shopUrl = body.getJSONObject(\"data\").getJSONObject(\"mtshops_pc\").getString(\"url\");\n        //清空数据库\n        iShopMapper.truncateShop();\n        redisCache.deleteObject(IMTCacheConstants.MT_SHOP_LIST);\n\n        String s = HttpUtil.get(shopUrl);\n\n        JSONObject jsonObject = JSONObject.parseObject(s);\n        Set<String> shopIdSet = jsonObject.keySet();\n        List<IShop> list = new ArrayList<>();\n        for (String iShopId : shopIdSet) {\n            JSONObject shop = jsonObject.getJSONObject(iShopId);\n            IShop iShop = new IShop(iShopId, shop);\n//            iShopMapper.insert(iShop);\n            list.add(iShop);\n        }\n        this.saveBatch(list);\n        redisCache.setCacheList(IMTCacheConstants.MT_SHOP_LIST, list);\n        redisCache.expire(IMTCacheConstants.MT_SHOP_LIST, 2, TimeUnit.HOURS);\n    }\n\n    @Override\n    public String getCurrentSessionId() {\n        String mtSessionId = Convert.toStr(redisCache.getCacheObject(IMTCacheConstants.MT_SESSION_ID));\n\n        long dayTime = LocalDate.now().atStartOfDay().toInstant(ZoneOffset.of(\"+8\")).toEpochMilli();\n        if (StringUtils.isNotEmpty(mtSessionId)) {\n            return mtSessionId;\n        }\n\n        String res = HttpUtil.get(\"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/index/session/get/\" + dayTime);\n        //替换 current_session_id 673 ['data']['sessionId']\n        JSONObject jsonObject = JSONObject.parseObject(res);\n\n        if (jsonObject.getString(\"code\").equals(\"2000\")) {\n            JSONObject data = jsonObject.getJSONObject(\"data\");\n            mtSessionId = data.getString(\"sessionId\");\n            redisCache.setCacheObject(IMTCacheConstants.MT_SESSION_ID, mtSessionId, 2, TimeUnit.HOURS);\n\n            iItemMapper.truncateItem();\n            //item插入数据库\n            JSONArray itemList = data.getJSONArray(\"itemList\");\n            for (Object obj : itemList) {\n                JSONObject item = (JSONObject) obj;\n                IItem iItem = new IItem(item);\n                iItemMapper.insert(iItem);\n            }\n\n        }\n\n        return mtSessionId;\n\n    }\n\n    @Override\n    public void refreshItem() {\n        redisCache.deleteObject(IMTCacheConstants.MT_SESSION_ID);\n        getCurrentSessionId();\n    }\n\n    @Override\n    public IShop selectByIShopId(String iShopId) {\n        List<IShop> iShopList = iShopMapper.selectList(\"i_shop_id\", iShopId);\n        if (iShopList != null && iShopList.size() > 0) {\n            return iShopList.get(0);\n        } else {\n            return null;\n        }\n    }\n\n    @Override\n    public List<IMTItemInfo> getShopsByProvince(String province, String itemId) {\n        String key = \"mt_province:\" + province + \".\" + getCurrentSessionId() + \".\" + itemId;\n        List<IMTItemInfo> cacheList = redisCache.getCacheList(key);\n        if (cacheList != null && cacheList.size() > 0) {\n            return cacheList;\n        } else {\n            List<IMTItemInfo> imtItemInfoList = reGetShopsByProvince(province, itemId);\n            redisCache.reSetCacheList(key, imtItemInfoList);\n            redisCache.expire(key, 60, TimeUnit.MINUTES);\n            return imtItemInfoList;\n        }\n    }\n\n    public List<IMTItemInfo> reGetShopsByProvince(String province, String itemId) {\n\n        long dayTime = LocalDate.now().atStartOfDay().toInstant(ZoneOffset.of(\"+8\")).toEpochMilli();\n\n        String url = \"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/shop/list/slim/v3/\" + getCurrentSessionId() + \"/\" + province + \"/\" + itemId + \"/\" + dayTime;\n\n        String urlRes = HttpUtil.get(url);\n        JSONObject res = null;\n        try {\n            res = JSONObject.parseObject(urlRes);\n        } catch (JSONException jsonException) {\n            String message = StringUtils.format(\"查询所在省市的投放产品和数量error: %s\", url);\n            log.error(message);\n            throw new ServiceException(message);\n        }\n\n//        JSONObject res = JSONObject.parseObject(HttpUtil.get(url));\n        if (!res.containsKey(\"code\") || !res.getString(\"code\").equals(\"2000\")) {\n            String message = StringUtils.format(\"查询所在省市的投放产品和数量error: %s\", url);\n            log.error(message);\n            throw new ServiceException(message);\n        }\n        //组合信息\n        List<IMTItemInfo> imtItemInfoList = new ArrayList<>();\n\n        JSONObject data = res.getJSONObject(\"data\");\n        JSONArray shopList = data.getJSONArray(\"shops\");\n\n        for (Object obj : shopList) {\n            JSONObject shops = (JSONObject) obj;\n            JSONArray items = shops.getJSONArray(\"items\");\n            for (Object item : items) {\n                JSONObject itemObj = (JSONObject) item;\n                if (itemObj.getString(\"itemId\").equals(itemId)) {\n                    IMTItemInfo iItem = new IMTItemInfo(shops.getString(\"shopId\"),\n                            itemObj.getIntValue(\"count\"), itemObj.getString(\"itemId\"), itemObj.getIntValue(\"inventory\"));\n                    //添加\n                    imtItemInfoList.add(iItem);\n                }\n\n            }\n\n\n        }\n        return imtItemInfoList;\n    }\n\n    @Override\n    public String getShopId(int shopType, String itemId, String province, String city, String lat, String lng) {\n        //查询所在省市的投放产品和数量\n        List<IMTItemInfo> shopList = getShopsByProvince(province, itemId);\n        //取id集合\n        List<String> shopIdList = shopList.stream().map(IMTItemInfo::getShopId).collect(Collectors.toList());\n        //获取门店列表\n        List<IShop> iShops = selectShopList();\n        //获取今日的门店信息列表\n        List<IShop> list = iShops.stream().filter(i -> shopIdList.contains(i.getIShopId())).collect(Collectors.toList());\n\n        String shopId = \"\";\n        if (shopType == 1) {\n            //预约本市出货量最大的门店\n            shopId = getMaxInventoryShopId(shopList, list, city);\n            if (StringUtils.isEmpty(shopId)) {\n                //本市没有则预约本省最近的\n                shopId = getMinDistanceShopId(list, province, lat, lng);\n            }\n        } else {\n            //预约本省距离最近的门店\n            shopId = getMinDistanceShopId(list, province, lat, lng);\n        }\n\n//        if (shopType == 2) {\n//            // 预约本省距离最近的门店\n//            shopId = getMinDistanceShopId(list, province, lat, lng);\n//        }\n\n        if (StringUtils.isEmpty(shopId)) {\n            throw new ServiceException(\"申购时根据类型获取的门店商品id为空\");\n        }\n\n\n        return shopId;\n    }\n\n    /**\n     * 预约本市出货量最大的门店\n     *\n     * @param list1\n     * @param list2\n     * @param city\n     * @return\n     */\n    public String getMaxInventoryShopId(List<IMTItemInfo> list1, List<IShop> list2, String city) {\n\n        //本城市的shopId集合\n        List<String> cityShopIdList = list2.stream().filter(iShop -> iShop.getCityName().contains(city))\n                .map(IShop::getIShopId).collect(Collectors.toList());\n\n        List<IMTItemInfo> collect = list1.stream().filter(i -> cityShopIdList.contains(i.getShopId())).sorted(Comparator.comparing(IMTItemInfo::getInventory).reversed()).collect(Collectors.toList());\n\n\n        if (collect != null && collect.size() > 0) {\n            return collect.get(0).getShopId();\n        }\n\n        return null;\n    }\n\n    /**\n     * 预约本省距离最近的门店\n     *\n     * @param list2\n     * @param province\n     * @param lat\n     * @param lng\n     * @return\n     */\n    public String getMinDistanceShopId(List<IShop> list2, String province, String lat, String lng) {\n        //本省的\n        List<IShop> iShopList = list2.stream().filter(iShop -> iShop.getProvinceName().contains(province))\n                .collect(Collectors.toList());\n\n        MapPoint myPoint = new MapPoint(Double.parseDouble(lat), Double.parseDouble(lng));\n        for (IShop iShop : iShopList) {\n            MapPoint point = new MapPoint(Double.parseDouble(iShop.getLat()), Double.parseDouble(iShop.getLng()));\n            Double disdance = getDisdance(myPoint, point);\n            iShop.setDistance(disdance);\n        }\n\n        List<IShop> collect = iShopList.stream().sorted(Comparator.comparing(IShop::getDistance)).collect(Collectors.toList());\n\n        return collect.get(0).getIShopId();\n\n    }\n\n    public static Double getDisdance(MapPoint point1, MapPoint point2) {\n        double lat1 = (point1.getLatitude() * Math.PI) / 180; //将角度换算为弧度\n        double lat2 = (point2.getLatitude() * Math.PI) / 180; //将角度换算为弧度\n        double latDifference = lat1 - lat2;\n        double lonDifference = (point1.getLongitude() * Math.PI) / 180 - (point2.getLongitude() * Math.PI) / 180;\n        //计算两点之间距离   6378137.0 取自WGS84标准参考椭球中的地球长半径(单位:m)\n        return 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(latDifference / 2), 2)\n                + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(lonDifference / 2), 2))) * 6378137.0;\n    }\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/service/impl/IUserServiceImpl.java",
    "content": "package com.oddfar.campus.business.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport com.alibaba.fastjson2.JSONObject;\nimport com.oddfar.campus.business.entity.IUser;\nimport com.oddfar.campus.business.mapper.IUserMapper;\nimport com.oddfar.campus.business.service.IUserService;\nimport com.oddfar.campus.common.domain.PageResult;\nimport com.oddfar.campus.common.exception.ServiceException;\nimport com.oddfar.campus.common.utils.SecurityUtils;\nimport com.oddfar.campus.common.utils.StringUtils;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\nimport java.util.UUID;\n\n@Service\npublic class IUserServiceImpl implements IUserService {\n    @Autowired\n    private IUserMapper iUserMapper;\n\n    @Override\n    public PageResult<IUser> page(IUser iUser) {\n        Long userId = SecurityUtils.getUserId();\n        if (userId != 1) {\n            return iUserMapper.selectPage(iUser, userId);\n        }\n        return iUserMapper.selectPage(iUser);\n    }\n\n    @Override\n    public int insertIUser(Long mobile, String deviceId, JSONObject jsonObject) {\n        JSONObject data = jsonObject.getJSONObject(\"data\");\n\n        IUser user = iUserMapper.selectById(mobile);\n\n        if (user != null) {\n            //存在则更新\n            IUser iUser = new IUser(mobile, jsonObject);\n            iUser.setCreateUser(SecurityUtils.getUserId());\n            BeanUtil.copyProperties(iUser, user, \"shopType\", \"minute\");\n            return iUserMapper.updateById(user);\n        } else {\n            if (StringUtils.isEmpty(deviceId)) {\n                deviceId = UUID.randomUUID().toString().toLowerCase();\n            }\n            IUser iUser = new IUser(mobile, deviceId, jsonObject);\n            iUser.setCreateUser(SecurityUtils.getUserId());\n            return iUserMapper.insert(iUser);\n        }\n\n\n    }\n\n    @Override\n    public List<IUser> selectReservationUser() {\n        return iUserMapper.selectReservationUser();\n\n    }\n\n    @Override\n    public List<IUser> selectReservationUserByMinute(int minute) {\n        return iUserMapper.selectReservationUserByMinute(minute);\n    }\n\n    @Override\n    public int insertIUser(IUser iUser) {\n\n        IUser user = iUserMapper.selectById(iUser.getMobile());\n        if (user != null) {\n            throw new ServiceException(\"禁止重复添加\");\n        }\n\n        if (StringUtils.isEmpty(iUser.getDeviceId())) {\n            iUser.setDeviceId(UUID.randomUUID().toString().toLowerCase());\n        }\n        iUser.setCreateUser(SecurityUtils.getUserId());\n        return iUserMapper.insert(iUser);\n    }\n\n    @Override\n    public int updateIUser(IUser iUser) {\n        if (SecurityUtils.getUserId() != 1 && !iUser.getCreateUser().equals(SecurityUtils.getUserId())) {\n            throw new ServiceException(\"只能修改自己创建的用户\");\n        }\n        return iUserMapper.updateById(iUser);\n    }\n\n    @Override\n    @Async\n    public void updateUserMinuteBatch() {\n        Long userCount = iUserMapper.selectCount();\n        if (userCount > 60) {\n            iUserMapper.updateUserMinuteEven();\n        }else {\n            iUserMapper.updateUserMinuteBatch();\n        }\n    }\n\n    @Override\n    public int deleteIUser(Long[] iUserId) {\n        return iUserMapper.deleteIUser(iUserId);\n    }\n}\n"
  },
  {
    "path": "campus-modular/src/main/java/com/oddfar/campus/business/task/CampusIMTTask.java",
    "content": "package com.oddfar.campus.business.task;\n\nimport com.oddfar.campus.business.service.IMTService;\nimport com.oddfar.campus.business.service.IUserService;\nimport lombok.RequiredArgsConstructor;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.scheduling.annotation.EnableScheduling;\nimport org.springframework.scheduling.annotation.Scheduled;\n\n/**\n * i茅台定时任务\n */\n@Configuration\n@EnableScheduling\n@RequiredArgsConstructor\npublic class CampusIMTTask {\n    private static final Logger logger = LoggerFactory.getLogger(CampusIMTTask.class);\n\n    private final IMTService imtService;\n\n    private final IUserService iUserService;\n\n\n    /**\n     * 1：10 批量修改用户随机预约的时间\n     */\n    @Async\n    @Scheduled(cron = \"0 10 1 ? * * \")\n    public void updateUserMinuteBatch() {\n        iUserService.updateUserMinuteBatch();\n    }\n\n\n    /**\n     * 11点期间，每分钟执行一次批量获得旅行奖励\n     */\n    @Async\n    @Scheduled(cron = \"0 0/1 11 ? * *\")\n    public void getTravelRewardBatch() {\n        imtService.getTravelRewardBatch();\n\n    }\n\n    /**\n     * 9点期间，每分钟执行一次\n     */\n    @Async\n    @Scheduled(cron = \"0 0/1 9 ? * *\")\n    public void reservationBatchTask() {\n        imtService.reservationBatch();\n\n    }\n\n\n    @Async\n    @Scheduled(cron = \"0 10,55 7,8 ? * * \")\n    public void refresh() {\n        logger.info(\"「刷新数据」开始刷新版本号，预约item，门店shop列表  \");\n        try {\n            imtService.refreshAll();\n        } catch (Exception e) {\n            logger.info(\"「刷新数据执行报错」%s\", e.getMessage());\n        }\n\n    }\n\n\n    /**\n     * 18.05分获取申购结果\n     */\n    @Async\n    @Scheduled(cron = \"0 5 18 ? * * \")\n    public void appointmentResults() {\n        imtService.appointmentResults();\n    }\n\n\n}"
  },
  {
    "path": "campus-modular/src/main/resources/application-dev.yml",
    "content": "--- # 数据源配置\nspring:\n  #数据源配置\n  datasource:\n    type: com.zaxxer.hikari.HikariDataSource\n    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content\n    dynamic:\n      # 性能分析插件(有性能损耗 不建议生产环境使用)\n      # 文档 https://baomidou.com/pages/833fab/\n      p6spy: true\n      # 设置默认的数据源或者数据源组,默认值即为 master\n      primary: master\n      # 严格模式 匹配不到数据源则报错\n      strict: true\n      datasource:\n        # 主库数据源\n        master:\n          type: ${spring.datasource.type}\n          driverClassName: com.mysql.cj.jdbc.Driver\n          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562\n          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)\n          url: jdbc:mysql://localhost:3306/campus_imaotai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true\n          username: root\n          password: 123456789\n        # 从库数据源\n      #        slave:\n      #          lazy: true\n      #          type: ${spring.datasource.type}\n      #          driverClassName: com.mysql.cj.jdbc.Driver\n      #          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true\n      #          username: root\n      #          password: 123456789\n      hikari:\n        # 最大连接池数量\n        maxPoolSize: 20\n        # 最小空闲线程数量\n        minIdle: 10\n        # 配置获取连接等待超时的时间\n        connectionTimeout: 30000\n        # 校验超时时间\n        validationTimeout: 5000\n        # 空闲连接存活最大时间，默认10分钟\n        idleTimeout: 600000\n        # 此属性控制池中连接的最长生命周期，值0表示无限生命周期，默认30分钟\n        maxLifetime: 1800000\n        # 连接测试query（配置检测连接是否有效）\n        connectionTestQuery: SELECT 1\n        # 多久检查一次连接的活性\n        keepaliveTime: 30000\n\n\n--- # redis\nspring:\n  redis:\n    # 地址\n    host: localhost\n    # 端口，默认为6379\n    port: 6379\n    # 数据库索引\n    database: 0\n    # 密码(如没有密码请注释掉)\n    # password:\n    # 连接超时时间\n    timeout: 10s"
  },
  {
    "path": "campus-modular/src/main/resources/application-prod.yml",
    "content": "--- # 数据源配置\nspring:\n  #数据源配置\n  datasource:\n    type: com.zaxxer.hikari.HikariDataSource\n    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content\n    dynamic:\n      # 性能分析插件(有性能损耗 不建议生产环境使用)\n      p6spy: false\n      # 设置默认的数据源或者数据源组,默认值即为 master\n      primary: master\n      # 严格模式 匹配不到数据源则报错\n      strict: true\n      datasource:\n        # 主库数据源\n        master:\n          type: ${spring.datasource.type}\n          driverClassName: com.mysql.cj.jdbc.Driver\n          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562\n          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)\n          url: jdbc:mysql://localhost:3306/campus_imaotai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true\n          username: root\n          password: 123456789\n        # 从库数据源\n      #        slave:\n      #          lazy: true\n      #          type: ${spring.datasource.type}\n      #          driverClassName: com.mysql.cj.jdbc.Driver\n      #          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true\n      #          username: root\n      #          password: 123456789\n      hikari:\n        # 最大连接池数量\n        maxPoolSize: 20\n        # 最小空闲线程数量\n        minIdle: 10\n        # 配置获取连接等待超时的时间\n        connectionTimeout: 30000\n        # 校验超时时间\n        validationTimeout: 5000\n        # 空闲连接存活最大时间，默认10分钟\n        idleTimeout: 600000\n        # 此属性控制池中连接的最长生命周期，值0表示无限生命周期，默认30分钟\n        maxLifetime: 1800000\n        # 连接测试query（配置检测连接是否有效）\n        connectionTestQuery: SELECT 1\n        # 多久检查一次连接的活性\n        keepaliveTime: 30000\n\n\n--- # redis\nspring:\n  redis:\n    # 地址\n    host: localhost\n    # 端口，默认为6379\n    port: 6379\n    # 数据库索引\n    database: 0\n    # 密码(如没有密码请注释掉)\n    # password:\n    # 连接超时时间\n    timeout: 10s"
  },
  {
    "path": "campus-modular/src/main/resources/application.yml",
    "content": "# 项目相关配置\ncampus:\n  # 名称\n  name: campus-imaotai\n  # 版本\n  version: ${revision}\n  frameworkVersion: ${campus.revision}\n\nserver:\n  port: 8160\n\n\nspring:\n  application:\n    name: ${campus.name}\n  profiles:\n    active: @profiles.active@\n  # 文件上传\n  servlet:\n    multipart:\n      # 单个文件大小\n      max-file-size: 20MB\n      # 设置总上传的文件大小\n      max-request-size: 20MB\n  #MyWebMvcConfig中开启@EnableWebMvc则失效\n  mvc:\n    format:\n      date-time: yyyy-MM-dd HH:mm:ss\n  jackson:\n    date-format: yyyy-MM-dd HH:mm:ss\n    #    time-zone: GMT+8\n    serialization:\n      # 格式化输出\n      indent_output: false\n      # 忽略无法转换的对象\n      fail_on_empty_beans: false\n    deserialization:\n      # 允许对象忽略json中不存在的属性\n      fail_on_unknown_properties: false\n\n  # 资源信息\n  messages:\n    # 国际化资源文件路径\n    basename: i18n/messages\n\n# token配置\ntoken:\n  # 令牌自定义标识\n  header: Authorization\n  # 令牌密钥\n  secret: abcdefghijklmnopqrstuvwxyz\n  # 令牌有效期（单位分钟）\n  expireTime: 1440\n\n# 用户配置\nuser:\n  password:\n    # 密码最大错误次数\n    maxRetryCount: 5\n    # 密码锁定时间（默认10分钟）\n    lockTime: 10\n\n# 日志配置\nlogging:\n  level:\n    com.oddfar: debug\n    org.springframework: warn\n\n# MyBatisPlus配置\n# https://baomidou.com/pages/56bac0/\nmybatis-plus:\n  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级\n  # 例如 com.**.**.mapper\n  mapperPackage: com.oddfar.**.mapper\n  # 配置mapper的扫描，找到所有的mapper.xml映射文件\n  mapperLocations: classpath*:mapper/**/*Mapper.xml\n  # 实体扫描，多个package用逗号或者分号分隔\n  typeAliasesPackage: com.oddfar.**.domain\n  configuration:\n    # 自动驼峰命名规则（camel case）映射\n    mapUnderscoreToCamelCase: true\n    # MyBatis 自动映射策略\n    # NONE：不启用 PARTIAL：只对非嵌套 resultMap 自动映射 FULL：对所有 resultMap 自动映射\n    autoMappingBehavior: FULL\n    # MyBatis 自动映射时未知列或未知属性处理策\n    # NONE：不做处理 WARNING：打印相关警告 FAILING：抛出异常和详细信息\n    autoMappingUnknownColumnBehavior: NONE\n    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl\n    # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl\n    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl\n    logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl\n  global-config:\n    #逻辑删除\n    dbConfig:\n      logic-delete-value: 1\n      logic-not-delete-value: 0\n\n\n\n# Swagger配置\nswagger:\n  # 是否开启swagger\n  enabled: true"
  },
  {
    "path": "campus-modular/src/main/resources/i18n/messages.properties",
    "content": "#错误消息\nnot.null=* 必须填写\nuser.jcaptcha.error=验证码错误\nuser.jcaptcha.expire=验证码已失效\nuser.not.exists=用户不存在/密码错误\nuser.password.not.match=用户不存在/密码错误\nuser.password.retry.limit.count=密码输入错误{0}次\nuser.password.retry.limit.exceed=密码输入错误{0}次，帐户锁定{1}分钟\nuser.password.delete=对不起，您的账号已被删除\nuser.blocked=用户已封禁，请联系管理员\nrole.blocked=角色已封禁，请联系管理员\nuser.logout.success=退出成功\n\nlength.not.valid=长度必须在{min}到{max}个字符之间\n\nuser.username.not.valid=* 2到20个汉字、字母、数字或下划线组成，且必须以非数字开头\nuser.password.not.valid=* 5-50个字符\n\nuser.email.not.valid=邮箱格式错误\nuser.mobile.phone.number.not.valid=手机号格式错误\nuser.login.success=登录成功\nuser.register.success=注册成功\nuser.notfound=请重新登录\nuser.forcelogout=管理员强制退出，请重新登录\nuser.unknown.error=未知错误，请重新登录\n\n##文件上传消息\nupload.exceed.maxSize=上传的文件大小超出限制的文件大小！<br/>允许的文件最大大小是：{0}MB！\nupload.filename.exceed.length=上传的文件名最长{0}个字符\n\n##权限\nno.permission=您没有数据的权限，请联系管理员添加权限 [{0}]\nno.create.permission=您没有创建数据的权限，请联系管理员添加权限 [{0}]\nno.update.permission=您没有修改数据的权限，请联系管理员添加权限 [{0}]\nno.delete.permission=您没有删除数据的权限，请联系管理员添加权限 [{0}]\nno.export.permission=您没有导出数据的权限，请联系管理员添加权限 [{0}]\nno.view.permission=您没有查看数据的权限，请联系管理员添加权限 [{0}]\n"
  },
  {
    "path": "campus-modular/src/main/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n    <!-- 日志存放路径 -->\n    <property name=\"log.path\" value=\"~/logs\"/>\n    <!-- 日志输出格式 -->\n    <property name=\"log.pattern\" value=\"%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n\"/>\n\n    <!-- 彩色日志 -->\n    <!-- 配置格式变量：CONSOLE_LOG_PATTERN 彩色日志格式 -->\n    <!-- magenta:洋红 -->\n    <!-- boldMagenta:粗红-->\n    <!-- cyan:青色 -->\n    <!-- white:白色 -->\n    <!-- magenta:洋红 -->\n    <property name=\"CONSOLE_LOG_PATTERN\"\n              value=\"%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level)  |%blue(%file:%line) |%cyan(%msg%n)\"/>\n\n\n    <!-- 控制台输出 -->\n    <appender name=\"console\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>${CONSOLE_LOG_PATTERN}</pattern>\n        </encoder>\n    </appender>\n\n    <!-- 系统日志输出 -->\n    <appender name=\"file_info\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/sys-info.log</file>\n        <!-- 循环政策：基于时间创建日志文件 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- 日志文件名格式 -->\n            <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>\n            <!-- 日志最大的历史 60天 -->\n            <maxHistory>60</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>${log.pattern}</pattern>\n        </encoder>\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <!-- 过滤的级别 -->\n            <level>INFO</level>\n            <!-- 匹配时的操作：接收（记录） -->\n            <onMatch>ACCEPT</onMatch>\n            <!-- 不匹配时的操作：拒绝（不记录） -->\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <appender name=\"file_error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/sys-error.log</file>\n        <!-- 循环政策：基于时间创建日志文件 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- 日志文件名格式 -->\n            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>\n            <!-- 日志最大的历史 60天 -->\n            <maxHistory>60</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>${log.pattern}</pattern>\n        </encoder>\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <!-- 过滤的级别 -->\n            <level>ERROR</level>\n            <!-- 匹配时的操作：接收（记录） -->\n            <onMatch>ACCEPT</onMatch>\n            <!-- 不匹配时的操作：拒绝（不记录） -->\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 用户访问日志输出  -->\n    <appender name=\"sys-user\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <file>${log.path}/sys-user.log</file>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- 按天回滚 daily -->\n            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>\n            <!-- 日志最大的历史 60天 -->\n            <maxHistory>60</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>${log.pattern}</pattern>\n        </encoder>\n    </appender>\n\n    <!-- 系统模块日志级别控制  -->\n    <logger name=\"com.oddfar\" level=\"info\"/>\n    <!-- Spring日志级别控制  -->\n    <logger name=\"org.springframework\" level=\"warn\"/>\n\n    <!--系统操作日志-->\n    <root level=\"info\">\n        <appender-ref ref=\"console\"/>\n        <appender-ref ref=\"file_info\"/>\n        <appender-ref ref=\"file_error\"/>\n    </root>\n\n    <!--系统用户操作日志-->\n    <logger name=\"sys-user\" level=\"info\">\n        <appender-ref ref=\"sys-user\"/>\n    </logger>\n</configuration> "
  },
  {
    "path": "campus-modular/src/main/resources/mapper/IItemMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.business.mapper.IItemMapper\">\n\n</mapper>\n"
  },
  {
    "path": "campus-modular/src/main/resources/mapper/ILogMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.business.mapper.ILogMapper\">\n\n</mapper>\n"
  },
  {
    "path": "campus-modular/src/main/resources/mapper/IShopMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.business.mapper.IShopMapper\">\n\n</mapper>\n"
  },
  {
    "path": "campus-modular/src/main/resources/mapper/IUserMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.oddfar.campus.business.mapper.IUserMapper\">\n\n    <delete id=\"deleteIUser\" parameterType=\"Long\">\n        delete from i_user where mobile in\n        <foreach item=\"id\" collection=\"array\" open=\"(\" separator=\",\" close=\")\">\n            #{id}\n        </foreach>\n    </delete>\n</mapper>\n"
  },
  {
    "path": "campus-modular/src/main/resources/spy.properties",
    "content": "# p6spy 性能分析插件配置文件\nmodulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory\n# 自定义日志打印\nlogMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger\n#日志输出到控制台\nappender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger\n# 使用日志系统记录 sql\n#appender=com.p6spy.engine.spy.appender.Slf4JLogger\n# 设置 p6spy driver 代理\n#deregisterdrivers=true\n# 取消JDBC URL前缀\nuseprefix=true\n# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.\nexcludecategories=info,debug,result,commit,resultset\n# 日期格式\ndateformat=yyyy-MM-dd HH:mm:ss\n# SQL语句打印时间格式\ndatabaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss\n# 实际驱动可多个\n#driverlist=org.h2.Driver\n# 是否开启慢SQL记录\noutagedetection=true\n# 慢SQL记录标准 2 秒\noutagedetectioninterval=2\n# 是否过滤 Log\nfilter=true\n# 过滤 Log 时所排除的 sql 关键字，以逗号分隔\nexclude=SELECT 1"
  },
  {
    "path": "doc/docker/docker-compose.yml",
    "content": "version : '3'\n\nservices:\n  mysql:\n    image: mysql:5.7\n    container_name: mysql\n    environment:\n      # root 密码\n      MYSQL_ROOT_PASSWORD: 123456789\n      # 初始化数据库(后续的初始化sql会在这个库执行)\n      MYSQL_DATABASE: 'campus_imaotai'\n    volumes:\n      # 数据挂载\n      - /docker/mysql/data/:/var/lib/mysql/\n      # 配置挂载\n      - /docker/mysql/conf/:/etc/mysql/conf.d/\n      # 日志\n      - /docker/mysql/logs:/logs\n    command: [\n          'mysqld',\n          '--innodb-buffer-pool-size=80M',\n          '--character-set-server=utf8mb4',\n          '--collation-server=utf8mb4_unicode_ci',\n          '--default-time-zone=+8:00',\n          '--lower-case-table-names=1'\n        ]\n    privileged: true\n    network_mode: \"host\"\n\n  redis:\n    image: redis:6.2.12\n    container_name: redis\n    volumes:\n      # 配置文件\n      - /docker/redis/conf:/redis/config\n      # 数据文件\n      - /docker/redis/data/:/redis/data/\n    command: \"redis-server /redis/config/redis.conf\"\n    privileged: true\n    network_mode: \"host\"\n\n\n  nginx-web:\n    image: nginx:1.23.4\n    container_name: nginx-web\n    environment:\n      # 时区上海\n      TZ: Asia/Shanghai\n    volumes:\n      # 页面目录\n      - /docker/nginx/html:/usr/share/nginx/html\n      # 证书映射\n      - /docker/nginx/cert:/etc/nginx/cert\n      # 配置文件映射\n      - /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf\n      # 日志目录\n      - /docker/nginx/log:/var/log/nginx\n    privileged: true\n    network_mode: \"host\"\n\n  campus-server:\n    image: campus/campus-imaotai:1.0.13\n    container_name: campus-imaotai\n    environment:\n      # 时区上海\n      TZ: Asia/Shanghai\n      SERVER_PORT: 8160\n      spring.config.additional-location: /home/campus/conf/application-prod.yml\n    volumes:\n      - /docker/server/conf:/home/campus/conf\n    privileged: true\n    network_mode: \"host\"\n"
  },
  {
    "path": "doc/docker/nginx/conf/nginx.conf",
    "content": "worker_processes  1;\n\nevents {\n    worker_connections  1024;\n}\n\nhttp {\n    include       mime.types;\n    default_type  application/octet-stream;\n    sendfile        on;\n    keepalive_timeout  65;\n\n    server {\n        listen       80;\n        server_name  127.0.0.1;\n\n        # https配置参考 start\n        #listen       443 ssl;\n\n        # 证书直接存放 /docker/nginx/cert/ 目录下即可 更改证书名称即可 无需更改证书路径\n        #ssl on;\n        #ssl_certificate      /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ 为docker映射路径 不允许更改\n        #ssl_certificate_key  /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为docker映射路径 不允许更改\n        #ssl_session_timeout 5m;\n        #ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;\n        #ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n        #ssl_prefer_server_ciphers on;\n        # https配置参考 end\n\n\t\tlocation / {\n            root   /usr/share/nginx/html;\n\t\t\ttry_files $uri $uri/ /index.html;\n            index  index.html index.htm;\n        }\n\n\t\tlocation /prod-api/{\n\t\t\tproxy_set_header Host $http_host;\n\t\t\tproxy_set_header X-Real-IP $remote_addr;\n\t\t\tproxy_set_header REMOTE-HOST $remote_addr;\n\t\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\t\t\tproxy_pass http://127.0.0.1:8160/;\n\t\t}\n\n        error_page   500 502 503 504  /50x.html;\n        location = /50x.html {\n            root   html;\n        }\n    }\n}\n"
  },
  {
    "path": "doc/docker/redis/conf/redis.conf",
    "content": "# redis 密码\n# requirepass 123456"
  },
  {
    "path": "doc/docker/redis/data/README.md",
    "content": "数据目录 请执行 `chmod 777 /docker/redis/data` 赋予读写权限 否则将无法写入数据"
  },
  {
    "path": "doc/docker/server/conf/README.md",
    "content": "若您需要挂载使用外部配置文件\n\n在 `/docker/server/conf` 创建 `application-prod.yml` 文件\n\n并 `Environment` 加 `spring.config.additional-location: /home/campus/conf/application-prod.yml`\n"
  },
  {
    "path": "doc/sql/campus_imaotai-1.0.5.sql",
    "content": "/*\n Date: 02/08/2023 17:33:32\n*/\n\nSET NAMES utf8;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for i_item\n-- ----------------------------\nDROP TABLE IF EXISTS `i_item`;\nCREATE TABLE `i_item` (\n                          `item_id` bigint DEFAULT NULL COMMENT 'id',\n                          `item_code` varchar(30) DEFAULT NULL COMMENT '预约商品编码',\n                          `title` varchar(50) DEFAULT NULL COMMENT '标题',\n                          `content` varchar(255) DEFAULT NULL COMMENT '内容\\n',\n                          `picture` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '图片url',\n                          `create_time` datetime DEFAULT NULL COMMENT '创建时间'\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='I茅台预约商品列表';\n\n-- ----------------------------\n-- Records of i_item\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for i_log\n-- ----------------------------\nDROP TABLE IF EXISTS `i_log`;\nCREATE TABLE `i_log` (\n                         `log_id` bigint NOT NULL COMMENT '主键',\n                         `mobile` bigint DEFAULT NULL COMMENT '操作人员',\n                         `log_content` varchar(2000) DEFAULT NULL COMMENT '日志记录内容',\n                         `status` int DEFAULT '0' COMMENT '操作状态（0正常 1异常）',\n                         `oper_time` datetime DEFAULT NULL COMMENT '操作时间',\n                         `create_user` bigint DEFAULT NULL COMMENT '创建人',\n                         PRIMARY KEY (`log_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;\n\n\n-- ----------------------------\n-- Table structure for i_shop\n-- ----------------------------\nDROP TABLE IF EXISTS `i_shop`;\nCREATE TABLE `i_shop` (\n                          `shop_id` bigint NOT NULL COMMENT 'ID',\n                          `i_shop_id` varchar(255) DEFAULT NULL COMMENT '商品ID',\n                          `province_name` varchar(50) DEFAULT NULL COMMENT '省份',\n                          `city_name` varchar(50) DEFAULT NULL COMMENT '城市',\n                          `district_name` varchar(50) DEFAULT NULL COMMENT '地区',\n                          `full_address` varchar(255) DEFAULT NULL COMMENT '完整地址',\n                          `lat` varchar(50) DEFAULT NULL COMMENT '纬度',\n                          `lng` varchar(50) DEFAULT NULL COMMENT '经度',\n                          `name` varchar(255) DEFAULT NULL COMMENT '名称',\n                          `tenant_name` varchar(255) DEFAULT NULL COMMENT '公司名称',\n                          `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n                          PRIMARY KEY (`shop_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;\n\n-- ----------------------------\n-- Records of i_shop\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for i_user\n-- ----------------------------\nDROP TABLE IF EXISTS `i_user`;\nCREATE TABLE `i_user` (\n                          `mobile` bigint NOT NULL COMMENT 'I茅台手机号',\n                          `user_id` bigint DEFAULT NULL COMMENT 'I茅台用户id',\n                          `token` varchar(255) DEFAULT NULL COMMENT 'I茅台toekn',\n                          `cookie` varchar(255) DEFAULT NULL COMMENT 'I茅台cookie',\n                          `device_id` varchar(50) DEFAULT NULL COMMENT '设备id',\n                          `item_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '商品预约code，用@间隔',\n                          `ishop_id` varchar(50) DEFAULT NULL COMMENT '门店商品id',\n                          `province_name` varchar(50) DEFAULT NULL COMMENT '省份',\n                          `city_name` varchar(50) DEFAULT NULL COMMENT '城市',\n                          `address` varchar(255) DEFAULT NULL COMMENT '完整地址',\n                          `lat` varchar(50) DEFAULT NULL COMMENT '纬度',\n                          `lng` varchar(50) DEFAULT NULL COMMENT '经度',\n                          `minute` int DEFAULT '5' COMMENT '预约的分钟（0-59）',\n                          `shop_type` int DEFAULT '1' COMMENT '1:预约本市出货量最大的门店;2:预约你的位置(经纬度)附近门店;',\n                          `random_minute` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '0' COMMENT '随机分钟预约，9点取一个时间（0:随机，1:不随机）',\n                          `push_plus_token` varchar(50) DEFAULT NULL COMMENT 'push_plus_token',\n                          `json_result` varchar(2000) DEFAULT NULL COMMENT '返回参数',\n                          `remark` varchar(255) DEFAULT NULL COMMENT '备注',\n                          `expire_time` datetime DEFAULT NULL COMMENT '到期时间',\n                          `del_flag` bit(1) DEFAULT b'0' COMMENT '逻辑删除(1:已删除，0:未删除)',\n                          `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n                          `create_user` bigint DEFAULT NULL COMMENT '创建人',\n                          `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n                          `update_user` bigint DEFAULT NULL COMMENT '更新人',\n                          PRIMARY KEY (`mobile`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='I茅台用户表';\n\n\n-- ----------------------------\n-- Table structure for sys_config\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_config`;\nCREATE TABLE `sys_config` (\n                              `config_id` bigint NOT NULL AUTO_INCREMENT COMMENT '参数主键',\n                              `config_name` varchar(100) DEFAULT '' COMMENT '参数名称',\n                              `config_key` varchar(100) DEFAULT '' COMMENT '参数键名',\n                              `config_value` varchar(500) DEFAULT '' COMMENT '参数键值',\n                              `config_type` char(1) DEFAULT 'N' COMMENT '系统内置（Y是 N否）',\n                              `group_code` varchar(100) DEFAULT NULL COMMENT '所属分类的编码',\n                              `remark` varchar(500) DEFAULT NULL COMMENT '备注',\n                              `del_flag` bit(1) DEFAULT b'0' COMMENT '逻辑删除(1:已删除，0:未删除)',\n                              `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n                              `create_user` bigint DEFAULT NULL COMMENT '创建人',\n                              `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n                              `update_user` bigint DEFAULT NULL COMMENT '更新人',\n                              PRIMARY KEY (`config_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1666438862429286403 DEFAULT CHARSET=utf8mb3 COMMENT='参数配置表';\n\n-- ----------------------------\n-- Records of sys_config\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (1, 'Mac本地文件路径', 'sys.local.profile.mac', '~/uploadPath', 'Y', 'file_config', NULL, b'0', '2022-11-10 14:06:44', 1, '2022-11-10 14:07:49', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (2, 'Linux本地文件路径', 'sys.local.profile.linux', '/data/uploadPath', 'Y', 'file_config', NULL, b'0', '2022-01-14 10:59:39', NULL, '2022-01-16 14:11:53', 1);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (3, 'Windows本地文件路径', 'sys.local.profile.win', 'D:\\\\uploadPath', 'Y', 'file_config', NULL, b'0', '2022-01-14 11:00:39', NULL, '2022-01-16 14:11:53', 1);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (4, '默认存储文件的bucket名称', 'sys.file.default.bucket', 'defaultBucket', 'Y', 'file_config', NULL, b'0', '2022-01-14 11:03:10', NULL, '2022-01-16 14:11:54', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (101, '阿里云邮件服务accessKeyId', 'sys.aliyun.mail.accessKeyId', '', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:04:08', NULL, '2022-01-19 10:49:30', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (102, '阿里云邮件服务accessKeySecret', 'sys.aliyun.mail.accessKeySecret', '', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:07:28', NULL, '2022-01-19 10:49:31', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (113, 'smtp服务器地址', 'sys.email.smtp.host', 'smtp.qq.com', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:33:50', NULL, '2022-01-24 11:28:13', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (114, 'smtp服务端口', 'sys.email.smtp.port', '465', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:35:29', NULL, '2022-01-24 11:28:14', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (115, '邮箱的发送方邮箱', 'sys.email.send.account', '3066693006@qq.com', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:38:17', NULL, '2022-01-24 11:28:15', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (116, '邮箱的密码或者授权码', 'sys.email.password', '**********', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:07:31', NULL, '2022-01-19 12:02:57', 1);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (117, '邮箱发送时的用户名', 'sys.email.name', '致远', 'Y', 'mail_config', NULL, b'0', '2022-01-19 11:10:47', NULL, '2022-01-24 11:28:19', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (202, '用户默认头像', 'sys.user.default.avatar', 'https://img0.baidu.com/it/u=1183896628,1403534286&fm=253&fmt=auto&app=138&f=PNG', 'Y', 'sys_config', NULL, b'0', '2022-02-08 11:35:31', NULL, '2022-02-08 11:40:15', 1);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (206, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'sys_config', '初始化密码 123456', b'0', '2022-11-09 01:41:52', 1, '2022-11-09 15:42:09', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (220, '全局日志记录', 'sys.log.global.flag', 'false', 'Y', 'sys_config', '全局日志记录，true则所有请求都将记录日志', b'0', '2023-06-07 21:36:00', 1, '2023-06-08 14:44:00', 1);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (300, '验证码类型', 'sys.login.captchaType', 'math', 'Y', 'sys_config', 'math 数组计算 char 字符验证', b'0', '2022-11-10 09:32:40', 1, '2022-11-30 12:14:30', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (301, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'sys_config', '是否开启验证码功能（true开启，false关闭）', b'0', '2023-02-01 21:48:05', 1, '2023-02-01 21:48:34', NULL);\nINSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (302, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'true', 'Y', 'sys_config', '是否开启注册用户功能（true开启，false关闭）', b'0', '2023-02-01 21:47:39', 1, '2023-02-01 21:48:31', NULL);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_dict_data\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_dict_data`;\nCREATE TABLE `sys_dict_data` (\n                                 `dict_code` bigint NOT NULL AUTO_INCREMENT COMMENT '字典编码',\n                                 `dict_sort` int DEFAULT '0' COMMENT '字典排序',\n                                 `dict_label` varchar(100) DEFAULT '' COMMENT '字典标签',\n                                 `dict_value` varchar(100) DEFAULT '' COMMENT '字典键值',\n                                 `dict_type` varchar(100) DEFAULT '' COMMENT '字典类型',\n                                 `css_class` varchar(100) DEFAULT NULL COMMENT '样式属性（其他样式扩展）',\n                                 `list_class` varchar(100) DEFAULT NULL COMMENT '表格回显样式',\n                                 `is_default` char(1) DEFAULT 'N' COMMENT '是否默认（Y是 N否）',\n                                 `status` char(1) DEFAULT '0' COMMENT '状态（0正常 1停用）',\n                                 `remark` varchar(500) DEFAULT NULL COMMENT '备注',\n                                 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n                                 `create_user` bigint DEFAULT NULL COMMENT '创建者',\n                                 `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',\n                                 `update_user` bigint DEFAULT NULL COMMENT '更新者',\n                                 `del_flag` bit(1) DEFAULT b'0' COMMENT '逻辑删除(1:已删除，0:未删除)',\n                                 PRIMARY KEY (`dict_code`)\n) ENGINE=InnoDB AUTO_INCREMENT=1666438168611713026 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='字典数据表';\n\n-- ----------------------------\n-- Records of sys_dict_data\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (1, 1, '男', '0', 'sys_user_sex', NULL, 'default', 'Y', '0', '性别男', NULL, NULL, NULL, NULL, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (2, 2, '女', '1', 'sys_user_sex', NULL, 'default', 'N', '0', '性别女', NULL, NULL, NULL, NULL, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (3, 1, '是', 'Y', 'sys_yes_no', NULL, 'success', 'N', '0', NULL, '2022-11-06 06:37:31', 1, '2022-11-06 06:39:34', 1, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (4, 2, '否', 'N', 'sys_yes_no', NULL, 'danger', 'N', '0', NULL, '2022-11-06 06:37:42', 1, '2022-11-06 06:39:34', 1, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (6, 1, '正常', '0', 'sys_normal_disable', NULL, 'primary', 'Y', '0', '正常状态', NULL, NULL, NULL, NULL, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (7, 2, '停用', '1', 'sys_normal_disable', NULL, 'danger', 'N', '0', '停用状态', NULL, NULL, NULL, NULL, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (41, 1, '显示', '0', 'sys_show_hide', NULL, 'primary', 'N', '0', '显示菜单', '2022-12-26 21:49:47', 1, '2022-12-26 21:49:47', NULL, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (42, 2, '隐藏', '1', 'sys_show_hide', NULL, 'danger', 'N', '0', '隐藏菜单', '2022-12-26 21:50:10', 1, '2022-12-26 21:50:10', NULL, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (101, 1, '系统配置', 'sys_config', 'sys_config_group', NULL, 'primary', 'N', '0', '配置群组的系统配置', '2022-11-06 19:27:23', NULL, '2022-11-06 06:07:20', 1, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (102, 2, '邮件配置', 'mail_config', 'sys_config_group', NULL, 'primary', 'N', '0', NULL, '2022-11-06 05:38:04', 1, '2022-11-06 06:07:20', 1, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (103, 3, '文件配置', 'file_config', 'sys_config_group', NULL, 'primary', 'N', '0', NULL, '2022-11-06 06:32:45', 1, '2022-11-06 20:32:44', 1, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (1666438029385986049, 1, '成功', '0', 'sys_common_status', NULL, 'primary', 'N', '0', '正常状态', '2023-06-07 21:32:42', 1, '2023-06-07 21:32:41', NULL, b'0');\nINSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (1666438168611713025, 2, '失败', '1', 'sys_common_status', NULL, 'danger', 'N', '0', '停用状态', '2023-06-07 21:33:15', 1, '2023-06-07 21:33:14', 1, b'0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_dict_type\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_dict_type`;\nCREATE TABLE `sys_dict_type` (\n                                 `dict_id` bigint NOT NULL AUTO_INCREMENT COMMENT '字典主键',\n                                 `dict_name` varchar(100) DEFAULT '' COMMENT '字典名称',\n                                 `dict_type` varchar(100) DEFAULT '' COMMENT '字典类型',\n                                 `status` char(1) DEFAULT '0' COMMENT '状态（0正常 1停用）',\n                                 `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',\n                                 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n                                 `create_user` bigint DEFAULT NULL COMMENT '创建者',\n                                 `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',\n                                 `update_user` bigint DEFAULT NULL COMMENT '更新者',\n                                 `del_flag` bit(1) DEFAULT b'0' COMMENT '逻辑删除(1:已删除，0:未删除)',\n                                 PRIMARY KEY (`dict_id`),\n                                 UNIQUE KEY `dict_type` (`dict_type`)\n) ENGINE=InnoDB AUTO_INCREMENT=1666437666566107138 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='字典类型表';\n\n-- ----------------------------\n-- Records of sys_dict_type\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (1, '用户性别', 'sys_user_sex', '0', '用户性别列表', NULL, NULL, '2022-11-06 01:10:21', 1, b'0');\nINSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (2, '系统是否', 'sys_yes_no', '0', '系统是否列表', '2022-11-06 06:37:05', 1, '2022-11-06 20:37:04', 1, b'0');\nINSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (3, '系统开关', 'sys_normal_disable', '0', '系统开关列表', NULL, NULL, NULL, NULL, b'0');\nINSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (4, '菜单状态', 'sys_show_hide', '0', '菜单状态列表', '2022-12-26 21:49:15', 1, '2022-12-26 21:49:15', NULL, b'0');\nINSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (10, '系统状态', 'sys_common_status', '0', '登录状态列表', '2023-06-07 21:31:15', 1, '2023-06-07 21:31:15', NULL, b'0');\nINSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (101, '配置群组', 'sys_config_group', '0', '配置群组', '2022-11-06 05:32:37', 1, '2022-11-06 19:32:37', 1, b'0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_log_login\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_log_login`;\nCREATE TABLE `sys_log_login` (\n                                 `info_id` bigint NOT NULL AUTO_INCREMENT COMMENT '访问ID',\n                                 `user_id` bigint DEFAULT NULL COMMENT '登录成功的用户id\\n',\n                                 `user_name` varchar(255) DEFAULT NULL COMMENT '用户账号',\n                                 `ipaddr` varchar(128) DEFAULT '' COMMENT '登录IP地址',\n                                 `login_location` varchar(255) DEFAULT '' COMMENT '登录地点',\n                                 `browser` varchar(50) DEFAULT '' COMMENT '浏览器类型',\n                                 `os` varchar(50) DEFAULT '' COMMENT '操作系统',\n                                 `status` char(1) DEFAULT '0' COMMENT '登录状态（0成功 1失败）',\n                                 `msg` varchar(255) DEFAULT '' COMMENT '提示消息',\n                                 `login_time` datetime DEFAULT NULL COMMENT '访问时间',\n                                 PRIMARY KEY (`info_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1686301476458135555 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='系统访问记录';\n\n-- ----------------------------\n-- Records of sys_log_login\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_log_login` (`info_id`, `user_id`, `user_name`, `ipaddr`, `login_location`, `browser`, `os`, `status`, `msg`, `login_time`) VALUES (1686301476458135554, 1, 'admin', '127.0.0.1', '内网IP', 'Chrome 11', 'Mac OS X', '0', '登录成功', '2023-08-01 17:02:57');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_log_oper\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_log_oper`;\nCREATE TABLE `sys_log_oper` (\n                                `oper_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',\n                                `app_name` varchar(255) DEFAULT NULL COMMENT '服务名称，一般为spring.application.name',\n                                `log_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '日志名称',\n                                `log_content` varchar(255) DEFAULT '0' COMMENT '日志记录内容',\n                                `method` varchar(100) DEFAULT '' COMMENT '方法名称',\n                                `request_method` varchar(10) DEFAULT '' COMMENT '请求方式',\n                                `oper_user_id` bigint DEFAULT NULL COMMENT '操作人员user_id',\n                                `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',\n                                `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',\n                                `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',\n                                `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',\n                                `status` int DEFAULT '0' COMMENT '操作状态（0正常 1异常）',\n                                `error_msg` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '错误消息',\n                                `oper_time` datetime DEFAULT NULL COMMENT '操作时间',\n                                PRIMARY KEY (`oper_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1686011339283152899 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='操作日志记录';\n\n-- ----------------------------\n-- Records of sys_log_oper\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_menu`;\nCREATE TABLE `sys_menu` (\n                            `menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',\n                            `menu_name` varchar(50) NOT NULL COMMENT '菜单名称',\n                            `parent_id` bigint DEFAULT '0' COMMENT '父菜单ID',\n                            `order_num` int DEFAULT '0' COMMENT '显示顺序',\n                            `path` varchar(200) DEFAULT '' COMMENT '路由地址',\n                            `component` varchar(255) DEFAULT NULL COMMENT '组件路径',\n                            `query` varchar(255) DEFAULT NULL COMMENT '路由参数',\n                            `is_frame` int DEFAULT '1' COMMENT '是否为外链（0是 1否）',\n                            `is_cache` int DEFAULT '0' COMMENT '是否缓存（0缓存 1不缓存）',\n                            `menu_type` char(1) DEFAULT '' COMMENT '菜单类型（M目录 C菜单 F按钮）',\n                            `visible` char(1) DEFAULT '0' COMMENT '菜单状态（0显示 1隐藏）',\n                            `status` char(1) DEFAULT '0' COMMENT '菜单状态（0正常 1停用）',\n                            `perms` varchar(100) DEFAULT NULL COMMENT '权限标识',\n                            `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',\n                            `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '备注',\n                            `del_flag` bit(1) DEFAULT NULL COMMENT '逻辑删除(1:已删除，0:未删除)',\n                            `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n                            `update_user` bigint DEFAULT NULL COMMENT '更新者',\n                            `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',\n                            `create_user` bigint DEFAULT NULL COMMENT '创建者',\n                            PRIMARY KEY (`menu_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1686232882739159042 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='菜单权限表';\n\n-- ----------------------------\n-- Records of sys_menu\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1, '系统管理', 0, 1, 'system', NULL, '', 1, 0, 'M', '0', '0', '', 'system', '系统管理目录', b'0', '2022-10-05 15:28:43', 1, '2022-11-14 14:41:50', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (2, '系统监控', 0, 2, 'monitor', NULL, '', 1, 0, 'M', '0', '0', '', 'monitor', '系统监控目录', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (3, '系统工具', 0, 3, 'tool', NULL, '', 1, 0, 'M', '0', '0', '', 'tool', '系统工具目录', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (4, '源码地址', 0, 10, 'http://github.com/oddfar/campus', NULL, '', 0, 0, 'M', '0', '0', '', 'guide', '若依官网地址', b'0', '2022-10-05 15:28:43', 1, '2022-11-21 17:11:40', NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', '用户管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', '角色管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', '菜单管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', '部门管理菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', '岗位管理菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', '字典管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (106, '参数设置', 1, 7, 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', '参数设置菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (107, '通知公告', 1, 8, 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', '通知公告菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (108, '日志管理', 1, 9, 'log', '', '', 1, 0, 'M', '0', '0', '', 'log', '日志管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', '在线用户菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (110, '定时任务', 2, 2, 'job', 'monitor/job/index', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', '定时任务菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', '数据监控菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (112, '服务监控', 2, 4, 'server', 'monitor/server/index', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', '服务监控菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', '缓存监控菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (114, '缓存列表', 2, 6, 'cacheList', 'monitor/cache/list', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', '缓存列表菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (115, '表单构建', 3, 1, 'build', 'tool/build/index', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', '表单构建菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (116, '代码生成', 3, 2, 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', '代码生成菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (117, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', '系统接口菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', '操作日志菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', '登录日志菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1000, '用户查询', 100, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1001, '用户新增', 100, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1002, '用户修改', 100, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1003, '用户删除', 100, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1004, '用户导出', 100, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1005, '用户导入', 100, 6, '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1006, '重置密码', 100, 7, '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1007, '角色查询', 101, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1008, '角色新增', 101, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1009, '角色修改', 101, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1010, '角色删除', 101, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1011, '角色导出', 101, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1012, '菜单查询', 102, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1013, '菜单新增', 102, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1014, '菜单修改', 102, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1015, '菜单删除', 102, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1016, '部门查询', 103, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1017, '部门新增', 103, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1018, '部门修改', 103, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1019, '部门删除', 103, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1020, '岗位查询', 104, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1021, '岗位新增', 104, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1022, '岗位修改', 104, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1023, '岗位删除', 104, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1024, '岗位导出', 104, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1025, '字典查询', 105, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1026, '字典新增', 105, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1027, '字典修改', 105, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1028, '字典删除', 105, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1029, '字典导出', 105, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1030, '参数查询', 106, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1031, '参数新增', 106, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1032, '参数修改', 106, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1033, '参数删除', 106, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1034, '参数导出', 106, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1035, '公告查询', 107, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1036, '公告新增', 107, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1037, '公告修改', 107, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1038, '公告删除', 107, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1039, '操作查询', 500, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1040, '操作删除', 500, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1041, '日志导出', 500, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1042, '登录查询', 501, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1043, '登录删除', 501, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1044, '日志导出', 501, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1045, '账户解锁', 501, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1046, '在线查询', 109, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1047, '批量强退', 109, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1048, '单条强退', 109, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1049, '任务查询', 110, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1050, '任务新增', 110, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1051, '任务修改', 110, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1052, '任务删除', 110, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1053, '状态修改', 110, 5, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1054, '任务导出', 110, 6, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1055, '生成查询', 116, 1, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1056, '生成修改', 116, 2, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1057, '生成删除', 116, 3, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1058, '导入代码', 116, 4, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1059, '预览代码', 116, 5, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1060, '生成代码', 116, 6, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1676581553133699073, 'i茅台', 0, 4, 'imt', NULL, NULL, 1, 0, 'M', '0', '0', NULL, 'star', '', b'0', '2023-07-05 21:19:26', 1, '2023-07-05 21:19:26', 1);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1676586683316944898, '预约项目', 1676581553133699073, 2, 'item', 'imt/item/index', NULL, 1, 1, 'C', '0', '0', NULL, '#', '', b'0', '2023-07-05 21:39:49', 1, '2023-07-05 21:39:49', 1);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1676598335282122754, '门店列表', 1676581553133699073, 3, 'shop', 'imt/shop/index', NULL, 1, 1, 'C', '0', '0', NULL, '#', '', b'0', '2023-07-05 22:26:07', 1, '2023-07-05 22:26:07', 1);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1676847133803773954, '用户管理', 1676581553133699073, 1, 'user', 'imt/user/index', NULL, 1, 1, 'C', '0', '0', NULL, 'user', '', b'0', '2023-07-06 14:54:46', 1, '2023-07-06 14:54:45', 1);\nINSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1686232882739159041, '日志', 1676581553133699073, 4, 'log', 'imt/log/index', NULL, 1, 0, 'C', '0', '0', NULL, '#', '', b'0', '2023-08-01 12:30:23', 1, '2023-08-01 12:30:22', 1);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_resource\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_resource`;\nCREATE TABLE `sys_resource` (\n                                `resource_id` bigint NOT NULL COMMENT '资源id',\n                                `app_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '应用编码',\n                                `resource_code` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '资源编码',\n                                `resource_name` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '资源名称',\n                                `class_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '类名称',\n                                `method_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '方法名称',\n                                `modular_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '资源模块名称，一般为控制器名称',\n                                `url` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '资源url',\n                                `http_method` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'http请求方法',\n                                `resource_biz_type` tinyint DEFAULT '1' COMMENT '资源的业务类型：1-业务类，2-系统类',\n                                `required_permission_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '是否需要鉴权：Y-是，N-否',\n                                `del_flag` bit(1) DEFAULT b'0' COMMENT '删除标志（0代表存在 1代表删除）',\n                                `create_user` bigint DEFAULT NULL COMMENT '创建人',\n                                `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n                                `update_user` bigint DEFAULT NULL COMMENT '更新人',\n                                `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',\n                                PRIMARY KEY (`resource_id`) USING BTREE,\n                                KEY `RESOURCE_CODE_URL` (`resource_code`,`url`) USING BTREE COMMENT '资源code和url的联合索引'\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC COMMENT='需要认证的接口资源controller';\n\n-- ----------------------------\n-- Records of sys_resource\n-- ----------------------------\nBEGIN;\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_role`;\nCREATE TABLE `sys_role` (\n                            `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',\n                            `role_name` varchar(30) NOT NULL COMMENT '角色名称',\n                            `role_key` varchar(100) NOT NULL COMMENT '角色权限字符串',\n                            `role_sort` int NOT NULL COMMENT '显示顺序',\n                            `menu_check_strictly` tinyint(1) DEFAULT '1' COMMENT '菜单树选择项是否关联显示',\n                            `status` char(1) NOT NULL COMMENT '角色状态（0正常 1停用）',\n                            `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',\n                            `del_flag` bit(1) DEFAULT b'0' COMMENT '删除标志（0代表存在 2代表删除）',\n                            `create_user` bigint DEFAULT NULL COMMENT '创建者',\n                            `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n                            `update_user` bigint DEFAULT NULL COMMENT '更新者',\n                            `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n                            PRIMARY KEY (`role_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1685558345957654530 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='角色信息表';\n\n-- ----------------------------\n-- Records of sys_role\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_sort`, `menu_check_strictly`, `status`, `remark`, `del_flag`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (1, '超级管理员', 'admin', 1, 1, '0', '超级管理员', b'0', NULL, '2022-10-05 15:28:43', NULL, NULL);\nINSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_sort`, `menu_check_strictly`, `status`, `remark`, `del_flag`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (2, '普通角色', 'common', 2, 1, '0', '普通角色', b'0', 1, '2022-10-05 15:28:43', 1, '2023-04-23 09:25:30');\nINSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_sort`, `menu_check_strictly`, `status`, `remark`, `del_flag`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (1685558345957654529, 'i茅台', 'imaotai', 3, 1, '0', NULL, b'0', 1, '2023-07-30 15:50:00', 1, '2023-08-01 12:34:59');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_role_menu\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_role_menu`;\nCREATE TABLE `sys_role_menu` (\n                                 `role_id` bigint NOT NULL COMMENT '角色ID',\n                                 `menu_id` bigint NOT NULL COMMENT '菜单ID',\n                                 PRIMARY KEY (`role_id`,`menu_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='角色和菜单关联表';\n\n-- ----------------------------\n-- Records of sys_role_menu\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 4);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 100);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 101);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1000);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1001);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1002);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1003);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1004);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1005);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1006);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1007);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1008);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1009);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1010);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1011);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1676581553133699073);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1676586683316944898);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1676598335282122754);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1676847133803773954);\nINSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1686232882739159041);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_role_resource\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_role_resource`;\nCREATE TABLE `sys_role_resource` (\n                                     `resource_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '资源编码',\n                                     `role_id` bigint NOT NULL COMMENT '角色id',\n                                     PRIMARY KEY (`role_id`,`resource_code`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色资源关联';\n\n-- ----------------------------\n-- Records of sys_role_resource\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_log.list', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.add', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.edit', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.get_info', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.list', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.login', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.remove', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.reservation', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.send_code', 1685558345957654529);\nINSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.travel_reward', 1685558345957654529);\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_user\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_user`;\nCREATE TABLE `sys_user` (\n                            `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',\n                            `user_name` varchar(30) NOT NULL COMMENT '用户账号',\n                            `nick_name` varchar(30) NOT NULL COMMENT '用户昵称',\n                            `user_type` varchar(2) DEFAULT '00' COMMENT '用户类型（00系统用户）',\n                            `email` varchar(50) DEFAULT '' COMMENT '用户邮箱',\n                            `phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',\n                            `sex` char(1) DEFAULT '0' COMMENT '用户性别（0男 1女 2未知）',\n                            `avatar` varchar(100) DEFAULT '' COMMENT '头像地址',\n                            `password` varchar(100) DEFAULT '' COMMENT '密码',\n                            `status` char(1) DEFAULT '0' COMMENT '帐号状态（0正常 1停用）',\n                            `login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',\n                            `login_date` datetime DEFAULT NULL COMMENT '最后登录时间',\n                            `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',\n                            `create_user` bigint DEFAULT NULL COMMENT '创建者',\n                            `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n                            `update_user` bigint DEFAULT NULL COMMENT '更新者',\n                            `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n                            `del_flag` bit(1) DEFAULT NULL COMMENT '逻辑删除(1:已删除，0:未删除)',\n                            PRIMARY KEY (`user_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1686027685614125058 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='用户信息表';\n\n-- ----------------------------\n-- Records of sys_user\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_user` (`user_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `login_ip`, `login_date`, `remark`, `create_user`, `create_time`, `update_user`, `update_time`, `del_flag`) VALUES (1, 'admin', 'admin', '00', 'oddfar@163.com', '15888888888', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '127.0.0.1', '2023-08-01 17:02:57', '管理员', 0, '2022-10-05 15:28:43', 1, '2023-08-01 17:02:57', b'0');\nINSERT INTO `sys_user` (`user_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `login_ip`, `login_date`, `remark`, `create_user`, `create_time`, `update_user`, `update_time`, `del_flag`) VALUES (2, 'zhiyuan', '致远', '00', 'a_zhiyuan@163.com', '15666666666', '1', 'https://img0.baidu.com/it/u=1183896628,1403534286&fm=253&fmt=auto&app=138&f=PNG', '$2a$10$0522gOEarwIDNCk57dsrNeGqXTDwx2Zpy447d8R7W5MbH4/j1rcQi', '0', '127.0.0.1', '2023-02-25 23:01:16', '致远', 0, '2022-10-05 15:28:43', 1, '2023-07-15 23:18:08', b'0');\nINSERT INTO `sys_user` (`user_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `login_ip`, `login_date`, `remark`, `create_user`, `create_time`, `update_user`, `update_time`, `del_flag`) VALUES (1686027685614125057, 'test', 'test', '00', '', '', '0', '', '$2a$10$23zCdQer/Killsky7gISDeFNQd5SDmf.LLai0sKF3jAR3BdcX2vTm', '0', '127.0.0.1', '2023-08-01 14:49:51', '测试', 0, '2023-07-31 22:55:00', 1, '2023-08-01 14:49:51', b'0');\nCOMMIT;\n\n-- ----------------------------\n-- Table structure for sys_user_role\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_user_role`;\nCREATE TABLE `sys_user_role` (\n                                 `user_id` bigint NOT NULL COMMENT '用户ID',\n                                 `role_id` bigint NOT NULL COMMENT '角色ID',\n                                 PRIMARY KEY (`user_id`,`role_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='用户和角色关联表';\n\n-- ----------------------------\n-- Records of sys_user_role\n-- ----------------------------\nBEGIN;\nINSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (1, 1);\nINSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (2, 2);\nINSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (1686027685614125057, 1685558345957654529);\nCOMMIT;\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.oddfar.campus</groupId>\n    <artifactId>campus</artifactId>\n    <version>${revision}</version>\n    <packaging>pom</packaging>\n\n    <name>campus</name>\n    <url>http://campus.oddfar.com</url>\n    <description>i茅台自动预约</description>\n\n    <modules>\n        <module>campus-common</module>\n        <module>campus-framework</module>\n        <module>campus-admin</module>\n        <module>campus-modular</module>\n    </modules>\n\n    <properties>\n        <revision>1.0.13</revision>\n        <campus.revision>1.1.9</campus.revision>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <java.version>1.8</java.version>\n        <bitwalker.version>1.21</bitwalker.version>\n        <mybatis.plus.boot.version>3.5.4.1</mybatis.plus.boot.version>\n        <p6spy.version>3.9.1</p6spy.version>\n        <dynamic-ds.version>4.2.0</dynamic-ds.version>\n        <swagger.version>3.0.0</swagger.version>\n        <fastjson.version>2.0.42</fastjson.version>\n        <hutool.version>5.8.23</hutool.version>\n        <commons.fileupload.version>1.4</commons.fileupload.version>\n        <mapstruct.version>1.5.3.Final</mapstruct.version>\n        <kaptcha.version>2.3.2</kaptcha.version>\n        <jwt.version>0.9.1</jwt.version>\n        <poi.version>4.1.2</poi.version>\n        <sunmail.version>1.6.2</sunmail.version>\n        <p6spy.spring.boot>1.8.1</p6spy.spring.boot>\n        <!-- 插件版本 -->\n        <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>\n    </properties>\n\n    <profiles>\n        <profile>\n            <id>dev</id>\n            <properties>\n                <!-- 环境标识，需要与配置文件的名称相对应 -->\n                <profiles.active>dev</profiles.active>\n                <logging.level>debug</logging.level>\n            </properties>\n            <activation>\n                <!-- 默认环境 -->\n                <activeByDefault>true</activeByDefault>\n            </activation>\n        </profile>\n        <profile>\n            <id>prod</id>\n            <properties>\n                <profiles.active>prod</profiles.active>\n                <logging.level>warn</logging.level>\n            </properties>\n        </profile>\n    </profiles>\n\n    <!-- 依赖声明 -->\n    <dependencyManagement>\n        <dependencies>\n            <!-- SpringBoot的依赖配置-->\n            <dependency>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-dependencies</artifactId>\n                <version>2.5.15</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n\n            <!-- mybatis-plus框架 -->\n            <dependency>\n                <groupId>com.baomidou</groupId>\n                <artifactId>mybatis-plus-boot-starter</artifactId>\n                <version>${mybatis.plus.boot.version}</version>\n            </dependency>\n\n            <!-- sql性能分析插件 -->\n            <dependency>\n                <groupId>p6spy</groupId>\n                <artifactId>p6spy</artifactId>\n                <version>${p6spy.version}</version>\n            </dependency>\n\n            <!-- dynamic-datasource 多数据源-->\n            <dependency>\n                <groupId>com.baomidou</groupId>\n                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>\n                <version>${dynamic-ds.version}</version>\n            </dependency>\n\n            <!-- Swagger3依赖 -->\n            <dependency>\n                <groupId>io.springfox</groupId>\n                <artifactId>springfox-boot-starter</artifactId>\n                <version>${swagger.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <groupId>io.swagger</groupId>\n                        <artifactId>swagger-models</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n\n            <!-- 解析客户端操作系统、浏览器等 -->\n            <dependency>\n                <groupId>eu.bitwalker</groupId>\n                <artifactId>UserAgentUtils</artifactId>\n                <version>${bitwalker.version}</version>\n            </dependency>\n\n            <!-- 验证码 -->\n            <dependency>\n                <groupId>com.github.penggle</groupId>\n                <artifactId>kaptcha</artifactId>\n                <version>${kaptcha.version}</version>\n            </dependency>\n\n            <!-- excel工具 -->\n            <dependency>\n                <groupId>org.apache.poi</groupId>\n                <artifactId>poi-ooxml</artifactId>\n                <version>${poi.version}</version>\n            </dependency>\n\n            <!-- Token生成与解析-->\n            <dependency>\n                <groupId>io.jsonwebtoken</groupId>\n                <artifactId>jjwt</artifactId>\n                <version>${jwt.version}</version>\n            </dependency>\n\n            <!-- 文件上传工具类 -->\n            <dependency>\n                <groupId>commons-fileupload</groupId>\n                <artifactId>commons-fileupload</artifactId>\n                <version>${commons.fileupload.version}</version>\n            </dependency>\n\n            <!-- 阿里JSON解析器 -->\n            <dependency>\n                <groupId>com.alibaba.fastjson2</groupId>\n                <artifactId>fastjson2</artifactId>\n                <version>${fastjson.version}</version>\n            </dependency>\n\n            <!--hutool-->\n            <dependency>\n                <groupId>cn.hutool</groupId>\n                <artifactId>hutool-all</artifactId>\n                <version>${hutool.version}</version>\n            </dependency>\n\n            <!--mapstruct-->\n            <dependency>\n                <groupId>org.mapstruct</groupId>\n                <artifactId>mapstruct</artifactId>\n                <version>${mapstruct.version}</version>\n            </dependency>\n\n            <!-- 邮件服务-->\n            <dependency>\n                <groupId>com.sun.mail</groupId>\n                <artifactId>javax.mail</artifactId>\n                <version>${sunmail.version}</version>\n            </dependency>\n\n            <!-- 通用工具-->\n            <dependency>\n                <groupId>com.oddfar.campus</groupId>\n                <artifactId>campus-common</artifactId>\n                <version>${revision}</version>\n            </dependency>\n            <!-- 系统模块-->\n            <dependency>\n                <groupId>com.oddfar.campus</groupId>\n                <artifactId>campus-framework</artifactId>\n                <version>${revision}</version>\n            </dependency>\n            <!--后台管理-->\n            <dependency>\n                <groupId>com.oddfar.campus</groupId>\n                <artifactId>campus-admin</artifactId>\n                <version>${revision}</version>\n            </dependency>\n        </dependencies>\n\n    </dependencyManagement>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.1</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                </configuration>\n            </plugin>\n            <!-- 统一版本号管理 -->\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>flatten-maven-plugin</artifactId>\n                <version>${flatten-maven-plugin.version}</version>\n                <configuration>\n                    <updatePomFile>true</updatePomFile>\n                    <flattenMode>resolveCiFriendliesOnly</flattenMode>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>flatten</id>\n                        <phase>process-resources</phase>\n                        <goals>\n                            <goal>flatten</goal>\n                        </goals>\n                    </execution>\n                    <execution>\n                        <id>flatten.clean</id>\n                        <phase>clean</phase>\n                        <goals>\n                            <goal>clean</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n        <resources>\n            <resource>\n                <directory>src/main/resources</directory>\n                <filtering>true</filtering>\n            </resource>\n        </resources>\n    </build>\n\n\n</project>"
  },
  {
    "path": "vue_campus_admin/.editorconfig",
    "content": "# 告诉EditorConfig插件，这是根文件，不用继续往上查找\nroot = true\n\n# 匹配全部文件\n[*]\n# 设置字符集\ncharset = utf-8\n# 缩进风格，可选space、tab\nindent_style = space\n# 缩进的空格数\nindent_size = 2\n# 结尾换行符，可选lf、cr、crlf\nend_of_line = lf\n# 在文件结尾插入新行\ninsert_final_newline = true\n# 删除一行中的前后空格\ntrim_trailing_whitespace = true\n\n# 匹配md结尾的文件\n[*.md]\ninsert_final_newline = false\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "vue_campus_admin/.eslintignore",
    "content": "# 忽略build目录下类型为js的文件的语法检查\nbuild/*.js\n# 忽略src/assets目录下文件的语法检查\nsrc/assets\n# 忽略public目录下文件的语法检查\npublic\n# 忽略当前目录下为js的文件的语法检查\n*.js\n# 忽略当前目录下为vue的文件的语法检查\n*.vue"
  },
  {
    "path": "vue_campus_admin/.eslintrc.js",
    "content": "// ESlint 检查配置\nmodule.exports = {\n  root: true,\n  parserOptions: {\n    parser: 'babel-eslint',\n    sourceType: 'module'\n  },\n  env: {\n    browser: true,\n    node: true,\n    es6: true,\n  },\n  extends: ['plugin:vue/recommended', 'eslint:recommended'],\n\n  // add your custom rules here\n  //it is base on https://github.com/vuejs/eslint-config-vue\n  rules: {\n    \"vue/max-attributes-per-line\": [2, {\n      \"singleline\": 10,\n      \"multiline\": {\n        \"max\": 1,\n        \"allowFirstLine\": false\n      }\n    }],\n    \"vue/singleline-html-element-content-newline\": \"off\",\n    \"vue/multiline-html-element-content-newline\":\"off\",\n    \"vue/name-property-casing\": [\"error\", \"PascalCase\"],\n    \"vue/no-v-html\": \"off\",\n    'accessor-pairs': 2,\n    'arrow-spacing': [2, {\n      'before': true,\n      'after': true\n    }],\n    'block-spacing': [2, 'always'],\n    'brace-style': [2, '1tbs', {\n      'allowSingleLine': true\n    }],\n    'camelcase': [0, {\n      'properties': 'always'\n    }],\n    'comma-dangle': [2, 'never'],\n    'comma-spacing': [2, {\n      'before': false,\n      'after': true\n    }],\n    'comma-style': [2, 'last'],\n    'constructor-super': 2,\n    'curly': [2, 'multi-line'],\n    'dot-location': [2, 'property'],\n    'eol-last': 2,\n    'eqeqeq': [\"error\", \"always\", {\"null\": \"ignore\"}],\n    'generator-star-spacing': [2, {\n      'before': true,\n      'after': true\n    }],\n    'handle-callback-err': [2, '^(err|error)$'],\n    'indent': [2, 2, {\n      'SwitchCase': 1\n    }],\n    'jsx-quotes': [2, 'prefer-single'],\n    'key-spacing': [2, {\n      'beforeColon': false,\n      'afterColon': true\n    }],\n    'keyword-spacing': [2, {\n      'before': true,\n      'after': true\n    }],\n    'new-cap': [2, {\n      'newIsCap': true,\n      'capIsNew': false\n    }],\n    'new-parens': 2,\n    'no-array-constructor': 2,\n    'no-caller': 2,\n    'no-console': 'off',\n    'no-class-assign': 2,\n    'no-cond-assign': 2,\n    'no-const-assign': 2,\n    'no-control-regex': 0,\n    'no-delete-var': 2,\n    'no-dupe-args': 2,\n    'no-dupe-class-members': 2,\n    'no-dupe-keys': 2,\n    'no-duplicate-case': 2,\n    'no-empty-character-class': 2,\n    'no-empty-pattern': 2,\n    'no-eval': 2,\n    'no-ex-assign': 2,\n    'no-extend-native': 2,\n    'no-extra-bind': 2,\n    'no-extra-boolean-cast': 2,\n    'no-extra-parens': [2, 'functions'],\n    'no-fallthrough': 2,\n    'no-floating-decimal': 2,\n    'no-func-assign': 2,\n    'no-implied-eval': 2,\n    'no-inner-declarations': [2, 'functions'],\n    'no-invalid-regexp': 2,\n    'no-irregular-whitespace': 2,\n    'no-iterator': 2,\n    'no-label-var': 2,\n    'no-labels': [2, {\n      'allowLoop': false,\n      'allowSwitch': false\n    }],\n    'no-lone-blocks': 2,\n    'no-mixed-spaces-and-tabs': 2,\n    'no-multi-spaces': 2,\n    'no-multi-str': 2,\n    'no-multiple-empty-lines': [2, {\n      'max': 1\n    }],\n    'no-native-reassign': 2,\n    'no-negated-in-lhs': 2,\n    'no-new-object': 2,\n    'no-new-require': 2,\n    'no-new-symbol': 2,\n    'no-new-wrappers': 2,\n    'no-obj-calls': 2,\n    'no-octal': 2,\n    'no-octal-escape': 2,\n    'no-path-concat': 2,\n    'no-proto': 2,\n    'no-redeclare': 2,\n    'no-regex-spaces': 2,\n    'no-return-assign': [2, 'except-parens'],\n    'no-self-assign': 2,\n    'no-self-compare': 2,\n    'no-sequences': 2,\n    'no-shadow-restricted-names': 2,\n    'no-spaced-func': 2,\n    'no-sparse-arrays': 2,\n    'no-this-before-super': 2,\n    'no-throw-literal': 2,\n    'no-trailing-spaces': 2,\n    'no-undef': 2,\n    'no-undef-init': 2,\n    'no-unexpected-multiline': 2,\n    'no-unmodified-loop-condition': 2,\n    'no-unneeded-ternary': [2, {\n      'defaultAssignment': false\n    }],\n    'no-unreachable': 2,\n    'no-unsafe-finally': 2,\n    'no-unused-vars': [2, {\n      'vars': 'all',\n      'args': 'none'\n    }],\n    'no-useless-call': 2,\n    'no-useless-computed-key': 2,\n    'no-useless-constructor': 2,\n    'no-useless-escape': 0,\n    'no-whitespace-before-property': 2,\n    'no-with': 2,\n    'one-var': [2, {\n      'initialized': 'never'\n    }],\n    'operator-linebreak': [2, 'after', {\n      'overrides': {\n        '?': 'before',\n        ':': 'before'\n      }\n    }],\n    'padded-blocks': [2, 'never'],\n    'quotes': [2, 'single', {\n      'avoidEscape': true,\n      'allowTemplateLiterals': true\n    }],\n    'semi': [2, 'never'],\n    'semi-spacing': [2, {\n      'before': false,\n      'after': true\n    }],\n    'space-before-blocks': [2, 'always'],\n    'space-before-function-paren': [2, 'never'],\n    'space-in-parens': [2, 'never'],\n    'space-infix-ops': 2,\n    'space-unary-ops': [2, {\n      'words': true,\n      'nonwords': false\n    }],\n    'spaced-comment': [2, 'always', {\n      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']\n    }],\n    'template-curly-spacing': [2, 'never'],\n    'use-isnan': 2,\n    'valid-typeof': 2,\n    'wrap-iife': [2, 'any'],\n    'yield-star-spacing': [2, 'both'],\n    'yoda': [2, 'never'],\n    'prefer-const': 2,\n    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,\n    'object-curly-spacing': [2, 'always', {\n      objectsInObjects: false\n    }],\n    'array-bracket-spacing': [2, 'never']\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/.gitignore",
    "content": ".DS_Store\nnode_modules/\ndist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n**/*.log\n\ntests/**/coverage/\ntests/e2e/reports\nselenium-debug.log\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.local\n\npackage-lock.json\nyarn.lock\n"
  },
  {
    "path": "vue_campus_admin/Dockerfile",
    "content": "FROM nginx:stable-alpine\n# author\nMAINTAINER oddfar\n# 复制html文件到路径\nCOPY dist /usr/share/nginx/html\n# 复制conf文件到路径\nCOPY ./doc/docker/nginx/conf/nginx.conf /etc/nginx/nginx.conf\n"
  },
  {
    "path": "vue_campus_admin/README.md",
    "content": "此项目用的`若依`项目\n\n## 开发\n\n```bash\n# 克隆项目\ngit clone https://github.com/oddfar/campus\n\n# 进入项目目录\ncd campus-ui\n\n# 安装依赖\nnpm install\n\n# 建议不要直接使用 cnpm 安装依赖，会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题\nnpm install --registry=https://registry.npmmirror.com\n\n# 启动服务\nnpm run dev\n```\n\n浏览器访问 http://localhost:1024\n\n## 发布\n\n```bash\n# 构建测试环境\nnpm run build:stage\n\n# 构建生产环境\nnpm run build:prod\n```"
  },
  {
    "path": "vue_campus_admin/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app\n    '@vue/cli-plugin-babel/preset'\n  ],\n  'env': {\n    'development': {\n      // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().\n      // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.\n      'plugins': ['dynamic-import-node']\n    }\n  }\n}"
  },
  {
    "path": "vue_campus_admin/bin/build.bat",
    "content": "@echo off\necho.\necho [Ϣ] Weḅdistļ\necho.\n\n%~d0\ncd %~dp0\n\ncd ..\nnpm run build:prod\n\npause"
  },
  {
    "path": "vue_campus_admin/bin/package.bat",
    "content": "@echo off\necho.\necho [Ϣ] װWeḅnode_modulesļ\necho.\n\n%~d0\ncd %~dp0\n\ncd ..\nnpm install --registry=https://registry.npmmirror.com\n\npause"
  },
  {
    "path": "vue_campus_admin/bin/run-web.bat",
    "content": "@echo off\necho.\necho [Ϣ] ʹ Vue CLI  Web ̡\necho.\n\n%~d0\ncd %~dp0\n\ncd ..\nnpm run dev\n\npause"
  },
  {
    "path": "vue_campus_admin/package.json",
    "content": "{\n  \"name\": \"oddfar\",\n  \"version\": \"1.1.0\",\n  \"description\": \"campus-imaotai\",\n  \"author\": \"oddfar\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"dev\": \"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve\",\n    \"build:prod\": \"vue-cli-service build\",\n    \"build:stage\": \"vue-cli-service build --mode staging\",\n    \"preview\": \"node build/index.js --preview\",\n    \"lint\": \"eslint --ext .js,.vue src\"\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  \"keywords\": [\n    \"vue\",\n    \"admin\",\n    \"dashboard\",\n    \"element-ui\",\n    \"boilerplate\",\n    \"admin-template\",\n    \"management-system\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/oddfar/campus-imaotai\"\n  },\n  \"dependencies\": {\n    \"@riophae/vue-treeselect\": \"0.4.0\",\n    \"axios\": \"0.24.0\",\n    \"clipboard\": \"2.0.8\",\n    \"core-js\": \"3.25.2\",\n    \"echarts\": \"4.9.0\",\n    \"element-ui\": \"2.15.10\",\n    \"file-saver\": \"2.0.5\",\n    \"fuse.js\": \"6.4.3\",\n    \"highlight.js\": \"9.18.5\",\n    \"js-beautify\": \"1.13.0\",\n    \"js-cookie\": \"3.0.1\",\n    \"jsencrypt\": \"3.0.0-rc.1\",\n    \"nprogress\": \"0.2.0\",\n    \"quill\": \"1.3.7\",\n    \"screenfull\": \"5.0.2\",\n    \"sortablejs\": \"1.10.2\",\n    \"vue\": \"2.6.12\",\n    \"vue-count-to\": \"1.0.13\",\n    \"vue-cropper\": \"0.5.5\",\n    \"vue-meta\": \"2.4.0\",\n    \"vue-router\": \"3.4.9\",\n    \"vuedraggable\": \"2.24.3\",\n    \"vuex\": \"3.6.0\",\n    \"watermark-dom\": \"^2.3.0\"\n  },\n  \"devDependencies\": {\n    \"@vue/cli-plugin-babel\": \"4.4.6\",\n    \"@vue/cli-plugin-eslint\": \"4.4.6\",\n    \"@vue/cli-service\": \"4.4.6\",\n    \"babel-eslint\": \"10.1.0\",\n    \"babel-plugin-dynamic-import-node\": \"2.3.3\",\n    \"chalk\": \"4.1.0\",\n    \"compression-webpack-plugin\": \"5.0.2\",\n    \"connect\": \"3.6.6\",\n    \"crypto-js\": \"^4.1.1\",\n    \"eslint\": \"7.15.0\",\n    \"eslint-plugin-vue\": \"7.2.0\",\n    \"lint-staged\": \"10.5.3\",\n    \"runjs\": \"4.4.2\",\n    \"sass\": \"1.32.13\",\n    \"sass-loader\": \"10.1.1\",\n    \"script-ext-html-webpack-plugin\": \"2.1.5\",\n    \"svg-sprite-loader\": \"5.1.1\",\n    \"vue-template-compiler\": \"2.6.12\"\n  },\n  \"engines\": {\n    \"node\": \">=8.9\",\n    \"npm\": \">= 3.0.0\"\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\"\n  ]\n}\n"
  },
  {
    "path": "vue_campus_admin/public/html/ie.html",
    "content": "\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\" />\n    <title>请升级您的浏览器</title>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge,chrome=1\" >\n    <meta name=\"renderer\" content=\"webkit\">\n    <base target=\"_blank\" />\n    <style type=\"text/css\">\n        html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}\n        a{text-decoration:none;color:#0072c6;}a:hover{text-decoration:none;color:#004d8c;}\n        body{width:960px;margin:0 auto;padding:10px;font-size:14px;line-height:24px;color:#454545;font-family:'Microsoft YaHei UI','Microsoft YaHei',DengXian,SimSun,'Segoe UI',Tahoma,Helvetica,sans-serif;overflow-y:scroll}\n        h1{font-size:40px;line-height:80px;font-weight:100;margin-bottom:10px;}\n        h2{font-size:20px;line-height:25px;font-weight:100;margin:10px 0;}\n        em{color:red}\n        p{margin-bottom:10px;}\n        hr{margin:20px 0;border:0;border-top:1px solid #dadada}\n        span{display:block;font-size:12px;line-height:12px;}\n        .clean{clear:both;}\n        .browser{padding:10px 10px;}\n        .browser li{width:auto;padding:0 80px;margin-top:30px;height:34px;line-height:22px;float:left;list-style:none;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAADMCAYAAAAWCXEwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAC7ESURBVHja5Lx5dFRV1rBfgHwYRQQVtB26ZWhtabtfeUGxGxFbUGZF8RMHGkVbRkekVYiKisicVhE0gEwBokgDAhEMMSSQkAECwcxkrlRSqVTqJqnxzs/vj5t7qUyAvr9e37fWV2vtleSm6p6n9t5nn733OVU2RaUaEP5PiqJSbeMXPBTA5/Xhzk9Vnd9vo3HFx21E2LYJX9IRgh6npvyCe9uaqS4K4C3IpXHFx9S99CTuJ8Z0KLVjRlA7ZgTuJ8ZgXxmJL+kIlwAkXBQk6HFq9pWRVA8fSvXwodYgdS892a6EA1UNvouqwXdR99KTeAtyfz2IL+kI1cOHYh9wqwVwKWJqpXbMCOv19gG3Imzb1JF2OgZxfr/NukH4jcNVfyEAE8IU+4BbKet1PfaVke3BtA/i/H6b8aIBt7a4mWmaC0nr55vmqRp8F5V33Mm5LhHtwbQF8SUdsSDCb1I1+K42g1xIWgOYYh9wK+e6RCBs29QxSIWus37aJM51iWjx4so77mwD1d5AHQ1eecedlN9yuyVlva6nrNf14Q7cEmRn4W7u3T2E9ME3UX7L7W1uZg5Weced1s3sA2613ql5LXzQjuRclwjcT4wxTXQeRHC7GLdnHPeensiCVwa3e0PznZk3EbZtwluQa0kofz8NcVNxr++Ce30XnNuv61Bcu7viXt8Fvyu7JYipjfGHxzD+8Bh2j+7fAiZcC+Y0zPDIbCyD6DyV6DyVeDcIQR2C39J4oieNJ3oSOnkVcnZ35Ozu6MVdDHF0N6S4C43OqJYg/0ydzb27hzDx0FjuPT2R+asfa6OVsl7X40s6QoWus/CQk6fWZPHChhxe3lbMCxtyrN9TyxSQSwidvMoC0XK6tRGybPjSRmOuNUKVo4Zxe8YxIu4+Jh4ay/jDY7j39MQWWjnXJYLGFR9Toes8tSaLiavTrIHDxfxfapkCwW8hy9YuhCmhk1fR1FRnaCS1NM4yy8RDYy2tjIkZRXq/HtYsCnqc2sJDTkYsTrU00J6YkEJQR7M/eEGY0MmrcOenqjZA2JmyzTJLuJiOe65LBHUvPUmGR2bE4lQmrk7jqTVZHcrE1WkMWpRIdJ4KnpUXBCHLRl3e16EWIOEaMU00/vAY9na/gsYVH/NdgYe+8w9bMBeSQYsSWXjICcFvL2ga+dhlFwcJ10rjio/ZklprgbSWiavTWvzdd/5hXt5W/OtATC201sq9u4eQ+PVijmSW0nf+YQYtSmTQosR2gUYsTmXQokT6zj9saeRCpmkJ0hxD2gOZeGgsI+Lu45+ps7FXlFmDmDDtSd/5h+k7/zCpZQpa9cwOQciyIR+77LyzFhXlMyZmFOP2jLP8orVWRsTdR2ppHFtSa+k6ZZM1WHvSdcomwyxySceayO4OWTY88TdirygzUkWf18eL2//RQiutYcwYE/Q4tagDOUQ8uo6uUzbRZ3qMJV2nbCLi0XU8tSbrolNXzu6OfOyylgEN4NOkaO5acw/j9ozr0ET37h5imehIZimPL91rAfSZHsOQBfuISS7E7vaTETeX0MmrOoQInbwK+dhlNKWsahni0zPSuGvNPW1M1BrI1NrOwt0WkCn2ijJSS+MYt2ccuQk3oxd36RCi8URPY+HLT1VbgGiSzPsx71laCddMe2Yygf6ZOtuScXvG0XfJn/n8YL+LQnjibyQ34WZ8Xl/bfKSoKL+FVi4EYwKZcu/uIQzaPoExMaPQcrq1ADFX33AI1+6u1OV9HVI6ShU/TYqm75I/dwjTHtDEQ2MZt2ccg7ZPaGGScIDWEBlxc42UoSMQ00StYdoDCgcbtH0Cbx+8p40ZTIBwiFM7RmB3+y+exZvT2YRpDdR6ZoVrw1xRWwN44m/Euf06A6Ki7NLrmnDNmH7TEdSg7RP4/GA/yLK1GdwEKNzSk1M7RlDlqPl1JefOlG2MXTGmXaAxMaMsB/XE34h4tH+7ANlrB7T2iV8OAlDlqOH9mPcsIBPKlF3R16Ad7GwlxoVberYAKCrKv1ghfmkg5sPldLIzZVsLqLErxpC9doAlp3aMICNurlGyVpRdSAu/HqS1Q58rd1JUlI87P1UtKsrHXlGG3e1HCOoov+x2wiX3RxT+o49L1IgutXxVUCfDIxNfLraQDI+M3e3/NdCXbhohqBNfLrIsVzZqmoT6dmXG0SBLTrmJLxd/CVRLECXcDGFaSC1TmHE0yKg4B0P2uxiy38WoOAePHaptAfHYoVqG7HcxcGc5o+IcfFfgsbQUPoYoSa213BbE78oGucTSwpJTbobFFjNgbQHdvi6g8/Z6Om+vZ8h+VxsQE7T/97UMWFvA+Og0UvIryfDIZBQ4CeXvt8a5IAhAY/RImlJWUaHrPHaolhuXFXHN+8e58qNcbomq5P6t3xG973WePLzPgnnsUG0LiP7f1zJwZzk3LisyctfSOFxOJ4lfLzYToQubxu/KpmpWBFWzInguOokrP8ql7/zDRMxLpFfUabasHwlZNnITbmbgznI6b6+3Bu7/fa2lrW5fF9Ar6jQD1hYwLLaYx5fupdi+EiGok748koa4qa010xKkKWUV2UM7kd6vB7tH9yfpnUFkLzQiZOGWnmgHO9N4oie9ok5bA4YPbkqvqNNc8/5xIuYl8tSaLOLLRXambENXF+PxNJD0ziAanVHhYaEliH1lJD/1iqD0qSsIzu2M/N550TZ3QjvYmS3rR1qDtwdhgpgwnabGMj46zRjQsxJdXYw7P1X1pY0GuaRjkMKxPah5qxuV8y6nct7l1LzVDfdyo6miHexM+ou9mblwKfdv/Y77t37HNe8fbwMQDhIxL5FOU2PZklqLJjUYdU7wWxBuN+ricBAF0KQG6pcNovZpw0fCQao/MEBcu7tSOLYHjnu7EZzbmeDczqyfNokrP8ptMXi4XDnzAJ0n72TIgn1oUoMB4VlpgIjj24I0payi9KkrqHj+Ssth2wM5c38f8p68D2nbHKRtc3h86d42A/eZHsOVMw9Y0nXKJmxDvyS1NA70z8Gz0qh5hNvbzpr6ZYMofzyiBUwLkOVdjfR/eVcao0dSl/d1aHx0GhHzEi0TXDnzAJ2mxtJpaixdp2yypM/0GLrcs5D3Y94ztNDsK7qjuxmzDBBz2rYGqZoVQc1b3dr4yfppk+g8eWeLd91aAxGPrqPbyKV0G7mUiEfXMWdz+nmQ0Jsgn1AbT/SkMXrkeZC6vK9DpU9d0S5I5bzLqf6gq6UV7WBn5q9+zDJBuEQ8us4SE6LLPQvpcs9CjmSW4ndlo1XPNBxWLiE34WbSX+wNapEBEsrfT/njERSO7WGBmDA1b3Wj9KkrSO/Xg1WjBjJl/CT+8sQ8a0BT/eGDhwN0uWchXe94ia07YkE+oSLc3gxyQt2yfiSrRg0E+YRqgRSO7UHh2B4UT7ragqmcdznFk67mp14ROO7txpTxk7AN/bLFgN1GLsU29EvrejiACdG59xQjKgu3GzVP9UwIvcmCVwYb102NmBHVBDFNVDUrgjP39yF98E0E5xox5Dcj5lsDhwOYQObg4dK59xR2RV8D4njEo/0NIEd3dkVfgy9t9HkfMTWSO6pXG63kjupF8aSrqXj+SoJzO1M573KmjJ/Eb0bM5y9PzGPBK4Mp3GKUEFvWj+Q3I+a3AOjcewp/eWKesUQ0T1mz2att7oSU9+F5EE2SqXvpSbKHdrIGNmHCoapmRVgh33LezZ3QNncyloGDnVnwyuA2IFvWj0Q+dplREzu6Wy0r9/KubVvg9pWRpPfrwZn7+1haMSHCxdSM/J4RWWufjiC9Xw/m9PgtN9w0uo1JbrhpNI0njAXTrAIbT/TEvb4LjdEj2641vqQjpPfrQfrgm1qYKHxKlz51BbmjerFj4G2WtAYwtWDKglcGG2ZoXrldu43AWDUrAmnbnLaRVZMayHvyPn7qZThoa38pfeoKap+OIDi3M6tGDeSGm0a3GTT82g03jeaGm0bj3H4d8rHLrN0I93LDpDsG3kb68si2a425hfZTrwjSB9/UBiZcM+YM6ghoyvhJpL/Ym+yFhknc67tYQVF+z3gjc3r8Fuf32zpOFTMeHXpRGDNfMYF2j+7PqlEDWTVqIOkv9rZ8SNvcCff6LlTOu9yK1Okv9mZOj9+S8ehQNKmBDhs17vxU9adeES1gwoHKH49oFyhcwhfKynmXWzOu4vkryR7aieyhnQjl7+84QzNNJGzbxN7uV1gw7WmntYZaLw2mmNdrn44ge2gnztzfx9od7zBnDa9t0pdHtgsTDhRustaaCndwEyLj0aG481PVS9r3FSUJj6eBrConMZHvnodpntrh2gkHCgcLl/TBN7G3+xXGLMlIo0LXjU7ixeoaUZIQ3C7OlTtJya8kJvJddgy8DctvWgGFaylcHPd2Y2/3K5jT47esGjWQrTtiyapy4nI6jUrvUmpfUytFRfmkZ6SxdUcs66dNYsfA2ywNtQBrJeb/dgy8jZjId/kx4YgF4fP6Ln1L3uyhhWvnSGYpOw6lEBP5LuunTWLDAw+x4YGHrAi74YGHWD9tEuunTSIm8l227ohtAyBK0i8/pNDagTVJxuf1YXf7OVfuJKvKMF16RhrpGWkcySwlJb+SrCqn1awRgjqaJP9nO0b/Zxo1v+ahS0ZqKJ9QCX5rJMyhN42aRj6h/udB5BKjiAp+i64uNrJ2M0Vs3rUiy4aU92G42X49iCYZDZjUMoX4ctFIcILfGgVU6E0LwEyCxKP98aWNxpc2GvFof+RjlyHlfdjxWnOxh93tJya5kIWHnDx2qJbnopP4NCmaYvtKC0LL6WYkQps70RA3laaUVbjzU1V7RRn2ijK8BbkWUJsM7VIAog7k8MyuPKtD1AJA/9zQQpYN9/oubFk/kpkLl7J4a0KbtrdZa/vSRrfMWS8GcSSzlGd25TH5VIjptTpR9T5SS+OMsrHZD3RHd7SDnTm1YwSzY2KsTtL46DSei07iSGZpm/tKeR8a5gnf0+vI8zfE5zAstpjptTrvifBJeeZ5LTQDkGXDtbsr0fte59mjDmaWaUyv1ZlZpvH3XJlRcQ6Grj5OTHJhy/t7VhrpwMVAog7kMCrOwcs+nZWaccak2L7S0oLpC6d2jGDJiUyWN8E6FVZqsLwJ5ruwYO5O9jFoUSIb4nPOT+/gtxf3kZjkQobFFreAaHRGGZoQbm+hhWd25fHsUQevHilgbo7bAmoNM2S/i6Grj3Mks9Tolcgn1Hb39MzHuXInw9edZrJd4z3xPISuLrYgCrf0ZOuOWKLzVFLLFDIKmlfr5EJmHMxhfoWvDczkUyELxl5RduFUUZNkIvdm8+BpkZd9eocQPyYc6XDnocpRQ+TebObmuFmptdTK5FMhBqwt4K1vMi4cWTMKnIyKczDZrvFJeWaHEBdrbVc5aphxMIflTR1rJaPA2TFI1IEc7k72tZwdYRCLtyZc6h4MMcmF7WrlwRSRAWsLiNyb3T6Iz+vjmV15jIpztIHwxN/I7JgY4svFS47CHk9DG62Y5hm4s5zx0Wntb0CnlikMiy3m06ToFpFSO9iZnSnbeGZXHkcyS8kocF6SHMksZc7m9AuaJyW/si3IltRaZsfEGNM09KZVs2bEzWV5EyzLlXn1SEG7MuNgTruy5JS73dlzd7IvPMi1BIlJLmRnyjbLJFawar7ZHi5NdrSS9jRyd7KPXlGnzQDXyjSlcYY2mk1SuKUnS05kslI7f9M9/HKgdaoh74nn/cR02NV7M9t2A9A/t/qf2uZOvB/zHvNdxk3Mm0bV+36VzK8wxHTWVutPmEbkE6q1hjQ3/yefCvGeeB7k1SPGlLsUeeubDOtnezJnczpvfZPBuXJnGEjzAqSri9FyulG4pSf3b/3OCvErNQNmxsEczpU70ST5kuWXJc9yiZXemQ3du5N9TK/VedmnW1qZm+M+v3r+gpTS42nA42nA5XRa4vE0hFd8zSDBb63cInvtAAYtSuTuZB+T7ZoFYy7tz+zK6+igQZtHRoGTyL3ZLab4M7vyGB+dxpAF+1i8NaEliLmWyNndsa+MZPi60/T/vpaJhTKT7ZqllZWaoZW3vsnA42m4IMS5cifPRScxN8fNeyK87NOZXqszsdDITa55/3i4dgVb0OPUTG2IR/vjzk9Vt6Qau5R3J/uYWCi3MJEJM2dzOkcyS80Q3WKrPia50IIIX2cmnwrxYIpIr6jTPBed1Mo0apFgpv0NcVMR3C5ESWLO5nS6fV3Ag6fFdmHmV/iYcTCHyL3ZRB3IsSRybzbP7MpjfoWvXYj+39cyZME+c7aEgTQ36smy0RA31dostrv9DF193IIJ9xcTxgSam+O2xAQwg9fMMo2JhTIPnjYgBi1KbC+RPq8REyR8iT9X7rRgWptpvssYLBwqHGB6rc7fc2ULYsh+F4MWJbLjUErH09c8ytcaxNTMCxtyGLC2oIUDT6/VO5TJdkMLJsTAneUMWpTYNotvE0eaj3rKxy6zun2t69mdKdt4fOley4lN35ls11pIOIC51D8XnWQu9xcGUQCteibyscuM5n31TKNqD5fm1H9DfA7PRScxdPVxhsUWMyy22Dq4MGS/i2GxxQxfd9oC2HEopb1WVcdtCU2Sqcv7OmTWpGbRLOV9SCh/P0GPUwvPvDIKnMQkFxK5N5s5m9N5LjqJ56KTeOubDFbvzSQlv7LN1P5FxzZ8Xp918v8SWk5WsWStLbr0a5oLHRdY/+GjPP8vtq7+0yCiJOHz+hDcLlxOJ2bzxeV0Irhdlk/9x0B8Xh9VjhoEt6s5rZTaFU1qQHC7qHLU/PpZ05EGqhw1uJxO0CVESSIlv5KoAznM2ZxufTJgzuZ0og7kkJJfaR1mcjmdVDlqflkc6ahSs1eUWdMzJrmQQYsSrYMJNy4raiHmYQWzD2IC2SvKLpa/dAzi8/qsc6cZBU6GLNjHlTMPcEtUJVMSdd45qRGdp7KxDOvDPu+c1JhxNMgtUZVcOfMAQxbss0K7vaLsQqbq+GCtCbEhPodOU2O58qNcZhwNsrMK4t0Xlp1VMONokCs/yqXT1FgrE7sATPvbJK0hblxWxDsnNWugvc7zcqFry3JlbomqbANzSdskpk9kFDjpOmWTpQnzne6sMgbbWWWYY8kpN0tOuYnOU1v8z9TcOyc1blxWRNcpmwwz6dLFjxr7vD5rY+eO13YSMS+Rh/co1iAby4wBluXKLDnl5rsCD1lVxk7FdwUelpxysyxXbvHcjWUwYb9CxLxE7nhtp7X10spELUHMMiHqQA6dJ+9k8KYaJh1u6ZRLTrnZklrb+hS3lURtSa1lySm39fyNZTAlUWfwpho6T95p1rqtS5LzICapJsmWNkbEBpiSqLMs1/gY3DsntfAuT4tDlkrYtci92bxzUmNjmaG9KYk6I2IDbbTStsBqjhma1EBKfiVdp2xiwNoCHt6jMOmwxjsnNev46KWUkaIksfCQk2W5Mu+c1Jh0WGPCfoUBawvoOmWT1d4Miy3nQczIuXpvJp2mxjJ4Uw0T9hsg09KM6fhcdBIxyYWXJM9FJzHjaJBpaTDpsAEzeFMNnabGGhVec+RtA1LlqAFd4vGley0Q8wZTEnWmpWGdWX3sUC3PHnW0K+b/n0qoZ1oaTEszfCQc5PGle0GXwv0k7PxI87S9EMjMMo35rvMdILPDbErrzlA4iOmw4SBh0/iXgUxLg8mnQvw9V2Zmmdau/D1XtpoxpiYe3qPw8B6FW6IqreOCvwpkWhqMinMwaFEi46PTfrFMXG38HLr6OHe8ttPykXZNYzrr4q0JdJoay4C1BS2cdfCmGuZsTrd6Hv/T5ozZJ7no9L1xWZE1fU0bD193unXx3GESFZNcyIb4nDazaUN8Dh6PkTy1O307CmgT9itM2K9YWnkuOumi26wTV6dZR43NXOXKj3LpPHknEY+us0DaDWiWnwCr92bSdcomBm+q4eE9ShsThTXh2jRn5mxOZ/CmmjYzZkRsgE5TY40Q33bhu/iiF66VcJjh604TuTfbUnnk3myGrzttQZgzZtJhzQrvfabHWGNccNELnz2tfSUcJjxADVhbwIC1BdYsMyOp+fyH9yhWGnAks/TS0gDTV4qK8q2NxU5TY7klqrIFTDhQ6+gZ/hwzdoSbpKgo/9LPj5hnR8yUwEwVw810MRkRG7BSRXPpLyrKv/RUsT2YI5mlLZLnEbEBK1q2lhGxASt5vuO1nZY5ioryL5TJX7icENwuioryjV1rr4+oAzkMWbDvouXEkAX7iDqQg8/rQ5MaLgZxaQWWJslWSWkWWBkFzl9UYP2PvgjFPNrj8/osM/2YcIQfE46QnpFmfL7K7SLocWpBj1Mz6+D0jLQWzzPb3b/6aI8SVnCbvXTTVOZxno6kqCjfKlPNUH4pIP9XPGz/N319UFnrf2iKLGi6LmggqCBoIOi6JuiqIqCrgqIrgqyrgoYu6JpiiK4LKgigCpquCCEdQdVVAU0VdP2iMGW29tplmtbcQNQ1QEXXNDQdQGsWHZBbvdQsKkTQfaiaBJrc/PyLPpQ2zqqbL9U10GV0TUbTZUCyQAoaJPaVinx5RmbVKZnVWRpf56r8WKlQFww2Q4bf8VdMXwsEtfkdGb97xSAb8yRG7df4zYYQ3deEsK2WsK1UsK1U6LIqxJWfKQzcEODVw0GS7KbG1F8Pout6C7WuL5Dpv1PBtlLEFgWXfyHTY61Ery91rvkiwLWfB7h6jcxV/5LoskLF9gl0+tjLI7FesuuxzKnrHeqneQdL143Bjacj6wqg4ZFUph8JYvusCdsXIldvhGvXi/T+SuS6dQrXrZO4fp3Ib76UuH5NiD6fi1z/mcgNnwa5epWMbbHG1StEvsoSjbeoq2i60h6MYNN1XTAhNF1vdlBoVFSG7/Nh+1Ti2o1Brl8v03uDyDVfN3DDVz5u+FKh15cKvdbp9FoHvT5X6PW5wjVr4LrPda6NkugTJdL1EwXbIpkVx5sdGaXZ8S9gGgNIJ6ipPHgghO3TED23h+ixTafXZpmb1ofos0ml+9dw1VcaV3wapMvKIF1WSVz+qULPzxV6faZw9Wc613yq0Xt1iN9Ehei+WMG2QObz03JHDtxsGk07P2XRmZ/hx7ZG5rqtMjdubqTHFonrNov8doPMZRvA9pmPqz8X+MNWhb/tkrg/VuGWaJXLPmmk85Imen6m0+sz6BMlcsNqP9etVujysU63jwIcrwy1N6UFm6Zrgma4KKBxrE7lyq999PnaT58dcMNWjV5bFa7d6sP2lcj/+szP6/FNHK2SqQtpSKqIKItUN2psyJH52yYXtkV+uq9UuP5fMj1XqVy9WuWGFSE6LQgxbHMQv6kVXW92B12wKZouSEjGNNMVJvwgYdugcGOsym+2q/TZqnD9dh3bVz5u3h4guVJtnpJa808zkJlBMMS7SQG6vB/gimUKvVdK9Fmu0nu5zLXLZGzvaWzLDhggmoysqwYIKoKqG+rKqVO5douP62JUfvutxg2xCn1iZTpv0rgpRuF0XQAIgRJElSUURUWWZWRZRpFlgrIKeIEg7yaC7X2FXkslei+XDVkmY1sQ4pFNDaA3hwcdNF0XbGjNZwNQWXZaxrZV5XexMjftFLnpW4ne34rYNvjZUywBQUJqEEkMoEk6oqIgySqipCCKEt6Qis8fRNEaAB+TtijYInV6Lwtx7VKRPstkIj5S6PGBRGFtwFCgApquCDYFTQANXZeZkiARsVPnlu9kfhcr0/cbiYivA4w94DM0oet4VQVJUQiJGiFRIiTKBEMSAX+QhoBIvU/C1SQCfpIKGrl8kZerFitcu0Tkuk9ErlsiYXtDYuMpYyobE0gVbIouC6DiDsgMiwtx406Z/rs0+u6WGPCNSI8tIZbnSoCCEvITkBRkWSMUkAgEJbz+EE2+IA3eAPUNjTR6fNTWSni9PuoFN/d8KtBpkcg1n3jp82GQ3h/6sc33seAHb/P6pYOmCTY0VQCNEkHhrgMhfrdL5k/fafT/XqT/boU+sRI/2r0AhESFYFDCF1TwBSWa/CE8TQHcjQFcDX6cdQGq63w43PWU1AoEmup4emMjtvl+enzop/d7Aa57N4Btvo/Z37jCHBzBpuqaAHDOHWDo/iD99in8+XuZO/er/H6fxsB/h0irDgGqoYGAhOAL0eALUd/oo87TRK2nCUddI3anQKXTTUl1DUVVNXga6nh2mwvb6066L3Bz3btOekU2YXtd5MVNDmuVVtEFm6brAmiUu4OMPODnjv0idx+UGHpQ4q6DEnf928+h0iCg0egN0OgXqW8MUCd4cXm81LgbqHIJlDs9lFd5KK90U1hWQ3GlgLOqlrs/rsQ2q45rFjq57q0yukc6sL3iYc62akBDR0fRNcGmq5oAQYSAyuQEibsPhnjgkMYD8T4ePOTnv/ZrfJrtBTWE4A3ibvRTJ3hx1jdRXddApbOeMoebEruL3Ao3p8vqOVVSR1JuDZkFtbywvgDb0zl0eqmanm+Wct2bFdhmlvP2Po/hH6qIrmiCTdNUAVVElTVeyfTz10My435UGHNE5JGfJIYf1ZiV4kFo8uILBKirD+LwBHC43Dhq6ymurqfAUU9ORS05RSU0NHmQVRW/JCMqOho6354U6DEri04z8+nxWim2fxSx8ZgLEAlJCqoiCzZZUwVZVECDjUVNDD8s8sRRlSmJOs8mwbPHZJ466iO2yI8aDNJU56a8tpGqaicOZx2FVfWcLa8lq7CMBn8IHfAGZQKSik/SQAoBOjEZtdiezqTTS/l0fzmPrFIBNB9CUCcoSYJN0TTBKymgS5TXBXn8pwCTj8lMT1WZmarx+nGR2Rk680+GOFleh9/bgMtZR3V1HYWVLvJKajiTV0pVjRsV8IVEgrLaLApeERSCAAx5/xS2+48zZvlZAmKIQFMTHq+PppAi2DRdFQIyyKIfRImoMz6ePO7lpUyJl08r/PN0iMjTEh9kS6zNEUgp92GvaaDAXstZh4DLG0JoChAMyviDMn5Jxi/K+EISIVnFr0h4JWPZ33a8mNteSCI6vhpZbMDhaqChyYfHHxRsmhYURBECoRDoMvkukVfTFN7IlHk7W+aDXIlVOSHW5ob4qhi2F4v8WNLIiSov5wLgkVVERSUYMqa2LyTjF1UCkkpQ1vGLImJAxCsai2SdKFJQ6aG0ooqK+gBuVxOCTxBsuq4IkqQSFCVCkgyqzg8lXt5J9/H+WViVJ7G+KMSOEoVdJSp77DJxdRrH3Rq5goLDJyMERRqCIt6QbPiHqBAQFSRJJSCrhGSZJklDUs/nIefsNRRXe3DWefE0NjUf21BURFEiGDRWVH9I5Nu8Rt7Pk/lXocbWIpFvKzT2VSr8YJdIcEqk1Svke2TsPhV3SMYTEmkISngDCr6QTFBSCUkqQUnFL2kEJUNLflFF1aGuyUepow6HuxG34DdyVkVRkCQFUVLxBWR0ScEfFPmuuIG1hTIxpSr/rpA46FBIqJHJdGmcqVPJa1Co9MrUBiTcQQlPQKYhoNAUUvCJCn5JJSApBCTZEr8oEVJU/IpKiaOOmnov9Q1+QyOqqiErGqKiIYk6/mAATQ4QalRItPvZU+EnvkrmxxqJRJdIVp1KTr1GQaNChVei2idTE9BwBRTqAzKeoEyjKNMkKvglhaCkNAMZogAeX4DS6npcDQE8jYHmM0aajqLqyLJOSNbwSTJev0woEKCxyU9OdZCEkgAJ1UGSBYWsBo3cRihq0qj0KVT5ZBwBjdqQRn1IRhBVGiWVRlklqOiIikZQ1hAV4ytjJE2n0ummqt6LU/AjNAYEm64jaBqoqo6iaEiKhiirBESVhkAQr9eH0ChSUu3nVGkdGY4mUmt8ZLoC5DWoFDUplHpVKnw6VT6ZWn+IuqCEJ6TQEFINzUgSflXFJ8nUe304XALVdQ3UNwaob/TT5A0ZILoO4TCyrBKSZHxBGcEfxNPgpdETwO32U+ZoIKesnrPlHrLtbn6urCfPXk+B3U2R3cO5qgbOVTVQUilwrkKgtEqguLKe4sp6yhwNlNg9VLkEhKYgjd4QTX6RYFA+X2Dpuo6maaiqiqqqKIqGKKn4QwrekERjIIC70YenMUBjk0S9EKK23our3ovb48Xj8SI0BfD4ROq9IdyNQeoa/Lg8AZxuPzV1PuobRASfguAN0egP4Q1KBEMykqwKNkAxMnpDNM1oSxhQGrKiI6oqTapIkyTiDYUIiDLBkEwoICOGjHghKxqKqqCoEooqEVJFgkqIkBIiKIsEpBB+MYA/FMAXkgiICiHRmK2KoilWo6bZRIKu61bjRdd1QdEQVBVBkzRBlVRBFhVBVTRBUXRBknVB1hAUECQQNF0XUHVB13RB0XRBVDRBUjRBUTVBUlRBlBRBlGQhJGuCJOuCouiCpuqCqqpl/7Eemqor5HnS2Ja/hPezpvCP1PuYlfo3vvo5EnfA0baH9qs+CKZpBIIh7DUuyuw1lNprqHDU4mnwoqoamq5xyn2YVTkv8cKJO3n+TH+eTB7Ao/H9eSr+TnbmrfyfgdiddZzKKaK0yklhuYN6oWVfvabay+6Tu3gzaSJPpPZm9E9XMmnvH1n60wKSanZypuEg35WuZlrCMLb9vPSXgzicdWTkFLX7vya5Dq/spk62s8v1AW+cu53ns29kSd6z/Fi9mZ/L8tpqVFfZeHYxBe7MSwdJy85v8Xd1oJwDFRtZlTeTD88+wcKsMSzMGsv8rL8wNbMnc7LuJN6xg6AcsF6TW1xBkzfQct9P8pDrSkfT1QuDKKrKz8UV1t+V3kKi89/m1YyhvHlyMPOz/ouFZ4fwYe59fJAzjLfO3s66wuep8p7jbF0iUTkzOe76/rzZ6jxUVteGtch06gL2C4PIikJFtcv6e3/ZeuamDOHNU//NivwxfFY8jnXlE/iyYiKflz/Eh4WD2Gv/CL/YQIJjI2+dvJvXTt7FtJS+LPt5OvVBY383KEoUlFaGzSz5wqb5ubC0WSsyG3PfZUbKnXzw8wOsKX6EdWUT+NI+nq8cY1nrGMnikjuJd0Xhld1sr3iTt37+IyuLHmZN0WMszxnPzLSBvJnxMMWNPxv7vUITLrdw8VlzMswnNud+xD+O3cGy3LF8ce5R1pZN4IuKsXzlGM0X1SP4uPJ2jgpraJAcfFb+CJHnbuOz8pF8UT6OL0om8nnRJFblPcrLaXfxxolROHzGd2idq7xIHBEavTQFQwAcLNvMP5Lu5JOcsawpmsRnJROIKnuYtVWjWVP9Vz6q7McRz0pUTSa2Zh6LSgeytOJPfGa/j3UVY1lTMoFPz01kdcEjLM95hNmp/8UHmU+j6MYnlrJyz3UMknHW0IbDW8rLyfexIGs4nxU8zqqi8Xx07gGiKkfyheN+ltnvJEFYGdYOFWlUqjniWcGK8iFElQ1jTek4Pi2awOqCR1iZ9wgfnx3Hs4l9+aHc+BqH2voGRFFqC+JpaEKSjOR2Y84iZqX8majcx1ieN57Xc+/hvXPD+aziAZaX30VGY0yH0/1s00E+KR7KquL7+ezceFbnT2BFzkSW5Uzg7VP38UbKQ3hCdc1aKWoLktHsG06/nbnJ9/H+6VGsyJnIC9l38kreMNaUPsKSkkHsdy26aABMcK3lw4L/5l9FY1mdP56lOeP55Ox4Psh+mOeT7+BAyUZj17O8qiWIKMkUlNoBOFQaw4zkQSw+M5bZp+7in7mPsKnkFVade4DPSsfTJNVeFCSk+lhbPIVl+Q+wMnccS8+OY/GZsXxwZjTTj9/OkqwXACi3O/H5A+dBKhy1lFQac33t2bf5R/KdvJnxFxadnkSyYzuf5j3BssIR/Kt4DBvLp/NF2dOsqXiSNRVPsKbyCeNnxZN8XjaFz4ufJrr4Bf5V8Agr8h5iWc5YPs4ey4enR/P+6YeYnfZn3kh9CAUfqgz2Gtd5kLOFpZTYjUMHH516jmlJA3jjxHCO2XexteBtFpwZyqqC0awo+huLCv7Eu4W38V7x73mvtD/vl/Xl/bJ+vFfye94tuo2F+X/g3dw/szT/b6zIHcMnZ0fz0ZmHWXT6ISKzRvJq5mBeSh5MSeNZyzyyrBggWTlFlNsNssiMKYz9oQe7i/9FmmM/r6bezZKfx7Is5yGW5f+NFYUjWHXuflaXDmN12V+JKhtGVNkwVpX9lZXFw1lRNILl+Q/ySc6DfHRmFIuyRhF5ciRvZ/6NNzPvZ3baIJ5N+AM/1xsfXcg9V47XH2wLMidpFE/9eAcVQg7Lsp7j9fShfHTmIT4+M4rIrKG8ljGAeSf78eaZfszP7sc/z/bln9n9mH+mH29m9eO1jP7MPfF7ZibfxvSE3zP1UD+eiruVxw/cxIT9fRj+764Mje3M6bqjAOQVl+MPhgyQvHPllFQapnkhfgRf5y7haNV3PJvwe945+QDvnnyAf2bcQ0zR22S7fySzbj+Z7n2cdO/jZP1eTtbvI9O9j8y6fWS49pHm3Edq9T6OV+0luXIPRyt2k1C+i/jybzhYupUfSrfjV40wX1zhQNN0A8RR66bEbjjr5p+Xc9IRz9snJvJ88h94O/N+3s64j1dS7mJLXuT/v0e/vT6qa93nnVXXdXLOlRtJi6qSWLmL8Yd682rGvcxLG8qbJ4byRuoQXj56L+UNuRcdoDHk5kDJNvaXbuZA2Rb2l21hX9nX7C3byNaCKJKr4pqnbw3+QLBlQDttxn4dPsh4hseP3sjcjP/m5dRBvJYymNdTBjMtvh8rT865KMja0wsZvqsr4/f3ZNyBnjx88CpGxV3BiAM2bt5iY8PPKwz/KKlsG1lDooTgCRJAYPKR/jyb2pcZaQOZdfyPzDn+J145/l/MSfojU364lW05yzuE2F30FU/80JcZSQN5+fifmH38Tmam3MGM1Dt4LOE6pv90DyHFCGLZ+SXtL3pn88rJCR5hbPy1TEq6jqnJv2XGsduZdfwPzD52By8n/5FZSX9g8sGbeDflGU7VHMUTqKMhVM/Z2hMsSZ/JY3G38I/E25l77I/MOv4HZhy/nRkptzE1+Rbu+beNhMrvjLEKSi+cj0T+8AaPZfTi2eQ/8Gj89fz96C3MSB7AjOTfMzPpNmYn3c7MowN4/IdrmXKoPy8l3MtLP/2Fpw7fxiMHr+HFxH7MTrqNmUm/56XkAbyY3I/pyb/jr/tsRJ542hqnOGydaRdkxv6J/DXBxvflX/Fd0Rru2W3jmYTrmZnUnxlJ/ZhxtB+zjg5g1tH+vJBwM1Pjr+fZ+Ot5PuFmZiX2Y9ZR43kvJfXlpeR+PJ90M3/da2Nm4gME5MZ2c5F2QV5OeYA/7rZxrOYgANE/f8S933ViTFxXZiX1ZfbRvsxK7MusxFuZnXgrs8JkZuKtzEi8lZlHf8espL48Gd+Lu3fbeDVpLA1BY+kvc7T7ZTktQUQlyLQjg/nzv20cyo+zrsdX7OKR/bcybLeNp368hpd+uok5ib9lbuKtzfI75ib+jtmJv2PGT7fwfMJveOj7zty/O4JPs+YjKsYUdTc04Wloav/YRusLz/04lAeTIsgsPENewfnc0is1EH32Qx47MICH913F+O//F+O/t/H4wW7877gIHtnfhXHfd2Hs91cyZl9v3k19lgLPaev15TV1NDR6Oz4/0vrC26ceYVhcL45X/GB4d2Eljf7Q+cJI9pHqiGPVqVeZd+wRZicOZ0bCvbyS9DAfpD3PnnNfUuO3ny9NVI2T+eVI8oVPGrUB2ZsfzX1HehJTtMK6FgyJZOYW0+gXf1EIz8wro9LhvKTn2lrugkMoFOS5n/7C0APXYK8tb3GepMrh5HB8Cmknz5JbXEpBSQVlFbVU2N0UlVWRW1RK1s95/JCQzMkzPyPLMpqm4ff7CQQChEIhJElCURQ0TcPsVOm6fn6tCT+oUOkq4bGE27n/qzv4KeMIwVCQQCBAbV0ttXW1VFRWkJ19lrS0DJKSj5F4NInk5OOcPHmK/Px8amtrcbvd1NTU4HQ6cbvdNDU1WTCyLKOqaguYDmvfgNzE4bIYdpWv4UT5EezuMkQl9B877PT/DQC7cLwx8LR3hQAAAABJRU5ErkJggg==) no-repeat;padding-left:40px}\n        .browser .browser-firefox{background-position:0 -34px}\n        .browser .browser-ie{background-position:0 -68px;margin-left:0px}\n        .browser .browser-360{background-position:0 -170px;margin-left: -27px}\n    </style>\n</head>\n<body style=\"margin-top:50px\">\n<h1>请升级您的浏览器，以便我们更好的为您提供服务！</h1>\n<p>您正在使用 Internet Explorer 的早期版本（IE11以下版本或使用该内核的浏览器）。这意味着在升级浏览器前，您将无法访问此网站。</p>\n<hr>\n<h2>请注意：微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束</h2>\n<p>自 2016 年 1 月 12 日起，Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新，您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击，它们可以窃取或损害您的业务数据和信息。请参阅 <a href=\"https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support\">微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明</a> 。</p>\n<hr>\n<h2>您可以选择更先进的浏览器</h2>\n<p>推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。</p>\n<ul class=\"browser\">\n    <li class=\"browser-chrome\"><a href=\"https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1\"> 谷歌浏览器<span>Google Chrome</span></a></li>\n    <li class=\"browser-firefox\"><a href=\"https://www.mozilla.org/zh-CN/firefox/new/\"> 火狐浏览器<span>Mozilla Firefox</span></a></li>\n    <li class=\"browser-ie\"><a href=\"https://windows.microsoft.com/zh-cn/internet-explorer/download-ie\"> IE 11 浏览器<span>Internet Explorer</span></a></li>\n    <li class=\"browser-360\"><a href=\"http://se.360.cn/\"> 360安全浏览器<span>360 Chrome</span></a></li>\n    <div class=\"clean\"></div>\n</ul>\n<hr>\n</body>\n</html>"
  },
  {
    "path": "vue_campus_admin/public/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n    <meta name=\"renderer\" content=\"webkit\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title><%= webpackConfig.name %></title>\n    <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->\n\t  <style>\n    html,\n    body,\n    #app {\n      height: 100%;\n      margin: 0px;\n      padding: 0px;\n    }\n    .chromeframe {\n      margin: 0.2em 0;\n      background: #ccc;\n      color: #000;\n      padding: 0.2em 0;\n    }\n\n    #loader-wrapper {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      z-index: 999999;\n    }\n\n    #loader {\n      display: block;\n      position: relative;\n      left: 50%;\n      top: 50%;\n      width: 150px;\n      height: 150px;\n      margin: -75px 0 0 -75px;\n      border-radius: 50%;\n      border: 3px solid transparent;\n      border-top-color: #FFF;\n      -webkit-animation: spin 2s linear infinite;\n      -ms-animation: spin 2s linear infinite;\n      -moz-animation: spin 2s linear infinite;\n      -o-animation: spin 2s linear infinite;\n      animation: spin 2s linear infinite;\n      z-index: 1001;\n    }\n\n    #loader:before {\n      content: \"\";\n      position: absolute;\n      top: 5px;\n      left: 5px;\n      right: 5px;\n      bottom: 5px;\n      border-radius: 50%;\n      border: 3px solid transparent;\n      border-top-color: #FFF;\n      -webkit-animation: spin 3s linear infinite;\n      -moz-animation: spin 3s linear infinite;\n      -o-animation: spin 3s linear infinite;\n      -ms-animation: spin 3s linear infinite;\n      animation: spin 3s linear infinite;\n    }\n\n    #loader:after {\n      content: \"\";\n      position: absolute;\n      top: 15px;\n      left: 15px;\n      right: 15px;\n      bottom: 15px;\n      border-radius: 50%;\n      border: 3px solid transparent;\n      border-top-color: #FFF;\n      -moz-animation: spin 1.5s linear infinite;\n      -o-animation: spin 1.5s linear infinite;\n      -ms-animation: spin 1.5s linear infinite;\n      -webkit-animation: spin 1.5s linear infinite;\n      animation: spin 1.5s linear infinite;\n    }\n\n\n    @-webkit-keyframes spin {\n      0% {\n        -webkit-transform: rotate(0deg);\n        -ms-transform: rotate(0deg);\n        transform: rotate(0deg);\n      }\n      100% {\n        -webkit-transform: rotate(360deg);\n        -ms-transform: rotate(360deg);\n        transform: rotate(360deg);\n      }\n    }\n\n    @keyframes spin {\n      0% {\n        -webkit-transform: rotate(0deg);\n        -ms-transform: rotate(0deg);\n        transform: rotate(0deg);\n      }\n      100% {\n        -webkit-transform: rotate(360deg);\n        -ms-transform: rotate(360deg);\n        transform: rotate(360deg);\n      }\n    }\n\n\n    #loader-wrapper .loader-section {\n      position: fixed;\n      top: 0;\n      width: 51%;\n      height: 100%;\n      background: #7171C6;\n      z-index: 1000;\n      -webkit-transform: translateX(0);\n      -ms-transform: translateX(0);\n      transform: translateX(0);\n    }\n\n    #loader-wrapper .loader-section.section-left {\n      left: 0;\n    }\n\n    #loader-wrapper .loader-section.section-right {\n      right: 0;\n    }\n\n\n    .loaded #loader-wrapper .loader-section.section-left {\n      -webkit-transform: translateX(-100%);\n      -ms-transform: translateX(-100%);\n      transform: translateX(-100%);\n      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);\n      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);\n    }\n\n    .loaded #loader-wrapper .loader-section.section-right {\n      -webkit-transform: translateX(100%);\n      -ms-transform: translateX(100%);\n      transform: translateX(100%);\n      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);\n      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);\n    }\n\n    .loaded #loader {\n      opacity: 0;\n      -webkit-transition: all 0.3s ease-out;\n      transition: all 0.3s ease-out;\n    }\n\n    .loaded #loader-wrapper {\n      visibility: hidden;\n      -webkit-transform: translateY(-100%);\n      -ms-transform: translateY(-100%);\n      transform: translateY(-100%);\n      -webkit-transition: all 0.3s 1s ease-out;\n      transition: all 0.3s 1s ease-out;\n    }\n\n    .no-js #loader-wrapper {\n      display: none;\n    }\n\n    .no-js h1 {\n      color: #222222;\n    }\n\n    #loader-wrapper .load_title {\n      font-family: 'Open Sans';\n      color: #FFF;\n      font-size: 19px;\n      width: 100%;\n      text-align: center;\n      z-index: 9999999999999;\n      position: absolute;\n      top: 60%;\n      opacity: 1;\n      line-height: 30px;\n    }\n\n    #loader-wrapper .load_title span {\n      font-weight: normal;\n      font-style: italic;\n      font-size: 13px;\n      color: #FFF;\n      opacity: 0.5;\n    }\n  </style>\n  </head>\n  <body>\n    <div id=\"app\">\n\t    <div id=\"loader-wrapper\">\n\t\t    <div id=\"loader\"></div>\n\t\t    <div class=\"loader-section section-left\"></div>\n\t\t    <div class=\"loader-section section-right\"></div>\n\t\t    <div class=\"load_title\">正在加载系统资源，请耐心等待</div>\n        </div>\n\t</div>\n  </body>\n</html>\n"
  },
  {
    "path": "vue_campus_admin/public/robots.txt",
    "content": "User-agent: *\nDisallow: /"
  },
  {
    "path": "vue_campus_admin/src/App.vue",
    "content": "<template>\n  <div id=\"app\">\n    <router-view />\n  </div>\n</template>\n\n<script>\nexport default  {\n  name:  'App',\n    metaInfo() {\n        return {\n            title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title,\n            titleTemplate: title => {\n                return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE\n            }\n        }\n    }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/api/imt/item.js",
    "content": "import request from '@/utils/request'\n\n// 查询I茅台预约商品列列表\nexport function listItem() {\n    return request({\n        url: '/imt/item/list',\n        method: 'get',\n    })\n}\n\n\n\n// 刷新i茅台预约商品列表\nexport function refreshItem(itemId) {\n    return request({\n        url: '/imt/item/refresh',\n        method: 'get'\n    })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/imt/log.js",
    "content": "import request from '@/utils/request'\n\n// 查询操作日志列表\nexport function list(query) {\n  return request({\n    url: '/imt/log/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 删除操作日志\nexport function delOperlog(operId) {\n  return request({\n    url: '/imt/log/' + operId,\n    method: 'delete'\n  })\n}\n\n// 清空操作日志\nexport function cleanOperlog() {\n  return request({\n    url: '/imt/log/clean',\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/imt/shop.js",
    "content": "import request from '@/utils/request'\n\n// 查询i茅台商品列表\nexport function listShop(query) {\n  return request({\n    url: '/imt/shop/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 删除i茅台商品\nexport function refreshShop() {\n  return request({\n    url: '/imt/shop/refresh' ,\n    method: 'get'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/imt/user.js",
    "content": "import request from '@/utils/request'\n\n\n// reservation\nexport function reservation(mobile) {\n    return request({\n        url: '/imt/user/reservation',\n        method: 'get',\n        params: { mobile }\n    })\n}\n//travelReward\nexport function travelReward(mobile) {\n    return request({\n        url: '/imt/user/travelReward',\n        method: 'get',\n        params: { mobile }\n    })\n}\n\n\n// 发送验证码\nexport function sendCode(mobile, deviceId) {\n    return request({\n        url: '/imt/user/sendCode',\n        method: 'get',\n        params: { mobile: mobile, deviceId: deviceId }\n    })\n}\n\n// 查询I茅台用户列表\nexport function login(mobile, code, deviceId) {\n    return request({\n        url: '/imt/user/login',\n        method: 'get',\n        params: { mobile: mobile, code: code, deviceId: deviceId }\n    })\n}\n\n// 查询I茅台用户列表\nexport function listUser(query) {\n    return request({\n        url: '/imt/user/list',\n        method: 'get',\n        params: query\n    })\n}\n\n// 查询I茅台用户详细\nexport function getUser(mobile) {\n    return request({\n        url: '/imt/user/' + mobile,\n        method: 'get'\n    })\n}\n\n// 新增I茅台用户\nexport function addUser(data) {\n    return request({\n        url: '/imt/user',\n        method: 'post',\n        data: data\n    })\n}\n\n// 修改I茅台用户\nexport function updateUser(data) {\n    return request({\n        url: '/imt/user',\n        method: 'put',\n        data: data\n    })\n}\n\n// 删除I茅台用户\nexport function delUser(mobile) {\n    return request({\n        url: '/imt/user/' + mobile,\n        method: 'delete'\n    })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/login.js",
    "content": "import request from '@/utils/request'\n\n// 登录方法\nexport function login(username, password, code, uuid) {\n  const data = {\n    username,\n    password,\n    code,\n    uuid\n  }\n  return request({\n    url: '/login',\n    headers: {\n      isToken: false\n    },\n    method: 'post',\n    data: data\n  })\n}\n\n// 注册方法\nexport function register(data) {\n  return request({\n    url: '/register',\n    headers: {\n      isToken: false\n    },\n    method: 'post',\n    data: data\n  })\n}\n\n// 获取用户详细信息\nexport function getInfo() {\n  return request({\n    url: '/getInfo',\n    method: 'get'\n  })\n}\n\n// 退出方法\nexport function logout() {\n  return request({\n    url: '/logout',\n    method: 'post'\n  })\n}\n\n// 获取验证码\nexport function getCodeImg() {\n  return request({\n    url: '/captchaImage',\n    headers: {\n      isToken: false\n    },\n    method: 'get',\n    timeout: 20000\n  })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/menu.js",
    "content": "import request from '@/utils/request'\n\n// 获取路由\nexport const getRouters = () => {\n  return request({\n    url: '/getRouters',\n    method: 'get'\n  })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/monitor/cache.js",
    "content": "import request from '@/utils/request'\n\n// 查询缓存详细\nexport function getCache() {\n  return request({\n    url: '/monitor/cache',\n    method: 'get'\n  })\n}\n\n// 查询缓存名称列表\nexport function listCacheName() {\n  return request({\n    url: '/monitor/cache/getNames',\n    method: 'get'\n  })\n}\n\n// 查询缓存键名列表\nexport function listCacheKey(cacheName) {\n  return request({\n    url: '/monitor/cache/getKeys/' + cacheName,\n    method: 'get'\n  })\n}\n\n// 查询缓存内容\nexport function getCacheValue(cacheName, cacheKey) {\n  return request({\n    url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,\n    method: 'get'\n  })\n}\n\n// 清理指定名称缓存\nexport function clearCacheName(cacheName) {\n  return request({\n    url: '/monitor/cache/clearCacheName/' + cacheName,\n    method: 'delete'\n  })\n}\n\n// 清理指定键名缓存\nexport function clearCacheKey(cacheKey) {\n  return request({\n    url: '/monitor/cache/clearCacheKey/' + cacheKey,\n    method: 'delete'\n  })\n}\n\n// 清理全部缓存\nexport function clearCacheAll() {\n  return request({\n    url: '/monitor/cache/clearCacheAll',\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/monitor/job.js",
    "content": "import request from '@/utils/request'\n\n// 查询定时任务调度列表\nexport function listJob(query) {\n  return request({\n    url: '/monitor/job/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询定时任务调度详细\nexport function getJob(jobId) {\n  return request({\n    url: '/monitor/job/' + jobId,\n    method: 'get'\n  })\n}\n\n// 新增定时任务调度\nexport function addJob(data) {\n  return request({\n    url: '/monitor/job',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改定时任务调度\nexport function updateJob(data) {\n  return request({\n    url: '/monitor/job',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除定时任务调度\nexport function delJob(jobId) {\n  return request({\n    url: '/monitor/job/' + jobId,\n    method: 'delete'\n  })\n}\n\n// 任务状态修改\nexport function changeJobStatus(jobId, status) {\n  const data = {\n    jobId,\n    status\n  }\n  return request({\n    url: '/monitor/job/changeStatus',\n    method: 'put',\n    data: data\n  })\n}\n\n\n// 定时任务立即执行一次\nexport function runJob(jobId, jobGroup) {\n  const data = {\n    jobId,\n    jobGroup\n  }\n  return request({\n    url: '/monitor/job/run',\n    method: 'put',\n    data: data\n  })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/monitor/jobLog.js",
    "content": "import request from '@/utils/request'\n\n// 查询调度日志列表\nexport function listJobLog(query) {\n  return request({\n    url: '/monitor/jobLog/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 删除调度日志\nexport function delJobLog(jobLogId) {\n  return request({\n    url: '/monitor/jobLog/' + jobLogId,\n    method: 'delete'\n  })\n}\n\n// 清空调度日志\nexport function cleanJobLog() {\n  return request({\n    url: '/monitor/jobLog/clean',\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/monitor/logininfor.js",
    "content": "import request from '@/utils/request'\n\n// 查询登录日志列表\nexport function list(query) {\n  return request({\n    url: '/monitor/logininfor/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 删除登录日志\nexport function delLogininfor(infoId) {\n  return request({\n    url: '/monitor/logininfor/' + infoId,\n    method: 'delete'\n  })\n}\n\n// 解锁用户登录状态\nexport function unlockLogininfor(userName) {\n  return request({\n    url: '/monitor/logininfor/unlock/' + userName,\n    method: 'get'\n  })\n}\n\n// 清空登录日志\nexport function cleanLogininfor() {\n  return request({\n    url: '/monitor/logininfor/clean',\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/monitor/online.js",
    "content": "import request from '@/utils/request'\n\n// 查询在线用户列表\nexport function list(query) {\n  return request({\n    url: '/monitor/online/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 强退用户\nexport function forceLogout(tokenId) {\n  return request({\n    url: '/monitor/online/' + tokenId,\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/monitor/operlog.js",
    "content": "import request from '@/utils/request'\n\n// 查询操作日志列表\nexport function list(query) {\n  return request({\n    url: '/monitor/operlog/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 删除操作日志\nexport function delOperlog(operId) {\n  return request({\n    url: '/monitor/operlog/' + operId,\n    method: 'delete'\n  })\n}\n\n// 清空操作日志\nexport function cleanOperlog() {\n  return request({\n    url: '/monitor/operlog/clean',\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/monitor/server.js",
    "content": "import request from '@/utils/request'\n\n// 获取服务信息\nexport function getServer() {\n  return request({\n    url: '/monitor/server',\n    method: 'get'\n  })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/system/config.js",
    "content": "import request from '@/utils/request'\n\n// 查询参数列表\nexport function listConfig(query) {\n  return request({\n    url: '/system/config/page',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询参数详细\nexport function getConfig(configId) {\n  return request({\n    url: '/system/config/' + configId,\n    method: 'get'\n  })\n}\n\n// 根据参数键名查询参数值\nexport function getConfigKey(configKey) {\n  return request({\n    url: '/system/config/configKey/' + configKey,\n    method: 'get'\n  })\n}\n\n// 新增参数配置\nexport function addConfig(data) {\n  return request({\n    url: '/system/config',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改参数配置\nexport function updateConfig(data) {\n  return request({\n    url: '/system/config',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除参数配置\nexport function delConfig(configId) {\n  return request({\n    url: '/system/config/' + configId,\n    method: 'delete'\n  })\n}\n\n// 刷新参数缓存\nexport function refreshCache() {\n  return request({\n    url: '/system/config/refreshCache',\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/system/dept.js",
    "content": "import request from '@/utils/request'\n\n// 查询部门列表\nexport function listDept(query) {\n  return request({\n    url: '/system/dept/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询部门列表（排除节点）\nexport function listDeptExcludeChild(deptId) {\n  return request({\n    url: '/system/dept/list/exclude/' + deptId,\n    method: 'get'\n  })\n}\n\n// 查询部门详细\nexport function getDept(deptId) {\n  return request({\n    url: '/system/dept/' + deptId,\n    method: 'get'\n  })\n}\n\n// 新增部门\nexport function addDept(data) {\n  return request({\n    url: '/system/dept',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改部门\nexport function updateDept(data) {\n  return request({\n    url: '/system/dept',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除部门\nexport function delDept(deptId) {\n  return request({\n    url: '/system/dept/' + deptId,\n    method: 'delete'\n  })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/system/dict/data.js",
    "content": "import request from '@/utils/request'\n\n// 查询字典数据列表\nexport function listData(query) {\n  return request({\n    url: '/system/dict/data/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询字典数据详细\nexport function getData(dictCode) {\n  return request({\n    url: '/system/dict/data/' + dictCode,\n    method: 'get'\n  })\n}\n\n// 根据字典类型查询字典数据信息\nexport function getDicts(dictType) {\n  return request({\n    url: '/system/dict/data/type/' + dictType,\n    method: 'get'\n  })\n}\n\n// 新增字典数据\nexport function addData(data) {\n  return request({\n    url: '/system/dict/data',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改字典数据\nexport function updateData(data) {\n  return request({\n    url: '/system/dict/data',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除字典数据\nexport function delData(dictCode) {\n  return request({\n    url: '/system/dict/data/' + dictCode,\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/system/dict/type.js",
    "content": "import request from '@/utils/request'\n\n// 查询字典类型列表\nexport function listType(query) {\n  return request({\n    url: '/system/dict/type/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询字典类型详细\nexport function getType(dictId) {\n  return request({\n    url: '/system/dict/type/' + dictId,\n    method: 'get'\n  })\n}\n\n// 新增字典类型\nexport function addType(data) {\n  return request({\n    url: '/system/dict/type',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改字典类型\nexport function updateType(data) {\n  return request({\n    url: '/system/dict/type',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除字典类型\nexport function delType(dictId) {\n  return request({\n    url: '/system/dict/type/' + dictId,\n    method: 'delete'\n  })\n}\n\n// 刷新字典缓存\nexport function refreshCache() {\n  return request({\n    url: '/system/dict/type/refreshCache',\n    method: 'delete'\n  })\n}\n\n// 获取字典选择框列表\nexport function optionselect() {\n  return request({\n    url: '/system/dict/type/optionselect',\n    method: 'get'\n  })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/system/index.js",
    "content": "import request from '@/utils/request'\n\n// 版本情况\nexport function getVersion() {\n  return request({\n    url: '/version',\n    method: 'get',\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/system/menu.js",
    "content": "import request from '@/utils/request'\n\n// 查询菜单列表\nexport function listMenu(query) {\n  return request({\n    url: '/system/menu/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询菜单详细\nexport function getMenu(menuId) {\n  return request({\n    url: '/system/menu/' + menuId,\n    method: 'get'\n  })\n}\n\n// 查询菜单下拉树结构\nexport function treeselect() {\n  return request({\n    url: '/system/menu/treeselect',\n    method: 'get'\n  })\n}\n\n// 根据角色ID查询菜单下拉树结构\nexport function roleMenuTreeselect(roleId) {\n  return request({\n    url: '/system/menu/roleMenuTreeselect/' + roleId,\n    method: 'get'\n  })\n}\n\n// 新增菜单\nexport function addMenu(data) {\n  return request({\n    url: '/system/menu',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改菜单\nexport function updateMenu(data) {\n  return request({\n    url: '/system/menu',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除菜单\nexport function delMenu(menuId) {\n  return request({\n    url: '/system/menu/' + menuId,\n    method: 'delete'\n  })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/system/notice.js",
    "content": "import request from '@/utils/request'\n\n// 查询公告列表\nexport function listNotice(query) {\n  return request({\n    url: '/system/notice/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询公告详细\nexport function getNotice(noticeId) {\n  return request({\n    url: '/system/notice/' + noticeId,\n    method: 'get'\n  })\n}\n\n// 新增公告\nexport function addNotice(data) {\n  return request({\n    url: '/system/notice',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改公告\nexport function updateNotice(data) {\n  return request({\n    url: '/system/notice',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除公告\nexport function delNotice(noticeId) {\n  return request({\n    url: '/system/notice/' + noticeId,\n    method: 'delete'\n  })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/system/post.js",
    "content": "import request from '@/utils/request'\n\n// 查询岗位列表\nexport function listPost(query) {\n  return request({\n    url: '/system/post/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询岗位详细\nexport function getPost(postId) {\n  return request({\n    url: '/system/post/' + postId,\n    method: 'get'\n  })\n}\n\n// 新增岗位\nexport function addPost(data) {\n  return request({\n    url: '/system/post',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改岗位\nexport function updatePost(data) {\n  return request({\n    url: '/system/post',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除岗位\nexport function delPost(postId) {\n  return request({\n    url: '/system/post/' + postId,\n    method: 'delete'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/system/resource.js",
    "content": "import request from '@/utils/request'\n\n\n// 根据角色ID查询资源下拉树结构\nexport function roleResourceTreeselect(roleId) {\n    return request({\n        url: '/system/resource/roleApiTreeselect/' + roleId,\n        method: 'get'\n    })\n}\n//修改角色与资源关联\nexport function editRoleResource(data) {\n\n    return request({\n        url: '/system/resource/roleApi',\n        method: 'put',\n        params: data\n    })\n}"
  },
  {
    "path": "vue_campus_admin/src/api/system/role.js",
    "content": "import request from '@/utils/request'\n\n// 查询角色列表\nexport function listRole(query) {\n  return request({\n    url: '/system/role/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询角色详细\nexport function getRole(roleId) {\n  return request({\n    url: '/system/role/' + roleId,\n    method: 'get'\n  })\n}\n\n// 新增角色\nexport function addRole(data) {\n  return request({\n    url: '/system/role',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改角色\nexport function updateRole(data) {\n  return request({\n    url: '/system/role',\n    method: 'put',\n    data: data\n  })\n}\n\n// 角色数据权限\nexport function dataScope(data) {\n  return request({\n    url: '/system/role/dataScope',\n    method: 'put',\n    data: data\n  })\n}\n\n// 角色状态修改\nexport function changeRoleStatus(roleId, status) {\n  const data = {\n    roleId,\n    status\n  }\n  return request({\n    url: '/system/role/changeStatus',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除角色\nexport function delRole(roleId) {\n  return request({\n    url: '/system/role/' + roleId,\n    method: 'delete'\n  })\n}\n\n// 查询角色已授权用户列表\nexport function allocatedUserList(query) {\n  return request({\n    url: '/system/role/authUser/allocatedList',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询角色未授权用户列表\nexport function unallocatedUserList(query) {\n  return request({\n    url: '/system/role/authUser/unallocatedList',\n    method: 'get',\n    params: query\n  })\n}\n\n// 取消用户授权角色\nexport function authUserCancel(data) {\n  return request({\n    url: '/system/role/authUser/cancel',\n    method: 'put',\n    data: data\n  })\n}\n\n// 批量取消用户授权角色\nexport function authUserCancelAll(data) {\n  return request({\n    url: '/system/role/authUser/cancelAll',\n    method: 'put',\n    params: data\n  })\n}\n\n// 授权用户选择\nexport function authUserSelectAll(data) {\n  return request({\n    url: '/system/role/authUser/selectAll',\n    method: 'put',\n    params: data\n  })\n}\n\n// 根据角色ID查询部门树结构\nexport function deptTreeSelect(roleId) {\n  return request({\n    url: '/system/role/deptTree/' + roleId,\n    method: 'get'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/api/system/user.js",
    "content": "import request from '@/utils/request'\nimport { parseStrEmpty } from \"@/utils/ruoyi\";\n\n// 查询用户列表\nexport function listUser(query) {\n  return request({\n    url: '/system/user/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询用户详细\nexport function getUser(userId) {\n  return request({\n    url: '/system/user/' + parseStrEmpty(userId),\n    method: 'get'\n  })\n}\n\n// 新增用户\nexport function addUser(data) {\n  return request({\n    url: '/system/user',\n    method: 'post',\n    data: data\n  })\n}\n\n// 修改用户\nexport function updateUser(data) {\n  return request({\n    url: '/system/user',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除用户\nexport function delUser(userId) {\n  return request({\n    url: '/system/user/' + userId,\n    method: 'delete'\n  })\n}\n\n// 用户密码重置\nexport function resetUserPwd(userId, password) {\n  const data = {\n    userId,\n    password\n  }\n  return request({\n    url: '/system/user/resetPwd',\n    method: 'put',\n    data: data\n  })\n}\n\n// 用户状态修改\nexport function changeUserStatus(userId, status) {\n  const data = {\n    userId,\n    status\n  }\n  return request({\n    url: '/system/user/changeStatus',\n    method: 'put',\n    data: data\n  })\n}\n\n// 查询用户个人信息\nexport function getUserProfile() {\n  return request({\n    url: '/system/user/profile',\n    method: 'get'\n  })\n}\n\n// 修改用户个人信息\nexport function updateUserProfile(data) {\n  return request({\n    url: '/system/user/profile',\n    method: 'put',\n    data: data\n  })\n}\n\n// 用户密码重置\nexport function updateUserPwd(oldPassword, newPassword) {\n  const data = {\n    oldPassword,\n    newPassword\n  }\n  return request({\n    url: '/system/user/profile/updatePwd',\n    method: 'put',\n    params: data\n  })\n}\n\n// 用户头像上传\nexport function uploadAvatar(data) {\n  return request({\n    url: '/system/user/profile/avatar',\n    method: 'post',\n    data: data\n  })\n}\n\n// 查询授权角色\nexport function getAuthRole(userId) {\n  return request({\n    url: '/system/user/authRole/' + userId,\n    method: 'get'\n  })\n}\n\n// 保存授权角色\nexport function updateAuthRole(data) {\n  return request({\n    url: '/system/user/authRole',\n    method: 'put',\n    params: data\n  })\n}\n\n"
  },
  {
    "path": "vue_campus_admin/src/api/tool/gen.js",
    "content": "import request from '@/utils/request'\n\n// 查询生成表数据\nexport function listTable(query) {\n  return request({\n    url: '/tool/gen/list',\n    method: 'get',\n    params: query\n  })\n}\n// 查询db数据库列表\nexport function listDbTable(query) {\n  return request({\n    url: '/tool/gen/db/list',\n    method: 'get',\n    params: query\n  })\n}\n\n// 查询表详细信息\nexport function getGenTable(tableId) {\n  return request({\n    url: '/tool/gen/' + tableId,\n    method: 'get'\n  })\n}\n\n// 修改代码生成信息\nexport function updateGenTable(data) {\n  return request({\n    url: '/tool/gen',\n    method: 'put',\n    data: data\n  })\n}\n\n// 导入表\nexport function importTable(data) {\n  return request({\n    url: '/tool/gen/importTable',\n    method: 'post',\n    params: data\n  })\n}\n\n// 预览生成代码\nexport function previewTable(tableId) {\n  return request({\n    url: '/tool/gen/preview/' + tableId,\n    method: 'get'\n  })\n}\n\n// 删除表数据\nexport function delTable(tableId) {\n  return request({\n    url: '/tool/gen/' + tableId,\n    method: 'delete'\n  })\n}\n\n// 生成代码（自定义路径）\nexport function genCode(tableName) {\n  return request({\n    url: '/tool/gen/genCode/' + tableName,\n    method: 'get'\n  })\n}\n\n// 同步数据库\nexport function synchDb(tableName) {\n  return request({\n    url: '/tool/gen/synchDb/' + tableName,\n    method: 'get'\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/assets/icons/index.js",
    "content": "import Vue from 'vue'\nimport SvgIcon from '@/components/SvgIcon'// svg component\n\n// register globally\nVue.component('svg-icon', SvgIcon)\n\nconst req = require.context('./svg', false, /\\.svg$/)\nconst requireAll = requireContext => requireContext.keys().map(requireContext)\nrequireAll(req)\n"
  },
  {
    "path": "vue_campus_admin/src/assets/icons/svgo.yml",
    "content": "# replace default config\n\n# multipass: true\n# full: true\n\nplugins:\n\n  # - name\n  #\n  # or:\n  # - name: false\n  # - name: true\n  #\n  # or:\n  # - name:\n  #     param1: 1\n  #     param2: 2\n\n- removeAttrs:\n    attrs:\n      - 'fill'\n      - 'fill-rule'\n"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/btn.scss",
    "content": "@import './variables.scss';\n\n@mixin colorBtn($color) {\n  background: $color;\n\n  &:hover {\n    color: $color;\n\n    &:before,\n    &:after {\n      background: $color;\n    }\n  }\n}\n\n.blue-btn {\n  @include colorBtn($blue)\n}\n\n.light-blue-btn {\n  @include colorBtn($light-blue)\n}\n\n.red-btn {\n  @include colorBtn($red)\n}\n\n.pink-btn {\n  @include colorBtn($pink)\n}\n\n.green-btn {\n  @include colorBtn($green)\n}\n\n.tiffany-btn {\n  @include colorBtn($tiffany)\n}\n\n.yellow-btn {\n  @include colorBtn($yellow)\n}\n\n.pan-btn {\n  font-size: 14px;\n  color: #fff;\n  padding: 14px 36px;\n  border-radius: 8px;\n  border: none;\n  outline: none;\n  transition: 600ms ease all;\n  position: relative;\n  display: inline-block;\n\n  &:hover {\n    background: #fff;\n\n    &:before,\n    &:after {\n      width: 100%;\n      transition: 600ms ease all;\n    }\n  }\n\n  &:before,\n  &:after {\n    content: '';\n    position: absolute;\n    top: 0;\n    right: 0;\n    height: 2px;\n    width: 0;\n    transition: 400ms ease all;\n  }\n\n  &::after {\n    right: inherit;\n    top: inherit;\n    left: 0;\n    bottom: 0;\n  }\n}\n\n.custom-button {\n  display: inline-block;\n  line-height: 1;\n  white-space: nowrap;\n  cursor: pointer;\n  background: #fff;\n  color: #fff;\n  -webkit-appearance: none;\n  text-align: center;\n  box-sizing: border-box;\n  outline: 0;\n  margin: 0;\n  padding: 10px 15px;\n  font-size: 14px;\n  border-radius: 4px;\n}\n"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/element-ui.scss",
    "content": "// cover some element-ui styles\n\n.el-breadcrumb__inner,\n.el-breadcrumb__inner a {\n  font-weight: 400 !important;\n}\n\n.el-upload {\n  input[type=\"file\"] {\n    display: none !important;\n  }\n}\n\n.el-upload__input {\n  display: none;\n}\n\n.cell {\n  .el-tag {\n    margin-right: 0px;\n  }\n}\n\n.small-padding {\n  .cell {\n    padding-left: 5px;\n    padding-right: 5px;\n  }\n}\n\n.fixed-width {\n  .el-button--mini {\n    padding: 7px 10px;\n    width: 60px;\n  }\n}\n\n.status-col {\n  .cell {\n    padding: 0 10px;\n    text-align: center;\n\n    .el-tag {\n      margin-right: 0px;\n    }\n  }\n}\n\n// to fixed https://github.com/ElemeFE/element/issues/2461\n.el-dialog {\n  transform: none;\n  left: 0;\n  position: relative;\n  margin: 0 auto;\n}\n\n// refine element ui upload\n.upload-container {\n  .el-upload {\n    width: 100%;\n\n    .el-upload-dragger {\n      width: 100%;\n      height: 200px;\n    }\n  }\n}\n\n// dropdown\n.el-dropdown-menu {\n  a {\n    display: block\n  }\n}\n\n// fix date-picker ui bug in filter-item\n.el-range-editor.el-input__inner {\n  display: inline-flex !important;\n}\n\n// to fix el-date-picker css style\n.el-range-separator {\n  box-sizing: content-box;\n}\n\n.el-menu--collapse\n  > div\n  > .el-submenu\n  > .el-submenu__title\n  .el-submenu__icon-arrow {\n  display: none;\n}"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/element-variables.scss",
    "content": "/**\n* I think element-ui's default theme color is too light for long-term use.\n* So I modified the default color and you can modify it to your liking.\n**/\n\n/* theme color */\n$--color-primary: #1890ff;\n$--color-success: #13ce66;\n$--color-warning: #ffba00;\n$--color-danger: #ff4949;\n// $--color-info: #1E1E1E;\n\n$--button-font-weight: 400;\n\n// $--color-text-regular: #1f2d3d;\n\n$--border-color-light: #dfe4ed;\n$--border-color-lighter: #e6ebf5;\n\n$--table-border: 1px solid #dfe6ec;\n\n/* icon font path, required */\n$--font-path: '~element-ui/lib/theme-chalk/fonts';\n\n@import \"~element-ui/packages/theme-chalk/src/index\";\n\n// the :export directive is the magic sauce for webpack\n// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass\n:export {\n  theme: $--color-primary;\n}\n"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/index.scss",
    "content": "@import './variables.scss';\n@import './mixin.scss';\n@import './transition.scss';\n@import './element-ui.scss';\n@import './sidebar.scss';\n@import './btn.scss';\n\nbody {\n  height: 100%;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  text-rendering: optimizeLegibility;\n  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;\n}\n\nlabel {\n  font-weight: 700;\n}\n\nhtml {\n  height: 100%;\n  box-sizing: border-box;\n}\n\n#app {\n  height: 100%;\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\n.no-padding {\n  padding: 0px !important;\n}\n\n.padding-content {\n  padding: 4px 0;\n}\n\na:focus,\na:active {\n  outline: none;\n}\n\na,\na:focus,\na:hover {\n  cursor: pointer;\n  color: inherit;\n  text-decoration: none;\n}\n\ndiv:focus {\n  outline: none;\n}\n\n.fr {\n  float: right;\n}\n\n.fl {\n  float: left;\n}\n\n.pr-5 {\n  padding-right: 5px;\n}\n\n.pl-5 {\n  padding-left: 5px;\n}\n\n.block {\n  display: block;\n}\n\n.pointer {\n  cursor: pointer;\n}\n\n.inlineBlock {\n  display: block;\n}\n\n.clearfix {\n  &:after {\n    visibility: hidden;\n    display: block;\n    font-size: 0;\n    content: \" \";\n    clear: both;\n    height: 0;\n  }\n}\n\naside {\n  background: #eef1f6;\n  padding: 8px 24px;\n  margin-bottom: 20px;\n  border-radius: 2px;\n  display: block;\n  line-height: 32px;\n  font-size: 16px;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n  color: #2c3e50;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n\n  a {\n    color: #337ab7;\n    cursor: pointer;\n\n    &:hover {\n      color: rgb(32, 160, 255);\n    }\n  }\n}\n\n//main-container全局样式\n.app-container {\n  padding: 20px;\n}\n\n.components-container {\n  margin: 30px 50px;\n  position: relative;\n}\n\n.pagination-container {\n  margin-top: 30px;\n}\n\n.text-center {\n  text-align: center\n}\n\n.sub-navbar {\n  height: 50px;\n  line-height: 50px;\n  position: relative;\n  width: 100%;\n  text-align: right;\n  padding-right: 20px;\n  transition: 600ms ease position;\n  background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);\n\n  .subtitle {\n    font-size: 20px;\n    color: #fff;\n  }\n\n  &.draft {\n    background: #d0d0d0;\n  }\n\n  &.deleted {\n    background: #d0d0d0;\n  }\n}\n\n.link-type,\n.link-type:focus {\n  color: #337ab7;\n  cursor: pointer;\n\n  &:hover {\n    color: rgb(32, 160, 255);\n  }\n}\n\n.filter-container {\n  padding-bottom: 10px;\n\n  .filter-item {\n    display: inline-block;\n    vertical-align: middle;\n    margin-bottom: 10px;\n  }\n}\n\n//refine vue-multiselect plugin\n.multiselect {\n  line-height: 16px;\n}\n\n.multiselect--active {\n  z-index: 1000 !important;\n}\n"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/mixin.scss",
    "content": "@mixin clearfix {\n  &:after {\n    content: \"\";\n    display: table;\n    clear: both;\n  }\n}\n\n@mixin scrollBar {\n  &::-webkit-scrollbar-track-piece {\n    background: #d3dce6;\n  }\n\n  &::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    background: #99a9bf;\n    border-radius: 20px;\n  }\n}\n\n@mixin relative {\n  position: relative;\n  width: 100%;\n  height: 100%;\n}\n\n@mixin pct($pct) {\n  width: #{$pct};\n  position: relative;\n  margin: 0 auto;\n}\n\n@mixin triangle($width, $height, $color, $direction) {\n  $width: $width/2;\n  $color-border-style: $height solid $color;\n  $transparent-border-style: $width solid transparent;\n  height: 0;\n  width: 0;\n\n  @if $direction==up {\n    border-bottom: $color-border-style;\n    border-left: $transparent-border-style;\n    border-right: $transparent-border-style;\n  }\n\n  @else if $direction==right {\n    border-left: $color-border-style;\n    border-top: $transparent-border-style;\n    border-bottom: $transparent-border-style;\n  }\n\n  @else if $direction==down {\n    border-top: $color-border-style;\n    border-left: $transparent-border-style;\n    border-right: $transparent-border-style;\n  }\n\n  @else if $direction==left {\n    border-right: $color-border-style;\n    border-top: $transparent-border-style;\n    border-bottom: $transparent-border-style;\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/ruoyi.scss",
    "content": " /**\n * 通用css样式布局处理\n * Copyright (c) 2019 ruoyi\n */\n\n /** 基础通用 **/\n.pt5 {\n\tpadding-top: 5px;\n}\n.pr5 {\n\tpadding-right: 5px;\n}\n.pb5 {\n\tpadding-bottom: 5px;\n}\n.mt5 {\n\tmargin-top: 5px;\n}\n.mr5 {\n\tmargin-right: 5px;\n}\n.mb5 {\n\tmargin-bottom: 5px;\n}\n.mb8 {\n\tmargin-bottom: 8px;\n}\n.ml5 {\n\tmargin-left: 5px;\n}\n.mt10 {\n\tmargin-top: 10px;\n}\n.mr10 {\n\tmargin-right: 10px;\n}\n.mb10 {\n\tmargin-bottom: 10px;\n}\n.ml10 {\n\tmargin-left: 10px;\n}\n.mt20 {\n\tmargin-top: 20px;\n}\n.mr20 {\n\tmargin-right: 20px;\n}\n.mb20 {\n\tmargin-bottom: 20px;\n}\n.ml20 {\n\tmargin-left: 20px;\n}\n\n.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {\n\tfont-family: inherit;\n\tfont-weight: 500;\n\tline-height: 1.1;\n\tcolor: inherit;\n}\n\n.el-dialog:not(.is-fullscreen) {\n\tmargin-top: 6vh !important;\n}\n\n.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body {\n    overflow: auto;\n\toverflow-x: hidden;\n\tmax-height: 70vh;\n\tpadding: 10px 20px 0;\n}\n\n.el-table {\n\t.el-table__header-wrapper, .el-table__fixed-header-wrapper {\n\t\tth {\n\t\t\tword-break: break-word;\n\t\t\tbackground-color: #f8f8f9;\n\t\t\tcolor: #515a6e;\n\t\t\theight: 40px;\n\t\t\tfont-size: 13px;\n\t\t}\n\t}\n\t.el-table__body-wrapper {\n\t\t.el-button [class*=\"el-icon-\"] + span {\n\t\t\tmargin-left: 1px;\n\t\t}\n\t}\n}\n\n/** 表单布局 **/\n.form-header {\n    font-size:15px;\n\tcolor:#6379bb;\n\tborder-bottom:1px solid #ddd;\n\tmargin:8px 10px 25px 10px;\n\tpadding-bottom:5px\n}\n\n/** 表格布局 **/\n.pagination-container {\n\tposition: relative;\n\theight: 25px;\n\tmargin-bottom: 10px;\n\tmargin-top: 15px;\n\tpadding: 10px 20px !important;\n}\n\n/* tree border */\n.tree-border {\n    margin-top: 5px;\n    border: 1px solid #e5e6e7;\n    background: #FFFFFF none;\n    border-radius:4px;\n}\n\n.pagination-container .el-pagination {\n\tright: 0;\n\tposition: absolute;\n}\n\n@media ( max-width : 768px) {\n  .pagination-container .el-pagination > .el-pagination__jump {\n    display: none !important;\n  }\n  .pagination-container .el-pagination > .el-pagination__sizes {\n    display: none !important;\n  }\n}\n\n.el-table .fixed-width .el-button--mini {\n\tpadding-left: 0;\n\tpadding-right: 0;\n\twidth: inherit;\n}\n\n/** 表格更多操作下拉样式 */\n.el-table .el-dropdown-link {\n\tcursor: pointer;\n\tcolor: #409EFF;\n\tmargin-left: 5px;\n}\n\n.el-table .el-dropdown, .el-icon-arrow-down {\n\tfont-size: 12px;\n}\n\n.el-tree-node__content > .el-checkbox {\n\tmargin-right: 8px;\n}\n\n.list-group-striped > .list-group-item {\n\tborder-left: 0;\n\tborder-right: 0;\n\tborder-radius: 0;\n\tpadding-left: 0;\n\tpadding-right: 0;\n}\n\n.list-group {\n\tpadding-left: 0px;\n\tlist-style: none;\n}\n\n.list-group-item {\n\tborder-bottom: 1px solid #e7eaec;\n\tborder-top: 1px solid #e7eaec;\n\tmargin-bottom: -1px;\n\tpadding: 11px 0px;\n\tfont-size: 13px;\n}\n\n.pull-right {\n\tfloat: right !important;\n}\n\n.el-card__header {\n\tpadding: 14px 15px 7px;\n\tmin-height: 40px;\n}\n\n.el-card__body {\n\tpadding: 15px 20px 20px 20px;\n}\n\n.card-box {\n\tpadding-right: 15px;\n\tpadding-left: 15px;\n\tmargin-bottom: 10px;\n}\n\n/* button color */\n.el-button--cyan.is-active,\n.el-button--cyan:active {\n  background: #20B2AA;\n  border-color: #20B2AA;\n  color: #FFFFFF;\n}\n\n.el-button--cyan:focus,\n.el-button--cyan:hover {\n  background: #48D1CC;\n  border-color: #48D1CC;\n  color: #FFFFFF;\n}\n\n.el-button--cyan {\n  background-color: #20B2AA;\n  border-color: #20B2AA;\n  color: #FFFFFF;\n}\n\n/* text color */\n.text-navy {\n\tcolor: #1ab394;\n}\n\n.text-primary {\n\tcolor: inherit;\n}\n\n.text-success {\n\tcolor: #1c84c6;\n}\n\n.text-info {\n\tcolor: #23c6c8;\n}\n\n.text-warning {\n\tcolor: #f8ac59;\n}\n\n.text-danger {\n\tcolor: #ed5565;\n}\n\n.text-muted {\n\tcolor: #888888;\n}\n\n/* image */\n.img-circle {\n\tborder-radius: 50%;\n}\n\n.img-lg {\n\twidth: 120px;\n\theight: 120px;\n}\n\n.avatar-upload-preview {\n\tposition: absolute;\n\ttop: 50%;\n\ttransform: translate(50%, -50%);\n\twidth: 200px;\n\theight: 200px;\n\tborder-radius: 50%;\n\tbox-shadow: 0 0 4px #ccc;\n\toverflow: hidden;\n}\n\n/* 拖拽列样式 */\n.sortable-ghost{\n\topacity: .8;\n\tcolor: #fff!important;\n\tbackground: #42b983!important;\n}\n\n.top-right-btn {\n\tposition: relative;\n\tfloat: right;\n}\n"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/sidebar.scss",
    "content": "#app {\n\n  .main-container {\n    min-height: 100%;\n    transition: margin-left .28s;\n    margin-left: $base-sidebar-width;\n    position: relative;\n  }\n\n  .sidebarHide {\n    margin-left: 0!important;\n  }\n\n  .sidebar-container {\n    -webkit-transition: width .28s;\n    transition: width 0.28s;\n    width: $base-sidebar-width !important;\n    background-color: $base-menu-background;\n    height: 100%;\n    position: fixed;\n    font-size: 0px;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    z-index: 1001;\n    overflow: hidden;\n    -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);\n    box-shadow: 2px 0 6px rgba(0,21,41,.35);\n\n    // reset element-ui css\n    .horizontal-collapse-transition {\n      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;\n    }\n\n    .scrollbar-wrapper {\n      overflow-x: hidden !important;\n    }\n\n    .el-scrollbar__bar.is-vertical {\n      right: 0px;\n    }\n\n    .el-scrollbar {\n      height: 100%;\n    }\n\n    &.has-logo {\n      .el-scrollbar {\n        height: calc(100% - 50px);\n      }\n    }\n\n    .is-horizontal {\n      display: none;\n    }\n\n    a {\n      display: inline-block;\n      width: 100%;\n      overflow: hidden;\n    }\n\n    .svg-icon {\n      margin-right: 16px;\n    }\n\n    .el-menu {\n      border: none;\n      height: 100%;\n      width: 100% !important;\n    }\n\n    .el-menu-item, .el-submenu__title {\n      overflow: hidden !important;\n      text-overflow: ellipsis !important;\n      white-space: nowrap !important;\n    }\n\n    // menu hover\n    .submenu-title-noDropdown,\n    .el-submenu__title {\n      &:hover {\n        background-color: rgba(0, 0, 0, 0.06) !important;\n      }\n    }\n\n    & .theme-dark .is-active > .el-submenu__title {\n      color: $base-menu-color-active !important;\n    }\n\n    & .nest-menu .el-submenu>.el-submenu__title,\n    & .el-submenu .el-menu-item {\n      min-width: $base-sidebar-width !important;\n\n      &:hover {\n        background-color: rgba(0, 0, 0, 0.06) !important;\n      }\n    }\n\n    & .theme-dark .nest-menu .el-submenu>.el-submenu__title,\n    & .theme-dark .el-submenu .el-menu-item {\n      background-color: $base-sub-menu-background !important;\n\n      &:hover {\n        background-color: $base-sub-menu-hover !important;\n      }\n    }\n  }\n\n  .hideSidebar {\n    .sidebar-container {\n      width: 54px !important;\n    }\n\n    .main-container {\n      margin-left: 54px;\n    }\n\n    .submenu-title-noDropdown {\n      padding: 0 !important;\n      position: relative;\n\n      .el-tooltip {\n        padding: 0 !important;\n\n        .svg-icon {\n          margin-left: 20px;\n        }\n      }\n    }\n\n    .el-submenu {\n      overflow: hidden;\n\n      &>.el-submenu__title {\n        padding: 0 !important;\n\n        .svg-icon {\n          margin-left: 20px;\n        }\n\n      }\n    }\n\n    .el-menu--collapse {\n      .el-submenu {\n        &>.el-submenu__title {\n          &>span {\n            height: 0;\n            width: 0;\n            overflow: hidden;\n            visibility: hidden;\n            display: inline-block;\n          }\n        }\n      }\n    }\n  }\n\n  .el-menu--collapse .el-menu .el-submenu {\n    min-width: $base-sidebar-width !important;\n  }\n\n  // mobile responsive\n  .mobile {\n    .main-container {\n      margin-left: 0px;\n    }\n\n    .sidebar-container {\n      transition: transform .28s;\n      width: $base-sidebar-width !important;\n    }\n\n    &.hideSidebar {\n      .sidebar-container {\n        pointer-events: none;\n        transition-duration: 0.3s;\n        transform: translate3d(-$base-sidebar-width, 0, 0);\n      }\n    }\n  }\n\n  .withoutAnimation {\n\n    .main-container,\n    .sidebar-container {\n      transition: none;\n    }\n  }\n}\n\n// when menu collapsed\n.el-menu--vertical {\n  &>.el-menu {\n    .svg-icon {\n      margin-right: 16px;\n    }\n  }\n\n  .nest-menu .el-submenu>.el-submenu__title,\n  .el-menu-item {\n    &:hover {\n      // you can use $subMenuHover\n      background-color: rgba(0, 0, 0, 0.06) !important;\n    }\n  }\n\n  // the scroll bar appears when the subMenu is too long\n  >.el-menu--popup {\n    max-height: 100vh;\n    overflow-y: auto;\n\n    &::-webkit-scrollbar-track-piece {\n      background: #d3dce6;\n    }\n\n    &::-webkit-scrollbar {\n      width: 6px;\n    }\n\n    &::-webkit-scrollbar-thumb {\n      background: #99a9bf;\n      border-radius: 20px;\n    }\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/transition.scss",
    "content": "// global transition css\n\n/* fade */\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.28s;\n}\n\n.fade-enter,\n.fade-leave-active {\n  opacity: 0;\n}\n\n/* fade-transform */\n.fade-transform--move,\n.fade-transform-leave-active,\n.fade-transform-enter-active {\n  transition: all .5s;\n}\n\n.fade-transform-leave-active {\n  position: absolute;\n}\n\n.fade-transform-enter {\n  opacity: 0;\n  transform: translateX(-30px);\n}\n\n.fade-transform-leave-to {\n  opacity: 0;\n  transform: translateX(30px);\n}\n\n/* breadcrumb transition */\n.breadcrumb-enter-active,\n.breadcrumb-leave-active {\n  transition: all .5s;\n}\n\n.breadcrumb-enter,\n.breadcrumb-leave-active {\n  opacity: 0;\n  transform: translateX(20px);\n}\n\n.breadcrumb-move {\n  transition: all .5s;\n}\n\n.breadcrumb-leave-active {\n  position: absolute;\n}\n"
  },
  {
    "path": "vue_campus_admin/src/assets/styles/variables.scss",
    "content": "// base color\n$blue:#324157;\n$light-blue:#3A71A8;\n$red:#C03639;\n$pink: #E65D6E;\n$green: #30B08F;\n$tiffany: #4AB7BD;\n$yellow:#FEC171;\n$panGreen: #30B08F;\n\n// 默认菜单主题风格\n$base-menu-color:#bfcbd9;\n$base-menu-color-active:#f4f4f5;\n$base-menu-background:#304156;\n$base-logo-title-color: #ffffff;\n\n$base-menu-light-color:rgba(0,0,0,.70);\n$base-menu-light-background:#ffffff;\n$base-logo-light-title-color: #001529;\n\n$base-sub-menu-background:#1f2d3d;\n$base-sub-menu-hover:#001528;\n\n// 自定义暗色菜单风格\n/**\n$base-menu-color:hsla(0,0%,100%,.65);\n$base-menu-color-active:#fff;\n$base-menu-background:#001529;\n$base-logo-title-color: #ffffff;\n\n$base-menu-light-color:rgba(0,0,0,.70);\n$base-menu-light-background:#ffffff;\n$base-logo-light-title-color: #001529;\n\n$base-sub-menu-background:#000c17;\n$base-sub-menu-hover:#001528;\n*/\n\n$base-sidebar-width: 200px;\n\n// the :export directive is the magic sauce for webpack\n// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass\n:export {\n  menuColor: $base-menu-color;\n  menuLightColor: $base-menu-light-color;\n  menuColorActive: $base-menu-color-active;\n  menuBackground: $base-menu-background;\n  menuLightBackground: $base-menu-light-background;\n  subMenuBackground: $base-sub-menu-background;\n  subMenuHover: $base-sub-menu-hover;\n  sideBarWidth: $base-sidebar-width;\n  logoTitleColor: $base-logo-title-color;\n  logoLightTitleColor: $base-logo-light-title-color\n}\n"
  },
  {
    "path": "vue_campus_admin/src/components/Breadcrumb/index.vue",
    "content": "<template>\n  <el-breadcrumb class=\"app-breadcrumb\" separator=\"/\">\n    <transition-group name=\"breadcrumb\">\n      <el-breadcrumb-item v-for=\"(item,index) in levelList\" :key=\"item.path\">\n        <span v-if=\"item.redirect === 'noRedirect' || index == levelList.length - 1\" class=\"no-redirect\">{{ item.meta.title }}</span>\n        <a v-else @click.prevent=\"handleLink(item)\">{{ item.meta.title }}</a>\n      </el-breadcrumb-item>\n    </transition-group>\n  </el-breadcrumb>\n</template>\n\n<script>\nexport default {\n  data() {\n    return {\n      levelList: null\n    }\n  },\n  watch: {\n    $route(route) {\n      // if you go to the redirect page, do not update the breadcrumbs\n      if (route.path.startsWith('/redirect/')) {\n        return\n      }\n      this.getBreadcrumb()\n    }\n  },\n  created() {\n    this.getBreadcrumb()\n  },\n  methods: {\n    getBreadcrumb() {\n      // only show routes with meta.title\n      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)\n      const first = matched[0]\n\n      if (!this.isDashboard(first)) {\n        matched = [{ path: '/index', meta: { title: '首页' }}].concat(matched)\n      }\n\n      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)\n    },\n    isDashboard(route) {\n      const name = route && route.name\n      if (!name) {\n        return false\n      }\n      return name.trim() === 'Index'\n    },\n    handleLink(item) {\n      const { redirect, path } = item\n      if (redirect) {\n        this.$router.push(redirect)\n        return\n      }\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.app-breadcrumb.el-breadcrumb {\n  display: inline-block;\n  font-size: 14px;\n  line-height: 50px;\n  margin-left: 8px;\n\n  .no-redirect {\n    color: #97a8be;\n    cursor: text;\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/day.vue",
    "content": "<template>\n\t<el-form size=\"small\">\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"1\">\n\t\t\t\t日，允许的通配符[, - * ? / L W]\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"2\">\n\t\t\t\t不指定\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"3\">\n\t\t\t\t周期从\n\t\t\t\t<el-input-number v-model='cycle01' :min=\"1\" :max=\"30\" /> -\n\t\t\t\t<el-input-number v-model='cycle02' :min=\"cycle01 ? cycle01 + 1 : 2\" :max=\"31\" /> 日\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"4\">\n\t\t\t\t从\n\t\t\t\t<el-input-number v-model='average01' :min=\"1\" :max=\"30\" /> 号开始，每\n\t\t\t\t<el-input-number v-model='average02' :min=\"1\" :max=\"31 - average01 || 1\" /> 日执行一次\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"5\">\n\t\t\t\t每月\n\t\t\t\t<el-input-number v-model='workday' :min=\"1\" :max=\"31\" /> 号最近的那个工作日\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"6\">\n\t\t\t\t本月最后一天\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"7\">\n\t\t\t\t指定\n\t\t\t\t<el-select clearable v-model=\"checkboxList\" placeholder=\"可多选\" multiple style=\"width:100%\">\n\t\t\t\t\t<el-option v-for=\"item in 31\" :key=\"item\" :value=\"item\">{{item}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\t</el-form>\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tradioValue: 1,\n\t\t\tworkday: 1,\n\t\t\tcycle01: 1,\n\t\t\tcycle02: 2,\n\t\t\taverage01: 1,\n\t\t\taverage02: 1,\n\t\t\tcheckboxList: [],\n\t\t\tcheckNum: this.$options.propsData.check\n\t\t}\n\t},\n\tname: 'crontab-day',\n\tprops: ['check', 'cron'],\n\tmethods: {\n\t\t// 单选按钮值变化时\n\t\tradioChange() {\n\t\t\t('day rachange');\n\t\t\tif (this.radioValue !== 2 && this.cron.week !== '?') {\n\t\t\t\tthis.$emit('update', 'week', '?', 'day')\n\t\t\t}\n\n\t\t\tswitch (this.radioValue) {\n\t\t\t\tcase 1:\n\t\t\t\t\tthis.$emit('update', 'day', '*');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.$emit('update', 'day', '?');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.$emit('update', 'day', this.cycleTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.$emit('update', 'day', this.averageTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 5:\n\t\t\t\t\tthis.$emit('update', 'day', this.workday + 'W');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 6:\n\t\t\t\t\tthis.$emit('update', 'day', 'L');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 7:\n\t\t\t\t\tthis.$emit('update', 'day', this.checkboxString);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\t('day rachange end');\n\t\t},\n\t\t// 周期两个值变化时\n\t\tcycleChange() {\n\t\t\tif (this.radioValue == '3') {\n\t\t\t\tthis.$emit('update', 'day', this.cycleTotal);\n\t\t\t}\n\t\t},\n\t\t// 平均两个值变化时\n\t\taverageChange() {\n\t\t\tif (this.radioValue == '4') {\n\t\t\t\tthis.$emit('update', 'day', this.averageTotal);\n\t\t\t}\n\t\t},\n\t\t// 最近工作日值变化时\n\t\tworkdayChange() {\n\t\t\tif (this.radioValue == '5') {\n\t\t\t\tthis.$emit('update', 'day', this.workdayCheck + 'W');\n\t\t\t}\n\t\t},\n\t\t// checkbox值变化时\n\t\tcheckboxChange() {\n\t\t\tif (this.radioValue == '7') {\n\t\t\t\tthis.$emit('update', 'day', this.checkboxString);\n\t\t\t}\n\t\t}\n\t},\n\twatch: {\n\t\t'radioValue': 'radioChange',\n\t\t'cycleTotal': 'cycleChange',\n\t\t'averageTotal': 'averageChange',\n\t\t'workdayCheck': 'workdayChange',\n\t\t'checkboxString': 'checkboxChange',\n\t},\n\tcomputed: {\n\t\t// 计算两个周期值\n\t\tcycleTotal: function () {\n\t\t\tconst cycle01 = this.checkNum(this.cycle01, 1, 30)\n\t\t\tconst cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 31, 31)\n\t\t\treturn cycle01 + '-' + cycle02;\n\t\t},\n\t\t// 计算平均用到的值\n\t\taverageTotal: function () {\n\t\t\tconst average01 = this.checkNum(this.average01, 1, 30)\n\t\t\tconst average02 = this.checkNum(this.average02, 1, 31 - average01 || 0)\n\t\t\treturn average01 + '/' + average02;\n\t\t},\n\t\t// 计算工作日格式\n\t\tworkdayCheck: function () {\n\t\t\tconst workday = this.checkNum(this.workday, 1, 31)\n\t\t\treturn workday;\n\t\t},\n\t\t// 计算勾选的checkbox值合集\n\t\tcheckboxString: function () {\n\t\t\tlet str = this.checkboxList.join();\n\t\t\treturn str == '' ? '*' : str;\n\t\t}\n\t}\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/hour.vue",
    "content": "<template>\n\t<el-form size=\"small\">\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"1\">\n\t\t\t\t小时，允许的通配符[, - * /]\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"2\">\n\t\t\t\t周期从\n\t\t\t\t<el-input-number v-model='cycle01' :min=\"0\" :max=\"22\" /> -\n\t\t\t\t<el-input-number v-model='cycle02' :min=\"cycle01 ? cycle01 + 1 : 1\" :max=\"23\" /> 小时\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"3\">\n\t\t\t\t从\n\t\t\t\t<el-input-number v-model='average01' :min=\"0\" :max=\"22\" /> 小时开始，每\n\t\t\t\t<el-input-number v-model='average02' :min=\"1\" :max=\"23 - average01 || 0\" /> 小时执行一次\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"4\">\n\t\t\t\t指定\n\t\t\t\t<el-select clearable v-model=\"checkboxList\" placeholder=\"可多选\" multiple style=\"width:100%\">\n\t\t\t\t\t<el-option v-for=\"item in 24\" :key=\"item\" :value=\"item-1\">{{item-1}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\t</el-form>\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tradioValue: 1,\n\t\t\tcycle01: 0,\n\t\t\tcycle02: 1,\n\t\t\taverage01: 0,\n\t\t\taverage02: 1,\n\t\t\tcheckboxList: [],\n\t\t\tcheckNum: this.$options.propsData.check\n\t\t}\n\t},\n\tname: 'crontab-hour',\n\tprops: ['check', 'cron'],\n\tmethods: {\n\t\t// 单选按钮值变化时\n\t\tradioChange() {\n\t\t\tswitch (this.radioValue) {\n\t\t\t\tcase 1:\n        \tthis.$emit('update', 'hour', '*')\n        \tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.$emit('update', 'hour', this.cycleTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.$emit('update', 'hour', this.averageTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.$emit('update', 'hour', this.checkboxString);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t// 周期两个值变化时\n\t\tcycleChange() {\n\t\t\tif (this.radioValue == '2') {\n\t\t\t\tthis.$emit('update', 'hour', this.cycleTotal);\n\t\t\t}\n\t\t},\n\t\t// 平均两个值变化时\n\t\taverageChange() {\n\t\t\tif (this.radioValue == '3') {\n\t\t\t\tthis.$emit('update', 'hour', this.averageTotal);\n\t\t\t}\n\t\t},\n\t\t// checkbox值变化时\n\t\tcheckboxChange() {\n\t\t\tif (this.radioValue == '4') {\n\t\t\t\tthis.$emit('update', 'hour', this.checkboxString);\n\t\t\t}\n\t\t}\n\t},\n\twatch: {\n\t\t'radioValue': 'radioChange',\n\t\t'cycleTotal': 'cycleChange',\n\t\t'averageTotal': 'averageChange',\n\t\t'checkboxString': 'checkboxChange'\n\t},\n\tcomputed: {\n\t\t// 计算两个周期值\n\t\tcycleTotal: function () {\n\t\t\tconst cycle01 = this.checkNum(this.cycle01, 0, 22)\n\t\t\tconst cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 23)\n\t\t\treturn cycle01 + '-' + cycle02;\n\t\t},\n\t\t// 计算平均用到的值\n\t\taverageTotal: function () {\n\t\t\tconst average01 = this.checkNum(this.average01, 0, 22)\n\t\t\tconst average02 = this.checkNum(this.average02, 1, 23 - average01 || 0)\n\t\t\treturn average01 + '/' + average02;\n\t\t},\n\t\t// 计算勾选的checkbox值合集\n\t\tcheckboxString: function () {\n\t\t\tlet str = this.checkboxList.join();\n\t\t\treturn str == '' ? '*' : str;\n\t\t}\n\t}\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/index.vue",
    "content": "<template>\n  <div>\n    <el-tabs type=\"border-card\">\n      <el-tab-pane label=\"秒\" v-if=\"shouldHide('second')\">\n        <CrontabSecond\n          @update=\"updateCrontabValue\"\n          :check=\"checkNumber\"\n          :cron=\"crontabValueObj\"\n          ref=\"cronsecond\"\n        />\n      </el-tab-pane>\n\n      <el-tab-pane label=\"分钟\" v-if=\"shouldHide('min')\">\n        <CrontabMin\n          @update=\"updateCrontabValue\"\n          :check=\"checkNumber\"\n          :cron=\"crontabValueObj\"\n          ref=\"cronmin\"\n        />\n      </el-tab-pane>\n\n      <el-tab-pane label=\"小时\" v-if=\"shouldHide('hour')\">\n        <CrontabHour\n          @update=\"updateCrontabValue\"\n          :check=\"checkNumber\"\n          :cron=\"crontabValueObj\"\n          ref=\"cronhour\"\n        />\n      </el-tab-pane>\n\n      <el-tab-pane label=\"日\" v-if=\"shouldHide('day')\">\n        <CrontabDay\n          @update=\"updateCrontabValue\"\n          :check=\"checkNumber\"\n          :cron=\"crontabValueObj\"\n          ref=\"cronday\"\n        />\n      </el-tab-pane>\n\n      <el-tab-pane label=\"月\" v-if=\"shouldHide('month')\">\n        <CrontabMonth\n          @update=\"updateCrontabValue\"\n          :check=\"checkNumber\"\n          :cron=\"crontabValueObj\"\n          ref=\"cronmonth\"\n        />\n      </el-tab-pane>\n\n      <el-tab-pane label=\"周\" v-if=\"shouldHide('week')\">\n        <CrontabWeek\n          @update=\"updateCrontabValue\"\n          :check=\"checkNumber\"\n          :cron=\"crontabValueObj\"\n          ref=\"cronweek\"\n        />\n      </el-tab-pane>\n\n      <el-tab-pane label=\"年\" v-if=\"shouldHide('year')\">\n        <CrontabYear\n          @update=\"updateCrontabValue\"\n          :check=\"checkNumber\"\n          :cron=\"crontabValueObj\"\n          ref=\"cronyear\"\n        />\n      </el-tab-pane>\n    </el-tabs>\n\n    <div class=\"popup-main\">\n      <div class=\"popup-result\">\n        <p class=\"title\">时间表达式</p>\n        <table>\n          <thead>\n            <th v-for=\"item of tabTitles\" width=\"40\" :key=\"item\">{{item}}</th>\n            <th>Cron 表达式</th>\n          </thead>\n          <tbody>\n            <td>\n              <span>{{crontabValueObj.second}}</span>\n            </td>\n            <td>\n              <span>{{crontabValueObj.min}}</span>\n            </td>\n            <td>\n              <span>{{crontabValueObj.hour}}</span>\n            </td>\n            <td>\n              <span>{{crontabValueObj.day}}</span>\n            </td>\n            <td>\n              <span>{{crontabValueObj.month}}</span>\n            </td>\n            <td>\n              <span>{{crontabValueObj.week}}</span>\n            </td>\n            <td>\n              <span>{{crontabValueObj.year}}</span>\n            </td>\n            <td>\n              <span>{{crontabValueString}}</span>\n            </td>\n          </tbody>\n        </table>\n      </div>\n      <CrontabResult :ex=\"crontabValueString\"></CrontabResult>\n\n      <div class=\"pop_btn\">\n        <el-button size=\"small\" type=\"primary\" @click=\"submitFill\">确定</el-button>\n        <el-button size=\"small\" type=\"warning\" @click=\"clearCron\">重置</el-button>\n        <el-button size=\"small\" @click=\"hidePopup\">取消</el-button>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport CrontabSecond from \"./second.vue\";\nimport CrontabMin from \"./min.vue\";\nimport CrontabHour from \"./hour.vue\";\nimport CrontabDay from \"./day.vue\";\nimport CrontabMonth from \"./month.vue\";\nimport CrontabWeek from \"./week.vue\";\nimport CrontabYear from \"./year.vue\";\nimport CrontabResult from \"./result.vue\";\n\nexport default {\n  data() {\n    return {\n      tabTitles: [\"秒\", \"分钟\", \"小时\", \"日\", \"月\", \"周\", \"年\"],\n      tabActive: 0,\n      myindex: 0,\n      crontabValueObj: {\n        second: \"*\",\n        min: \"*\",\n        hour: \"*\",\n        day: \"*\",\n        month: \"*\",\n        week: \"?\",\n        year: \"\",\n      },\n    };\n  },\n  name: \"vcrontab\",\n  props: [\"expression\", \"hideComponent\"],\n  methods: {\n    shouldHide(key) {\n      if (this.hideComponent && this.hideComponent.includes(key)) return false;\n      return true;\n    },\n    resolveExp() {\n      // 反解析 表达式\n      if (this.expression) {\n        let arr = this.expression.split(\" \");\n        if (arr.length >= 6) {\n          //6 位以上是合法表达式\n          let obj = {\n            second: arr[0],\n            min: arr[1],\n            hour: arr[2],\n            day: arr[3],\n            month: arr[4],\n            week: arr[5],\n            year: arr[6] ? arr[6] : \"\",\n          };\n          this.crontabValueObj = {\n            ...obj,\n          };\n          for (let i in obj) {\n            if (obj[i]) this.changeRadio(i, obj[i]);\n          }\n        }\n      } else {\n        // 没有传入的表达式 则还原\n        this.clearCron();\n      }\n    },\n    // tab切换值\n    tabCheck(index) {\n      this.tabActive = index;\n    },\n    // 由子组件触发，更改表达式组成的字段值\n    updateCrontabValue(name, value, from) {\n      \"updateCrontabValue\", name, value, from;\n      this.crontabValueObj[name] = value;\n      if (from && from !== name) {\n        console.log(`来自组件 ${from} 改变了 ${name} ${value}`);\n        this.changeRadio(name, value);\n      }\n    },\n    // 赋值到组件\n    changeRadio(name, value) {\n      let arr = [\"second\", \"min\", \"hour\", \"month\"],\n        refName = \"cron\" + name,\n        insValue;\n\n      if (!this.$refs[refName]) return;\n\n      if (arr.includes(name)) {\n        if (value === \"*\") {\n          insValue = 1;\n        } else if (value.indexOf(\"-\") > -1) {\n          let indexArr = value.split(\"-\");\n          isNaN(indexArr[0])\n            ? (this.$refs[refName].cycle01 = 0)\n            : (this.$refs[refName].cycle01 = indexArr[0]);\n          this.$refs[refName].cycle02 = indexArr[1];\n          insValue = 2;\n        } else if (value.indexOf(\"/\") > -1) {\n          let indexArr = value.split(\"/\");\n          isNaN(indexArr[0])\n            ? (this.$refs[refName].average01 = 0)\n            : (this.$refs[refName].average01 = indexArr[0]);\n          this.$refs[refName].average02 = indexArr[1];\n          insValue = 3;\n        } else {\n          insValue = 4;\n          this.$refs[refName].checkboxList = value.split(\",\");\n        }\n      } else if (name == \"day\") {\n        if (value === \"*\") {\n          insValue = 1;\n        } else if (value == \"?\") {\n          insValue = 2;\n        } else if (value.indexOf(\"-\") > -1) {\n          let indexArr = value.split(\"-\");\n          isNaN(indexArr[0])\n            ? (this.$refs[refName].cycle01 = 0)\n            : (this.$refs[refName].cycle01 = indexArr[0]);\n          this.$refs[refName].cycle02 = indexArr[1];\n          insValue = 3;\n        } else if (value.indexOf(\"/\") > -1) {\n          let indexArr = value.split(\"/\");\n          isNaN(indexArr[0])\n            ? (this.$refs[refName].average01 = 0)\n            : (this.$refs[refName].average01 = indexArr[0]);\n          this.$refs[refName].average02 = indexArr[1];\n          insValue = 4;\n        } else if (value.indexOf(\"W\") > -1) {\n          let indexArr = value.split(\"W\");\n          isNaN(indexArr[0])\n            ? (this.$refs[refName].workday = 0)\n            : (this.$refs[refName].workday = indexArr[0]);\n          insValue = 5;\n        } else if (value === \"L\") {\n          insValue = 6;\n        } else {\n          this.$refs[refName].checkboxList = value.split(\",\");\n          insValue = 7;\n        }\n      } else if (name == \"week\") {\n        if (value === \"*\") {\n          insValue = 1;\n        } else if (value == \"?\") {\n          insValue = 2;\n        } else if (value.indexOf(\"-\") > -1) {\n          let indexArr = value.split(\"-\");\n          isNaN(indexArr[0])\n            ? (this.$refs[refName].cycle01 = 0)\n            : (this.$refs[refName].cycle01 = indexArr[0]);\n          this.$refs[refName].cycle02 = indexArr[1];\n          insValue = 3;\n        } else if (value.indexOf(\"#\") > -1) {\n          let indexArr = value.split(\"#\");\n          isNaN(indexArr[0])\n            ? (this.$refs[refName].average01 = 1)\n            : (this.$refs[refName].average01 = indexArr[0]);\n          this.$refs[refName].average02 = indexArr[1];\n          insValue = 4;\n        } else if (value.indexOf(\"L\") > -1) {\n          let indexArr = value.split(\"L\");\n          isNaN(indexArr[0])\n            ? (this.$refs[refName].weekday = 1)\n            : (this.$refs[refName].weekday = indexArr[0]);\n          insValue = 5;\n        } else {\n          this.$refs[refName].checkboxList = value.split(\",\");\n          insValue = 6;\n        }\n      } else if (name == \"year\") {\n        if (value == \"\") {\n          insValue = 1;\n        } else if (value == \"*\") {\n          insValue = 2;\n        } else if (value.indexOf(\"-\") > -1) {\n          insValue = 3;\n        } else if (value.indexOf(\"/\") > -1) {\n          insValue = 4;\n        } else {\n          this.$refs[refName].checkboxList = value.split(\",\");\n          insValue = 5;\n        }\n      }\n      this.$refs[refName].radioValue = insValue;\n    },\n    // 表单选项的子组件校验数字格式（通过-props传递）\n    checkNumber(value, minLimit, maxLimit) {\n      // 检查必须为整数\n      value = Math.floor(value);\n      if (value < minLimit) {\n        value = minLimit;\n      } else if (value > maxLimit) {\n        value = maxLimit;\n      }\n      return value;\n    },\n    // 隐藏弹窗\n    hidePopup() {\n      this.$emit(\"hide\");\n    },\n    // 填充表达式\n    submitFill() {\n      this.$emit(\"fill\", this.crontabValueString);\n      this.hidePopup();\n    },\n    clearCron() {\n      // 还原选择项\n      (\"准备还原\");\n      this.crontabValueObj = {\n        second: \"*\",\n        min: \"*\",\n        hour: \"*\",\n        day: \"*\",\n        month: \"*\",\n        week: \"?\",\n        year: \"\",\n      };\n      for (let j in this.crontabValueObj) {\n        this.changeRadio(j, this.crontabValueObj[j]);\n      }\n    },\n  },\n  computed: {\n    crontabValueString: function() {\n      let obj = this.crontabValueObj;\n      let str =\n        obj.second +\n        \" \" +\n        obj.min +\n        \" \" +\n        obj.hour +\n        \" \" +\n        obj.day +\n        \" \" +\n        obj.month +\n        \" \" +\n        obj.week +\n        (obj.year == \"\" ? \"\" : \" \" + obj.year);\n      return str;\n    },\n  },\n  components: {\n    CrontabSecond,\n    CrontabMin,\n    CrontabHour,\n    CrontabDay,\n    CrontabMonth,\n    CrontabWeek,\n    CrontabYear,\n    CrontabResult,\n  },\n  watch: {\n    expression: \"resolveExp\",\n    hideComponent(value) {\n      // 隐藏部分组件\n    },\n  },\n  mounted: function() {\n    this.resolveExp();\n  },\n};\n</script>\n<style scoped>\n.pop_btn {\n  text-align: center;\n  margin-top: 20px;\n}\n.popup-main {\n  position: relative;\n  margin: 10px auto;\n  background: #fff;\n  border-radius: 5px;\n  font-size: 12px;\n  overflow: hidden;\n}\n.popup-title {\n  overflow: hidden;\n  line-height: 34px;\n  padding-top: 6px;\n  background: #f2f2f2;\n}\n.popup-result {\n  box-sizing: border-box;\n  line-height: 24px;\n  margin: 25px auto;\n  padding: 15px 10px 10px;\n  border: 1px solid #ccc;\n  position: relative;\n}\n.popup-result .title {\n  position: absolute;\n  top: -28px;\n  left: 50%;\n  width: 140px;\n  font-size: 14px;\n  margin-left: -70px;\n  text-align: center;\n  line-height: 30px;\n  background: #fff;\n}\n.popup-result table {\n  text-align: center;\n  width: 100%;\n  margin: 0 auto;\n}\n.popup-result table span {\n  display: block;\n  width: 100%;\n  font-family: arial;\n  line-height: 30px;\n  height: 30px;\n  white-space: nowrap;\n  overflow: hidden;\n  border: 1px solid #e8e8e8;\n}\n.popup-result-scroll {\n  font-size: 12px;\n  line-height: 24px;\n  height: 10em;\n  overflow-y: auto;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/min.vue",
    "content": "<template>\n\t<el-form size=\"small\">\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"1\">\n\t\t\t\t分钟，允许的通配符[, - * /]\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"2\">\n\t\t\t\t周期从\n\t\t\t\t<el-input-number v-model='cycle01' :min=\"0\" :max=\"58\" /> -\n\t\t\t\t<el-input-number v-model='cycle02' :min=\"cycle01 ? cycle01 + 1 : 1\" :max=\"59\" /> 分钟\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"3\">\n\t\t\t\t从\n\t\t\t\t<el-input-number v-model='average01' :min=\"0\" :max=\"58\" /> 分钟开始，每\n\t\t\t\t<el-input-number v-model='average02' :min=\"1\" :max=\"59 - average01 || 0\" /> 分钟执行一次\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"4\">\n\t\t\t\t指定\n\t\t\t\t<el-select clearable v-model=\"checkboxList\" placeholder=\"可多选\" multiple style=\"width:100%\">\n\t\t\t\t\t<el-option v-for=\"item in 60\" :key=\"item\" :value=\"item-1\">{{item-1}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\t</el-form>\n\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tradioValue: 1,\n\t\t\tcycle01: 1,\n\t\t\tcycle02: 2,\n\t\t\taverage01: 0,\n\t\t\taverage02: 1,\n\t\t\tcheckboxList: [],\n\t\t\tcheckNum: this.$options.propsData.check\n\t\t}\n\t},\n\tname: 'crontab-min',\n\tprops: ['check', 'cron'],\n\tmethods: {\n\t\t// 单选按钮值变化时\n\t\tradioChange() {\n\t\t\tswitch (this.radioValue) {\n\t\t\t\tcase 1:\n\t\t\t\t\tthis.$emit('update', 'min', '*', 'min');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.$emit('update', 'min', this.cycleTotal, 'min');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.$emit('update', 'min', this.averageTotal, 'min');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.$emit('update', 'min', this.checkboxString, 'min');\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t// 周期两个值变化时\n\t\tcycleChange() {\n\t\t\tif (this.radioValue == '2') {\n\t\t\t\tthis.$emit('update', 'min', this.cycleTotal, 'min');\n\t\t\t}\n\t\t},\n\t\t// 平均两个值变化时\n\t\taverageChange() {\n\t\t\tif (this.radioValue == '3') {\n\t\t\t\tthis.$emit('update', 'min', this.averageTotal, 'min');\n\t\t\t}\n\t\t},\n\t\t// checkbox值变化时\n\t\tcheckboxChange() {\n\t\t\tif (this.radioValue == '4') {\n\t\t\t\tthis.$emit('update', 'min', this.checkboxString, 'min');\n\t\t\t}\n\t\t},\n\n\t},\n\twatch: {\n\t\t'radioValue': 'radioChange',\n\t\t'cycleTotal': 'cycleChange',\n\t\t'averageTotal': 'averageChange',\n\t\t'checkboxString': 'checkboxChange',\n\t},\n\tcomputed: {\n\t\t// 计算两个周期值\n\t\tcycleTotal: function () {\n\t\t\tconst cycle01 = this.checkNum(this.cycle01, 0, 58)\n\t\t\tconst cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)\n\t\t\treturn cycle01 + '-' + cycle02;\n\t\t},\n\t\t// 计算平均用到的值\n\t\taverageTotal: function () {\n\t\t\tconst average01 = this.checkNum(this.average01, 0, 58)\n\t\t\tconst average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)\n\t\t\treturn average01 + '/' + average02;\n\t\t},\n\t\t// 计算勾选的checkbox值合集\n\t\tcheckboxString: function () {\n\t\t\tlet str = this.checkboxList.join();\n\t\t\treturn str == '' ? '*' : str;\n\t\t}\n\t}\n}\n</script>"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/month.vue",
    "content": "<template>\n\t<el-form size='small'>\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"1\">\n\t\t\t\t月，允许的通配符[, - * /]\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"2\">\n\t\t\t\t周期从\n\t\t\t\t<el-input-number v-model='cycle01' :min=\"1\" :max=\"11\" /> -\n\t\t\t\t<el-input-number v-model='cycle02' :min=\"cycle01 ? cycle01 + 1 : 2\" :max=\"12\" /> 月\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"3\">\n\t\t\t\t从\n\t\t\t\t<el-input-number v-model='average01' :min=\"1\" :max=\"11\" /> 月开始，每\n\t\t\t\t<el-input-number v-model='average02' :min=\"1\" :max=\"12 - average01 || 0\" /> 月月执行一次\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"4\">\n\t\t\t\t指定\n\t\t\t\t<el-select clearable v-model=\"checkboxList\" placeholder=\"可多选\" multiple style=\"width:100%\">\n\t\t\t\t\t<el-option v-for=\"item in 12\" :key=\"item\" :value=\"item\">{{item}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\t</el-form>\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tradioValue: 1,\n\t\t\tcycle01: 1,\n\t\t\tcycle02: 2,\n\t\t\taverage01: 1,\n\t\t\taverage02: 1,\n\t\t\tcheckboxList: [],\n\t\t\tcheckNum: this.check\n\t\t}\n\t},\n\tname: 'crontab-month',\n\tprops: ['check', 'cron'],\n\tmethods: {\n\t\t// 单选按钮值变化时\n\t\tradioChange() {\n\t\t\tswitch (this.radioValue) {\n\t\t\t\tcase 1:\n\t\t\t\t\tthis.$emit('update', 'month', '*');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.$emit('update', 'month', this.cycleTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.$emit('update', 'month', this.averageTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.$emit('update', 'month', this.checkboxString);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t// 周期两个值变化时\n\t\tcycleChange() {\n\t\t\tif (this.radioValue == '2') {\n\t\t\t\tthis.$emit('update', 'month', this.cycleTotal);\n\t\t\t}\n\t\t},\n\t\t// 平均两个值变化时\n\t\taverageChange() {\n\t\t\tif (this.radioValue == '3') {\n\t\t\t\tthis.$emit('update', 'month', this.averageTotal);\n\t\t\t}\n\t\t},\n\t\t// checkbox值变化时\n\t\tcheckboxChange() {\n\t\t\tif (this.radioValue == '4') {\n\t\t\t\tthis.$emit('update', 'month', this.checkboxString);\n\t\t\t}\n\t\t}\n\t},\n\twatch: {\n\t\t'radioValue': 'radioChange',\n\t\t'cycleTotal': 'cycleChange',\n\t\t'averageTotal': 'averageChange',\n\t\t'checkboxString': 'checkboxChange'\n\t},\n\tcomputed: {\n\t\t// 计算两个周期值\n\t\tcycleTotal: function () {\n\t\t\tconst cycle01 = this.checkNum(this.cycle01, 1, 11)\n\t\t\tconst cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 12)\n\t\t\treturn cycle01 + '-' + cycle02;\n\t\t},\n\t\t// 计算平均用到的值\n\t\taverageTotal: function () {\n\t\t\tconst average01 = this.checkNum(this.average01, 1, 11)\n\t\t\tconst average02 = this.checkNum(this.average02, 1, 12 - average01 || 0)\n\t\t\treturn average01 + '/' + average02;\n\t\t},\n\t\t// 计算勾选的checkbox值合集\n\t\tcheckboxString: function () {\n\t\t\tlet str = this.checkboxList.join();\n\t\t\treturn str == '' ? '*' : str;\n\t\t}\n\t}\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/result.vue",
    "content": "<template>\n\t<div class=\"popup-result\">\n\t\t<p class=\"title\">最近5次运行时间</p>\n\t\t<ul class=\"popup-result-scroll\">\n\t\t\t<template v-if='isShow'>\n\t\t\t\t<li v-for='item in resultList' :key=\"item\">{{item}}</li>\n\t\t\t</template>\n\t\t\t<li v-else>计算结果中...</li>\n\t\t</ul>\n\t</div>\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tdayRule: '',\n\t\t\tdayRuleSup: '',\n\t\t\tdateArr: [],\n\t\t\tresultList: [],\n\t\t\tisShow: false\n\t\t}\n\t},\n\tname: 'crontab-result',\n\tmethods: {\n\t\t// 表达式值变化时，开始去计算结果\n\t\texpressionChange() {\n\n\t\t\t// 计算开始-隐藏结果\n\t\t\tthis.isShow = false;\n\t\t\t// 获取规则数组[0秒、1分、2时、3日、4月、5星期、6年]\n\t\t\tlet ruleArr = this.$options.propsData.ex.split(' ');\n\t\t\t// 用于记录进入循环的次数\n\t\t\tlet nums = 0;\n\t\t\t// 用于暂时存符号时间规则结果的数组\n\t\t\tlet resultArr = [];\n\t\t\t// 获取当前时间精确至[年、月、日、时、分、秒]\n\t\t\tlet nTime = new Date();\n\t\t\tlet nYear = nTime.getFullYear();\n\t\t\tlet nMonth = nTime.getMonth() + 1;\n\t\t\tlet nDay = nTime.getDate();\n\t\t\tlet nHour = nTime.getHours();\n\t\t\tlet nMin = nTime.getMinutes();\n\t\t\tlet nSecond = nTime.getSeconds();\n\t\t\t// 根据规则获取到近100年可能年数组、月数组等等\n\t\t\tthis.getSecondArr(ruleArr[0]);\n\t\t\tthis.getMinArr(ruleArr[1]);\n\t\t\tthis.getHourArr(ruleArr[2]);\n\t\t\tthis.getDayArr(ruleArr[3]);\n\t\t\tthis.getMonthArr(ruleArr[4]);\n\t\t\tthis.getWeekArr(ruleArr[5]);\n\t\t\tthis.getYearArr(ruleArr[6], nYear);\n\t\t\t// 将获取到的数组赋值-方便使用\n\t\t\tlet sDate = this.dateArr[0];\n\t\t\tlet mDate = this.dateArr[1];\n\t\t\tlet hDate = this.dateArr[2];\n\t\t\tlet DDate = this.dateArr[3];\n\t\t\tlet MDate = this.dateArr[4];\n\t\t\tlet YDate = this.dateArr[5];\n\t\t\t// 获取当前时间在数组中的索引\n\t\t\tlet sIdx = this.getIndex(sDate, nSecond);\n\t\t\tlet mIdx = this.getIndex(mDate, nMin);\n\t\t\tlet hIdx = this.getIndex(hDate, nHour);\n\t\t\tlet DIdx = this.getIndex(DDate, nDay);\n\t\t\tlet MIdx = this.getIndex(MDate, nMonth);\n\t\t\tlet YIdx = this.getIndex(YDate, nYear);\n\t\t\t// 重置月日时分秒的函数(后面用的比较多)\n\t\t\tconst resetSecond = function () {\n\t\t\t\tsIdx = 0;\n\t\t\t\tnSecond = sDate[sIdx]\n\t\t\t}\n\t\t\tconst resetMin = function () {\n\t\t\t\tmIdx = 0;\n\t\t\t\tnMin = mDate[mIdx]\n\t\t\t\tresetSecond();\n\t\t\t}\n\t\t\tconst resetHour = function () {\n\t\t\t\thIdx = 0;\n\t\t\t\tnHour = hDate[hIdx]\n\t\t\t\tresetMin();\n\t\t\t}\n\t\t\tconst resetDay = function () {\n\t\t\t\tDIdx = 0;\n\t\t\t\tnDay = DDate[DIdx]\n\t\t\t\tresetHour();\n\t\t\t}\n\t\t\tconst resetMonth = function () {\n\t\t\t\tMIdx = 0;\n\t\t\t\tnMonth = MDate[MIdx]\n\t\t\t\tresetDay();\n\t\t\t}\n\t\t\t// 如果当前年份不为数组中当前值\n\t\t\tif (nYear !== YDate[YIdx]) {\n\t\t\t\tresetMonth();\n\t\t\t}\n\t\t\t// 如果当前月份不为数组中当前值\n\t\t\tif (nMonth !== MDate[MIdx]) {\n\t\t\t\tresetDay();\n\t\t\t}\n\t\t\t// 如果当前“日”不为数组中当前值\n\t\t\tif (nDay !== DDate[DIdx]) {\n\t\t\t\tresetHour();\n\t\t\t}\n\t\t\t// 如果当前“时”不为数组中当前值\n\t\t\tif (nHour !== hDate[hIdx]) {\n\t\t\t\tresetMin();\n\t\t\t}\n\t\t\t// 如果当前“分”不为数组中当前值\n\t\t\tif (nMin !== mDate[mIdx]) {\n\t\t\t\tresetSecond();\n\t\t\t}\n\n\t\t\t// 循环年份数组\n\t\t\tgoYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) {\n\t\t\t\tlet YY = YDate[Yi];\n\t\t\t\t// 如果到达最大值时\n\t\t\t\tif (nMonth > MDate[MDate.length - 1]) {\n\t\t\t\t\tresetMonth();\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// 循环月份数组\n\t\t\t\tgoMonth: for (let Mi = MIdx; Mi < MDate.length; Mi++) {\n\t\t\t\t\t// 赋值、方便后面运算\n\t\t\t\t\tlet MM = MDate[Mi];\n\t\t\t\t\tMM = MM < 10 ? '0' + MM : MM;\n\t\t\t\t\t// 如果到达最大值时\n\t\t\t\t\tif (nDay > DDate[DDate.length - 1]) {\n\t\t\t\t\t\tresetDay();\n\t\t\t\t\t\tif (Mi == MDate.length - 1) {\n\t\t\t\t\t\t\tresetMonth();\n\t\t\t\t\t\t\tcontinue goYear;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t// 循环日期数组\n\t\t\t\t\tgoDay: for (let Di = DIdx; Di < DDate.length; Di++) {\n\t\t\t\t\t\t// 赋值、方便后面运算\n\t\t\t\t\t\tlet DD = DDate[Di];\n\t\t\t\t\t\tlet thisDD = DD < 10 ? '0' + DD : DD;\n\n\t\t\t\t\t\t// 如果到达最大值时\n\t\t\t\t\t\tif (nHour > hDate[hDate.length - 1]) {\n\t\t\t\t\t\t\tresetHour();\n\t\t\t\t\t\t\tif (Di == DDate.length - 1) {\n\t\t\t\t\t\t\t\tresetDay();\n\t\t\t\t\t\t\t\tif (Mi == MDate.length - 1) {\n\t\t\t\t\t\t\t\t\tresetMonth();\n\t\t\t\t\t\t\t\t\tcontinue goYear;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontinue goMonth;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 判断日期的合法性，不合法的话也是跳出当前循环\n\t\t\t\t\t\tif (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true && this.dayRule !== 'workDay' && this.dayRule !== 'lastWeek' && this.dayRule !== 'lastDay') {\n\t\t\t\t\t\t\tresetDay();\n\t\t\t\t\t\t\tcontinue goMonth;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 如果日期规则中有值时\n\t\t\t\t\t\tif (this.dayRule == 'lastDay') {\n\t\t\t\t\t\t\t// 如果不是合法日期则需要将前将日期调到合法日期即月末最后一天\n\n\t\t\t\t\t\t\tif (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {\n\t\t\t\t\t\t\t\twhile (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {\n\t\t\t\t\t\t\t\t\tDD--;\n\n\t\t\t\t\t\t\t\t\tthisDD = DD < 10 ? '0' + DD : DD;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (this.dayRule == 'workDay') {\n\t\t\t\t\t\t\t// 校验并调整如果是2月30号这种日期传进来时需调整至正常月底\n\t\t\t\t\t\t\tif (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {\n\t\t\t\t\t\t\t\twhile (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {\n\t\t\t\t\t\t\t\t\tDD--;\n\t\t\t\t\t\t\t\t\tthisDD = DD < 10 ? '0' + DD : DD;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 获取达到条件的日期是星期X\n\t\t\t\t\t\t\tlet thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');\n\t\t\t\t\t\t\t// 当星期日时\n\t\t\t\t\t\t\tif (thisWeek == 1) {\n\t\t\t\t\t\t\t\t// 先找下一个日，并判断是否为月底\n\t\t\t\t\t\t\t\tDD++;\n\t\t\t\t\t\t\t\tthisDD = DD < 10 ? '0' + DD : DD;\n\t\t\t\t\t\t\t\t// 判断下一日已经不是合法日期\n\t\t\t\t\t\t\t\tif (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {\n\t\t\t\t\t\t\t\t\tDD -= 3;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (thisWeek == 7) {\n\t\t\t\t\t\t\t\t// 当星期6时只需判断不是1号就可进行操作\n\t\t\t\t\t\t\t\tif (this.dayRuleSup !== 1) {\n\t\t\t\t\t\t\t\t\tDD--;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tDD += 2;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (this.dayRule == 'weekDay') {\n\t\t\t\t\t\t\t// 如果指定了是星期几\n\t\t\t\t\t\t\t// 获取当前日期是属于星期几\n\t\t\t\t\t\t\tlet thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');\n\t\t\t\t\t\t\t// 校验当前星期是否在星期池（dayRuleSup）中\n\t\t\t\t\t\t\tif (this.dayRuleSup.indexOf(thisWeek) < 0) {\n\t\t\t\t\t\t\t\t// 如果到达最大值时\n\t\t\t\t\t\t\t\tif (Di == DDate.length - 1) {\n\t\t\t\t\t\t\t\t\tresetDay();\n\t\t\t\t\t\t\t\t\tif (Mi == MDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\tresetMonth();\n\t\t\t\t\t\t\t\t\t\tcontinue goYear;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tcontinue goMonth;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (this.dayRule == 'assWeek') {\n\t\t\t\t\t\t\t// 如果指定了是第几周的星期几\n\t\t\t\t\t\t\t// 获取每月1号是属于星期几\n\t\t\t\t\t\t\tlet thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');\n\t\t\t\t\t\t\tif (this.dayRuleSup[1] >= thisWeek) {\n\t\t\t\t\t\t\t\tDD = (this.dayRuleSup[0] - 1) * 7 + this.dayRuleSup[1] - thisWeek + 1;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tDD = this.dayRuleSup[0] * 7 + this.dayRuleSup[1] - thisWeek + 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (this.dayRule == 'lastWeek') {\n\t\t\t\t\t\t\t// 如果指定了每月最后一个星期几\n\t\t\t\t\t\t\t// 校验并调整如果是2月30号这种日期传进来时需调整至正常月底\n\t\t\t\t\t\t\tif (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {\n\t\t\t\t\t\t\t\twhile (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {\n\t\t\t\t\t\t\t\t\tDD--;\n\t\t\t\t\t\t\t\t\tthisDD = DD < 10 ? '0' + DD : DD;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 获取月末最后一天是星期几\n\t\t\t\t\t\t\tlet thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');\n\t\t\t\t\t\t\t// 找到要求中最近的那个星期几\n\t\t\t\t\t\t\tif (this.dayRuleSup < thisWeek) {\n\t\t\t\t\t\t\t\tDD -= thisWeek - this.dayRuleSup;\n\t\t\t\t\t\t\t} else if (this.dayRuleSup > thisWeek) {\n\t\t\t\t\t\t\t\tDD -= 7 - (this.dayRuleSup - thisWeek)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 判断时间值是否小于10置换成“05”这种格式\n\t\t\t\t\t\tDD = DD < 10 ? '0' + DD : DD;\n\n\t\t\t\t\t\t// 循环“时”数组\n\t\t\t\t\t\tgoHour: for (let hi = hIdx; hi < hDate.length; hi++) {\n\t\t\t\t\t\t\tlet hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi]\n\n\t\t\t\t\t\t\t// 如果到达最大值时\n\t\t\t\t\t\t\tif (nMin > mDate[mDate.length - 1]) {\n\t\t\t\t\t\t\t\tresetMin();\n\t\t\t\t\t\t\t\tif (hi == hDate.length - 1) {\n\t\t\t\t\t\t\t\t\tresetHour();\n\t\t\t\t\t\t\t\t\tif (Di == DDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\tresetDay();\n\t\t\t\t\t\t\t\t\t\tif (Mi == MDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\tresetMonth();\n\t\t\t\t\t\t\t\t\t\t\tcontinue goYear;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tcontinue goMonth;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tcontinue goDay;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 循环\"分\"数组\n\t\t\t\t\t\t\tgoMin: for (let mi = mIdx; mi < mDate.length; mi++) {\n\t\t\t\t\t\t\t\tlet mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi];\n\n\t\t\t\t\t\t\t\t// 如果到达最大值时\n\t\t\t\t\t\t\t\tif (nSecond > sDate[sDate.length - 1]) {\n\t\t\t\t\t\t\t\t\tresetSecond();\n\t\t\t\t\t\t\t\t\tif (mi == mDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\tresetMin();\n\t\t\t\t\t\t\t\t\t\tif (hi == hDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\tresetHour();\n\t\t\t\t\t\t\t\t\t\t\tif (Di == DDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tresetDay();\n\t\t\t\t\t\t\t\t\t\t\t\tif (Mi == MDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tresetMonth();\n\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue goYear;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue goMonth;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tcontinue goDay;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tcontinue goHour;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// 循环\"秒\"数组\n\t\t\t\t\t\t\t\tgoSecond: for (let si = sIdx; si <= sDate.length - 1; si++) {\n\t\t\t\t\t\t\t\t\tlet ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si];\n\t\t\t\t\t\t\t\t\t// 添加当前时间（时间合法性在日期循环时已经判断）\n\t\t\t\t\t\t\t\t\tif (MM !== '00' && DD !== '00') {\n\t\t\t\t\t\t\t\t\t\tresultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss)\n\t\t\t\t\t\t\t\t\t\tnums++;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t// 如果条数满了就退出循环\n\t\t\t\t\t\t\t\t\tif (nums == 5) break goYear;\n\t\t\t\t\t\t\t\t\t// 如果到达最大值时\n\t\t\t\t\t\t\t\t\tif (si == sDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\tresetSecond();\n\t\t\t\t\t\t\t\t\t\tif (mi == mDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\tresetMin();\n\t\t\t\t\t\t\t\t\t\t\tif (hi == hDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\t\tresetHour();\n\t\t\t\t\t\t\t\t\t\t\t\tif (Di == DDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tresetDay();\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (Mi == MDate.length - 1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresetMonth();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue goYear;\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue goMonth;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue goDay;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tcontinue goHour;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tcontinue goMin;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} //goSecond\n\t\t\t\t\t\t\t} //goMin\n\t\t\t\t\t\t}//goHour\n\t\t\t\t\t}//goDay\n\t\t\t\t}//goMonth\n\t\t\t}\n\t\t\t// 判断100年内的结果条数\n\t\t\tif (resultArr.length == 0) {\n\t\t\t\tthis.resultList = ['没有达到条件的结果！'];\n\t\t\t} else {\n\t\t\t\tthis.resultList = resultArr;\n\t\t\t\tif (resultArr.length !== 5) {\n\t\t\t\t\tthis.resultList.push('最近100年内只有上面' + resultArr.length + '条结果！')\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 计算完成-显示结果\n\t\t\tthis.isShow = true;\n\n\n\t\t},\n\t\t// 用于计算某位数字在数组中的索引\n\t\tgetIndex(arr, value) {\n\t\t\tif (value <= arr[0] || value > arr[arr.length - 1]) {\n\t\t\t\treturn 0;\n\t\t\t} else {\n\t\t\t\tfor (let i = 0; i < arr.length - 1; i++) {\n\t\t\t\t\tif (value > arr[i] && value <= arr[i + 1]) {\n\t\t\t\t\t\treturn i + 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 获取\"年\"数组\n\t\tgetYearArr(rule, year) {\n\t\t\tthis.dateArr[5] = this.getOrderArr(year, year + 100);\n\t\t\tif (rule !== undefined) {\n\t\t\t\tif (rule.indexOf('-') >= 0) {\n\t\t\t\t\tthis.dateArr[5] = this.getCycleArr(rule, year + 100, false)\n\t\t\t\t} else if (rule.indexOf('/') >= 0) {\n\t\t\t\t\tthis.dateArr[5] = this.getAverageArr(rule, year + 100)\n\t\t\t\t} else if (rule !== '*') {\n\t\t\t\t\tthis.dateArr[5] = this.getAssignArr(rule)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 获取\"月\"数组\n\t\tgetMonthArr(rule) {\n\t\t\tthis.dateArr[4] = this.getOrderArr(1, 12);\n\t\t\tif (rule.indexOf('-') >= 0) {\n\t\t\t\tthis.dateArr[4] = this.getCycleArr(rule, 12, false)\n\t\t\t} else if (rule.indexOf('/') >= 0) {\n\t\t\t\tthis.dateArr[4] = this.getAverageArr(rule, 12)\n\t\t\t} else if (rule !== '*') {\n\t\t\t\tthis.dateArr[4] = this.getAssignArr(rule)\n\t\t\t}\n\t\t},\n\t\t// 获取\"日\"数组-主要为日期规则\n\t\tgetWeekArr(rule) {\n\t\t\t// 只有当日期规则的两个值均为“”时则表达日期是有选项的\n\t\t\tif (this.dayRule == '' && this.dayRuleSup == '') {\n\t\t\t\tif (rule.indexOf('-') >= 0) {\n\t\t\t\t\tthis.dayRule = 'weekDay';\n\t\t\t\t\tthis.dayRuleSup = this.getCycleArr(rule, 7, false)\n\t\t\t\t} else if (rule.indexOf('#') >= 0) {\n\t\t\t\t\tthis.dayRule = 'assWeek';\n\t\t\t\t\tlet matchRule = rule.match(/[0-9]{1}/g);\n\t\t\t\t\tthis.dayRuleSup = [Number(matchRule[1]), Number(matchRule[0])];\n\t\t\t\t\tthis.dateArr[3] = [1];\n\t\t\t\t\tif (this.dayRuleSup[1] == 7) {\n\t\t\t\t\t\tthis.dayRuleSup[1] = 0;\n\t\t\t\t\t}\n\t\t\t\t} else if (rule.indexOf('L') >= 0) {\n\t\t\t\t\tthis.dayRule = 'lastWeek';\n\t\t\t\t\tthis.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]);\n\t\t\t\t\tthis.dateArr[3] = [31];\n\t\t\t\t\tif (this.dayRuleSup == 7) {\n\t\t\t\t\t\tthis.dayRuleSup = 0;\n\t\t\t\t\t}\n\t\t\t\t} else if (rule !== '*' && rule !== '?') {\n\t\t\t\t\tthis.dayRule = 'weekDay';\n\t\t\t\t\tthis.dayRuleSup = this.getAssignArr(rule)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 获取\"日\"数组-少量为日期规则\n\t\tgetDayArr(rule) {\n\t\t\tthis.dateArr[3] = this.getOrderArr(1, 31);\n\t\t\tthis.dayRule = '';\n\t\t\tthis.dayRuleSup = '';\n\t\t\tif (rule.indexOf('-') >= 0) {\n\t\t\t\tthis.dateArr[3] = this.getCycleArr(rule, 31, false)\n\t\t\t\tthis.dayRuleSup = 'null';\n\t\t\t} else if (rule.indexOf('/') >= 0) {\n\t\t\t\tthis.dateArr[3] = this.getAverageArr(rule, 31)\n\t\t\t\tthis.dayRuleSup = 'null';\n\t\t\t} else if (rule.indexOf('W') >= 0) {\n\t\t\t\tthis.dayRule = 'workDay';\n\t\t\t\tthis.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]);\n\t\t\t\tthis.dateArr[3] = [this.dayRuleSup];\n\t\t\t} else if (rule.indexOf('L') >= 0) {\n\t\t\t\tthis.dayRule = 'lastDay';\n\t\t\t\tthis.dayRuleSup = 'null';\n\t\t\t\tthis.dateArr[3] = [31];\n\t\t\t} else if (rule !== '*' && rule !== '?') {\n\t\t\t\tthis.dateArr[3] = this.getAssignArr(rule)\n\t\t\t\tthis.dayRuleSup = 'null';\n\t\t\t} else if (rule == '*') {\n\t\t\t\tthis.dayRuleSup = 'null';\n\t\t\t}\n\t\t},\n\t\t// 获取\"时\"数组\n\t\tgetHourArr(rule) {\n\t\t\tthis.dateArr[2] = this.getOrderArr(0, 23);\n\t\t\tif (rule.indexOf('-') >= 0) {\n\t\t\t\tthis.dateArr[2] = this.getCycleArr(rule, 24, true)\n\t\t\t} else if (rule.indexOf('/') >= 0) {\n\t\t\t\tthis.dateArr[2] = this.getAverageArr(rule, 23)\n\t\t\t} else if (rule !== '*') {\n\t\t\t\tthis.dateArr[2] = this.getAssignArr(rule)\n\t\t\t}\n\t\t},\n\t\t// 获取\"分\"数组\n\t\tgetMinArr(rule) {\n\t\t\tthis.dateArr[1] = this.getOrderArr(0, 59);\n\t\t\tif (rule.indexOf('-') >= 0) {\n\t\t\t\tthis.dateArr[1] = this.getCycleArr(rule, 60, true)\n\t\t\t} else if (rule.indexOf('/') >= 0) {\n\t\t\t\tthis.dateArr[1] = this.getAverageArr(rule, 59)\n\t\t\t} else if (rule !== '*') {\n\t\t\t\tthis.dateArr[1] = this.getAssignArr(rule)\n\t\t\t}\n\t\t},\n\t\t// 获取\"秒\"数组\n\t\tgetSecondArr(rule) {\n\t\t\tthis.dateArr[0] = this.getOrderArr(0, 59);\n\t\t\tif (rule.indexOf('-') >= 0) {\n\t\t\t\tthis.dateArr[0] = this.getCycleArr(rule, 60, true)\n\t\t\t} else if (rule.indexOf('/') >= 0) {\n\t\t\t\tthis.dateArr[0] = this.getAverageArr(rule, 59)\n\t\t\t} else if (rule !== '*') {\n\t\t\t\tthis.dateArr[0] = this.getAssignArr(rule)\n\t\t\t}\n\t\t},\n\t\t// 根据传进来的min-max返回一个顺序的数组\n\t\tgetOrderArr(min, max) {\n\t\t\tlet arr = [];\n\t\t\tfor (let i = min; i <= max; i++) {\n\t\t\t\tarr.push(i);\n\t\t\t}\n\t\t\treturn arr;\n\t\t},\n\t\t// 根据规则中指定的零散值返回一个数组\n\t\tgetAssignArr(rule) {\n\t\t\tlet arr = [];\n\t\t\tlet assiginArr = rule.split(',');\n\t\t\tfor (let i = 0; i < assiginArr.length; i++) {\n\t\t\t\tarr[i] = Number(assiginArr[i])\n\t\t\t}\n\t\t\tarr.sort(this.compare)\n\t\t\treturn arr;\n\t\t},\n\t\t// 根据一定算术规则计算返回一个数组\n\t\tgetAverageArr(rule, limit) {\n\t\t\tlet arr = [];\n\t\t\tlet agArr = rule.split('/');\n\t\t\tlet min = Number(agArr[0]);\n\t\t\tlet step = Number(agArr[1]);\n\t\t\twhile (min <= limit) {\n\t\t\t\tarr.push(min);\n\t\t\t\tmin += step;\n\t\t\t}\n\t\t\treturn arr;\n\t\t},\n\t\t// 根据规则返回一个具有周期性的数组\n\t\tgetCycleArr(rule, limit, status) {\n\t\t\t// status--表示是否从0开始（则从1开始）\n\t\t\tlet arr = [];\n\t\t\tlet cycleArr = rule.split('-');\n\t\t\tlet min = Number(cycleArr[0]);\n\t\t\tlet max = Number(cycleArr[1]);\n\t\t\tif (min > max) {\n\t\t\t\tmax += limit;\n\t\t\t}\n\t\t\tfor (let i = min; i <= max; i++) {\n\t\t\t\tlet add = 0;\n\t\t\t\tif (status == false && i % limit == 0) {\n\t\t\t\t\tadd = limit;\n\t\t\t\t}\n\t\t\t\tarr.push(Math.round(i % limit + add))\n\t\t\t}\n\t\t\tarr.sort(this.compare)\n\t\t\treturn arr;\n\t\t},\n\t\t// 比较数字大小（用于Array.sort）\n\t\tcompare(value1, value2) {\n\t\t\tif (value2 - value1 > 0) {\n\t\t\t\treturn -1;\n\t\t\t} else {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t},\n\t\t// 格式化日期格式如：2017-9-19 18:04:33\n\t\tformatDate(value, type) {\n\t\t\t// 计算日期相关值\n\t\t\tlet time = typeof value == 'number' ? new Date(value) : value;\n\t\t\tlet Y = time.getFullYear();\n\t\t\tlet M = time.getMonth() + 1;\n\t\t\tlet D = time.getDate();\n\t\t\tlet h = time.getHours();\n\t\t\tlet m = time.getMinutes();\n\t\t\tlet s = time.getSeconds();\n\t\t\tlet week = time.getDay();\n\t\t\t// 如果传递了type的话\n\t\t\tif (type == undefined) {\n\t\t\t\treturn Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);\n\t\t\t} else if (type == 'week') {\n\t\t\t\t// 在quartz中 1为星期日\n\t\t\t\treturn week + 1;\n\t\t\t}\n\t\t},\n\t\t// 检查日期是否存在\n\t\tcheckDate(value) {\n\t\t\tlet time = new Date(value);\n\t\t\tlet format = this.formatDate(time)\n\t\t\treturn value === format;\n\t\t}\n\t},\n\twatch: {\n\t\t'ex': 'expressionChange'\n\t},\n\tprops: ['ex'],\n\tmounted: function () {\n\t\t// 初始化 获取一次结果\n\t\tthis.expressionChange();\n\t}\n}\n\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/second.vue",
    "content": "<template>\n\t<el-form size=\"small\">\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"1\">\n\t\t\t\t秒，允许的通配符[, - * /]\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"2\">\n\t\t\t\t周期从\n\t\t\t\t<el-input-number v-model='cycle01' :min=\"0\" :max=\"58\" /> -\n\t\t\t\t<el-input-number v-model='cycle02' :min=\"cycle01 ? cycle01 + 1 : 1\" :max=\"59\" /> 秒\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"3\">\n\t\t\t\t从\n\t\t\t\t<el-input-number v-model='average01' :min=\"0\" :max=\"58\" /> 秒开始，每\n\t\t\t\t<el-input-number v-model='average02' :min=\"1\" :max=\"59 - average01 || 0\" /> 秒执行一次\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"4\">\n\t\t\t\t指定\n\t\t\t\t<el-select clearable v-model=\"checkboxList\" placeholder=\"可多选\" multiple style=\"width:100%\">\n\t\t\t\t\t<el-option v-for=\"item in 60\" :key=\"item\" :value=\"item-1\">{{item-1}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\t</el-form>\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tradioValue: 1,\n\t\t\tcycle01: 1,\n\t\t\tcycle02: 2,\n\t\t\taverage01: 0,\n\t\t\taverage02: 1,\n\t\t\tcheckboxList: [],\n\t\t\tcheckNum: this.$options.propsData.check\n\t\t}\n\t},\n\tname: 'crontab-second',\n\tprops: ['check', 'radioParent'],\n\tmethods: {\n\t\t// 单选按钮值变化时\n\t\tradioChange() {\n\t\t\tswitch (this.radioValue) {\n\t\t\t\tcase 1:\n\t\t\t\t\tthis.$emit('update', 'second', '*', 'second');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.$emit('update', 'second', this.cycleTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.$emit('update', 'second', this.averageTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.$emit('update', 'second', this.checkboxString);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t// 周期两个值变化时\n\t\tcycleChange() {\n\t\t\tif (this.radioValue == '2') {\n\t\t\t\tthis.$emit('update', 'second', this.cycleTotal);\n\t\t\t}\n\t\t},\n\t\t// 平均两个值变化时\n\t\taverageChange() {\n\t\t\tif (this.radioValue == '3') {\n\t\t\t\tthis.$emit('update', 'second', this.averageTotal);\n\t\t\t}\n\t\t},\n\t\t// checkbox值变化时\n\t\tcheckboxChange() {\n\t\t\tif (this.radioValue == '4') {\n\t\t\t\tthis.$emit('update', 'second', this.checkboxString);\n\t\t\t}\n\t\t}\n\t},\n\twatch: {\n\t\t'radioValue': 'radioChange',\n\t\t'cycleTotal': 'cycleChange',\n\t\t'averageTotal': 'averageChange',\n\t\t'checkboxString': 'checkboxChange',\n\t\tradioParent() {\n\t\t\tthis.radioValue = this.radioParent\n\t\t}\n\t},\n\tcomputed: {\n\t\t// 计算两个周期值\n\t\tcycleTotal: function () {\n\t\t\tconst cycle01 = this.checkNum(this.cycle01, 0, 58)\n\t\t\tconst cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)\n\t\t\treturn cycle01 + '-' + cycle02;\n\t\t},\n\t\t// 计算平均用到的值\n\t\taverageTotal: function () {\n\t\t\tconst average01 = this.checkNum(this.average01, 0, 58)\n\t\t\tconst average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)\n\t\t\treturn average01 + '/' + average02;\n\t\t},\n\t\t// 计算勾选的checkbox值合集\n\t\tcheckboxString: function () {\n\t\t\tlet str = this.checkboxList.join();\n\t\t\treturn str == '' ? '*' : str;\n\t\t}\n\t}\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/week.vue",
    "content": "<template>\n\t<el-form size='small'>\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"1\">\n\t\t\t\t周，允许的通配符[, - * ? / L #]\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"2\">\n\t\t\t\t不指定\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"3\">\n\t\t\t\t周期从星期\n\t\t\t\t<el-select clearable v-model=\"cycle01\">\n\t\t\t\t\t<el-option\n\t\t\t\t\t\tv-for=\"(item,index) of weekList\"\n\t\t\t\t\t\t:key=\"index\"\n\t\t\t\t\t\t:label=\"item.value\"\n\t\t\t\t\t\t:value=\"item.key\"\n\t\t\t\t\t\t:disabled=\"item.key === 1\"\n\t\t\t\t\t>{{item.value}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t\t-\n\t\t\t\t<el-select clearable v-model=\"cycle02\">\n\t\t\t\t\t<el-option\n\t\t\t\t\t\tv-for=\"(item,index) of weekList\"\n\t\t\t\t\t\t:key=\"index\"\n\t\t\t\t\t\t:label=\"item.value\"\n\t\t\t\t\t\t:value=\"item.key\"\n\t\t\t\t\t\t:disabled=\"item.key < cycle01 && item.key !== 1\"\n\t\t\t\t\t>{{item.value}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"4\">\n\t\t\t\t第\n\t\t\t\t<el-input-number v-model='average01' :min=\"1\" :max=\"4\" /> 周的星期\n\t\t\t\t<el-select clearable v-model=\"average02\">\n\t\t\t\t\t<el-option v-for=\"(item,index) of weekList\" :key=\"index\" :label=\"item.value\" :value=\"item.key\">{{item.value}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"5\">\n\t\t\t\t本月最后一个星期\n\t\t\t\t<el-select clearable v-model=\"weekday\">\n\t\t\t\t\t<el-option v-for=\"(item,index) of weekList\" :key=\"index\" :label=\"item.value\" :value=\"item.key\">{{item.value}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio v-model='radioValue' :label=\"6\">\n\t\t\t\t指定\n\t\t\t\t<el-select clearable v-model=\"checkboxList\" placeholder=\"可多选\" multiple style=\"width:100%\">\n\t\t\t\t\t<el-option v-for=\"(item,index) of weekList\" :key=\"index\" :label=\"item.value\" :value=\"String(item.key)\">{{item.value}}</el-option>\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t</el-form>\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tradioValue: 2,\n\t\t\tweekday: 2,\n\t\t\tcycle01: 2,\n\t\t\tcycle02: 3,\n\t\t\taverage01: 1,\n\t\t\taverage02: 2,\n\t\t\tcheckboxList: [],\n\t\t\tweekList: [\n\t\t\t\t{\n\t\t\t\t\tkey: 2,\n\t\t\t\t\tvalue: '星期一'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: 3,\n\t\t\t\t\tvalue: '星期二'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: 4,\n\t\t\t\t\tvalue: '星期三'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: 5,\n\t\t\t\t\tvalue: '星期四'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: 6,\n\t\t\t\t\tvalue: '星期五'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: 7,\n\t\t\t\t\tvalue: '星期六'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: 1,\n\t\t\t\t\tvalue: '星期日'\n\t\t\t\t}\n\t\t\t],\n\t\t\tcheckNum: this.$options.propsData.check\n\t\t}\n\t},\n\tname: 'crontab-week',\n\tprops: ['check', 'cron'],\n\tmethods: {\n\t\t// 单选按钮值变化时\n\t\tradioChange() {\n\t\t\tif (this.radioValue !== 2 && this.cron.day !== '?') {\n\t\t\t\tthis.$emit('update', 'day', '?', 'week');\n\t\t\t}\n\t\t\tswitch (this.radioValue) {\n\t\t\t\tcase 1:\n\t\t\t\t\tthis.$emit('update', 'week', '*');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.$emit('update', 'week', '?');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.$emit('update', 'week', this.cycleTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.$emit('update', 'week', this.averageTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 5:\n\t\t\t\t\tthis.$emit('update', 'week', this.weekdayCheck + 'L');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 6:\n\t\t\t\t\tthis.$emit('update', 'week', this.checkboxString);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\n\t\t// 周期两个值变化时\n\t\tcycleChange() {\n\t\t\tif (this.radioValue == '3') {\n\t\t\t\tthis.$emit('update', 'week', this.cycleTotal);\n\t\t\t}\n\t\t},\n\t\t// 平均两个值变化时\n\t\taverageChange() {\n\t\t\tif (this.radioValue == '4') {\n\t\t\t\tthis.$emit('update', 'week', this.averageTotal);\n\t\t\t}\n\t\t},\n\t\t// 最近工作日值变化时\n\t\tweekdayChange() {\n\t\t\tif (this.radioValue == '5') {\n\t\t\t\tthis.$emit('update', 'week', this.weekday + 'L');\n\t\t\t}\n\t\t},\n\t\t// checkbox值变化时\n\t\tcheckboxChange() {\n\t\t\tif (this.radioValue == '6') {\n\t\t\t\tthis.$emit('update', 'week', this.checkboxString);\n\t\t\t}\n\t\t},\n\t},\n\twatch: {\n\t\t'radioValue': 'radioChange',\n\t\t'cycleTotal': 'cycleChange',\n\t\t'averageTotal': 'averageChange',\n\t\t'weekdayCheck': 'weekdayChange',\n\t\t'checkboxString': 'checkboxChange',\n\t},\n\tcomputed: {\n\t\t// 计算两个周期值\n\t\tcycleTotal: function () {\n\t\t\tthis.cycle01 = this.checkNum(this.cycle01, 1, 7)\n\t\t\tthis.cycle02 = this.checkNum(this.cycle02, 1, 7)\n\t\t\treturn this.cycle01 + '-' + this.cycle02;\n\t\t},\n\t\t// 计算平均用到的值\n\t\taverageTotal: function () {\n\t\t\tthis.average01 = this.checkNum(this.average01, 1, 4)\n\t\t\tthis.average02 = this.checkNum(this.average02, 1, 7)\n\t\t\treturn this.average02 + '#' + this.average01;\n\t\t},\n\t\t// 最近的工作日（格式）\n\t\tweekdayCheck: function () {\n\t\t\tthis.weekday = this.checkNum(this.weekday, 1, 7)\n\t\t\treturn this.weekday;\n\t\t},\n\t\t// 计算勾选的checkbox值合集\n\t\tcheckboxString: function () {\n\t\t\tlet str = this.checkboxList.join();\n\t\t\treturn str == '' ? '*' : str;\n\t\t}\n\t}\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Crontab/year.vue",
    "content": "<template>\n\t<el-form size=\"small\">\n\t\t<el-form-item>\n\t\t\t<el-radio :label=\"1\" v-model='radioValue'>\n\t\t\t\t不填，允许的通配符[, - * /]\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio :label=\"2\" v-model='radioValue'>\n\t\t\t\t每年\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio :label=\"3\" v-model='radioValue'>\n\t\t\t\t周期从\n\t\t\t\t<el-input-number v-model='cycle01' :min='fullYear' :max=\"2098\" /> -\n\t\t\t\t<el-input-number v-model='cycle02' :min=\"cycle01 ? cycle01 + 1 : fullYear + 1\" :max=\"2099\" />\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio :label=\"4\" v-model='radioValue'>\n\t\t\t\t从\n\t\t\t\t<el-input-number v-model='average01' :min='fullYear' :max=\"2098\"/> 年开始，每\n\t\t\t\t<el-input-number v-model='average02' :min=\"1\" :max=\"2099 - average01 || fullYear\" /> 年执行一次\n\t\t\t</el-radio>\n\n\t\t</el-form-item>\n\n\t\t<el-form-item>\n\t\t\t<el-radio :label=\"5\" v-model='radioValue'>\n\t\t\t\t指定\n\t\t\t\t<el-select clearable v-model=\"checkboxList\" placeholder=\"可多选\" multiple>\n\t\t\t\t\t<el-option v-for=\"item in 9\" :key=\"item\" :value=\"item - 1 + fullYear\" :label=\"item -1 + fullYear\" />\n\t\t\t\t</el-select>\n\t\t\t</el-radio>\n\t\t</el-form-item>\n\t</el-form>\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tfullYear: 0,\n\t\t\tradioValue: 1,\n\t\t\tcycle01: 0,\n\t\t\tcycle02: 0,\n\t\t\taverage01: 0,\n\t\t\taverage02: 1,\n\t\t\tcheckboxList: [],\n\t\t\tcheckNum: this.$options.propsData.check\n\t\t}\n\t},\n\tname: 'crontab-year',\n\tprops: ['check', 'month', 'cron'],\n\tmethods: {\n\t\t// 单选按钮值变化时\n\t\tradioChange() {\n\t\t\tswitch (this.radioValue) {\n\t\t\t\tcase 1:\n\t\t\t\t\tthis.$emit('update', 'year', '');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.$emit('update', 'year', '*');\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.$emit('update', 'year', this.cycleTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.$emit('update', 'year', this.averageTotal);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 5:\n\t\t\t\t\tthis.$emit('update', 'year', this.checkboxString);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t// 周期两个值变化时\n\t\tcycleChange() {\n\t\t\tif (this.radioValue == '3') {\n\t\t\t\tthis.$emit('update', 'year', this.cycleTotal);\n\t\t\t}\n\t\t},\n\t\t// 平均两个值变化时\n\t\taverageChange() {\n\t\t\tif (this.radioValue == '4') {\n\t\t\t\tthis.$emit('update', 'year', this.averageTotal);\n\t\t\t}\n\t\t},\n\t\t// checkbox值变化时\n\t\tcheckboxChange() {\n\t\t\tif (this.radioValue == '5') {\n\t\t\t\tthis.$emit('update', 'year', this.checkboxString);\n\t\t\t}\n\t\t}\n\t},\n\twatch: {\n\t\t'radioValue': 'radioChange',\n\t\t'cycleTotal': 'cycleChange',\n\t\t'averageTotal': 'averageChange',\n\t\t'checkboxString': 'checkboxChange'\n\t},\n\tcomputed: {\n\t\t// 计算两个周期值\n\t\tcycleTotal: function () {\n\t\t\tconst cycle01 = this.checkNum(this.cycle01, this.fullYear, 2098)\n\t\t\tconst cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : this.fullYear + 1, 2099)\n\t\t\treturn cycle01 + '-' + cycle02;\n\t\t},\n\t\t// 计算平均用到的值\n\t\taverageTotal: function () {\n\t\t\tconst average01 = this.checkNum(this.average01, this.fullYear, 2098)\n\t\t\tconst average02 = this.checkNum(this.average02, 1, 2099 - average01 || this.fullYear)\n\t\t\treturn average01 + '/' + average02;\n\t\t},\n\t\t// 计算勾选的checkbox值合集\n\t\tcheckboxString: function () {\n\t\t\tlet str = this.checkboxList.join();\n\t\t\treturn str;\n\t\t}\n\t},\n\tmounted: function () {\n\t\t// 仅获取当前年份\n\t\tthis.fullYear = Number(new Date().getFullYear());\n\t\tthis.cycle01 = this.fullYear\n\t\tthis.average01 = this.fullYear\n\t}\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/components/DictData/index.js",
    "content": "import Vue from 'vue'\nimport store from '@/store'\nimport DataDict from '@/utils/dict'\nimport { getDicts as getDicts } from '@/api/system/dict/data'\n\nfunction searchDictByKey(dict, key) {\n  if (key == null && key == \"\") {\n    return null\n  }\n  try {\n    for (let i = 0; i < dict.length; i++) {\n      if (dict[i].key == key) {\n        return dict[i].value\n      }\n    }\n  } catch (e) {\n    return null\n  }\n}\n\nfunction install() {\n  Vue.use(DataDict, {\n    metas: {\n      '*': {\n        labelField: 'dictLabel',\n        valueField: 'dictValue',\n        request(dictMeta) {\n          const storeDict = searchDictByKey(store.getters.dict, dictMeta.type)\n          if (storeDict) {\n            return new Promise(resolve => { resolve(storeDict) })\n          } else {\n            return new Promise((resolve, reject) => {\n              getDicts(dictMeta.type).then(res => {\n                store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data })\n                resolve(res.data)\n              }).catch(error => {\n                reject(error)\n              })\n            })\n          }\n        },\n      },\n    },\n  })\n}\n\nexport default {\n  install,\n}"
  },
  {
    "path": "vue_campus_admin/src/components/DictTag/index.vue",
    "content": "<template>\n  <div>\n    <template v-for=\"(item, index) in options\">\n      <template v-if=\"values.includes(item.value)\">\n        <span\n          v-if=\"item.raw.listClass == 'default' || item.raw.listClass == ''\"\n          :key=\"item.value\"\n          :index=\"index\"\n          :class=\"item.raw.cssClass\"\n          >{{ item.label }}</span\n        >\n        <el-tag\n          v-else\n          :disable-transitions=\"true\"\n          :key=\"item.value\"\n          :index=\"index\"\n          :type=\"item.raw.listClass == 'primary' ? '' : item.raw.listClass\"\n          :class=\"item.raw.cssClass\"\n        >\n          {{ item.label }}\n        </el-tag>\n      </template>\n    </template>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: \"DictTag\",\n  props: {\n    options: {\n      type: Array,\n      default: null,\n    },\n    value: [Number, String, Array],\n  },\n  computed: {\n    values() {\n      if (this.value !== null && typeof this.value !== 'undefined') {\n        return Array.isArray(this.value) ? this.value : [String(this.value)];\n      } else {\n        return [];\n      }\n    },\n  },\n};\n</script>\n<style scoped>\n.el-tag + .el-tag {\n  margin-left: 10px;\n}\n</style>"
  },
  {
    "path": "vue_campus_admin/src/components/Editor/index.vue",
    "content": "<template>\n  <div>\n    <el-upload\n      :action=\"uploadUrl\"\n      :before-upload=\"handleBeforeUpload\"\n      :on-success=\"handleUploadSuccess\"\n      :on-error=\"handleUploadError\"\n      name=\"file\"\n      :show-file-list=\"false\"\n      :headers=\"headers\"\n      style=\"display: none\"\n      ref=\"upload\"\n      v-if=\"this.type == 'url'\"\n    >\n    </el-upload>\n    <div class=\"editor\" ref=\"editor\" :style=\"styles\"></div>\n  </div>\n</template>\n\n<script>\nimport Quill from \"quill\";\nimport \"quill/dist/quill.core.css\";\nimport \"quill/dist/quill.snow.css\";\nimport \"quill/dist/quill.bubble.css\";\nimport { getToken } from \"@/utils/auth\";\n\nexport default {\n  name: \"Editor\",\n  props: {\n    /* 编辑器的内容 */\n    value: {\n      type: String,\n      default: \"\",\n    },\n    /* 高度 */\n    height: {\n      type: Number,\n      default: null,\n    },\n    /* 最小高度 */\n    minHeight: {\n      type: Number,\n      default: null,\n    },\n    /* 只读 */\n    readOnly: {\n      type: Boolean,\n      default: false,\n    },\n    // 上传文件大小限制(MB)\n    fileSize: {\n      type: Number,\n      default: 5,\n    },\n    /* 类型（base64格式、url格式） */\n    type: {\n      type: String,\n      default: \"url\",\n    }\n  },\n  data() {\n    return {\n      uploadUrl: process.env.VUE_APP_BASE_API + \"/common/upload\", // 上传的图片服务器地址\n      headers: {\n        Authorization: \"Bearer \" + getToken()\n      },\n      Quill: null,\n      currentValue: \"\",\n      options: {\n        theme: \"snow\",\n        bounds: document.body,\n        debug: \"warn\",\n        modules: {\n          // 工具栏配置\n          toolbar: [\n            [\"bold\", \"italic\", \"underline\", \"strike\"],       // 加粗 斜体 下划线 删除线\n            [\"blockquote\", \"code-block\"],                    // 引用  代码块\n            [{ list: \"ordered\" }, { list: \"bullet\" }],       // 有序、无序列表\n            [{ indent: \"-1\" }, { indent: \"+1\" }],            // 缩进\n            [{ size: [\"small\", false, \"large\", \"huge\"] }],   // 字体大小\n            [{ header: [1, 2, 3, 4, 5, 6, false] }],         // 标题\n            [{ color: [] }, { background: [] }],             // 字体颜色、字体背景颜色\n            [{ align: [] }],                                 // 对齐方式\n            [\"clean\"],                                       // 清除文本格式\n            [\"link\", \"image\", \"video\"]                       // 链接、图片、视频\n          ],\n        },\n        placeholder: \"请输入内容\",\n        readOnly: this.readOnly,\n      },\n    };\n  },\n  computed: {\n    styles() {\n      let style = {};\n      if (this.minHeight) {\n        style.minHeight = `${this.minHeight}px`;\n      }\n      if (this.height) {\n        style.height = `${this.height}px`;\n      }\n      return style;\n    },\n  },\n  watch: {\n    value: {\n      handler(val) {\n        if (val !== this.currentValue) {\n          this.currentValue = val === null ? \"\" : val;\n          if (this.Quill) {\n            this.Quill.pasteHTML(this.currentValue);\n          }\n        }\n      },\n      immediate: true,\n    },\n  },\n  mounted() {\n    this.init();\n  },\n  beforeDestroy() {\n    this.Quill = null;\n  },\n  methods: {\n    init() {\n      const editor = this.$refs.editor;\n      this.Quill = new Quill(editor, this.options);\n      // 如果设置了上传地址则自定义图片上传事件\n      if (this.type == 'url') {\n        let toolbar = this.Quill.getModule(\"toolbar\");\n        toolbar.addHandler(\"image\", (value) => {\n          this.uploadType = \"image\";\n          if (value) {\n            this.$refs.upload.$children[0].$refs.input.click();\n          } else {\n            this.quill.format(\"image\", false);\n          }\n        });\n      }\n      this.Quill.pasteHTML(this.currentValue);\n      this.Quill.on(\"text-change\", (delta, oldDelta, source) => {\n        const html = this.$refs.editor.children[0].innerHTML;\n        const text = this.Quill.getText();\n        const quill = this.Quill;\n        this.currentValue = html;\n        this.$emit(\"input\", html);\n        this.$emit(\"on-change\", { html, text, quill });\n      });\n      this.Quill.on(\"text-change\", (delta, oldDelta, source) => {\n        this.$emit(\"on-text-change\", delta, oldDelta, source);\n      });\n      this.Quill.on(\"selection-change\", (range, oldRange, source) => {\n        this.$emit(\"on-selection-change\", range, oldRange, source);\n      });\n      this.Quill.on(\"editor-change\", (eventName, ...args) => {\n        this.$emit(\"on-editor-change\", eventName, ...args);\n      });\n    },\n    // 上传前校检格式和大小\n    handleBeforeUpload(file) {\n      // 校检文件大小\n      if (this.fileSize) {\n        const isLt = file.size / 1024 / 1024 < this.fileSize;\n        if (!isLt) {\n          this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);\n          return false;\n        }\n      }\n      return true;\n    },\n    handleUploadSuccess(res, file) {\n      // 获取富文本组件实例\n      let quill = this.Quill;\n      // 如果上传成功\n      if (res.code == 200) {\n        // 获取光标所在位置\n        let length = quill.getSelection().index;\n        // 插入图片  res.url为服务器返回的图片地址\n        quill.insertEmbed(length, \"image\", process.env.VUE_APP_BASE_API + res.fileName);\n        // 调整光标到最后\n        quill.setSelection(length + 1);\n      } else {\n        this.$message.error(\"图片插入失败\");\n      }\n    },\n    handleUploadError() {\n      this.$message.error(\"图片插入失败\");\n    },\n  },\n};\n</script>\n\n<style>\n.editor, .ql-toolbar {\n  white-space: pre-wrap !important;\n  line-height: normal !important;\n}\n.quill-img {\n  display: none;\n}\n.ql-snow .ql-tooltip[data-mode=\"link\"]::before {\n  content: \"请输入链接地址:\";\n}\n.ql-snow .ql-tooltip.ql-editing a.ql-action::after {\n  border-right: 0px;\n  content: \"保存\";\n  padding-right: 0px;\n}\n\n.ql-snow .ql-tooltip[data-mode=\"video\"]::before {\n  content: \"请输入视频地址:\";\n}\n\n.ql-snow .ql-picker.ql-size .ql-picker-label::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item::before {\n  content: \"14px\";\n}\n.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=\"small\"]::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=\"small\"]::before {\n  content: \"10px\";\n}\n.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=\"large\"]::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=\"large\"]::before {\n  content: \"18px\";\n}\n.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=\"huge\"]::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=\"huge\"]::before {\n  content: \"32px\";\n}\n\n.ql-snow .ql-picker.ql-header .ql-picker-label::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item::before {\n  content: \"文本\";\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"1\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"1\"]::before {\n  content: \"标题1\";\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"2\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"2\"]::before {\n  content: \"标题2\";\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"3\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"3\"]::before {\n  content: \"标题3\";\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"4\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"4\"]::before {\n  content: \"标题4\";\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"5\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"5\"]::before {\n  content: \"标题5\";\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"6\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"6\"]::before {\n  content: \"标题6\";\n}\n\n.ql-snow .ql-picker.ql-font .ql-picker-label::before,\n.ql-snow .ql-picker.ql-font .ql-picker-item::before {\n  content: \"标准字体\";\n}\n.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=\"serif\"]::before,\n.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=\"serif\"]::before {\n  content: \"衬线字体\";\n}\n.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=\"monospace\"]::before,\n.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=\"monospace\"]::before {\n  content: \"等宽字体\";\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/FileUpload/index.vue",
    "content": "<template>\n  <div class=\"upload-file\">\n    <el-upload\n      multiple\n      :action=\"uploadFileUrl\"\n      :before-upload=\"handleBeforeUpload\"\n      :file-list=\"fileList\"\n      :limit=\"limit\"\n      :on-error=\"handleUploadError\"\n      :on-exceed=\"handleExceed\"\n      :on-success=\"handleUploadSuccess\"\n      :show-file-list=\"false\"\n      :headers=\"headers\"\n      class=\"upload-file-uploader\"\n      ref=\"fileUpload\"\n    >\n      <!-- 上传按钮 -->\n      <el-button size=\"mini\" type=\"primary\">选取文件</el-button>\n      <!-- 上传提示 -->\n      <div class=\"el-upload__tip\" slot=\"tip\" v-if=\"showTip\">\n        请上传\n        <template v-if=\"fileSize\"> 大小不超过 <b style=\"color: #f56c6c\">{{ fileSize }}MB</b> </template>\n        <template v-if=\"fileType\"> 格式为 <b style=\"color: #f56c6c\">{{ fileType.join(\"/\") }}</b> </template>\n        的文件\n      </div>\n    </el-upload>\n\n    <!-- 文件列表 -->\n    <transition-group class=\"upload-file-list el-upload-list el-upload-list--text\" name=\"el-fade-in-linear\" tag=\"ul\">\n      <li :key=\"file.url\" class=\"el-upload-list__item ele-upload-list__item-content\" v-for=\"(file, index) in fileList\">\n        <el-link :href=\"`${baseUrl}${file.url}`\" :underline=\"false\" target=\"_blank\">\n          <span class=\"el-icon-document\"> {{ getFileName(file.name) }} </span>\n        </el-link>\n        <div class=\"ele-upload-list__item-content-action\">\n          <el-link :underline=\"false\" @click=\"handleDelete(index)\" type=\"danger\">删除</el-link>\n        </div>\n      </li>\n    </transition-group>\n  </div>\n</template>\n\n<script>\nimport { getToken } from \"@/utils/auth\";\n\nexport default {\n  name: \"FileUpload\",\n  props: {\n    // 值\n    value: [String, Object, Array],\n    // 数量限制\n    limit: {\n      type: Number,\n      default: 5,\n    },\n    // 大小限制(MB)\n    fileSize: {\n      type: Number,\n      default: 5,\n    },\n    // 文件类型, 例如['png', 'jpg', 'jpeg']\n    fileType: {\n      type: Array,\n      default: () => [\"doc\", \"xls\", \"ppt\", \"txt\", \"pdf\"],\n    },\n    // 是否显示提示\n    isShowTip: {\n      type: Boolean,\n      default: true\n    }\n  },\n  data() {\n    return {\n      number: 0,\n      uploadList: [],\n      baseUrl: process.env.VUE_APP_BASE_API,\n      uploadFileUrl: process.env.VUE_APP_BASE_API + \"/common/upload\", // 上传的图片服务器地址\n      headers: {\n        Authorization: \"Bearer \" + getToken(),\n      },\n      fileList: [],\n    };\n  },\n  watch: {\n    value: {\n      handler(val) {\n        if (val) {\n          let temp = 1;\n          // 首先将值转为数组\n          const list = Array.isArray(val) ? val : this.value.split(',');\n          // 然后将数组转为对象数组\n          this.fileList = list.map(item => {\n            if (typeof item === \"string\") {\n              item = { name: item, url: item };\n            }\n            item.uid = item.uid || new Date().getTime() + temp++;\n            return item;\n          });\n        } else {\n          this.fileList = [];\n          return [];\n        }\n      },\n      deep: true,\n      immediate: true\n    }\n  },\n  computed: {\n    // 是否显示提示\n    showTip() {\n      return this.isShowTip && (this.fileType || this.fileSize);\n    },\n  },\n  methods: {\n    // 上传前校检格式和大小\n    handleBeforeUpload(file) {\n      // 校检文件类型\n      if (this.fileType) {\n        let fileExtension = \"\";\n        if (file.name.lastIndexOf(\".\") > -1) {\n          fileExtension = file.name.slice(file.name.lastIndexOf(\".\") + 1);\n        }\n        const isTypeOk = this.fileType.some((type) => {\n          if (file.type.indexOf(type) > -1) return true;\n          if (fileExtension && fileExtension.indexOf(type) > -1) return true;\n          return false;\n        });\n        if (!isTypeOk) {\n          this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join(\"/\")}格式文件!`);\n          return false;\n        }\n      }\n      // 校检文件大小\n      if (this.fileSize) {\n        const isLt = file.size / 1024 / 1024 < this.fileSize;\n        if (!isLt) {\n          this.$modal.msgError(`上传文件大小不能超过 ${this.fileSize} MB!`);\n          return false;\n        }\n      }\n      this.$modal.loading(\"正在上传文件，请稍候...\");\n      this.number++;\n      return true;\n    },\n    // 文件个数超出\n    handleExceed() {\n      this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);\n    },\n    // 上传失败\n    handleUploadError(err) {\n      this.$modal.msgError(\"上传图片失败，请重试\");\n      this.$modal.closeLoading()\n    },\n    // 上传成功回调\n    handleUploadSuccess(res, file) {\n      if (res.code === 200) {\n        this.uploadList.push({ name: res.fileName, url: res.fileName });\n        this.uploadedSuccessfully();\n      } else {\n        this.number--;\n        this.$modal.closeLoading();\n        this.$modal.msgError(res.msg);\n        this.$refs.fileUpload.handleRemove(file);\n        this.uploadedSuccessfully();\n      }\n    },\n    // 删除文件\n    handleDelete(index) {\n      this.fileList.splice(index, 1);\n      this.$emit(\"input\", this.listToString(this.fileList));\n    },\n    // 上传结束处理\n    uploadedSuccessfully() {\n      if (this.number > 0 && this.uploadList.length === this.number) {\n        this.fileList = this.fileList.concat(this.uploadList);\n        this.uploadList = [];\n        this.number = 0;\n        this.$emit(\"input\", this.listToString(this.fileList));\n        this.$modal.closeLoading();\n      }\n    },\n    // 获取文件名称\n    getFileName(name) {\n      if (name.lastIndexOf(\"/\") > -1) {\n        return name.slice(name.lastIndexOf(\"/\") + 1);\n      } else {\n        return \"\";\n      }\n    },\n    // 对象转成指定字符串分隔\n    listToString(list, separator) {\n      let strs = \"\";\n      separator = separator || \",\";\n      for (let i in list) {\n        strs += list[i].url + separator;\n      }\n      return strs != '' ? strs.substr(0, strs.length - 1) : '';\n    }\n  }\n};\n</script>\n\n<style scoped lang=\"scss\">\n.upload-file-uploader {\n  margin-bottom: 5px;\n}\n.upload-file-list .el-upload-list__item {\n  border: 1px solid #e4e7ed;\n  line-height: 2;\n  margin-bottom: 10px;\n  position: relative;\n}\n.upload-file-list .ele-upload-list__item-content {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  color: inherit;\n}\n.ele-upload-list__item-content-action .el-link {\n  margin-right: 10px;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/Hamburger/index.vue",
    "content": "<template>\n  <div style=\"padding: 0 15px;\" @click=\"toggleClick\">\n    <svg\n      :class=\"{'is-active':isActive}\"\n      class=\"hamburger\"\n      viewBox=\"0 0 1024 1024\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"64\"\n      height=\"64\"\n    >\n      <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z\" />\n    </svg>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'Hamburger',\n  props: {\n    isActive: {\n      type: Boolean,\n      default: false\n    }\n  },\n  methods: {\n    toggleClick() {\n      this.$emit('toggleClick')\n    }\n  }\n}\n</script>\n\n<style scoped>\n.hamburger {\n  display: inline-block;\n  vertical-align: middle;\n  width: 20px;\n  height: 20px;\n}\n\n.hamburger.is-active {\n  transform: rotate(180deg);\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/HeaderSearch/index.vue",
    "content": "<template>\n  <div :class=\"{'show':show}\" class=\"header-search\">\n    <svg-icon class-name=\"search-icon\" icon-class=\"search\" @click.stop=\"click\" />\n    <el-select\n      ref=\"headerSearchSelect\"\n      v-model=\"search\"\n      :remote-method=\"querySearch\"\n      filterable\n      default-first-option\n      remote\n      placeholder=\"Search\"\n      class=\"header-search-select\"\n      @change=\"change\"\n    >\n      <el-option v-for=\"option in options\" :key=\"option.item.path\" :value=\"option.item\" :label=\"option.item.title.join(' > ')\" />\n    </el-select>\n  </div>\n</template>\n\n<script>\n// fuse is a lightweight fuzzy-search module\n// make search results more in line with expectations\nimport Fuse from 'fuse.js/dist/fuse.min.js'\nimport path from 'path'\n\nexport default {\n  name: 'HeaderSearch',\n  data() {\n    return {\n      search: '',\n      options: [],\n      searchPool: [],\n      show: false,\n      fuse: undefined\n    }\n  },\n  computed: {\n    routes() {\n      return this.$store.getters.permission_routes\n    }\n  },\n  watch: {\n    routes() {\n      this.searchPool = this.generateRoutes(this.routes)\n    },\n    searchPool(list) {\n      this.initFuse(list)\n    },\n    show(value) {\n      if (value) {\n        document.body.addEventListener('click', this.close)\n      } else {\n        document.body.removeEventListener('click', this.close)\n      }\n    }\n  },\n  mounted() {\n    this.searchPool = this.generateRoutes(this.routes)\n  },\n  methods: {\n    click() {\n      this.show = !this.show\n      if (this.show) {\n        this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()\n      }\n    },\n    close() {\n      this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()\n      this.options = []\n      this.show = false\n    },\n    change(val) {\n      const path = val.path;\n      if(this.ishttp(val.path)) {\n        // http(s):// 路径新窗口打开\n        const pindex = path.indexOf(\"http\");\n        window.open(path.substr(pindex, path.length), \"_blank\");\n      } else {\n        this.$router.push(val.path)\n      }\n      this.search = ''\n      this.options = []\n      this.$nextTick(() => {\n        this.show = false\n      })\n    },\n    initFuse(list) {\n      this.fuse = new Fuse(list, {\n        shouldSort: true,\n        threshold: 0.4,\n        location: 0,\n        distance: 100,\n        maxPatternLength: 32,\n        minMatchCharLength: 1,\n        keys: [{\n          name: 'title',\n          weight: 0.7\n        }, {\n          name: 'path',\n          weight: 0.3\n        }]\n      })\n    },\n    // Filter out the routes that can be displayed in the sidebar\n    // And generate the internationalized title\n    generateRoutes(routes, basePath = '/', prefixTitle = []) {\n      let res = []\n\n      for (const router of routes) {\n        // skip hidden router\n        if (router.hidden) { continue }\n\n        const data = {\n          path: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : router.path,\n          title: [...prefixTitle]\n        }\n\n        if (router.meta && router.meta.title) {\n          data.title = [...data.title, router.meta.title]\n\n          if (router.redirect !== 'noRedirect') {\n            // only push the routes with title\n            // special case: need to exclude parent router without redirect\n            res.push(data)\n          }\n        }\n\n        // recursive child routes\n        if (router.children) {\n          const tempRoutes = this.generateRoutes(router.children, data.path, data.title)\n          if (tempRoutes.length >= 1) {\n            res = [...res, ...tempRoutes]\n          }\n        }\n      }\n      return res\n    },\n    querySearch(query) {\n      if (query !== '') {\n        this.options = this.fuse.search(query)\n      } else {\n        this.options = []\n      }\n    },\n    ishttp(url) {\n      return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.header-search {\n  font-size: 0 !important;\n\n  .search-icon {\n    cursor: pointer;\n    font-size: 18px;\n    vertical-align: middle;\n  }\n\n  .header-search-select {\n    font-size: 18px;\n    transition: width 0.2s;\n    width: 0;\n    overflow: hidden;\n    background: transparent;\n    border-radius: 0;\n    display: inline-block;\n    vertical-align: middle;\n\n    ::v-deep .el-input__inner {\n      border-radius: 0;\n      border: 0;\n      padding-left: 0;\n      padding-right: 0;\n      box-shadow: none !important;\n      border-bottom: 1px solid #d9d9d9;\n      vertical-align: middle;\n    }\n  }\n\n  &.show {\n    .header-search-select {\n      width: 210px;\n      margin-left: 10px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/IconSelect/index.vue",
    "content": "<!-- @author zhengjie -->\n<template>\n  <div class=\"icon-body\">\n    <el-input v-model=\"name\" style=\"position: relative;\" clearable placeholder=\"请输入图标名称\" @clear=\"filterIcons\" @input.native=\"filterIcons\">\n      <i slot=\"suffix\" class=\"el-icon-search el-input__icon\" />\n    </el-input>\n    <div class=\"icon-list\">\n      <div v-for=\"(item, index) in iconList\" :key=\"index\" @click=\"selectedIcon(item)\">\n        <svg-icon :icon-class=\"item\" style=\"height: 30px;width: 16px;\" />\n        <span>{{ item }}</span>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport icons from './requireIcons'\nexport default {\n  name: 'IconSelect',\n  data() {\n    return {\n      name: '',\n      iconList: icons\n    }\n  },\n  methods: {\n    filterIcons() {\n      this.iconList = icons\n      if (this.name) {\n        this.iconList = this.iconList.filter(item => item.includes(this.name))\n      }\n    },\n    selectedIcon(name) {\n      this.$emit('selected', name)\n      document.body.click()\n    },\n    reset() {\n      this.name = ''\n      this.iconList = icons\n    }\n  }\n}\n</script>\n\n<style rel=\"stylesheet/scss\" lang=\"scss\" scoped>\n  .icon-body {\n    width: 100%;\n    padding: 10px;\n    .icon-list {\n      height: 200px;\n      overflow-y: scroll;\n      div {\n        height: 30px;\n        line-height: 30px;\n        margin-bottom: -5px;\n        cursor: pointer;\n        width: 33%;\n        float: left;\n      }\n      span {\n        display: inline-block;\n        vertical-align: -0.15em;\n        fill: currentColor;\n        overflow: hidden;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/IconSelect/requireIcons.js",
    "content": "\nconst req = require.context('../../assets/icons/svg', false, /\\.svg$/)\nconst requireAll = requireContext => requireContext.keys()\n\nconst re = /\\.\\/(.*)\\.svg/\n\nconst icons = requireAll(req).map(i => {\n  return i.match(re)[1]\n})\n\nexport default icons\n"
  },
  {
    "path": "vue_campus_admin/src/components/ImagePreview/index.vue",
    "content": "<template>\n  <el-image\n    :src=\"`${realSrc}`\"\n    fit=\"cover\"\n    :style=\"`width:${realWidth};height:${realHeight};`\"\n    :preview-src-list=\"realSrcList\"\n  >\n    <div slot=\"error\" class=\"image-slot\">\n      <i class=\"el-icon-picture-outline\"></i>\n    </div>\n  </el-image>\n</template>\n\n<script>\nimport { isExternal } from \"@/utils/validate\";\n\nexport default {\n  name: \"ImagePreview\",\n  props: {\n    src: {\n      type: String,\n      default: \"\"\n    },\n    width: {\n      type: [Number, String],\n      default: \"\"\n    },\n    height: {\n      type: [Number, String],\n      default: \"\"\n    }\n  },\n  computed: {\n    realSrc() {\n      if (!this.src) {\n        return;\n      }\n      let real_src = this.src.split(\",\")[0];\n      if (isExternal(real_src)) {\n        return real_src;\n      }\n      return process.env.VUE_APP_BASE_API + real_src;\n    },\n    realSrcList() {\n      if (!this.src) {\n        return;\n      }\n      let real_src_list = this.src.split(\",\");\n      let srcList = [];\n      real_src_list.forEach(item => {\n        if (isExternal(item)) {\n          return srcList.push(item);\n        }\n        return srcList.push(process.env.VUE_APP_BASE_API + item);\n      });\n      return srcList;\n    },\n    realWidth() {\n      return typeof this.width == \"string\" ? this.width : `${this.width}px`;\n    },\n    realHeight() {\n      return typeof this.height == \"string\" ? this.height : `${this.height}px`;\n    }\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.el-image {\n  border-radius: 5px;\n  background-color: #ebeef5;\n  box-shadow: 0 0 5px 1px #ccc;\n  ::v-deep .el-image__inner {\n    transition: all 0.3s;\n    cursor: pointer;\n    &:hover {\n      transform: scale(1.2);\n    }\n  }\n  ::v-deep .image-slot {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    width: 100%;\n    height: 100%;\n    color: #909399;\n    font-size: 30px;\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/ImageUpload/index.vue",
    "content": "<template>\n  <div class=\"component-upload-image\">\n    <el-upload\n      multiple\n      :action=\"uploadImgUrl\"\n      list-type=\"picture-card\"\n      :on-success=\"handleUploadSuccess\"\n      :before-upload=\"handleBeforeUpload\"\n      :limit=\"limit\"\n      :on-error=\"handleUploadError\"\n      :on-exceed=\"handleExceed\"\n      ref=\"imageUpload\"\n      :on-remove=\"handleDelete\"\n      :show-file-list=\"true\"\n      :headers=\"headers\"\n      :file-list=\"fileList\"\n      :on-preview=\"handlePictureCardPreview\"\n      :class=\"{hide: this.fileList.length >= this.limit}\"\n    >\n      <i class=\"el-icon-plus\"></i>\n    </el-upload>\n    \n    <!-- 上传提示 -->\n    <div class=\"el-upload__tip\" slot=\"tip\" v-if=\"showTip\">\n      请上传\n      <template v-if=\"fileSize\"> 大小不超过 <b style=\"color: #f56c6c\">{{ fileSize }}MB</b> </template>\n      <template v-if=\"fileType\"> 格式为 <b style=\"color: #f56c6c\">{{ fileType.join(\"/\") }}</b> </template>\n      的文件\n    </div>\n\n    <el-dialog\n      :visible.sync=\"dialogVisible\"\n      title=\"预览\"\n      width=\"800\"\n      append-to-body\n    >\n      <img\n        :src=\"dialogImageUrl\"\n        style=\"display: block; max-width: 100%; margin: 0 auto\"\n      />\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { getToken } from \"@/utils/auth\";\n\nexport default {\n  props: {\n    value: [String, Object, Array],\n    // 图片数量限制\n    limit: {\n      type: Number,\n      default: 5,\n    },\n    // 大小限制(MB)\n    fileSize: {\n       type: Number,\n      default: 5,\n    },\n    // 文件类型, 例如['png', 'jpg', 'jpeg']\n    fileType: {\n      type: Array,\n      default: () => [\"png\", \"jpg\", \"jpeg\"],\n    },\n    // 是否显示提示\n    isShowTip: {\n      type: Boolean,\n      default: true\n    }\n  },\n  data() {\n    return {\n      number: 0,\n      uploadList: [],\n      dialogImageUrl: \"\",\n      dialogVisible: false,\n      hideUpload: false,\n      baseUrl: process.env.VUE_APP_BASE_API,\n      uploadImgUrl: process.env.VUE_APP_BASE_API + \"/common/upload\", // 上传的图片服务器地址\n      headers: {\n        Authorization: \"Bearer \" + getToken(),\n      },\n      fileList: []\n    };\n  },\n  watch: {\n    value: {\n      handler(val) {\n        if (val) {\n          // 首先将值转为数组\n          const list = Array.isArray(val) ? val : this.value.split(',');\n          // 然后将数组转为对象数组\n          this.fileList = list.map(item => {\n            if (typeof item === \"string\") {\n              if (item.indexOf(this.baseUrl) === -1) {\n                  item = { name: this.baseUrl + item, url: this.baseUrl + item };\n              } else {\n                  item = { name: item, url: item };\n              }\n            }\n            return item;\n          });\n        } else {\n          this.fileList = [];\n          return [];\n        }\n      },\n      deep: true,\n      immediate: true\n    }\n  },\n  computed: {\n    // 是否显示提示\n    showTip() {\n      return this.isShowTip && (this.fileType || this.fileSize);\n    },\n  },\n  methods: {\n    // 上传前loading加载\n    handleBeforeUpload(file) {\n      let isImg = false;\n      if (this.fileType.length) {\n        let fileExtension = \"\";\n        if (file.name.lastIndexOf(\".\") > -1) {\n          fileExtension = file.name.slice(file.name.lastIndexOf(\".\") + 1);\n        }\n        isImg = this.fileType.some(type => {\n          if (file.type.indexOf(type) > -1) return true;\n          if (fileExtension && fileExtension.indexOf(type) > -1) return true;\n          return false;\n        });\n      } else {\n        isImg = file.type.indexOf(\"image\") > -1;\n      }\n\n      if (!isImg) {\n        this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join(\"/\")}图片格式文件!`);\n        return false;\n      }\n      if (this.fileSize) {\n        const isLt = file.size / 1024 / 1024 < this.fileSize;\n        if (!isLt) {\n          this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`);\n          return false;\n        }\n      }\n      this.$modal.loading(\"正在上传图片，请稍候...\");\n      this.number++;\n    },\n    // 文件个数超出\n    handleExceed() {\n      this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);\n    },\n    // 上传成功回调\n    handleUploadSuccess(res, file) {\n      if (res.code === 200) {\n        this.uploadList.push({ name: res.fileName, url: res.fileName });\n        this.uploadedSuccessfully();\n      } else {\n        this.number--;\n        this.$modal.closeLoading();\n        this.$modal.msgError(res.msg);\n        this.$refs.imageUpload.handleRemove(file);\n        this.uploadedSuccessfully();\n      }\n    },\n    // 删除图片\n    handleDelete(file) {\n      const findex = this.fileList.map(f => f.name).indexOf(file.name);\n      if(findex > -1) {\n        this.fileList.splice(findex, 1);\n        this.$emit(\"input\", this.listToString(this.fileList));\n      }\n    },\n    // 上传失败\n    handleUploadError() {\n      this.$modal.msgError(\"上传图片失败，请重试\");\n      this.$modal.closeLoading();\n    },\n    // 上传结束处理\n    uploadedSuccessfully() {\n      if (this.number > 0 && this.uploadList.length === this.number) {\n        this.fileList = this.fileList.concat(this.uploadList);\n        this.uploadList = [];\n        this.number = 0;\n        this.$emit(\"input\", this.listToString(this.fileList));\n        this.$modal.closeLoading();\n      }\n    },\n    // 预览\n    handlePictureCardPreview(file) {\n      this.dialogImageUrl = file.url;\n      this.dialogVisible = true;\n    },\n    // 对象转成指定字符串分隔\n    listToString(list, separator) {\n      let strs = \"\";\n      separator = separator || \",\";\n      for (let i in list) {\n        if (list[i].url) {\n          strs += list[i].url.replace(this.baseUrl, \"\") + separator;\n        }\n      }\n      return strs != '' ? strs.substr(0, strs.length - 1) : '';\n    }\n  }\n};\n</script>\n<style scoped lang=\"scss\">\n// .el-upload--picture-card 控制加号部分\n::v-deep.hide .el-upload--picture-card {\n    display: none;\n}\n// 去掉动画效果\n::v-deep .el-list-enter-active,\n::v-deep .el-list-leave-active {\n    transition: all 0s;\n}\n\n::v-deep .el-list-enter, .el-list-leave-active {\n    opacity: 0;\n    transform: translateY(0);\n}\n</style>\n\n"
  },
  {
    "path": "vue_campus_admin/src/components/Pagination/index.vue",
    "content": "<template>\n  <div :class=\"{'hidden':hidden}\" class=\"pagination-container\">\n    <el-pagination\n      :background=\"background\"\n      :current-page.sync=\"currentPage\"\n      :page-size.sync=\"pageSize\"\n      :layout=\"layout\"\n      :page-sizes=\"pageSizes\"\n      :pager-count=\"pagerCount\"\n      :total=\"parseInt(total)\"\n      v-bind=\"$attrs\"\n      @size-change=\"handleSizeChange\"\n      @current-change=\"handleCurrentChange\"\n    />\n  </div>\n</template>\n\n<script>\nimport { scrollTo } from '@/utils/scroll-to'\n\nexport default {\n  name: 'Pagination',\n  props: {\n    total: {\n      required: true,\n      // type: Number\n    },\n    page: {\n      type: Number,\n      default: 1\n    },\n    limit: {\n      type: Number,\n      default: 20\n    },\n    pageSizes: {\n      type: Array,\n      default() {\n        return [10, 20, 30, 50]\n      }\n    },\n    // 移动端页码按钮的数量端默认值5\n    pagerCount: {\n      type: Number,\n      default: document.body.clientWidth < 992 ? 5 : 7\n    },\n    layout: {\n      type: String,\n      default: 'total, sizes, prev, pager, next, jumper'\n    },\n    background: {\n      type: Boolean,\n      default: true\n    },\n    autoScroll: {\n      type: Boolean,\n      default: true\n    },\n    hidden: {\n      type: Boolean,\n      default: false\n    }\n  },\n  data() {\n    return {\n    };\n  },\n  created() {\n  },\n  computed: {\n    currentPage: {\n      get() {\n        return this.page\n      },\n      set(val) {\n        this.$emit('update:page', val)\n      }\n    },\n    pageSize: {\n      get() {\n        return this.limit\n      },\n      set(val) {\n        this.$emit('update:limit', val)\n      }\n    }\n  },\n  methods: {\n    handleSizeChange(val) {\n      if (this.currentPage * val > parseInt(this.total)) {\n        this.currentPage = 1\n      }\n      this.$emit('pagination', { page: this.currentPage, limit: val })\n      if (this.autoScroll) {\n        scrollTo(0, 800)\n      }\n    },\n    handleCurrentChange(val) {\n      this.$emit('pagination', { page: val, limit: this.pageSize })\n      if (this.autoScroll) {\n        scrollTo(0, 800)\n      }\n    }\n  }\n}\n</script>\n\n<style scoped>\n.pagination-container {\n  background: #fff;\n  padding: 32px 16px;\n}\n.pagination-container.hidden {\n  display: none;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/PanThumb/index.vue",
    "content": "<template>\n  <div :style=\"{zIndex:zIndex,height:height,width:width}\" class=\"pan-item\">\n    <div class=\"pan-info\">\n      <div class=\"pan-info-roles-container\">\n        <slot />\n      </div>\n    </div>\n    <!-- eslint-disable-next-line -->\n    <div :style=\"{backgroundImage: `url(${image})`}\" class=\"pan-thumb\"></div>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'PanThumb',\n  props: {\n    image: {\n      type: String,\n      required: true\n    },\n    zIndex: {\n      type: Number,\n      default: 1\n    },\n    width: {\n      type: String,\n      default: '150px'\n    },\n    height: {\n      type: String,\n      default: '150px'\n    }\n  }\n}\n</script>\n\n<style scoped>\n.pan-item {\n  width: 200px;\n  height: 200px;\n  border-radius: 50%;\n  display: inline-block;\n  position: relative;\n  cursor: default;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n.pan-info-roles-container {\n  padding: 20px;\n  text-align: center;\n}\n\n.pan-thumb {\n  width: 100%;\n  height: 100%;\n  background-position: center center;\n  background-size: cover;\n  border-radius: 50%;\n  overflow: hidden;\n  position: absolute;\n  transform-origin: 95% 40%;\n  transition: all 0.3s ease-in-out;\n}\n\n/* .pan-thumb:after {\n  content: '';\n  width: 8px;\n  height: 8px;\n  position: absolute;\n  border-radius: 50%;\n  top: 40%;\n  left: 95%;\n  margin: -4px 0 0 -4px;\n  background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);\n  box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);\n} */\n\n.pan-info {\n  position: absolute;\n  width: inherit;\n  height: inherit;\n  border-radius: 50%;\n  overflow: hidden;\n  box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);\n}\n\n.pan-info h3 {\n  color: #fff;\n  text-transform: uppercase;\n  position: relative;\n  letter-spacing: 2px;\n  font-size: 18px;\n  margin: 0 60px;\n  padding: 22px 0 0 0;\n  height: 85px;\n  font-family: 'Open Sans', Arial, sans-serif;\n  text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);\n}\n\n.pan-info p {\n  color: #fff;\n  padding: 10px 5px;\n  font-style: italic;\n  margin: 0 30px;\n  font-size: 12px;\n  border-top: 1px solid rgba(255, 255, 255, 0.5);\n}\n\n.pan-info p a {\n  display: block;\n  color: #333;\n  width: 80px;\n  height: 80px;\n  background: rgba(255, 255, 255, 0.3);\n  border-radius: 50%;\n  color: #fff;\n  font-style: normal;\n  font-weight: 700;\n  text-transform: uppercase;\n  font-size: 9px;\n  letter-spacing: 1px;\n  padding-top: 24px;\n  margin: 7px auto 0;\n  font-family: 'Open Sans', Arial, sans-serif;\n  opacity: 0;\n  transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;\n  transform: translateX(60px) rotate(90deg);\n}\n\n.pan-info p a:hover {\n  background: rgba(255, 255, 255, 0.5);\n}\n\n.pan-item:hover .pan-thumb {\n  transform: rotate(-110deg);\n}\n\n.pan-item:hover .pan-info p a {\n  opacity: 1;\n  transform: translateX(0px) rotate(0deg);\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/ParentView/index.vue",
    "content": "<template >\n  <router-view />\n</template>\n"
  },
  {
    "path": "vue_campus_admin/src/components/RightPanel/index.vue",
    "content": "<template>\n  <div ref=\"rightPanel\" class=\"rightPanel-container\">\n    <div class=\"rightPanel-background\" />\n    <div class=\"rightPanel\">\n      <div class=\"rightPanel-items\">\n        <slot />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'RightPanel',\n  props: {\n    clickNotClose: {\n      default: false,\n      type: Boolean\n    }\n  },\n  computed: {\n    show: {\n      get() {\n        return this.$store.state.settings.showSettings\n      },\n      set(val) {\n        this.$store.dispatch('settings/changeSetting', {\n          key: 'showSettings',\n          value: val\n        })\n      }\n    }\n  },\n  watch: {\n    show(value) {\n      if (value && !this.clickNotClose) {\n        this.addEventClick()\n      }\n    }\n  },\n  mounted() {\n    this.insertToBody()\n    this.addEventClick()\n  },\n  beforeDestroy() {\n    const elx = this.$refs.rightPanel\n    elx.remove()\n  },\n  methods: {\n    addEventClick() {\n      window.addEventListener('click', this.closeSidebar)\n    },\n    closeSidebar(evt) {\n      const parent = evt.target.closest('.el-drawer__body')\n      if (!parent) {\n        this.show = false\n        window.removeEventListener('click', this.closeSidebar)\n      }\n    },\n    insertToBody() {\n      const elx = this.$refs.rightPanel\n      const body = document.querySelector('body')\n      body.insertBefore(elx, body.firstChild)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.rightPanel-background {\n  position: fixed;\n  top: 0;\n  left: 0;\n  opacity: 0;\n  transition: opacity .3s cubic-bezier(.7, .3, .1, 1);\n  background: rgba(0, 0, 0, .2);\n  z-index: -1;\n}\n\n.rightPanel {\n  width: 100%;\n  max-width: 260px;\n  height: 100vh;\n  position: fixed;\n  top: 0;\n  right: 0;\n  box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);\n  transition: all .25s cubic-bezier(.7, .3, .1, 1);\n  transform: translate(100%);\n  background: #fff;\n  z-index: 40000;\n}\n\n.handle-button {\n  width: 48px;\n  height: 48px;\n  position: absolute;\n  left: -48px;\n  text-align: center;\n  font-size: 24px;\n  border-radius: 6px 0 0 6px !important;\n  z-index: 0;\n  pointer-events: auto;\n  cursor: pointer;\n  color: #fff;\n  line-height: 48px;\n  i {\n    font-size: 24px;\n    line-height: 48px;\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/RightToolbar/index.vue",
    "content": "<template>\n  <div class=\"top-right-btn\" :style=\"style\">\n    <el-row>\n      <el-tooltip class=\"item\" effect=\"dark\" :content=\"showSearch ? '隐藏搜索' : '显示搜索'\" placement=\"top\" v-if=\"search\">\n        <el-button size=\"mini\" circle icon=\"el-icon-search\" @click=\"toggleSearch()\" />\n      </el-tooltip>\n      <el-tooltip class=\"item\" effect=\"dark\" content=\"刷新\" placement=\"top\">\n        <el-button size=\"mini\" circle icon=\"el-icon-refresh\" @click=\"refresh()\" />\n      </el-tooltip>\n      <el-tooltip class=\"item\" effect=\"dark\" content=\"显隐列\" placement=\"top\" v-if=\"columns\">\n        <el-button size=\"mini\" circle icon=\"el-icon-menu\" @click=\"showColumn()\" />\n      </el-tooltip>\n    </el-row>\n    <el-dialog :title=\"title\" :visible.sync=\"open\" append-to-body>\n      <el-transfer\n        :titles=\"['显示', '隐藏']\"\n        v-model=\"value\"\n        :data=\"columns\"\n        @change=\"dataChange\"\n      ></el-transfer>\n    </el-dialog>\n  </div>\n</template>\n<script>\nexport default {\n  name: \"RightToolbar\",\n  data() {\n    return {\n      // 显隐数据\n      value: [],\n      // 弹出层标题\n      title: \"显示/隐藏\",\n      // 是否显示弹出层\n      open: false,\n    };\n  },\n  props: {\n    showSearch: {\n      type: Boolean,\n      default: true,\n    },\n    columns: {\n      type: Array,\n    },\n    search: {\n      type: Boolean,\n      default: true,\n    },\n    gutter: {\n      type: Number,\n      default: 10,\n    },\n  },\n  computed: {\n    style() {\n      const ret = {};\n      if (this.gutter) {\n        ret.marginRight = `${this.gutter / 2}px`;\n      }\n      return ret;\n    }\n  },\n  created() {\n    // 显隐列初始默认隐藏列\n    for (let item in this.columns) {\n      if (this.columns[item].visible === false) {\n        this.value.push(parseInt(item));\n      }\n    }\n  },\n  methods: {\n    // 搜索\n    toggleSearch() {\n      this.$emit(\"update:showSearch\", !this.showSearch);\n    },\n    // 刷新\n    refresh() {\n      this.$emit(\"queryTable\");\n    },\n    // 右侧列表元素变化\n    dataChange(data) {\n      for (let item in this.columns) {\n        const key = this.columns[item].key;\n        this.columns[item].visible = !data.includes(key);\n      }\n    },\n    // 打开显隐列dialog\n    showColumn() {\n      this.open = true;\n    },\n  },\n};\n</script>\n<style lang=\"scss\" scoped>\n::v-deep .el-transfer__button {\n  border-radius: 50%;\n  padding: 12px;\n  display: block;\n  margin-left: 0px;\n}\n::v-deep .el-transfer__button:first-child {\n  margin-bottom: 10px;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/RuoYi/Doc/index.vue",
    "content": "<template>\n  <div>\n    <svg-icon icon-class=\"question\" @click=\"goto\" />\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'RuoYiDoc',\n  data() {\n    return {\n      url: 'https://github.com/oddfar/campus-imaotai/wiki/'\n    }\n  },\n  methods: {\n    goto() {\n      window.open(this.url)\n    }\n  }\n}\n</script>"
  },
  {
    "path": "vue_campus_admin/src/components/RuoYi/Git/index.vue",
    "content": "<template>\n  <div>\n    <svg-icon icon-class=\"github\" @click=\"goto\" />\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'RuoYiGit',\n  data() {\n    return {\n      url: 'https://github.com/oddfar/campus-imaotai'\n    }\n  },\n  methods: {\n    goto() {\n      window.open(this.url)\n    }\n  }\n}\n</script>"
  },
  {
    "path": "vue_campus_admin/src/components/Screenfull/index.vue",
    "content": "<template>\n  <div>\n    <svg-icon :icon-class=\"isFullscreen?'exit-fullscreen':'fullscreen'\" @click=\"click\" />\n  </div>\n</template>\n\n<script>\nimport screenfull from 'screenfull'\n\nexport default {\n  name: 'Screenfull',\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.$message({ message: '你的浏览器不支持全屏', type: 'warning' })\n        return false\n      }\n      screenfull.toggle()\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\n<style scoped>\n.screenfull-svg {\n  display: inline-block;\n  cursor: pointer;\n  fill: #5a5e66;;\n  width: 20px;\n  height: 20px;\n  vertical-align: 10px;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/SizeSelect/index.vue",
    "content": "<template>\n  <el-dropdown trigger=\"click\" @command=\"handleSetSize\">\n    <div>\n      <svg-icon class-name=\"size-icon\" icon-class=\"size\" />\n    </div>\n    <el-dropdown-menu slot=\"dropdown\">\n      <el-dropdown-item v-for=\"item of sizeOptions\" :key=\"item.value\" :disabled=\"size===item.value\" :command=\"item.value\">\n        {{ item.label }}\n      </el-dropdown-item>\n    </el-dropdown-menu>\n  </el-dropdown>\n</template>\n\n<script>\nexport default {\n  data() {\n    return {\n      sizeOptions: [\n        { label: 'Default', value: 'default' },\n        { label: 'Medium', value: 'medium' },\n        { label: 'Small', value: 'small' },\n        { label: 'Mini', value: 'mini' }\n      ]\n    }\n  },\n  computed: {\n    size() {\n      return this.$store.getters.size\n    }\n  },\n  methods: {\n    handleSetSize(size) {\n      this.$ELEMENT.size = size\n      this.$store.dispatch('app/setSize', size)\n      this.refreshView()\n      this.$message({\n        message: 'Switch Size Success',\n        type: 'success'\n      })\n    },\n    refreshView() {\n      // In order to make the cached page re-rendered\n      this.$store.dispatch('tagsView/delAllCachedViews', this.$route)\n\n      const { fullPath } = this.$route\n\n      this.$nextTick(() => {\n        this.$router.replace({\n          path: '/redirect' + fullPath\n        })\n      })\n    }\n  }\n\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/components/SvgIcon/index.vue",
    "content": "<template>\n  <div v-if=\"isExternal\" :style=\"styleExternalIcon\" class=\"svg-external-icon svg-icon\" v-on=\"$listeners\" />\n  <svg v-else :class=\"svgClass\" aria-hidden=\"true\" v-on=\"$listeners\">\n    <use :xlink:href=\"iconName\" />\n  </svg>\n</template>\n\n<script>\nimport { isExternal } from '@/utils/validate'\n\nexport default {\n  name: 'SvgIcon',\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 `#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 scoped>\n.svg-icon {\n  width: 1em;\n  height: 1em;\n  vertical-align: -0.15em;\n  fill: currentColor;\n  overflow: hidden;\n}\n\n.svg-external-icon {\n  background-color: currentColor;\n  mask-size: cover!important;\n  display: inline-block;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/ThemePicker/index.vue",
    "content": "<template>\n  <el-color-picker\n    v-model=\"theme\"\n    :predefine=\"['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]\"\n    class=\"theme-picker\"\n    popper-class=\"theme-picker-dropdown\"\n  />\n</template>\n\n<script>\nconst version = require('element-ui/package.json').version // element-ui version from node_modules\nconst ORIGINAL_THEME = '#409EFF' // default color\n\nexport default {\n  data() {\n    return {\n      chalk: '', // content of theme-chalk css\n      theme: ''\n    }\n  },\n  computed: {\n    defaultTheme() {\n      return this.$store.state.settings.theme\n    }\n  },\n  watch: {\n    defaultTheme: {\n      handler: function(val, oldVal) {\n        this.theme = val\n      },\n      immediate: true\n    },\n    async theme(val) {\n      await this.setTheme(val)\n    }\n  },\n  created() {\n    if(this.defaultTheme !== ORIGINAL_THEME) {\n      this.setTheme(this.defaultTheme)\n    }\n  },\n\n  methods: {\n    async setTheme(val) {\n      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME\n      if (typeof val !== 'string') return\n      const themeCluster = this.getThemeCluster(val.replace('#', ''))\n      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))\n\n      const getHandler = (variable, id) => {\n        return () => {\n          const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))\n          const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)\n\n          let styleTag = document.getElementById(id)\n          if (!styleTag) {\n            styleTag = document.createElement('style')\n            styleTag.setAttribute('id', id)\n            document.head.appendChild(styleTag)\n          }\n          styleTag.innerText = newStyle\n        }\n      }\n\n      if (!this.chalk) {\n        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`\n        await this.getCSSString(url, 'chalk')\n      }\n\n      const chalkHandler = getHandler('chalk', 'chalk-style')\n\n      chalkHandler()\n\n      const styles = [].slice.call(document.querySelectorAll('style'))\n        .filter(style => {\n          const text = style.innerText\n          return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)\n        })\n      styles.forEach(style => {\n        const { innerText } = style\n        if (typeof innerText !== 'string') return\n        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)\n      })\n\n      this.$emit('change', val)\n    },\n\n    updateStyle(style, oldCluster, newCluster) {\n      let newStyle = style\n      oldCluster.forEach((color, index) => {\n        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])\n      })\n      return newStyle\n    },\n\n    getCSSString(url, variable) {\n      return new Promise(resolve => {\n        const xhr = new XMLHttpRequest()\n        xhr.onreadystatechange = () => {\n          if (xhr.readyState === 4 && xhr.status === 200) {\n            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')\n            resolve()\n          }\n        }\n        xhr.open('GET', url)\n        xhr.send()\n      })\n    },\n\n    getThemeCluster(theme) {\n      const tintColor = (color, tint) => {\n        let red = parseInt(color.slice(0, 2), 16)\n        let green = parseInt(color.slice(2, 4), 16)\n        let blue = parseInt(color.slice(4, 6), 16)\n\n        if (tint === 0) { // when primary color is in its rgb space\n          return [red, green, blue].join(',')\n        } else {\n          red += Math.round(tint * (255 - red))\n          green += Math.round(tint * (255 - green))\n          blue += Math.round(tint * (255 - blue))\n\n          red = red.toString(16)\n          green = green.toString(16)\n          blue = blue.toString(16)\n\n          return `#${red}${green}${blue}`\n        }\n      }\n\n      const shadeColor = (color, shade) => {\n        let red = parseInt(color.slice(0, 2), 16)\n        let green = parseInt(color.slice(2, 4), 16)\n        let blue = parseInt(color.slice(4, 6), 16)\n\n        red = Math.round((1 - shade) * red)\n        green = Math.round((1 - shade) * green)\n        blue = Math.round((1 - shade) * blue)\n\n        red = red.toString(16)\n        green = green.toString(16)\n        blue = blue.toString(16)\n\n        return `#${red}${green}${blue}`\n      }\n\n      const clusters = [theme]\n      for (let i = 0; i <= 9; i++) {\n        clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))\n      }\n      clusters.push(shadeColor(theme, 0.1))\n      return clusters\n    }\n  }\n}\n</script>\n\n<style>\n.theme-message,\n.theme-picker-dropdown {\n  z-index: 99999 !important;\n}\n\n.theme-picker .el-color-picker__trigger {\n  height: 26px !important;\n  width: 26px !important;\n  padding: 2px;\n}\n\n.theme-picker-dropdown .el-color-dropdown__link-btn {\n  display: none;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/TopNav/index.vue",
    "content": "<template>\n  <el-menu\n    :default-active=\"activeMenu\"\n    mode=\"horizontal\"\n    @select=\"handleSelect\"\n  >\n    <template v-for=\"(item, index) in topMenus\">\n      <el-menu-item :style=\"{'--theme': theme}\" :index=\"item.path\" :key=\"index\" v-if=\"index < visibleNumber\"\n        ><svg-icon :icon-class=\"item.meta.icon\" />\n        {{ item.meta.title }}</el-menu-item\n      >\n    </template>\n\n    <!-- 顶部菜单超出数量折叠 -->\n    <el-submenu :style=\"{'--theme': theme}\" index=\"more\" v-if=\"topMenus.length > visibleNumber\">\n      <template slot=\"title\">更多菜单</template>\n      <template v-for=\"(item, index) in topMenus\">\n        <el-menu-item\n          :index=\"item.path\"\n          :key=\"index\"\n          v-if=\"index >= visibleNumber\"\n          ><svg-icon :icon-class=\"item.meta.icon\" />\n          {{ item.meta.title }}</el-menu-item\n        >\n      </template>\n    </el-submenu>\n  </el-menu>\n</template>\n\n<script>\nimport { constantRoutes } from \"@/router\";\n\n// 隐藏侧边栏路由\nconst hideList = ['/index', '/user/profile'];\n\nexport default {\n  data() {\n    return {\n      // 顶部栏初始数\n      visibleNumber: 5,\n      // 当前激活菜单的 index\n      currentIndex: undefined\n    };\n  },\n  computed: {\n    theme() {\n      return this.$store.state.settings.theme;\n    },\n    // 顶部显示菜单\n    topMenus() {\n      let topMenus = [];\n      this.routers.map((menu) => {\n        if (menu.hidden !== true) {\n          // 兼容顶部栏一级菜单内部跳转\n          if (menu.path === \"/\") {\n              topMenus.push(menu.children[0]);\n          } else {\n              topMenus.push(menu);\n          }\n        }\n      });\n      return topMenus;\n    },\n    // 所有的路由信息\n    routers() {\n      return this.$store.state.permission.topbarRouters;\n    },\n    // 设置子路由\n    childrenMenus() {\n      var childrenMenus = [];\n      this.routers.map((router) => {\n        for (var item in router.children) {\n          if (router.children[item].parentPath === undefined) {\n            if(router.path === \"/\") {\n              router.children[item].path = \"/\" + router.children[item].path;\n            } else {\n              if(!this.ishttp(router.children[item].path)) {\n                router.children[item].path = router.path + \"/\" + router.children[item].path;\n              }\n            }\n            router.children[item].parentPath = router.path;\n          }\n          childrenMenus.push(router.children[item]);\n        }\n      });\n      return constantRoutes.concat(childrenMenus);\n    },\n    // 默认激活的菜单\n    activeMenu() {\n      const path = this.$route.path;\n      let activePath = path;\n      if (path !== undefined && path.lastIndexOf(\"/\") > 0 && hideList.indexOf(path) === -1) {\n        const tmpPath = path.substring(1, path.length);\n        activePath = \"/\" + tmpPath.substring(0, tmpPath.indexOf(\"/\"));\n        this.$store.dispatch('app/toggleSideBarHide', false);\n      } else if(!this.$route.children) {\n        activePath = path;\n        this.$store.dispatch('app/toggleSideBarHide', true);\n      }\n      this.activeRoutes(activePath);\n      return activePath;\n    },\n  },\n  beforeMount() {\n    window.addEventListener('resize', this.setVisibleNumber)\n  },\n  beforeDestroy() {\n    window.removeEventListener('resize', this.setVisibleNumber)\n  },\n  mounted() {\n    this.setVisibleNumber();\n  },\n  methods: {\n    // 根据宽度计算设置显示栏数\n    setVisibleNumber() {\n      const width = document.body.getBoundingClientRect().width / 3;\n      this.visibleNumber = parseInt(width / 85);\n    },\n    // 菜单选择事件\n    handleSelect(key, keyPath) {\n      this.currentIndex = key;\n      const route = this.routers.find(item => item.path === key);\n      if (this.ishttp(key)) {\n        // http(s):// 路径新窗口打开\n        window.open(key, \"_blank\");\n      } else if (!route || !route.children) {\n        // 没有子路由路径内部打开\n        this.$router.push({ path: key });\n        this.$store.dispatch('app/toggleSideBarHide', true);\n      } else {\n        // 显示左侧联动菜单\n        this.activeRoutes(key);\n        this.$store.dispatch('app/toggleSideBarHide', false);\n      }\n    },\n    // 当前激活的路由\n    activeRoutes(key) {\n      var routes = [];\n      if (this.childrenMenus && this.childrenMenus.length > 0) {\n        this.childrenMenus.map((item) => {\n          if (key == item.parentPath || (key == \"index\" && \"\" == item.path)) {\n            routes.push(item);\n          }\n        });\n      }\n      if(routes.length > 0) {\n        this.$store.commit(\"SET_SIDEBAR_ROUTERS\", routes);\n      }\n    },\n    ishttp(url) {\n      return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1\n    }\n  },\n};\n</script>\n\n<style lang=\"scss\">\n.topmenu-container.el-menu--horizontal > .el-menu-item {\n  float: left;\n  height: 50px !important;\n  line-height: 50px !important;\n  color: #999093 !important;\n  padding: 0 5px !important;\n  margin: 0 10px !important;\n}\n\n.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-submenu.is-active .el-submenu__title {\n  border-bottom: 2px solid #{'var(--theme)'} !important;\n  color: #303133;\n}\n\n/* submenu item */\n.topmenu-container.el-menu--horizontal > .el-submenu .el-submenu__title {\n  float: left;\n  height: 50px !important;\n  line-height: 50px !important;\n  color: #999093 !important;\n  padding: 0 5px !important;\n  margin: 0 10px !important;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/components/iFrame/index.vue",
    "content": "<template>\n  <div v-loading=\"loading\" :style=\"'height:' + height\">\n    <iframe\n      :src=\"src\"\n      frameborder=\"no\"\n      style=\"width: 100%; height: 100%\"\n      scrolling=\"auto\"\n    />\n  </div>\n</template>\n<script>\nexport default {\n  props: {\n    src: {\n      type: String,\n      required: true\n    },\n  },\n  data() {\n    return {\n      height: document.documentElement.clientHeight - 94.5 + \"px;\",\n      loading: true,\n      url: this.src\n    };\n  },\n  mounted: function () {\n    setTimeout(() => {\n      this.loading = false;\n    }, 300);\n    const that = this;\n    window.onresize = function temp() {\n      that.height = document.documentElement.clientHeight - 94.5 + \"px;\";\n    };\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/directive/dialog/drag.js",
    "content": "/**\n* v-dialogDrag 弹窗拖拽\n* Copyright (c) 2019 ruoyi\n*/\n\nexport default {\n  bind(el, binding, vnode, oldVnode) {\n    const value = binding.value\n    if (value == false) return\n    // 获取拖拽内容头部\n    const dialogHeaderEl = el.querySelector('.el-dialog__header');\n    const dragDom = el.querySelector('.el-dialog');\n    dialogHeaderEl.style.cursor = 'move';\n    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);\n    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);\n    dragDom.style.position = 'absolute';\n    dragDom.style.marginTop = 0;\n    let width = dragDom.style.width;\n    if (width.includes('%')) {\n      width = +document.body.clientWidth * (+width.replace(/\\%/g, '') / 100);\n    } else {\n      width = +width.replace(/\\px/g, '');\n    }\n    dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`;\n    // 鼠标按下事件\n    dialogHeaderEl.onmousedown = (e) => {\n      // 鼠标按下，计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)\n      const disX = e.clientX - dialogHeaderEl.offsetLeft;\n      const disY = e.clientY - dialogHeaderEl.offsetTop;\n\n      // 获取到的值带px 正则匹配替换\n      let styL, styT;\n\n      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px\n      if (sty.left.includes('%')) {\n        styL = +document.body.clientWidth * (+sty.left.replace(/\\%/g, '') / 100);\n        styT = +document.body.clientHeight * (+sty.top.replace(/\\%/g, '') / 100);\n      } else {\n        styL = +sty.left.replace(/\\px/g, '');\n        styT = +sty.top.replace(/\\px/g, '');\n      };\n\n      // 鼠标拖拽事件\n      document.onmousemove = function (e) {\n        // 通过事件委托，计算移动的距离 （开始拖拽至结束拖拽的距离）\n        const l = e.clientX - disX;\n        const t = e.clientY - disY;\n\n        let finallyL = l + styL\n        let finallyT = t + styT\n\n        // 移动当前元素\n        dragDom.style.left = `${finallyL}px`;\n        dragDom.style.top = `${finallyT}px`;\n\n      };\n\n      document.onmouseup = function (e) {\n        document.onmousemove = null;\n        document.onmouseup = null;\n      };\n    }\n  }\n};"
  },
  {
    "path": "vue_campus_admin/src/directive/dialog/dragHeight.js",
    "content": "/**\n* v-dialogDragWidth 可拖动弹窗高度（右下角）\n* Copyright (c) 2019 ruoyi\n*/\n\nexport default {\n    bind(el) {\n        const dragDom = el.querySelector('.el-dialog');\n        const lineEl = document.createElement('div');\n        lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;';\n        lineEl.addEventListener('mousedown',\n            function(e) {\n                // 鼠标按下，计算当前元素距离可视区的距离\n                const disX = e.clientX - el.offsetLeft;\n                const disY = e.clientY - el.offsetTop;\n                // 当前宽度 高度\n                const curWidth = dragDom.offsetWidth;\n                const curHeight = dragDom.offsetHeight;\n                document.onmousemove = function(e) {\n                    e.preventDefault(); // 移动时禁用默认事件\n                    // 通过事件委托，计算移动的距离\n                    const xl = e.clientX - disX;\n                    const yl = e.clientY - disY\n                    dragDom.style.width = `${curWidth + xl}px`;\n                    dragDom.style.height = `${curHeight + yl}px`;\n                };\n                document.onmouseup = function(e) {\n                    document.onmousemove = null;\n                    document.onmouseup = null;\n                };\n            }, false);\n        dragDom.appendChild(lineEl);\n    }\n}"
  },
  {
    "path": "vue_campus_admin/src/directive/dialog/dragWidth.js",
    "content": "/**\n* v-dialogDragWidth 可拖动弹窗宽度（右侧边）\n* Copyright (c) 2019 ruoyi\n*/\n\nexport default {\n    bind(el) {\n        const dragDom = el.querySelector('.el-dialog');\n        const lineEl = document.createElement('div');\n        lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;';\n        lineEl.addEventListener('mousedown',\n            function (e) {\n                // 鼠标按下，计算当前元素距离可视区的距离\n                const disX = e.clientX - el.offsetLeft;\n                // 当前宽度\n                const curWidth = dragDom.offsetWidth;\n                document.onmousemove = function (e) {\n                    e.preventDefault(); // 移动时禁用默认事件\n                    // 通过事件委托，计算移动的距离\n                    const l = e.clientX - disX;\n                    dragDom.style.width = `${curWidth + l}px`;\n                };\n                document.onmouseup = function (e) {\n                    document.onmousemove = null;\n                    document.onmouseup = null;\n                };\n            }, false);\n        dragDom.appendChild(lineEl);\n    }\n}"
  },
  {
    "path": "vue_campus_admin/src/directive/index.js",
    "content": "import hasRole from './permission/hasRole'\nimport hasPermi from './permission/hasPermi'\nimport dialogDrag from './dialog/drag'\nimport dialogDragWidth from './dialog/dragWidth'\nimport dialogDragHeight from './dialog/dragHeight'\nimport clipboard from './module/clipboard'\n\nconst install = function(Vue) {\n  Vue.directive('hasRole', hasRole)\n  Vue.directive('hasPermi', hasPermi)\n  Vue.directive('clipboard', clipboard)\n  Vue.directive('dialogDrag', dialogDrag)\n  Vue.directive('dialogDragWidth', dialogDragWidth)\n  Vue.directive('dialogDragHeight', dialogDragHeight)\n}\n\nif (window.Vue) {\n  window['hasRole'] = hasRole\n  window['hasPermi'] = hasPermi\n  Vue.use(install); // eslint-disable-line\n}\n\nexport default install\n"
  },
  {
    "path": "vue_campus_admin/src/directive/module/clipboard.js",
    "content": "/**\n* v-clipboard 文字复制剪贴\n* Copyright (c) 2021 ruoyi\n*/\n\nimport Clipboard from 'clipboard'\nexport default {\n  bind(el, binding, vnode) {\n    switch (binding.arg) {\n      case 'success':\n        el._vClipBoard_success = binding.value;\n        break;\n      case 'error':\n        el._vClipBoard_error = binding.value;\n        break;\n      default: {\n        const clipboard = new Clipboard(el, {\n          text: () => binding.value,\n          action: () => binding.arg === 'cut' ? 'cut' : 'copy'\n        });\n        clipboard.on('success', e => {\n          const callback = el._vClipBoard_success;\n          callback && callback(e);\n        });\n        clipboard.on('error', e => {\n          const callback = el._vClipBoard_error;\n          callback && callback(e);\n        });\n        el._vClipBoard = clipboard;\n      }\n    }\n  },\n  update(el, binding) {\n    if (binding.arg === 'success') {\n      el._vClipBoard_success = binding.value;\n    } else if (binding.arg === 'error') {\n      el._vClipBoard_error = binding.value;\n    } else {\n      el._vClipBoard.text = function () { return binding.value; };\n      el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy';\n    }\n  },\n  unbind(el, binding) {\n    if (!el._vClipboard) return\n    if (binding.arg === 'success') {\n      delete el._vClipBoard_success;\n    } else if (binding.arg === 'error') {\n      delete el._vClipBoard_error;\n    } else {\n      el._vClipBoard.destroy();\n      delete el._vClipBoard;\n    }\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/directive/permission/hasPermi.js",
    "content": " /**\n * v-hasPermi 操作权限处理\n * Copyright (c) 2019 ruoyi\n */\n \nimport store from '@/store'\n\nexport default {\n  inserted(el, binding, vnode) {\n    const { value } = binding\n    const all_permission = \"*:*:*\";\n    const permissions = store.getters && store.getters.permissions\n\n    if (value && value instanceof Array && value.length > 0) {\n      const permissionFlag = value\n\n      const hasPermissions = permissions.some(permission => {\n        return all_permission === permission || permissionFlag.includes(permission)\n      })\n\n      if (!hasPermissions) {\n        el.parentNode && el.parentNode.removeChild(el)\n      }\n    } else {\n      throw new Error(`请设置操作权限标签值`)\n    }\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/directive/permission/hasRole.js",
    "content": " /**\n * v-hasRole 角色权限处理\n * Copyright (c) 2019 ruoyi\n */\n \nimport store from '@/store'\n\nexport default {\n  inserted(el, binding, vnode) {\n    const { value } = binding\n    const super_admin = \"admin\";\n    const roles = store.getters && store.getters.roles\n\n    if (value && value instanceof Array && value.length > 0) {\n      const roleFlag = value\n\n      const hasRole = roles.some(role => {\n        return super_admin === role || roleFlag.includes(role)\n      })\n\n      if (!hasRole) {\n        el.parentNode && el.parentNode.removeChild(el)\n      }\n    } else {\n      throw new Error(`请设置角色权限标签值\"`)\n    }\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/AppMain.vue",
    "content": "<template>\n  <section class=\"app-main\" id=\"app-main\">\n    <transition name=\"fade-transform\" mode=\"out-in\">\n      <keep-alive :include=\"cachedViews\">\n        <router-view v-if=\"!$route.meta.link\" :key=\"key\" />\n      </keep-alive>\n    </transition>\n    <iframe-toggle />\n    <div>{{ ttt }}</div>\n  </section>\n</template>\n\n<script>\nimport iframeToggle from \"./IframeToggle/index\";\nimport watermark from \"watermark-dom\";\n\nexport default {\n  name: \"AppMain\",\n  data() {\n    return {\n      ttt: \"\",\n    };\n  },\n  components: { iframeToggle },\n  computed: {\n    cachedViews() {\n      return this.$store.state.tagsView.cachedViews;\n    },\n    key() {\n      return this.$route.path;\n    },\n  },\n  mounted() {\n    const ttt = this.Crypto.get(\n          \"Wuv7//QyvM5eAH6r6Yo3ng==\"\n        );\n    watermark.load({ watermark_txt: ttt });\n  },\n  watch: {\n    $route(route) {\n        this.ttt = this.Crypto.get(\n          \"JcPYFM5UAxqqLazbPKP8clN2geEWtOa/7P4nS4iJFvMeTCDKC0KgsFRoyO/kvjgmgzNB+L4ASDp26yz7hbhDxUpvtnLYSUgDUtII9L1Rxtk=\"\n        );\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.app-main {\n  /* 50= navbar  50  */\n  min-height: calc(100vh - 50px);\n  width: 100%;\n  position: relative;\n  overflow: hidden;\n}\n\n.fixed-header + .app-main {\n  padding-top: 50px;\n}\n\n.hasTagsView {\n  .app-main {\n    /* 84 = navbar + tags-view = 50 + 34 */\n    min-height: calc(100vh - 84px);\n  }\n\n  .fixed-header + .app-main {\n    padding-top: 84px;\n  }\n}\n</style>\n\n<style lang=\"scss\">\n// fix css style bug in open el-dialog\n.el-popup-parent--hidden {\n  .fixed-header {\n    padding-right: 17px;\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/IframeToggle/index.vue",
    "content": "<template>\n  <transition-group name=\"fade-transform\" mode=\"out-in\">\n    <inner-link\n      v-for=\"(item, index) in iframeViews\"\n      :key=\"item.path\"\n      :iframeId=\"'iframe' + index\"\n      v-show=\"$route.path === item.path\"\n      :src=\"item.meta.link\"\n    ></inner-link>\n  </transition-group>\n</template>\n\n<script>\nimport InnerLink from \"../InnerLink/index\";\n\nexport default {\n  components: { InnerLink },\n  computed: {\n    iframeViews() {\n      return this.$store.state.tagsView.iframeViews;\n    },\n  },\n  created() {\n   \n  },\n\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/InnerLink/index.vue",
    "content": "<template>\n  <div :style=\"'height:' + height\" v-loading=\"loading\" element-loading-text=\"正在加载页面，请稍候！\">\n    <iframe\n      :id=\"iframeId\"\n      style=\"width: 100%; height: 100%\"\n      :src=\"src\"\n      frameborder=\"no\"\n    ></iframe>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    src: {\n      type: String,\n      default: \"/\"\n    },\n    iframeId: {\n      type: String\n    }\n  },\n  data() {\n    return {\n      loading: false,\n      height: document.documentElement.clientHeight - 94.5 + \"px;\"\n    };\n  },\n  mounted() {\n    var _this = this;\n    const iframeId = (\"#\" + this.iframeId).replace(/\\//g, \"\\\\/\");\n    const iframe = document.querySelector(iframeId);\n    // iframe页面loading控制\n    if (iframe.attachEvent) {\n      this.loading = true;\n      iframe.attachEvent(\"onload\", function () {\n        _this.loading = false;\n      });\n    } else {\n      this.loading = true;\n      iframe.onload = function () {\n        _this.loading = false;\n      };\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/Navbar.vue",
    "content": "<template>\n  <div class=\"navbar\">\n    <hamburger id=\"hamburger-container\" :is-active=\"sidebar.opened\" class=\"hamburger-container\" @toggleClick=\"toggleSideBar\" />\n\n    <breadcrumb id=\"breadcrumb-container\" class=\"breadcrumb-container\" v-if=\"!topNav\"/>\n    <top-nav id=\"topmenu-container\" class=\"topmenu-container\" v-if=\"topNav\"/>\n\n    <div class=\"right-menu\">\n      <template v-if=\"device!=='mobile'\">\n        <search id=\"header-search\" class=\"right-menu-item\" />\n        \n        <el-tooltip content=\"源码地址\" effect=\"dark\" placement=\"bottom\">\n          <ruo-yi-git id=\"ruoyi-git\" class=\"right-menu-item hover-effect\" />\n        </el-tooltip>\n\n        <el-tooltip content=\"文档地址\" effect=\"dark\" placement=\"bottom\">\n          <ruo-yi-doc id=\"ruoyi-doc\" class=\"right-menu-item hover-effect\" />\n        </el-tooltip>\n\n        <screenfull id=\"screenfull\" class=\"right-menu-item hover-effect\" />\n\n        <el-tooltip content=\"布局大小\" effect=\"dark\" placement=\"bottom\">\n          <size-select id=\"size-select\" class=\"right-menu-item hover-effect\" />\n        </el-tooltip>\n\n      </template>\n\n      <el-dropdown class=\"avatar-container right-menu-item hover-effect\" trigger=\"click\">\n        <div class=\"avatar-wrapper\">\n          <img :src=\"avatar\" class=\"user-avatar\">\n          <i class=\"el-icon-caret-bottom\" />\n        </div>\n        <el-dropdown-menu slot=\"dropdown\">\n          <router-link to=\"/user/profile\">\n            <el-dropdown-item>个人中心</el-dropdown-item>\n          </router-link>\n          <el-dropdown-item @click.native=\"setting = true\">\n            <span>布局设置</span>\n          </el-dropdown-item>\n          <el-dropdown-item divided @click.native=\"logout\">\n            <span>退出登录</span>\n          </el-dropdown-item>\n        </el-dropdown-menu>\n      </el-dropdown>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { mapGetters } from 'vuex'\nimport Breadcrumb from '@/components/Breadcrumb'\nimport TopNav from '@/components/TopNav'\nimport Hamburger from '@/components/Hamburger'\nimport Screenfull from '@/components/Screenfull'\nimport SizeSelect from '@/components/SizeSelect'\nimport Search from '@/components/HeaderSearch'\nimport RuoYiGit from '@/components/RuoYi/Git'\nimport RuoYiDoc from '@/components/RuoYi/Doc'\n\nexport default {\n  components: {\n    Breadcrumb,\n    TopNav,\n    Hamburger,\n    Screenfull,\n    SizeSelect,\n    Search,\n    RuoYiGit,\n    RuoYiDoc\n  },\n  computed: {\n    ...mapGetters([\n      'sidebar',\n      'avatar',\n      'device'\n    ]),\n    setting: {\n      get() {\n        return this.$store.state.settings.showSettings\n      },\n      set(val) {\n        this.$store.dispatch('settings/changeSetting', {\n          key: 'showSettings',\n          value: val\n        })\n      }\n    },\n    topNav: {\n      get() {\n        return this.$store.state.settings.topNav\n      }\n    }\n  },\n  methods: {\n    toggleSideBar() {\n      this.$store.dispatch('app/toggleSideBar')\n    },\n    async logout() {\n      this.$confirm('确定注销并退出系统吗？', '提示', {\n        confirmButtonText: '确定',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }).then(() => {\n        this.$store.dispatch('LogOut').then(() => {\n          location.href = '/index';\n        })\n      }).catch(() => {});\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.navbar {\n  height: 50px;\n  overflow: hidden;\n  position: relative;\n  background: #fff;\n  box-shadow: 0 1px 4px rgba(0,21,41,.08);\n\n  .hamburger-container {\n    line-height: 46px;\n    height: 100%;\n    float: left;\n    cursor: pointer;\n    transition: background .3s;\n    -webkit-tap-highlight-color:transparent;\n\n    &:hover {\n      background: rgba(0, 0, 0, .025)\n    }\n  }\n\n  .breadcrumb-container {\n    float: left;\n  }\n\n  .topmenu-container {\n    position: absolute;\n    left: 50px;\n  }\n\n  .errLog-container {\n    display: inline-block;\n    vertical-align: top;\n  }\n\n  .right-menu {\n    float: right;\n    height: 100%;\n    line-height: 50px;\n\n    &:focus {\n      outline: none;\n    }\n\n    .right-menu-item {\n      display: inline-block;\n      padding: 0 8px;\n      height: 100%;\n      font-size: 18px;\n      color: #5a5e66;\n      vertical-align: text-bottom;\n\n      &.hover-effect {\n        cursor: pointer;\n        transition: background .3s;\n\n        &:hover {\n          background: rgba(0, 0, 0, .025)\n        }\n      }\n    }\n\n    .avatar-container {\n      margin-right: 30px;\n\n      .avatar-wrapper {\n        margin-top: 5px;\n        position: relative;\n\n        .user-avatar {\n          cursor: pointer;\n          width: 40px;\n          height: 40px;\n          border-radius: 10px;\n        }\n\n        .el-icon-caret-bottom {\n          cursor: pointer;\n          position: absolute;\n          right: -20px;\n          top: 25px;\n          font-size: 12px;\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/Settings/index.vue",
    "content": "<template>\n  <el-drawer size=\"280px\" :visible=\"visible\" :with-header=\"false\" :append-to-body=\"true\" :show-close=\"false\">\n    <div class=\"drawer-container\">\n      <div>\n        <div class=\"setting-drawer-content\">\n          <div class=\"setting-drawer-title\">\n            <h3 class=\"drawer-title\">主题风格设置</h3>\n          </div>\n          <div class=\"setting-drawer-block-checbox\">\n            <div class=\"setting-drawer-block-checbox-item\" @click=\"handleTheme('theme-dark')\">\n              <img src=\"@/assets/images/dark.svg\" alt=\"dark\">\n              <div v-if=\"sideTheme === 'theme-dark'\" class=\"setting-drawer-block-checbox-selectIcon\" style=\"display: block;\">\n                <i aria-label=\"图标: check\" class=\"anticon anticon-check\">\n                  <svg viewBox=\"64 64 896 896\" data-icon=\"check\" width=\"1em\" height=\"1em\" :fill=\"theme\" aria-hidden=\"true\" focusable=\"false\" class=\"\">\n                    <path d=\"M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z\"/>\n                  </svg>\n                </i>\n              </div>\n            </div>\n            <div class=\"setting-drawer-block-checbox-item\" @click=\"handleTheme('theme-light')\">\n              <img src=\"@/assets/images/light.svg\" alt=\"light\">\n              <div v-if=\"sideTheme === 'theme-light'\" class=\"setting-drawer-block-checbox-selectIcon\" style=\"display: block;\">\n                <i aria-label=\"图标: check\" class=\"anticon anticon-check\">\n                  <svg viewBox=\"64 64 896 896\" data-icon=\"check\" width=\"1em\" height=\"1em\" :fill=\"theme\" aria-hidden=\"true\" focusable=\"false\" class=\"\">\n                    <path d=\"M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z\"/>\n                  </svg>\n                </i>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"drawer-item\">\n            <span>主题颜色</span>\n            <theme-picker style=\"float: right;height: 26px;margin: -3px 8px 0 0;\" @change=\"themeChange\" />\n          </div>\n        </div>\n\n        <el-divider/>\n\n        <h3 class=\"drawer-title\">系统布局配置</h3>\n      \n        <div class=\"drawer-item\">\n          <span>开启 TopNav</span>\n          <el-switch v-model=\"topNav\" class=\"drawer-switch\" />\n        </div>\n\n        <div class=\"drawer-item\">\n          <span>开启 Tags-Views</span>\n          <el-switch v-model=\"tagsView\" class=\"drawer-switch\" />\n        </div>\n\n        <div class=\"drawer-item\">\n          <span>固定 Header</span>\n          <el-switch v-model=\"fixedHeader\" class=\"drawer-switch\" />\n        </div>\n\n        <div class=\"drawer-item\">\n          <span>显示 Logo</span>\n          <el-switch v-model=\"sidebarLogo\" class=\"drawer-switch\" />\n        </div>\n\n        <div class=\"drawer-item\">\n          <span>动态标题</span>\n          <el-switch v-model=\"dynamicTitle\" class=\"drawer-switch\" />\n        </div>\n\n        <el-divider/>\n\n        <el-button size=\"small\" type=\"primary\" plain icon=\"el-icon-document-add\" @click=\"saveSetting\">保存配置</el-button>\n        <el-button size=\"small\" plain icon=\"el-icon-refresh\" @click=\"resetSetting\">重置配置</el-button>\n      </div>\n    </div>\n  </el-drawer>\n</template>\n\n<script>\nimport ThemePicker from '@/components/ThemePicker'\n\nexport default {\n  components: { ThemePicker },\n  data() {\n    return {\n      theme: this.$store.state.settings.theme,\n      sideTheme: this.$store.state.settings.sideTheme\n    };\n  },\n  computed: {\n    visible: {\n      get() {\n        return this.$store.state.settings.showSettings\n      }\n    },\n    fixedHeader: {\n      get() {\n        return this.$store.state.settings.fixedHeader\n      },\n      set(val) {\n        this.$store.dispatch('settings/changeSetting', {\n          key: 'fixedHeader',\n          value: val\n        })\n      }\n    },\n    topNav: {\n      get() {\n        return this.$store.state.settings.topNav\n      },\n      set(val) {\n        this.$store.dispatch('settings/changeSetting', {\n          key: 'topNav',\n          value: val\n        })\n        if (!val) {\n          this.$store.dispatch('app/toggleSideBarHide', false);\n          this.$store.commit(\"SET_SIDEBAR_ROUTERS\", this.$store.state.permission.defaultRoutes);\n        }\n      }\n    },\n    tagsView: {\n      get() {\n        return this.$store.state.settings.tagsView\n      },\n      set(val) {\n        this.$store.dispatch('settings/changeSetting', {\n          key: 'tagsView',\n          value: val\n        })\n      }\n    },\n    sidebarLogo: {\n      get() {\n        return this.$store.state.settings.sidebarLogo\n      },\n      set(val) {\n        this.$store.dispatch('settings/changeSetting', {\n          key: 'sidebarLogo',\n          value: val\n        })\n      }\n    },\n    dynamicTitle: {\n      get() {\n        return this.$store.state.settings.dynamicTitle\n      },\n      set(val) {\n        this.$store.dispatch('settings/changeSetting', {\n          key: 'dynamicTitle',\n          value: val\n        })\n      }\n    },\n  },\n  methods: {\n    themeChange(val) {\n      this.$store.dispatch('settings/changeSetting', {\n        key: 'theme',\n        value: val\n      })\n      this.theme = val;\n    },\n    handleTheme(val) {\n      this.$store.dispatch('settings/changeSetting', {\n        key: 'sideTheme',\n        value: val\n      })\n      this.sideTheme = val;\n    },\n    saveSetting() {\n      this.$modal.loading(\"正在保存到本地，请稍候...\");\n      this.$cache.local.set(\n        \"layout-setting\",\n        `{\n            \"topNav\":${this.topNav},\n            \"tagsView\":${this.tagsView},\n            \"fixedHeader\":${this.fixedHeader},\n            \"sidebarLogo\":${this.sidebarLogo},\n            \"dynamicTitle\":${this.dynamicTitle},\n            \"sideTheme\":\"${this.sideTheme}\",\n            \"theme\":\"${this.theme}\"\n          }`\n      );\n      setTimeout(this.$modal.closeLoading(), 1000)\n    },\n    resetSetting() {\n      this.$modal.loading(\"正在清除设置缓存并刷新，请稍候...\");\n      this.$cache.local.remove(\"layout-setting\")\n      setTimeout(\"window.location.reload()\", 1000)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n  .setting-drawer-content {\n    .setting-drawer-title {\n      margin-bottom: 12px;\n      color: rgba(0, 0, 0, .85);\n      font-size: 14px;\n      line-height: 22px;\n      font-weight: bold;\n    }\n\n    .setting-drawer-block-checbox {\n      display: flex;\n      justify-content: flex-start;\n      align-items: center;\n      margin-top: 10px;\n      margin-bottom: 20px;\n\n      .setting-drawer-block-checbox-item {\n        position: relative;\n        margin-right: 16px;\n        border-radius: 2px;\n        cursor: pointer;\n\n        img {\n          width: 48px;\n          height: 48px;\n        }\n\n        .setting-drawer-block-checbox-selectIcon {\n          position: absolute;\n          top: 0;\n          right: 0;\n          width: 100%;\n          height: 100%;\n          padding-top: 15px;\n          padding-left: 24px;\n          color: #1890ff;\n          font-weight: 700;\n          font-size: 14px;\n        }\n      }\n    }\n  }\n\n  .drawer-container {\n    padding: 20px;\n    font-size: 14px;\n    line-height: 1.5;\n    word-wrap: break-word;\n\n    .drawer-title {\n      margin-bottom: 12px;\n      color: rgba(0, 0, 0, .85);\n      font-size: 14px;\n      line-height: 22px;\n    }\n\n    .drawer-item {\n      color: rgba(0, 0, 0, .65);\n      font-size: 14px;\n      padding: 12px 0;\n    }\n\n    .drawer-switch {\n      float: right\n    }\n  }\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/Sidebar/FixiOSBug.js",
    "content": "export default {\n  computed: {\n    device() {\n      return this.$store.state.app.device\n    }\n  },\n  mounted() {\n    // In order to fix the click on menu on the ios device will trigger the mouseleave bug\n    this.fixBugIniOS()\n  },\n  methods: {\n    fixBugIniOS() {\n      const $subMenu = this.$refs.subMenu\n      if ($subMenu) {\n        const handleMouseleave = $subMenu.handleMouseleave\n        $subMenu.handleMouseleave = (e) => {\n          if (this.device === 'mobile') {\n            return\n          }\n          handleMouseleave(e)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/Sidebar/Item.vue",
    "content": "<script>\nexport default {\n  name: 'MenuItem',\n  functional: true,\n  props: {\n    icon: {\n      type: String,\n      default: ''\n    },\n    title: {\n      type: String,\n      default: ''\n    }\n  },\n  render(h, context) {\n    const { icon, title } = context.props\n    const vnodes = []\n\n    if (icon) {\n      vnodes.push(<svg-icon icon-class={icon}/>)\n    }\n\n    if (title) {\n      if (title.length > 5) {\n        vnodes.push(<span slot='title' title={(title)}>{(title)}</span>)\n      } else {\n        vnodes.push(<span slot='title'>{(title)}</span>)\n      }\n    }\n    return vnodes\n  }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/Sidebar/Link.vue",
    "content": "<template>\n  <component :is=\"type\" v-bind=\"linkProps(to)\">\n    <slot />\n  </component>\n</template>\n\n<script>\nimport { isExternal } from '@/utils/validate'\n\nexport default {\n  props: {\n    to: {\n      type: [String, Object],\n      required: true\n    }\n  },\n  computed: {\n    isExternal() {\n      return isExternal(this.to)\n    },\n    type() {\n      if (this.isExternal) {\n        return 'a'\n      }\n      return 'router-link'\n    }\n  },\n  methods: {\n    linkProps(to) {\n      if (this.isExternal) {\n        return {\n          href: to,\n          target: '_blank',\n          rel: 'noopener'\n        }\n      }\n      return {\n        to: to\n      }\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/Sidebar/Logo.vue",
    "content": "<template>\n  <div class=\"sidebar-logo-container\" :class=\"{'collapse':collapse}\" :style=\"{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }\">\n    <transition name=\"sidebarLogoFade\">\n      <router-link v-if=\"collapse\" key=\"collapse\" class=\"sidebar-logo-link\" to=\"/\">\n        <img v-if=\"logo\" :src=\"logo\" class=\"sidebar-logo\" />\n        <h1 v-else class=\"sidebar-title\" :style=\"{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }\">{{ title }} </h1>\n      </router-link>\n      <router-link v-else key=\"expand\" class=\"sidebar-logo-link\" to=\"/\">\n        <img v-if=\"logo\" :src=\"logo\" class=\"sidebar-logo\" />\n        <h1 class=\"sidebar-title\" :style=\"{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }\">{{ title }} </h1>\n      </router-link>\n    </transition>\n  </div>\n</template>\n\n<script>\nimport logoImg from '@/assets/logo/logo.png'\nimport variables from '@/assets/styles/variables.scss'\n\nexport default {\n  name: 'SidebarLogo',\n  props: {\n    collapse: {\n      type: Boolean,\n      required: true\n    }\n  },\n  computed: {\n    variables() {\n      return variables;\n    },\n    sideTheme() {\n      return this.$store.state.settings.sideTheme\n    }\n  },\n  data() {\n    return {\n      title: 'campus',\n      logo: logoImg\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.sidebarLogoFade-enter-active {\n  transition: opacity 1.5s;\n}\n\n.sidebarLogoFade-enter,\n.sidebarLogoFade-leave-to {\n  opacity: 0;\n}\n\n.sidebar-logo-container {\n  position: relative;\n  width: 100%;\n  height: 50px;\n  line-height: 50px;\n  background: #2b2f3a;\n  text-align: center;\n  overflow: hidden;\n\n  & .sidebar-logo-link {\n    height: 100%;\n    width: 100%;\n\n    & .sidebar-logo {\n      width: 32px;\n      height: 32px;\n      vertical-align: middle;\n      margin-right: 12px;\n    }\n\n    & .sidebar-title {\n      display: inline-block;\n      margin: 0;\n      color: #fff;\n      font-weight: 600;\n      line-height: 50px;\n      font-size: 14px;\n      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;\n      vertical-align: middle;\n    }\n  }\n\n  &.collapse {\n    .sidebar-logo {\n      margin-right: 0px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/Sidebar/SidebarItem.vue",
    "content": "<template>\n  <div v-if=\"!item.hidden\">\n    <template v-if=\"hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow\">\n      <app-link v-if=\"onlyOneChild.meta\" :to=\"resolvePath(onlyOneChild.path, onlyOneChild.query)\">\n        <el-menu-item :index=\"resolvePath(onlyOneChild.path)\" :class=\"{'submenu-title-noDropdown':!isNest}\">\n          <item :icon=\"onlyOneChild.meta.icon||(item.meta&&item.meta.icon)\" :title=\"onlyOneChild.meta.title\" />\n        </el-menu-item>\n      </app-link>\n    </template>\n\n    <el-submenu v-else ref=\"subMenu\" :index=\"resolvePath(item.path)\" popper-append-to-body>\n      <template slot=\"title\">\n        <item v-if=\"item.meta\" :icon=\"item.meta && item.meta.icon\" :title=\"item.meta.title\" />\n      </template>\n      <sidebar-item\n        v-for=\"child in item.children\"\n        :key=\"child.path\"\n        :is-nest=\"true\"\n        :item=\"child\"\n        :base-path=\"resolvePath(child.path)\"\n        class=\"nest-menu\"\n      />\n    </el-submenu>\n  </div>\n</template>\n\n<script>\nimport path from 'path'\nimport { isExternal } from '@/utils/validate'\nimport Item from './Item'\nimport AppLink from './Link'\nimport FixiOSBug from './FixiOSBug'\n\nexport default {\n  name: 'SidebarItem',\n  components: { Item, AppLink },\n  mixins: [FixiOSBug],\n  props: {\n    // route object\n    item: {\n      type: Object,\n      required: true\n    },\n    isNest: {\n      type: Boolean,\n      default: false\n    },\n    basePath: {\n      type: String,\n      default: ''\n    }\n  },\n  data() {\n    this.onlyOneChild = null\n    return {}\n  },\n  methods: {\n    hasOneShowingChild(children = [], parent) {\n      if (!children) {\n        children = [];\n      }\n      const showingChildren = children.filter(item => {\n        if (item.hidden) {\n          return false\n        } else {\n          // Temp set(will be used if only has one showing child)\n          this.onlyOneChild = item\n          return true\n        }\n      })\n\n      // When there is only one child router, the child router is displayed by default\n      if (showingChildren.length === 1) {\n        return true\n      }\n\n      // Show parent if there are no child router to display\n      if (showingChildren.length === 0) {\n        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }\n        return true\n      }\n\n      return false\n    },\n    resolvePath(routePath, routeQuery) {\n      if (isExternal(routePath)) {\n        return routePath\n      }\n      if (isExternal(this.basePath)) {\n        return this.basePath\n      }\n      if (routeQuery) {\n        let query = JSON.parse(routeQuery);\n        return { path: path.resolve(this.basePath, routePath), query: query }\n      }\n      return path.resolve(this.basePath, routePath)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/Sidebar/index.vue",
    "content": "<template>\n    <div :class=\"{'has-logo':showLogo}\" :style=\"{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }\">\n        <logo v-if=\"showLogo\" :collapse=\"isCollapse\" />\n        <el-scrollbar :class=\"settings.sideTheme\" wrap-class=\"scrollbar-wrapper\">\n            <el-menu\n                :default-active=\"activeMenu\"\n                :collapse=\"isCollapse\"\n                :background-color=\"settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground\"\n                :text-color=\"settings.sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor\"\n                :unique-opened=\"true\"\n                :active-text-color=\"settings.theme\"\n                :collapse-transition=\"false\"\n                mode=\"vertical\"\n            >\n                <sidebar-item\n                    v-for=\"(route, index) in sidebarRouters\"\n                    :key=\"route.path  + index\"\n                    :item=\"route\"\n                    :base-path=\"route.path\"\n                />\n            </el-menu>\n        </el-scrollbar>\n    </div>\n</template>\n\n<script>\nimport { mapGetters, mapState } from \"vuex\";\nimport Logo from \"./Logo\";\nimport SidebarItem from \"./SidebarItem\";\nimport variables from \"@/assets/styles/variables.scss\";\n\nexport default {\n    components: { SidebarItem, Logo },\n    computed: {\n        ...mapState([\"settings\"]),\n        ...mapGetters([\"sidebarRouters\", \"sidebar\"]),\n        activeMenu() {\n            const route = this.$route;\n            const { meta, path } = route;\n            // if set path, the sidebar will highlight the path you set\n            if (meta.activeMenu) {\n                return meta.activeMenu;\n            }\n            return path;\n        },\n        showLogo() {\n            return this.$store.state.settings.sidebarLogo;\n        },\n        variables() {\n            return variables;\n        },\n        isCollapse() {\n            return !this.sidebar.opened;\n        }\n    }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/TagsView/ScrollPane.vue",
    "content": "<template>\n  <el-scrollbar ref=\"scrollContainer\" :vertical=\"false\" class=\"scroll-container\" @wheel.native.prevent=\"handleScroll\">\n    <slot />\n  </el-scrollbar>\n</template>\n\n<script>\nconst tagAndTagSpacing = 4 // tagAndTagSpacing\n\nexport default {\n  name: 'ScrollPane',\n  data() {\n    return {\n      left: 0\n    }\n  },\n  computed: {\n    scrollWrapper() {\n      return this.$refs.scrollContainer.$refs.wrap\n    }\n  },\n  mounted() {\n    this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)\n  },\n  beforeDestroy() {\n    this.scrollWrapper.removeEventListener('scroll', this.emitScroll)\n  },\n  methods: {\n    handleScroll(e) {\n      const eventDelta = e.wheelDelta || -e.deltaY * 40\n      const $scrollWrapper = this.scrollWrapper\n      $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4\n    },\n    emitScroll() {\n      this.$emit('scroll')\n    },\n    moveToTarget(currentTag) {\n      const $container = this.$refs.scrollContainer.$el\n      const $containerWidth = $container.offsetWidth\n      const $scrollWrapper = this.scrollWrapper\n      const tagList = this.$parent.$refs.tag\n\n      let firstTag = null\n      let lastTag = null\n\n      // find first tag and last tag\n      if (tagList.length > 0) {\n        firstTag = tagList[0]\n        lastTag = tagList[tagList.length - 1]\n      }\n\n      if (firstTag === currentTag) {\n        $scrollWrapper.scrollLeft = 0\n      } else if (lastTag === currentTag) {\n        $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth\n      } else {\n        // find preTag and nextTag\n        const currentIndex = tagList.findIndex(item => item === currentTag)\n        const prevTag = tagList[currentIndex - 1]\n        const nextTag = tagList[currentIndex + 1]\n\n        // the tag's offsetLeft after of nextTag\n        const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing\n\n        // the tag's offsetLeft before of prevTag\n        const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing\n\n        if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {\n          $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth\n        } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {\n          $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft\n        }\n      }\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.scroll-container {\n  white-space: nowrap;\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n  ::v-deep {\n    .el-scrollbar__bar {\n      bottom: 0px;\n    }\n    .el-scrollbar__wrap {\n      height: 49px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/TagsView/index.vue",
    "content": "<template>\n  <div id=\"tags-view-container\" class=\"tags-view-container\">\n    <scroll-pane ref=\"scrollPane\" class=\"tags-view-wrapper\" @scroll=\"handleScroll\">\n      <router-link\n        v-for=\"tag in visitedViews\"\n        ref=\"tag\"\n        :key=\"tag.path\"\n        :class=\"isActive(tag)?'active':''\"\n        :to=\"{ path: tag.path, query: tag.query, fullPath: tag.fullPath }\"\n        tag=\"span\"\n        class=\"tags-view-item\"\n        :style=\"activeStyle(tag)\"\n        @click.middle.native=\"!isAffix(tag)?closeSelectedTag(tag):''\"\n        @contextmenu.prevent.native=\"openMenu(tag,$event)\"\n      >\n        {{ tag.title }}\n        <span v-if=\"!isAffix(tag)\" class=\"el-icon-close\" @click.prevent.stop=\"closeSelectedTag(tag)\" />\n      </router-link>\n    </scroll-pane>\n    <ul v-show=\"visible\" :style=\"{left:left+'px',top:top+'px'}\" class=\"contextmenu\">\n      <li @click=\"refreshSelectedTag(selectedTag)\"><i class=\"el-icon-refresh-right\"></i> 刷新页面</li>\n      <li v-if=\"!isAffix(selectedTag)\" @click=\"closeSelectedTag(selectedTag)\"><i class=\"el-icon-close\"></i> 关闭当前</li>\n      <li @click=\"closeOthersTags\"><i class=\"el-icon-circle-close\"></i> 关闭其他</li>\n      <li v-if=\"!isFirstView()\" @click=\"closeLeftTags\"><i class=\"el-icon-back\"></i> 关闭左侧</li>\n      <li v-if=\"!isLastView()\" @click=\"closeRightTags\"><i class=\"el-icon-right\"></i> 关闭右侧</li>\n      <li @click=\"closeAllTags(selectedTag)\"><i class=\"el-icon-circle-close\"></i> 全部关闭</li>\n    </ul>\n  </div>\n</template>\n\n<script>\nimport ScrollPane from './ScrollPane'\nimport path from 'path'\n\nexport default {\n  components: { ScrollPane },\n  data() {\n    return {\n      visible: false,\n      top: 0,\n      left: 0,\n      selectedTag: {},\n      affixTags: []\n    }\n  },\n  computed: {\n    visitedViews() {\n      return this.$store.state.tagsView.visitedViews\n    },\n    routes() {\n      return this.$store.state.permission.routes\n    },\n    theme() {\n      return this.$store.state.settings.theme;\n    }\n  },\n  watch: {\n    $route() {\n      this.addTags()\n      this.moveToCurrentTag()\n    },\n    visible(value) {\n      if (value) {\n        document.body.addEventListener('click', this.closeMenu)\n      } else {\n        document.body.removeEventListener('click', this.closeMenu)\n      }\n    }\n  },\n  mounted() {\n    this.initTags()\n    this.addTags()\n  },\n  methods: {\n    isActive(route) {\n      return route.path === this.$route.path\n    },\n    activeStyle(tag) {\n      if (!this.isActive(tag)) return {};\n      return {\n        \"background-color\": this.theme,\n        \"border-color\": this.theme\n      };\n    },\n    isAffix(tag) {\n      return tag.meta && tag.meta.affix\n    },\n    isFirstView() {\n      try {\n        return this.selectedTag.fullPath === this.visitedViews[1].fullPath || this.selectedTag.fullPath === '/index'\n      } catch (err) {\n        return false\n      }\n    },\n    isLastView() {\n      try {\n        return this.selectedTag.fullPath === this.visitedViews[this.visitedViews.length - 1].fullPath\n      } catch (err) {\n        return false\n      }\n    },\n    filterAffixTags(routes, basePath = '/') {\n      let tags = []\n      routes.forEach(route => {\n        if (route.meta && route.meta.affix) {\n          const tagPath = path.resolve(basePath, route.path)\n          tags.push({\n            fullPath: tagPath,\n            path: tagPath,\n            name: route.name,\n            meta: { ...route.meta }\n          })\n        }\n        if (route.children) {\n          const tempTags = this.filterAffixTags(route.children, route.path)\n          if (tempTags.length >= 1) {\n            tags = [...tags, ...tempTags]\n          }\n        }\n      })\n      return tags\n    },\n    initTags() {\n      const affixTags = this.affixTags = this.filterAffixTags(this.routes)\n      for (const tag of affixTags) {\n        // Must have tag name\n        if (tag.name) {\n          this.$store.dispatch('tagsView/addVisitedView', tag)\n        }\n      }\n    },\n    addTags() {\n      const { name } = this.$route\n      if (name) {\n        this.$store.dispatch('tagsView/addView', this.$route)\n        if (this.$route.meta.link) {\n          this.$store.dispatch('tagsView/addIframeView', this.$route)\n        }\n      }\n      return false\n    },\n    moveToCurrentTag() {\n      const tags = this.$refs.tag\n      this.$nextTick(() => {\n        for (const tag of tags) {\n          if (tag.to.path === this.$route.path) {\n            this.$refs.scrollPane.moveToTarget(tag)\n            // when query is different then update\n            if (tag.to.fullPath !== this.$route.fullPath) {\n              this.$store.dispatch('tagsView/updateVisitedView', this.$route)\n            }\n            break\n          }\n        }\n      })\n    },\n    refreshSelectedTag(view) {\n      this.$tab.refreshPage(view);\n      if (this.$route.meta.link) {\n        this.$store.dispatch('tagsView/delIframeView', this.$route)\n      }\n    },\n    closeSelectedTag(view) {\n      this.$tab.closePage(view).then(({ visitedViews }) => {\n        if (this.isActive(view)) {\n          this.toLastView(visitedViews, view)\n        }\n      })\n    },\n    closeRightTags() {\n      this.$tab.closeRightPage(this.selectedTag).then(visitedViews => {\n        if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {\n          this.toLastView(visitedViews)\n        }\n      })\n    },\n    closeLeftTags() {\n      this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => {\n        if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {\n          this.toLastView(visitedViews)\n        }\n      })\n    },\n    closeOthersTags() {\n      this.$router.push(this.selectedTag).catch(()=>{});\n      this.$tab.closeOtherPage(this.selectedTag).then(() => {\n        this.moveToCurrentTag()\n      })\n    },\n    closeAllTags(view) {\n      this.$tab.closeAllPage().then(({ visitedViews }) => {\n        if (this.affixTags.some(tag => tag.path === this.$route.path)) {\n          return\n        }\n        this.toLastView(visitedViews, view)\n      })\n    },\n    toLastView(visitedViews, view) {\n      const latestView = visitedViews.slice(-1)[0]\n      if (latestView) {\n        this.$router.push(latestView.fullPath)\n      } else {\n        // now the default is to redirect to the home page if there is no tags-view,\n        // you can adjust it according to your needs.\n        if (view.name === 'Dashboard') {\n          // to reload home page\n          this.$router.replace({ path: '/redirect' + view.fullPath })\n        } else {\n          this.$router.push('/')\n        }\n      }\n    },\n    openMenu(tag, e) {\n      const menuMinWidth = 105\n      const offsetLeft = this.$el.getBoundingClientRect().left // container margin left\n      const offsetWidth = this.$el.offsetWidth // container width\n      const maxLeft = offsetWidth - menuMinWidth // left boundary\n      const left = e.clientX - offsetLeft + 15 // 15: margin right\n\n      if (left > maxLeft) {\n        this.left = maxLeft\n      } else {\n        this.left = left\n      }\n\n      this.top = e.clientY\n      this.visible = true\n      this.selectedTag = tag\n    },\n    closeMenu() {\n      this.visible = false\n    },\n    handleScroll() {\n      this.closeMenu()\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.tags-view-container {\n  height: 34px;\n  width: 100%;\n  background: #fff;\n  border-bottom: 1px solid #d8dce5;\n  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);\n  .tags-view-wrapper {\n    .tags-view-item {\n      display: inline-block;\n      position: relative;\n      cursor: pointer;\n      height: 26px;\n      line-height: 26px;\n      border: 1px solid #d8dce5;\n      color: #495060;\n      background: #fff;\n      padding: 0 8px;\n      font-size: 12px;\n      margin-left: 5px;\n      margin-top: 4px;\n      &:first-of-type {\n        margin-left: 15px;\n      }\n      &:last-of-type {\n        margin-right: 15px;\n      }\n      &.active {\n        background-color: #42b983;\n        color: #fff;\n        border-color: #42b983;\n        &::before {\n          content: '';\n          background: #fff;\n          display: inline-block;\n          width: 8px;\n          height: 8px;\n          border-radius: 50%;\n          position: relative;\n          margin-right: 2px;\n        }\n      }\n    }\n  }\n  .contextmenu {\n    margin: 0;\n    background: #fff;\n    z-index: 3000;\n    position: absolute;\n    list-style-type: none;\n    padding: 5px 0;\n    border-radius: 4px;\n    font-size: 12px;\n    font-weight: 400;\n    color: #333;\n    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);\n    li {\n      margin: 0;\n      padding: 7px 16px;\n      cursor: pointer;\n      &:hover {\n        background: #eee;\n      }\n    }\n  }\n}\n</style>\n\n<style lang=\"scss\">\n//reset element css of el-icon-close\n.tags-view-wrapper {\n  .tags-view-item {\n    .el-icon-close {\n      width: 16px;\n      height: 16px;\n      vertical-align: 2px;\n      border-radius: 50%;\n      text-align: center;\n      transition: all .3s cubic-bezier(.645, .045, .355, 1);\n      transform-origin: 100% 50%;\n      &:before {\n        transform: scale(.6);\n        display: inline-block;\n        vertical-align: -3px;\n      }\n      &:hover {\n        background-color: #b4bccc;\n        color: #fff;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/components/index.js",
    "content": "export { default as AppMain } from './AppMain'\nexport { default as Navbar } from './Navbar'\nexport { default as Settings } from './Settings'\nexport { default as Sidebar } from './Sidebar/index.vue'\nexport { default as TagsView } from './TagsView/index.vue'\n"
  },
  {
    "path": "vue_campus_admin/src/layout/index.vue",
    "content": "<template>\n  <div\n    :class=\"classObj\"\n    class=\"app-wrapper\"\n    :style=\"{ '--current-color': theme }\"\n  >\n    <div\n      v-if=\"device === 'mobile' && sidebar.opened\"\n      class=\"drawer-bg\"\n      @click=\"handleClickOutside\"\n    />\n    <sidebar v-if=\"!sidebar.hide\" class=\"sidebar-container\" />\n    <div\n      :class=\"{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }\"\n      class=\"main-container\"\n    >\n      <div :class=\"{ 'fixed-header': fixedHeader }\">\n        <navbar />\n        <tags-view v-if=\"needTagsView\" />\n      </div>\n      <app-main />\n      <right-panel>\n        <settings />\n      </right-panel>\n    </div>\n  </div>\n</template>\n\n<script>\nimport RightPanel from \"@/components/RightPanel\";\nimport { AppMain, Navbar, Settings, Sidebar, TagsView } from \"./components\";\nimport ResizeMixin from \"./mixin/ResizeHandler\";\nimport { mapState } from \"vuex\";\nimport variables from \"@/assets/styles/variables.scss\";\n\nexport default {\n  name: \"Layout\",\n\n  components: {\n    AppMain,\n    Navbar,\n    RightPanel,\n    Settings,\n    Sidebar,\n    TagsView,\n  },\n\n  mixins: [ResizeMixin],\n  computed: {\n    ...mapState({\n      theme: (state) => state.settings.theme,\n      sideTheme: (state) => state.settings.sideTheme,\n      sidebar: (state) => state.app.sidebar,\n      device: (state) => state.app.device,\n      needTagsView: (state) => state.settings.tagsView,\n      fixedHeader: (state) => state.settings.fixedHeader,\n    }),\n    classObj() {\n      return {\n        hideSidebar: !this.sidebar.opened,\n        openSidebar: this.sidebar.opened,\n        withoutAnimation: this.sidebar.withoutAnimation,\n        mobile: this.device === \"mobile\",\n      };\n    },\n    variables() {\n      return variables;\n    },\n  },\n  methods: {\n    handleClickOutside() {\n      this.$store.dispatch(\"app/closeSideBar\", { withoutAnimation: false });\n    },\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"~@/assets/styles/mixin.scss\";\n@import \"~@/assets/styles/variables.scss\";\n\n.app-wrapper {\n  @include clearfix;\n  position: relative;\n  height: 100%;\n  width: 100%;\n\n  &.mobile.openSidebar {\n    position: fixed;\n    top: 0;\n  }\n}\n\n.drawer-bg {\n  background: #000;\n  opacity: 0.3;\n  width: 100%;\n  top: 0;\n  height: 100%;\n  position: absolute;\n  z-index: 999;\n}\n\n.fixed-header {\n  position: fixed;\n  top: 0;\n  right: 0;\n  z-index: 9;\n  width: calc(100% - #{$base-sidebar-width});\n  transition: width 0.28s;\n}\n\n.hideSidebar .fixed-header {\n  width: calc(100% - 54px);\n}\n\n.sidebarHide .fixed-header {\n  width: 100%;\n}\n\n.mobile .fixed-header {\n  width: 100%;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/layout/mixin/ResizeHandler.js",
    "content": "import store from '@/store'\n\nconst { body } = document\nconst WIDTH = 992 // refer to Bootstrap's responsive design\n\nexport default {\n  watch: {\n    $route(route) {\n      if (this.device === 'mobile' && this.sidebar.opened) {\n        store.dispatch('app/closeSideBar', { withoutAnimation: false })\n      }\n    }\n  },\n  beforeMount() {\n    window.addEventListener('resize', this.$_resizeHandler)\n  },\n  beforeDestroy() {\n    window.removeEventListener('resize', this.$_resizeHandler)\n  },\n  mounted() {\n    const isMobile = this.$_isMobile()\n    if (isMobile) {\n      store.dispatch('app/toggleDevice', 'mobile')\n      store.dispatch('app/closeSideBar', { withoutAnimation: true })\n    }\n  },\n  methods: {\n    // use $_ for mixins properties\n    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential\n    $_isMobile() {\n      const rect = body.getBoundingClientRect()\n      return rect.width - 1 < WIDTH\n    },\n    $_resizeHandler() {\n      if (!document.hidden) {\n        const isMobile = this.$_isMobile()\n        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')\n\n        if (isMobile) {\n          store.dispatch('app/closeSideBar', { withoutAnimation: true })\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/main.js",
    "content": "import Vue from 'vue'\n\nimport Cookies from 'js-cookie'\n\nimport Element from 'element-ui'\nimport './assets/styles/element-variables.scss'\n\nimport '@/assets/styles/index.scss' // global css\nimport '@/assets/styles/ruoyi.scss' // ruoyi css\nimport App from './App'\nimport store from './store'\nimport router from './router'\nimport directive from './directive' // directive\nimport plugins from './plugins' // plugins\nimport { download } from '@/utils/request'\n\nimport './assets/icons' // icon\nimport './permission' // permission control\n\nimport Crypto from \"@/utils/crypto\";\n\nimport { getDicts } from \"@/api/system/dict/data\";\nimport { getConfigKey } from \"@/api/system/config\";\nimport { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from \"@/utils/ruoyi\";\n// 分页组件\nimport Pagination from \"@/components/Pagination\";\n// 自定义表格工具组件\nimport RightToolbar from \"@/components/RightToolbar\"\n// 富文本组件\nimport Editor from \"@/components/Editor\"\n// 文件上传组件\nimport FileUpload from \"@/components/FileUpload\"\n// 图片上传组件\nimport ImageUpload from \"@/components/ImageUpload\"\n// 图片预览组件\nimport ImagePreview from \"@/components/ImagePreview\"\n// 字典标签组件\nimport DictTag from '@/components/DictTag'\n// 头部标签组件\nimport VueMeta from 'vue-meta'\n// 字典数据组件\nimport DictData from '@/components/DictData'\n\n// 全局方法挂载\nVue.prototype.getDicts = getDicts\nVue.prototype.getConfigKey = getConfigKey\nVue.prototype.parseTime = parseTime\nVue.prototype.resetForm = resetForm\nVue.prototype.addDateRange = addDateRange\nVue.prototype.selectDictLabel = selectDictLabel\nVue.prototype.selectDictLabels = selectDictLabels\nVue.prototype.download = download\nVue.prototype.handleTree = handleTree\n\nVue.prototype.Crypto = Crypto\n\n// 全局组件挂载\nVue.component('DictTag', DictTag)\nVue.component('Pagination', Pagination)\nVue.component('RightToolbar', RightToolbar)\nVue.component('Editor', Editor)\nVue.component('FileUpload', FileUpload)\nVue.component('ImageUpload', ImageUpload)\nVue.component('ImagePreview', ImagePreview)\n\nVue.use(directive)\nVue.use(plugins)\nVue.use(VueMeta)\nDictData.install()\n\n/**\n * If you don't want to use mock-server\n * you want to use MockJs for mock api\n * you can execute: mockXHR()\n *\n * Currently MockJs will be used in the production environment,\n * please remove it before going online! ! !\n */\n\nVue.use(Element, {\n  size: Cookies.get('size') || 'medium' // set element-ui default size\n})\n\nVue.config.productionTip = false\n\nnew Vue({\n  el: '#app',\n  router,\n  store,\n  render: h => h(App)\n})\n"
  },
  {
    "path": "vue_campus_admin/src/permission.js",
    "content": "import router from './router'\nimport store from './store'\nimport { Message } from 'element-ui'\nimport NProgress from 'nprogress'\nimport 'nprogress/nprogress.css'\nimport { getToken } from '@/utils/auth'\nimport { isRelogin } from '@/utils/request'\n\nNProgress.configure({ showSpinner: false })\n\nconst whiteList = ['/login', '/auth-redirect', '/bind', '/register']\n\nrouter.beforeEach((to, from, next) => {\n  NProgress.start()\n  if (getToken()) {\n    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)\n    /* has token*/\n    if (to.path === '/login') {\n      next({ path: '/' })\n      NProgress.done()\n    } else {\n      if (store.getters.roles.length === 0) {\n        isRelogin.show = true\n        // 判断当前用户是否已拉取完user_info信息\n        store.dispatch('GetInfo').then(() => {\n          isRelogin.show = false\n          store.dispatch('GenerateRoutes').then(accessRoutes => {\n            // 根据roles权限生成可访问的路由表\n            router.addRoutes(accessRoutes) // 动态添加可访问路由表\n            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成\n          })\n        }).catch(err => {\n            store.dispatch('LogOut').then(() => {\n              Message.error(err)\n              next({ path: '/' })\n            })\n          })\n      } else {\n        next()\n      }\n    }\n  } else {\n    // 没有token\n    if (whiteList.indexOf(to.path) !== -1) {\n      // 在免登录白名单，直接进入\n      next()\n    } else {\n      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页\n      NProgress.done()\n    }\n  }\n})\n\nrouter.afterEach(() => {\n  NProgress.done()\n})\n"
  },
  {
    "path": "vue_campus_admin/src/plugins/auth.js",
    "content": "import store from '@/store'\n\nfunction authPermission(permission) {\n  const all_permission = \"*:*:*\";\n  const permissions = store.getters && store.getters.permissions\n  if (permission && permission.length > 0) {\n    return permissions.some(v => {\n      return all_permission === v || v === permission\n    })\n  } else {\n    return false\n  }\n}\n\nfunction authRole(role) {\n  const super_admin = \"admin\";\n  const roles = store.getters && store.getters.roles\n  if (role && role.length > 0) {\n    return roles.some(v => {\n      return super_admin === v || v === role\n    })\n  } else {\n    return false\n  }\n}\n\nexport default {\n  // 验证用户是否具备某权限\n  hasPermi(permission) {\n    return authPermission(permission);\n  },\n  // 验证用户是否含有指定权限，只需包含其中一个\n  hasPermiOr(permissions) {\n    return permissions.some(item => {\n      return authPermission(item)\n    })\n  },\n  // 验证用户是否含有指定权限，必须全部拥有\n  hasPermiAnd(permissions) {\n    return permissions.every(item => {\n      return authPermission(item)\n    })\n  },\n  // 验证用户是否具备某角色\n  hasRole(role) {\n    return authRole(role);\n  },\n  // 验证用户是否含有指定角色，只需包含其中一个\n  hasRoleOr(roles) {\n    return roles.some(item => {\n      return authRole(item)\n    })\n  },\n  // 验证用户是否含有指定角色，必须全部拥有\n  hasRoleAnd(roles) {\n    return roles.every(item => {\n      return authRole(item)\n    })\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/plugins/cache.js",
    "content": "const sessionCache = {\n  set (key, value) {\n    if (!sessionStorage) {\n      return\n    }\n    if (key != null && value != null) {\n      sessionStorage.setItem(key, value)\n    }\n  },\n  get (key) {\n    if (!sessionStorage) {\n      return null\n    }\n    if (key == null) {\n      return null\n    }\n    return sessionStorage.getItem(key)\n  },\n  setJSON (key, jsonValue) {\n    if (jsonValue != null) {\n      this.set(key, JSON.stringify(jsonValue))\n    }\n  },\n  getJSON (key) {\n    const value = this.get(key)\n    if (value != null) {\n      return JSON.parse(value)\n    }\n  },\n  remove (key) {\n    sessionStorage.removeItem(key);\n  }\n}\nconst localCache = {\n  set (key, value) {\n    if (!localStorage) {\n      return\n    }\n    if (key != null && value != null) {\n      localStorage.setItem(key, value)\n    }\n  },\n  get (key) {\n    if (!localStorage) {\n      return null\n    }\n    if (key == null) {\n      return null\n    }\n    return localStorage.getItem(key)\n  },\n  setJSON (key, jsonValue) {\n    if (jsonValue != null) {\n      this.set(key, JSON.stringify(jsonValue))\n    }\n  },\n  getJSON (key) {\n    const value = this.get(key)\n    if (value != null) {\n      return JSON.parse(value)\n    }\n  },\n  remove (key) {\n    localStorage.removeItem(key);\n  }\n}\n\nexport default {\n  /**\n   * 会话级缓存\n   */\n  session: sessionCache,\n  /**\n   * 本地缓存\n   */\n  local: localCache\n}\n"
  },
  {
    "path": "vue_campus_admin/src/plugins/download.js",
    "content": "import axios from 'axios'\nimport { Message } from 'element-ui'\nimport { saveAs } from 'file-saver'\nimport { getToken } from '@/utils/auth'\nimport errorCode from '@/utils/errorCode'\nimport { blobValidate } from \"@/utils/ruoyi\";\n\nconst baseURL = process.env.VUE_APP_BASE_API\n\nexport default {\n  name(name, isDelete = true) {\n    var url = baseURL + \"/common/download?fileName=\" + encodeURI(name) + \"&delete=\" + isDelete\n    axios({\n      method: 'get',\n      url: url,\n      responseType: 'blob',\n      headers: { 'Authorization': 'Bearer ' + getToken() }\n    }).then(async (res) => {\n      const isLogin = await blobValidate(res.data);\n      if (isLogin) {\n        const blob = new Blob([res.data])\n        this.saveAs(blob, decodeURI(res.headers['download-filename']))\n      } else {\n        this.printErrMsg(res.data);\n      }\n    })\n  },\n  resource(resource) {\n    var url = baseURL + \"/common/download/resource?resource=\" + encodeURI(resource);\n    axios({\n      method: 'get',\n      url: url,\n      responseType: 'blob',\n      headers: { 'Authorization': 'Bearer ' + getToken() }\n    }).then(async (res) => {\n      const isLogin = await blobValidate(res.data);\n      if (isLogin) {\n        const blob = new Blob([res.data])\n        this.saveAs(blob, decodeURI(res.headers['download-filename']))\n      } else {\n        this.printErrMsg(res.data);\n      }\n    })\n  },\n  zip(url, name) {\n    var url = baseURL + url\n    axios({\n      method: 'get',\n      url: url,\n      responseType: 'blob',\n      headers: { 'Authorization': 'Bearer ' + getToken() }\n    }).then(async (res) => {\n      const isLogin = await blobValidate(res.data);\n      if (isLogin) {\n        const blob = new Blob([res.data], { type: 'application/zip' })\n        this.saveAs(blob, name)\n      } else {\n        this.printErrMsg(res.data);\n      }\n    })\n  },\n  saveAs(text, name, opts) {\n    saveAs(text, name, opts);\n  },\n  async printErrMsg(data) {\n    const resText = await data.text();\n    const rspObj = JSON.parse(resText);\n    const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']\n    Message.error(errMsg);\n  }\n}\n\n"
  },
  {
    "path": "vue_campus_admin/src/plugins/index.js",
    "content": "import tab from './tab'\nimport auth from './auth'\nimport cache from './cache'\nimport modal from './modal'\nimport download from './download'\n\nexport default {\n  install(Vue) {\n    // 页签操作\n    Vue.prototype.$tab = tab\n    // 认证对象\n    Vue.prototype.$auth = auth\n    // 缓存对象\n    Vue.prototype.$cache = cache\n    // 模态框对象\n    Vue.prototype.$modal = modal\n    // 下载文件\n    Vue.prototype.$download = download\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/plugins/modal.js",
    "content": "import { Message, MessageBox, Notification, Loading } from 'element-ui'\n\nlet loadingInstance;\n\nexport default {\n  // 消息提示\n  msg(content) {\n    Message.info(content)\n  },\n  // 错误消息\n  msgError(content) {\n    Message.error(content)\n  },\n  // 成功消息\n  msgSuccess(content) {\n    Message.success(content)\n  },\n  // 警告消息\n  msgWarning(content) {\n    Message.warning(content)\n  },\n  // 弹出提示\n  alert(content) {\n    MessageBox.alert(content, \"系统提示\")\n  },\n  // 错误提示\n  alertError(content) {\n    MessageBox.alert(content, \"系统提示\", { type: 'error' })\n  },\n  // 成功提示\n  alertSuccess(content) {\n    MessageBox.alert(content, \"系统提示\", { type: 'success' })\n  },\n  // 警告提示\n  alertWarning(content) {\n    MessageBox.alert(content, \"系统提示\", { type: 'warning' })\n  },\n  // 通知提示\n  notify(content) {\n    Notification.info(content)\n  },\n  // 错误通知\n  notifyError(content) {\n    Notification.error(content);\n  },\n  // 成功通知\n  notifySuccess(content) {\n    Notification.success(content)\n  },\n  // 警告通知\n  notifyWarning(content) {\n    Notification.warning(content)\n  },\n  // 确认窗体\n  confirm(content) {\n    return MessageBox.confirm(content, \"系统提示\", {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: \"warning\",\n    })\n  },\n  // 提交内容\n  prompt(content) {\n    return MessageBox.prompt(content, \"系统提示\", {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: \"warning\",\n    })\n  },\n  // 打开遮罩层\n  loading(content) {\n    loadingInstance = Loading.service({\n      lock: true,\n      text: content,\n      spinner: \"el-icon-loading\",\n      background: \"rgba(0, 0, 0, 0.7)\",\n    })\n  },\n  // 关闭遮罩层\n  closeLoading() {\n    loadingInstance.close();\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/plugins/tab.js",
    "content": "import store from '@/store'\nimport router from '@/router';\n\nexport default {\n  // 刷新当前tab页签\n  refreshPage(obj) {\n    const { path, query, matched } = router.currentRoute;\n    if (obj === undefined) {\n      matched.forEach((m) => {\n        if (m.components && m.components.default && m.components.default.name) {\n          if (!['Layout', 'ParentView'].includes(m.components.default.name)) {\n            obj = { name: m.components.default.name, path: path, query: query };\n          }\n        }\n      });\n    }\n    return store.dispatch('tagsView/delCachedView', obj).then(() => {\n      const { path, query } = obj\n      router.replace({\n        path: '/redirect' + path,\n        query: query\n      })\n    })\n  },\n  // 关闭当前tab页签，打开新页签\n  closeOpenPage(obj) {\n    store.dispatch(\"tagsView/delView\", router.currentRoute);\n    if (obj !== undefined) {\n      return router.push(obj);\n    }\n  },\n  // 关闭指定tab页签\n  closePage(obj) {\n    if (obj === undefined) {\n      return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => {\n        return router.push(lastPath || '/');\n      });\n    }\n    return store.dispatch('tagsView/delView', obj);\n  },\n  // 关闭所有tab页签\n  closeAllPage() {\n    return store.dispatch('tagsView/delAllViews');\n  },\n  // 关闭左侧tab页签\n  closeLeftPage(obj) {\n    return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute);\n  },\n  // 关闭右侧tab页签\n  closeRightPage(obj) {\n    return store.dispatch('tagsView/delRightTags', obj || router.currentRoute);\n  },\n  // 关闭其他tab页签\n  closeOtherPage(obj) {\n    return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute);\n  },\n  // 添加tab页签\n  openPage(title, url, params) {\n    var obj = { path: url, meta: { title: title } }\n    store.dispatch('tagsView/addView', obj);\n    return router.push({ path: url, query: params });\n  },\n  // 修改tab页签\n  updatePage(obj) {\n    return store.dispatch('tagsView/updateVisitedView', obj);\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/router/index.js",
    "content": "import Vue from 'vue'\nimport Router from 'vue-router'\n\nVue.use(Router)\n\n/* Layout */\nimport Layout from '@/layout'\n\n/**\n * Note: 路由配置项\n *\n * hidden: true                     // 当设置 true 的时候该路由不会再侧边栏出现 如401，login等页面，或者如一些编辑页面/edit/1\n * alwaysShow: true                 // 当你一个路由下面的 children 声明的路由大于1个时，自动会变成嵌套的模式--如组件页面\n *                                  // 只有一个时，会将那个子路由当做根路由显示在侧边栏--如引导页面\n *                                  // 若你想不管路由下面的 children 声明的个数都显示你的根路由\n *                                  // 你可以设置 alwaysShow: true，这样它就会忽略之前定义的规则，一直显示根路由\n * redirect: noRedirect             // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击\n * name:'router-name'               // 设定路由的名字，一定要填写不然使用<keep-alive>时会出现各种问题\n * query: '{\"id\": 1, \"name\": \"ry\"}' // 访问路由的默认传递参数\n * roles: ['admin', 'common']       // 访问路由的角色权限\n * permissions: ['a:a:a', 'b:b:b']  // 访问路由的菜单权限\n * meta : {\n    noCache: true                   // 如果设置为true，则不会被 <keep-alive> 缓存(默认 false)\n    title: 'title'                  // 设置该路由在侧边栏和面包屑中展示的名字\n    icon: 'svg-name'                // 设置该路由的图标，对应路径src/assets/icons/svg\n    breadcrumb: false               // 如果设置为false，则不会在breadcrumb面包屑中显示\n    activeMenu: '/system/user'      // 当路由设置了该属性，则会高亮相对应的侧边栏。\n  }\n */\n\n// 公共路由\nexport const constantRoutes = [\n  {\n    path: '/redirect',\n    component: Layout,\n    hidden: true,\n    children: [\n      {\n        path: '/redirect/:path(.*)',\n        component: () => import('@/views/redirect')\n      }\n    ]\n  },\n  {\n    path: '/login',\n    component: () => import('@/views/login'),\n    hidden: true\n  },\n  {\n    path: '/register',\n    component: () => import('@/views/register'),\n    hidden: true\n  },\n  {\n    path: '/404',\n    component: () => import('@/views/error/404'),\n    hidden: true\n  },\n  {\n    path: '/401',\n    component: () => import('@/views/error/401'),\n    hidden: true\n  },\n  {\n    path: '',\n    component: Layout,\n    redirect: 'index',\n    children: [\n      {\n        path: 'index',\n        component: () => import('@/views/index'),\n        name: 'Index',\n        meta: { title: '首页', icon: 'dashboard', affix: true }\n      }\n    ]\n  },\n  {\n    path: '/user',\n    component: Layout,\n    hidden: true,\n    redirect: 'noredirect',\n    children: [\n      {\n        path: 'profile',\n        component: () => import('@/views/system/user/profile/index'),\n        name: 'Profile',\n        meta: { title: '个人中心', icon: 'user' }\n      }\n    ]\n  }\n]\n\n// 动态路由，基于用户权限动态去加载\nexport const dynamicRoutes = [\n  {\n    path: '/system/user-auth',\n    component: Layout,\n    hidden: true,\n    permissions: ['system:user:edit'],\n    children: [\n      {\n        path: 'role/:userId(\\\\d+)',\n        component: () => import('@/views/system/user/authRole'),\n        name: 'AuthRole',\n        meta: { title: '分配角色', activeMenu: '/system/user' }\n      }\n    ]\n  },\n  {\n    path: '/system/role-auth',\n    component: Layout,\n    hidden: true,\n    permissions: ['system:role:edit'],\n    children: [\n      {\n        path: 'user/:roleId(\\\\d+)',\n        component: () => import('@/views/system/role/authUser'),\n        name: 'AuthUser',\n        meta: { title: '分配用户', activeMenu: '/system/role' }\n      }\n    ]\n  },\n  {\n    path: '/system/dict-data',\n    component: Layout,\n    hidden: true,\n    permissions: ['system:dict:list'],\n    children: [\n      {\n        path: 'index/:dictId(\\\\d+)',\n        component: () => import('@/views/system/dict/data'),\n        name: 'Data',\n        meta: { title: '字典数据', activeMenu: '/system/dict' }\n      }\n    ]\n  },\n  {\n    path: '/monitor/job-log',\n    component: Layout,\n    hidden: true,\n    permissions: ['monitor:job:list'],\n    children: [\n      {\n        path: 'index',\n        component: () => import('@/views/monitor/job/log'),\n        name: 'JobLog',\n        meta: { title: '调度日志', activeMenu: '/monitor/job' }\n      }\n    ]\n  },\n  {\n    path: '/tool/gen-edit',\n    component: Layout,\n    hidden: true,\n    permissions: ['tool:gen:edit'],\n    children: [\n      {\n        path: 'index/:tableId(\\\\d+)',\n        component: () => import('@/views/tool/gen/editTable'),\n        name: 'GenEdit',\n        meta: { title: '修改生成配置', activeMenu: '/tool/gen' }\n      }\n    ]\n  }\n]\n\n// 防止连续点击多次路由报错\nlet routerPush = Router.prototype.push;\nRouter.prototype.push = function push(location) {\n  return routerPush.call(this, location).catch(err => err)\n}\n\nexport default new Router({\n  mode: 'history', // 去掉url中的#\n  scrollBehavior: () => ({ y: 0 }),\n  routes: constantRoutes\n})\n"
  },
  {
    "path": "vue_campus_admin/src/settings.js",
    "content": "module.exports = {\n  /**\n   * 侧边栏主题 深色主题theme-dark，浅色主题theme-light\n   */\n  sideTheme: 'theme-dark',\n\n  /**\n   * 是否系统布局配置\n   */\n  showSettings: false,\n\n  /**\n   * 是否显示顶部导航\n   */\n  topNav: false,\n\n  /**\n   * 是否显示 tagsView\n   */\n  tagsView: true,\n\n  /**\n   * 是否固定头部\n   */\n  fixedHeader: false,\n\n  /**\n   * 是否显示logo\n   */\n  sidebarLogo: true,\n\n  /**\n   * 是否显示动态标题\n   */\n  dynamicTitle: false,\n\n  /**\n   * @type {string | array} 'production' | ['production', 'development']\n   * @description Need show err logs component.\n   * The default is only used in the production env\n   * If you want to also use it in dev, you can pass ['production', 'development']\n   */\n  errorLog: 'production'\n}\n"
  },
  {
    "path": "vue_campus_admin/src/store/getters.js",
    "content": "const getters = {\n  sidebar: state => state.app.sidebar,\n  size: state => state.app.size,\n  device: state => state.app.device,\n  dict: state => state.dict.dict,\n  visitedViews: state => state.tagsView.visitedViews,\n  cachedViews: state => state.tagsView.cachedViews,\n  token: state => state.user.token,\n  avatar: state => state.user.avatar,\n  name: state => state.user.name,\n  introduction: state => state.user.introduction,\n  roles: state => state.user.roles,\n  permissions: state => state.user.permissions,\n  permission_routes: state => state.permission.routes,\n  topbarRouters:state => state.permission.topbarRouters,\n  defaultRoutes:state => state.permission.defaultRoutes,\n  sidebarRouters:state => state.permission.sidebarRouters,\n}\nexport default getters\n"
  },
  {
    "path": "vue_campus_admin/src/store/index.js",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport app from './modules/app'\nimport dict from './modules/dict'\nimport user from './modules/user'\nimport tagsView from './modules/tagsView'\nimport permission from './modules/permission'\nimport settings from './modules/settings'\nimport getters from './getters'\n\nVue.use(Vuex)\n\nconst store = new Vuex.Store({\n  modules: {\n    app,\n    dict,\n    user,\n    tagsView,\n    permission,\n    settings\n  },\n  getters\n})\n\nexport default store\n"
  },
  {
    "path": "vue_campus_admin/src/store/modules/app.js",
    "content": "import Cookies from 'js-cookie'\n\nconst state = {\n  sidebar: {\n    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,\n    withoutAnimation: false,\n    hide: false\n  },\n  device: 'desktop',\n  size: Cookies.get('size') || 'medium'\n}\n\nconst mutations = {\n  TOGGLE_SIDEBAR: state => {\n    if (state.sidebar.hide) {\n      return false;\n    }\n    state.sidebar.opened = !state.sidebar.opened\n    state.sidebar.withoutAnimation = false\n    if (state.sidebar.opened) {\n      Cookies.set('sidebarStatus', 1)\n    } else {\n      Cookies.set('sidebarStatus', 0)\n    }\n  },\n  CLOSE_SIDEBAR: (state, withoutAnimation) => {\n    Cookies.set('sidebarStatus', 0)\n    state.sidebar.opened = false\n    state.sidebar.withoutAnimation = withoutAnimation\n  },\n  TOGGLE_DEVICE: (state, device) => {\n    state.device = device\n  },\n  SET_SIZE: (state, size) => {\n    state.size = size\n    Cookies.set('size', size)\n  },\n  SET_SIDEBAR_HIDE: (state, status) => {\n    state.sidebar.hide = status\n  }\n}\n\nconst actions = {\n  toggleSideBar({ commit }) {\n    commit('TOGGLE_SIDEBAR')\n  },\n  closeSideBar({ commit }, { withoutAnimation }) {\n    commit('CLOSE_SIDEBAR', withoutAnimation)\n  },\n  toggleDevice({ commit }, device) {\n    commit('TOGGLE_DEVICE', device)\n  },\n  setSize({ commit }, size) {\n    commit('SET_SIZE', size)\n  },\n  toggleSideBarHide({ commit }, status) {\n    commit('SET_SIDEBAR_HIDE', status)\n  }\n}\n\nexport default {\n  namespaced: true,\n  state,\n  mutations,\n  actions\n}\n"
  },
  {
    "path": "vue_campus_admin/src/store/modules/dict.js",
    "content": "const state = {\n  dict: new Array()\n}\nconst mutations = {\n  SET_DICT: (state, { key, value }) => {\n    if (key !== null && key !== \"\") {\n      state.dict.push({\n        key: key,\n        value: value\n      })\n    }\n  },\n  REMOVE_DICT: (state, key) => {\n    try {\n      for (let i = 0; i < state.dict.length; i++) {\n        if (state.dict[i].key == key) {\n          state.dict.splice(i, i)\n          return true\n        }\n      }\n    } catch (e) {\n    }\n  },\n  CLEAN_DICT: (state) => {\n    state.dict = new Array()\n  }\n}\n\nconst actions = {\n  // 设置字典\n  setDict({ commit }, data) {\n    commit('SET_DICT', data)\n  },\n  // 删除字典\n  removeDict({ commit }, key) {\n    commit('REMOVE_DICT', key)\n  },\n  // 清空字典\n  cleanDict({ commit }) {\n    commit('CLEAN_DICT')\n  }\n}\n\nexport default {\n  namespaced: true,\n  state,\n  mutations,\n  actions\n}\n\n"
  },
  {
    "path": "vue_campus_admin/src/store/modules/permission.js",
    "content": "import auth from '@/plugins/auth'\nimport router, { constantRoutes, dynamicRoutes } from '@/router'\nimport { getRouters } from '@/api/menu'\nimport Layout from '@/layout/index'\nimport ParentView from '@/components/ParentView'\nimport InnerLink from '@/layout/components/InnerLink'\n\nconst permission = {\n  state: {\n    routes: [],\n    addRoutes: [],\n    defaultRoutes: [],\n    topbarRouters: [],\n    sidebarRouters: []\n  },\n  mutations: {\n    SET_ROUTES: (state, routes) => {\n      state.addRoutes = routes\n      state.routes = constantRoutes.concat(routes)\n    },\n    SET_DEFAULT_ROUTES: (state, routes) => {\n      state.defaultRoutes = constantRoutes.concat(routes)\n    },\n    SET_TOPBAR_ROUTES: (state, routes) => {\n      state.topbarRouters = routes\n    },\n    SET_SIDEBAR_ROUTERS: (state, routes) => {\n      state.sidebarRouters = routes\n    },\n  },\n  actions: {\n    // 生成路由\n    GenerateRoutes({ commit }) {\n      return new Promise(resolve => {\n        // 向后端请求路由数据\n        getRouters().then(res => {\n          const sdata = JSON.parse(JSON.stringify(res.data))\n          const rdata = JSON.parse(JSON.stringify(res.data))\n          const sidebarRoutes = filterAsyncRouter(sdata)\n          const rewriteRoutes = filterAsyncRouter(rdata, false, true)\n          const asyncRoutes = filterDynamicRoutes(dynamicRoutes);\n          rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })\n          router.addRoutes(asyncRoutes);\n          commit('SET_ROUTES', rewriteRoutes)\n          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))\n          commit('SET_DEFAULT_ROUTES', sidebarRoutes)\n          commit('SET_TOPBAR_ROUTES', sidebarRoutes)\n          resolve(rewriteRoutes)\n        })\n      })\n    }\n  }\n}\n\n// 遍历后台传来的路由字符串，转换为组件对象\nfunction filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {\n  return asyncRouterMap.filter(route => {\n    if (type && route.children) {\n      route.children = filterChildren(route.children)\n    }\n    if (route.component) {\n      // Layout ParentView 组件特殊处理\n      if (route.component === 'Layout') {\n        route.component = Layout\n      } else if (route.component === 'ParentView') {\n        route.component = ParentView\n      } else if (route.component === 'InnerLink') {\n        route.component = InnerLink\n      } else {\n        route.component = loadView(route.component)\n      }\n    }\n    if (route.children != null && route.children && route.children.length) {\n      route.children = filterAsyncRouter(route.children, route, type)\n    } else {\n      delete route['children']\n      delete route['redirect']\n    }\n    return true\n  })\n}\n\nfunction filterChildren(childrenMap, lastRouter = false) {\n  var children = []\n  childrenMap.forEach((el, index) => {\n    if (el.children && el.children.length) {\n      if (el.component === 'ParentView' && !lastRouter) {\n        el.children.forEach(c => {\n          c.path = el.path + '/' + c.path\n          if (c.children && c.children.length) {\n            children = children.concat(filterChildren(c.children, c))\n            return\n          }\n          children.push(c)\n        })\n        return\n      }\n    }\n    if (lastRouter) {\n      el.path = lastRouter.path + '/' + el.path\n    }\n    children = children.concat(el)\n  })\n  return children\n}\n\n// 动态路由遍历，验证是否具备权限\nexport function filterDynamicRoutes(routes) {\n  const res = []\n  routes.forEach(route => {\n    if (route.permissions) {\n      if (auth.hasPermiOr(route.permissions)) {\n        res.push(route)\n      }\n    } else if (route.roles) {\n      if (auth.hasRoleOr(route.roles)) {\n        res.push(route)\n      }\n    }\n  })\n  return res\n}\n\nexport const loadView = (view) => {\n  if (process.env.NODE_ENV === 'development') {\n    return (resolve) => require([`@/views/${view}`], resolve)\n  } else {\n    // 使用 import 实现生产环境的路由懒加载\n    return () => import(`@/views/${view}`)\n  }\n}\n\nexport default permission\n"
  },
  {
    "path": "vue_campus_admin/src/store/modules/settings.js",
    "content": "import defaultSettings from '@/settings'\n\nconst { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings\n\nconst storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''\nconst state = {\n  title: '',\n  theme: storageSetting.theme || '#409EFF',\n  sideTheme: storageSetting.sideTheme || sideTheme,\n  showSettings: showSettings,\n  topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,\n  tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,\n  fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,\n  sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,\n  dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle\n}\nconst mutations = {\n  CHANGE_SETTING: (state, { key, value }) => {\n    if (state.hasOwnProperty(key)) {\n      state[key] = value\n    }\n  }\n}\n\nconst actions = {\n  // 修改布局设置\n  changeSetting({ commit }, data) {\n    commit('CHANGE_SETTING', data)\n  },\n  // 设置网页标题\n  setTitle({ commit }, title) {\n    state.title = title\n  }\n}\n\nexport default {\n  namespaced: true,\n  state,\n  mutations,\n  actions\n}\n\n"
  },
  {
    "path": "vue_campus_admin/src/store/modules/tagsView.js",
    "content": "const state = {\n  visitedViews: [],\n  cachedViews: [],\n  iframeViews: []\n}\n\nconst mutations = {\n  ADD_IFRAME_VIEW: (state, view) => {\n    if (state.iframeViews.some(v => v.path === view.path)) return\n    state.iframeViews.push(\n      Object.assign({}, view, {\n        title: view.meta.title || 'no-name'\n      })\n    )\n  },\n  ADD_VISITED_VIEW: (state, view) => {\n    if (state.visitedViews.some(v => v.path === view.path)) return\n    state.visitedViews.push(\n      Object.assign({}, view, {\n        title: view.meta.title || 'no-name'\n      })\n    )\n  },\n  ADD_CACHED_VIEW: (state, view) => {\n    if (state.cachedViews.includes(view.name)) return\n    if (view.meta && !view.meta.noCache) {\n      state.cachedViews.push(view.name)\n    }\n  },\n  DEL_VISITED_VIEW: (state, view) => {\n    for (const [i, v] of state.visitedViews.entries()) {\n      if (v.path === view.path) {\n        state.visitedViews.splice(i, 1)\n        break\n      }\n    }\n    state.iframeViews = state.iframeViews.filter(item => item.path !== view.path)\n  },\n  DEL_IFRAME_VIEW: (state, view) => {\n    state.iframeViews = state.iframeViews.filter(item => item.path !== view.path)\n  },\n  DEL_CACHED_VIEW: (state, view) => {\n    const index = state.cachedViews.indexOf(view.name)\n    index > -1 && state.cachedViews.splice(index, 1)\n  },\n\n  DEL_OTHERS_VISITED_VIEWS: (state, view) => {\n    state.visitedViews = state.visitedViews.filter(v => {\n      return v.meta.affix || v.path === view.path\n    })\n    state.iframeViews = state.iframeViews.filter(item => item.path === view.path)\n  },\n  DEL_OTHERS_CACHED_VIEWS: (state, view) => {\n    const index = state.cachedViews.indexOf(view.name)\n    if (index > -1) {\n      state.cachedViews = state.cachedViews.slice(index, index + 1)\n    } else {\n      state.cachedViews = []\n    }\n  },\n  DEL_ALL_VISITED_VIEWS: state => {\n    // keep affix tags\n    const affixTags = state.visitedViews.filter(tag => tag.meta.affix)\n    state.visitedViews = affixTags\n    state.iframeViews = []\n  },\n  DEL_ALL_CACHED_VIEWS: state => {\n    state.cachedViews = []\n  },\n  UPDATE_VISITED_VIEW: (state, view) => {\n    for (let v of state.visitedViews) {\n      if (v.path === view.path) {\n        v = Object.assign(v, view)\n        break\n      }\n    }\n  },\n  DEL_RIGHT_VIEWS: (state, view) => {\n    const index = state.visitedViews.findIndex(v => v.path === view.path)\n    if (index === -1) {\n      return\n    }\n    state.visitedViews = state.visitedViews.filter((item, idx) => {\n      if (idx <= index || (item.meta && item.meta.affix)) {\n        return true\n      }\n      const i = state.cachedViews.indexOf(item.name)\n      if (i > -1) {\n        state.cachedViews.splice(i, 1)\n      }\n      if(item.meta.link) {\n        const fi = state.iframeViews.findIndex(v => v.path === item.path)\n        state.iframeViews.splice(fi, 1)\n      }\n      return false\n    })\n  },\n  DEL_LEFT_VIEWS: (state, view) => {\n    const index = state.visitedViews.findIndex(v => v.path === view.path)\n    if (index === -1) {\n      return\n    }\n    state.visitedViews = state.visitedViews.filter((item, idx) => {\n      if (idx >= index || (item.meta && item.meta.affix)) {\n        return true\n      }\n      const i = state.cachedViews.indexOf(item.name)\n      if (i > -1) {\n        state.cachedViews.splice(i, 1)\n      }\n      if(item.meta.link) {\n        const fi = state.iframeViews.findIndex(v => v.path === item.path)\n        state.iframeViews.splice(fi, 1)\n      }\n      return false\n    })\n  }\n}\n\nconst actions = {\n  addView({ dispatch }, view) {\n    dispatch('addVisitedView', view)\n    dispatch('addCachedView', view)\n  },\n  addIframeView({ commit }, view) {\n    commit('ADD_IFRAME_VIEW', view)\n  },\n  addVisitedView({ commit }, view) {\n    commit('ADD_VISITED_VIEW', view)\n  },\n  addCachedView({ commit }, view) {\n    commit('ADD_CACHED_VIEW', view)\n  },\n  delView({ dispatch, state }, view) {\n    return new Promise(resolve => {\n      dispatch('delVisitedView', view)\n      dispatch('delCachedView', view)\n      resolve({\n        visitedViews: [...state.visitedViews],\n        cachedViews: [...state.cachedViews]\n      })\n    })\n  },\n  delVisitedView({ commit, state }, view) {\n    return new Promise(resolve => {\n      commit('DEL_VISITED_VIEW', view)\n      resolve([...state.visitedViews])\n    })\n  },\n  delIframeView({ commit, state }, view) {\n    return new Promise(resolve => {\n      commit('DEL_IFRAME_VIEW', view)\n      resolve([...state.iframeViews])\n    })\n  },\n  delCachedView({ commit, state }, view) {\n    return new Promise(resolve => {\n      commit('DEL_CACHED_VIEW', view)\n      resolve([...state.cachedViews])\n    })\n  },\n  delOthersViews({ dispatch, state }, view) {\n    return new Promise(resolve => {\n      dispatch('delOthersVisitedViews', view)\n      dispatch('delOthersCachedViews', view)\n      resolve({\n        visitedViews: [...state.visitedViews],\n        cachedViews: [...state.cachedViews]\n      })\n    })\n  },\n  delOthersVisitedViews({ commit, state }, view) {\n    return new Promise(resolve => {\n      commit('DEL_OTHERS_VISITED_VIEWS', view)\n      resolve([...state.visitedViews])\n    })\n  },\n  delOthersCachedViews({ commit, state }, view) {\n    return new Promise(resolve => {\n      commit('DEL_OTHERS_CACHED_VIEWS', view)\n      resolve([...state.cachedViews])\n    })\n  },\n  delAllViews({ dispatch, state }, view) {\n    return new Promise(resolve => {\n      dispatch('delAllVisitedViews', view)\n      dispatch('delAllCachedViews', view)\n      resolve({\n        visitedViews: [...state.visitedViews],\n        cachedViews: [...state.cachedViews]\n      })\n    })\n  },\n  delAllVisitedViews({ commit, state }) {\n    return new Promise(resolve => {\n      commit('DEL_ALL_VISITED_VIEWS')\n      resolve([...state.visitedViews])\n    })\n  },\n  delAllCachedViews({ commit, state }) {\n    return new Promise(resolve => {\n      commit('DEL_ALL_CACHED_VIEWS')\n      resolve([...state.cachedViews])\n    })\n  },\n  updateVisitedView({ commit }, view) {\n    commit('UPDATE_VISITED_VIEW', view)\n  },\n  delRightTags({ commit }, view) {\n    return new Promise(resolve => {\n      commit('DEL_RIGHT_VIEWS', view)\n      resolve([...state.visitedViews])\n    })\n  },\n  delLeftTags({ commit }, view) {\n    return new Promise(resolve => {\n      commit('DEL_LEFT_VIEWS', view)\n      resolve([...state.visitedViews])\n    })\n  },\n}\n\nexport default {\n  namespaced: true,\n  state,\n  mutations,\n  actions\n}\n"
  },
  {
    "path": "vue_campus_admin/src/store/modules/user.js",
    "content": "import { login, logout, getInfo } from '@/api/login'\nimport { getToken, setToken, removeToken } from '@/utils/auth'\n\nconst user = {\n  state: {\n    token: getToken(),\n    name: '',\n    avatar: '',\n    roles: [],\n    permissions: []\n  },\n\n  mutations: {\n    SET_TOKEN: (state, token) => {\n      state.token = token\n    },\n    SET_NAME: (state, name) => {\n      state.name = name\n    },\n    SET_AVATAR: (state, avatar) => {\n      state.avatar = avatar\n    },\n    SET_ROLES: (state, roles) => {\n      state.roles = roles\n    },\n    SET_PERMISSIONS: (state, permissions) => {\n      state.permissions = permissions\n    }\n  },\n\n  actions: {\n    // 登录\n    Login({ commit }, userInfo) {\n      const username = userInfo.username.trim()\n      const password = userInfo.password\n      const code = userInfo.code\n      const uuid = userInfo.uuid\n      return new Promise((resolve, reject) => {\n        login(username, password, code, uuid).then(res => {\n          setToken(res.token)\n          commit('SET_TOKEN', res.token)\n          resolve()\n        }).catch(error => {\n          reject(error)\n        })\n      })\n    },\n\n    // 获取用户信息\n    GetInfo({ commit, state }) {\n      return new Promise((resolve, reject) => {\n        getInfo().then(res => {\n          const user = res.user\n          let avatar = user.avatar;\n          if (user.avatar.trim().toLowerCase().substring(0, 4) != \"http\") {\n            avatar = (user.avatar == \"\" || user.avatar == null) ? require(\"@/assets/images/profile.jpg\") : process.env.VUE_APP_BASE_API + user.avatar;\n          }\n\n          if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组\n            commit('SET_ROLES', res.roles)\n            commit('SET_PERMISSIONS', res.permissions)\n          } else {\n            commit('SET_ROLES', ['ROLE_DEFAULT'])\n          }\n          commit('SET_NAME', user.userName)\n          commit('SET_AVATAR', avatar)\n          resolve(res)\n        }).catch(error => {\n          reject(error)\n        })\n      })\n    },\n\n    // 退出系统\n    LogOut({ commit, state }) {\n      return new Promise((resolve, reject) => {\n        logout(state.token).then(() => {\n          commit('SET_TOKEN', '')\n          commit('SET_ROLES', [])\n          commit('SET_PERMISSIONS', [])\n          removeToken()\n          resolve()\n        }).catch(error => {\n          reject(error)\n        })\n      })\n    },\n\n    // 前端 登出\n    FedLogOut({ commit }) {\n      return new Promise(resolve => {\n        commit('SET_TOKEN', '')\n        removeToken()\n        resolve()\n      })\n    }\n  }\n}\n\nexport default user\n"
  },
  {
    "path": "vue_campus_admin/src/utils/auth.js",
    "content": "import Cookies from 'js-cookie'\n\nconst TokenKey = 'Admin-Token'\n\nexport function getToken() {\n  return Cookies.get(TokenKey)\n}\n\nexport function setToken(token) {\n  return Cookies.set(TokenKey, token)\n}\n\nexport function removeToken() {\n  return Cookies.remove(TokenKey)\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/crypto.js",
    "content": "//crypto.js文件内容\nimport CryptoJS from 'crypto-js'\nexport default { // 加密\n    /**\n     * @description: 加密\n     * @param {*} word\n     * @param {*} keyStr\n     */\n    set(word, keyStr) {\n        keyStr = keyStr || 'abcdef0123456789' // 16位的密钥，自己定义，和下面的密钥要相同\n        var srcs = CryptoJS.enc.Utf8.parse(word) //  字符串到数组转换，解析明文\n        var key = CryptoJS.enc.Utf8.parse(keyStr) //  字符串到数组转换，解析秘钥\n        // mode:加密方式；padding:填充方式；iv便宜向量（可选）\n        var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })\n        return encrypted.toString() // 加密后的结果是对象，要转换为文本\n    },\n\n    /**\n     * @description: 解密\n     * @param {*} word\n     * @param {*} keyStr\n     */\n    get(word, keyStr) {\n        keyStr = keyStr || 'abcdef0123456789'\n        var key = CryptoJS.enc.Utf8.parse(keyStr) //  字符串到数组转换\n        var decrypt = CryptoJS.AES.decrypt(word, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })\n        return CryptoJS.enc.Utf8.stringify(decrypt).toString() //  数组到字符串转换\n    }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/dict/Dict.js",
    "content": "import Vue from 'vue'\nimport { mergeRecursive } from \"@/utils/ruoyi\";\nimport DictMeta from './DictMeta'\nimport DictData from './DictData'\n\nconst DEFAULT_DICT_OPTIONS = {\n  types: [],\n}\n\n/**\n * @classdesc 字典\n * @property {Object} label 标签对象，内部属性名为字典类型名称\n * @property {Object} dict 字段数组，内部属性名为字典类型名称\n * @property {Array.<DictMeta>} _dictMetas 字典元数据数组\n */\nexport default class Dict {\n  constructor() {\n    this.owner = null\n    this.label = {}\n    this.type = {}\n  }\n\n  init(options) {\n    if (options instanceof Array) {\n      options = { types: options }\n    }\n    const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options)\n    if (opts.types === undefined) {\n      throw new Error('need dict types')\n    }\n    const ps = []\n    this._dictMetas = opts.types.map(t => DictMeta.parse(t))\n    this._dictMetas.forEach(dictMeta => {\n      const type = dictMeta.type\n      Vue.set(this.label, type, {})\n      Vue.set(this.type, type, [])\n      if (dictMeta.lazy) {\n        return\n      }\n      ps.push(loadDict(this, dictMeta))\n    })\n    return Promise.all(ps)\n  }\n\n  /**\n   * 重新加载字典\n   * @param {String} type 字典类型\n   */\n  reloadDict(type) {\n    const dictMeta = this._dictMetas.find(e => e.type === type)\n    if (dictMeta === undefined) {\n      return Promise.reject(`the dict meta of ${type} was not found`)\n    }\n    return loadDict(this, dictMeta)\n  }\n}\n\n/**\n * 加载字典\n * @param {Dict} dict 字典\n * @param {DictMeta} dictMeta 字典元数据\n * @returns {Promise}\n */\nfunction loadDict(dict, dictMeta) {\n  return dictMeta.request(dictMeta)\n    .then(response => {\n      const type = dictMeta.type\n      let dicts = dictMeta.responseConverter(response, dictMeta)\n      if (!(dicts instanceof Array)) {\n        console.error('the return of responseConverter must be Array.<DictData>')\n        dicts = []\n      } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) {\n        console.error('the type of elements in dicts must be DictData')\n        dicts = []\n      }\n      dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts)\n      dicts.forEach(d => {\n        Vue.set(dict.label[type], d.value, d.label)\n      })\n      return dicts\n    })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/dict/DictConverter.js",
    "content": "import DictOptions from './DictOptions'\nimport DictData from './DictData'\n\nexport default function(dict, dictMeta) {\n  const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS)\n  const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS)\n  return new DictData(dict[label], dict[value], dict)\n}\n\n/**\n * 确定字典字段\n * @param {DictData} dict\n * @param  {...String} fields\n */\nfunction determineDictField(dict, ...fields) {\n  return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f))\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/dict/DictData.js",
    "content": "/**\n * @classdesc 字典数据\n * @property {String} label 标签\n * @property {*} value 标签\n * @property {Object} raw 原始数据\n */\nexport default class DictData {\n  constructor(label, value, raw) {\n    this.label = label\n    this.value = value\n    this.raw = raw\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/dict/DictMeta.js",
    "content": "import { mergeRecursive } from \"@/utils/ruoyi\";\nimport DictOptions from './DictOptions'\n\n/**\n * @classdesc 字典元数据\n * @property {String} type 类型\n * @property {Function} request 请求\n * @property {String} label 标签字段\n * @property {String} value 值字段\n */\nexport default class DictMeta {\n  constructor(options) {\n    this.type = options.type\n    this.request = options.request\n    this.responseConverter = options.responseConverter\n    this.labelField = options.labelField\n    this.valueField = options.valueField\n    this.lazy = options.lazy === true\n  }\n}\n\n\n/**\n * 解析字典元数据\n * @param {Object} options\n * @returns {DictMeta}\n */\nDictMeta.parse= function(options) {\n  let opts = null\n  if (typeof options === 'string') {\n    opts = DictOptions.metas[options] || {}\n    opts.type = options\n  } else if (typeof options === 'object') {\n    opts = options\n  }\n  opts = mergeRecursive(DictOptions.metas['*'], opts)\n  return new DictMeta(opts)\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/dict/DictOptions.js",
    "content": "import { mergeRecursive } from \"@/utils/ruoyi\";\nimport dictConverter from './DictConverter'\n\nexport const options = {\n  metas: {\n    '*': {\n      /**\n       * 字典请求，方法签名为function(dictMeta: DictMeta): Promise\n       */\n      request: (dictMeta) => {\n        console.log(`load dict ${dictMeta.type}`)\n        return Promise.resolve([])\n      },\n      /**\n       * 字典响应数据转换器，方法签名为function(response: Object, dictMeta: DictMeta): DictData\n       */\n      responseConverter,\n      labelField: 'label',\n      valueField: 'value',\n    },\n  },\n  /**\n   * 默认标签字段\n   */\n  DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'],\n  /**\n   * 默认值字段\n   */\n  DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'],\n}\n\n/**\n * 映射字典\n * @param {Object} response 字典数据\n * @param {DictMeta} dictMeta 字典元数据\n * @returns {DictData}\n */\nfunction responseConverter(response, dictMeta) {\n  const dicts = response.content instanceof Array ? response.content : response\n  if (dicts === undefined) {\n    console.warn(`no dict data of \"${dictMeta.type}\" found in the response`)\n    return []\n  }\n  return dicts.map(d => dictConverter(d, dictMeta))\n}\n\nexport function mergeOptions(src) {\n  mergeRecursive(options, src)\n}\n\nexport default options\n"
  },
  {
    "path": "vue_campus_admin/src/utils/dict/index.js",
    "content": "import Dict from './Dict'\nimport { mergeOptions } from './DictOptions'\n\nexport default function(Vue, options) {\n  mergeOptions(options)\n  Vue.mixin({\n    data() {\n      if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) {\n        return {}\n      }\n      const dict = new Dict()\n      dict.owner = this\n      return {\n        dict\n      }\n    },\n    created() {\n      if (!(this.dict instanceof Dict)) {\n        return\n      }\n      options.onCreated && options.onCreated(this.dict)\n      this.dict.init(this.$options.dicts).then(() => {\n        options.onReady && options.onReady(this.dict)\n        this.$nextTick(() => {\n          this.$emit('dictReady', this.dict)\n          if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) {\n            this.$options.methods.onDictReady.call(this, this.dict)\n          }\n        })\n      })\n    },\n  })\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/errorCode.js",
    "content": "export default {\n  '401': '认证失败，无法访问系统资源',\n  '403': '当前操作没有权限',\n  '404': '访问资源不存在',\n  'default': '系统未知错误，请反馈给管理员'\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/generator/config.js",
    "content": "export const formConf = {\n  formRef: 'elForm',\n  formModel: 'formData',\n  size: 'medium',\n  labelPosition: 'right',\n  labelWidth: 100,\n  formRules: 'rules',\n  gutter: 15,\n  disabled: false,\n  span: 24,\n  formBtns: true\n}\n\nexport const inputComponents = [\n  {\n    label: '单行文本',\n    tag: 'el-input',\n    tagIcon: 'input',\n    placeholder: '请输入',\n    defaultValue: undefined,\n    span: 24,\n    labelWidth: null,\n    style: { width: '100%' },\n    clearable: true,\n    prepend: '',\n    append: '',\n    'prefix-icon': '',\n    'suffix-icon': '',\n    maxlength: null,\n    'show-word-limit': false,\n    readonly: false,\n    disabled: false,\n    required: true,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/input'\n  },\n  {\n    label: '多行文本',\n    tag: 'el-input',\n    tagIcon: 'textarea',\n    type: 'textarea',\n    placeholder: '请输入',\n    defaultValue: undefined,\n    span: 24,\n    labelWidth: null,\n    autosize: {\n      minRows: 4,\n      maxRows: 4\n    },\n    style: { width: '100%' },\n    maxlength: null,\n    'show-word-limit': false,\n    readonly: false,\n    disabled: false,\n    required: true,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/input'\n  },\n  {\n    label: '密码',\n    tag: 'el-input',\n    tagIcon: 'password',\n    placeholder: '请输入',\n    defaultValue: undefined,\n    span: 24,\n    'show-password': true,\n    labelWidth: null,\n    style: { width: '100%' },\n    clearable: true,\n    prepend: '',\n    append: '',\n    'prefix-icon': '',\n    'suffix-icon': '',\n    maxlength: null,\n    'show-word-limit': false,\n    readonly: false,\n    disabled: false,\n    required: true,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/input'\n  },\n  {\n    label: '计数器',\n    tag: 'el-input-number',\n    tagIcon: 'number',\n    placeholder: '',\n    defaultValue: undefined,\n    span: 24,\n    labelWidth: null,\n    min: undefined,\n    max: undefined,\n    step: undefined,\n    'step-strictly': false,\n    precision: undefined,\n    'controls-position': '',\n    disabled: false,\n    required: true,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/input-number'\n  }\n]\n\nexport const selectComponents = [\n  {\n    label: '下拉选择',\n    tag: 'el-select',\n    tagIcon: 'select',\n    placeholder: '请选择',\n    defaultValue: undefined,\n    span: 24,\n    labelWidth: null,\n    style: { width: '100%' },\n    clearable: true,\n    disabled: false,\n    required: true,\n    filterable: false,\n    multiple: false,\n    options: [{\n      label: '选项一',\n      value: 1\n    }, {\n      label: '选项二',\n      value: 2\n    }],\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/select'\n  },\n  {\n    label: '级联选择',\n    tag: 'el-cascader',\n    tagIcon: 'cascader',\n    placeholder: '请选择',\n    defaultValue: [],\n    span: 24,\n    labelWidth: null,\n    style: { width: '100%' },\n    props: {\n      props: {\n        multiple: false\n      }\n    },\n    'show-all-levels': true,\n    disabled: false,\n    clearable: true,\n    filterable: false,\n    required: true,\n    options: [{\n      id: 1,\n      value: 1,\n      label: '选项1',\n      children: [{\n        id: 2,\n        value: 2,\n        label: '选项1-1'\n      }]\n    }],\n    dataType: 'dynamic',\n    labelKey: 'label',\n    valueKey: 'value',\n    childrenKey: 'children',\n    separator: '/',\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/cascader'\n  },\n  {\n    label: '单选框组',\n    tag: 'el-radio-group',\n    tagIcon: 'radio',\n    defaultValue: undefined,\n    span: 24,\n    labelWidth: null,\n    style: {},\n    optionType: 'default',\n    border: false,\n    size: 'medium',\n    disabled: false,\n    required: true,\n    options: [{\n      label: '选项一',\n      value: 1\n    }, {\n      label: '选项二',\n      value: 2\n    }],\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/radio'\n  },\n  {\n    label: '多选框组',\n    tag: 'el-checkbox-group',\n    tagIcon: 'checkbox',\n    defaultValue: [],\n    span: 24,\n    labelWidth: null,\n    style: {},\n    optionType: 'default',\n    border: false,\n    size: 'medium',\n    disabled: false,\n    required: true,\n    options: [{\n      label: '选项一',\n      value: 1\n    }, {\n      label: '选项二',\n      value: 2\n    }],\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/checkbox'\n  },\n  {\n    label: '开关',\n    tag: 'el-switch',\n    tagIcon: 'switch',\n    defaultValue: false,\n    span: 24,\n    labelWidth: null,\n    style: {},\n    disabled: false,\n    required: true,\n    'active-text': '',\n    'inactive-text': '',\n    'active-color': null,\n    'inactive-color': null,\n    'active-value': true,\n    'inactive-value': false,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/switch'\n  },\n  {\n    label: '滑块',\n    tag: 'el-slider',\n    tagIcon: 'slider',\n    defaultValue: null,\n    span: 24,\n    labelWidth: null,\n    disabled: false,\n    required: true,\n    min: 0,\n    max: 100,\n    step: 1,\n    'show-stops': false,\n    range: false,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/slider'\n  },\n  {\n    label: '时间选择',\n    tag: 'el-time-picker',\n    tagIcon: 'time',\n    placeholder: '请选择',\n    defaultValue: null,\n    span: 24,\n    labelWidth: null,\n    style: { width: '100%' },\n    disabled: false,\n    clearable: true,\n    required: true,\n    'picker-options': {\n      selectableRange: '00:00:00-23:59:59'\n    },\n    format: 'HH:mm:ss',\n    'value-format': 'HH:mm:ss',\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'\n  },\n  {\n    label: '时间范围',\n    tag: 'el-time-picker',\n    tagIcon: 'time-range',\n    defaultValue: null,\n    span: 24,\n    labelWidth: null,\n    style: { width: '100%' },\n    disabled: false,\n    clearable: true,\n    required: true,\n    'is-range': true,\n    'range-separator': '至',\n    'start-placeholder': '开始时间',\n    'end-placeholder': '结束时间',\n    format: 'HH:mm:ss',\n    'value-format': 'HH:mm:ss',\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'\n  },\n  {\n    label: '日期选择',\n    tag: 'el-date-picker',\n    tagIcon: 'date',\n    placeholder: '请选择',\n    defaultValue: null,\n    type: 'date',\n    span: 24,\n    labelWidth: null,\n    style: { width: '100%' },\n    disabled: false,\n    clearable: true,\n    required: true,\n    format: 'yyyy-MM-dd',\n    'value-format': 'yyyy-MM-dd',\n    readonly: false,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'\n  },\n  {\n    label: '日期范围',\n    tag: 'el-date-picker',\n    tagIcon: 'date-range',\n    defaultValue: null,\n    span: 24,\n    labelWidth: null,\n    style: { width: '100%' },\n    type: 'daterange',\n    'range-separator': '至',\n    'start-placeholder': '开始日期',\n    'end-placeholder': '结束日期',\n    disabled: false,\n    clearable: true,\n    required: true,\n    format: 'yyyy-MM-dd',\n    'value-format': 'yyyy-MM-dd',\n    readonly: false,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'\n  },\n  {\n    label: '评分',\n    tag: 'el-rate',\n    tagIcon: 'rate',\n    defaultValue: 0,\n    span: 24,\n    labelWidth: null,\n    style: {},\n    max: 5,\n    'allow-half': false,\n    'show-text': false,\n    'show-score': false,\n    disabled: false,\n    required: true,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/rate'\n  },\n  {\n    label: '颜色选择',\n    tag: 'el-color-picker',\n    tagIcon: 'color',\n    defaultValue: null,\n    labelWidth: null,\n    'show-alpha': false,\n    'color-format': '',\n    disabled: false,\n    required: true,\n    size: 'medium',\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/color-picker'\n  },\n  {\n    label: '上传',\n    tag: 'el-upload',\n    tagIcon: 'upload',\n    action: 'https://jsonplaceholder.typicode.com/posts/',\n    defaultValue: null,\n    labelWidth: null,\n    disabled: false,\n    required: true,\n    accept: '',\n    name: 'file',\n    'auto-upload': true,\n    showTip: false,\n    buttonText: '点击上传',\n    fileSize: 2,\n    sizeUnit: 'MB',\n    'list-type': 'text',\n    multiple: false,\n    regList: [],\n    changeTag: true,\n    document: 'https://element.eleme.cn/#/zh-CN/component/upload'\n  }\n]\n\nexport const layoutComponents = [\n  {\n    layout: 'rowFormItem',\n    tagIcon: 'row',\n    type: 'default',\n    justify: 'start',\n    align: 'top',\n    label: '行容器',\n    layoutTree: true,\n    children: [],\n    document: 'https://element.eleme.cn/#/zh-CN/component/layout'\n  },\n  {\n    layout: 'colFormItem',\n    label: '按钮',\n    changeTag: true,\n    labelWidth: null,\n    tag: 'el-button',\n    tagIcon: 'button',\n    span: 24,\n    default: '主要按钮',\n    type: 'primary',\n    icon: 'el-icon-search',\n    size: 'medium',\n    disabled: false,\n    document: 'https://element.eleme.cn/#/zh-CN/component/button'\n  }\n]\n\n// 组件rule的触发方式，无触发方式的组件不生成rule\nexport const trigger = {\n  'el-input': 'blur',\n  'el-input-number': 'blur',\n  'el-select': 'change',\n  'el-radio-group': 'change',\n  'el-checkbox-group': 'change',\n  'el-cascader': 'change',\n  'el-time-picker': 'change',\n  'el-date-picker': 'change',\n  'el-rate': 'change'\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/generator/css.js",
    "content": "const styles = {\n  'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}',\n  'el-upload': '.el-upload__tip{line-height: 1.2;}'\n}\n\nfunction addCss(cssList, el) {\n  const css = styles[el.tag]\n  css && cssList.indexOf(css) === -1 && cssList.push(css)\n  if (el.children) {\n    el.children.forEach(el2 => addCss(cssList, el2))\n  }\n}\n\nexport function makeUpCss(conf) {\n  const cssList = []\n  conf.fields.forEach(el => addCss(cssList, el))\n  return cssList.join('\\n')\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/generator/drawingDefault.js",
    "content": "export default [\n  {\n    layout: 'colFormItem',\n    tagIcon: 'input',\n    label: '手机号',\n    vModel: 'mobile',\n    formId: 6,\n    tag: 'el-input',\n    placeholder: '请输入手机号',\n    defaultValue: '',\n    span: 24,\n    style: { width: '100%' },\n    clearable: true,\n    prepend: '',\n    append: '',\n    'prefix-icon': 'el-icon-mobile',\n    'suffix-icon': '',\n    maxlength: 11,\n    'show-word-limit': true,\n    readonly: false,\n    disabled: false,\n    required: true,\n    changeTag: true,\n    regList: [{\n      pattern: '/^1(3|4|5|7|8|9)\\\\d{9}$/',\n      message: '手机号格式错误'\n    }]\n  }\n]\n"
  },
  {
    "path": "vue_campus_admin/src/utils/generator/html.js",
    "content": "/* eslint-disable max-len */\nimport { trigger } from './config'\n\nlet confGlobal\nlet someSpanIsNot24\n\nexport function dialogWrapper(str) {\n  return `<el-dialog v-bind=\"$attrs\" v-on=\"$listeners\" @open=\"onOpen\" @close=\"onClose\" title=\"Dialog Title\">\n    ${str}\n    <div slot=\"footer\">\n      <el-button @click=\"close\">取消</el-button>\n      <el-button type=\"primary\" @click=\"handleConfirm\">确定</el-button>\n    </div>\n  </el-dialog>`\n}\n\nexport function vueTemplate(str) {\n  return `<template>\n    <div>\n      ${str}\n    </div>\n  </template>`\n}\n\nexport function vueScript(str) {\n  return `<script>\n    ${str}\n  </script>`\n}\n\nexport function cssStyle(cssStr) {\n  return `<style>\n    ${cssStr}\n  </style>`\n}\n\nfunction buildFormTemplate(conf, child, type) {\n  let labelPosition = ''\n  if (conf.labelPosition !== 'right') {\n    labelPosition = `label-position=\"${conf.labelPosition}\"`\n  }\n  const disabled = conf.disabled ? `:disabled=\"${conf.disabled}\"` : ''\n  let str = `<el-form ref=\"${conf.formRef}\" :model=\"${conf.formModel}\" :rules=\"${conf.formRules}\" size=\"${conf.size}\" ${disabled} label-width=\"${conf.labelWidth}px\" ${labelPosition}>\n      ${child}\n      ${buildFromBtns(conf, type)}\n    </el-form>`\n  if (someSpanIsNot24) {\n    str = `<el-row :gutter=\"${conf.gutter}\">\n        ${str}\n      </el-row>`\n  }\n  return str\n}\n\nfunction buildFromBtns(conf, type) {\n  let str = ''\n  if (conf.formBtns && type === 'file') {\n    str = `<el-form-item size=\"large\">\n          <el-button type=\"primary\" @click=\"submitForm\">提交</el-button>\n          <el-button @click=\"resetForm\">重置</el-button>\n        </el-form-item>`\n    if (someSpanIsNot24) {\n      str = `<el-col :span=\"24\">\n          ${str}\n        </el-col>`\n    }\n  }\n  return str\n}\n\n// span不为24的用el-col包裹\nfunction colWrapper(element, str) {\n  if (someSpanIsNot24 || element.span !== 24) {\n    return `<el-col :span=\"${element.span}\">\n      ${str}\n    </el-col>`\n  }\n  return str\n}\n\nconst layouts = {\n  colFormItem(element) {\n    let labelWidth = ''\n    if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) {\n      labelWidth = `label-width=\"${element.labelWidth}px\"`\n    }\n    const required = !trigger[element.tag] && element.required ? 'required' : ''\n    const tagDom = tags[element.tag] ? tags[element.tag](element) : null\n    let str = `<el-form-item ${labelWidth} label=\"${element.label}\" prop=\"${element.vModel}\" ${required}>\n        ${tagDom}\n      </el-form-item>`\n    str = colWrapper(element, str)\n    return str\n  },\n  rowFormItem(element) {\n    const type = element.type === 'default' ? '' : `type=\"${element.type}\"`\n    const justify = element.type === 'default' ? '' : `justify=\"${element.justify}\"`\n    const align = element.type === 'default' ? '' : `align=\"${element.align}\"`\n    const gutter = element.gutter ? `gutter=\"${element.gutter}\"` : ''\n    const children = element.children.map(el => layouts[el.layout](el))\n    let str = `<el-row ${type} ${justify} ${align} ${gutter}>\n      ${children.join('\\n')}\n    </el-row>`\n    str = colWrapper(element, str)\n    return str\n  }\n}\n\nconst tags = {\n  'el-button': el => {\n    const {\n      tag, disabled\n    } = attrBuilder(el)\n    const type = el.type ? `type=\"${el.type}\"` : ''\n    const icon = el.icon ? `icon=\"${el.icon}\"` : ''\n    const size = el.size ? `size=\"${el.size}\"` : ''\n    let child = buildElButtonChild(el)\n\n    if (child) child = `\\n${child}\\n` // 换行\n    return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}</${el.tag}>`\n  },\n  'el-input': el => {\n    const {\n      disabled, vModel, clearable, placeholder, width\n    } = attrBuilder(el)\n    const maxlength = el.maxlength ? `:maxlength=\"${el.maxlength}\"` : ''\n    const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : ''\n    const readonly = el.readonly ? 'readonly' : ''\n    const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : ''\n    const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : ''\n    const showPassword = el['show-password'] ? 'show-password' : ''\n    const type = el.type ? `type=\"${el.type}\"` : ''\n    const autosize = el.autosize && el.autosize.minRows\n      ? `:autosize=\"{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}\"`\n      : ''\n    let child = buildElInputChild(el)\n\n    if (child) child = `\\n${child}\\n` // 换行\n    return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${el.tag}>`\n  },\n  'el-input-number': el => {\n    const { disabled, vModel, placeholder } = attrBuilder(el)\n    const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : ''\n    const min = el.min ? `:min='${el.min}'` : ''\n    const max = el.max ? `:max='${el.max}'` : ''\n    const step = el.step ? `:step='${el.step}'` : ''\n    const stepStrictly = el['step-strictly'] ? 'step-strictly' : ''\n    const precision = el.precision ? `:precision='${el.precision}'` : ''\n\n    return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${el.tag}>`\n  },\n  'el-select': el => {\n    const {\n      disabled, vModel, clearable, placeholder, width\n    } = attrBuilder(el)\n    const filterable = el.filterable ? 'filterable' : ''\n    const multiple = el.multiple ? 'multiple' : ''\n    let child = buildElSelectChild(el)\n\n    if (child) child = `\\n${child}\\n` // 换行\n    return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${el.tag}>`\n  },\n  'el-radio-group': el => {\n    const { disabled, vModel } = attrBuilder(el)\n    const size = `size=\"${el.size}\"`\n    let child = buildElRadioGroupChild(el)\n\n    if (child) child = `\\n${child}\\n` // 换行\n    return `<${el.tag} ${vModel} ${size} ${disabled}>${child}</${el.tag}>`\n  },\n  'el-checkbox-group': el => {\n    const { disabled, vModel } = attrBuilder(el)\n    const size = `size=\"${el.size}\"`\n    const min = el.min ? `:min=\"${el.min}\"` : ''\n    const max = el.max ? `:max=\"${el.max}\"` : ''\n    let child = buildElCheckboxGroupChild(el)\n\n    if (child) child = `\\n${child}\\n` // 换行\n    return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${el.tag}>`\n  },\n  'el-switch': el => {\n    const { disabled, vModel } = attrBuilder(el)\n    const activeText = el['active-text'] ? `active-text=\"${el['active-text']}\"` : ''\n    const inactiveText = el['inactive-text'] ? `inactive-text=\"${el['inactive-text']}\"` : ''\n    const activeColor = el['active-color'] ? `active-color=\"${el['active-color']}\"` : ''\n    const inactiveColor = el['inactive-color'] ? `inactive-color=\"${el['inactive-color']}\"` : ''\n    const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : ''\n    const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : ''\n\n    return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${el.tag}>`\n  },\n  'el-cascader': el => {\n    const {\n      disabled, vModel, clearable, placeholder, width\n    } = attrBuilder(el)\n    const options = el.options ? `:options=\"${el.vModel}Options\"` : ''\n    const props = el.props ? `:props=\"${el.vModel}Props\"` : ''\n    const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels=\"false\"'\n    const filterable = el.filterable ? 'filterable' : ''\n    const separator = el.separator === '/' ? '' : `separator=\"${el.separator}\"`\n\n    return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${el.tag}>`\n  },\n  'el-slider': el => {\n    const { disabled, vModel } = attrBuilder(el)\n    const min = el.min ? `:min='${el.min}'` : ''\n    const max = el.max ? `:max='${el.max}'` : ''\n    const step = el.step ? `:step='${el.step}'` : ''\n    const range = el.range ? 'range' : ''\n    const showStops = el['show-stops'] ? `:show-stops=\"${el['show-stops']}\"` : ''\n\n    return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${el.tag}>`\n  },\n  'el-time-picker': el => {\n    const {\n      disabled, vModel, clearable, placeholder, width\n    } = attrBuilder(el)\n    const startPlaceholder = el['start-placeholder'] ? `start-placeholder=\"${el['start-placeholder']}\"` : ''\n    const endPlaceholder = el['end-placeholder'] ? `end-placeholder=\"${el['end-placeholder']}\"` : ''\n    const rangeSeparator = el['range-separator'] ? `range-separator=\"${el['range-separator']}\"` : ''\n    const isRange = el['is-range'] ? 'is-range' : ''\n    const format = el.format ? `format=\"${el.format}\"` : ''\n    const valueFormat = el['value-format'] ? `value-format=\"${el['value-format']}\"` : ''\n    const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : ''\n\n    return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${el.tag}>`\n  },\n  'el-date-picker': el => {\n    const {\n      disabled, vModel, clearable, placeholder, width\n    } = attrBuilder(el)\n    const startPlaceholder = el['start-placeholder'] ? `start-placeholder=\"${el['start-placeholder']}\"` : ''\n    const endPlaceholder = el['end-placeholder'] ? `end-placeholder=\"${el['end-placeholder']}\"` : ''\n    const rangeSeparator = el['range-separator'] ? `range-separator=\"${el['range-separator']}\"` : ''\n    const format = el.format ? `format=\"${el.format}\"` : ''\n    const valueFormat = el['value-format'] ? `value-format=\"${el['value-format']}\"` : ''\n    const type = el.type === 'date' ? '' : `type=\"${el.type}\"`\n    const readonly = el.readonly ? 'readonly' : ''\n\n    return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${el.tag}>`\n  },\n  'el-rate': el => {\n    const { disabled, vModel } = attrBuilder(el)\n    const max = el.max ? `:max='${el.max}'` : ''\n    const allowHalf = el['allow-half'] ? 'allow-half' : ''\n    const showText = el['show-text'] ? 'show-text' : ''\n    const showScore = el['show-score'] ? 'show-score' : ''\n\n    return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}></${el.tag}>`\n  },\n  'el-color-picker': el => {\n    const { disabled, vModel } = attrBuilder(el)\n    const size = `size=\"${el.size}\"`\n    const showAlpha = el['show-alpha'] ? 'show-alpha' : ''\n    const colorFormat = el['color-format'] ? `color-format=\"${el['color-format']}\"` : ''\n\n    return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${el.tag}>`\n  },\n  'el-upload': el => {\n    const disabled = el.disabled ? ':disabled=\\'true\\'' : ''\n    const action = el.action ? `:action=\"${el.vModel}Action\"` : ''\n    const multiple = el.multiple ? 'multiple' : ''\n    const listType = el['list-type'] !== 'text' ? `list-type=\"${el['list-type']}\"` : ''\n    const accept = el.accept ? `accept=\"${el.accept}\"` : ''\n    const name = el.name !== 'file' ? `name=\"${el.name}\"` : ''\n    const autoUpload = el['auto-upload'] === false ? ':auto-upload=\"false\"' : ''\n    const beforeUpload = `:before-upload=\"${el.vModel}BeforeUpload\"`\n    const fileList = `:file-list=\"${el.vModel}fileList\"`\n    const ref = `ref=\"${el.vModel}\"`\n    let child = buildElUploadChild(el)\n\n    if (child) child = `\\n${child}\\n` // 换行\n    return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${el.tag}>`\n  }\n}\n\nfunction attrBuilder(el) {\n  return {\n    vModel: `v-model=\"${confGlobal.formModel}.${el.vModel}\"`,\n    clearable: el.clearable ? 'clearable' : '',\n    placeholder: el.placeholder ? `placeholder=\"${el.placeholder}\"` : '',\n    width: el.style && el.style.width ? ':style=\"{width: \\'100%\\'}\"' : '',\n    disabled: el.disabled ? ':disabled=\\'true\\'' : ''\n  }\n}\n\n// el-buttin 子级\nfunction buildElButtonChild(conf) {\n  const children = []\n  if (conf.default) {\n    children.push(conf.default)\n  }\n  return children.join('\\n')\n}\n\n// el-input innerHTML\nfunction buildElInputChild(conf) {\n  const children = []\n  if (conf.prepend) {\n    children.push(`<template slot=\"prepend\">${conf.prepend}</template>`)\n  }\n  if (conf.append) {\n    children.push(`<template slot=\"append\">${conf.append}</template>`)\n  }\n  return children.join('\\n')\n}\n\nfunction buildElSelectChild(conf) {\n  const children = []\n  if (conf.options && conf.options.length) {\n    children.push(`<el-option v-for=\"(item, index) in ${conf.vModel}Options\" :key=\"index\" :label=\"item.label\" :value=\"item.value\" :disabled=\"item.disabled\"></el-option>`)\n  }\n  return children.join('\\n')\n}\n\nfunction buildElRadioGroupChild(conf) {\n  const children = []\n  if (conf.options && conf.options.length) {\n    const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio'\n    const border = conf.border ? 'border' : ''\n    children.push(`<${tag} v-for=\"(item, index) in ${conf.vModel}Options\" :key=\"index\" :label=\"item.value\" :disabled=\"item.disabled\" ${border}>{{item.label}}</${tag}>`)\n  }\n  return children.join('\\n')\n}\n\nfunction buildElCheckboxGroupChild(conf) {\n  const children = []\n  if (conf.options && conf.options.length) {\n    const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'\n    const border = conf.border ? 'border' : ''\n    children.push(`<${tag} v-for=\"(item, index) in ${conf.vModel}Options\" :key=\"index\" :label=\"item.value\" :disabled=\"item.disabled\" ${border}>{{item.label}}</${tag}>`)\n  }\n  return children.join('\\n')\n}\n\nfunction buildElUploadChild(conf) {\n  const list = []\n  if (conf['list-type'] === 'picture-card') list.push('<i class=\"el-icon-plus\"></i>')\n  else list.push(`<el-button size=\"small\" type=\"primary\" icon=\"el-icon-upload\">${conf.buttonText}</el-button>`)\n  if (conf.showTip) list.push(`<div slot=\"tip\" class=\"el-upload__tip\">只能上传不超过 ${conf.fileSize}${conf.sizeUnit} 的${conf.accept}文件</div>`)\n  return list.join('\\n')\n}\n\nexport function makeUpHtml(conf, type) {\n  const htmlList = []\n  confGlobal = conf\n  someSpanIsNot24 = conf.fields.some(item => item.span !== 24)\n  conf.fields.forEach(el => {\n    htmlList.push(layouts[el.layout](el))\n  })\n  const htmlStr = htmlList.join('\\n')\n\n  let temp = buildFormTemplate(conf, htmlStr, type)\n  if (type === 'dialog') {\n    temp = dialogWrapper(temp)\n  }\n  confGlobal = null\n  return temp\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/generator/icon.json",
    "content": "[\"platform-eleme\",\"eleme\",\"delete-solid\",\"delete\",\"s-tools\",\"setting\",\"user-solid\",\"user\",\"phone\",\"phone-outline\",\"more\",\"more-outline\",\"star-on\",\"star-off\",\"s-goods\",\"goods\",\"warning\",\"warning-outline\",\"question\",\"info\",\"remove\",\"circle-plus\",\"success\",\"error\",\"zoom-in\",\"zoom-out\",\"remove-outline\",\"circle-plus-outline\",\"circle-check\",\"circle-close\",\"s-help\",\"help\",\"minus\",\"plus\",\"check\",\"close\",\"picture\",\"picture-outline\",\"picture-outline-round\",\"upload\",\"upload2\",\"download\",\"camera-solid\",\"camera\",\"video-camera-solid\",\"video-camera\",\"message-solid\",\"bell\",\"s-cooperation\",\"s-order\",\"s-platform\",\"s-fold\",\"s-unfold\",\"s-operation\",\"s-promotion\",\"s-home\",\"s-release\",\"s-ticket\",\"s-management\",\"s-open\",\"s-shop\",\"s-marketing\",\"s-flag\",\"s-comment\",\"s-finance\",\"s-claim\",\"s-custom\",\"s-opportunity\",\"s-data\",\"s-check\",\"s-grid\",\"menu\",\"share\",\"d-caret\",\"caret-left\",\"caret-right\",\"caret-bottom\",\"caret-top\",\"bottom-left\",\"bottom-right\",\"back\",\"right\",\"bottom\",\"top\",\"top-left\",\"top-right\",\"arrow-left\",\"arrow-right\",\"arrow-down\",\"arrow-up\",\"d-arrow-left\",\"d-arrow-right\",\"video-pause\",\"video-play\",\"refresh\",\"refresh-right\",\"refresh-left\",\"finished\",\"sort\",\"sort-up\",\"sort-down\",\"rank\",\"loading\",\"view\",\"c-scale-to-original\",\"date\",\"edit\",\"edit-outline\",\"folder\",\"folder-opened\",\"folder-add\",\"folder-remove\",\"folder-delete\",\"folder-checked\",\"tickets\",\"document-remove\",\"document-delete\",\"document-copy\",\"document-checked\",\"document\",\"document-add\",\"printer\",\"paperclip\",\"takeaway-box\",\"search\",\"monitor\",\"attract\",\"mobile\",\"scissors\",\"umbrella\",\"headset\",\"brush\",\"mouse\",\"coordinate\",\"magic-stick\",\"reading\",\"data-line\",\"data-board\",\"pie-chart\",\"data-analysis\",\"collection-tag\",\"film\",\"suitcase\",\"suitcase-1\",\"receiving\",\"collection\",\"files\",\"notebook-1\",\"notebook-2\",\"toilet-paper\",\"office-building\",\"school\",\"table-lamp\",\"house\",\"no-smoking\",\"smoking\",\"shopping-cart-full\",\"shopping-cart-1\",\"shopping-cart-2\",\"shopping-bag-1\",\"shopping-bag-2\",\"sold-out\",\"sell\",\"present\",\"box\",\"bank-card\",\"money\",\"coin\",\"wallet\",\"discount\",\"price-tag\",\"news\",\"guide\",\"male\",\"female\",\"thumb\",\"cpu\",\"link\",\"connection\",\"open\",\"turn-off\",\"set-up\",\"chat-round\",\"chat-line-round\",\"chat-square\",\"chat-dot-round\",\"chat-dot-square\",\"chat-line-square\",\"message\",\"postcard\",\"position\",\"turn-off-microphone\",\"microphone\",\"close-notification\",\"bangzhu\",\"time\",\"odometer\",\"crop\",\"aim\",\"switch-button\",\"full-screen\",\"copy-document\",\"mic\",\"stopwatch\",\"medal-1\",\"medal\",\"trophy\",\"trophy-1\",\"first-aid-kit\",\"discover\",\"place\",\"location\",\"location-outline\",\"location-information\",\"add-location\",\"delete-location\",\"map-location\",\"alarm-clock\",\"timer\",\"watch-1\",\"watch\",\"lock\",\"unlock\",\"key\",\"service\",\"mobile-phone\",\"bicycle\",\"truck\",\"ship\",\"basketball\",\"football\",\"soccer\",\"baseball\",\"wind-power\",\"light-rain\",\"lightning\",\"heavy-rain\",\"sunrise\",\"sunrise-1\",\"sunset\",\"sunny\",\"cloudy\",\"partly-cloudy\",\"cloudy-and-sunny\",\"moon\",\"moon-night\",\"dish\",\"dish-1\",\"food\",\"chicken\",\"fork-spoon\",\"knife-fork\",\"burger\",\"tableware\",\"sugar\",\"dessert\",\"ice-cream\",\"hot-water\",\"water-cup\",\"coffee-cup\",\"cold-drink\",\"goblet\",\"goblet-full\",\"goblet-square\",\"goblet-square-full\",\"refrigerator\",\"grape\",\"watermelon\",\"cherry\",\"apple\",\"pear\",\"orange\",\"coffee\",\"ice-tea\",\"ice-drink\",\"milk-tea\",\"potato-strips\",\"lollipop\",\"ice-cream-square\",\"ice-cream-round\"]"
  },
  {
    "path": "vue_campus_admin/src/utils/generator/js.js",
    "content": "import { isArray } from 'util'\nimport { exportDefault, titleCase } from '@/utils/index'\nimport { trigger } from './config'\n\nconst units = {\n  KB: '1024',\n  MB: '1024 / 1024',\n  GB: '1024 / 1024 / 1024'\n}\nlet confGlobal\nconst inheritAttrs = {\n  file: '',\n  dialog: 'inheritAttrs: false,'\n}\n\n\nexport function makeUpJs(conf, type) {\n  confGlobal = conf = JSON.parse(JSON.stringify(conf))\n  const dataList = []\n  const ruleList = []\n  const optionsList = []\n  const propsList = []\n  const methodList = mixinMethod(type)\n  const uploadVarList = []\n\n  conf.fields.forEach(el => {\n    buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList)\n  })\n\n  const script = buildexport(\n    conf,\n    type,\n    dataList.join('\\n'),\n    ruleList.join('\\n'),\n    optionsList.join('\\n'),\n    uploadVarList.join('\\n'),\n    propsList.join('\\n'),\n    methodList.join('\\n')\n  )\n  confGlobal = null\n  return script\n}\n\nfunction buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) {\n  buildData(el, dataList)\n  buildRules(el, ruleList)\n\n  if (el.options && el.options.length) {\n    buildOptions(el, optionsList)\n    if (el.dataType === 'dynamic') {\n      const model = `${el.vModel}Options`\n      const options = titleCase(model)\n      buildOptionMethod(`get${options}`, model, methodList)\n    }\n  }\n\n  if (el.props && el.props.props) {\n    buildProps(el, propsList)\n  }\n\n  if (el.action && el.tag === 'el-upload') {\n    uploadVarList.push(\n      `${el.vModel}Action: '${el.action}',\n      ${el.vModel}fileList: [],`\n    )\n    methodList.push(buildBeforeUpload(el))\n    if (!el['auto-upload']) {\n      methodList.push(buildSubmitUpload(el))\n    }\n  }\n\n  if (el.children) {\n    el.children.forEach(el2 => {\n      buildAttributes(el2, dataList, ruleList, optionsList, methodList, propsList, uploadVarList)\n    })\n  }\n}\n\nfunction mixinMethod(type) {\n  const list = []; const\n    minxins = {\n      file: confGlobal.formBtns ? {\n        submitForm: `submitForm() {\n        this.$refs['${confGlobal.formRef}'].validate(valid => {\n          if(!valid) return\n          // TODO 提交表单\n        })\n      },`,\n        resetForm: `resetForm() {\n        this.$refs['${confGlobal.formRef}'].resetFields()\n      },`\n      } : null,\n      dialog: {\n        onOpen: 'onOpen() {},',\n        onClose: `onClose() {\n        this.$refs['${confGlobal.formRef}'].resetFields()\n      },`,\n        close: `close() {\n        this.$emit('update:visible', false)\n      },`,\n        handleConfirm: `handleConfirm() {\n        this.$refs['${confGlobal.formRef}'].validate(valid => {\n          if(!valid) return\n          this.close()\n        })\n      },`\n      }\n    }\n\n  const methods = minxins[type]\n  if (methods) {\n    Object.keys(methods).forEach(key => {\n      list.push(methods[key])\n    })\n  }\n\n  return list\n}\n\nfunction buildData(conf, dataList) {\n  if (conf.vModel === undefined) return\n  let defaultValue\n  if (typeof (conf.defaultValue) === 'string' && !conf.multiple) {\n    defaultValue = `'${conf.defaultValue}'`\n  } else {\n    defaultValue = `${JSON.stringify(conf.defaultValue)}`\n  }\n  dataList.push(`${conf.vModel}: ${defaultValue},`)\n}\n\nfunction buildRules(conf, ruleList) {\n  if (conf.vModel === undefined) return\n  const rules = []\n  if (trigger[conf.tag]) {\n    if (conf.required) {\n      const type = isArray(conf.defaultValue) ? 'type: \\'array\\',' : ''\n      let message = isArray(conf.defaultValue) ? `请至少选择一个${conf.vModel}` : conf.placeholder\n      if (message === undefined) message = `${conf.label}不能为空`\n      rules.push(`{ required: true, ${type} message: '${message}', trigger: '${trigger[conf.tag]}' }`)\n    }\n    if (conf.regList && isArray(conf.regList)) {\n      conf.regList.forEach(item => {\n        if (item.pattern) {\n          rules.push(`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${trigger[conf.tag]}' }`)\n        }\n      })\n    }\n    ruleList.push(`${conf.vModel}: [${rules.join(',')}],`)\n  }\n}\n\nfunction buildOptions(conf, optionsList) {\n  if (conf.vModel === undefined) return\n  if (conf.dataType === 'dynamic') { conf.options = [] }\n  const str = `${conf.vModel}Options: ${JSON.stringify(conf.options)},`\n  optionsList.push(str)\n}\n\nfunction buildProps(conf, propsList) {\n  if (conf.dataType === 'dynamic') {\n    conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey)\n    conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey)\n    conf.childrenKey !== 'children' && (conf.props.props.children = conf.childrenKey)\n  }\n  const str = `${conf.vModel}Props: ${JSON.stringify(conf.props.props)},`\n  propsList.push(str)\n}\n\nfunction buildBeforeUpload(conf) {\n  const unitNum = units[conf.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const\n    returnList = []\n  if (conf.fileSize) {\n    rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize}\n    if(!isRightSize){\n      this.$message.error('文件大小超过 ${conf.fileSize}${conf.sizeUnit}')\n    }`\n    returnList.push('isRightSize')\n  }\n  if (conf.accept) {\n    acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type)\n    if(!isAccept){\n      this.$message.error('应该选择${conf.accept}类型的文件')\n    }`\n    returnList.push('isAccept')\n  }\n  const str = `${conf.vModel}BeforeUpload(file) {\n    ${rightSizeCode}\n    ${acceptCode}\n    return ${returnList.join('&&')}\n  },`\n  return returnList.length ? str : ''\n}\n\nfunction buildSubmitUpload(conf) {\n  const str = `submitUpload() {\n    this.$refs['${conf.vModel}'].submit()\n  },`\n  return str\n}\n\nfunction buildOptionMethod(methodName, model, methodList) {\n  const str = `${methodName}() {\n    // TODO 发起请求获取数据\n    this.${model}\n  },`\n  methodList.push(str)\n}\n\nfunction buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods) {\n  const str = `${exportDefault}{\n  ${inheritAttrs[type]}\n  components: {},\n  props: [],\n  data () {\n    return {\n      ${conf.formModel}: {\n        ${data}\n      },\n      ${conf.formRules}: {\n        ${rules}\n      },\n      ${uploadVar}\n      ${selectOptions}\n      ${props}\n    }\n  },\n  computed: {},\n  watch: {},\n  created () {},\n  mounted () {},\n  methods: {\n    ${methods}\n  }\n}`\n  return str\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/generator/render.js",
    "content": "import { makeMap } from '@/utils/index'\n\n// 参考https://github.com/vuejs/vue/blob/v2.6.10/src/platforms/web/server/util.js\nconst isAttr = makeMap(\n  'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,'\n  + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,'\n  + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,'\n  + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,'\n  + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,'\n  + 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,'\n  + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,'\n  + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,'\n  + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,'\n  + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,'\n  + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,'\n  + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,'\n  + 'target,title,type,usemap,value,width,wrap'\n)\n\nfunction vModel(self, dataObject, defaultValue) {\n  dataObject.props.value = defaultValue\n\n  dataObject.on.input = val => {\n    self.$emit('input', val)\n  }\n}\n\nconst componentChild = {\n  'el-button': {\n    default(h, conf, key) {\n      return conf[key]\n    },\n  },\n  'el-input': {\n    prepend(h, conf, key) {\n      return <template slot=\"prepend\">{conf[key]}</template>\n    },\n    append(h, conf, key) {\n      return <template slot=\"append\">{conf[key]}</template>\n    }\n  },\n  'el-select': {\n    options(h, conf, key) {\n      const list = []\n      conf.options.forEach(item => {\n        list.push(<el-option label={item.label} value={item.value} disabled={item.disabled}></el-option>)\n      })\n      return list\n    }\n  },\n  'el-radio-group': {\n    options(h, conf, key) {\n      const list = []\n      conf.options.forEach(item => {\n        if (conf.optionType === 'button') list.push(<el-radio-button label={item.value}>{item.label}</el-radio-button>)\n        else list.push(<el-radio label={item.value} border={conf.border}>{item.label}</el-radio>)\n      })\n      return list\n    }\n  },\n  'el-checkbox-group': {\n    options(h, conf, key) {\n      const list = []\n      conf.options.forEach(item => {\n        if (conf.optionType === 'button') {\n          list.push(<el-checkbox-button label={item.value}>{item.label}</el-checkbox-button>)\n        } else {\n          list.push(<el-checkbox label={item.value} border={conf.border}>{item.label}</el-checkbox>)\n        }\n      })\n      return list\n    }\n  },\n  'el-upload': {\n    'list-type': (h, conf, key) => {\n      const list = []\n      if (conf['list-type'] === 'picture-card') {\n        list.push(<i class=\"el-icon-plus\"></i>)\n      } else {\n        list.push(<el-button size=\"small\" type=\"primary\" icon=\"el-icon-upload\">{conf.buttonText}</el-button>)\n      }\n      if (conf.showTip) {\n        list.push(<div slot=\"tip\" class=\"el-upload__tip\">只能上传不超过 {conf.fileSize}{conf.sizeUnit} 的{conf.accept}文件</div>)\n      }\n      return list\n    }\n  }\n}\n\nexport default {\n  render(h) {\n    const dataObject = {\n      attrs: {},\n      props: {},\n      on: {},\n      style: {}\n    }\n    const confClone = JSON.parse(JSON.stringify(this.conf))\n    const children = []\n\n    const childObjs = componentChild[confClone.tag]\n    if (childObjs) {\n      Object.keys(childObjs).forEach(key => {\n        const childFunc = childObjs[key]\n        if (confClone[key]) {\n          children.push(childFunc(h, confClone, key))\n        }\n      })\n    }\n\n    Object.keys(confClone).forEach(key => {\n      const val = confClone[key]\n      if (key === 'vModel') {\n        vModel(this, dataObject, confClone.defaultValue)\n      } else if (dataObject[key]) {\n        dataObject[key] = val\n      } else if (!isAttr(key)) {\n        dataObject.props[key] = val\n      } else {\n        dataObject.attrs[key] = val\n      }\n    })\n    return h(this.conf.tag, dataObject, children)\n  },\n  props: ['conf']\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/index.js",
    "content": "import { parseTime } from './ruoyi'\n\n/**\n * 表格时间格式化\n */\nexport function formatDate(cellValue) {\n  if (cellValue == null || cellValue == \"\") return \"\";\n  var date = new Date(cellValue) \n  var year = date.getFullYear()\n  var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1\n  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() \n  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() \n  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() \n  var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()\n  return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds\n}\n\n/**\n * @param {number} time\n * @param {string} 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 * @param {string} url\n * @returns {Object}\n */\nexport function getQueryObject(url) {\n  url = url == null ? window.location.href : url\n  const search = url.substring(url.lastIndexOf('?') + 1)\n  const obj = {}\n  const reg = /([^?&=]+)=([^?&=]*)/g\n  search.replace(reg, (rs, $1, $2) => {\n    const name = decodeURIComponent($1)\n    let val = decodeURIComponent($2)\n    val = String(val)\n    obj[name] = val\n    return rs\n  })\n  return obj\n}\n\n/**\n * @param {string} input value\n * @returns {number} output value\n */\nexport function byteLength(str) {\n  // returns the byte length of an utf8 string\n  let s = str.length\n  for (var i = str.length - 1; i >= 0; i--) {\n    const code = str.charCodeAt(i)\n    if (code > 0x7f && code <= 0x7ff) s++\n    else if (code > 0x7ff && code <= 0xffff) s += 2\n    if (code >= 0xDC00 && code <= 0xDFFF) i--\n  }\n  return s\n}\n\n/**\n * @param {Array} actual\n * @returns {Array}\n */\nexport function cleanArray(actual) {\n  const newArray = []\n  for (let i = 0; i < actual.length; i++) {\n    if (actual[i]) {\n      newArray.push(actual[i])\n    }\n  }\n  return newArray\n}\n\n/**\n * @param {Object} json\n * @returns {Array}\n */\nexport function param(json) {\n  if (!json) return ''\n  return cleanArray(\n    Object.keys(json).map(key => {\n      if (json[key] === undefined) return ''\n      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])\n    })\n  ).join('&')\n}\n\n/**\n * @param {string} url\n * @returns {Object}\n */\nexport function param2Obj(url) {\n  const search = decodeURIComponent(url.split('?')[1]).replace(/\\+/g, ' ')\n  if (!search) {\n    return {}\n  }\n  const obj = {}\n  const searchArr = search.split('&')\n  searchArr.forEach(v => {\n    const index = v.indexOf('=')\n    if (index !== -1) {\n      const name = v.substring(0, index)\n      const val = v.substring(index + 1, v.length)\n      obj[name] = val\n    }\n  })\n  return obj\n}\n\n/**\n * @param {string} val\n * @returns {string}\n */\nexport function html2Text(val) {\n  const div = document.createElement('div')\n  div.innerHTML = val\n  return div.textContent || div.innerText\n}\n\n/**\n * Merges two objects, giving the last one precedence\n * @param {Object} target\n * @param {(Object|Array)} source\n * @returns {Object}\n */\nexport function objectMerge(target, source) {\n  if (typeof target !== 'object') {\n    target = {}\n  }\n  if (Array.isArray(source)) {\n    return source.slice()\n  }\n  Object.keys(source).forEach(property => {\n    const sourceProperty = source[property]\n    if (typeof sourceProperty === 'object') {\n      target[property] = objectMerge(target[property], sourceProperty)\n    } else {\n      target[property] = sourceProperty\n    }\n  })\n  return target\n}\n\n/**\n * @param {HTMLElement} element\n * @param {string} className\n */\nexport function toggleClass(element, className) {\n  if (!element || !className) {\n    return\n  }\n  let classString = element.className\n  const nameIndex = classString.indexOf(className)\n  if (nameIndex === -1) {\n    classString += '' + className\n  } else {\n    classString =\n      classString.substr(0, nameIndex) +\n      classString.substr(nameIndex + className.length)\n  }\n  element.className = classString\n}\n\n/**\n * @param {string} type\n * @returns {Date}\n */\nexport function getTime(type) {\n  if (type === 'start') {\n    return new Date().getTime() - 3600 * 1000 * 24 * 90\n  } else {\n    return new Date(new Date().toDateString())\n  }\n}\n\n/**\n * @param {Function} func\n * @param {number} wait\n * @param {boolean} immediate\n * @return {*}\n */\nexport function debounce(func, wait, immediate) {\n  let timeout, args, context, timestamp, result\n\n  const later = function() {\n    // 据上一次触发时间间隔\n    const last = +new Date() - timestamp\n\n    // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait\n    if (last < wait && last > 0) {\n      timeout = setTimeout(later, wait - last)\n    } else {\n      timeout = null\n      // 如果设定为immediate===true，因为开始边界已经调用过了此处无需调用\n      if (!immediate) {\n        result = func.apply(context, args)\n        if (!timeout) context = args = null\n      }\n    }\n  }\n\n  return function(...args) {\n    context = this\n    timestamp = +new Date()\n    const callNow = immediate && !timeout\n    // 如果延时不存在，重新设定延时\n    if (!timeout) timeout = setTimeout(later, wait)\n    if (callNow) {\n      result = func.apply(context, args)\n      context = args = null\n    }\n\n    return result\n  }\n}\n\n/**\n * This is just a simple version of deep copy\n * Has a lot of edge cases bug\n * If you want to use a perfect deep copy, use lodash's _.cloneDeep\n * @param {Object} source\n * @returns {Object}\n */\nexport function deepClone(source) {\n  if (!source && typeof source !== 'object') {\n    throw new Error('error arguments', 'deepClone')\n  }\n  const targetObj = source.constructor === Array ? [] : {}\n  Object.keys(source).forEach(keys => {\n    if (source[keys] && typeof source[keys] === 'object') {\n      targetObj[keys] = deepClone(source[keys])\n    } else {\n      targetObj[keys] = source[keys]\n    }\n  })\n  return targetObj\n}\n\n/**\n * @param {Array} arr\n * @returns {Array}\n */\nexport function uniqueArr(arr) {\n  return Array.from(new Set(arr))\n}\n\n/**\n * @returns {string}\n */\nexport function createUniqueString() {\n  const timestamp = +new Date() + ''\n  const randomNum = parseInt((1 + Math.random()) * 65536) + ''\n  return (+(randomNum + timestamp)).toString(32)\n}\n\n/**\n * Check if an element has a class\n * @param {HTMLElement} elm\n * @param {string} cls\n * @returns {boolean}\n */\nexport function hasClass(ele, cls) {\n  return !!ele.className.match(new RegExp('(\\\\s|^)' + cls + '(\\\\s|$)'))\n}\n\n/**\n * Add class to element\n * @param {HTMLElement} elm\n * @param {string} cls\n */\nexport function addClass(ele, cls) {\n  if (!hasClass(ele, cls)) ele.className += ' ' + cls\n}\n\n/**\n * Remove class from element\n * @param {HTMLElement} elm\n * @param {string} cls\n */\nexport function removeClass(ele, cls) {\n  if (hasClass(ele, cls)) {\n    const reg = new RegExp('(\\\\s|^)' + cls + '(\\\\s|$)')\n    ele.className = ele.className.replace(reg, ' ')\n  }\n}\n\nexport function makeMap(str, expectsLowerCase) {\n  const map = Object.create(null)\n  const list = str.split(',')\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true\n  }\n  return expectsLowerCase\n    ? val => map[val.toLowerCase()]\n    : val => map[val]\n}\n \nexport const exportDefault = 'export default '\n\nexport const beautifierConf = {\n  html: {\n    indent_size: '2',\n    indent_char: ' ',\n    max_preserve_newlines: '-1',\n    preserve_newlines: false,\n    keep_array_indentation: false,\n    break_chained_methods: false,\n    indent_scripts: 'separate',\n    brace_style: 'end-expand',\n    space_before_conditional: true,\n    unescape_strings: false,\n    jslint_happy: false,\n    end_with_newline: true,\n    wrap_line_length: '110',\n    indent_inner_html: true,\n    comma_first: false,\n    e4x: true,\n    indent_empty_lines: true\n  },\n  js: {\n    indent_size: '2',\n    indent_char: ' ',\n    max_preserve_newlines: '-1',\n    preserve_newlines: false,\n    keep_array_indentation: false,\n    break_chained_methods: false,\n    indent_scripts: 'normal',\n    brace_style: 'end-expand',\n    space_before_conditional: true,\n    unescape_strings: false,\n    jslint_happy: true,\n    end_with_newline: true,\n    wrap_line_length: '110',\n    indent_inner_html: true,\n    comma_first: false,\n    e4x: true,\n    indent_empty_lines: true\n  }\n}\n\n// 首字母大小\nexport function titleCase(str) {\n  return str.replace(/( |^)[a-z]/g, L => L.toUpperCase())\n}\n\n// 下划转驼峰\nexport function camelCase(str) {\n  return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase())\n}\n\nexport function isNumberStr(str) {\n  return /^[+-]?(0|([1-9]\\d*))(\\.\\d+)?$/g.test(str)\n}\n \n"
  },
  {
    "path": "vue_campus_admin/src/utils/jsencrypt.js",
    "content": "import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'\n\n// 密钥对生成 http://web.chacuo.net/netrsakeypair\n\nconst publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\\n' +\n  'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='\n\nconst privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\\n' +\n  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\\n' +\n  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\\n' +\n  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\\n' +\n  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\\n' +\n  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\\n' +\n  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\\n' +\n  'UP8iWi1Qw0Y='\n\n// 加密\nexport function encrypt(txt) {\n  const encryptor = new JSEncrypt()\n  encryptor.setPublicKey(publicKey) // 设置公钥\n  return encryptor.encrypt(txt) // 对数据进行加密\n}\n\n// 解密\nexport function decrypt(txt) {\n  const encryptor = new JSEncrypt()\n  encryptor.setPrivateKey(privateKey) // 设置私钥\n  return encryptor.decrypt(txt) // 对数据进行解密\n}\n\n"
  },
  {
    "path": "vue_campus_admin/src/utils/permission.js",
    "content": "import store from '@/store'\n\n/**\n * 字符权限校验\n * @param {Array} value 校验值\n * @returns {Boolean}\n */\nexport function checkPermi(value) {\n  if (value && value instanceof Array && value.length > 0) {\n    const permissions = store.getters && store.getters.permissions\n    const permissionDatas = value\n    const all_permission = \"*:*:*\";\n\n    const hasPermission = permissions.some(permission => {\n      return all_permission === permission || permissionDatas.includes(permission)\n    })\n\n    if (!hasPermission) {\n      return false\n    }\n    return true\n  } else {\n    console.error(`need roles! Like checkPermi=\"['system:user:add','system:user:edit']\"`)\n    return false\n  }\n}\n\n/**\n * 角色权限校验\n * @param {Array} value 校验值\n * @returns {Boolean}\n */\nexport function checkRole(value) {\n  if (value && value instanceof Array && value.length > 0) {\n    const roles = store.getters && store.getters.roles\n    const permissionRoles = value\n    const super_admin = \"admin\";\n\n    const hasRole = roles.some(role => {\n      return super_admin === role || permissionRoles.includes(role)\n    })\n\n    if (!hasRole) {\n      return false\n    }\n    return true\n  } else {\n    console.error(`need roles! Like checkRole=\"['admin','editor']\"`)\n    return false\n  }\n}"
  },
  {
    "path": "vue_campus_admin/src/utils/request.js",
    "content": "import axios from 'axios'\nimport { Notification, MessageBox, Message, Loading } from 'element-ui'\nimport store from '@/store'\nimport { getToken } from '@/utils/auth'\nimport errorCode from '@/utils/errorCode'\nimport { tansParams, blobValidate } from \"@/utils/ruoyi\";\nimport cache from '@/plugins/cache'\nimport { saveAs } from 'file-saver'\n\nlet downloadLoadingInstance;\n// 是否显示重新登录\nexport let isRelogin = { show: false };\n\naxios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'\n// 创建axios实例\nconst service = axios.create({\n  // axios中请求配置有baseURL选项，表示请求URL公共部分\n  baseURL: process.env.VUE_APP_BASE_API,\n  // 超时\n  timeout: 20000\n})\n\n// request拦截器\nservice.interceptors.request.use(config => {\n  // 是否需要设置 token\n  const isToken = (config.headers || {}).isToken === false\n  // 是否需要防止数据重复提交\n  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false\n  if (getToken() && !isToken) {\n    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改\n  }\n  // get请求映射params参数\n  if (config.method === 'get' && config.params) {\n    let url = config.url + '?' + tansParams(config.params);\n    url = url.slice(0, -1);\n    config.params = {};\n    config.url = url;\n  }\n  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {\n    const requestObj = {\n      url: config.url,\n      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,\n      time: new Date().getTime()\n    }\n    const sessionObj = cache.session.getJSON('sessionObj')\n    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {\n      cache.session.setJSON('sessionObj', requestObj)\n    } else {\n      const s_url = sessionObj.url;                  // 请求地址\n      const s_data = sessionObj.data;                // 请求数据\n      const s_time = sessionObj.time;                // 请求时间\n      const interval = 1000;                         // 间隔时间(ms)，小于此时间视为重复提交\n      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {\n        const message = '数据正在处理，请勿重复提交';\n        console.warn(`[${s_url}]: ` + message)\n        return Promise.reject(new Error(message))\n      } else {\n        cache.session.setJSON('sessionObj', requestObj)\n      }\n    }\n  }\n  return config\n}, error => {\n    console.log(error)\n    Promise.reject(error)\n})\n\n// 响应拦截器\nservice.interceptors.response.use(res => {\n    // 未设置状态码则默认成功状态\n    const code = res.data.code || 200;\n    // 获取错误信息\n    const msg = errorCode[code] || res.data.msg || errorCode['default']\n    // 二进制数据则直接返回\n    if(res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer'){\n      return res.data\n    }\n    if (code === 401) {\n      if (!isRelogin.show) {\n        isRelogin.show = true;\n        MessageBox.confirm('登录状态已过期，您可以继续留在该页面，或者重新登录', '系统提示', {\n          confirmButtonText: '重新登录',\n          cancelButtonText: '取消',\n          type: 'warning'\n        }\n      ).then(() => {\n        isRelogin.show = false;\n        store.dispatch('LogOut').then(() => {\n          location.href = '/index';\n        })\n      }).catch(() => {\n        isRelogin.show = false;\n      });\n    }\n      return Promise.reject('无效的会话，或者会话已过期，请重新登录。')\n    } else if (code === 500) {\n      Message({\n        message: msg,\n        type: 'error'\n      })\n      return Promise.reject(new Error(msg))\n    } else if (code !== 200) {\n      Notification.error({\n        title: msg\n      })\n      return Promise.reject('error')\n    } else {\n      return res.data\n    }\n  },\n  error => {\n    console.log('err' + error)\n    let { message } = error;\n    if (message == \"Network Error\") {\n      message = \"后端接口连接异常\";\n    }\n    else if (message.includes(\"timeout\")) {\n      message = \"系统接口请求超时\";\n    }\n    else if (message.includes(\"Request failed with status code\")) {\n      message = \"系统接口\" + message.substr(message.length - 3) + \"异常\";\n    }\n    Message({\n      message: message,\n      type: 'error',\n      duration: 5 * 1000\n    })\n    return Promise.reject(error)\n  }\n)\n\n// 通用下载方法\nexport function download(url, params, filename, config) {\n  downloadLoadingInstance = Loading.service({ text: \"正在下载数据，请稍候\", spinner: \"el-icon-loading\", background: \"rgba(0, 0, 0, 0.7)\", })\n  return service.post(url, params, {\n    transformRequest: [(params) => { return tansParams(params) }],\n    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n    responseType: 'blob',\n    ...config\n  }).then(async (data) => {\n    const isLogin = await blobValidate(data);\n    if (isLogin) {\n      const blob = new Blob([data])\n      saveAs(blob, filename)\n    } else {\n      const resText = await data.text();\n      const rspObj = JSON.parse(resText);\n      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']\n      Message.error(errMsg);\n    }\n    downloadLoadingInstance.close();\n  }).catch((r) => {\n    console.error(r)\n    Message.error('下载文件出现错误，请联系管理员！')\n    downloadLoadingInstance.close();\n  })\n}\n\nexport default service\n"
  },
  {
    "path": "vue_campus_admin/src/utils/ruoyi.js",
    "content": "\n\n/**\n * 通用js方法封装处理\n * Copyright (c) 2019 ruoyi\n */\n\n// 日期格式化\nexport function parseTime(time, pattern) {\n  if (arguments.length === 0 || !time) {\n    return null\n  }\n  const format = pattern || '{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    } else if (typeof time === 'string') {\n      time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\\.[\\d]{3}/gm), '');\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    // Note: getDay() returns 0 on Sunday\n    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }\n    if (result.length > 0 && value < 10) {\n      value = '0' + value\n    }\n    return value || 0\n  })\n  return time_str\n}\n\n// 表单重置\nexport function resetForm(refName) {\n  if (this.$refs[refName]) {\n    this.$refs[refName].resetFields();\n  }\n}\n\n// 添加日期范围\nexport function addDateRange(params, dateRange, propName) {\n  let search = params;\n  search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};\n  dateRange = Array.isArray(dateRange) ? dateRange : [];\n  if (typeof (propName) === 'undefined') {\n    search.params['beginTime'] = dateRange[0];\n    search.params['endTime'] = dateRange[1];\n  } else {\n    search.params['begin' + propName] = dateRange[0];\n    search.params['end' + propName] = dateRange[1];\n  }\n  return search;\n}\n\n// 回显数据字典\nexport function selectDictLabel(datas, value) {\n  if (value === undefined) {\n    return \"\";\n  }\n  var actions = [];\n  Object.keys(datas).some((key) => {\n    if (datas[key].value == ('' + value)) {\n      actions.push(datas[key].label);\n      return true;\n    }\n  })\n  if (actions.length === 0) {\n    actions.push(value);\n  }\n  return actions.join('');\n}\n\n// 回显数据字典（字符串数组）\nexport function selectDictLabels(datas, value, separator) {\n  if (value === undefined) {\n    return \"\";\n  }\n  var actions = [];\n  var currentSeparator = undefined === separator ? \",\" : separator;\n  var temp = value.split(currentSeparator);\n  Object.keys(value.split(currentSeparator)).some((val) => {\n    var match = false;\n    Object.keys(datas).some((key) => {\n      if (datas[key].value == ('' + temp[val])) {\n        actions.push(datas[key].label + currentSeparator);\n        match = true;\n      }\n    })\n    if (!match) {\n      actions.push(temp[val] + currentSeparator);\n    }\n  })\n  return actions.join('').substring(0, actions.join('').length - 1);\n}\n\n// 字符串格式化(%s )\nexport function sprintf(str) {\n  var args = arguments, flag = true, i = 1;\n  str = str.replace(/%s/g, function () {\n    var arg = args[i++];\n    if (typeof arg === 'undefined') {\n      flag = false;\n      return '';\n    }\n    return arg;\n  });\n  return flag ? str : '';\n}\n\n// 转换字符串，undefined,null等转化为\"\"\nexport function parseStrEmpty(str) {\n  if (!str || str == \"undefined\" || str == \"null\") {\n    return \"\";\n  }\n  return str;\n}\n\n// 数据合并\nexport function mergeRecursive(source, target) {\n  for (var p in target) {\n    try {\n      if (target[p].constructor == Object) {\n        source[p] = mergeRecursive(source[p], target[p]);\n      } else {\n        source[p] = target[p];\n      }\n    } catch (e) {\n      source[p] = target[p];\n    }\n  }\n  return source;\n};\n\n/**\n * 构造树型结构数据\n * @param {*} data 数据源\n * @param {*} id id字段 默认 'id'\n * @param {*} parentId 父节点字段 默认 'parentId'\n * @param {*} children 孩子节点字段 默认 'children'\n */\nexport function handleTree(data, id, parentId, children) {\n  let config = {\n    id: id || 'id',\n    parentId: parentId || 'parentId',\n    childrenList: children || 'children'\n  };\n\n  var childrenListMap = {};\n  var nodeIds = {};\n  var tree = [];\n\n  for (let d of data) {\n    let parentId = d[config.parentId];\n    if (childrenListMap[parentId] == null) {\n      childrenListMap[parentId] = [];\n    }\n    nodeIds[d[config.id]] = d;\n    childrenListMap[parentId].push(d);\n  }\n\n  for (let d of data) {\n    let parentId = d[config.parentId];\n    if (nodeIds[parentId] == null) {\n      tree.push(d);\n    }\n  }\n\n  for (let t of tree) {\n    adaptToChildrenList(t);\n  }\n\n  function adaptToChildrenList(o) {\n    if (childrenListMap[o[config.id]] !== null) {\n      o[config.childrenList] = childrenListMap[o[config.id]];\n    }\n    if (o[config.childrenList]) {\n      for (let c of o[config.childrenList]) {\n        adaptToChildrenList(c);\n      }\n    }\n  }\n  return tree;\n}\n\n/**\n* 参数处理\n* @param {*} params  参数\n*/\nexport function tansParams(params) {\n  let result = ''\n  for (const propName of Object.keys(params)) {\n    const value = params[propName];\n    var part = encodeURIComponent(propName) + \"=\";\n    if (value !== null && value !== \"\" && typeof (value) !== \"undefined\") {\n      if (typeof value === 'object') {\n        for (const key of Object.keys(value)) {\n          if (value[key] !== null && value[key] !== \"\" && typeof (value[key]) !== 'undefined') {\n            let params = propName + '[' + key + ']';\n            var subPart = encodeURIComponent(params) + \"=\";\n            result += subPart + encodeURIComponent(value[key]) + \"&\";\n          }\n        }\n      } else {\n        result += part + encodeURIComponent(value) + \"&\";\n      }\n    }\n  }\n  return result\n}\n\n// 验证是否为blob格式\nexport async function blobValidate(data) {\n  try {\n    const text = await data.text();\n    JSON.parse(text);\n    return false;\n  } catch (error) {\n    return true;\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/scroll-to.js",
    "content": "Math.easeInOutQuad = function(t, b, c, d) {\n  t /= d / 2\n  if (t < 1) {\n    return c / 2 * t * t + b\n  }\n  t--\n  return -c / 2 * (t * (t - 2) - 1) + b\n}\n\n// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts\nvar requestAnimFrame = (function() {\n  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }\n})()\n\n/**\n * Because it's so fucking difficult to detect the scrolling element, just move them all\n * @param {number} amount\n */\nfunction move(amount) {\n  document.documentElement.scrollTop = amount\n  document.body.parentNode.scrollTop = amount\n  document.body.scrollTop = amount\n}\n\nfunction position() {\n  return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop\n}\n\n/**\n * @param {number} to\n * @param {number} duration\n * @param {Function} callback\n */\nexport function scrollTo(to, duration, callback) {\n  const start = position()\n  const change = to - start\n  const increment = 20\n  let currentTime = 0\n  duration = (typeof (duration) === 'undefined') ? 500 : duration\n  var animateScroll = function() {\n    // increment the time\n    currentTime += increment\n    // find the value with the quadratic in-out easing function\n    var val = Math.easeInOutQuad(currentTime, start, change, duration)\n    // move the document.body\n    move(val)\n    // do the animation unless its over\n    if (currentTime < duration) {\n      requestAnimFrame(animateScroll)\n    } else {\n      if (callback && typeof (callback) === 'function') {\n        // the animation is done so lets callback\n        callback()\n      }\n    }\n  }\n  animateScroll()\n}\n"
  },
  {
    "path": "vue_campus_admin/src/utils/validate.js",
    "content": "/**\n * @param {string} path\n * @returns {Boolean}\n */\nexport function isExternal(path) {\n  return /^(https?:|mailto:|tel:)/.test(path)\n}\n\n/**\n * @param {string} str\n * @returns {Boolean}\n */\nexport function validUsername(str) {\n  const valid_map = ['admin', 'editor']\n  return valid_map.indexOf(str.trim()) >= 0\n}\n\n/**\n * @param {string} url\n * @returns {Boolean}\n */\nexport function validURL(url) {\n  const reg = /^(https?|ftp):\\/\\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\\.)*[a-zA-Z0-9-]+\\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\\/($|[a-zA-Z0-9.,?'\\\\+&%$#=~_-]+))*$/\n  return reg.test(url)\n}\n\n/**\n * @param {string} str\n * @returns {Boolean}\n */\nexport function validLowerCase(str) {\n  const reg = /^[a-z]+$/\n  return reg.test(str)\n}\n\n/**\n * @param {string} str\n * @returns {Boolean}\n */\nexport function validUpperCase(str) {\n  const reg = /^[A-Z]+$/\n  return reg.test(str)\n}\n\n/**\n * @param {string} str\n * @returns {Boolean}\n */\nexport function validAlphabets(str) {\n  const reg = /^[A-Za-z]+$/\n  return reg.test(str)\n}\n\n/**\n * @param {string} email\n * @returns {Boolean}\n */\nexport function validEmail(email) {\n  const reg = /^(([^<>()\\[\\]\\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/\n  return reg.test(email)\n}\n\n/**\n * @param {string} str\n * @returns {Boolean}\n */\nexport function isString(str) {\n  if (typeof str === 'string' || str instanceof String) {\n    return true\n  }\n  return false\n}\n\n/**\n * @param {Array} arg\n * @returns {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"
  },
  {
    "path": "vue_campus_admin/src/views/components/icons/element-icons.js",
    "content": "const elementIcons = ['platform-eleme', 'eleme', 'delete-solid', 'delete', 's-tools', 'setting', 'user-solid', 'user', 'phone', 'phone-outline', 'more', 'more-outline', 'star-on', 'star-off', 's-goods', 'goods', 'warning', 'warning-outline', 'question', 'info', 'remove', 'circle-plus', 'success', 'error', 'zoom-in', 'zoom-out', 'remove-outline', 'circle-plus-outline', 'circle-check', 'circle-close', 's-help', 'help', 'minus', 'plus', 'check', 'close', 'picture', 'picture-outline', 'picture-outline-round', 'upload', 'upload2', 'download', 'camera-solid', 'camera', 'video-camera-solid', 'video-camera', 'message-solid', 'bell', 's-cooperation', 's-order', 's-platform', 's-fold', 's-unfold', 's-operation', 's-promotion', 's-home', 's-release', 's-ticket', 's-management', 's-open', 's-shop', 's-marketing', 's-flag', 's-comment', 's-finance', 's-claim', 's-custom', 's-opportunity', 's-data', 's-check', 's-grid', 'menu', 'share', 'd-caret', 'caret-left', 'caret-right', 'caret-bottom', 'caret-top', 'bottom-left', 'bottom-right', 'back', 'right', 'bottom', 'top', 'top-left', 'top-right', 'arrow-left', 'arrow-right', 'arrow-down', 'arrow-up', 'd-arrow-left', 'd-arrow-right', 'video-pause', 'video-play', 'refresh', 'refresh-right', 'refresh-left', 'finished', 'sort', 'sort-up', 'sort-down', 'rank', 'loading', 'view', 'c-scale-to-original', 'date', 'edit', 'edit-outline', 'folder', 'folder-opened', 'folder-add', 'folder-remove', 'folder-delete', 'folder-checked', 'tickets', 'document-remove', 'document-delete', 'document-copy', 'document-checked', 'document', 'document-add', 'printer', 'paperclip', 'takeaway-box', 'search', 'monitor', 'attract', 'mobile', 'scissors', 'umbrella', 'headset', 'brush', 'mouse', 'coordinate', 'magic-stick', 'reading', 'data-line', 'data-board', 'pie-chart', 'data-analysis', 'collection-tag', 'film', 'suitcase', 'suitcase-1', 'receiving', 'collection', 'files', 'notebook-1', 'notebook-2', 'toilet-paper', 'office-building', 'school', 'table-lamp', 'house', 'no-smoking', 'smoking', 'shopping-cart-full', 'shopping-cart-1', 'shopping-cart-2', 'shopping-bag-1', 'shopping-bag-2', 'sold-out', 'sell', 'present', 'box', 'bank-card', 'money', 'coin', 'wallet', 'discount', 'price-tag', 'news', 'guide', 'male', 'female', 'thumb', 'cpu', 'link', 'connection', 'open', 'turn-off', 'set-up', 'chat-round', 'chat-line-round', 'chat-square', 'chat-dot-round', 'chat-dot-square', 'chat-line-square', 'message', 'postcard', 'position', 'turn-off-microphone', 'microphone', 'close-notification', 'bangzhu', 'time', 'odometer', 'crop', 'aim', 'switch-button', 'full-screen', 'copy-document', 'mic', 'stopwatch', 'medal-1', 'medal', 'trophy', 'trophy-1', 'first-aid-kit', 'discover', 'place', 'location', 'location-outline', 'location-information', 'add-location', 'delete-location', 'map-location', 'alarm-clock', 'timer', 'watch-1', 'watch', 'lock', 'unlock', 'key', 'service', 'mobile-phone', 'bicycle', 'truck', 'ship', 'basketball', 'football', 'soccer', 'baseball', 'wind-power', 'light-rain', 'lightning', 'heavy-rain', 'sunrise', 'sunrise-1', 'sunset', 'sunny', 'cloudy', 'partly-cloudy', 'cloudy-and-sunny', 'moon', 'moon-night', 'dish', 'dish-1', 'food', 'chicken', 'fork-spoon', 'knife-fork', 'burger', 'tableware', 'sugar', 'dessert', 'ice-cream', 'hot-water', 'water-cup', 'coffee-cup', 'cold-drink', 'goblet', 'goblet-full', 'goblet-square', 'goblet-square-full', 'refrigerator', 'grape', 'watermelon', 'cherry', 'apple', 'pear', 'orange', 'coffee', 'ice-tea', 'ice-drink', 'milk-tea', 'potato-strips', 'lollipop', 'ice-cream-square', 'ice-cream-round']\n\nexport default elementIcons\n"
  },
  {
    "path": "vue_campus_admin/src/views/components/icons/index.vue",
    "content": "<template>\n  <div class=\"icons-container\">\n    <aside>\n      <a href=\"#\" target=\"_blank\">Add and use\n      </a>\n    </aside>\n    <el-tabs type=\"border-card\">\n      <el-tab-pane label=\"Icons\">\n        <div v-for=\"item of svgIcons\" :key=\"item\">\n          <el-tooltip placement=\"top\">\n            <div slot=\"content\">\n              {{ generateIconCode(item) }}\n            </div>\n            <div class=\"icon-item\">\n              <svg-icon :icon-class=\"item\" class-name=\"disabled\" />\n              <span>{{ item }}</span>\n            </div>\n          </el-tooltip>\n        </div>\n      </el-tab-pane>\n      <el-tab-pane label=\"Element-UI Icons\">\n        <div v-for=\"item of elementIcons\" :key=\"item\">\n          <el-tooltip placement=\"top\">\n            <div slot=\"content\">\n              {{ generateElementIconCode(item) }}\n            </div>\n            <div class=\"icon-item\">\n              <i :class=\"'el-icon-' + item\" />\n              <span>{{ item }}</span>\n            </div>\n          </el-tooltip>\n        </div>\n      </el-tab-pane>\n    </el-tabs>\n  </div>\n</template>\n\n<script>\nimport svgIcons from './svg-icons'\nimport elementIcons from './element-icons'\n\nexport default {\n  name: 'Icons',\n  data() {\n    return {\n      svgIcons,\n      elementIcons\n    }\n  },\n  methods: {\n    generateIconCode(symbol) {\n      return `<svg-icon icon-class=\"${symbol}\" />`\n    },\n    generateElementIconCode(symbol) {\n      return `<i class=\"el-icon-${symbol}\" />`\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.icons-container {\n  margin: 10px 20px 0;\n  overflow: hidden;\n\n  .icon-item {\n    margin: 20px;\n    height: 85px;\n    text-align: center;\n    width: 100px;\n    float: left;\n    font-size: 30px;\n    color: #24292e;\n    cursor: pointer;\n  }\n\n  span {\n    display: block;\n    font-size: 16px;\n    margin-top: 10px;\n  }\n\n  .disabled {\n    pointer-events: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/views/components/icons/svg-icons.js",
    "content": "const req = require.context('../../../assets/icons/svg', false, /\\.svg$/)\nconst requireAll = requireContext => requireContext.keys()\n\nconst re = /\\.\\/(.*)\\.svg/\n\nconst svgIcons = requireAll(req).map(i => {\n  return i.match(re)[1]\n})\n\nexport default svgIcons\n"
  },
  {
    "path": "vue_campus_admin/src/views/dashboard/BarChart.vue",
    "content": "<template>\n  <div :class=\"className\" :style=\"{height:height,width:width}\" />\n</template>\n\n<script>\nimport echarts from 'echarts'\nrequire('echarts/theme/macarons') // echarts theme\nimport resize from './mixins/resize'\n\nconst animationDuration = 6000\n\nexport default {\n  mixins: [resize],\n  props: {\n    className: {\n      type: String,\n      default: 'chart'\n    },\n    width: {\n      type: String,\n      default: '100%'\n    },\n    height: {\n      type: String,\n      default: '300px'\n    }\n  },\n  data() {\n    return {\n      chart: null\n    }\n  },\n  mounted() {\n    this.$nextTick(() => {\n      this.initChart()\n    })\n  },\n  beforeDestroy() {\n    if (!this.chart) {\n      return\n    }\n    this.chart.dispose()\n    this.chart = null\n  },\n  methods: {\n    initChart() {\n      this.chart = echarts.init(this.$el, 'macarons')\n\n      this.chart.setOption({\n        tooltip: {\n          trigger: 'axis',\n          axisPointer: { // 坐标轴指示器，坐标轴触发有效\n            type: 'shadow' // 默认为直线，可选为：'line' | 'shadow'\n          }\n        },\n        grid: {\n          top: 10,\n          left: '2%',\n          right: '2%',\n          bottom: '3%',\n          containLabel: true\n        },\n        xAxis: [{\n          type: 'category',\n          data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],\n          axisTick: {\n            alignWithLabel: true\n          }\n        }],\n        yAxis: [{\n          type: 'value',\n          axisTick: {\n            show: false\n          }\n        }],\n        series: [{\n          name: 'pageA',\n          type: 'bar',\n          stack: 'vistors',\n          barWidth: '60%',\n          data: [79, 52, 200, 334, 390, 330, 220],\n          animationDuration\n        }, {\n          name: 'pageB',\n          type: 'bar',\n          stack: 'vistors',\n          barWidth: '60%',\n          data: [80, 52, 200, 334, 390, 330, 220],\n          animationDuration\n        }, {\n          name: 'pageC',\n          type: 'bar',\n          stack: 'vistors',\n          barWidth: '60%',\n          data: [30, 52, 200, 334, 390, 330, 220],\n          animationDuration\n        }]\n      })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/dashboard/LineChart.vue",
    "content": "<template>\n  <div :class=\"className\" :style=\"{height:height,width:width}\" />\n</template>\n\n<script>\nimport echarts from 'echarts'\nrequire('echarts/theme/macarons') // echarts theme\nimport resize from './mixins/resize'\n\nexport default {\n  mixins: [resize],\n  props: {\n    className: {\n      type: String,\n      default: 'chart'\n    },\n    width: {\n      type: String,\n      default: '100%'\n    },\n    height: {\n      type: String,\n      default: '350px'\n    },\n    autoResize: {\n      type: Boolean,\n      default: true\n    },\n    chartData: {\n      type: Object,\n      required: true\n    }\n  },\n  data() {\n    return {\n      chart: null\n    }\n  },\n  watch: {\n    chartData: {\n      deep: true,\n      handler(val) {\n        this.setOptions(val)\n      }\n    }\n  },\n  mounted() {\n    this.$nextTick(() => {\n      this.initChart()\n    })\n  },\n  beforeDestroy() {\n    if (!this.chart) {\n      return\n    }\n    this.chart.dispose()\n    this.chart = null\n  },\n  methods: {\n    initChart() {\n      this.chart = echarts.init(this.$el, 'macarons')\n      this.setOptions(this.chartData)\n    },\n    setOptions({ expectedData, actualData } = {}) {\n      this.chart.setOption({\n        xAxis: {\n          data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],\n          boundaryGap: false,\n          axisTick: {\n            show: false\n          }\n        },\n        grid: {\n          left: 10,\n          right: 10,\n          bottom: 20,\n          top: 30,\n          containLabel: true\n        },\n        tooltip: {\n          trigger: 'axis',\n          axisPointer: {\n            type: 'cross'\n          },\n          padding: [5, 10]\n        },\n        yAxis: {\n          axisTick: {\n            show: false\n          }\n        },\n        legend: {\n          data: ['expected', 'actual']\n        },\n        series: [{\n          name: 'expected', itemStyle: {\n            normal: {\n              color: '#FF005A',\n              lineStyle: {\n                color: '#FF005A',\n                width: 2\n              }\n            }\n          },\n          smooth: true,\n          type: 'line',\n          data: expectedData,\n          animationDuration: 2800,\n          animationEasing: 'cubicInOut'\n        },\n        {\n          name: 'actual',\n          smooth: true,\n          type: 'line',\n          itemStyle: {\n            normal: {\n              color: '#3888fa',\n              lineStyle: {\n                color: '#3888fa',\n                width: 2\n              },\n              areaStyle: {\n                color: '#f3f8ff'\n              }\n            }\n          },\n          data: actualData,\n          animationDuration: 2800,\n          animationEasing: 'quadraticOut'\n        }]\n      })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/dashboard/PanelGroup.vue",
    "content": "<template>\n  <el-row :gutter=\"40\" class=\"panel-group\">\n    <el-col :xs=\"12\" :sm=\"12\" :lg=\"6\" class=\"card-panel-col\">\n      <div class=\"card-panel\" @click=\"handleSetLineChartData('newVisitis')\">\n        <div class=\"card-panel-icon-wrapper icon-people\">\n          <svg-icon icon-class=\"peoples\" class-name=\"card-panel-icon\" />\n        </div>\n        <div class=\"card-panel-description\">\n          <div class=\"card-panel-text\">\n            访客\n          </div>\n          <count-to :start-val=\"0\" :end-val=\"102400\" :duration=\"2600\" class=\"card-panel-num\" />\n        </div>\n      </div>\n    </el-col>\n    <el-col :xs=\"12\" :sm=\"12\" :lg=\"6\" class=\"card-panel-col\">\n      <div class=\"card-panel\" @click=\"handleSetLineChartData('messages')\">\n        <div class=\"card-panel-icon-wrapper icon-message\">\n          <svg-icon icon-class=\"message\" class-name=\"card-panel-icon\" />\n        </div>\n        <div class=\"card-panel-description\">\n          <div class=\"card-panel-text\">\n            消息\n          </div>\n          <count-to :start-val=\"0\" :end-val=\"81212\" :duration=\"3000\" class=\"card-panel-num\" />\n        </div>\n      </div>\n    </el-col>\n    <el-col :xs=\"12\" :sm=\"12\" :lg=\"6\" class=\"card-panel-col\">\n      <div class=\"card-panel\" @click=\"handleSetLineChartData('purchases')\">\n        <div class=\"card-panel-icon-wrapper icon-money\">\n          <svg-icon icon-class=\"money\" class-name=\"card-panel-icon\" />\n        </div>\n        <div class=\"card-panel-description\">\n          <div class=\"card-panel-text\">\n            金额\n          </div>\n          <count-to :start-val=\"0\" :end-val=\"9280\" :duration=\"3200\" class=\"card-panel-num\" />\n        </div>\n      </div>\n    </el-col>\n    <el-col :xs=\"12\" :sm=\"12\" :lg=\"6\" class=\"card-panel-col\">\n      <div class=\"card-panel\" @click=\"handleSetLineChartData('shoppings')\">\n        <div class=\"card-panel-icon-wrapper icon-shopping\">\n          <svg-icon icon-class=\"shopping\" class-name=\"card-panel-icon\" />\n        </div>\n        <div class=\"card-panel-description\">\n          <div class=\"card-panel-text\">\n            订单\n          </div>\n          <count-to :start-val=\"0\" :end-val=\"13600\" :duration=\"3600\" class=\"card-panel-num\" />\n        </div>\n      </div>\n    </el-col>\n  </el-row>\n</template>\n\n<script>\nimport CountTo from 'vue-count-to'\n\nexport default {\n  components: {\n    CountTo\n  },\n  methods: {\n    handleSetLineChartData(type) {\n      this.$emit('handleSetLineChartData', type)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.panel-group {\n  margin-top: 18px;\n\n  .card-panel-col {\n    margin-bottom: 32px;\n  }\n\n  .card-panel {\n    height: 108px;\n    cursor: pointer;\n    font-size: 12px;\n    position: relative;\n    overflow: hidden;\n    color: #666;\n    background: #fff;\n    box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);\n    border-color: rgba(0, 0, 0, .05);\n\n    &:hover {\n      .card-panel-icon-wrapper {\n        color: #fff;\n      }\n\n      .icon-people {\n        background: #40c9c6;\n      }\n\n      .icon-message {\n        background: #36a3f7;\n      }\n\n      .icon-money {\n        background: #f4516c;\n      }\n\n      .icon-shopping {\n        background: #34bfa3\n      }\n    }\n\n    .icon-people {\n      color: #40c9c6;\n    }\n\n    .icon-message {\n      color: #36a3f7;\n    }\n\n    .icon-money {\n      color: #f4516c;\n    }\n\n    .icon-shopping {\n      color: #34bfa3\n    }\n\n    .card-panel-icon-wrapper {\n      float: left;\n      margin: 14px 0 0 14px;\n      padding: 16px;\n      transition: all 0.38s ease-out;\n      border-radius: 6px;\n    }\n\n    .card-panel-icon {\n      float: left;\n      font-size: 48px;\n    }\n\n    .card-panel-description {\n      float: right;\n      font-weight: bold;\n      margin: 26px;\n      margin-left: 0px;\n\n      .card-panel-text {\n        line-height: 18px;\n        color: rgba(0, 0, 0, 0.45);\n        font-size: 16px;\n        margin-bottom: 12px;\n      }\n\n      .card-panel-num {\n        font-size: 20px;\n      }\n    }\n  }\n}\n\n@media (max-width:550px) {\n  .card-panel-description {\n    display: none;\n  }\n\n  .card-panel-icon-wrapper {\n    float: none !important;\n    width: 100%;\n    height: 100%;\n    margin: 0 !important;\n\n    .svg-icon {\n      display: block;\n      margin: 14px auto !important;\n      float: none !important;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/views/dashboard/PieChart.vue",
    "content": "<template>\n  <div :class=\"className\" :style=\"{height:height,width:width}\" />\n</template>\n\n<script>\nimport echarts from 'echarts'\nrequire('echarts/theme/macarons') // echarts theme\nimport resize from './mixins/resize'\n\nexport default {\n  mixins: [resize],\n  props: {\n    className: {\n      type: String,\n      default: 'chart'\n    },\n    width: {\n      type: String,\n      default: '100%'\n    },\n    height: {\n      type: String,\n      default: '300px'\n    }\n  },\n  data() {\n    return {\n      chart: null\n    }\n  },\n  mounted() {\n    this.$nextTick(() => {\n      this.initChart()\n    })\n  },\n  beforeDestroy() {\n    if (!this.chart) {\n      return\n    }\n    this.chart.dispose()\n    this.chart = null\n  },\n  methods: {\n    initChart() {\n      this.chart = echarts.init(this.$el, 'macarons')\n\n      this.chart.setOption({\n        tooltip: {\n          trigger: 'item',\n          formatter: '{a} <br/>{b} : {c} ({d}%)'\n        },\n        legend: {\n          left: 'center',\n          bottom: '10',\n          data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts']\n        },\n        series: [\n          {\n            name: 'WEEKLY WRITE ARTICLES',\n            type: 'pie',\n            roseType: 'radius',\n            radius: [15, 95],\n            center: ['50%', '38%'],\n            data: [\n              { value: 320, name: 'Industries' },\n              { value: 240, name: 'Technology' },\n              { value: 149, name: 'Forex' },\n              { value: 100, name: 'Gold' },\n              { value: 59, name: 'Forecasts' }\n            ],\n            animationEasing: 'cubicInOut',\n            animationDuration: 2600\n          }\n        ]\n      })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/dashboard/RaddarChart.vue",
    "content": "<template>\n  <div :class=\"className\" :style=\"{height:height,width:width}\" />\n</template>\n\n<script>\nimport echarts from 'echarts'\nrequire('echarts/theme/macarons') // echarts theme\nimport resize from './mixins/resize'\n\nconst animationDuration = 3000\n\nexport default {\n  mixins: [resize],\n  props: {\n    className: {\n      type: String,\n      default: 'chart'\n    },\n    width: {\n      type: String,\n      default: '100%'\n    },\n    height: {\n      type: String,\n      default: '300px'\n    }\n  },\n  data() {\n    return {\n      chart: null\n    }\n  },\n  mounted() {\n    this.$nextTick(() => {\n      this.initChart()\n    })\n  },\n  beforeDestroy() {\n    if (!this.chart) {\n      return\n    }\n    this.chart.dispose()\n    this.chart = null\n  },\n  methods: {\n    initChart() {\n      this.chart = echarts.init(this.$el, 'macarons')\n\n      this.chart.setOption({\n        tooltip: {\n          trigger: 'axis',\n          axisPointer: { // 坐标轴指示器，坐标轴触发有效\n            type: 'shadow' // 默认为直线，可选为：'line' | 'shadow'\n          }\n        },\n        radar: {\n          radius: '66%',\n          center: ['50%', '42%'],\n          splitNumber: 8,\n          splitArea: {\n            areaStyle: {\n              color: 'rgba(127,95,132,.3)',\n              opacity: 1,\n              shadowBlur: 45,\n              shadowColor: 'rgba(0,0,0,.5)',\n              shadowOffsetX: 0,\n              shadowOffsetY: 15\n            }\n          },\n          indicator: [\n            { name: 'Sales', max: 10000 },\n            { name: 'Administration', max: 20000 },\n            { name: 'Information Techology', max: 20000 },\n            { name: 'Customer Support', max: 20000 },\n            { name: 'Development', max: 20000 },\n            { name: 'Marketing', max: 20000 }\n          ]\n        },\n        legend: {\n          left: 'center',\n          bottom: '10',\n          data: ['Allocated Budget', 'Expected Spending', 'Actual Spending']\n        },\n        series: [{\n          type: 'radar',\n          symbolSize: 0,\n          areaStyle: {\n            normal: {\n              shadowBlur: 13,\n              shadowColor: 'rgba(0,0,0,.2)',\n              shadowOffsetX: 0,\n              shadowOffsetY: 10,\n              opacity: 1\n            }\n          },\n          data: [\n            {\n              value: [5000, 7000, 12000, 11000, 15000, 14000],\n              name: 'Allocated Budget'\n            },\n            {\n              value: [4000, 9000, 15000, 15000, 13000, 11000],\n              name: 'Expected Spending'\n            },\n            {\n              value: [5500, 11000, 12000, 15000, 12000, 12000],\n              name: 'Actual Spending'\n            }\n          ],\n          animationDuration: animationDuration\n        }]\n      })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/dashboard/mixins/resize.js",
    "content": "import { debounce } from '@/utils'\n\nexport default {\n  data() {\n    return {\n      $_sidebarElm: null,\n      $_resizeHandler: null\n    }\n  },\n  mounted() {\n    this.initListener()\n  },\n  activated() {\n    if (!this.$_resizeHandler) {\n      // avoid duplication init\n      this.initListener()\n    }\n\n    // when keep-alive chart activated, auto resize\n    this.resize()\n  },\n  beforeDestroy() {\n    this.destroyListener()\n  },\n  deactivated() {\n    this.destroyListener()\n  },\n  methods: {\n    // use $_ for mixins properties\n    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential\n    $_sidebarResizeHandler(e) {\n      if (e.propertyName === 'width') {\n        this.$_resizeHandler()\n      }\n    },\n    initListener() {\n      this.$_resizeHandler = debounce(() => {\n        this.resize()\n      }, 100)\n      window.addEventListener('resize', this.$_resizeHandler)\n\n      this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]\n      this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)\n    },\n    destroyListener() {\n      window.removeEventListener('resize', this.$_resizeHandler)\n      this.$_resizeHandler = null\n\n      this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)\n    },\n    resize() {\n      const { chart } = this\n      chart && chart.resize()\n    }\n  }\n}\n"
  },
  {
    "path": "vue_campus_admin/src/views/error/401.vue",
    "content": "<template>\n  <div class=\"errPage-container\">\n    <el-button icon=\"arrow-left\" class=\"pan-back-btn\" @click=\"back\">\n      返回\n    </el-button>\n    <el-row>\n      <el-col :span=\"12\">\n        <h1 class=\"text-jumbo text-ginormous\">\n          401错误!\n        </h1>\n        <h2>您没有访问权限！</h2>\n        <h6>对不起，您没有访问权限，请不要进行非法操作！您可以返回主页面</h6>\n        <ul class=\"list-unstyled\">\n          <li class=\"link-type\">\n            <router-link to=\"/\">\n              回首页\n            </router-link>\n          </li>\n        </ul>\n      </el-col>\n      <el-col :span=\"12\">\n        <img :src=\"errGif\" width=\"313\" height=\"428\" alt=\"Girl has dropped her ice cream.\">\n      </el-col>\n    </el-row>\n  </div>\n</template>\n\n<script>\nimport errGif from '@/assets/401_images/401.gif'\n\nexport default {\n  name: 'Page401',\n  data() {\n    return {\n      errGif: errGif + '?' + +new Date()\n    }\n  },\n  methods: {\n    back() {\n      if (this.$route.query.noGoBack) {\n        this.$router.push({ path: '/' })\n      } else {\n        this.$router.go(-1)\n      }\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n  .errPage-container {\n    width: 800px;\n    max-width: 100%;\n    margin: 100px auto;\n    .pan-back-btn {\n      background: #008489;\n      color: #fff;\n      border: none!important;\n    }\n    .pan-gif {\n      margin: 0 auto;\n      display: block;\n    }\n    .pan-img {\n      display: block;\n      margin: 0 auto;\n      width: 100%;\n    }\n    .text-jumbo {\n      font-size: 60px;\n      font-weight: 700;\n      color: #484848;\n    }\n    .list-unstyled {\n      font-size: 14px;\n      li {\n        padding-bottom: 5px;\n      }\n      a {\n        color: #008489;\n        text-decoration: none;\n        &:hover {\n          text-decoration: underline;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/views/error/404.vue",
    "content": "<template>\n  <div class=\"wscn-http404-container\">\n    <div class=\"wscn-http404\">\n      <div class=\"pic-404\">\n        <img class=\"pic-404__parent\" src=\"@/assets/404_images/404.png\" alt=\"404\">\n        <img class=\"pic-404__child left\" src=\"@/assets/404_images/404_cloud.png\" alt=\"404\">\n        <img class=\"pic-404__child mid\" src=\"@/assets/404_images/404_cloud.png\" alt=\"404\">\n        <img class=\"pic-404__child right\" src=\"@/assets/404_images/404_cloud.png\" alt=\"404\">\n      </div>\n      <div class=\"bullshit\">\n        <div class=\"bullshit__oops\">\n          404错误!\n        </div>\n        <div class=\"bullshit__headline\">\n          {{ message }}\n        </div>\n        <div class=\"bullshit__info\">\n          对不起，您正在寻找的页面不存在。尝试检查URL的错误，然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。\n        </div>\n        <router-link to=\"/\" class=\"bullshit__return-home\">\n          返回首页\n        </router-link>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\n\nexport default {\n  name: 'Page404',\n  computed: {\n    message() {\n      return '找不到网页！'\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.wscn-http404-container{\n  transform: translate(-50%,-50%);\n  position: absolute;\n  top: 40%;\n  left: 50%;\n}\n.wscn-http404 {\n  position: relative;\n  width: 1200px;\n  padding: 0 50px;\n  overflow: hidden;\n  .pic-404 {\n    position: relative;\n    float: left;\n    width: 600px;\n    overflow: hidden;\n    &__parent {\n      width: 100%;\n    }\n    &__child {\n      position: absolute;\n      &.left {\n        width: 80px;\n        top: 17px;\n        left: 220px;\n        opacity: 0;\n        animation-name: cloudLeft;\n        animation-duration: 2s;\n        animation-timing-function: linear;\n        animation-fill-mode: forwards;\n        animation-delay: 1s;\n      }\n      &.mid {\n        width: 46px;\n        top: 10px;\n        left: 420px;\n        opacity: 0;\n        animation-name: cloudMid;\n        animation-duration: 2s;\n        animation-timing-function: linear;\n        animation-fill-mode: forwards;\n        animation-delay: 1.2s;\n      }\n      &.right {\n        width: 62px;\n        top: 100px;\n        left: 500px;\n        opacity: 0;\n        animation-name: cloudRight;\n        animation-duration: 2s;\n        animation-timing-function: linear;\n        animation-fill-mode: forwards;\n        animation-delay: 1s;\n      }\n      @keyframes cloudLeft {\n        0% {\n          top: 17px;\n          left: 220px;\n          opacity: 0;\n        }\n        20% {\n          top: 33px;\n          left: 188px;\n          opacity: 1;\n        }\n        80% {\n          top: 81px;\n          left: 92px;\n          opacity: 1;\n        }\n        100% {\n          top: 97px;\n          left: 60px;\n          opacity: 0;\n        }\n      }\n      @keyframes cloudMid {\n        0% {\n          top: 10px;\n          left: 420px;\n          opacity: 0;\n        }\n        20% {\n          top: 40px;\n          left: 360px;\n          opacity: 1;\n        }\n        70% {\n          top: 130px;\n          left: 180px;\n          opacity: 1;\n        }\n        100% {\n          top: 160px;\n          left: 120px;\n          opacity: 0;\n        }\n      }\n      @keyframes cloudRight {\n        0% {\n          top: 100px;\n          left: 500px;\n          opacity: 0;\n        }\n        20% {\n          top: 120px;\n          left: 460px;\n          opacity: 1;\n        }\n        80% {\n          top: 180px;\n          left: 340px;\n          opacity: 1;\n        }\n        100% {\n          top: 200px;\n          left: 300px;\n          opacity: 0;\n        }\n      }\n    }\n  }\n  .bullshit {\n    position: relative;\n    float: left;\n    width: 300px;\n    padding: 30px 0;\n    overflow: hidden;\n    &__oops {\n      font-size: 32px;\n      font-weight: bold;\n      line-height: 40px;\n      color: #1482f0;\n      opacity: 0;\n      margin-bottom: 20px;\n      animation-name: slideUp;\n      animation-duration: 0.5s;\n      animation-fill-mode: forwards;\n    }\n    &__headline {\n      font-size: 20px;\n      line-height: 24px;\n      color: #222;\n      font-weight: bold;\n      opacity: 0;\n      margin-bottom: 10px;\n      animation-name: slideUp;\n      animation-duration: 0.5s;\n      animation-delay: 0.1s;\n      animation-fill-mode: forwards;\n    }\n    &__info {\n      font-size: 13px;\n      line-height: 21px;\n      color: grey;\n      opacity: 0;\n      margin-bottom: 30px;\n      animation-name: slideUp;\n      animation-duration: 0.5s;\n      animation-delay: 0.2s;\n      animation-fill-mode: forwards;\n    }\n    &__return-home {\n      display: block;\n      float: left;\n      width: 110px;\n      height: 36px;\n      background: #1482f0;\n      border-radius: 100px;\n      text-align: center;\n      color: #ffffff;\n      opacity: 0;\n      font-size: 14px;\n      line-height: 36px;\n      cursor: pointer;\n      animation-name: slideUp;\n      animation-duration: 0.5s;\n      animation-delay: 0.3s;\n      animation-fill-mode: forwards;\n    }\n    @keyframes slideUp {\n      0% {\n        transform: translateY(60px);\n        opacity: 0;\n      }\n      100% {\n        transform: translateY(0);\n        opacity: 1;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/views/imt/item/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-refresh\"\n          size=\"mini\"\n          @click=\"handleRefresh\"\n          >刷新i茅台预约商品列表</el-button\n        >\n      </el-col>\n\n      <right-toolbar\n        :showSearch.sync=\"showSearch\"\n        @queryTable=\"getList\"\n      ></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"itemList\">\n      <!-- <el-table-column label=\"id\" align=\"center\" prop=\"itemId\" /> -->\n      <el-table-column label=\"预约商品code\" align=\"center\" prop=\"itemCode\" />\n      <el-table-column label=\"标题\" align=\"center\" prop=\"title\" />\n      <el-table-column\n        label=\"内容\"\n        align=\"center\"\n        prop=\"content\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column label=\"图片\" align=\"center\" prop=\"picture\">\n        <template slot-scope=\"scope\">\n          <el-image\n            style=\"width: 100px; \"\n            :src=\"scope.row.picture\"\n            fit=\"fit\"\n          ></el-image>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n      />\n    </el-table>\n  </div>\n</template>\n\n<script>\nimport { listItem, refreshItem } from \"@/api/imt/item\";\n\nexport default {\n  name: \"Item\",\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n\n      // 显示搜索条件\n      showSearch: true,\n      // I茅台预约商品列表格数据\n      itemList: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {},\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询I茅台预约商品列列表 */\n    getList() {\n      this.loading = true;\n      listItem().then((response) => {\n        this.itemList = response.data;\n        this.loading = false;\n      });\n    },\n\n    /** 刷新i茅台预约商品列表 */\n    handleRefresh() {\n      refreshItem().then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"刷新成功\");\n      });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/imt/log/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form\n      :model=\"queryParams\"\n      ref=\"queryForm\"\n      size=\"small\"\n      :inline=\"true\"\n      v-show=\"showSearch\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户\" prop=\"mobile\">\n        <el-input\n          v-model=\"queryParams.mobile\"\n          placeholder=\"请输入用户\"\n          clearable\n          style=\"width: 240px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"操作状态\"\n          clearable\n          style=\"width: 240px\"\n        >\n          <el-option\n            v-for=\"dict in dict.type.sys_common_status\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"操作时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 340px\"\n          value-format=\"yyyy-MM-dd HH:mm:ss\"\n          type=\"datetimerange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n      <el-form-item>\n        <el-button\n          type=\"primary\"\n          icon=\"el-icon-search\"\n          size=\"mini\"\n          @click=\"handleQuery\"\n          >搜索</el-button\n        >\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\"\n          >重置</el-button\n        >\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['monitor:operlog:remove']\"\n          >删除</el-button\n        >\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          @click=\"handleClean\"\n          v-hasPermi=\"['monitor:operlog:remove']\"\n          >清空</el-button\n        >\n      </el-col>\n      <right-toolbar\n        :showSearch.sync=\"showSearch\"\n        @queryTable=\"getList\"\n      ></right-toolbar>\n    </el-row>\n\n    <el-table\n      ref=\"tables\"\n      v-loading=\"loading\"\n      :data=\"list\"\n      @selection-change=\"handleSelectionChange\"\n      @sort-change=\"handleSortChange\"\n    >\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"用户\" align=\"center\" prop=\"mobile\" />\n      <el-table-column\n        label=\"日志记录内容\"\n        align=\"center\"\n        prop=\"logContent\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column label=\"操作状态\" align=\"center\" prop=\"status\">\n        <template slot-scope=\"scope\">\n          <dict-tag\n            :options=\"dict.type.sys_common_status\"\n            :value=\"scope.row.status\"\n          />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"操作日期\"\n        align=\"center\"\n        prop=\"operTime\"\n        sortable=\"custom\"\n        width=\"180\"\n      >\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.operTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"操作\"\n        align=\"center\"\n        class-name=\"small-padding fixed-width\"\n      >\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-view\"\n            @click=\"handleView(scope.row, scope.index)\"\n            >详细</el-button\n          >\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total > 0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 操作日志详细 -->\n    <el-dialog\n      title=\"操作日志详细\"\n      :visible.sync=\"open\"\n      width=\"700px\"\n      append-to-body\n    >\n      <el-form ref=\"form\" :model=\"form\" label-width=\"100px\" size=\"mini\">\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"操作状态：\">\n              <div v-if=\"form.status === 0\">正常</div>\n              <div v-else-if=\"form.status === 1\">失败</div>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"操作时间：\">{{\n              parseTime(form.operTime)\n            }}</el-form-item>\n          </el-col>\n\n          <el-col :span=\"24\">\n            <el-form-item style=\"white-space: pre-wrap\" label=\"日志内容：\">{{\n              form.logContent\n            }}</el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button @click=\"open = false\">关 闭</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { list, delOperlog, cleanOperlog } from \"@/api/imt/log\";\n\nexport default {\n  name: \"Operlog\",\n  dicts: [\"sys_oper_type\", \"sys_common_status\"],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 表格数据\n      list: [],\n      // 是否显示弹出层\n      open: false,\n      // 日期范围\n      dateRange: [],\n      // 表单参数\n      form: {},\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        mobile: undefined,\n        logName: undefined,\n        status: undefined,\n      },\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询登录日志 */\n    getList() {\n      this.loading = true;\n      list(this.addDateRange(this.queryParams, this.dateRange)).then(\n        (response) => {\n          this.list = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 多选框选中数据 */\n    handleSelectionChange(selection) {\n      this.ids = selection.map((item) => item.logId);\n      this.multiple = !selection.length;\n    },\n    /** 排序触发事件 */\n    handleSortChange(column, prop, order) {\n      this.queryParams.orderByColumn = column.prop;\n      this.queryParams.isAsc = column.order;\n      this.getList();\n    },\n    /** 详细按钮操作 */\n    handleView(row) {\n      this.open = true;\n      this.form = row;\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const logIds = row.logId || this.ids;\n      this.$modal\n        .confirm('是否确认删除日志编号为\"' + logIds + '\"的数据项？')\n        .then(function () {\n          return delOperlog(logIds);\n        })\n        .then(() => {\n          this.getList();\n          this.$modal.msgSuccess(\"删除成功\");\n        })\n        .catch(() => {});\n    },\n    /** 清空按钮操作 */\n    handleClean() {\n      this.$modal\n        .confirm(\"是否确认清空所有操作日志数据项？\")\n        .then(function () {\n          return cleanOperlog();\n        })\n        .then(() => {\n          this.getList();\n          this.$modal.msgSuccess(\"清空成功\");\n        })\n        .catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download(\n        \"monitor/operlog/export\",\n        {\n          ...this.queryParams,\n        },\n        `operlog_${new Date().getTime()}.xlsx`\n      );\n    },\n  },\n};\n</script>\n\n"
  },
  {
    "path": "vue_campus_admin/src/views/imt/shop/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form\n      :model=\"queryParams\"\n      ref=\"queryForm\"\n      size=\"small\"\n      :inline=\"true\"\n      v-show=\"showSearch\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"商品ID\" prop=\"iShopId\">\n        <el-input\n          v-model=\"queryParams.iShopId\"\n          placeholder=\"请输入商品ID\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"省份\" prop=\"provinceName\">\n        <el-input\n          v-model=\"queryParams.provinceName\"\n          placeholder=\"请输入省份\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"城市\" prop=\"cityName\">\n        <el-input\n          v-model=\"queryParams.cityName\"\n          placeholder=\"请输入城市\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"地区\" prop=\"districtName\">\n        <el-input\n          v-model=\"queryParams.districtName\"\n          placeholder=\"请输入地区\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <!-- <el-form-item label=\"完整地址\" prop=\"fullAddress\">\n        <el-input\n          v-model=\"queryParams.fullAddress\"\n          placeholder=\"请输入完整地址\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item> -->\n      <!-- <el-form-item label=\"纬度\" prop=\"lat\">\n        <el-input\n          v-model=\"queryParams.lat\"\n          placeholder=\"请输入纬度\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"经度\" prop=\"lng\">\n        <el-input\n          v-model=\"queryParams.lng\"\n          placeholder=\"请输入经度\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item> -->\n      <!-- <el-form-item label=\"名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入名称\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item> -->\n      <el-form-item label=\"公司名称\" prop=\"tenantName\">\n        <el-input\n          v-model=\"queryParams.tenantName\"\n          placeholder=\"请输入公司名称\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button\n          type=\"primary\"\n          icon=\"el-icon-search\"\n          size=\"mini\"\n          @click=\"handleQuery\"\n          >搜索</el-button\n        >\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\"\n          >重置</el-button\n        >\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-refresh\"\n          size=\"mini\"\n          @click=\"handleRefresh\"\n          >刷新i茅台商品列表</el-button\n        >\n      </el-col>\n      <right-toolbar\n        :showSearch.sync=\"showSearch\"\n        @queryTable=\"getList\"\n      ></right-toolbar>\n    </el-row>\n\n    <el-table\n      v-loading=\"loading\"\n      :data=\"shopList\"\n    >\n      <!-- <el-table-column type=\"selection\" width=\"55\" align=\"center\" /> -->\n      <!-- <el-table-column label=\"ID\" align=\"center\" prop=\"shopId\" /> -->\n      <el-table-column label=\"商品ID\" align=\"center\" prop=\"ishopId\" />\n      <el-table-column label=\"省份\" align=\"center\" prop=\"provinceName\" />\n      <el-table-column label=\"城市\" align=\"center\" prop=\"cityName\" />\n      <el-table-column label=\"地区\" align=\"center\" prop=\"districtName\" />\n      <el-table-column label=\"完整地址\" align=\"center\" prop=\"fullAddress\" />\n      <el-table-column label=\"纬度\" align=\"center\" prop=\"lat\" />\n      <el-table-column label=\"经度\" align=\"center\" prop=\"lng\" />\n      <el-table-column label=\"名称\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"公司名称\" align=\"center\" prop=\"tenantName\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n      />\n\n      <!-- <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n            v-hasPermi=\"['system:shop:edit']\"\n          >修改</el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['system:shop:remove']\"\n          >删除</el-button>\n        </template>\n      </el-table-column> -->\n    </el-table>\n\n    <pagination\n      v-show=\"total > 0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </div>\n</template>\n\n<script>\nimport { listShop, refreshShop } from \"@/api/imt/shop\";\n\nexport default {\n  name: \"Shop\",\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // i茅台商品表格数据\n      shopList: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        iShopId: null,\n        provinceName: null,\n        cityName: null,\n        districtName: null,\n        fullAddress: null,\n        lat: null,\n        lng: null,\n        name: null,\n        tenantName: null,\n      },\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {},\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询i茅台商品列表 */\n    getList() {\n      this.loading = true;\n      listShop(this.queryParams).then((response) => {\n        this.shopList = response.rows;\n        this.total = response.total;\n        this.loading = false;\n      });\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        shopId: null,\n        iShopId: null,\n        provinceName: null,\n        cityName: null,\n        districtName: null,\n        fullAddress: null,\n        lat: null,\n        lng: null,\n        name: null,\n        tenantName: null,\n        createTime: null,\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n\n    handleRefresh() {\n      refreshShop().then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"刷新成功\");\n      });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/imt/user/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form\n      :model=\"queryParams\"\n      ref=\"queryForm\"\n      size=\"small\"\n      :inline=\"true\"\n      v-show=\"showSearch\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"手机号\" prop=\"mobile\">\n        <el-input\n          v-model=\"queryParams.mobile\"\n          placeholder=\"请输入I茅台手机号\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户id\" prop=\"userId\">\n        <el-input\n          v-model=\"queryParams.userId\"\n          placeholder=\"请输入I茅台用户id\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <!-- <el-form-item label=\"商品预约code，用@间隔\" prop=\"itemCode\">\n        <el-input\n          v-model=\"queryParams.itemCode\"\n          placeholder=\"请输入商品预约code，用@间隔\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item> -->\n      <el-form-item label=\"省份\" prop=\"provinceName\">\n        <el-input\n          v-model=\"queryParams.provinceName\"\n          placeholder=\"请输入省份\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"城市\" prop=\"cityName\">\n        <el-input\n          v-model=\"queryParams.cityName\"\n          placeholder=\"请输入城市\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <!-- <el-form-item label=\"完整地址\" prop=\"address\">\n        <el-input\n          v-model=\"queryParams.address\"\n          placeholder=\"请输入完整地址\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item> -->\n\n      <el-form-item label=\"到期时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 240px\"\n          value-format=\"yyyy-MM-dd\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n\n      <el-form-item>\n        <el-button\n          type=\"primary\"\n          icon=\"el-icon-search\"\n          size=\"mini\"\n          @click=\"handleQuery\"\n        >搜索\n        </el-button\n        >\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\"\n        >重置\n        </el-button\n        >\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAddIUser\"\n        >添加账号\n        </el-button\n        >\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAdd\"\n        >直接新增\n        </el-button\n        >\n      </el-col>\n\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"success\"\n          plain\n          icon=\"el-icon-edit\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleUpdate\"\n        >修改\n        </el-button\n        >\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n        >删除\n        </el-button\n        >\n      </el-col>\n\n      <right-toolbar\n        :showSearch.sync=\"showSearch\"\n        @queryTable=\"getList\"\n      ></right-toolbar>\n    </el-row>\n\n    <el-table\n      v-loading=\"loading\"\n      :data=\"userList\"\n      @selection-change=\"handleSelectionChange\"\n    >\n      <el-table-column type=\"expand\">\n        <template slot-scope=\"props\">\n          <el-form label-position=\"left\" inline class=\"demo-table-expand\">\n            <el-form-item label=\"手机号\">\n              <span>{{ props.row.mobile }}</span>\n            </el-form-item>\n            <el-form-item label=\"I茅台用户id\">\n              <span>{{ props.row.userId }}</span>\n            </el-form-item>\n            <el-form-item label=\"备注\">\n              <span>{{ props.row.remark }}</span>\n            </el-form-item>\n            <el-form-item label=\"i茅台token\">\n              <span v-if=\"props.row.token\">{{\n                  props.row.token.substring(0, 5) + \"......\"\n                }}</span>\n            </el-form-item>\n            <el-form-item label=\"i茅台cookie\">\n              <span v-if=\"props.row.cookie\">{{\n                  props.row.cookie.substring(0, 5) + \"......\"\n                }}</span>\n            </el-form-item>\n            <el-form-item label=\"pulsh推送token\">\n              <span v-if=\"props.row.pushPlusToken\">{{\n                  props.row.pushPlusToken.substring(0, 5) + \"......\"\n                }}</span>\n            </el-form-item>\n            <el-form-item label=\"设备id\">\n              <span>{{ props.row.deviceId }}</span>\n            </el-form-item>\n            <el-form-item label=\"预约项目code\">\n              <span>{{ props.row.itemCode }}</span>\n            </el-form-item>\n            <el-form-item label=\"省份\">\n              <span>{{ props.row.provinceName }}</span>\n            </el-form-item>\n            <el-form-item label=\"城市\">\n              <span>{{ props.row.cityName }}</span>\n            </el-form-item>\n            <el-form-item label=\"纬度\">\n              <span>{{ props.row.lat }}</span>\n            </el-form-item>\n            <el-form-item label=\"经度\">\n              <span>{{ props.row.lng }}</span>\n            </el-form-item>\n            <el-form-item label=\"经度\">\n              <span>{{ props.row.lng }}</span>\n            </el-form-item>\n            <el-form-item label=\"随机预约\">\n              <dict-tag\n                :options=\"dict.type.sys_normal_disable\"\n                :value=\"props.row.randomMinute\"\n              />\n            </el-form-item>\n            <el-form-item label=\"创建时间\">\n              <span>{{ props.row.createTime }}</span>\n            </el-form-item>\n            <el-form-item label=\"到期时间\">\n              <span>{{ props.row.expireTime }}</span>\n            </el-form-item>\n            <el-form-item label=\"创建人\">\n              <span>{{ props.row.createUser }}</span>\n            </el-form-item>\n          </el-form>\n        </template>\n      </el-table-column>\n\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\"/>\n      <el-table-column label=\"手机号\" align=\"center\" prop=\"mobile\"/>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\"/>\n      <el-table-column label=\"预约项目code\" align=\"center\" prop=\"itemCode\"/>\n      <el-table-column label=\"省份\" align=\"center\" prop=\"provinceName\"/>\n      <el-table-column\n        label=\"类型\"\n        align=\"center\"\n        prop=\"shopType\"\n        :show-overflow-tooltip=\"true\"\n      >\n        <template slot-scope=\"scope\">\n          <span>{{\n              scope.row.shopType == 1 ? \"预约出货量最大门店\" : \"预约附近门店\"\n            }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"预约执行分钟\" align=\"center\" prop=\"minute\"/>\n\n      <el-table-column\n        label=\"到期时间\"\n        align=\"center\"\n        prop=\"expireTime\"\n        width=\"180\"\n      >\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.expireTime, \"{y}-{m}-{d}\") }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"操作\"\n        align=\"center\"\n        class-name=\"small-padding fixed-width\"\n      >\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-thumb\"\n            @click=\"reservation(scope.row)\"\n          >预约\n          </el-button\n          >\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-thumb\"\n            @click=\"travelReward(scope.row)\"\n          >旅行\n          </el-button\n          >\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n          >修改\n          </el-button\n          >\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-refresh\"\n            @click=\"handleUpdateToken(scope.row)\"\n          >\n            刷新token\n          </el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n          >删除\n          </el-button\n          >\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total > 0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 添加或修改I茅台用户对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"600px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"100px\">\n        <el-form-item v-if=\"toAdd != 1\" label=\"手机号\" prop=\"mobile\">\n          <el-input v-model=\"form.mobile\" placeholder=\"请输入I茅台用户手机号\"/>\n        </el-form-item>\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"备注\" prop=\"remark\">\n              <el-input v-model=\"form.remark\" placeholder=\"请输入备注\"/>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"用户id\" prop=\"userId\">\n              <el-input v-model=\"form.userId\" placeholder=\"请输入I茅台用户id\"/>\n            </el-form-item>\n          </el-col>\n        </el-row>\n\n        <el-form-item label=\"toekn\" prop=\"token\">\n          <el-input v-model=\"form.token\" placeholder=\"请输入I茅台toekn\"/>\n        </el-form-item>\n        <el-form-item label=\"cookie\" prop=\"cookie\">\n          <el-input v-model=\"form.cookie\" placeholder=\"请输入I茅台cookie\"/>\n        </el-form-item>\n        <el-form-item label=\"设备id\" prop=\"deviceId\">\n          <el-input v-model=\"form.deviceId\" placeholder=\"请输入设备id\"/>\n        </el-form-item>\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"预约code\" prop=\"itemCode\">\n              <!-- <el-input\n                v-model=\"form.itemCode\"\n                placeholder=\"请输入商品预约code，用@间隔\"\n              /> -->\n              <el-select\n                v-model=\"itemSelect\"\n                multiple\n                placeholder=\"请选择\"\n                @change=\"changeItem\"\n              >\n                <el-option\n                  v-for=\"item in itemList\"\n                  :key=\"item.itemCode\"\n                  :label=\"item.title\"\n                  :value=\"item.itemCode\"\n                >\n                </el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"分钟\" prop=\"minute\">\n              <el-input\n                v-model=\"form.minute\"\n                placeholder=\"预约执行的时间(单位分)，例如15分执行\"\n              />\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"随机时间预约\" prop=\"randomMinute\">\n              <el-select\n                style=\"width: 150px\"\n                v-model=\"form.randomMinute\"\n                placeholder=\"随机时间预约\"\n              >\n                <el-option\n                  v-for=\"dict in dict.type.sys_normal_disable\"\n                  :key=\"dict.value\"\n                  :label=\"dict.label\"\n                  :value=\"dict.value\"\n                ></el-option>\n              </el-select>\n            </el-form-item\n            >\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"类型\" prop=\"shopType\">\n              <el-select v-model=\"form.shopType\" placeholder=\"请选择\">\n                <el-option\n                  v-for=\"item in typeOptions\"\n                  :key=\"item.value\"\n                  :label=\"item.label\"\n                  :value=\"item.value\"\n                >\n                </el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n        </el-row>\n\n        <!-- <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"省份\" prop=\"provinceName\">\n              <el-input\n                v-model=\"form.provinceName\"\n                placeholder=\"请输入省份\"\n              /> </el-form-item\n          ></el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"城市\" prop=\"cityName\">\n              <el-input\n                v-model=\"form.cityName\"\n                placeholder=\"请输入城市\"\n              /> </el-form-item\n          ></el-col>\n        </el-row>\n\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"纬度\" prop=\"lat\">\n              <el-input v-model=\"form.lat\" placeholder=\"请输入纬度\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"经度\" prop=\"lng\">\n              <el-input v-model=\"form.lng\" placeholder=\"请输入经度\" />\n            </el-form-item>\n          </el-col>\n        </el-row> -->\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"门店商品ID\" prop=\"ishopId\">\n              <el-input v-model=\"form.ishopId\" placeholder=\"请输入门店商品ID\"/>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"推送token\" prop=\"pushPlusToken\">\n              <el-input\n                v-model=\"form.pushPlusToken\"\n                placeholder=\"请输入推送token\"\n              />\n            </el-form-item>\n          </el-col>\n        </el-row>\n\n        <!-- <el-form-item label=\"完整地址\" prop=\"address\">\n          <el-input v-model=\"form.address\" placeholder=\"请输入完整地址\" />\n        </el-form-item> -->\n\n        <el-form-item label=\"到期时间\" prop=\"expireTime\">\n          <el-date-picker\n            v-model=\"form.expireTime\"\n            type=\"datetime\"\n            value-format=\"yyyy-MM-dd HH:mm:ss\"\n            placeholder=\"选择日期时间\"\n          >\n          </el-date-picker>\n        </el-form-item>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n\n    <el-dialog\n      title=\"添加\\更新用户\"\n      :visible.sync=\"openUser\"\n      width=\"500px\"\n      append-to-body\n    >\n      <el-form ref=\"form\" :model=\"form\">\n        <el-form-item label=\"手机号\" prop=\"mobile\">\n          <el-input v-model=\"form.mobile\" placeholder=\"请输入I茅台用户手机号\"/>\n          <div style=\"margin-top: 10px\">\n            <el-button\n              type=\"primary\"\n              @click=\"sendCode(form.mobile)\"\n              :disabled=\"state\"\n            >发送验证码<span v-if=\"state\">({{ stateNum }})</span>\n            </el-button>\n          </div>\n        </el-form-item>\n\n        <el-form-item label=\"验证码\" prop=\"userId\">\n          <el-input v-model=\"form.code\" placeholder=\"请输入验证码\"/>\n        </el-form-item>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"login(form.mobile, form.code)\"\n        >登 录\n        </el-button\n        >\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n\n    <el-dialog :title=\"title\" :visible.sync=\"refreshToken\" width=\"500px\">\n      <el-form ref=\"form\" :model=\"form\">\n        <el-form-item label=\"手机号\" prop=\"mobile\">\n          <el-input\n            v-model=\"form.mobile\"\n            readonly\n            placeholder=\"请输入I茅台用户手机号\"\n          />\n          <div style=\"margin-top: 10px\">\n            <el-button\n              type=\"primary\"\n              @click=\"sendCode(form.mobile, form.deviceId)\"\n              :disabled=\"state\"\n            >发送验证码<span v-if=\"state\">({{ stateNum }})</span>\n            </el-button>\n          </div>\n        </el-form-item>\n\n        <el-form-item label=\"验证码\" prop=\"code\">\n          <el-input v-model=\"form.code\" placeholder=\"请输入验证码\"/>\n        </el-form-item>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button\n          type=\"primary\"\n          @click=\"refresh(form.mobile, form.code, form.deviceId, 1)\"\n        >刷 新\n        </el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport {\n  listUser,\n  getUser,\n  delUser,\n  addUser,\n  updateUser,\n  sendCode,\n  login,\n  reservation,\n  travelReward,\n} from \"@/api/imt/user\";\n\nimport {listItem} from \"@/api/imt/item\";\n\nexport default {\n  name: \"User\",\n  dicts: [\"sys_normal_disable\"],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // I茅台用户表格数据\n      userList: [],\n      // 日期范围\n      dateRange: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      openUser: false,\n      refreshToken: false,\n      // 发送短信按钮倒计时\n      state: false,\n      stateNum: 60,\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        mobile: null,\n        userId: null,\n        token: null,\n        itemCode: null,\n        deviceId: null,\n        provinceName: null,\n        cityName: null,\n        address: null,\n        lat: null,\n        lng: null,\n        shopType: null,\n        jsonResult: null,\n        expireTime: null,\n      },\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {\n        mobile: [\n          {required: true, message: \"手机号不能为空\", trigger: \"blur\"},\n        ],\n      },\n      //0:新增，1:修改\n      toAdd: 0,\n      typeOptions: [\n        {\n          value: 1,\n          label: \"预约本市出货量最大的门店\",\n        },\n        {\n          value: 2,\n          label: \"预约你的位置(经纬度)附近门店\",\n        },\n      ],\n      // I茅台预约商品列表格数据\n      itemList: [],\n      //选择的数据\n      itemSelect: [],\n    };\n  },\n  created() {\n    this.getList();\n    listItem().then((response) => {\n      this.itemList = response.data;\n    });\n  },\n  methods: {\n    //item下拉框选择\n    changeItem(e) {\n      this.form.itemCode = \"\";\n      this.itemSelect.forEach((e) => {\n        this.form.itemCode += e + \"@\";\n      });\n    },\n    guid() {\n      return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(\n        /[xy]/g,\n        function (c) {\n          var r = (Math.random() * 16) | 0,\n            v = c == \"x\" ? r : (r & 0x3) | 0x8;\n          return v.toString(16);\n        }\n      );\n    },\n    /** 查询I茅台用户列表 */\n    getList() {\n      this.loading = true;\n      listUser(this.addDateRange(this.queryParams, this.dateRange)).then(\n        (response) => {\n          this.userList = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.openUser = false;\n      this.refreshToken = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        mobile: null,\n        userId: null,\n        token: null,\n        itemCode: null,\n        provinceName: null,\n        cityName: null,\n        address: null,\n        lat: null,\n        lng: null,\n        jsonResult: null,\n        createTime: null,\n        minute: 5,\n        shopType: 1,\n        ishopId: null,\n        randomMinute: \"0\",\n        remark: null,\n        expireTime: null,\n        pushPlusToken: null,\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map((item) => item.mobile);\n      this.single = selection.length !== 1;\n      this.multiple = !selection.length;\n    },\n    /** 新增按钮操作 */\n    handleAdd() {\n      this.reset();\n      this.open = true;\n      this.title = \"添加I茅台用户\";\n      this.toAdd = 0;\n      this.itemSelect = [];\n    },\n    handleAddIUser() {\n      this.reset();\n      this.openUser = true;\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      const mobile = row.mobile || this.ids;\n      getUser(mobile).then((response) => {\n        this.toAdd = 1;\n        this.form = response.data;\n        this.open = true;\n        this.title = \"修改I茅台用户\";\n        this.itemSelect = [];\n        if (\n          this.form.itemCode.indexOf(\"@\") == -1 &&\n          this.form.itemCode !== \"\"\n        ) {\n          this.itemSelect.push(this.form.itemCode);\n        } else {\n          let arr = this.form.itemCode.split(\"@\");\n          arr.forEach((e) => {\n            if (e !== \"\") {\n              this.itemSelect.push(e);\n            }\n          });\n        }\n      });\n    },\n    //预约\n    reservation(row) {\n      const mobile = row.mobile || this.ids;\n      reservation(mobile).then((response) => {\n        this.$modal.msgSuccess(\"请求成功，结果看日志\");\n      });\n    },\n    //小茅运旅行活动\n    travelReward(row) {\n      const mobile = row.mobile || this.ids;\n      travelReward(mobile).then((response) => {\n        this.$modal.msgSuccess(\"请求成功，结果看日志\");\n      });\n    },\n    /** 提交按钮 */\n    submitForm() {\n      this.$refs[\"form\"].validate((valid) => {\n        if (valid) {\n          if (this.toAdd != 0) {\n            updateUser(this.form).then((response) => {\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            addUser(this.form).then((response) => {\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    //发生验证码\n    sendCode(mobile, deviceId) {\n      if (deviceId == undefined || deviceId == \"\") {\n        this.form.deviceId = this.guid();\n      } else {\n        this.form.deviceId = deviceId;\n      }\n      sendCode(mobile, this.form.deviceId).then((response) => {\n        this.$modal.msgSuccess(\"发送成功\");\n        this.state = true;\n        let timer = setInterval(() => {\n          this.stateNum--;\n          if (this.stateNum === 0) {\n            clearInterval(timer);\n            this.state = false;\n            this.stateNum = 60;\n          }\n        }, 1000);\n      });\n    },\n    //登录\n    login(mobile, code) {\n      this.refresh(mobile, code, this.form.deviceId, 0);\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const mobiles = row.mobile || this.ids;\n      this.$modal\n        .confirm('是否确认删除I茅台用户编号为\"' + mobiles + '\"的数据项？')\n        .then(function () {\n          return delUser(mobiles);\n        })\n        .then(() => {\n          this.getList();\n          this.$modal.msgSuccess(\"删除成功\");\n        })\n        .catch(() => {\n        });\n    },\n    refresh(mobile, code, deviceId, status) {\n      const msg = status ? \"刷新成功\" : \"登录成功\";\n      login(mobile, code, deviceId).then((response) => {\n        this.$modal.msgSuccess(msg);\n        this.open = false;\n        this.openUser = false;\n        this.refreshToken = false;\n        this.getList();\n      });\n    },\n    handleUpdateToken(row) {\n      this.refreshToken = true;\n      this.form = {\n        mobile: row.mobile,\n        deviceId: row.deviceId,\n      };\n      this.title = \"刷新用户:\" + row.remark + \"(\" + row.mobile + \")登录信息\";\n    },\n  },\n};\n</script>\n<style>\n.demo-table-expand {\n  font-size: 0;\n}\n\n.demo-table-expand label {\n  width: 120px;\n  color: #99a9bf;\n}\n\n.demo-table-expand .el-form-item {\n  margin-right: 0;\n  margin-bottom: 0;\n  width: 50%;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/views/index.vue",
    "content": "<template>\n  <div class=\"app-container home\">\n    <p>\n      本项目中所有内容只供学习和研究使用，不得将本项目中任何内容用于违反国家/地区/组织等的法律法规或相关规定的其他用途。\n    </p>\n    <p>\n      所有直接或间接使用本项目的个人和组织，应24小时内完成学习和研究，并及时删除本项目中的所有内容。如对本项目的功能有需求，应自行开发相关功能。\n    </p>\n    <p></p>\n    <p>本项目免费，无任何盈利</p>\n    <p>项目完全开源，更新地址：https://github.com/oddfar/campus-imaotai</p>\n\n    <el-card class=\"box-card\">\n      <p>版本情况:</p>\n      <p>campus-imaotai:{{ version }}</p>\n      <p>campus框架:{{ frameworkVersion }}</p>\n    </el-card>\n  </div>\n</template>\n\n<script>\nimport {getVersion} from \"@/api/system/index\";\n\nexport default {\n  name: \"Index\",\n  data() {\n    return {\n      // 版本号\n      version: \"\",\n      frameworkVersion: \"\",\n    };\n  },\n  created() {\n    this.getVersion();\n  },\n  methods: {\n    getVersion() {\n      getVersion().then((response) => {\n          this.version = response.data.version;\n          this.frameworkVersion = response.data.frameworkVersion;\n        }\n      );\n    }\n  },\n};\n</script>\n\n<style scoped lang=\"scss\">\n.home {\n  blockquote {\n    padding: 10px 20px;\n    margin: 0 0 20px;\n    font-size: 17.5px;\n    border-left: 5px solid #eee;\n  }\n\n  hr {\n    margin-top: 20px;\n    margin-bottom: 20px;\n    border: 0;\n    border-top: 1px solid #eee;\n  }\n\n  .col-item {\n    margin-bottom: 20px;\n  }\n\n  ul {\n    padding: 0;\n    margin: 0;\n  }\n\n  font-family: \"open sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 13px;\n  color: #676a6c;\n  overflow-x: hidden;\n\n  ul {\n    list-style-type: none;\n  }\n\n  h4 {\n    margin-top: 0px;\n  }\n\n  h2 {\n    margin-top: 10px;\n    font-size: 26px;\n    font-weight: 100;\n  }\n\n  p {\n    margin-top: 10px;\n\n    b {\n      font-weight: 700;\n    }\n  }\n\n  .update-log {\n    ol {\n      display: block;\n      list-style-type: decimal;\n      margin-block-start: 1em;\n      margin-block-end: 1em;\n      margin-inline-start: 0;\n      margin-inline-end: 0;\n      padding-inline-start: 40px;\n    }\n  }\n}\n</style>\n\n"
  },
  {
    "path": "vue_campus_admin/src/views/index_v1.vue",
    "content": "<template>\n  <div class=\"dashboard-editor-container\">\n\n    <panel-group @handleSetLineChartData=\"handleSetLineChartData\" />\n\n    <el-row style=\"background:#fff;padding:16px 16px 0;margin-bottom:32px;\">\n      <line-chart :chart-data=\"lineChartData\" />\n    </el-row>\n\n    <el-row :gutter=\"32\">\n      <el-col :xs=\"24\" :sm=\"24\" :lg=\"8\">\n        <div class=\"chart-wrapper\">\n          <raddar-chart />\n        </div>\n      </el-col>\n      <el-col :xs=\"24\" :sm=\"24\" :lg=\"8\">\n        <div class=\"chart-wrapper\">\n          <pie-chart />\n        </div>\n      </el-col>\n      <el-col :xs=\"24\" :sm=\"24\" :lg=\"8\">\n        <div class=\"chart-wrapper\">\n          <bar-chart />\n        </div>\n      </el-col>\n    </el-row>\n\n    \n  </div>\n</template>\n\n<script>\nimport PanelGroup from './dashboard/PanelGroup'\nimport LineChart from './dashboard/LineChart'\nimport RaddarChart from './dashboard/RaddarChart'\nimport PieChart from './dashboard/PieChart'\nimport BarChart from './dashboard/BarChart'\n\nconst lineChartData = {\n  newVisitis: {\n    expectedData: [100, 120, 161, 134, 105, 160, 165],\n    actualData: [120, 82, 91, 154, 162, 140, 145]\n  },\n  messages: {\n    expectedData: [200, 192, 120, 144, 160, 130, 140],\n    actualData: [180, 160, 151, 106, 145, 150, 130]\n  },\n  purchases: {\n    expectedData: [80, 100, 121, 104, 105, 90, 100],\n    actualData: [120, 90, 100, 138, 142, 130, 130]\n  },\n  shoppings: {\n    expectedData: [130, 140, 141, 142, 145, 150, 160],\n    actualData: [120, 82, 91, 154, 162, 140, 130]\n  }\n}\n\nexport default {\n  name: 'Index',\n  components: {\n    PanelGroup,\n    LineChart,\n    RaddarChart,\n    PieChart,\n    BarChart\n  },\n  data() {\n    return {\n      lineChartData: lineChartData.newVisitis\n    }\n  },\n  methods: {\n    handleSetLineChartData(type) {\n      this.lineChartData = lineChartData[type]\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.dashboard-editor-container {\n  padding: 32px;\n  background-color: rgb(240, 242, 245);\n  position: relative;\n\n  .chart-wrapper {\n    background: #fff;\n    padding: 16px 16px 0;\n    margin-bottom: 32px;\n  }\n}\n\n@media (max-width:1024px) {\n  .chart-wrapper {\n    padding: 8px;\n  }\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/views/login.vue",
    "content": "<template>\n  <div class=\"login\">\n    <el-form ref=\"loginForm\" :model=\"loginForm\" :rules=\"loginRules\" class=\"login-form\">\n      <h3 class=\"title\">campus-imaotai后台管理系统</h3>\n      <el-form-item prop=\"username\">\n        <el-input\n          v-model=\"loginForm.username\"\n          type=\"text\"\n          auto-complete=\"off\"\n          placeholder=\"账号\"\n        >\n          <svg-icon slot=\"prefix\" icon-class=\"user\" class=\"el-input__icon input-icon\" />\n        </el-input>\n      </el-form-item>\n      <el-form-item prop=\"password\">\n        <el-input\n          v-model=\"loginForm.password\"\n          type=\"password\"\n          auto-complete=\"off\"\n          placeholder=\"密码\"\n          @keyup.enter.native=\"handleLogin\"\n        >\n          <svg-icon slot=\"prefix\" icon-class=\"password\" class=\"el-input__icon input-icon\" />\n        </el-input>\n      </el-form-item>\n      <el-form-item prop=\"code\" v-if=\"captchaEnabled\">\n        <el-input\n          v-model=\"loginForm.code\"\n          auto-complete=\"off\"\n          placeholder=\"验证码\"\n          style=\"width: 63%\"\n          @keyup.enter.native=\"handleLogin\"\n        >\n          <svg-icon slot=\"prefix\" icon-class=\"validCode\" class=\"el-input__icon input-icon\" />\n        </el-input>\n        <div class=\"login-code\">\n          <img :src=\"codeUrl\" @click=\"getCode\" class=\"login-code-img\"/>\n        </div>\n      </el-form-item>\n      <el-checkbox v-model=\"loginForm.rememberMe\" style=\"margin:0px 0px 25px 0px;\">记住密码</el-checkbox>\n      <el-form-item style=\"width:100%;\">\n        <el-button\n          :loading=\"loading\"\n          size=\"medium\"\n          type=\"primary\"\n          style=\"width:100%;\"\n          @click.native.prevent=\"handleLogin\"\n        >\n          <span v-if=\"!loading\">登 录</span>\n          <span v-else>登 录 中...</span>\n        </el-button>\n        <div style=\"float: right;\" v-if=\"register\">\n          <router-link class=\"link-type\" :to=\"'/register'\">立即注册</router-link>\n        </div>\n      </el-form-item>\n    </el-form>\n    <!--  底部  -->\n    <div class=\"el-login-footer\">\n      <span>Copyright © 2023 oddfar.com All Rights Reserved.</span>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { getCodeImg } from \"@/api/login\";\nimport Cookies from \"js-cookie\";\nimport { encrypt, decrypt } from '@/utils/jsencrypt'\n\nexport default {\n  name: \"Login\",\n  data() {\n    return {\n      codeUrl: \"\",\n      loginForm: {\n        username: \"\",\n        password: \"\",\n        rememberMe: false,\n        code: \"\",\n        uuid: \"\"\n      },\n      loginRules: {\n        username: [\n          { required: true, trigger: \"blur\", message: \"请输入您的账号\" }\n        ],\n        password: [\n          { required: true, trigger: \"blur\", message: \"请输入您的密码\" }\n        ],\n        code: [{ required: true, trigger: \"change\", message: \"请输入验证码\" }]\n      },\n      loading: false,\n      // 验证码开关\n      captchaEnabled: true,\n      // 注册开关\n      register: false,\n      redirect: undefined\n    };\n  },\n  watch: {\n    $route: {\n      handler: function(route) {\n        this.redirect = route.query && route.query.redirect;\n      },\n      immediate: true\n    }\n  },\n  created() {\n    this.getCode();\n    this.getCookie();\n  },\n  methods: {\n    getCode() {\n      getCodeImg().then(res => {\n        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;\n        if (this.captchaEnabled) {\n          this.codeUrl = \"data:image/gif;base64,\" + res.img;\n          this.loginForm.uuid = res.uuid;\n        }\n      });\n    },\n    getCookie() {\n      const username = Cookies.get(\"username\");\n      const password = Cookies.get(\"password\");\n      const rememberMe = Cookies.get('rememberMe')\n      this.loginForm = {\n        username: username === undefined ? this.loginForm.username : username,\n        password: password === undefined ? this.loginForm.password : decrypt(password),\n        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)\n      };\n    },\n    handleLogin() {\n      this.$refs.loginForm.validate(valid => {\n        if (valid) {\n          this.loading = true;\n          if (this.loginForm.rememberMe) {\n            Cookies.set(\"username\", this.loginForm.username, { expires: 30 });\n            Cookies.set(\"password\", encrypt(this.loginForm.password), { expires: 30 });\n            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });\n          } else {\n            Cookies.remove(\"username\");\n            Cookies.remove(\"password\");\n            Cookies.remove('rememberMe');\n          }\n          this.$store.dispatch(\"Login\", this.loginForm).then(() => {\n            this.$router.push({ path: this.redirect || \"/\" }).catch(()=>{});\n          }).catch(() => {\n            this.loading = false;\n            if (this.captchaEnabled) {\n              this.getCode();\n            }\n          });\n        }\n      });\n    }\n  }\n};\n</script>\n\n<style rel=\"stylesheet/scss\" lang=\"scss\">\n.login {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n  background-image: url(\"../assets/images/login-background.jpg\");\n  background-size: cover;\n}\n.title {\n  margin: 0px auto 30px auto;\n  text-align: center;\n  color: #707070;\n}\n\n.login-form {\n  border-radius: 6px;\n  background: #ffffff;\n  width: 400px;\n  padding: 25px 25px 5px 25px;\n  .el-input {\n    height: 38px;\n    input {\n      height: 38px;\n    }\n  }\n  .input-icon {\n    height: 39px;\n    width: 14px;\n    margin-left: 2px;\n  }\n}\n.login-tip {\n  font-size: 13px;\n  text-align: center;\n  color: #bfbfbf;\n}\n.login-code {\n  width: 33%;\n  height: 38px;\n  float: right;\n  img {\n    cursor: pointer;\n    vertical-align: middle;\n  }\n}\n.el-login-footer {\n  height: 40px;\n  line-height: 40px;\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  text-align: center;\n  color: #fff;\n  font-family: Arial;\n  font-size: 12px;\n  letter-spacing: 1px;\n}\n.login-code-img {\n  height: 38px;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/views/monitor/job/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"任务名称\" prop=\"jobName\">\n        <el-input\n          v-model=\"queryParams.jobName\"\n          placeholder=\"请输入任务名称\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"任务组名\" prop=\"jobGroup\">\n        <el-select v-model=\"queryParams.jobGroup\" placeholder=\"请选择任务组名\" clearable>\n          <el-option\n            v-for=\"dict in dict.type.sys_job_group\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"任务状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" placeholder=\"请选择任务状态\" clearable>\n          <el-option\n            v-for=\"dict in dict.type.sys_job_status\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAdd\"\n          v-hasPermi=\"['monitor:job:add']\"\n        >新增</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"success\"\n          plain\n          icon=\"el-icon-edit\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleUpdate\"\n          v-hasPermi=\"['monitor:job:edit']\"\n        >修改</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['monitor:job:remove']\"\n        >删除</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleExport\"\n          v-hasPermi=\"['monitor:job:export']\"\n        >导出</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"info\"\n          plain\n          icon=\"el-icon-s-operation\"\n          size=\"mini\"\n          @click=\"handleJobLog\"\n          v-hasPermi=\"['monitor:job:query']\"\n        >日志</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"jobList\" @selection-change=\"handleSelectionChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"任务编号\" width=\"100\" align=\"center\" prop=\"jobId\" />\n      <el-table-column label=\"任务名称\" align=\"center\" prop=\"jobName\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"任务组名\" align=\"center\" prop=\"jobGroup\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_job_group\" :value=\"scope.row.jobGroup\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"调用目标字符串\" align=\"center\" prop=\"invokeTarget\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"cron执行表达式\" align=\"center\" prop=\"cronExpression\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"状态\" align=\"center\">\n        <template slot-scope=\"scope\">\n          <el-switch\n            v-model=\"scope.row.status\"\n            active-value=\"0\"\n            inactive-value=\"1\"\n            @change=\"handleStatusChange(scope.row)\"\n          ></el-switch>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n            v-hasPermi=\"['monitor:job:edit']\"\n          >修改</el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['monitor:job:remove']\"\n          >删除</el-button>\n          <el-dropdown size=\"mini\" @command=\"(command) => handleCommand(command, scope.row)\" v-hasPermi=\"['monitor:job:changeStatus', 'monitor:job:query']\">\n            <span class=\"el-dropdown-link\">\n              <i class=\"el-icon-d-arrow-right el-icon--right\"></i>更多\n            </span>\n            <el-dropdown-menu slot=\"dropdown\">\n              <el-dropdown-item command=\"handleRun\" icon=\"el-icon-caret-right\"\n                v-hasPermi=\"['monitor:job:changeStatus']\">执行一次</el-dropdown-item>\n              <el-dropdown-item command=\"handleView\" icon=\"el-icon-view\"\n                v-hasPermi=\"['monitor:job:query']\">任务详细</el-dropdown-item>\n              <el-dropdown-item command=\"handleJobLog\" icon=\"el-icon-s-operation\"\n                v-hasPermi=\"['monitor:job:query']\">调度日志</el-dropdown-item>\n            </el-dropdown-menu>\n          </el-dropdown>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 添加或修改定时任务对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"800px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"120px\">\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"任务名称\" prop=\"jobName\">\n              <el-input v-model=\"form.jobName\" placeholder=\"请输入任务名称\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"任务分组\" prop=\"jobGroup\">\n              <el-select v-model=\"form.jobGroup\" placeholder=\"请选择任务分组\">\n                <el-option\n                  v-for=\"dict in dict.type.sys_job_group\"\n                  :key=\"dict.value\"\n                  :label=\"dict.label\"\n                  :value=\"dict.value\"\n                ></el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item prop=\"invokeTarget\">\n              <span slot=\"label\">\n                调用方法\n                <el-tooltip placement=\"top\">\n                  <div slot=\"content\">\n                    Bean调用示例：ryTask.ryParams('ry')\n                    <br />Class类调用示例：com.ruoyi.quartz.task.RyTask.ryParams('ry')\n                    <br />参数说明：支持字符串，布尔类型，长整型，浮点型，整型\n                  </div>\n                  <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n              </span>\n              <el-input v-model=\"form.invokeTarget\" placeholder=\"请输入调用目标字符串\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"cron表达式\" prop=\"cronExpression\">\n              <el-input v-model=\"form.cronExpression\" placeholder=\"请输入cron执行表达式\">\n                <template slot=\"append\">\n                  <el-button type=\"primary\" @click=\"handleShowCron\">\n                    生成表达式\n                    <i class=\"el-icon-time el-icon--right\"></i>\n                  </el-button>\n                </template>\n              </el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"执行策略\" prop=\"misfirePolicy\">\n              <el-radio-group v-model=\"form.misfirePolicy\" size=\"small\">\n                <el-radio-button label=\"1\">立即执行</el-radio-button>\n                <el-radio-button label=\"2\">执行一次</el-radio-button>\n                <el-radio-button label=\"3\">放弃执行</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"是否并发\" prop=\"concurrent\">\n              <el-radio-group v-model=\"form.concurrent\" size=\"small\">\n                <el-radio-button label=\"0\">允许</el-radio-button>\n                <el-radio-button label=\"1\">禁止</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"状态\">\n              <el-radio-group v-model=\"form.status\">\n                <el-radio\n                  v-for=\"dict in dict.type.sys_job_status\"\n                  :key=\"dict.value\"\n                  :label=\"dict.value\"\n                >{{dict.label}}</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n\n    <el-dialog title=\"Cron表达式生成器\" :visible.sync=\"openCron\" append-to-body destroy-on-close class=\"scrollbar\">\n      <crontab @hide=\"openCron=false\" @fill=\"crontabFill\" :expression=\"expression\"></crontab>\n    </el-dialog>\n\n    <!-- 任务日志详细 -->\n    <el-dialog title=\"任务详细\" :visible.sync=\"openView\" width=\"700px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" label-width=\"120px\" size=\"mini\">\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"任务编号：\">{{ form.jobId }}</el-form-item>\n            <el-form-item label=\"任务名称：\">{{ form.jobName }}</el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"任务分组：\">{{ jobGroupFormat(form) }}</el-form-item>\n            <el-form-item label=\"创建时间：\">{{ form.createTime }}</el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"cron表达式：\">{{ form.cronExpression }}</el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"下次执行时间：\">{{ parseTime(form.nextValidTime) }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"调用目标方法：\">{{ form.invokeTarget }}</el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"任务状态：\">\n              <div v-if=\"form.status == 0\">正常</div>\n              <div v-else-if=\"form.status == 1\">失败</div>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"是否并发：\">\n              <div v-if=\"form.concurrent == 0\">允许</div>\n              <div v-else-if=\"form.concurrent == 1\">禁止</div>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"执行策略：\">\n              <div v-if=\"form.misfirePolicy == 0\">默认策略</div>\n              <div v-else-if=\"form.misfirePolicy == 1\">立即执行</div>\n              <div v-else-if=\"form.misfirePolicy == 2\">执行一次</div>\n              <div v-else-if=\"form.misfirePolicy == 3\">放弃执行</div>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button @click=\"openView = false\">关 闭</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from \"@/api/monitor/job\";\nimport Crontab from '@/components/Crontab'\n\nexport default {\n  components: { Crontab },\n  name: \"Job\",\n  dicts: ['sys_job_group', 'sys_job_status'],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 定时任务表格数据\n      jobList: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      // 是否显示详细弹出层\n      openView: false,\n      // 是否显示Cron表达式弹出层\n      openCron: false,\n      // 传入的表达式\n      expression: \"\",\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        jobName: undefined,\n        jobGroup: undefined,\n        status: undefined\n      },\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {\n        jobName: [\n          { required: true, message: \"任务名称不能为空\", trigger: \"blur\" }\n        ],\n        invokeTarget: [\n          { required: true, message: \"调用目标字符串不能为空\", trigger: \"blur\" }\n        ],\n        cronExpression: [\n          { required: true, message: \"cron执行表达式不能为空\", trigger: \"blur\" }\n        ]\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询定时任务列表 */\n    getList() {\n      this.loading = true;\n      listJob(this.queryParams).then(response => {\n        this.jobList = response.rows;\n        this.total = response.total;\n        this.loading = false;\n      });\n    },\n    // 任务组名字典翻译\n    jobGroupFormat(row, column) {\n      return this.selectDictLabel(this.dict.type.sys_job_group, row.jobGroup);\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        jobId: undefined,\n        jobName: undefined,\n        jobGroup: undefined,\n        invokeTarget: undefined,\n        cronExpression: undefined,\n        misfirePolicy: 1,\n        concurrent: 1,\n        status: \"0\"\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.jobId);\n      this.single = selection.length != 1;\n      this.multiple = !selection.length;\n    },\n    // 更多操作触发\n    handleCommand(command, row) {\n      switch (command) {\n        case \"handleRun\":\n          this.handleRun(row);\n          break;\n        case \"handleView\":\n          this.handleView(row);\n          break;\n        case \"handleJobLog\":\n          this.handleJobLog(row);\n          break;\n        default:\n          break;\n      }\n    },\n    // 任务状态修改\n    handleStatusChange(row) {\n      let text = row.status === \"0\" ? \"启用\" : \"停用\";\n      this.$modal.confirm('确认要\"' + text + '\"\"' + row.jobName + '\"任务吗？').then(function() {\n        return changeJobStatus(row.jobId, row.status);\n      }).then(() => {\n        this.$modal.msgSuccess(text + \"成功\");\n      }).catch(function() {\n        row.status = row.status === \"0\" ? \"1\" : \"0\";\n      });\n    },\n    /* 立即执行一次 */\n    handleRun(row) {\n      this.$modal.confirm('确认要立即执行一次\"' + row.jobName + '\"任务吗？').then(function() {\n        return runJob(row.jobId, row.jobGroup);\n      }).then(() => {\n        this.$modal.msgSuccess(\"执行成功\");\n      }).catch(() => {});\n    },\n    /** 任务详细信息 */\n    handleView(row) {\n      getJob(row.jobId).then(response => {\n        this.form = response.data;\n        this.openView = true;\n      });\n    },\n    /** cron表达式按钮操作 */\n    handleShowCron() {\n      this.expression = this.form.cronExpression;\n      this.openCron = true;\n    },\n    /** 确定后回传值 */\n    crontabFill(value) {\n      this.form.cronExpression = value;\n    },\n    /** 任务日志列表查询 */\n    handleJobLog(row) {\n      const jobId = row.jobId || 0;\n      this.$router.push({ path: '/monitor/job-log/index', query: { jobId: jobId } })\n    },\n    /** 新增按钮操作 */\n    handleAdd() {\n      this.reset();\n      this.open = true;\n      this.title = \"添加任务\";\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      const jobId = row.jobId || this.ids;\n      getJob(jobId).then(response => {\n        this.form = response.data;\n        this.open = true;\n        this.title = \"修改任务\";\n      });\n    },\n    /** 提交按钮 */\n    submitForm: function() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          if (this.form.jobId != undefined) {\n            updateJob(this.form).then(response => {\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            addJob(this.form).then(response => {\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const jobIds = row.jobId || this.ids;\n      this.$modal.confirm('是否确认删除定时任务编号为\"' + jobIds + '\"的数据项？').then(function() {\n        return delJob(jobIds);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download('monitor/job/export', {\n        ...this.queryParams\n      }, `job_${new Date().getTime()}.xlsx`)\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/monitor/job/log.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"任务名称\" prop=\"jobName\">\n        <el-input\n          v-model=\"queryParams.jobName\"\n          placeholder=\"请输入任务名称\"\n          clearable\n          style=\"width: 240px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"任务组名\" prop=\"jobGroup\">\n        <el-select\n          v-model=\"queryParams.jobGroup\"\n          placeholder=\"请选择任务组名\"\n          clearable\n          style=\"width: 240px\"\n        >\n          <el-option\n            v-for=\"dict in dict.type.sys_job_group\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"执行状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"请选择执行状态\"\n          clearable\n          style=\"width: 240px\"\n        >\n          <el-option\n            v-for=\"dict in dict.type.sys_common_status\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"执行时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 240px\"\n          value-format=\"yyyy-MM-dd\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['monitor:job:remove']\"\n        >删除</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          @click=\"handleClean\"\n          v-hasPermi=\"['monitor:job:remove']\"\n        >清空</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleExport\"\n          v-hasPermi=\"['monitor:job:export']\"\n        >导出</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-close\"\n          size=\"mini\"\n          @click=\"handleClose\"\n        >关闭</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"jobLogList\" @selection-change=\"handleSelectionChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"日志编号\" width=\"80\" align=\"center\" prop=\"jobLogId\" />\n      <el-table-column label=\"任务名称\" align=\"center\" prop=\"jobName\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"任务组名\" align=\"center\" prop=\"jobGroup\" :show-overflow-tooltip=\"true\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_job_group\" :value=\"scope.row.jobGroup\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"调用目标字符串\" align=\"center\" prop=\"invokeTarget\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"日志信息\" align=\"center\" prop=\"jobMessage\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"执行状态\" align=\"center\" prop=\"status\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_common_status\" :value=\"scope.row.status\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"执行时间\" align=\"center\" prop=\"createTime\" width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-view\"\n            @click=\"handleView(scope.row)\"\n            v-hasPermi=\"['monitor:job:query']\"\n          >详细</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 调度日志详细 -->\n    <el-dialog title=\"调度日志详细\" :visible.sync=\"open\" width=\"700px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" label-width=\"100px\" size=\"mini\">\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"日志序号：\">{{ form.jobLogId }}</el-form-item>\n            <el-form-item label=\"任务名称：\">{{ form.jobName }}</el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"任务分组：\">{{ form.jobGroup }}</el-form-item>\n            <el-form-item label=\"执行时间：\">{{ form.createTime }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"调用方法：\">{{ form.invokeTarget }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"日志信息：\">{{ form.jobMessage }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"执行状态：\">\n              <div v-if=\"form.status == 0\">正常</div>\n              <div v-else-if=\"form.status == 1\">失败</div>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"异常信息：\" v-if=\"form.status == 1\">{{ form.exceptionInfo }}</el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button @click=\"open = false\">关 闭</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { getJob} from \"@/api/monitor/job\";\nimport { listJobLog, delJobLog, cleanJobLog } from \"@/api/monitor/jobLog\";\n\nexport default {\n  name: \"JobLog\",\n  dicts: ['sys_common_status', 'sys_job_group'],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 调度日志表格数据\n      jobLogList: [],\n      // 是否显示弹出层\n      open: false,\n      // 日期范围\n      dateRange: [],\n      // 表单参数\n      form: {},\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        jobName: undefined,\n        jobGroup: undefined,\n        status: undefined\n      }\n    };\n  },\n  created() {\n    const jobId = this.$route.query.jobId;\n    if (jobId !== undefined && jobId != 0) {\n      getJob(jobId).then(response => {\n        this.queryParams.jobName = response.data.jobName;\n        this.queryParams.jobGroup = response.data.jobGroup;\n        this.getList();\n      });\n    } else {\n      this.getList();\n    }\n  },\n  methods: {\n    /** 查询调度日志列表 */\n    getList() {\n      this.loading = true;\n      listJobLog(this.addDateRange(this.queryParams, this.dateRange)).then(response => {\n          this.jobLogList = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    // 返回按钮\n    handleClose() {\n      const obj = { path: \"/monitor/job\" };\n      this.$tab.closeOpenPage(obj);\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.jobLogId);\n      this.multiple = !selection.length;\n    },\n    /** 详细按钮操作 */\n    handleView(row) {\n      this.open = true;\n      this.form = row;\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const jobLogIds = this.ids;\n      this.$modal.confirm('是否确认删除调度日志编号为\"' + jobLogIds + '\"的数据项？').then(function() {\n        return delJobLog(jobLogIds);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    },\n    /** 清空按钮操作 */\n    handleClean() {\n      this.$modal.confirm('是否确认清空所有调度日志数据项？').then(function() {\n        return cleanJobLog();\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"清空成功\");\n      }).catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download('/monitor/jobLog/export', {\n        ...this.queryParams\n      }, `log_${new Date().getTime()}.xlsx`)\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/monitor/logininfor/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"登录地址\" prop=\"ipaddr\">\n        <el-input\n          v-model=\"queryParams.ipaddr\"\n          placeholder=\"请输入登录地址\"\n          clearable\n          style=\"width: 240px;\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户名称\" prop=\"userName\">\n        <el-input\n          v-model=\"queryParams.userName\"\n          placeholder=\"请输入用户名称\"\n          clearable\n          style=\"width: 240px;\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n        <el-form-item label=\"用户id\" prop=\"userId\">\n        <el-input\n          v-model=\"queryParams.userId\"\n          placeholder=\"请输入用户id\"\n          clearable\n          style=\"width: 240px;\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"登录状态\"\n          clearable\n          style=\"width: 240px\"\n        >\n          <el-option\n            v-for=\"dict in dict.type.sys_common_status\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"登录时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 240px\"\n          value-format=\"yyyy-MM-dd\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['monitor:logininfor:remove']\"\n        >删除</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          @click=\"handleClean\"\n          v-hasPermi=\"['monitor:logininfor:remove']\"\n        >清空</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-unlock\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleUnlock\"\n          v-hasPermi=\"['monitor:logininfor:unlock']\"\n        >解锁</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleExport\"\n          v-hasPermi=\"['monitor:logininfor:export']\"\n        >导出</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table ref=\"tables\" v-loading=\"loading\" :data=\"list\" @selection-change=\"handleSelectionChange\" :default-sort=\"defaultSort\" @sort-change=\"handleSortChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"访问编号\" align=\"center\" prop=\"infoId\" />\n      <el-table-column label=\"用户名称\" align=\"center\" prop=\"userName\" :show-overflow-tooltip=\"true\" sortable=\"custom\" :sort-orders=\"['descending', 'ascending']\" />\n      <el-table-column label=\"登录地址\" align=\"center\" prop=\"ipaddr\" width=\"130\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"登录地点\" align=\"center\" prop=\"loginLocation\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"浏览器\" align=\"center\" prop=\"browser\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"操作系统\" align=\"center\" prop=\"os\" />\n      <el-table-column label=\"登录状态\" align=\"center\" prop=\"status\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_common_status\" :value=\"scope.row.status\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作信息\" align=\"center\" prop=\"msg\" />\n      <el-table-column label=\"登录日期\" align=\"center\" prop=\"loginTime\" sortable=\"custom\" :sort-orders=\"['descending', 'ascending']\" width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.loginTime) }}</span>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </div>\n</template>\n\n<script>\nimport { list, delLogininfor, cleanLogininfor, unlockLogininfor } from \"@/api/monitor/logininfor\";\n\nexport default {\n  name: \"Logininfor\",\n  dicts: ['sys_common_status'],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 选择用户名\n      selectName: \"\",\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 表格数据\n      list: [],\n      // 日期范围\n      dateRange: [],\n      // 默认排序\n      defaultSort: {prop: 'loginTime', order: 'descending'},\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        ipaddr: undefined,\n        userName: undefined,\n        userId:undefined,\n        status: undefined\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询登录日志列表 */\n    getList() {\n      this.loading = true;\n      list(this.addDateRange(this.queryParams, this.dateRange)).then(response => {\n          this.list = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.queryParams.pageNum = 1;\n      this.$refs.tables.sort(this.defaultSort.prop, this.defaultSort.order)\n    },\n    /** 多选框选中数据 */\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.infoId)\n      this.single = selection.length!=1\n      this.multiple = !selection.length\n      this.selectName = selection.map(item => item.userName);\n    },\n    /** 排序触发事件 */\n    handleSortChange(column, prop, order) {\n      this.queryParams.orderByColumn = column.prop;\n      this.queryParams.isAsc = column.order;\n      this.getList();\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const infoIds = row.infoId || this.ids;\n      this.$modal.confirm('是否确认删除访问编号为\"' + infoIds + '\"的数据项？').then(function() {\n        return delLogininfor(infoIds);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    },\n    /** 清空按钮操作 */\n    handleClean() {\n      this.$modal.confirm('是否确认清空所有登录日志数据项？').then(function() {\n        return cleanLogininfor();\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"清空成功\");\n      }).catch(() => {});\n    },\n    /** 解锁按钮操作 */\n    handleUnlock() {\n      const username = this.selectName;\n      this.$modal.confirm('是否确认解锁用户\"' + username + '\"数据项?').then(function() {\n        return unlockLogininfor(username);\n      }).then(() => {\n        this.$modal.msgSuccess(\"用户\" + username + \"解锁成功\");\n      }).catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download('monitor/logininfor/export', {\n        ...this.queryParams\n      }, `logininfor_${new Date().getTime()}.xlsx`)\n    }\n  }\n};\n</script>\n\n"
  },
  {
    "path": "vue_campus_admin/src/views/monitor/online/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" label-width=\"68px\">\n      <el-form-item label=\"登录地址\" prop=\"ipaddr\">\n        <el-input\n          v-model=\"queryParams.ipaddr\"\n          placeholder=\"请输入登录地址\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户名称\" prop=\"userName\">\n        <el-input\n          v-model=\"queryParams.userName\"\n          placeholder=\"请输入用户名称\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n\n    </el-form>\n    <el-table\n      v-loading=\"loading\"\n      :data=\"list.slice((pageNum-1)*pageSize,pageNum*pageSize)\"\n      style=\"width: 100%;\"\n    >\n      <el-table-column label=\"序号\" type=\"index\" align=\"center\">\n        <template slot-scope=\"scope\">\n          <span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"会话编号\" align=\"center\" prop=\"tokenId\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"登录名称\" align=\"center\" prop=\"userName\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"部门名称\" align=\"center\" prop=\"deptName\" />\n      <el-table-column label=\"主机\" align=\"center\" prop=\"ipaddr\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"登录地点\" align=\"center\" prop=\"loginLocation\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"浏览器\" align=\"center\" prop=\"browser\" />\n      <el-table-column label=\"操作系统\" align=\"center\" prop=\"os\" />\n      <el-table-column label=\"登录时间\" align=\"center\" prop=\"loginTime\" width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.loginTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleForceLogout(scope.row)\"\n            v-hasPermi=\"['monitor:online:forceLogout']\"\n          >强退</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination v-show=\"total>0\" :total=\"total\" :page.sync=\"pageNum\" :limit.sync=\"pageSize\" />\n  </div>\n</template>\n\n<script>\nimport { list, forceLogout } from \"@/api/monitor/online\";\n\nexport default {\n  name: \"Online\",\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 总条数\n      total: 0,\n      // 表格数据\n      list: [],\n      pageNum: 1,\n      pageSize: 10,\n      // 查询参数\n      queryParams: {\n        ipaddr: undefined,\n        userName: undefined\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询登录日志列表 */\n    getList() {\n      this.loading = true;\n      list(this.queryParams).then(response => {\n        this.list = response.rows;\n        this.total = response.total;\n        this.loading = false;\n      });\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    /** 强退按钮操作 */\n    handleForceLogout(row) {\n      this.$modal.confirm('是否确认强退名称为\"' + row.userName + '\"的用户？').then(function() {\n        return forceLogout(row.tokenId);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"强退成功\");\n      }).catch(() => {});\n    }\n  }\n};\n</script>\n\n"
  },
  {
    "path": "vue_campus_admin/src/views/monitor/operlog/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"系统模块\" prop=\"appName\">\n        <el-input\n          v-model=\"queryParams.appName\"\n          placeholder=\"请输入应用编码\"\n          clearable\n          style=\"width: 240px;\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"操作人员\" prop=\"operId\">\n        <el-input\n          v-model=\"queryParams.operId\"\n          placeholder=\"请输入操作人员\"\n          clearable\n          style=\"width: 240px;\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"日志名称\" prop=\"logName\">\n         <el-input\n          v-model=\"queryParams.logName\"\n          placeholder=\"请输入日志名称\"\n          clearable\n          style=\"width: 240px;\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n          <el-form-item label=\"主机地址\" prop=\"operIp\">\n         <el-input\n          v-model=\"queryParams.operIp\"\n          placeholder=\"请输入主机地址\"\n          clearable\n          style=\"width: 240px;\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"操作状态\"\n          clearable\n          style=\"width: 240px\"\n        >\n          <el-option\n            v-for=\"dict in dict.type.sys_common_status\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"操作时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 240px\"\n          value-format=\"yyyy-MM-dd\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['monitor:operlog:remove']\"\n        >删除</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          @click=\"handleClean\"\n          v-hasPermi=\"['monitor:operlog:remove']\"\n        >清空</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleExport\"\n          v-hasPermi=\"['monitor:operlog:export']\"\n        >导出</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table ref=\"tables\" v-loading=\"loading\" :data=\"list\" @selection-change=\"handleSelectionChange\" :default-sort=\"defaultSort\" @sort-change=\"handleSortChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"日志编号\" align=\"center\" prop=\"operId\" />\n      <el-table-column label=\"系统模块\" align=\"center\" prop=\"appName\" />\n      <el-table-column label=\"日志名称\" align=\"center\" prop=\"logName\"/>\n       \n      <el-table-column label=\"请求方式\" align=\"center\" prop=\"requestMethod\" />\n      <el-table-column label=\"操作人员\" align=\"center\" prop=\"operUserId\" width=\"100\" :show-overflow-tooltip=\"true\" sortable=\"custom\" :sort-orders=\"['descending', 'ascending']\" >\n        <template slot-scope=\"scope\">\n          <span>{{ scope.row.operUserId== null ?'游客': scope.row.operUserId}}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作地址\" align=\"center\" prop=\"operIp\" width=\"130\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"日志记录内容\" align=\"center\" prop=\"logContent\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"操作状态\" align=\"center\" prop=\"status\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_common_status\" :value=\"scope.row.status\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作日期\" align=\"center\" prop=\"operTime\" sortable=\"custom\"  width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.operTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-view\"\n            @click=\"handleView(scope.row,scope.index)\"\n            v-hasPermi=\"['monitor:operlog:query']\"\n          >详细</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 操作日志详细 -->\n    <el-dialog title=\"操作日志详细\" :visible.sync=\"open\" width=\"700px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" label-width=\"100px\" size=\"mini\">\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"应用编码：\">{{ form.appName }} / {{ form.logName }}</el-form-item>\n            <el-form-item\n              label=\"登录信息：\"\n            > {{ form.operIp }} </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"请求地址：\">{{ form.operUrl }}</el-form-item>\n            <el-form-item label=\"请求方式：\">{{ form.requestMethod }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"操作方法：\">{{ form.method }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"请求参数：\">{{ form.operParam }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"返回参数：\">{{ form.jsonResult }}</el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"操作状态：\">\n              <div v-if=\"form.status === 0\">正常</div>\n              <div v-else-if=\"form.status === 1\">失败</div>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"操作时间：\">{{ parseTime(form.operTime) }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"异常信息：\" v-if=\"form.status === 1\">{{ form.errorMsg }}</el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item style=\"white-space: pre-wrap\" label=\"日志内容：\"  >{{ form.logContent }}</el-form-item>\n          </el-col>\n          \n        </el-row>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button @click=\"open = false\">关 闭</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { list, delOperlog, cleanOperlog } from \"@/api/monitor/operlog\";\n\nexport default {\n  name: \"Operlog\",\n  dicts: ['sys_oper_type', 'sys_common_status'],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 表格数据\n      list: [],\n      // 是否显示弹出层\n      open: false,\n      // 日期范围\n      dateRange: [],\n      // 表单参数\n      form: {},\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        title: undefined,\n        operId: undefined,\n        operIp:undefined,\n        logName: undefined,\n        status: undefined\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询登录日志 */\n    getList() {\n      this.loading = true;\n      list(this.addDateRange(this.queryParams, this.dateRange)).then( response => {\n          this.list = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.queryParams.pageNum = 1;\n      // this.$refs.tables.sort(this.defaultSort.prop, this.defaultSort.order)\n    },\n    /** 多选框选中数据 */\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.operId)\n      this.multiple = !selection.length\n    },\n    /** 排序触发事件 */\n    handleSortChange(column, prop, order) {\n      this.queryParams.orderByColumn = column.prop;\n      this.queryParams.isAsc = column.order;\n      this.getList();\n    },\n    /** 详细按钮操作 */\n    handleView(row) {\n      this.open = true;\n      this.form = row;\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const operIds = row.operId || this.ids;\n      this.$modal.confirm('是否确认删除日志编号为\"' + operIds + '\"的数据项？').then(function() {\n        return delOperlog(operIds);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    },\n    /** 清空按钮操作 */\n    handleClean() {\n      this.$modal.confirm('是否确认清空所有操作日志数据项？').then(function() {\n        return cleanOperlog();\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"清空成功\");\n      }).catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download('monitor/operlog/export', {\n        ...this.queryParams\n      }, `operlog_${new Date().getTime()}.xlsx`)\n    }\n  }\n};\n</script>\n\n"
  },
  {
    "path": "vue_campus_admin/src/views/redirect.vue",
    "content": "<script>\nexport default {\n  created() {\n    const { params, query } = this.$route\n    const { path } = params\n    this.$router.replace({ path: '/' + path, query })\n  },\n  render: function(h) {\n    return h() // avoid warning message\n  }\n}\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/register.vue",
    "content": "<template>\n  <div class=\"register\">\n    <el-form ref=\"registerForm\" :model=\"registerForm\" :rules=\"registerRules\" class=\"register-form\">\n      <h3 class=\"title\">campus后台管理系统</h3>\n      <el-form-item prop=\"username\">\n        <el-input v-model=\"registerForm.username\" type=\"text\" auto-complete=\"off\" placeholder=\"账号\">\n          <svg-icon slot=\"prefix\" icon-class=\"user\" class=\"el-input__icon input-icon\" />\n        </el-input>\n      </el-form-item>\n      <el-form-item prop=\"password\">\n        <el-input\n          v-model=\"registerForm.password\"\n          type=\"password\"\n          auto-complete=\"off\"\n          placeholder=\"密码\"\n          @keyup.enter.native=\"handleRegister\"\n        >\n          <svg-icon slot=\"prefix\" icon-class=\"password\" class=\"el-input__icon input-icon\" />\n        </el-input>\n      </el-form-item>\n      <el-form-item prop=\"confirmPassword\">\n        <el-input\n          v-model=\"registerForm.confirmPassword\"\n          type=\"password\"\n          auto-complete=\"off\"\n          placeholder=\"确认密码\"\n          @keyup.enter.native=\"handleRegister\"\n        >\n          <svg-icon slot=\"prefix\" icon-class=\"password\" class=\"el-input__icon input-icon\" />\n        </el-input>\n      </el-form-item>\n      <el-form-item prop=\"code\" v-if=\"captchaEnabled\">\n        <el-input\n          v-model=\"registerForm.code\"\n          auto-complete=\"off\"\n          placeholder=\"验证码\"\n          style=\"width: 63%\"\n          @keyup.enter.native=\"handleRegister\"\n        >\n          <svg-icon slot=\"prefix\" icon-class=\"validCode\" class=\"el-input__icon input-icon\" />\n        </el-input>\n        <div class=\"register-code\">\n          <img :src=\"codeUrl\" @click=\"getCode\" class=\"register-code-img\"/>\n        </div>\n      </el-form-item>\n      <el-form-item style=\"width:100%;\">\n        <el-button\n          :loading=\"loading\"\n          size=\"medium\"\n          type=\"primary\"\n          style=\"width:100%;\"\n          @click.native.prevent=\"handleRegister\"\n        >\n          <span v-if=\"!loading\">注 册</span>\n          <span v-else>注 册 中...</span>\n        </el-button>\n        <div style=\"float: right;\">\n          <router-link class=\"link-type\" :to=\"'/login'\">使用已有账户登录</router-link>\n        </div>\n      </el-form-item>\n    </el-form>\n    <!--  底部  -->\n    <div class=\"el-register-footer\">\n      <span>Copyright © 2023 oddfar.com All Rights Reserved.</span>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { getCodeImg, register } from \"@/api/login\";\n\nexport default {\n  name: \"Register\",\n  data() {\n    const equalToPassword = (rule, value, callback) => {\n      if (this.registerForm.password !== value) {\n        callback(new Error(\"两次输入的密码不一致\"));\n      } else {\n        callback();\n      }\n    };\n    return {\n      codeUrl: \"\",\n      registerForm: {\n        username: \"\",\n        password: \"\",\n        confirmPassword: \"\",\n        code: \"\",\n        uuid: \"\"\n      },\n      registerRules: {\n        username: [\n          { required: true, trigger: \"blur\", message: \"请输入您的账号\" },\n          { min: 2, max: 20, message: '用户账号长度必须介于 2 和 20 之间', trigger: 'blur' }\n        ],\n        password: [\n          { required: true, trigger: \"blur\", message: \"请输入您的密码\" },\n          { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }\n        ],\n        confirmPassword: [\n          { required: true, trigger: \"blur\", message: \"请再次输入您的密码\" },\n          { required: true, validator: equalToPassword, trigger: \"blur\" }\n        ],\n        code: [{ required: true, trigger: \"change\", message: \"请输入验证码\" }]\n      },\n      loading: false,\n      captchaEnabled: true\n    };\n  },\n  created() {\n    this.getCode();\n  },\n  methods: {\n    getCode() {\n      getCodeImg().then(res => {\n        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;\n        if (this.captchaEnabled) {\n          this.codeUrl = \"data:image/gif;base64,\" + res.img;\n          this.registerForm.uuid = res.uuid;\n        }\n      });\n    },\n    handleRegister() {\n      this.$refs.registerForm.validate(valid => {\n        if (valid) {\n          this.loading = true;\n          register(this.registerForm).then(res => {\n            const username = this.registerForm.username;\n            this.$alert(\"<font color='red'>恭喜你，您的账号 \" + username + \" 注册成功！</font>\", '系统提示', {\n              dangerouslyUseHTMLString: true,\n              type: 'success'\n            }).then(() => {\n              this.$router.push(\"/login\");\n            }).catch(() => {});\n          }).catch(() => {\n            this.loading = false;\n            if (this.captchaEnabled) {\n              this.getCode();\n            }\n          })\n        }\n      });\n    }\n  }\n};\n</script>\n\n<style rel=\"stylesheet/scss\" lang=\"scss\">\n.register {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n  background-image: url(\"../assets/images/login-background.jpg\");\n  background-size: cover;\n}\n.title {\n  margin: 0px auto 30px auto;\n  text-align: center;\n  color: #707070;\n}\n\n.register-form {\n  border-radius: 6px;\n  background: #ffffff;\n  width: 400px;\n  padding: 25px 25px 5px 25px;\n  .el-input {\n    height: 38px;\n    input {\n      height: 38px;\n    }\n  }\n  .input-icon {\n    height: 39px;\n    width: 14px;\n    margin-left: 2px;\n  }\n}\n.register-tip {\n  font-size: 13px;\n  text-align: center;\n  color: #bfbfbf;\n}\n.register-code {\n  width: 33%;\n  height: 38px;\n  float: right;\n  img {\n    cursor: pointer;\n    vertical-align: middle;\n  }\n}\n.el-register-footer {\n  height: 40px;\n  line-height: 40px;\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  text-align: center;\n  color: #fff;\n  font-family: Arial;\n  font-size: 12px;\n  letter-spacing: 1px;\n}\n.register-code-img {\n  height: 38px;\n}\n</style>\n"
  },
  {
    "path": "vue_campus_admin/src/views/system/config/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"参数名称\" prop=\"configName\">\n        <el-input\n          v-model=\"queryParams.configName\"\n          placeholder=\"请输入参数名称\"\n          clearable\n          style=\"width: 200px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"参数键名\" prop=\"configKey\">\n        <el-input\n          v-model=\"queryParams.configKey\"\n          placeholder=\"请输入参数键名\"\n          clearable\n          style=\"width: 200px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"系统内置\" prop=\"configType\">\n        <el-select v-model=\"queryParams.configType\" placeholder=\"系统内置\" clearable>\n          <el-option\n            v-for=\"dict in dict.type.sys_yes_no\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"参数分组\" prop=\"configType\">\n        <el-select v-model=\"queryParams.groupCode\" placeholder=\"参数分组\"  clearable>\n          <el-option\n            v-for=\"dict in dict.type.sys_config_group\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 240px\"\n          value-format=\"yyyy-MM-dd\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAdd\"\n          v-hasPermi=\"['system:config:add']\"\n        >新增</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"success\"\n          plain\n          icon=\"el-icon-edit\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleUpdate\"\n          v-hasPermi=\"['system:config:edit']\"\n        >修改</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['system:config:remove']\"\n        >删除</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleExport\"\n          v-hasPermi=\"['system:config:export']\"\n        >导出</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-refresh\"\n          size=\"mini\"\n          @click=\"handleRefreshCache\"\n          v-hasPermi=\"['system:config:remove']\"\n        >刷新缓存</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"configList\" @selection-change=\"handleSelectionChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"参数主键\" align=\"center\" prop=\"configId\" />\n      <el-table-column label=\"参数名称\" align=\"center\" prop=\"configName\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"参数键名\" align=\"center\" prop=\"configKey\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"参数键值\" align=\"center\" prop=\"configValue\" />\n      <el-table-column label=\"系统内置\" align=\"center\" prop=\"configType\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_yes_no\" :value=\"scope.row.configType\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"分组\" align=\"center\" prop=\"groupCode\" >\n        <template slot-scope=\"scope\">\n             <dict-tag :options=\"dict.type.sys_config_group\" :value=\"scope.row.groupCode\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n            v-hasPermi=\"['system:config:edit']\"\n          >修改</el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['system:config:remove']\"\n          >删除</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 添加或修改参数配置对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"500px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"80px\">\n           <el-form-item label=\"参数分组\" prop=\"groupCode\">\n            <el-select v-model=\"form.groupCode\" placeholder=\"参数分组\" clearable>\n                <el-option\n                v-for=\"dict in dict.type.sys_config_group\"\n                :key=\"dict.value\"\n               :label=\"dict.label\"\n               :value=\"dict.value\"\n               />\n           </el-select>\n         </el-form-item>\n        <el-form-item label=\"参数名称\" prop=\"configName\">\n          <el-input v-model=\"form.configName\" placeholder=\"请输入参数名称\" />\n        </el-form-item>\n        <el-form-item label=\"参数键名\" prop=\"configKey\">\n          <el-input v-model=\"form.configKey\" placeholder=\"请输入参数键名\" />\n        </el-form-item>\n        <el-form-item label=\"参数键值\" prop=\"configValue\">\n          <el-input v-model=\"form.configValue\" placeholder=\"请输入参数键值\" />\n        </el-form-item>\n        <el-form-item label=\"系统内置\" prop=\"configType\">\n          <el-radio-group v-model=\"form.configType\">\n            <el-radio\n              v-for=\"dict in dict.type.sys_yes_no\"\n              :key=\"dict.value\"\n              :label=\"dict.value\"\n            >{{dict.label}}</el-radio>\n          </el-radio-group>\n        </el-form-item>\n        <el-form-item label=\"备注\" prop=\"remark\">\n          <el-input v-model=\"form.remark\" type=\"textarea\" placeholder=\"请输入内容\" />\n        </el-form-item>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from \"@/api/system/config\";\n\nexport default {\n  name: \"Config\",\n  dicts: ['sys_yes_no','sys_config_group'],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 参数表格数据\n      configList: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      // 日期范围\n      dateRange: [],\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        configName: undefined,\n        configKey: undefined,\n        configType: undefined,\n        groupCode: undefined\n      },\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {\n        configName: [\n          { required: true, message: \"参数名称不能为空\", trigger: \"blur\" }\n        ],\n        configKey: [\n          { required: true, message: \"参数键名不能为空\", trigger: \"blur\" }\n        ],\n        configValue: [\n          { required: true, message: \"参数键值不能为空\", trigger: \"blur\" }\n        ],\n        groupCode: [\n           { required: true, message: \"参数分组不能为空\", trigger: \"blur\" }\n        ]\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询参数列表 */\n    getList() {\n      this.loading = true;\n      listConfig(this.addDateRange(this.queryParams, this.dateRange)).then(response => {\n          this.configList = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        configId: undefined,\n        configName: undefined,\n        configKey: undefined,\n        configValue: undefined,\n        configType: \"Y\",\n        groupCode:undefined,\n        remark: undefined\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    /** 新增按钮操作 */\n    handleAdd() {\n      this.reset();\n      this.open = true;\n      this.title = \"添加参数\";\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.configId)\n      this.single = selection.length!=1\n      this.multiple = !selection.length\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      const configId = row.configId || this.ids\n      getConfig(configId).then(response => {\n        this.form = response.data;\n        this.open = true;\n        this.title = \"修改参数\";\n      });\n    },\n    /** 提交按钮 */\n    submitForm: function() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          if (this.form.configId != undefined) {\n            updateConfig(this.form).then(response => {\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            addConfig(this.form).then(response => {\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const configIds = row.configId || this.ids;\n      this.$modal.confirm('是否确认删除参数编号为\"' + configIds + '\"的数据项？').then(function() {\n          return delConfig(configIds);\n        }).then(() => {\n          this.getList();\n          this.$modal.msgSuccess(\"删除成功\");\n        }).catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download('system/config/export', {\n        ...this.queryParams\n      }, `config_${new Date().getTime()}.xlsx`)\n    },\n    /** 刷新缓存按钮操作 */\n    handleRefreshCache() {\n      refreshCache().then(() => {\n        this.$modal.msgSuccess(\"刷新成功\");\n      });\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/system/dict/data.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"字典名称\" prop=\"dictType\">\n        <el-select v-model=\"queryParams.dictType\">\n          <el-option\n            v-for=\"item in typeOptions\"\n            :key=\"item.dictId\"\n            :label=\"item.dictName\"\n            :value=\"item.dictType\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"字典标签\" prop=\"dictLabel\">\n        <el-input\n          v-model=\"queryParams.dictLabel\"\n          placeholder=\"请输入字典标签\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" placeholder=\"数据状态\" clearable>\n          <el-option\n            v-for=\"dict in dict.type.sys_normal_disable\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAdd\"\n          v-hasPermi=\"['system:dict:add']\"\n        >新增</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"success\"\n          plain\n          icon=\"el-icon-edit\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleUpdate\"\n          v-hasPermi=\"['system:dict:edit']\"\n        >修改</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['system:dict:remove']\"\n        >删除</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleExport\"\n          v-hasPermi=\"['system:dict:export']\"\n        >导出</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-close\"\n          size=\"mini\"\n          @click=\"handleClose\"\n        >关闭</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"dataList\" @selection-change=\"handleSelectionChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"字典编码\" align=\"center\" prop=\"dictCode\" />\n      <el-table-column label=\"字典标签\" align=\"center\" prop=\"dictLabel\">\n        <template slot-scope=\"scope\">\n          <span v-if=\"scope.row.listClass == '' || scope.row.listClass == 'default'\">{{scope.row.dictLabel}}</span>\n          <el-tag v-else :type=\"scope.row.listClass == 'primary' ? '' : scope.row.listClass\">{{scope.row.dictLabel}}</el-tag>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"字典键值\" align=\"center\" prop=\"dictValue\" />\n      <el-table-column label=\"字典排序\" align=\"center\" prop=\"dictSort\" />\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_normal_disable\" :value=\"scope.row.status\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n            v-hasPermi=\"['system:dict:edit']\"\n          >修改</el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['system:dict:remove']\"\n          >删除</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 添加或修改参数配置对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"500px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"80px\">\n        <el-form-item label=\"字典类型\">\n          <el-input v-model=\"form.dictType\" :disabled=\"true\" />\n        </el-form-item>\n        <el-form-item label=\"数据标签\" prop=\"dictLabel\">\n          <el-input v-model=\"form.dictLabel\" placeholder=\"请输入数据标签\" />\n        </el-form-item>\n        <el-form-item label=\"数据键值\" prop=\"dictValue\">\n          <el-input v-model=\"form.dictValue\" placeholder=\"请输入数据键值\" />\n        </el-form-item>\n        <el-form-item label=\"样式属性\" prop=\"cssClass\">\n          <el-input v-model=\"form.cssClass\" placeholder=\"请输入样式属性\" />\n        </el-form-item>\n        <el-form-item label=\"显示排序\" prop=\"dictSort\">\n          <el-input-number v-model=\"form.dictSort\" controls-position=\"right\" :min=\"0\" />\n        </el-form-item>\n        <el-form-item label=\"回显样式\" prop=\"listClass\">\n          <el-select v-model=\"form.listClass\">\n            <el-option\n              v-for=\"item in listClassOptions\"\n              :key=\"item.value\"\n              :label=\"item.label + '(' + item.value + ')'\"\n              :value=\"item.value\"\n            ></el-option>\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"状态\" prop=\"status\">\n          <el-radio-group v-model=\"form.status\">\n            <el-radio\n              v-for=\"dict in dict.type.sys_normal_disable\"\n              :key=\"dict.value\"\n              :label=\"dict.value\"\n            >{{dict.label}}</el-radio>\n          </el-radio-group>\n        </el-form-item>\n        <el-form-item label=\"备注\" prop=\"remark\">\n          <el-input v-model=\"form.remark\" type=\"textarea\" placeholder=\"请输入内容\"></el-input>\n        </el-form-item>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { listData, getData, delData, addData, updateData } from \"@/api/system/dict/data\";\nimport { optionselect as getDictOptionselect, getType } from \"@/api/system/dict/type\";\n\nexport default {\n  name: \"Data\",\n  dicts: ['sys_normal_disable'],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 字典表格数据\n      dataList: [],\n      // 默认字典类型\n      defaultDictType: \"\",\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      // 数据标签回显样式\n      listClassOptions: [\n        {\n          value: \"default\",\n          label: \"默认\"\n        },\n        {\n          value: \"primary\",\n          label: \"主要\"\n        },\n        {\n          value: \"success\",\n          label: \"成功\"\n        },\n        {\n          value: \"info\",\n          label: \"信息\"\n        },\n        {\n          value: \"warning\",\n          label: \"警告\"\n        },\n        {\n          value: \"danger\",\n          label: \"危险\"\n        }\n      ],\n      // 类型数据字典\n      typeOptions: [],\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        dictName: undefined,\n        dictType: undefined,\n        status: undefined\n      },\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {\n        dictLabel: [\n          { required: true, message: \"数据标签不能为空\", trigger: \"blur\" }\n        ],\n        dictValue: [\n          { required: true, message: \"数据键值不能为空\", trigger: \"blur\" }\n        ],\n        dictSort: [\n          { required: true, message: \"数据顺序不能为空\", trigger: \"blur\" }\n        ]\n      }\n    };\n  },\n  created() {\n    const dictId = this.$route.params && this.$route.params.dictId;\n    this.getType(dictId);\n    this.getTypeList();\n  },\n  methods: {\n    /** 查询字典类型详细 */\n    getType(dictId) {\n      getType(dictId).then(response => {\n        this.queryParams.dictType = response.data.dictType;\n        this.defaultDictType = response.data.dictType;\n        this.getList();\n      });\n    },\n    /** 查询字典类型列表 */\n    getTypeList() {\n      getDictOptionselect().then(response => {\n        this.typeOptions = response.data;\n      });\n    },\n    /** 查询字典数据列表 */\n    getList() {\n      this.loading = true;\n      listData(this.queryParams).then(response => {\n        this.dataList = response.rows;\n        this.total = response.total;\n        this.loading = false;\n      });\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        dictCode: undefined,\n        dictLabel: undefined,\n        dictValue: undefined,\n        cssClass: undefined,\n        listClass: 'default',\n        dictSort: 0,\n        status: \"0\",\n        remark: undefined\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 返回按钮操作 */\n    handleClose() {\n      const obj = { path: \"/system/dict\" };\n      this.$tab.closeOpenPage(obj);\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.queryParams.dictType = this.defaultDictType;\n      this.handleQuery();\n    },\n    /** 新增按钮操作 */\n    handleAdd() {\n      this.reset();\n      this.open = true;\n      this.title = \"添加字典数据\";\n      this.form.dictType = this.queryParams.dictType;\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.dictCode)\n      this.single = selection.length!=1\n      this.multiple = !selection.length\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      const dictCode = row.dictCode || this.ids\n      getData(dictCode).then(response => {\n        this.form = response.data;\n        this.open = true;\n        this.title = \"修改字典数据\";\n      });\n    },\n    /** 提交按钮 */\n    submitForm: function() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          if (this.form.dictCode != undefined) {\n            updateData(this.form).then(response => {\n              this.$store.dispatch('dict/removeDict', this.queryParams.dictType);\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            addData(this.form).then(response => {\n              this.$store.dispatch('dict/removeDict', this.queryParams.dictType);\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const dictCodes = row.dictCode || this.ids;\n      this.$modal.confirm('是否确认删除字典编码为\"' + dictCodes + '\"的数据项？').then(function() {\n        return delData(dictCodes);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n        this.$store.dispatch('dict/removeDict', this.queryParams.dictType);\n      }).catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download('system/dict/data/export', {\n        ...this.queryParams\n      }, `data_${new Date().getTime()}.xlsx`)\n    }\n  }\n};\n</script>"
  },
  {
    "path": "vue_campus_admin/src/views/system/dict/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"字典名称\" prop=\"dictName\">\n        <el-input\n          v-model=\"queryParams.dictName\"\n          placeholder=\"请输入字典名称\"\n          clearable\n          style=\"width: 240px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"字典类型\" prop=\"dictType\">\n        <el-input\n          v-model=\"queryParams.dictType\"\n          placeholder=\"请输入字典类型\"\n          clearable\n          style=\"width: 240px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"字典状态\"\n          clearable\n          style=\"width: 240px\"\n        >\n          <el-option\n            v-for=\"dict in dict.type.sys_normal_disable\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 240px\"\n          value-format=\"yyyy-MM-dd\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAdd\"\n          v-hasPermi=\"['system:dict:add']\"\n        >新增</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"success\"\n          plain\n          icon=\"el-icon-edit\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleUpdate\"\n          v-hasPermi=\"['system:dict:edit']\"\n        >修改</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['system:dict:remove']\"\n        >删除</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleExport\"\n          v-hasPermi=\"['system:dict:export']\"\n        >导出</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-refresh\"\n          size=\"mini\"\n          @click=\"handleRefreshCache\"\n          v-hasPermi=\"['system:dict:remove']\"\n        >刷新缓存</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"typeList\" @selection-change=\"handleSelectionChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"字典编号\" align=\"center\" prop=\"dictId\" />\n      <el-table-column label=\"字典名称\" align=\"center\" prop=\"dictName\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"字典类型\" align=\"center\" :show-overflow-tooltip=\"true\">\n        <template slot-scope=\"scope\">\n          <router-link :to=\"'/system/dict-data/index/' + scope.row.dictId\" class=\"link-type\">\n            <span>{{ scope.row.dictType }}</span>\n          </router-link>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_normal_disable\" :value=\"scope.row.status\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n            v-hasPermi=\"['system:dict:edit']\"\n          >修改</el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['system:dict:remove']\"\n          >删除</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 添加或修改参数配置对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"500px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"80px\">\n        <el-form-item label=\"字典名称\" prop=\"dictName\">\n          <el-input v-model=\"form.dictName\" placeholder=\"请输入字典名称\" />\n        </el-form-item>\n        <el-form-item label=\"字典类型\" prop=\"dictType\">\n          <el-input v-model=\"form.dictType\" placeholder=\"请输入字典类型\" />\n        </el-form-item>\n        <el-form-item label=\"状态\" prop=\"status\">\n          <el-radio-group v-model=\"form.status\">\n            <el-radio\n              v-for=\"dict in dict.type.sys_normal_disable\"\n              :key=\"dict.value\"\n              :label=\"dict.value\"\n            >{{dict.label}}</el-radio>\n          </el-radio-group>\n        </el-form-item>\n        <el-form-item label=\"备注\" prop=\"remark\">\n          <el-input v-model=\"form.remark\" type=\"textarea\" placeholder=\"请输入内容\"></el-input>\n        </el-form-item>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { listType, getType, delType, addType, updateType, refreshCache } from \"@/api/system/dict/type\";\n\nexport default {\n  name: \"Dict\",\n  dicts: ['sys_normal_disable'],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 字典表格数据\n      typeList: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      // 日期范围\n      dateRange: [],\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        dictName: undefined,\n        dictType: undefined,\n        status: undefined\n      },\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {\n        dictName: [\n          { required: true, message: \"字典名称不能为空\", trigger: \"blur\" }\n        ],\n        dictType: [\n          { required: true, message: \"字典类型不能为空\", trigger: \"blur\" }\n        ]\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询字典类型列表 */\n    getList() {\n      this.loading = true;\n      listType(this.addDateRange(this.queryParams, this.dateRange)).then(response => {\n          this.typeList = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        dictId: undefined,\n        dictName: undefined,\n        dictType: undefined,\n        status: \"0\",\n        remark: undefined\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    /** 新增按钮操作 */\n    handleAdd() {\n      this.reset();\n      this.open = true;\n      this.title = \"添加字典类型\";\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.dictId)\n      this.single = selection.length!=1\n      this.multiple = !selection.length\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      const dictId = row.dictId || this.ids\n      getType(dictId).then(response => {\n        this.form = response.data;\n        this.open = true;\n        this.title = \"修改字典类型\";\n      });\n    },\n    /** 提交按钮 */\n    submitForm: function() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          if (this.form.dictId != undefined) {\n            updateType(this.form).then(response => {\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            addType(this.form).then(response => {\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const dictIds = row.dictId || this.ids;\n      this.$modal.confirm('是否确认删除字典编号为\"' + dictIds + '\"的数据项？').then(function() {\n        return delType(dictIds);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download('system/dict/type/export', {\n        ...this.queryParams\n      }, `type_${new Date().getTime()}.xlsx`)\n    },\n    /** 刷新缓存按钮操作 */\n    handleRefreshCache() {\n      refreshCache().then(() => {\n        this.$modal.msgSuccess(\"刷新成功\");\n        this.$store.dispatch('dict/cleanDict');\n      });\n    }\n  }\n};\n</script>"
  },
  {
    "path": "vue_campus_admin/src/views/system/menu/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\">\n      <el-form-item label=\"菜单名称\" prop=\"menuName\">\n        <el-input\n          v-model=\"queryParams.menuName\"\n          placeholder=\"请输入菜单名称\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" placeholder=\"菜单状态\" clearable>\n          <el-option\n            v-for=\"dict in dict.type.sys_normal_disable\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAdd\"\n          v-hasPermi=\"['system:menu:add']\"\n        >新增</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"info\"\n          plain\n          icon=\"el-icon-sort\"\n          size=\"mini\"\n          @click=\"toggleExpandAll\"\n        >展开/折叠</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table\n      v-if=\"refreshTable\"\n      v-loading=\"loading\"\n      :data=\"menuList\"\n      row-key=\"menuId\"\n      :default-expand-all=\"isExpandAll\"\n      :tree-props=\"{children: 'children', hasChildren: 'hasChildren'}\"\n    >\n      <el-table-column prop=\"menuName\" label=\"菜单名称\" :show-overflow-tooltip=\"true\" width=\"160\"></el-table-column>\n      <el-table-column prop=\"icon\" label=\"图标\" align=\"center\" width=\"100\">\n        <template slot-scope=\"scope\">\n          <svg-icon :icon-class=\"scope.row.icon\" />\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"orderNum\" label=\"排序\" width=\"60\"></el-table-column>\n      <el-table-column prop=\"perms\" label=\"权限标识\" :show-overflow-tooltip=\"true\"></el-table-column>\n      <el-table-column prop=\"component\" label=\"组件路径\" :show-overflow-tooltip=\"true\"></el-table-column>\n      <el-table-column prop=\"status\" label=\"状态\" width=\"80\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_normal_disable\" :value=\"scope.row.status\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button \n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n            v-hasPermi=\"['system:menu:edit']\"\n          >修改</el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-plus\"\n            @click=\"handleAdd(scope.row)\"\n            v-hasPermi=\"['system:menu:add']\"\n          >新增</el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['system:menu:remove']\"\n          >删除</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <!-- 添加或修改菜单对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"680px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"100px\">\n        <el-row>\n          <el-col :span=\"24\">\n            <el-form-item label=\"上级菜单\" prop=\"parentId\">\n              <treeselect\n                v-model=\"form.parentId\"\n                :options=\"menuOptions\"\n                :normalizer=\"normalizer\"\n                :show-count=\"true\"\n                placeholder=\"选择上级菜单\"\n              />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"菜单类型\" prop=\"menuType\">\n              <el-radio-group v-model=\"form.menuType\">\n                <el-radio label=\"M\">目录</el-radio>\n                <el-radio label=\"C\">菜单</el-radio>\n                <el-radio label=\"F\">按钮</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\" v-if=\"form.menuType != 'F'\">\n            <el-form-item label=\"菜单图标\" prop=\"icon\">\n              <el-popover\n                placement=\"bottom-start\"\n                width=\"460\"\n                trigger=\"click\"\n                @show=\"$refs['iconSelect'].reset()\"\n              >\n                <IconSelect ref=\"iconSelect\" @selected=\"selected\" />\n                <el-input slot=\"reference\" v-model=\"form.icon\" placeholder=\"点击选择图标\" readonly>\n                  <svg-icon\n                    v-if=\"form.icon\"\n                    slot=\"prefix\"\n                    :icon-class=\"form.icon\"\n                    class=\"el-input__icon\"\n                    style=\"height: 32px;width: 16px;\"\n                  />\n                  <i v-else slot=\"prefix\" class=\"el-icon-search el-input__icon\" />\n                </el-input>\n              </el-popover>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"菜单名称\" prop=\"menuName\">\n              <el-input v-model=\"form.menuName\" placeholder=\"请输入菜单名称\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"显示排序\" prop=\"orderNum\">\n              <el-input-number v-model=\"form.orderNum\" controls-position=\"right\" :min=\"0\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\" v-if=\"form.menuType != 'F'\">\n            <el-form-item prop=\"isFrame\">\n              <span slot=\"label\">\n                <el-tooltip content=\"选择是外链则路由地址需要以`http(s)://`开头\" placement=\"top\">\n                <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n                是否外链\n              </span>\n              <el-radio-group v-model=\"form.isFrame\">\n                <el-radio label=\"0\">是</el-radio>\n                <el-radio label=\"1\">否</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\" v-if=\"form.menuType != 'F'\">\n            <el-form-item prop=\"path\">\n              <span slot=\"label\">\n                <el-tooltip content=\"访问的路由地址，如：`user`，如外网地址需内链访问则以`http(s)://`开头\" placement=\"top\">\n                <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n                路由地址\n              </span>\n              <el-input v-model=\"form.path\" placeholder=\"请输入路由地址\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\" v-if=\"form.menuType == 'C'\">\n            <el-form-item prop=\"component\">\n              <span slot=\"label\">\n                <el-tooltip content=\"访问的组件路径，如：`system/user/index`，默认在`views`目录下\" placement=\"top\">\n                <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n                组件路径\n              </span>\n              <el-input v-model=\"form.component\" placeholder=\"请输入组件路径\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\" v-if=\"form.menuType != 'M'\">\n            <el-form-item prop=\"perms\">\n              <el-input v-model=\"form.perms\" placeholder=\"请输入权限标识\" maxlength=\"100\" />\n              <span slot=\"label\">\n                <el-tooltip content=\"控制器中定义的权限字符，如：@PreAuthorize(`@ss.hasPermi('system:user:list')`)\" placement=\"top\">\n                <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n                权限字符\n              </span>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\" v-if=\"form.menuType == 'C'\">\n            <el-form-item prop=\"query\">\n              <el-input v-model=\"form.query\" placeholder=\"请输入路由参数\" maxlength=\"255\" />\n              <span slot=\"label\">\n                <el-tooltip content='访问路由的默认传递参数，如：`{\"id\": 1, \"name\": \"ry\"}`' placement=\"top\">\n                <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n                路由参数\n              </span>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\" v-if=\"form.menuType == 'C'\">\n            <el-form-item prop=\"isCache\">\n              <span slot=\"label\">\n                <el-tooltip content=\"选择是则会被`keep-alive`缓存，需要匹配组件的`name`和地址保持一致\" placement=\"top\">\n                <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n                是否缓存\n              </span>\n              <el-radio-group v-model=\"form.isCache\">\n                <el-radio label=\"0\">缓存</el-radio>\n                <el-radio label=\"1\">不缓存</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\" v-if=\"form.menuType != 'F'\">\n            <el-form-item prop=\"visible\">\n              <span slot=\"label\">\n                <el-tooltip content=\"选择隐藏则路由将不会出现在侧边栏，但仍然可以访问\" placement=\"top\">\n                <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n                显示状态\n              </span>\n              <el-radio-group v-model=\"form.visible\">\n                <el-radio\n                  v-for=\"dict in dict.type.sys_show_hide\"\n                  :key=\"dict.value\"\n                  :label=\"dict.value\"\n                >{{dict.label}}</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\" v-if=\"form.menuType != 'F'\">\n            <el-form-item prop=\"status\">\n              <span slot=\"label\">\n                <el-tooltip content=\"选择停用则路由将不会出现在侧边栏，也不能被访问\" placement=\"top\">\n                <i class=\"el-icon-question\"></i>\n                </el-tooltip>\n                菜单状态\n              </span>\n              <el-radio-group v-model=\"form.status\">\n                <el-radio\n                  v-for=\"dict in dict.type.sys_normal_disable\"\n                  :key=\"dict.value\"\n                  :label=\"dict.value\"\n                >{{dict.label}}</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { listMenu, getMenu, delMenu, addMenu, updateMenu } from \"@/api/system/menu\";\nimport Treeselect from \"@riophae/vue-treeselect\";\nimport \"@riophae/vue-treeselect/dist/vue-treeselect.css\";\nimport IconSelect from \"@/components/IconSelect\";\n\nexport default {\n  name: \"Menu\",\n  dicts: ['sys_show_hide', 'sys_normal_disable'],\n  components: { Treeselect, IconSelect },\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 菜单表格树数据\n      menuList: [],\n      // 菜单树选项\n      menuOptions: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      // 是否展开，默认全部折叠\n      isExpandAll: false,\n      // 重新渲染表格状态\n      refreshTable: true,\n      // 查询参数\n      queryParams: {\n        menuName: undefined,\n        visible: undefined\n      },\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {\n        menuName: [\n          { required: true, message: \"菜单名称不能为空\", trigger: \"blur\" }\n        ],\n        orderNum: [\n          { required: true, message: \"菜单顺序不能为空\", trigger: \"blur\" }\n        ],\n        path: [\n          { required: true, message: \"路由地址不能为空\", trigger: \"blur\" }\n        ]\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    // 选择图标\n    selected(name) {\n      this.form.icon = name;\n    },\n    /** 查询菜单列表 */\n    getList() {\n      this.loading = true;\n      listMenu(this.queryParams).then(response => {\n        this.menuList = this.handleTree(response.data, \"menuId\");\n        this.loading = false;\n      });\n    },\n    /** 转换菜单数据结构 */\n    normalizer(node) {\n      if (node.children && !node.children.length) {\n        delete node.children;\n      }\n      return {\n        id: node.menuId,\n        label: node.menuName,\n        children: node.children\n      };\n    },\n    /** 查询菜单下拉树结构 */\n    getTreeselect() {\n      listMenu().then(response => {\n        this.menuOptions = [];\n        const menu = { menuId: 0, menuName: '主类目', children: [] };\n        menu.children = this.handleTree(response.data, \"menuId\");\n        this.menuOptions.push(menu);\n      });\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        menuId: undefined,\n        parentId: 0,\n        menuName: undefined,\n        icon: undefined,\n        menuType: \"M\",\n        orderNum: undefined,\n        isFrame: \"1\",\n        isCache: \"0\",\n        visible: \"0\",\n        status: \"0\"\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    /** 新增按钮操作 */\n    handleAdd(row) {\n      this.reset();\n      this.getTreeselect();\n      if (row != null && row.menuId) {\n        this.form.parentId = row.menuId;\n      } else {\n        this.form.parentId = 0;\n      }\n      this.open = true;\n      this.title = \"添加菜单\";\n    },\n    /** 展开/折叠操作 */\n    toggleExpandAll() {\n      this.refreshTable = false;\n      this.isExpandAll = !this.isExpandAll;\n      this.$nextTick(() => {\n        this.refreshTable = true;\n      });\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      this.getTreeselect();\n      getMenu(row.menuId).then(response => {\n        this.form = response.data;\n        this.open = true;\n        this.title = \"修改菜单\";\n      });\n    },\n    /** 提交按钮 */\n    submitForm: function() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          if (this.form.menuId != undefined) {\n            updateMenu(this.form).then(response => {\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            addMenu(this.form).then(response => {\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      this.$modal.confirm('是否确认删除名称为\"' + row.menuName + '\"的数据项？').then(function() {\n        return delMenu(row.menuId);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/system/notice/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"公告标题\" prop=\"noticeTitle\">\n        <el-input\n          v-model=\"queryParams.noticeTitle\"\n          placeholder=\"请输入公告标题\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"操作人员\" prop=\"createBy\">\n        <el-input\n          v-model=\"queryParams.createBy\"\n          placeholder=\"请输入操作人员\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"类型\" prop=\"noticeType\">\n        <el-select v-model=\"queryParams.noticeType\" placeholder=\"公告类型\" clearable>\n          <el-option\n            v-for=\"dict in dict.type.sys_notice_type\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAdd\"\n          v-hasPermi=\"['system:notice:add']\"\n        >新增</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"success\"\n          plain\n          icon=\"el-icon-edit\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleUpdate\"\n          v-hasPermi=\"['system:notice:edit']\"\n        >修改</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['system:notice:remove']\"\n        >删除</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"noticeList\" @selection-change=\"handleSelectionChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"序号\" align=\"center\" prop=\"noticeId\" width=\"100\" />\n      <el-table-column\n        label=\"公告标题\"\n        align=\"center\"\n        prop=\"noticeTitle\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column label=\"公告类型\" align=\"center\" prop=\"noticeType\" width=\"100\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_notice_type\" :value=\"scope.row.noticeType\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\" width=\"100\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_notice_status\" :value=\"scope.row.status\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"创建者\" align=\"center\" prop=\"createBy\" width=\"100\" />\n      <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" width=\"100\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n            v-hasPermi=\"['system:notice:edit']\"\n          >修改</el-button>\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['system:notice:remove']\"\n          >删除</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 添加或修改公告对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"780px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"80px\">\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"公告标题\" prop=\"noticeTitle\">\n              <el-input v-model=\"form.noticeTitle\" placeholder=\"请输入公告标题\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"公告类型\" prop=\"noticeType\">\n              <el-select v-model=\"form.noticeType\" placeholder=\"请选择公告类型\">\n                <el-option\n                  v-for=\"dict in dict.type.sys_notice_type\"\n                  :key=\"dict.value\"\n                  :label=\"dict.label\"\n                  :value=\"dict.value\"\n                ></el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"状态\">\n              <el-radio-group v-model=\"form.status\">\n                <el-radio\n                  v-for=\"dict in dict.type.sys_notice_status\"\n                  :key=\"dict.value\"\n                  :label=\"dict.value\"\n                >{{dict.label}}</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"内容\">\n              <editor v-model=\"form.noticeContent\" :min-height=\"192\"/>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { listNotice, getNotice, delNotice, addNotice, updateNotice } from \"@/api/system/notice\";\n\nexport default {\n  name: \"Notice\",\n  dicts: ['sys_notice_status', 'sys_notice_type'],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 公告表格数据\n      noticeList: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        noticeTitle: undefined,\n        createBy: undefined,\n        status: undefined\n      },\n      // 表单参数\n      form: {},\n      // 表单校验\n      rules: {\n        noticeTitle: [\n          { required: true, message: \"公告标题不能为空\", trigger: \"blur\" }\n        ],\n        noticeType: [\n          { required: true, message: \"公告类型不能为空\", trigger: \"change\" }\n        ]\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询公告列表 */\n    getList() {\n      this.loading = true;\n      listNotice(this.queryParams).then(response => {\n        this.noticeList = response.rows;\n        this.total = response.total;\n        this.loading = false;\n      });\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        noticeId: undefined,\n        noticeTitle: undefined,\n        noticeType: undefined,\n        noticeContent: undefined,\n        status: \"0\"\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.noticeId)\n      this.single = selection.length!=1\n      this.multiple = !selection.length\n    },\n    /** 新增按钮操作 */\n    handleAdd() {\n      this.reset();\n      this.open = true;\n      this.title = \"添加公告\";\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      const noticeId = row.noticeId || this.ids\n      getNotice(noticeId).then(response => {\n        this.form = response.data;\n        this.open = true;\n        this.title = \"修改公告\";\n      });\n    },\n    /** 提交按钮 */\n    submitForm: function() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          if (this.form.noticeId != undefined) {\n            updateNotice(this.form).then(response => {\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            addNotice(this.form).then(response => {\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const noticeIds = row.noticeId || this.ids\n      this.$modal.confirm('是否确认删除公告编号为\"' + noticeIds + '\"的数据项？').then(function() {\n        return delNotice(noticeIds);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/system/role/authUser.vue",
    "content": "<template>\n  <div class=\"app-container\">\n     <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\">\n      <el-form-item label=\"用户名称\" prop=\"userName\">\n        <el-input\n          v-model=\"queryParams.userName\"\n          placeholder=\"请输入用户名称\"\n          clearable\n          style=\"width: 240px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"手机号码\" prop=\"phonenumber\">\n        <el-input\n          v-model=\"queryParams.phonenumber\"\n          placeholder=\"请输入手机号码\"\n          clearable\n          style=\"width: 240px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"openSelectUser\"\n          v-hasPermi=\"['system:role:add']\"\n        >添加用户</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-circle-close\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"cancelAuthUserAll\"\n          v-hasPermi=\"['system:role:remove']\"\n        >批量取消授权</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-close\"\n          size=\"mini\"\n          @click=\"handleClose\"\n        >关闭</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"userList\" @selection-change=\"handleSelectionChange\">\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"用户名称\" prop=\"userName\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"用户昵称\" prop=\"nickName\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"邮箱\" prop=\"email\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"手机\" prop=\"phonenumber\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template slot-scope=\"scope\">\n          <dict-tag :options=\"dict.type.sys_normal_disable\" :value=\"scope.row.status\"/>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-circle-close\"\n            @click=\"cancelAuthUser(scope.row)\"\n            v-hasPermi=\"['system:role:remove']\"\n          >取消授权</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n    <select-user ref=\"select\" :roleId=\"queryParams.roleId\" @ok=\"handleQuery\" />\n  </div>\n</template>\n\n<script>\nimport { allocatedUserList, authUserCancel, authUserCancelAll } from \"@/api/system/role\";\nimport selectUser from \"./selectUser\";\n\nexport default {\n  name: \"AuthUser\",\n  dicts: ['sys_normal_disable'],\n  components: { selectUser },\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中用户组\n      userIds: [],\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 用户表格数据\n      userList: [],\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        roleId: undefined,\n        userName: undefined,\n        phonenumber: undefined\n      }\n    };\n  },\n  created() {\n    const roleId = this.$route.params && this.$route.params.roleId;\n    if (roleId) {\n      this.queryParams.roleId = roleId;\n      this.getList();\n    }\n  },\n  methods: {\n    /** 查询授权用户列表 */\n    getList() {\n      this.loading = true;\n      allocatedUserList(this.queryParams).then(response => {\n          this.userList = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    // 返回按钮\n    handleClose() {\n      const obj = { path: \"/system/role\" };\n      this.$tab.closeOpenPage(obj);\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.userIds = selection.map(item => item.userId)\n      this.multiple = !selection.length\n    },\n    /** 打开授权用户表弹窗 */\n    openSelectUser() {\n      this.$refs.select.show();\n    },\n    /** 取消授权按钮操作 */\n    cancelAuthUser(row) {\n      const roleId = this.queryParams.roleId;\n      this.$modal.confirm('确认要取消该用户\"' + row.userName + '\"角色吗？').then(function() {\n        return authUserCancel({ userId: row.userId, roleId: roleId });\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"取消授权成功\");\n      }).catch(() => {});\n    },\n    /** 批量取消授权按钮操作 */\n    cancelAuthUserAll(row) {\n      const roleId = this.queryParams.roleId;\n      const userIds = this.userIds.join(\",\");\n      this.$modal.confirm('是否取消选中用户授权数据项？').then(function() {\n        return authUserCancelAll({ roleId: roleId, userIds: userIds });\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"取消授权成功\");\n      }).catch(() => {});\n    }\n  }\n};\n</script>"
  },
  {
    "path": "vue_campus_admin/src/views/system/role/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form\n      :model=\"queryParams\"\n      ref=\"queryForm\"\n      size=\"small\"\n      :inline=\"true\"\n      v-show=\"showSearch\"\n    >\n      <el-form-item label=\"角色名称\" prop=\"roleName\">\n        <el-input\n          v-model=\"queryParams.roleName\"\n          placeholder=\"请输入角色名称\"\n          clearable\n          style=\"width: 240px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"权限字符\" prop=\"roleKey\">\n        <el-input\n          v-model=\"queryParams.roleKey\"\n          placeholder=\"请输入权限字符\"\n          clearable\n          style=\"width: 240px\"\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"角色状态\"\n          clearable\n          style=\"width: 240px\"\n        >\n          <el-option\n            v-for=\"dict in dict.type.sys_normal_disable\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 240px\"\n          value-format=\"yyyy-MM-dd\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n      <el-form-item>\n        <el-button\n          type=\"primary\"\n          icon=\"el-icon-search\"\n          size=\"mini\"\n          @click=\"handleQuery\"\n          >搜索</el-button\n        >\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\"\n          >重置</el-button\n        >\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-plus\"\n          size=\"mini\"\n          @click=\"handleAdd\"\n          v-hasPermi=\"['system:role:add']\"\n          >新增</el-button\n        >\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"success\"\n          plain\n          icon=\"el-icon-edit\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleUpdate\"\n          v-hasPermi=\"['system:role:edit']\"\n          >修改</el-button\n        >\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['system:role:remove']\"\n          >删除</el-button\n        >\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"warning\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleExport\"\n          v-hasPermi=\"['system:role:export']\"\n          >导出</el-button\n        >\n      </el-col>\n      <right-toolbar\n        :showSearch.sync=\"showSearch\"\n        @queryTable=\"getList\"\n      ></right-toolbar>\n    </el-row>\n\n    <el-table\n      v-loading=\"loading\"\n      :data=\"roleList\"\n      @selection-change=\"handleSelectionChange\"\n    >\n      <el-table-column type=\"selection\" width=\"55\" align=\"center\" />\n      <el-table-column label=\"角色编号\" prop=\"roleId\" width=\"120\" />\n      <el-table-column\n        label=\"角色名称\"\n        prop=\"roleName\"\n        :show-overflow-tooltip=\"true\"\n        width=\"150\"\n      />\n      <el-table-column\n        label=\"权限字符\"\n        prop=\"roleKey\"\n        :show-overflow-tooltip=\"true\"\n        width=\"150\"\n      />\n      <el-table-column label=\"显示顺序\" prop=\"roleSort\" width=\"100\" />\n      <el-table-column label=\"状态\" align=\"center\" width=\"100\">\n        <template slot-scope=\"scope\">\n          <el-switch\n            v-model=\"scope.row.status\"\n            active-value=\"0\"\n            inactive-value=\"1\"\n            @change=\"handleStatusChange(scope.row)\"\n          ></el-switch>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n      >\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"操作\"\n        align=\"center\"\n        class-name=\"small-padding fixed-width\"\n      >\n        <template slot-scope=\"scope\" v-if=\"scope.row.roleId != 1\">\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-edit\"\n            @click=\"handleUpdate(scope.row)\"\n            v-hasPermi=\"['system:role:edit']\"\n            >修改</el-button\n          >\n          <el-button\n            size=\"mini\"\n            type=\"text\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['system:role:remove']\"\n            >删除</el-button\n          >\n          <el-dropdown\n            size=\"mini\"\n            @command=\"(command) => handleCommand(command, scope.row)\"\n            v-hasPermi=\"['system:role:edit']\"\n          >\n            <span class=\"el-dropdown-link\">\n              <i class=\"el-icon-d-arrow-right el-icon--right\"></i>更多\n            </span>\n            <el-dropdown-menu slot=\"dropdown\">\n              <el-dropdown-item\n                command=\"handleAuthApi\"\n                icon=\"el-icon-circle-check\"\n                v-hasPermi=\"['system:role:edit']\"\n                >分配接口</el-dropdown-item\n              >\n              <el-dropdown-item\n                command=\"handleAuthUser\"\n                icon=\"el-icon-user\"\n                v-hasPermi=\"['system:role:edit']\"\n                >分配用户</el-dropdown-item\n              >\n            </el-dropdown-menu>\n          </el-dropdown>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <pagination\n      v-show=\"total > 0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n\n    <!-- 添加或修改角色配置对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"500px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"100px\">\n        <el-form-item label=\"角色名称\" prop=\"roleName\">\n          <el-input v-model=\"form.roleName\" placeholder=\"请输入角色名称\" />\n        </el-form-item>\n        <el-form-item prop=\"roleKey\">\n          <span slot=\"label\">\n            <el-tooltip\n              content=\"控制器中定义的权限字符，如：@PreAuthorize(`@ss.hasRole('admin')`)\"\n              placement=\"top\"\n            >\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n            权限字符\n          </span>\n          <el-input v-model=\"form.roleKey\" placeholder=\"请输入权限字符\" />\n        </el-form-item>\n        <el-form-item label=\"角色顺序\" prop=\"roleSort\">\n          <el-input-number\n            v-model=\"form.roleSort\"\n            controls-position=\"right\"\n            :min=\"0\"\n          />\n        </el-form-item>\n        <el-form-item label=\"状态\">\n          <el-radio-group v-model=\"form.status\">\n            <el-radio\n              v-for=\"dict in dict.type.sys_normal_disable\"\n              :key=\"dict.value\"\n              :label=\"dict.value\"\n              >{{ dict.label }}</el-radio\n            >\n          </el-radio-group>\n        </el-form-item>\n        <el-form-item label=\"菜单权限\">\n          <el-checkbox\n            v-model=\"menuExpand\"\n            @change=\"handleCheckedTreeExpand($event, 'menu')\"\n            >展开/折叠</el-checkbox\n          >\n          <el-checkbox\n            v-model=\"menuNodeAll\"\n            @change=\"handleCheckedTreeNodeAll($event, 'menu')\"\n            >全选/全不选</el-checkbox\n          >\n          <el-checkbox\n            v-model=\"form.menuCheckStrictly\"\n            @change=\"handleCheckedTreeConnect($event, 'menu')\"\n            >父子联动</el-checkbox\n          >\n          <el-tree\n            class=\"tree-border\"\n            :data=\"menuOptions\"\n            show-checkbox\n            ref=\"menu\"\n            node-key=\"id\"\n            :check-strictly=\"!form.menuCheckStrictly\"\n            empty-text=\"加载中，请稍候\"\n            :props=\"defaultProps\"\n          ></el-tree>\n        </el-form-item>\n        <el-form-item label=\"备注\">\n          <el-input\n            v-model=\"form.remark\"\n            type=\"textarea\"\n            placeholder=\"请输入内容\"\n          ></el-input>\n        </el-form-item>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n\n    <el-dialog\n      title=\"分配接口\"\n      :visible.sync=\"openAuthApi\"\n      width=\"500px\"\n      append-to-body\n    >\n      <el-form ref=\"form2\" label-width=\"100px\">\n        <el-form-item label=\"菜单权限\">\n          <el-checkbox\n            v-model=\"resourceExpand\"\n            @change=\"handleCheckedTreeExpand($event, 'resource')\"\n            >展开/折叠</el-checkbox\n          >\n          <el-checkbox\n            v-model=\"resourceNodeAll\"\n            @change=\"handleCheckedTreeNodeAll($event, 'resource')\"\n            >全选/全不选</el-checkbox\n          >\n          <el-checkbox\n            v-model=\"form.resourceCheckStrictly\"\n            @change=\"handleCheckedTreeConnect($event, 'resource')\"\n            >父子联动</el-checkbox\n          >\n          <el-tree\n            class=\"tree-border\"\n            :data=\"resourceOptions\"\n            show-checkbox\n            ref=\"resource\"\n            node-key=\"id\"\n            :check-strictly=\"!form.resourceCheckStrictly\"\n            empty-text=\"加载中，请稍候\"\n            :props=\"defaultProps\"\n          ></el-tree>\n        </el-form-item>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitApiResource\">确 定</el-button>\n        <el-button @click=\"cancelApiResource\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport {\n  listRole,\n  getRole,\n  delRole,\n  addRole,\n  updateRole,\n  dataScope,\n  changeRoleStatus,\n} from \"@/api/system/role\";\nimport {\n  treeselect as menuTreeselect,\n  roleMenuTreeselect,\n} from \"@/api/system/menu\";\nimport {\n  roleResourceTreeselect,\n  editRoleResource,\n} from \"@/api/system/resource\";\n\nexport default {\n  name: \"Role\",\n  dicts: [\"sys_normal_disable\"],\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 角色表格数据\n      roleList: [],\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      //是否显示弹出层(分配接口)\n      openAuthApi: false,\n      menuExpand: false,\n      menuNodeAll: false,\n      resourceExpand: true,\n      resourceNodeAll: false,\n      // 日期范围\n      dateRange: [],\n      // 菜单列表\n      menuOptions: [],\n      // 资源列表\n      resourceOptions: [],\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        roleName: undefined,\n        roleKey: undefined,\n        status: undefined,\n      },\n      // 表单参数\n      form: {},\n      defaultProps: {\n        children: \"children\",\n        label: \"label\",\n      },\n      // 表单校验\n      rules: {\n        roleName: [\n          { required: true, message: \"角色名称不能为空\", trigger: \"blur\" },\n        ],\n        roleKey: [\n          { required: true, message: \"权限字符不能为空\", trigger: \"blur\" },\n        ],\n        roleSort: [\n          { required: true, message: \"角色顺序不能为空\", trigger: \"blur\" },\n        ],\n      },\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询角色列表 */\n    getList() {\n      this.loading = true;\n      listRole(this.addDateRange(this.queryParams, this.dateRange)).then(\n        (response) => {\n          this.roleList = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    /** 查询菜单树结构 */\n    getMenuTreeselect() {\n      menuTreeselect().then((response) => {\n        this.menuOptions = response.data;\n      });\n    },\n    // 所有菜单节点数据\n    getMenuAllCheckedKeys() {\n      // 目前被选中的菜单节点\n      let checkedKeys = this.$refs.menu.getCheckedKeys();\n      // 半选中的菜单节点\n      let halfCheckedKeys = this.$refs.menu.getHalfCheckedKeys();\n      checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);\n      return checkedKeys;\n    },\n    // 所有api节点数据\n    getResourceAllCheckedKeys() {\n      // 目前被选中的api节点 仅返回被选中的叶子节点的 keys\n      let checkedKeys = this.$refs.resource.getCheckedKeys(true);\n\n      return checkedKeys;\n    },\n    /** 根据角色ID查询菜单树结构 */\n    getRoleMenuTreeselect(roleId) {\n      return roleMenuTreeselect(roleId).then((response) => {\n        this.menuOptions = response.menus;\n        return response;\n      });\n    },\n    /** 根据角色ID查询api资源树结构 */\n    getRoleResourceTreeselect(roleId) {\n      return roleResourceTreeselect(roleId).then((response) => {\n        this.resourceOptions = response.resources;\n        return response;\n      });\n    },\n    // 角色状态修改\n    handleStatusChange(row) {\n      let text = row.status === \"0\" ? \"启用\" : \"停用\";\n      this.$modal\n        .confirm('确认要\"' + text + '\"\"' + row.roleName + '\"角色吗？')\n        .then(function () {\n          return changeRoleStatus(row.roleId, row.status);\n        })\n        .then(() => {\n          this.$modal.msgSuccess(text + \"成功\");\n        })\n        .catch(function () {\n          row.status = row.status === \"0\" ? \"1\" : \"0\";\n        });\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 取消按钮（分配接口）\n    cancelApiResource() {\n      this.openAuthApi = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      if (this.$refs.menu != undefined) {\n        this.$refs.menu.setCheckedKeys([]);\n      }\n      (this.menuExpand = false),\n        (this.menuNodeAll = false),\n        (this.resourceExpand = true),\n        (this.resourceNodeAll = false),\n        (this.form = {\n          roleId: undefined,\n          roleName: undefined,\n          roleKey: undefined,\n          roleSort: 0,\n          status: \"0\",\n          menuIds: [],\n          resourceIds: [],\n          menuCheckStrictly: true,\n          resourceCheckStrictly: true,\n          remark: undefined,\n        });\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map((item) => item.roleId);\n      this.single = selection.length != 1;\n      this.multiple = !selection.length;\n    },\n    // 更多操作触发\n    handleCommand(command, row) {\n      switch (command) {\n        case \"handleAuthApi\":\n          this.handleAuthApi(row);\n          break;\n        case \"handleAuthUser\":\n          this.handleAuthUser(row);\n          break;\n        default:\n          break;\n      }\n    },\n    // 树权限（展开/折叠）\n    handleCheckedTreeExpand(value, type) {\n      if (type == \"menu\") {\n        let treeList = this.menuOptions;\n        for (let i = 0; i < treeList.length; i++) {\n          this.$refs.menu.store.nodesMap[treeList[i].id].expanded = value;\n        }\n      } else if (type == \"resource\") {\n        let treeList = this.resourceOptions;\n        for (let i = 0; i < treeList.length; i++) {\n          this.$refs.resource.store.nodesMap[treeList[i].id].expanded = value;\n        }\n      }\n    },\n    // 树权限（全选/全不选）\n    handleCheckedTreeNodeAll(value, type) {\n      if (type == \"menu\") {\n        this.$refs.menu.setCheckedNodes(value ? this.menuOptions : []);\n      } else if (type == \"resource\") {\n        this.$refs.resource.setCheckedNodes(value ? this.resourceOptions : []);\n      }\n    },\n    // 树权限（父子联动）\n    handleCheckedTreeConnect(value, type) {\n      if (type == \"menu\") {\n        this.form.menuCheckStrictly = value ? true : false;\n      } else if (type == \"resource\") {\n        this.form.resourceCheckStrictly = value ? true : false;\n      }\n    },\n    /** 新增按钮操作 */\n    handleAdd() {\n      this.reset();\n      this.getMenuTreeselect();\n      this.open = true;\n      this.title = \"添加角色\";\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      const roleId = row.roleId || this.ids;\n      const roleMenu = this.getRoleMenuTreeselect(roleId);\n      getRole(roleId).then((response) => {\n        this.form = response.data;\n        this.open = true;\n        this.$nextTick(() => {\n          roleMenu.then((res) => {\n            let checkedKeys = res.checkedKeys;\n            checkedKeys.forEach((v) => {\n              this.$nextTick(() => {\n                this.$refs.menu.setChecked(v, true, false);\n              });\n            });\n          });\n        });\n        this.title = \"修改角色\";\n      });\n    },\n    /** 分配接口 */\n    handleAuthApi(row) {\n      this.reset();\n      const roleId = row.roleId || this.ids;\n      this.form.roleId = roleId;\n      const roleResource = this.getRoleResourceTreeselect(roleId);\n      this.openAuthApi = true;\n      this.$nextTick(() => {\n        roleResource.then((res) => {\n          let checkedKeys = res.checkedKeys;\n          checkedKeys.forEach((v) => {\n            this.$nextTick(() => {\n              this.$refs.resource.setChecked(v, true, false);\n            });\n          });\n        });\n      });\n      this.title = \"分配接口\";\n    },\n    /** 分配用户操作 */\n    handleAuthUser: function (row) {\n      const roleId = row.roleId;\n      this.$router.push(\"/system/role-auth/user/\" + roleId);\n    },\n    /** 提交按钮 */\n    submitForm: function () {\n      this.$refs[\"form\"].validate((valid) => {\n        if (valid) {\n          if (this.form.roleId != undefined) {\n            this.form.menuIds = this.getMenuAllCheckedKeys();\n            updateRole(this.form).then((response) => {\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            this.form.menuIds = this.getMenuAllCheckedKeys();\n            addRole(this.form).then((response) => {\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    /** 提交按钮（分配接口） */\n    submitApiResource: function () {\n      if (this.form.roleId != undefined) {\n        this.form.resourceIds = this.getResourceAllCheckedKeys();\n        let resourceIds = this.form.resourceIds.join(\",\");\n\n        editRoleResource({\n          roleId: this.form.roleId,\n          resourceIds: resourceIds,\n        }).then((response) => {\n          this.$modal.msgSuccess(\"修改成功\");\n           this.openAuthApi = false;\n        });\n        // console.log(this.form.resourceIds);\n      }\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const roleIds = row.roleId || this.ids;\n      this.$modal\n        .confirm('是否确认删除角色编号为\"' + roleIds + '\"的数据项？')\n        .then(function () {\n          return delRole(roleIds);\n        })\n        .then(() => {\n          this.getList();\n          this.$modal.msgSuccess(\"删除成功\");\n        })\n        .catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download(\n        \"system/role/export\",\n        {\n          ...this.queryParams,\n        },\n        `role_${new Date().getTime()}.xlsx`\n      );\n    },\n  },\n};\n</script>"
  },
  {
    "path": "vue_campus_admin/src/views/system/role/selectUser.vue",
    "content": "<template>\n  <!-- 授权用户 -->\n  <el-dialog title=\"选择用户\" :visible.sync=\"visible\" width=\"800px\" top=\"5vh\" append-to-body>\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\">\n      <el-form-item label=\"用户名称\" prop=\"userName\">\n        <el-input\n          v-model=\"queryParams.userName\"\n          placeholder=\"请输入用户名称\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"手机号码\" prop=\"phonenumber\">\n        <el-input\n          v-model=\"queryParams.phonenumber\"\n          placeholder=\"请输入手机号码\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n    <el-row>\n      <el-table @row-click=\"clickRow\" ref=\"table\" :data=\"userList\" @selection-change=\"handleSelectionChange\" height=\"260px\">\n        <el-table-column type=\"selection\" width=\"55\"></el-table-column>\n        <el-table-column label=\"用户名称\" prop=\"userName\" :show-overflow-tooltip=\"true\" />\n        <el-table-column label=\"用户昵称\" prop=\"nickName\" :show-overflow-tooltip=\"true\" />\n        <el-table-column label=\"邮箱\" prop=\"email\" :show-overflow-tooltip=\"true\" />\n        <el-table-column label=\"手机\" prop=\"phonenumber\" :show-overflow-tooltip=\"true\" />\n        <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n          <template slot-scope=\"scope\">\n            <dict-tag :options=\"dict.type.sys_normal_disable\" :value=\"scope.row.status\"/>\n          </template>\n        </el-table-column>\n        <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" width=\"180\">\n          <template slot-scope=\"scope\">\n            <span>{{ parseTime(scope.row.createTime) }}</span>\n          </template>\n        </el-table-column>\n      </el-table>\n      <pagination\n        v-show=\"total>0\"\n        :total=\"total\"\n        :page.sync=\"queryParams.pageNum\"\n        :limit.sync=\"queryParams.pageSize\"\n        @pagination=\"getList\"\n      />\n    </el-row>\n    <div slot=\"footer\" class=\"dialog-footer\">\n      <el-button type=\"primary\" @click=\"handleSelectUser\">确 定</el-button>\n      <el-button @click=\"visible = false\">取 消</el-button>\n    </div>\n  </el-dialog>\n</template>\n\n<script>\nimport { unallocatedUserList, authUserSelectAll } from \"@/api/system/role\";\nexport default {\n  dicts: ['sys_normal_disable'],\n  props: {\n    // 角色编号\n    roleId: {\n      type: [Number, String]\n    }\n  },\n  data() {\n    return {\n      // 遮罩层\n      visible: false,\n      // 选中数组值\n      userIds: [],\n      // 总条数\n      total: 0,\n      // 未授权用户数据\n      userList: [],\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        roleId: undefined,\n        userName: undefined,\n        phonenumber: undefined\n      }\n    };\n  },\n  methods: {\n    // 显示弹框\n    show() {\n      this.queryParams.roleId = this.roleId;\n      this.getList();\n      this.visible = true;\n    },\n    clickRow(row) {\n      this.$refs.table.toggleRowSelection(row);\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.userIds = selection.map(item => item.userId);\n    },\n    // 查询表数据\n    getList() {\n      unallocatedUserList(this.queryParams).then(res => {\n        this.userList = res.rows;\n        this.total = res.total;\n      });\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    /** 选择授权用户操作 */\n    handleSelectUser() {\n      const roleId = this.queryParams.roleId;\n      const userIds = this.userIds.join(\",\");\n      if (userIds == \"\") {\n        this.$modal.msgError(\"请选择要分配的用户\");\n        return;\n      }\n      authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => {\n        this.$modal.msgSuccess(res.msg);\n        if (res.code === 200) {\n          this.visible = false;\n          this.$emit(\"ok\");\n        }\n      });\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/system/user/authRole.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <h4 class=\"form-header h4\">基本信息</h4>\n    <el-form ref=\"form\" :model=\"form\" label-width=\"80px\">\n      <el-row>\n        <el-col :span=\"8\" :offset=\"2\">\n          <el-form-item label=\"用户昵称\" prop=\"nickName\">\n            <el-input v-model=\"form.nickName\" disabled />\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"8\" :offset=\"2\">\n          <el-form-item label=\"登录账号\" prop=\"userName\">\n            <el-input  v-model=\"form.userName\" disabled />\n          </el-form-item>\n        </el-col>\n      </el-row>\n    </el-form>\n\n    <h4 class=\"form-header h4\">角色信息</h4>\n    <el-table v-loading=\"loading\" :row-key=\"getRowKey\" @row-click=\"clickRow\" ref=\"table\" @selection-change=\"handleSelectionChange\" :data=\"roles.slice((pageNum-1)*pageSize,pageNum*pageSize)\">\n      <el-table-column label=\"序号\" type=\"index\" align=\"center\">\n        <template slot-scope=\"scope\">\n          <span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span>\n        </template>\n      </el-table-column>\n      <el-table-column type=\"selection\" :reserve-selection=\"true\" width=\"55\"></el-table-column>\n      <el-table-column label=\"角色编号\" align=\"center\" prop=\"roleId\" />\n      <el-table-column label=\"角色名称\" align=\"center\" prop=\"roleName\" />\n      <el-table-column label=\"权限字符\" align=\"center\" prop=\"roleKey\" />\n      <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" width=\"180\">\n        <template slot-scope=\"scope\">\n          <span>{{ parseTime(scope.row.createTime) }}</span>\n        </template>\n      </el-table-column>\n    </el-table>\n    \n    <pagination v-show=\"total>0\" :total=\"total\" :page.sync=\"pageNum\" :limit.sync=\"pageSize\" />\n\n    <el-form label-width=\"100px\">\n      <el-form-item style=\"text-align: center;margin-left:-120px;margin-top:30px;\">\n        <el-button type=\"primary\" @click=\"submitForm()\">提交</el-button>\n        <el-button @click=\"close()\">返回</el-button>\n      </el-form-item>\n    </el-form>\n  </div>\n</template>\n\n<script>\nimport { getAuthRole, updateAuthRole } from \"@/api/system/user\";\n\nexport default {\n  name: \"AuthRole\",\n  data() {\n    return {\n       // 遮罩层\n      loading: true,\n      // 分页信息\n      total: 0,\n      pageNum: 1,\n      pageSize: 10,\n      // 选中角色编号\n      roleIds:[],\n      // 角色信息\n      roles: [],\n      // 用户信息\n      form: {}\n    };\n  },\n  created() {\n    const userId = this.$route.params && this.$route.params.userId;\n    if (userId) {\n      this.loading = true;\n      getAuthRole(userId).then((response) => {\n        this.form = response.user;\n        this.roles = response.roles;\n        this.total = this.roles.length;\n        this.$nextTick(() => {\n          this.roles.forEach((row) => {\n            if (row.flag) {\n              this.$refs.table.toggleRowSelection(row);\n            }\n          });\n        });\n        this.loading = false;\n      });\n    }\n  },\n  methods: {\n    /** 单击选中行数据 */\n    clickRow(row) {\n      this.$refs.table.toggleRowSelection(row);\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.roleIds = selection.map((item) => item.roleId);\n    },\n    // 保存选中的数据编号\n    getRowKey(row) {\n      return row.roleId;\n    },\n    /** 提交按钮 */\n    submitForm() {\n      const userId = this.form.userId;\n      const roleIds = this.roleIds.join(\",\");\n      updateAuthRole({ userId: userId, roleIds: roleIds }).then((response) => {\n        this.$modal.msgSuccess(\"授权成功\");\n        this.close();\n      });\n    },\n    /** 关闭按钮 */\n    close() {\n      const obj = { path: \"/system/user\" };\n      this.$tab.closeOpenPage(obj);\n    },\n  },\n};\n</script>"
  },
  {
    "path": "vue_campus_admin/src/views/system/user/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n      <!--用户数据-->\n        <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n          <el-form-item label=\"用户名称\" prop=\"userName\">\n            <el-input\n              v-model=\"queryParams.userName\"\n              placeholder=\"请输入用户名称\"\n              clearable\n              style=\"width: 240px\"\n              @keyup.enter.native=\"handleQuery\"\n            />\n          </el-form-item>\n          <el-form-item label=\"手机号码\" prop=\"phonenumber\">\n            <el-input\n              v-model=\"queryParams.phonenumber\"\n              placeholder=\"请输入手机号码\"\n              clearable\n              style=\"width: 240px\"\n              @keyup.enter.native=\"handleQuery\"\n            />\n          </el-form-item>\n          <el-form-item label=\"状态\" prop=\"status\">\n            <el-select\n              v-model=\"queryParams.status\"\n              placeholder=\"用户状态\"\n              clearable\n              style=\"width: 240px\"\n            >\n              <el-option\n                v-for=\"dict in dict.type.sys_normal_disable\"\n                :key=\"dict.value\"\n                :label=\"dict.label\"\n                :value=\"dict.value\"\n              />\n            </el-select>\n          </el-form-item>\n          <el-form-item label=\"创建时间\">\n            <el-date-picker\n              v-model=\"dateRange\"\n              style=\"width: 240px\"\n              value-format=\"yyyy-MM-dd\"\n              type=\"daterange\"\n              range-separator=\"-\"\n              start-placeholder=\"开始日期\"\n              end-placeholder=\"结束日期\"\n            ></el-date-picker>\n          </el-form-item>\n          <el-form-item>\n            <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n            <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n          </el-form-item>\n        </el-form>\n\n        <el-row :gutter=\"10\" class=\"mb8\">\n          <el-col :span=\"1.5\">\n            <el-button\n              type=\"primary\"\n              plain\n              icon=\"el-icon-plus\"\n              size=\"mini\"\n              @click=\"handleAdd\"\n              v-hasPermi=\"['system:user:add']\"\n            >新增</el-button>\n          </el-col>\n          <el-col :span=\"1.5\">\n            <el-button\n              type=\"success\"\n              plain\n              icon=\"el-icon-edit\"\n              size=\"mini\"\n              :disabled=\"single\"\n              @click=\"handleUpdate\"\n              v-hasPermi=\"['system:user:edit']\"\n            >修改</el-button>\n          </el-col>\n          <el-col :span=\"1.5\">\n            <el-button\n              type=\"danger\"\n              plain\n              icon=\"el-icon-delete\"\n              size=\"mini\"\n              :disabled=\"multiple\"\n              @click=\"handleDelete\"\n              v-hasPermi=\"['system:user:remove']\"\n            >删除</el-button>\n          </el-col>\n          <el-col :span=\"1.5\">\n            <el-button\n              type=\"info\"\n              plain\n              icon=\"el-icon-upload2\"\n              size=\"mini\"\n              @click=\"handleImport\"\n              v-hasPermi=\"['system:user:import']\"\n            >导入</el-button>\n          </el-col>\n          <el-col :span=\"1.5\">\n            <el-button\n              type=\"warning\"\n              plain\n              icon=\"el-icon-download\"\n              size=\"mini\"\n              @click=\"handleExport\"\n              v-hasPermi=\"['system:user:export']\"\n            >导出</el-button>\n          </el-col>\n          <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\" :columns=\"columns\"></right-toolbar>\n        </el-row>\n\n        <el-table v-loading=\"loading\" :data=\"userList\" @selection-change=\"handleSelectionChange\">\n          <el-table-column type=\"selection\" width=\"50\" align=\"center\" />\n          <el-table-column label=\"用户编号\" align=\"center\" key=\"userId\" prop=\"userId\" v-if=\"columns[0].visible\" />\n          <el-table-column label=\"用户名称\" align=\"center\" key=\"userName\" prop=\"userName\" v-if=\"columns[1].visible\" :show-overflow-tooltip=\"true\" />\n          <el-table-column label=\"用户昵称\" align=\"center\" key=\"nickName\" prop=\"nickName\" v-if=\"columns[2].visible\" :show-overflow-tooltip=\"true\" />\n          <el-table-column label=\"手机号码\" align=\"center\" key=\"phonenumber\" prop=\"phonenumber\" v-if=\"columns[3].visible\" width=\"120\" />\n          <el-table-column label=\"状态\" align=\"center\" key=\"status\" v-if=\"columns[4].visible\">\n            <template slot-scope=\"scope\">\n              <el-switch\n                v-model=\"scope.row.status\"\n                active-value=\"0\"\n                inactive-value=\"1\"\n                @change=\"handleStatusChange(scope.row)\"\n              ></el-switch>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" v-if=\"columns[5].visible\" width=\"160\">\n            <template slot-scope=\"scope\">\n              <span>{{ parseTime(scope.row.createTime) }}</span>\n            </template>\n          </el-table-column>\n          <el-table-column\n            label=\"操作\"\n            align=\"center\"\n            width=\"160\"\n            class-name=\"small-padding fixed-width\"\n          >\n            <template slot-scope=\"scope\" v-if=\"scope.row.userId !== 1\">\n              <el-button\n                size=\"mini\"\n                type=\"text\"\n                icon=\"el-icon-edit\"\n                @click=\"handleUpdate(scope.row)\"\n                v-hasPermi=\"['system:user:edit']\"\n              >修改</el-button>\n              <el-button\n                size=\"mini\"\n                type=\"text\"\n                icon=\"el-icon-delete\"\n                @click=\"handleDelete(scope.row)\"\n                v-hasPermi=\"['system:user:remove']\"\n              >删除</el-button>\n              <el-dropdown size=\"mini\" @command=\"(command) => handleCommand(command, scope.row)\" v-hasPermi=\"['system:user:resetPwd', 'system:user:edit']\">\n                <span class=\"el-dropdown-link\">\n                  <i class=\"el-icon-d-arrow-right el-icon--right\"></i>更多\n                </span>\n                <el-dropdown-menu slot=\"dropdown\">\n                  <el-dropdown-item command=\"handleResetPwd\" icon=\"el-icon-key\"\n                    v-hasPermi=\"['system:user:resetPwd']\">重置密码</el-dropdown-item>\n                  <el-dropdown-item command=\"handleAuthRole\" icon=\"el-icon-circle-check\"\n                    v-hasPermi=\"['system:user:edit']\">分配角色</el-dropdown-item>\n                </el-dropdown-menu>\n              </el-dropdown>\n            </template>\n          </el-table-column>\n        </el-table>\n\n        <pagination\n          v-show=\"total>0\"\n          :total=\"total\"\n          :page.sync=\"queryParams.pageNum\"\n          :limit.sync=\"queryParams.pageSize\"\n          @pagination=\"getList\"\n        />\n      \n\n\n    <!-- 添加或修改用户配置对话框 -->\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"600px\" append-to-body>\n      <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"80px\">\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"用户昵称\" prop=\"nickName\">\n              <el-input v-model=\"form.nickName\" placeholder=\"请输入用户昵称\" maxlength=\"30\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item v-if=\"form.userId == undefined\" label=\"用户账号\" prop=\"userName\">\n              <el-input v-model=\"form.userName\" placeholder=\"请输入用户账号\" maxlength=\"30\" />\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"手机号码\" prop=\"phonenumber\">\n              <el-input v-model=\"form.phonenumber\" placeholder=\"请输入手机号码\" maxlength=\"11\" />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item label=\"邮箱\" prop=\"email\">\n              <el-input v-model=\"form.email\" placeholder=\"请输入邮箱\" maxlength=\"50\" />\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item v-if=\"form.userId == undefined\" label=\"用户密码\" prop=\"password\">\n              <el-input v-model=\"form.password\" placeholder=\"请输入用户密码\" type=\"password\" maxlength=\"20\" show-password/>\n            </el-form-item>\n          </el-col>\n            <el-col :span=\"12\">\n              <el-form-item label=\"用户性别\">\n                <el-select v-model=\"form.sex\" placeholder=\"请选择性别\">\n                  <el-option\n                    v-for=\"dict in dict.type.sys_user_sex\"\n                    :key=\"dict.value\"\n                    :label=\"dict.label\"\n                    :value=\"dict.value\"\n                  ></el-option>\n                </el-select>\n              </el-form-item>\n            </el-col>\n        </el-row>\n        <el-row>\n          <el-col :span=\"12\">\n            <el-form-item label=\"状态\">\n              <el-radio-group v-model=\"form.status\">\n                <el-radio\n                  v-for=\"dict in dict.type.sys_normal_disable\"\n                  :key=\"dict.value\"\n                  :label=\"dict.value\"\n                >{{dict.label}}</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n         <el-col :span=\"12\">\n            <el-form-item label=\"角色\">\n              <el-select v-model=\"form.roleIds\" multiple placeholder=\"请选择角色\">\n                <el-option\n                  v-for=\"item in roleOptions\"\n                  :key=\"item.roleId\"\n                  :label=\"item.roleName\"\n                  :value=\"item.roleId\"\n                  :disabled=\"item.status == 1\"\n                ></el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n        </el-row>\n\n        <el-row>\n          <el-col :span=\"24\">\n            <el-form-item label=\"备注\">\n              <el-input v-model=\"form.remark\" type=\"textarea\" placeholder=\"请输入内容\"></el-input>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n        <el-button @click=\"cancel\">取 消</el-button>\n      </div>\n    </el-dialog>\n\n    <!-- 用户导入对话框 -->\n    <el-dialog :title=\"upload.title\" :visible.sync=\"upload.open\" width=\"400px\" append-to-body>\n      <el-upload\n        ref=\"upload\"\n        :limit=\"1\"\n        accept=\".xlsx, .xls\"\n        :headers=\"upload.headers\"\n        :action=\"upload.url + '?updateSupport=' + upload.updateSupport\"\n        :disabled=\"upload.isUploading\"\n        :on-progress=\"handleFileUploadProgress\"\n        :on-success=\"handleFileSuccess\"\n        :auto-upload=\"false\"\n        drag\n      >\n        <i class=\"el-icon-upload\"></i>\n        <div class=\"el-upload__text\">将文件拖到此处，或<em>点击上传</em></div>\n        <div class=\"el-upload__tip text-center\" slot=\"tip\">\n          <div class=\"el-upload__tip\" slot=\"tip\">\n            <el-checkbox v-model=\"upload.updateSupport\" /> 是否更新已经存在的用户数据\n          </div>\n          <span>仅允许导入xls、xlsx格式文件。</span>\n          <el-link type=\"primary\" :underline=\"false\" style=\"font-size:12px;vertical-align: baseline;\" @click=\"importTemplate\">下载模板</el-link>\n        </div>\n      </el-upload>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitFileForm\">确 定</el-button>\n        <el-button @click=\"upload.open = false\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUserStatus } from \"@/api/system/user\";\nimport { getToken } from \"@/utils/auth\";\nimport Treeselect from \"@riophae/vue-treeselect\";\nimport \"@riophae/vue-treeselect/dist/vue-treeselect.css\";\n\nexport default {\n  name: \"User\",\n  dicts: ['sys_normal_disable', 'sys_user_sex'],\n  components: { Treeselect },\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 选中数组\n      ids: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 用户表格数据\n      userList: null,\n      // 弹出层标题\n      title: \"\",\n      // 是否显示弹出层\n      open: false,\n      // 默认密码\n      initPassword: undefined,\n      // 日期范围\n      dateRange: [],\n      // 岗位选项\n      postOptions: [],\n      // 角色选项\n      roleOptions: [],\n      // 表单参数\n      form: {},\n      defaultProps: {\n        children: \"children\",\n        label: \"label\"\n      },\n      // 用户导入参数\n      upload: {\n        // 是否显示弹出层（用户导入）\n        open: false,\n        // 弹出层标题（用户导入）\n        title: \"\",\n        // 是否禁用上传\n        isUploading: false,\n        // 是否更新已经存在的用户数据\n        updateSupport: 0,\n        // 设置上传的请求头部\n        headers: { Authorization: \"Bearer \" + getToken() },\n        // 上传的地址\n        url: process.env.VUE_APP_BASE_API + \"/system/user/importData\"\n      },\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        userName: undefined,\n        phonenumber: undefined,\n        status: undefined\n      },\n      // 列信息\n      columns: [\n        { key: 0, label: `用户编号`, visible: true },\n        { key: 1, label: `用户名称`, visible: true },\n        { key: 2, label: `用户昵称`, visible: true },\n        { key: 3, label: `手机号码`, visible: true },\n        { key: 4, label: `状态`, visible: true },\n        { key: 5, label: `创建时间`, visible: true }\n      ],\n      // 表单校验\n      rules: {\n        userName: [\n          { required: true, message: \"用户名称不能为空\", trigger: \"blur\" },\n          { min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' }\n        ],\n        nickName: [\n          { required: true, message: \"用户昵称不能为空\", trigger: \"blur\" }\n        ],\n        password: [\n          { required: true, message: \"用户密码不能为空\", trigger: \"blur\" },\n          { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }\n        ],\n        email: [\n          {\n            type: \"email\",\n            message: \"请输入正确的邮箱地址\",\n            trigger: [\"blur\", \"change\"]\n          }\n        ],\n        phonenumber: [\n          {\n            pattern: /^1[3|4|5|6|7|8|9][0-9]\\d{8}$/,\n            message: \"请输入正确的手机号码\",\n            trigger: \"blur\"\n          }\n        ]\n      }\n    };\n  },\n  watch: {\n\n  },\n  created() {\n    this.getList();\n    this.getConfigKey(\"sys.user.initPassword\").then(response => {\n      this.initPassword = response.msg;\n    });\n  },\n  methods: {\n    /** 查询用户列表 */\n    getList() {\n      this.loading = true;\n      listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {\n          this.userList = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    // 用户状态修改\n    handleStatusChange(row) {\n      let text = row.status === \"0\" ? \"启用\" : \"停用\";\n      this.$modal.confirm('确认要\"' + text + '\"\"' + row.userName + '\"用户吗？').then(function() {\n        return changeUserStatus(row.userId, row.status);\n      }).then(() => {\n        this.$modal.msgSuccess(text + \"成功\");\n      }).catch(function() {\n        row.status = row.status === \"0\" ? \"1\" : \"0\";\n      });\n    },\n    // 取消按钮\n    cancel() {\n      this.open = false;\n      this.reset();\n    },\n    // 表单重置\n    reset() {\n      this.form = {\n        userId: undefined,\n        userName: undefined,\n        nickName: undefined,\n        password: undefined,\n        phonenumber: undefined,\n        email: undefined,\n        sex: undefined,\n        status: \"0\",\n        remark: undefined,\n        postIds: [],\n        roleIds: []\n      };\n      this.resetForm(\"form\");\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.userId);\n      this.single = selection.length != 1;\n      this.multiple = !selection.length;\n    },\n    // 更多操作触发\n    handleCommand(command, row) {\n      switch (command) {\n        case \"handleResetPwd\":\n          this.handleResetPwd(row);\n          break;\n        case \"handleAuthRole\":\n          this.handleAuthRole(row);\n          break;\n        default:\n          break;\n      }\n    },\n    /** 新增按钮操作 */\n    handleAdd() {\n      this.reset();\n      getUser().then(response => {\n        this.postOptions = response.posts;\n        this.roleOptions = response.roles;\n        this.open = true;\n        this.title = \"添加用户\";\n        this.form.password = this.initPassword;\n      });\n    },\n    /** 修改按钮操作 */\n    handleUpdate(row) {\n      this.reset();\n      const userId = row.userId || this.ids;\n      getUser(userId).then(response => {\n        this.form = response.data;\n        this.postOptions = response.posts;\n        this.roleOptions = response.roles;\n        this.form.postIds = response.postIds;\n        this.form.roleIds = response.roleIds;\n        this.open = true;\n        this.title = \"修改用户\";\n        this.form.password = \"\";\n      });\n    },\n    /** 重置密码按钮操作 */\n    handleResetPwd(row) {\n      this.$prompt('请输入\"' + row.userName + '\"的新密码', \"提示\", {\n        confirmButtonText: \"确定\",\n        cancelButtonText: \"取消\",\n        closeOnClickModal: false,\n        inputPattern: /^.{5,20}$/,\n        inputErrorMessage: \"用户密码长度必须介于 5 和 20 之间\"\n      }).then(({ value }) => {\n          resetUserPwd(row.userId, value).then(response => {\n            this.$modal.msgSuccess(\"修改成功，新密码是：\" + value);\n          });\n        }).catch(() => {});\n    },\n    /** 分配角色操作 */\n    handleAuthRole: function(row) {\n      const userId = row.userId;\n      this.$router.push(\"/system/user-auth/role/\" + userId);\n    },\n    /** 提交按钮 */\n    submitForm: function() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          if (this.form.userId != undefined) {\n            updateUser(this.form).then(response => {\n              this.$modal.msgSuccess(\"修改成功\");\n              this.open = false;\n              this.getList();\n            });\n          } else {\n            addUser(this.form).then(response => {\n              this.$modal.msgSuccess(\"新增成功\");\n              this.open = false;\n              this.getList();\n            });\n          }\n        }\n      });\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const userIds = row.userId || this.ids;\n      this.$modal.confirm('是否确认删除用户编号为\"' + userIds + '\"的数据项？').then(function() {\n        return delUser(userIds);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    },\n    /** 导出按钮操作 */\n    handleExport() {\n      this.download('system/user/export', {\n        ...this.queryParams\n      }, `user_${new Date().getTime()}.xlsx`)\n    },\n    /** 导入按钮操作 */\n    handleImport() {\n      this.upload.title = \"用户导入\";\n      this.upload.open = true;\n    },\n    /** 下载模板操作 */\n    importTemplate() {\n      this.download('system/user/importTemplate', {\n      }, `user_template_${new Date().getTime()}.xlsx`)\n    },\n    // 文件上传中处理\n    handleFileUploadProgress(event, file, fileList) {\n      this.upload.isUploading = true;\n    },\n    // 文件上传成功处理\n    handleFileSuccess(response, file, fileList) {\n      this.upload.open = false;\n      this.upload.isUploading = false;\n      this.$refs.upload.clearFiles();\n      this.$alert(\"<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>\" + response.msg + \"</div>\", \"导入结果\", { dangerouslyUseHTMLString: true });\n      this.getList();\n    },\n    // 提交上传文件\n    submitFileForm() {\n      this.$refs.upload.submit();\n    }\n  }\n};\n</script>"
  },
  {
    "path": "vue_campus_admin/src/views/system/user/profile/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-row :gutter=\"20\">\n      <el-col :span=\"6\" :xs=\"24\">\n        <el-card class=\"box-card\">\n          <div slot=\"header\" class=\"clearfix\">\n            <span>个人信息</span>\n          </div>\n          <div>\n            <div class=\"text-center\">\n              <userAvatar :user=\"user\" />\n            </div>\n            <ul class=\"list-group list-group-striped\">\n              <li class=\"list-group-item\">\n                <svg-icon icon-class=\"user\" />用户名称\n                <div class=\"pull-right\">{{ user.userName }}</div>\n              </li>\n              <li class=\"list-group-item\">\n                <svg-icon icon-class=\"phone\" />手机号码\n                <div class=\"pull-right\">{{ user.phonenumber }}</div>\n              </li>\n              <li class=\"list-group-item\">\n                <svg-icon icon-class=\"email\" />用户邮箱\n                <div class=\"pull-right\">{{ user.email }}</div>\n              </li>\n\n              <li class=\"list-group-item\">\n                <svg-icon icon-class=\"peoples\" />所属角色\n                <div class=\"pull-right\">{{ roleGroup }}</div>\n              </li>\n              <li class=\"list-group-item\">\n                <svg-icon icon-class=\"date\" />创建日期\n                <div class=\"pull-right\">{{ user.createTime }}</div>\n              </li>\n            </ul>\n          </div>\n        </el-card>\n      </el-col>\n      <el-col :span=\"18\" :xs=\"24\">\n        <el-card>\n          <div slot=\"header\" class=\"clearfix\">\n            <span>基本资料</span>\n          </div>\n          <el-tabs v-model=\"activeTab\">\n            <el-tab-pane label=\"基本资料\" name=\"userinfo\">\n              <userInfo :user=\"user\" />\n            </el-tab-pane>\n            <el-tab-pane label=\"修改密码\" name=\"resetPwd\">\n              <resetPwd />\n            </el-tab-pane>\n          </el-tabs>\n        </el-card>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n\n<script>\nimport userAvatar from \"./userAvatar\";\nimport userInfo from \"./userInfo\";\nimport resetPwd from \"./resetPwd\";\nimport { getUserProfile } from \"@/api/system/user\";\n\nexport default {\n  name: \"Profile\",\n  components: { userAvatar, userInfo, resetPwd },\n  data() {\n    return {\n      user: {},\n      roleGroup: {},\n      activeTab: \"userinfo\"\n    };\n  },\n  created() {\n    this.getUser();\n  },\n  methods: {\n    getUser() {\n      getUserProfile().then(response => {\n        this.user = response.data;\n        this.roleGroup = response.roleGroup;\n      });\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/system/user/profile/resetPwd.vue",
    "content": "<template>\n  <el-form ref=\"form\" :model=\"user\" :rules=\"rules\" label-width=\"80px\">\n    <el-form-item label=\"旧密码\" prop=\"oldPassword\">\n      <el-input v-model=\"user.oldPassword\" placeholder=\"请输入旧密码\" type=\"password\" show-password/>\n    </el-form-item>\n    <el-form-item label=\"新密码\" prop=\"newPassword\">\n      <el-input v-model=\"user.newPassword\" placeholder=\"请输入新密码\" type=\"password\" show-password/>\n    </el-form-item>\n    <el-form-item label=\"确认密码\" prop=\"confirmPassword\">\n      <el-input v-model=\"user.confirmPassword\" placeholder=\"请确认新密码\" type=\"password\" show-password/>\n    </el-form-item>\n    <el-form-item>\n      <el-button type=\"primary\" size=\"mini\" @click=\"submit\">保存</el-button>\n      <el-button type=\"danger\" size=\"mini\" @click=\"close\">关闭</el-button>\n    </el-form-item>\n  </el-form>\n</template>\n\n<script>\nimport { updateUserPwd } from \"@/api/system/user\";\n\nexport default {\n  data() {\n    const equalToPassword = (rule, value, callback) => {\n      if (this.user.newPassword !== value) {\n        callback(new Error(\"两次输入的密码不一致\"));\n      } else {\n        callback();\n      }\n    };\n    return {\n      user: {\n        oldPassword: undefined,\n        newPassword: undefined,\n        confirmPassword: undefined\n      },\n      // 表单校验\n      rules: {\n        oldPassword: [\n          { required: true, message: \"旧密码不能为空\", trigger: \"blur\" }\n        ],\n        newPassword: [\n          { required: true, message: \"新密码不能为空\", trigger: \"blur\" },\n          { min: 6, max: 20, message: \"长度在 6 到 20 个字符\", trigger: \"blur\" }\n        ],\n        confirmPassword: [\n          { required: true, message: \"确认密码不能为空\", trigger: \"blur\" },\n          { required: true, validator: equalToPassword, trigger: \"blur\" }\n        ]\n      }\n    };\n  },\n  methods: {\n    submit() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => {\n            this.$modal.msgSuccess(\"修改成功\");\n          });\n        }\n      });\n    },\n    close() {\n      this.$tab.closePage();\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/system/user/profile/userAvatar.vue",
    "content": "<template>\n  <div>\n    <div class=\"user-info-head\" @click=\"editCropper()\"><img v-bind:src=\"options.img\" title=\"点击上传头像\" class=\"img-circle img-lg\" /></div>\n    <el-dialog :title=\"title\" :visible.sync=\"open\" width=\"800px\" append-to-body @opened=\"modalOpened\"  @close=\"closeDialog\">\n      <el-row>\n        <el-col :xs=\"24\" :md=\"12\" :style=\"{height: '350px'}\">\n          <vue-cropper\n            ref=\"cropper\"\n            :img=\"options.img\"\n            :info=\"true\"\n            :autoCrop=\"options.autoCrop\"\n            :autoCropWidth=\"options.autoCropWidth\"\n            :autoCropHeight=\"options.autoCropHeight\"\n            :fixedBox=\"options.fixedBox\"\n            @realTime=\"realTime\"\n            v-if=\"visible\"\n          />\n        </el-col>\n        <el-col :xs=\"24\" :md=\"12\" :style=\"{height: '350px'}\">\n          <div class=\"avatar-upload-preview\">\n            <img :src=\"previews.url\" :style=\"previews.img\" />\n          </div>\n        </el-col>\n      </el-row>\n      <br />\n      <el-row>\n        <el-col :lg=\"2\" :md=\"2\">\n          <el-upload action=\"#\" :http-request=\"requestUpload\" :show-file-list=\"false\" :before-upload=\"beforeUpload\">\n            <el-button size=\"small\">\n              选择\n              <i class=\"el-icon-upload el-icon--right\"></i>\n            </el-button>\n          </el-upload>\n        </el-col>\n        <el-col :lg=\"{span: 1, offset: 2}\" :md=\"2\">\n          <el-button icon=\"el-icon-plus\" size=\"small\" @click=\"changeScale(1)\"></el-button>\n        </el-col>\n        <el-col :lg=\"{span: 1, offset: 1}\" :md=\"2\">\n          <el-button icon=\"el-icon-minus\" size=\"small\" @click=\"changeScale(-1)\"></el-button>\n        </el-col>\n        <el-col :lg=\"{span: 1, offset: 1}\" :md=\"2\">\n          <el-button icon=\"el-icon-refresh-left\" size=\"small\" @click=\"rotateLeft()\"></el-button>\n        </el-col>\n        <el-col :lg=\"{span: 1, offset: 1}\" :md=\"2\">\n          <el-button icon=\"el-icon-refresh-right\" size=\"small\" @click=\"rotateRight()\"></el-button>\n        </el-col>\n        <el-col :lg=\"{span: 2, offset: 6}\" :md=\"2\">\n          <el-button type=\"primary\" size=\"small\" @click=\"uploadImg()\">提 交</el-button>\n        </el-col>\n      </el-row>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\nimport store from \"@/store\";\nimport { VueCropper } from \"vue-cropper\";\nimport { uploadAvatar } from \"@/api/system/user\";\n\nexport default {\n  components: { VueCropper },\n  props: {\n    user: {\n      type: Object\n    }\n  },\n  data() {\n    return {\n      // 是否显示弹出层\n      open: false,\n      // 是否显示cropper\n      visible: false,\n      // 弹出层标题\n      title: \"修改头像\",\n      options: {\n        img: store.getters.avatar, //裁剪图片的地址\n        autoCrop: true, // 是否默认生成截图框\n        autoCropWidth: 200, // 默认生成截图框宽度\n        autoCropHeight: 200, // 默认生成截图框高度\n        fixedBox: true // 固定截图框大小 不允许改变\n      },\n      previews: {}\n    };\n  },\n  methods: {\n    // 编辑头像\n    editCropper() {\n      this.open = true;\n    },\n    // 打开弹出层结束时的回调\n    modalOpened() {\n      this.visible = true;\n    },\n    // 覆盖默认的上传行为\n    requestUpload() {\n    },\n    // 向左旋转\n    rotateLeft() {\n      this.$refs.cropper.rotateLeft();\n    },\n    // 向右旋转\n    rotateRight() {\n      this.$refs.cropper.rotateRight();\n    },\n    // 图片缩放\n    changeScale(num) {\n      num = num || 1;\n      this.$refs.cropper.changeScale(num);\n    },\n    // 上传预处理\n    beforeUpload(file) {\n      if (file.type.indexOf(\"image/\") == -1) {\n        this.$modal.msgError(\"文件格式错误，请上传图片类型,如：JPG，PNG后缀的文件。\");\n      } else {\n        const reader = new FileReader();\n        reader.readAsDataURL(file);\n        reader.onload = () => {\n          this.options.img = reader.result;\n        };\n      }\n    },\n    // 上传图片\n    uploadImg() {\n      this.$refs.cropper.getCropBlob(data => {\n        let formData = new FormData();\n        formData.append(\"avatarfile\", data);\n        uploadAvatar(formData).then(response => {\n          this.open = false;\n          this.options.img = process.env.VUE_APP_BASE_API + response.imgUrl;\n          store.commit('SET_AVATAR', this.options.img);\n          this.$modal.msgSuccess(\"修改成功\");\n          this.visible = false;\n        });\n      });\n    },\n    // 实时预览\n    realTime(data) {\n      this.previews = data;\n    },\n    // 关闭窗口\n    closeDialog() {\n      this.options.img = store.getters.avatar\n      this.visible = false;\n    }\n  }\n};\n</script>\n<style scoped lang=\"scss\">\n.user-info-head {\n  position: relative;\n  display: inline-block;\n  height: 120px;\n}\n\n.user-info-head:hover:after {\n  content: '+';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  color: #eee;\n  background: rgba(0, 0, 0, 0.5);\n  font-size: 24px;\n  font-style: normal;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  cursor: pointer;\n  line-height: 110px;\n  border-radius: 50%;\n}\n</style>"
  },
  {
    "path": "vue_campus_admin/src/views/system/user/profile/userInfo.vue",
    "content": "<template>\n  <el-form ref=\"form\" :model=\"user\" :rules=\"rules\" label-width=\"80px\">\n    <el-form-item label=\"用户昵称\" prop=\"nickName\">\n      <el-input v-model=\"user.nickName\" maxlength=\"30\" />\n    </el-form-item> \n    <el-form-item label=\"手机号码\" prop=\"phonenumber\">\n      <el-input v-model=\"user.phonenumber\" maxlength=\"11\" />\n    </el-form-item>\n    <el-form-item label=\"邮箱\" prop=\"email\">\n      <el-input v-model=\"user.email\" maxlength=\"50\" />\n    </el-form-item>\n    <el-form-item label=\"性别\">\n      <el-radio-group v-model=\"user.sex\">\n        <el-radio label=\"0\">男</el-radio>\n        <el-radio label=\"1\">女</el-radio>\n      </el-radio-group>\n    </el-form-item>\n    <el-form-item>\n      <el-button type=\"primary\" size=\"mini\" @click=\"submit\">保存</el-button>\n      <el-button type=\"danger\" size=\"mini\" @click=\"close\">关闭</el-button>\n    </el-form-item>\n  </el-form>\n</template>\n\n<script>\nimport { updateUserProfile } from \"@/api/system/user\";\n\nexport default {\n  props: {\n    user: {\n      type: Object\n    }\n  },\n  data() {\n    return {\n      // 表单校验\n      rules: {\n        nickName: [\n          { required: true, message: \"用户昵称不能为空\", trigger: \"blur\" }\n        ],\n        email: [\n          { required: true, message: \"邮箱地址不能为空\", trigger: \"blur\" },\n          {\n            type: \"email\",\n            message: \"请输入正确的邮箱地址\",\n            trigger: [\"blur\", \"change\"]\n          }\n        ],\n        phonenumber: [\n          { required: true, message: \"手机号码不能为空\", trigger: \"blur\" },\n          {\n            pattern: /^1[3|4|5|6|7|8|9][0-9]\\d{8}$/,\n            message: \"请输入正确的手机号码\",\n            trigger: \"blur\"\n          }\n        ]\n      }\n    };\n  },\n  methods: {\n    submit() {\n      this.$refs[\"form\"].validate(valid => {\n        if (valid) {\n          updateUserProfile(this.user).then(response => {\n            this.$modal.msgSuccess(\"修改成功\");\n          });\n        }\n      });\n    },\n    close() {\n      this.$tab.closePage();\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/tool/gen/basicInfoForm.vue",
    "content": "<template>\n  <el-form ref=\"basicInfoForm\" :model=\"info\" :rules=\"rules\" label-width=\"150px\">\n    <el-row>\n      <el-col :span=\"12\">\n        <el-form-item label=\"表名称\" prop=\"tableName\">\n          <el-input placeholder=\"请输入仓库名称\" v-model=\"info.tableName\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item label=\"表描述\" prop=\"tableComment\">\n          <el-input placeholder=\"请输入\" v-model=\"info.tableComment\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item label=\"实体类名称\" prop=\"className\">\n          <el-input placeholder=\"请输入\" v-model=\"info.className\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item label=\"作者\" prop=\"functionAuthor\">\n          <el-input placeholder=\"请输入\" v-model=\"info.functionAuthor\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"24\">\n        <el-form-item label=\"备注\" prop=\"remark\">\n          <el-input type=\"textarea\" :rows=\"3\" v-model=\"info.remark\"></el-input>\n        </el-form-item>\n      </el-col>\n    </el-row>\n  </el-form>\n</template>\n\n<script>\nexport default {\n  props: {\n    info: {\n      type: Object,\n      default: null\n    }\n  },\n  data() {\n    return {\n      rules: {\n        tableName: [\n          { required: true, message: \"请输入表名称\", trigger: \"blur\" }\n        ],\n        tableComment: [\n          { required: true, message: \"请输入表描述\", trigger: \"blur\" }\n        ],\n        className: [\n          { required: true, message: \"请输入实体类名称\", trigger: \"blur\" }\n        ],\n        functionAuthor: [\n          { required: true, message: \"请输入作者\", trigger: \"blur\" }\n        ]\n      }\n    };\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/tool/gen/editTable.vue",
    "content": "<template>\n  <el-card>\n    <el-tabs v-model=\"activeName\">\n      <el-tab-pane label=\"基本信息\" name=\"basic\">\n        <basic-info-form ref=\"basicInfo\" :info=\"info\" />\n      </el-tab-pane>\n      <el-tab-pane label=\"字段信息\" name=\"columnInfo\">\n        <el-table ref=\"dragTable\" :data=\"columns\" row-key=\"columnId\" :max-height=\"tableHeight\">\n          <el-table-column label=\"序号\" type=\"index\" min-width=\"5%\" class-name=\"allowDrag\" />\n          <el-table-column\n            label=\"字段列名\"\n            prop=\"columnName\"\n            min-width=\"10%\"\n            :show-overflow-tooltip=\"true\"\n          />\n          <el-table-column label=\"字段描述\" min-width=\"10%\">\n            <template slot-scope=\"scope\">\n              <el-input v-model=\"scope.row.columnComment\"></el-input>\n            </template>\n          </el-table-column>\n          <el-table-column\n            label=\"物理类型\"\n            prop=\"columnType\"\n            min-width=\"10%\"\n            :show-overflow-tooltip=\"true\"\n          />\n          <el-table-column label=\"Java类型\" min-width=\"11%\">\n            <template slot-scope=\"scope\">\n              <el-select v-model=\"scope.row.javaType\">\n                <el-option label=\"Long\" value=\"Long\" />\n                <el-option label=\"String\" value=\"String\" />\n                <el-option label=\"Integer\" value=\"Integer\" />\n                <el-option label=\"Double\" value=\"Double\" />\n                <el-option label=\"BigDecimal\" value=\"BigDecimal\" />\n                <el-option label=\"Date\" value=\"Date\" />\n                <el-option label=\"Boolean\" value=\"Boolean\" />\n              </el-select>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"java属性\" min-width=\"10%\">\n            <template slot-scope=\"scope\">\n              <el-input v-model=\"scope.row.javaField\"></el-input>\n            </template>\n          </el-table-column>\n\n          <el-table-column label=\"插入\" min-width=\"5%\">\n            <template slot-scope=\"scope\">\n              <el-checkbox true-label=\"1\" v-model=\"scope.row.isInsert\"></el-checkbox>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"编辑\" min-width=\"5%\">\n            <template slot-scope=\"scope\">\n              <el-checkbox true-label=\"1\" v-model=\"scope.row.isEdit\"></el-checkbox>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"列表\" min-width=\"5%\">\n            <template slot-scope=\"scope\">\n              <el-checkbox true-label=\"1\" v-model=\"scope.row.isList\"></el-checkbox>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"查询\" min-width=\"5%\">\n            <template slot-scope=\"scope\">\n              <el-checkbox true-label=\"1\" v-model=\"scope.row.isQuery\"></el-checkbox>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"查询方式\" min-width=\"10%\">\n            <template slot-scope=\"scope\">\n              <el-select v-model=\"scope.row.queryType\">\n                <el-option label=\"=\" value=\"EQ\" />\n                <el-option label=\"!=\" value=\"NE\" />\n                <el-option label=\">\" value=\"GT\" />\n                <el-option label=\">=\" value=\"GTE\" />\n                <el-option label=\"<\" value=\"LT\" />\n                <el-option label=\"<=\" value=\"LTE\" />\n                <el-option label=\"LIKE\" value=\"LIKE\" />\n                <el-option label=\"BETWEEN\" value=\"BETWEEN\" />\n              </el-select>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"必填\" min-width=\"5%\">\n            <template slot-scope=\"scope\">\n              <el-checkbox true-label=\"1\" v-model=\"scope.row.isRequired\"></el-checkbox>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"显示类型\" min-width=\"12%\">\n            <template slot-scope=\"scope\">\n              <el-select v-model=\"scope.row.htmlType\">\n                <el-option label=\"文本框\" value=\"input\" />\n                <el-option label=\"文本域\" value=\"textarea\" />\n                <el-option label=\"下拉框\" value=\"select\" />\n                <el-option label=\"单选框\" value=\"radio\" />\n                <el-option label=\"复选框\" value=\"checkbox\" />\n                <el-option label=\"日期控件\" value=\"datetime\" />\n                <el-option label=\"图片上传\" value=\"imageUpload\" />\n                <el-option label=\"文件上传\" value=\"fileUpload\" />\n                <el-option label=\"富文本控件\" value=\"editor\" />\n              </el-select>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"字典类型\" min-width=\"12%\">\n            <template slot-scope=\"scope\">\n              <el-select v-model=\"scope.row.dictType\" clearable filterable placeholder=\"请选择\">\n                <el-option\n                  v-for=\"dict in dictOptions\"\n                  :key=\"dict.dictType\"\n                  :label=\"dict.dictName\"\n                  :value=\"dict.dictType\">\n                  <span style=\"float: left\">{{ dict.dictName }}</span>\n                  <span style=\"float: right; color: #8492a6; font-size: 13px\">{{ dict.dictType }}</span>\n              </el-option>\n              </el-select>\n            </template>\n          </el-table-column>\n        </el-table>\n      </el-tab-pane>\n      <el-tab-pane label=\"生成信息\" name=\"genInfo\">\n        <gen-info-form ref=\"genInfo\" :info=\"info\" :tables=\"tables\" :menus=\"menus\"/>\n      </el-tab-pane>\n    </el-tabs>\n    <el-form label-width=\"100px\">\n      <el-form-item style=\"text-align: center;margin-left:-100px;margin-top:10px;\">\n        <el-button type=\"primary\" @click=\"submitForm()\">提交</el-button>\n        <el-button @click=\"close()\">返回</el-button>\n      </el-form-item>\n    </el-form>\n  </el-card>\n</template>\n\n<script>\nimport { getGenTable, updateGenTable } from \"@/api/tool/gen\";\nimport { optionselect as getDictOptionselect } from \"@/api/system/dict/type\";\nimport { listMenu as getMenuTreeselect } from \"@/api/system/menu\";\nimport basicInfoForm from \"./basicInfoForm\";\nimport genInfoForm from \"./genInfoForm\";\nimport Sortable from 'sortablejs'\n\nexport default {\n  name: \"GenEdit\",\n  components: {\n    basicInfoForm,\n    genInfoForm\n  },\n  data() {\n    return {\n      // 选中选项卡的 name\n      activeName: \"columnInfo\",\n      // 表格的高度\n      tableHeight: document.documentElement.scrollHeight - 245 + \"px\",\n      // 表信息\n      tables: [],\n      // 表列信息\n      columns: [],\n      // 字典信息\n      dictOptions: [],\n      // 菜单信息\n      menus: [],\n      // 表详细信息\n      info: {}\n    };\n  },\n  created() {\n    const tableId = this.$route.params && this.$route.params.tableId;\n    if (tableId) {\n      // 获取表详细信息\n      getGenTable(tableId).then(res => {\n        this.columns = res.data.rows;\n        this.info = res.data.info;\n        this.tables = res.data.tables;\n      });\n      /** 查询字典下拉列表 */\n      getDictOptionselect().then(response => {\n        this.dictOptions = response.data;\n      });\n      /** 查询菜单下拉列表 */\n      getMenuTreeselect().then(response => {\n        this.menus = this.handleTree(response.data, \"menuId\");\n      });\n    }\n  },\n  methods: {\n    /** 提交按钮 */\n    submitForm() {\n      const basicForm = this.$refs.basicInfo.$refs.basicInfoForm;\n      const genForm = this.$refs.genInfo.$refs.genInfoForm;\n      Promise.all([basicForm, genForm].map(this.getFormPromise)).then(res => {\n        const validateResult = res.every(item => !!item);\n        if (validateResult) {\n          const genTable = Object.assign({}, basicForm.model, genForm.model);\n          genTable.columns = this.columns;\n          genTable.params = {\n            treeCode: genTable.treeCode,\n            treeName: genTable.treeName,\n            treeParentCode: genTable.treeParentCode,\n            parentMenuId: genTable.parentMenuId\n          };\n          updateGenTable(genTable).then(res => {\n            this.$modal.msgSuccess(res.msg);\n            if (res.code === 200) {\n              this.close();\n            }\n          });\n        } else {\n          this.$modal.msgError(\"表单校验未通过，请重新检查提交内容\");\n        }\n      });\n    },\n    getFormPromise(form) {\n      return new Promise(resolve => {\n        form.validate(res => {\n          resolve(res);\n        });\n      });\n    },\n    /** 关闭按钮 */\n    close() {\n      const obj = { path: \"/tool/gen\", query: { t: Date.now(), pageNum: this.$route.query.pageNum } };\n      this.$tab.closeOpenPage(obj);\n    }\n  },\n  mounted() {\n    const el = this.$refs.dragTable.$el.querySelectorAll(\".el-table__body-wrapper > table > tbody\")[0];\n    const sortable = Sortable.create(el, {\n      handle: \".allowDrag\",\n      onEnd: evt => {\n        const targetRow = this.columns.splice(evt.oldIndex, 1)[0];\n        this.columns.splice(evt.newIndex, 0, targetRow);\n        for (let index in this.columns) {\n          this.columns[index].sort = parseInt(index) + 1;\n        }\n      }\n    });\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/tool/gen/genInfoForm.vue",
    "content": "<template>\n  <el-form ref=\"genInfoForm\" :model=\"info\" :rules=\"rules\" label-width=\"150px\">\n    <el-row>\n      <el-col :span=\"12\">\n        <el-form-item prop=\"tplCategory\">\n          <span slot=\"label\">生成模板</span>\n          <el-select v-model=\"info.tplCategory\" @change=\"tplSelectChange\">\n            <el-option label=\"单表（增删改查）\" value=\"crud\" />\n            <el-option label=\"树表（增删改查）\" value=\"tree\" />\n            <el-option label=\"主子表（增删改查）\" value=\"sub\" />\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item prop=\"packageName\">\n          <span slot=\"label\">\n            生成包路径\n            <el-tooltip content=\"生成在哪个java包下，例如 com.ruoyi.system\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-input v-model=\"info.packageName\" />\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"12\">\n        <el-form-item prop=\"moduleName\">\n          <span slot=\"label\">\n            生成模块名\n            <el-tooltip content=\"可理解为子系统名，例如 system\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-input v-model=\"info.moduleName\" />\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"12\">\n        <el-form-item prop=\"businessName\">\n          <span slot=\"label\">\n            生成业务名\n            <el-tooltip content=\"可理解为功能英文名，例如 user\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-input v-model=\"info.businessName\" />\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"12\">\n        <el-form-item prop=\"functionName\">\n          <span slot=\"label\">\n            生成功能名\n            <el-tooltip content=\"用作类描述，例如 用户\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-input v-model=\"info.functionName\" />\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"12\">\n        <el-form-item>\n          <span slot=\"label\">\n            上级菜单\n            <el-tooltip content=\"分配到指定菜单下，例如 系统管理\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <treeselect\n            :append-to-body=\"true\"\n            v-model=\"info.parentMenuId\"\n            :options=\"menus\"\n            :normalizer=\"normalizer\"\n            :show-count=\"true\"\n            placeholder=\"请选择系统菜单\"\n          />\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"12\">\n        <el-form-item prop=\"genType\">\n          <span slot=\"label\">\n            生成代码方式\n            <el-tooltip content=\"默认为zip压缩包下载，也可以自定义生成路径\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-radio v-model=\"info.genType\" label=\"0\">zip压缩包</el-radio>\n          <el-radio v-model=\"info.genType\" label=\"1\">自定义路径</el-radio>\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"24\" v-if=\"info.genType == '1'\">\n        <el-form-item prop=\"genPath\">\n          <span slot=\"label\">\n            自定义路径\n            <el-tooltip content=\"填写磁盘绝对路径，若不填写，则生成到当前Web项目下\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-input v-model=\"info.genPath\">\n            <el-dropdown slot=\"append\">\n              <el-button type=\"primary\">\n                最近路径快速选择\n                <i class=\"el-icon-arrow-down el-icon--right\"></i>\n              </el-button>\n              <el-dropdown-menu slot=\"dropdown\">\n                <el-dropdown-item @click.native=\"info.genPath = '/'\">恢复默认的生成基础路径</el-dropdown-item>\n              </el-dropdown-menu>\n            </el-dropdown>\n          </el-input>\n        </el-form-item>\n      </el-col>\n    </el-row>\n\n    <el-row v-show=\"info.tplCategory == 'tree'\">\n      <h4 class=\"form-header\">其他信息</h4>\n      <el-col :span=\"12\">\n        <el-form-item>\n          <span slot=\"label\">\n            树编码字段\n            <el-tooltip content=\"树显示的编码字段名， 如：dept_id\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-select v-model=\"info.treeCode\" placeholder=\"请选择\">\n            <el-option\n              v-for=\"(column, index) in info.columns\"\n              :key=\"index\"\n              :label=\"column.columnName + '：' + column.columnComment\"\n              :value=\"column.columnName\"\n            ></el-option>\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item>\n          <span slot=\"label\">\n            树父编码字段\n            <el-tooltip content=\"树显示的父编码字段名， 如：parent_Id\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-select v-model=\"info.treeParentCode\" placeholder=\"请选择\">\n            <el-option\n              v-for=\"(column, index) in info.columns\"\n              :key=\"index\"\n              :label=\"column.columnName + '：' + column.columnComment\"\n              :value=\"column.columnName\"\n            ></el-option>\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item>\n          <span slot=\"label\">\n            树名称字段\n            <el-tooltip content=\"树节点的显示名称字段名， 如：dept_name\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-select v-model=\"info.treeName\" placeholder=\"请选择\">\n            <el-option\n              v-for=\"(column, index) in info.columns\"\n              :key=\"index\"\n              :label=\"column.columnName + '：' + column.columnComment\"\n              :value=\"column.columnName\"\n            ></el-option>\n          </el-select>\n        </el-form-item>\n      </el-col>\n    </el-row>\n    <el-row v-show=\"info.tplCategory == 'sub'\">\n      <h4 class=\"form-header\">关联信息</h4>\n      <el-col :span=\"12\">\n        <el-form-item>\n          <span slot=\"label\">\n            关联子表的表名\n            <el-tooltip content=\"关联子表的表名， 如：sys_user\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-select v-model=\"info.subTableName\" placeholder=\"请选择\" @change=\"subSelectChange\">\n            <el-option\n              v-for=\"(table, index) in tables\"\n              :key=\"index\"\n              :label=\"table.tableName + '：' + table.tableComment\"\n              :value=\"table.tableName\"\n            ></el-option>\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item>\n          <span slot=\"label\">\n            子表关联的外键名\n            <el-tooltip content=\"子表关联的外键名， 如：user_id\" placement=\"top\">\n              <i class=\"el-icon-question\"></i>\n            </el-tooltip>\n          </span>\n          <el-select v-model=\"info.subTableFkName\" placeholder=\"请选择\">\n            <el-option\n              v-for=\"(column, index) in subColumns\"\n              :key=\"index\"\n              :label=\"column.columnName + '：' + column.columnComment\"\n              :value=\"column.columnName\"\n            ></el-option>\n          </el-select>\n        </el-form-item>\n      </el-col>\n    </el-row>\n  </el-form>\n</template>\n\n<script>\nimport Treeselect from \"@riophae/vue-treeselect\";\nimport \"@riophae/vue-treeselect/dist/vue-treeselect.css\";\n\nexport default {\n  components: { Treeselect },\n  props: {\n    info: {\n      type: Object,\n      default: null\n    },\n    tables: {\n      type: Array,\n      default: null\n    },\n    menus: {\n      type: Array,\n      default: []\n    },\n  },\n  data() {\n    return {\n      subColumns: [],\n      rules: {\n        tplCategory: [\n          { required: true, message: \"请选择生成模板\", trigger: \"blur\" }\n        ],\n        packageName: [\n          { required: true, message: \"请输入生成包路径\", trigger: \"blur\" }\n        ],\n        moduleName: [\n          { required: true, message: \"请输入生成模块名\", trigger: \"blur\" }\n        ],\n        businessName: [\n          { required: true, message: \"请输入生成业务名\", trigger: \"blur\" }\n        ],\n        functionName: [\n          { required: true, message: \"请输入生成功能名\", trigger: \"blur\" }\n        ],\n      }\n    };\n  },\n  created() {},\n  watch: {\n    'info.subTableName': function(val) {\n      this.setSubTableColumns(val);\n    }\n  },\n  methods: {\n    /** 转换菜单数据结构 */\n    normalizer(node) {\n      if (node.children && !node.children.length) {\n        delete node.children;\n      }\n      return {\n        id: node.menuId,\n        label: node.menuName,\n        children: node.children\n      };\n    },\n    /** 选择子表名触发 */\n    subSelectChange(value) {\n      this.info.subTableFkName = '';\n    },\n    /** 选择生成模板触发 */\n    tplSelectChange(value) {\n      if(value !== 'sub') {\n        this.info.subTableName = '';\n        this.info.subTableFkName = '';\n      }\n    },\n    /** 设置关联外键 */\n    setSubTableColumns(value) {\n      for (var item in this.tables) {\n        const name = this.tables[item].tableName;\n        if (value === name) {\n          this.subColumns = this.tables[item].columns;\n          break;\n        }\n      }\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/tool/gen/importTable.vue",
    "content": "<template>\n  <!-- 导入表 -->\n  <el-dialog title=\"导入表\" :visible.sync=\"visible\" width=\"800px\" top=\"5vh\" append-to-body>\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\">\n      <el-form-item label=\"表名称\" prop=\"tableName\">\n        <el-input\n          v-model=\"queryParams.tableName\"\n          placeholder=\"请输入表名称\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"表描述\" prop=\"tableComment\">\n        <el-input\n          v-model=\"queryParams.tableComment\"\n          placeholder=\"请输入表描述\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n    <el-row>\n      <el-table @row-click=\"clickRow\" ref=\"table\" :data=\"dbTableList\" @selection-change=\"handleSelectionChange\" height=\"260px\">\n        <el-table-column type=\"selection\" width=\"55\"></el-table-column>\n        <el-table-column prop=\"tableName\" label=\"表名称\" :show-overflow-tooltip=\"true\"></el-table-column>\n        <el-table-column prop=\"tableComment\" label=\"表描述\" :show-overflow-tooltip=\"true\"></el-table-column>\n        <el-table-column prop=\"createTime\" label=\"创建时间\"></el-table-column>\n        <el-table-column prop=\"updateTime\" label=\"更新时间\"></el-table-column>\n      </el-table>\n      <pagination\n        v-show=\"total>0\"\n        :total=\"total\"\n        :page.sync=\"queryParams.pageNum\"\n        :limit.sync=\"queryParams.pageSize\"\n        @pagination=\"getList\"\n      />\n    </el-row>\n    <div slot=\"footer\" class=\"dialog-footer\">\n      <el-button type=\"primary\" @click=\"handleImportTable\">确 定</el-button>\n      <el-button @click=\"visible = false\">取 消</el-button>\n    </div>\n  </el-dialog>\n</template>\n\n<script>\nimport { listDbTable, importTable } from \"@/api/tool/gen\";\nexport default {\n  data() {\n    return {\n      // 遮罩层\n      visible: false,\n      // 选中数组值\n      tables: [],\n      // 总条数\n      total: 0,\n      // 表数据\n      dbTableList: [],\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        tableName: undefined,\n        tableComment: undefined\n      }\n    };\n  },\n  methods: {\n    // 显示弹框\n    show() {\n      this.getList();\n      this.visible = true;\n    },\n    clickRow(row) {\n      this.$refs.table.toggleRowSelection(row);\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.tables = selection.map(item => item.tableName);\n    },\n    // 查询表数据\n    getList() {\n      listDbTable(this.queryParams).then(res => {\n        if (res.code === 200) {\n          this.dbTableList = res.rows;\n          this.total = res.total;\n        }\n      });\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    /** 导入按钮操作 */\n    handleImportTable() {\n      const tableNames = this.tables.join(\",\");\n      if (tableNames == \"\") {\n        this.$modal.msgError(\"请选择要导入的表\");\n        return;\n      }\n      importTable({ tables: tableNames }).then(res => {\n        this.$modal.msgSuccess(res.msg);\n        if (res.code === 200) {\n          this.visible = false;\n          this.$emit(\"ok\");\n        }\n      });\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/tool/gen/index.vue",
    "content": "<template>\n  <div class=\"app-container\">\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n      <el-form-item label=\"表名称\" prop=\"tableName\">\n        <el-input\n          v-model=\"queryParams.tableName\"\n          placeholder=\"请输入表名称\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"表描述\" prop=\"tableComment\">\n        <el-input\n          v-model=\"queryParams.tableComment\"\n          placeholder=\"请输入表描述\"\n          clearable\n          @keyup.enter.native=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"创建时间\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          style=\"width: 240px\"\n          value-format=\"yyyy-MM-dd\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n        ></el-date-picker>\n      </el-form-item>\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" size=\"mini\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" size=\"mini\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"primary\"\n          plain\n          icon=\"el-icon-download\"\n          size=\"mini\"\n          @click=\"handleGenTable\"\n          v-hasPermi=\"['tool:gen:code']\"\n        >生成</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"info\"\n          plain\n          icon=\"el-icon-upload\"\n          size=\"mini\"\n          @click=\"openImportTable\"\n          v-hasPermi=\"['tool:gen:import']\"\n        >导入</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"success\"\n          plain\n          icon=\"el-icon-edit\"\n          size=\"mini\"\n          :disabled=\"single\"\n          @click=\"handleEditTable\"\n          v-hasPermi=\"['tool:gen:edit']\"\n        >修改</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button\n          type=\"danger\"\n          plain\n          icon=\"el-icon-delete\"\n          size=\"mini\"\n          :disabled=\"multiple\"\n          @click=\"handleDelete\"\n          v-hasPermi=\"['tool:gen:remove']\"\n        >删除</el-button>\n      </el-col>\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n    <el-table v-loading=\"loading\" :data=\"tableList\" @selection-change=\"handleSelectionChange\">\n      <el-table-column type=\"selection\" align=\"center\" width=\"55\"></el-table-column>\n      <el-table-column label=\"序号\" type=\"index\" width=\"50\" align=\"center\">\n        <template slot-scope=\"scope\">\n          <span>{{(queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1}}</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"表名称\"\n        align=\"center\"\n        prop=\"tableName\"\n        :show-overflow-tooltip=\"true\"\n        width=\"120\"\n      />\n      <el-table-column\n        label=\"表描述\"\n        align=\"center\"\n        prop=\"tableComment\"\n        :show-overflow-tooltip=\"true\"\n        width=\"120\"\n      />\n      <el-table-column\n        label=\"实体\"\n        align=\"center\"\n        prop=\"className\"\n        :show-overflow-tooltip=\"true\"\n        width=\"120\"\n      />\n      <el-table-column label=\"创建时间\" align=\"center\" prop=\"createTime\" width=\"160\" />\n      <el-table-column label=\"更新时间\" align=\"center\" prop=\"updateTime\" width=\"160\" />\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template slot-scope=\"scope\">\n          <el-button\n            type=\"text\"\n            size=\"small\"\n            icon=\"el-icon-view\"\n            @click=\"handlePreview(scope.row)\"\n            v-hasPermi=\"['tool:gen:preview']\"\n          >预览</el-button>\n          <el-button\n            type=\"text\"\n            size=\"small\"\n            icon=\"el-icon-edit\"\n            @click=\"handleEditTable(scope.row)\"\n            v-hasPermi=\"['tool:gen:edit']\"\n          >编辑</el-button>\n          <el-button\n            type=\"text\"\n            size=\"small\"\n            icon=\"el-icon-delete\"\n            @click=\"handleDelete(scope.row)\"\n            v-hasPermi=\"['tool:gen:remove']\"\n          >删除</el-button>\n          <el-button\n            type=\"text\"\n            size=\"small\"\n            icon=\"el-icon-refresh\"\n            @click=\"handleSynchDb(scope.row)\"\n            v-hasPermi=\"['tool:gen:edit']\"\n          >同步</el-button>\n          <el-button\n            type=\"text\"\n            size=\"small\"\n            icon=\"el-icon-download\"\n            @click=\"handleGenTable(scope.row)\"\n            v-hasPermi=\"['tool:gen:code']\"\n          >生成代码</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <pagination\n      v-show=\"total>0\"\n      :total=\"total\"\n      :page.sync=\"queryParams.pageNum\"\n      :limit.sync=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n    <!-- 预览界面 -->\n    <el-dialog :title=\"preview.title\" :visible.sync=\"preview.open\" width=\"80%\" top=\"5vh\" append-to-body class=\"scrollbar\">\n      <el-tabs v-model=\"preview.activeName\">\n        <el-tab-pane\n          v-for=\"(value, key) in preview.data\"\n          :label=\"key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))\"\n          :name=\"key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))\"\n          :key=\"key\"\n        >\n          <el-link :underline=\"false\" icon=\"el-icon-document-copy\" v-clipboard:copy=\"value\" v-clipboard:success=\"clipboardSuccess\" style=\"float:right\">复制</el-link>\n          <pre><code class=\"hljs\" v-html=\"highlightedCode(value, key)\"></code></pre>\n        </el-tab-pane>\n      </el-tabs>\n    </el-dialog>\n    <import-table ref=\"import\" @ok=\"handleQuery\" />\n  </div>\n</template>\n\n<script>\nimport { listTable, previewTable, delTable, genCode, synchDb } from \"@/api/tool/gen\";\nimport importTable from \"./importTable\";\nimport hljs from \"highlight.js/lib/highlight\";\nimport \"highlight.js/styles/github-gist.css\";\nhljs.registerLanguage(\"java\", require(\"highlight.js/lib/languages/java\"));\nhljs.registerLanguage(\"xml\", require(\"highlight.js/lib/languages/xml\"));\nhljs.registerLanguage(\"html\", require(\"highlight.js/lib/languages/xml\"));\nhljs.registerLanguage(\"vue\", require(\"highlight.js/lib/languages/xml\"));\nhljs.registerLanguage(\"javascript\", require(\"highlight.js/lib/languages/javascript\"));\nhljs.registerLanguage(\"sql\", require(\"highlight.js/lib/languages/sql\"));\n\nexport default {\n  name: \"Gen\",\n  components: { importTable },\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 唯一标识符\n      uniqueId: \"\",\n      // 选中数组\n      ids: [],\n      // 选中表数组\n      tableNames: [],\n      // 非单个禁用\n      single: true,\n      // 非多个禁用\n      multiple: true,\n      // 显示搜索条件\n      showSearch: true,\n      // 总条数\n      total: 0,\n      // 表数据\n      tableList: [],\n      // 日期范围\n      dateRange: \"\",\n      // 查询参数\n      queryParams: {\n        pageNum: 1,\n        pageSize: 10,\n        tableName: undefined,\n        tableComment: undefined\n      },\n      // 预览参数\n      preview: {\n        open: false,\n        title: \"代码预览\",\n        data: {},\n        activeName: \"domain.java\"\n      }\n    };\n  },\n  created() {\n    this.getList();\n  },\n  activated() {\n    const time = this.$route.query.t;\n    if (time != null && time != this.uniqueId) {\n      this.uniqueId = time;\n      this.queryParams.pageNum = Number(this.$route.query.pageNum);\n      this.getList();\n    }\n  },\n  methods: {\n    /** 查询表集合 */\n    getList() {\n      this.loading = true;\n      listTable(this.addDateRange(this.queryParams, this.dateRange)).then(response => {\n          this.tableList = response.rows;\n          this.total = response.total;\n          this.loading = false;\n        }\n      );\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNum = 1;\n      this.getList();\n    },\n    /** 生成代码操作 */\n    handleGenTable(row) {\n      const tableNames = row.tableName || this.tableNames;\n      if (tableNames == \"\") {\n        this.$modal.msgError(\"请选择要生成的数据\");\n        return;\n      }\n      if(row.genType === \"1\") {\n        genCode(row.tableName).then(response => {\n          this.$modal.msgSuccess(\"成功生成到自定义路径：\" + row.genPath);\n        });\n      } else {\n        this.$download.zip(\"/tool/gen/batchGenCode?tables=\" + tableNames, \"ruoyi\");\n      }\n    },\n    /** 同步数据库操作 */\n    handleSynchDb(row) {\n      const tableName = row.tableName;\n      this.$modal.confirm('确认要强制同步\"' + tableName + '\"表结构吗？').then(function() {\n        return synchDb(tableName);\n      }).then(() => {\n        this.$modal.msgSuccess(\"同步成功\");\n      }).catch(() => {});\n    },\n    /** 打开导入表弹窗 */\n    openImportTable() {\n      this.$refs.import.show();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.dateRange = [];\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    /** 预览按钮 */\n    handlePreview(row) {\n      previewTable(row.tableId).then(response => {\n        this.preview.data = response.data;\n        this.preview.open = true;\n        this.preview.activeName = \"domain.java\";\n      });\n    },\n    /** 高亮显示 */\n    highlightedCode(code, key) {\n      const vmName = key.substring(key.lastIndexOf(\"/\") + 1, key.indexOf(\".vm\"));\n      var language = vmName.substring(vmName.indexOf(\".\") + 1, vmName.length);\n      const result = hljs.highlight(language, code || \"\", true);\n      return result.value || '&nbsp;';\n    },\n    /** 复制代码成功 */\n    clipboardSuccess() {\n      this.$modal.msgSuccess(\"复制成功\");\n    },\n    // 多选框选中数据\n    handleSelectionChange(selection) {\n      this.ids = selection.map(item => item.tableId);\n      this.tableNames = selection.map(item => item.tableName);\n      this.single = selection.length != 1;\n      this.multiple = !selection.length;\n    },\n    /** 修改按钮操作 */\n    handleEditTable(row) {\n      const tableId = row.tableId || this.ids[0];\n      const tableName = row.tableName || this.tableNames[0];\n      const params = { pageNum: this.queryParams.pageNum };\n      this.$tab.openPage(\"修改[\" + tableName + \"]生成配置\", '/tool/gen-edit/index/' + tableId, params);\n    },\n    /** 删除按钮操作 */\n    handleDelete(row) {\n      const tableIds = row.tableId || this.ids;\n      this.$modal.confirm('是否确认删除表编号为\"' + tableIds + '\"的数据项？').then(function() {\n        return delTable(tableIds);\n      }).then(() => {\n        this.getList();\n        this.$modal.msgSuccess(\"删除成功\");\n      }).catch(() => {});\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/src/views/tool/swagger/index.vue",
    "content": "<template>\n  <i-frame :src=\"url\" />\n</template>\n<script>\nimport iFrame from \"@/components/iFrame/index\";\nexport default {\n  name: \"Swagger\",\n  components: { iFrame },\n  data() {\n    return {\n      url: process.env.VUE_APP_BASE_API + \"/swagger-ui/index.html\"\n    };\n  },\n};\n</script>\n"
  },
  {
    "path": "vue_campus_admin/vue.config.js",
    "content": "'use strict'\nconst path = require('path')\n\nfunction resolve(dir) {\n  return path.join(__dirname, dir)\n}\n\nconst CompressionPlugin = require('compression-webpack-plugin')\n\nconst name = process.env.VUE_APP_TITLE || 'campus' // 网页标题\n\nconst port = process.env.port || process.env.npm_config_port || 80 // 端口\n\n// vue.config.js 配置说明\n//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions\n// 这里只列一部分，具体配置参考文档\nmodule.exports = {\n  // 部署生产环境和开发环境下的URL。\n  // 默认情况下，Vue CLI 会假设你的应用是被部署在一个域名的根路径上\n  // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上，你就需要用这个选项指定这个子路径。例如，如果你的应用被部署在 https://www.ruoyi.vip/admin/，则设置 baseUrl 为 /admin/。\n  publicPath: process.env.NODE_ENV === \"production\" ? \"/\" : \"/\",\n  // 在npm run build 或 yarn build 时 ，生成文件的目录名称（要和baseUrl的生产环境路径一致）（默认dist）\n  outputDir: 'dist',\n  // 用于放置生成的静态资源 (js、css、img、fonts) 的；（项目打包之后，静态资源会放在这个文件夹下）\n  assetsDir: 'static',\n  // 是否开启eslint保存检测，有效值：ture | false | 'error'\n  lintOnSave: process.env.NODE_ENV === 'development',\n  // 如果你不需要生产环境的 source map，可以将其设置为 false 以加速生产环境构建。\n  productionSourceMap: false,\n  // webpack-dev-server 相关配置\n  devServer: {\n    host: '0.0.0.0',\n    port: port,\n    open: true,\n    proxy: {\n      // detail: https://cli.vuejs.org/config/#devserver-proxy\n      [process.env.VUE_APP_BASE_API]: {\n        target: `http://localhost:8160`,\n        changeOrigin: true,\n        pathRewrite: {\n          ['^' + process.env.VUE_APP_BASE_API]: ''\n        }\n      }\n    },\n    disableHostCheck: true\n  },\n  css: {\n    loaderOptions: {\n      sass: {\n        sassOptions: { outputStyle: \"expanded\" }\n      }\n    }\n  },\n  configureWebpack: {\n    name: name,\n    resolve: {\n      alias: {\n        '@': resolve('src')\n      }\n    },\n    plugins: [\n      // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件\n      new CompressionPlugin({\n        cache: false,                   // 不启用文件缓存\n        test: /\\.(js|css|html)?$/i,     // 压缩文件格式\n        filename: '[path].gz[query]',   // 压缩后的文件名\n        algorithm: 'gzip',              // 使用gzip压缩\n        minRatio: 0.8                   // 压缩率小于1才会压缩\n      })\n    ],\n  },\n  chainWebpack(config) {\n    config.plugins.delete('preload') // TODO: need test\n    config.plugins.delete('prefetch') // TODO: need test\n\n    // set svg-sprite-loader\n    config.module\n      .rule('svg')\n      .exclude.add(resolve('src/assets/icons'))\n      .end()\n    config.module\n      .rule('icons')\n      .test(/\\.svg$/)\n      .include.add(resolve('src/assets/icons'))\n      .end()\n      .use('svg-sprite-loader')\n      .loader('svg-sprite-loader')\n      .options({\n        symbolId: 'icon-[name]'\n      })\n      .end()\n\n    config\n      .when(process.env.NODE_ENV !== 'development',\n        config => {\n          config\n            .plugin('ScriptExtHtmlWebpackPlugin')\n            .after('html')\n            .use('script-ext-html-webpack-plugin', [{\n            // `runtime` must same as runtimeChunk name. default is `runtime`\n              inline: /runtime\\..*\\.js$/\n            }])\n            .end()\n          config\n            .optimization.splitChunks({\n              chunks: 'all',\n              cacheGroups: {\n                libs: {\n                  name: 'chunk-libs',\n                  test: /[\\\\/]node_modules[\\\\/]/,\n                  priority: 10,\n                  chunks: 'initial' // only package third parties that are initially dependent\n                },\n                elementUI: {\n                  name: 'chunk-elementUI', // split elementUI into a single package\n                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app\n                  test: /[\\\\/]node_modules[\\\\/]_?element-ui(.*)/ // in order to adapt to cnpm\n                },\n                commons: {\n                  name: 'chunk-commons',\n                  test: resolve('src/components'), // can customize your rules\n                  minChunks: 3, //  minimum common number\n                  priority: 5,\n                  reuseExistingChunk: true\n                }\n              }\n            })\n          config.optimization.runtimeChunk('single'),\n          {\n             from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件\n             to: './' //到根目录下\n          }\n        }\n      )\n  }\n}\n"
  }
]