├── .gitignore ├── LICENSE ├── README.md ├── demo ├── 1.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png └── 9.png ├── postgreSQL ├── Internalization.sql ├── Log.sql ├── Menu.sql ├── Message.sql ├── MessageRead.sql ├── Organization.sql ├── Permission.sql ├── Post.sql ├── Role.sql ├── User.sql └── public.sql ├── server ├── .env ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package.json ├── pnpm-lock.yaml ├── prisma │ ├── migrations │ │ ├── 20241125094642_init │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma ├── src │ ├── app.module.ts │ ├── config │ │ └── winston.config.ts │ ├── dto │ │ ├── params.dto.ts │ │ └── response.dto.ts │ ├── enums │ │ └── index.ts │ ├── filter │ │ ├── all-exception.filter.ts │ │ └── http-exception.filter.ts │ ├── interceptor │ │ └── logger.interceptor.ts │ ├── main.ts │ ├── middleware │ │ ├── logger.middleware.ts │ │ └── request.middleware.ts │ ├── modules │ │ ├── administrative │ │ │ ├── message │ │ │ │ ├── dto │ │ │ │ │ ├── params-message.dto.ts │ │ │ │ │ ├── response-message.dto.ts │ │ │ │ │ └── save-message.dto.ts │ │ │ │ ├── message.controller.ts │ │ │ │ ├── message.module.ts │ │ │ │ └── message.service.ts │ │ │ ├── organazation │ │ │ │ ├── dto │ │ │ │ │ ├── params-organazation.dto.ts │ │ │ │ │ ├── response-organazation.dto.ts │ │ │ │ │ └── save-organazation.dto.ts │ │ │ │ ├── organazation.controller.ts │ │ │ │ ├── organazation.module.ts │ │ │ │ └── organazation.service.ts │ │ │ └── post-manage │ │ │ │ ├── dto │ │ │ │ ├── params-post.dto.ts │ │ │ │ ├── response-post.dto.ts │ │ │ │ └── save-post.dto.ts │ │ │ │ ├── post-manage.controller.ts │ │ │ │ ├── post-manage.module.ts │ │ │ │ └── post-manage.service.ts │ │ ├── auth │ │ │ ├── auth.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto │ │ │ │ ├── params-auth.dto.ts │ │ │ │ └── response-auth.dto.ts │ │ │ └── jwt.strategy.ts │ │ ├── file-upload │ │ │ ├── dto │ │ │ │ └── index.ts │ │ │ ├── file-upload.controller.ts │ │ │ ├── file-upload.module.ts │ │ │ └── file-upload.service.ts │ │ ├── prisma │ │ │ ├── prisma.module.ts │ │ │ └── prisma.service.ts │ │ └── system-manage │ │ │ ├── internalization │ │ │ ├── dto │ │ │ │ ├── params-internalization.dto.ts │ │ │ │ ├── response-internalization.dto.ts │ │ │ │ └── save-internalization.dto.ts │ │ │ ├── internalization.controller.ts │ │ │ ├── internalization.module.ts │ │ │ └── internalization.service.ts │ │ │ ├── menu-manage │ │ │ ├── dto │ │ │ │ ├── params-menu.dto.ts │ │ │ │ ├── response-menu.dto.ts │ │ │ │ └── save-menu.dto.ts │ │ │ ├── menu-manage.controller.ts │ │ │ ├── menu-manage.module.ts │ │ │ └── menu-manage.service.ts │ │ │ ├── operation-log │ │ │ ├── dto │ │ │ │ ├── params-log.dto.ts │ │ │ │ └── response-log.dto.ts │ │ │ ├── operation-log.controller.ts │ │ │ ├── operation-log.module.ts │ │ │ └── operation-log.service.ts │ │ │ ├── role-manage │ │ │ ├── dto │ │ │ │ ├── params-role.dto.ts │ │ │ │ ├── response-role.dto.ts │ │ │ │ └── save-role.dto.ts │ │ │ ├── role-manage.controller.ts │ │ │ ├── role-manage.module.ts │ │ │ └── role.manage.service.ts │ │ │ └── user-manage │ │ │ ├── dto │ │ │ ├── params-user.dto.ts │ │ │ ├── response-user.dto.ts │ │ │ └── save-user.dto.ts │ │ │ ├── user-manage.controller.ts │ │ │ ├── user-manage.module.ts │ │ │ └── user-manage.service.ts │ ├── pipe │ │ └── validation.pipe.ts │ ├── typings │ │ └── index.d.ts │ └── utils │ │ └── index.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json ├── upload │ └── image │ │ ├── 2024-07 │ │ ├── 6f6e22a3-1e6a-4aa3-bbe9-6b2b0e6ea5aa.jpeg │ │ └── cc9e77ee-cf84-48e8-a9d0-dc3e9d21224c.jpeg │ │ ├── 2024-08 │ │ └── f92269b2-77b7-48ef-83b9-e8bd4970c304.jpeg │ │ └── 2024-09 │ │ ├── 2126ab07-c104-4fe3-a6c2-1c1d72223614.jpeg │ │ ├── 72840555-ee20-4679-a54d-4b39c622567d.jpeg │ │ ├── a370df3c-4d3d-4e2d-81ac-25790e2a1789.jpeg │ │ ├── ac7b73c5-3c4e-41c4-94a8-21393e5682e4.jpeg │ │ └── fcdab14e-29d4-43e3-8630-9cf064646f35.jpeg └── webpack-hmr.config.js └── web ├── .browserslistrc ├── .env ├── .env.development ├── .env.preview ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .lintstagedrc.json ├── .prettierrc ├── .stylelintrc.js ├── .travis.yml ├── LICENSE ├── README.md ├── README.zh-CN.md ├── babel.config.js ├── commitlint.config.js ├── config ├── plugin.config.js └── themePluginConfig.js ├── jsconfig.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── administrative │ │ ├── message.js │ │ ├── organization.js │ │ └── post.js │ ├── auth │ │ └── index.js │ └── system-manage │ │ ├── internationalization.js │ │ ├── menu-manage.js │ │ ├── operation-log.js │ │ ├── role-manage.js │ │ └── user-manage.js ├── assets │ ├── icons │ │ ├── about.svg │ │ ├── administrative.svg │ │ ├── antd.svg │ │ ├── captcha.svg │ │ ├── colorthief.svg │ │ ├── dashboard.svg │ │ ├── directive.svg │ │ ├── eye-dropper.svg │ │ ├── features.svg │ │ ├── internationalization.svg │ │ ├── lazyload.svg │ │ ├── locale.svg │ │ ├── log.svg │ │ ├── nestjs.svg │ │ ├── organization.svg │ │ ├── pickr.svg │ │ ├── post.svg │ │ ├── swiper.svg │ │ ├── viewer.svg │ │ ├── vue.svg │ │ └── waterfall.svg │ ├── img │ │ ├── 1.jpg │ │ ├── 10.jpg │ │ ├── 11.jpg │ │ ├── 12.jpg │ │ ├── 13.jpg │ │ ├── 14.jpg │ │ ├── 15.jpg │ │ ├── 16.jpg │ │ ├── 17.jpg │ │ ├── 18.jpg │ │ ├── 19.jpg │ │ ├── 2.jpg │ │ ├── 20.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ ├── 8.jpg │ │ └── 9.jpg │ ├── logo.svg │ ├── office │ │ ├── test.docx │ │ ├── test.pdf │ │ └── test.xlsx │ └── reset.png ├── components │ ├── AvatarList │ │ ├── Item.jsx │ │ ├── List.jsx │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── Charts │ │ ├── ChartCard.vue │ │ ├── MiniArea.vue │ │ ├── MiniBar.vue │ │ ├── MiniProgress.vue │ │ └── chart.less │ ├── ColorPicker │ │ └── index.vue │ ├── Dialog.js │ ├── Editor │ │ └── QuillEditor.vue │ ├── GlobalFooter │ │ └── index.vue │ ├── GlobalHeader │ │ ├── AvatarDropdown.vue │ │ ├── DetailModal.vue │ │ ├── FullScreen.vue │ │ ├── MessageButton.vue │ │ └── RightContent.vue │ ├── IconSelector │ │ ├── README.md │ │ ├── icons.js │ │ └── index.vue │ ├── MultiTab │ │ ├── MultiTab.vue │ │ ├── events.js │ │ ├── index.js │ │ └── index.less │ ├── NProgress │ │ └── nprogress.less │ ├── PageLoading │ │ └── index.jsx │ ├── SelectLang │ │ ├── index.jsx │ │ └── index.less │ ├── Table │ │ ├── README.md │ │ └── index.js │ ├── Trend │ │ ├── Trend.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── UserTags │ │ └── index.vue │ ├── index.js │ └── index.less ├── config │ ├── defaultSettings.js │ └── router.config.js ├── constant │ ├── action.js │ ├── i18n.js │ └── index.js ├── core │ ├── bootstrap.js │ ├── directives │ │ ├── action.js │ │ ├── copy.js │ │ ├── debounce.js │ │ ├── draggable.js │ │ ├── emoji.js │ │ ├── index.js │ │ ├── lazyLoad.js │ │ ├── longpress.js │ │ ├── permission.js │ │ ├── throllte.js │ │ ├── waterMarker.js │ │ └── wave.js │ ├── icons.js │ ├── lazy_use.js │ └── use.js ├── global.less ├── layouts │ ├── BasicLayout.less │ ├── BasicLayout.vue │ ├── BlankLayout.vue │ ├── PageView.vue │ ├── RouteView.vue │ ├── UserLayout.vue │ └── index.js ├── locales │ └── index.js ├── main.js ├── permission.js ├── router │ ├── README.md │ ├── generator-routers.js │ └── index.js ├── store │ ├── app-mixin.js │ ├── device-mixin.js │ ├── getters.js │ ├── i18n-mixin.js │ ├── index.js │ ├── modules │ │ ├── app.js │ │ ├── async-router.js │ │ ├── static-router.js │ │ └── user.js │ └── mutation-types.js ├── utils │ ├── axios.js │ ├── bus.js │ ├── domUtil.js │ ├── index.js │ └── request.js └── views │ ├── 404.vue │ ├── about │ └── index.vue │ ├── administrative │ ├── framework │ │ └── index.vue │ ├── message │ │ ├── components │ │ │ ├── FormModal.vue │ │ │ ├── HeaderSearch.vue │ │ │ └── TableList.vue │ │ └── index.vue │ ├── organization │ │ ├── components │ │ │ ├── FormDrawer.vue │ │ │ ├── HeaderSearch.vue │ │ │ └── TableList.vue │ │ └── index.vue │ └── post-manage │ │ ├── components │ │ ├── FormDrawer.vue │ │ ├── HeaderSearch.vue │ │ └── TableList.vue │ │ └── index.vue │ ├── dashboard │ ├── components │ │ ├── BlogLog.vue │ │ ├── ExtraContent.vue │ │ ├── HeaderContent.vue │ │ ├── OperationEffect.vue │ │ ├── PageView.vue │ │ ├── PaymentNumber.vue │ │ ├── ProjectNews.vue │ │ └── SaleCard.vue │ └── index.vue │ ├── exception │ ├── 403.vue │ ├── 404.vue │ └── 500.vue │ ├── features │ ├── captcha │ │ ├── components │ │ │ ├── drag-captcha.vue │ │ │ ├── genera-captcha.vue │ │ │ ├── operation-captcha.vue │ │ │ ├── points-captcha.vue │ │ │ ├── puzzle-captcha.vue │ │ │ └── rotate-captcha.vue │ │ └── index.vue │ ├── colorthief │ │ └── index.vue │ ├── draggable │ │ └── index.vue │ ├── eye-dropper │ │ └── index.vue │ ├── lazyload │ │ └── index.vue │ ├── pickr │ │ └── index.vue │ ├── print │ │ └── index.vue │ ├── swiper │ │ ├── components │ │ │ ├── coverflow-swiper.vue │ │ │ ├── cube-swiper.vue │ │ │ ├── fade-swiper.vue │ │ │ ├── flip-swiper.vue │ │ │ ├── thumbnail-swiper.vue │ │ │ └── visual-swiper.vue │ │ └── index.vue │ ├── viewer │ │ └── index.vue │ ├── vue-directive │ │ └── index.vue │ ├── vue-office │ │ ├── components │ │ │ ├── vue-office-docx.vue │ │ │ ├── vue-office-excel.vue │ │ │ └── vue-office-pdf.vue │ │ └── index.vue │ └── waterfall │ │ └── index.vue │ ├── system-manage │ ├── internationalization │ │ ├── components │ │ │ ├── FormModal.vue │ │ │ ├── HeaderSearch.vue │ │ │ └── TableList.vue │ │ └── index.vue │ ├── menu-manage │ │ ├── components │ │ │ ├── FormDrawer.vue │ │ │ ├── HeaderSearch.vue │ │ │ └── TableList.vue │ │ └── index.vue │ ├── operation-log │ │ ├── components │ │ │ ├── HeaderSearch.vue │ │ │ └── TableList.vue │ │ └── index.vue │ ├── role-manage │ │ ├── components │ │ │ ├── FormDrawer.vue │ │ │ ├── HeaderSearch.vue │ │ │ └── TableList.vue │ │ └── index.vue │ └── user-manage │ │ ├── components │ │ ├── AvatarModal.vue │ │ ├── FormDrawer.vue │ │ ├── HeaderSearch.vue │ │ ├── SettingAvatar.vue │ │ └── TableList.vue │ │ └── index.vue │ ├── user-center │ ├── components │ │ ├── BasicSetting.vue │ │ ├── ChangePassword.vue │ │ ├── PersonInfo.vue │ │ ├── PersonSetting.vue │ │ ├── SecuritySetting.vue │ │ └── constant.js │ └── index.vue │ └── user │ └── Login.vue ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | /*.rar 3 | /*.sql 4 | /web/*.rar 5 | /server/*.rar 6 | -------------------------------------------------------------------------------- /demo/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/1.png -------------------------------------------------------------------------------- /demo/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/10.png -------------------------------------------------------------------------------- /demo/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/11.png -------------------------------------------------------------------------------- /demo/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/12.png -------------------------------------------------------------------------------- /demo/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/13.png -------------------------------------------------------------------------------- /demo/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/14.png -------------------------------------------------------------------------------- /demo/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/2.png -------------------------------------------------------------------------------- /demo/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/3.png -------------------------------------------------------------------------------- /demo/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/4.png -------------------------------------------------------------------------------- /demo/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/5.png -------------------------------------------------------------------------------- /demo/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/6.png -------------------------------------------------------------------------------- /demo/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/7.png -------------------------------------------------------------------------------- /demo/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/8.png -------------------------------------------------------------------------------- /demo/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/demo/9.png -------------------------------------------------------------------------------- /server/.env: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | DATABASE_URL="postgresql://postgres:123456@localhost:5432/vue2-admin?schema=public" 8 | 9 | # JWT 密钥 10 | JWT_SECRET = "baiwumm" 11 | 12 | # 生产环境域名 13 | DOMAIN_NAME = "https://vue2.baiwumm.com" 14 | 15 | # 开发环境域名 16 | DEV_DOMAIN_NAME = "http://localhost:3000" 17 | 18 | # ------- 高德 key 用户显示操作日志里面的 IP 所在地 --------------------- 19 | GAODE_MAP_KEY = -------------------------------------------------------------------------------- /server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'], 9 | extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], 10 | root: true, 11 | env: { 12 | node: true, 13 | jest: true, 14 | }, 15 | ignorePatterns: ['.eslintrc.js'], 16 | rules: { 17 | '@typescript-eslint/interface-name-prefix': 'off', 18 | '@typescript-eslint/explicit-function-return-type': 'off', 19 | '@typescript-eslint/explicit-module-boundary-types': 'off', 20 | '@typescript-eslint/no-explicit-any': 'off', 21 | 'simple-import-sort/imports': 'error', 22 | 'simple-import-sort/exports': 'error', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | # .env 40 | # .env.development.local 41 | # .env.test.local 42 | # .env.production.local 43 | # .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "auto", 3 | "printWidth": 120, 4 | "semi": true, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | "trailingComma": "all", 8 | "bracketSpacing": true 9 | } 10 | 11 | -------------------------------------------------------------------------------- /server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /server/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /server/src/dto/params.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-11 16:39:34 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-22 10:19:30 6 | * @Description: 公共参数 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { Transform } from 'class-transformer'; 10 | import { IsInt, IsNotEmpty, Min } from 'class-validator'; 11 | 12 | export class PaginatingDTO { 13 | @ApiProperty({ 14 | type: Number, 15 | description: '当前页码', 16 | default: 1, 17 | }) 18 | @IsInt({ message: 'current 参数只能是 number 类型' }) 19 | @Min(1, { message: 'current 参数不能小于 1' }) 20 | @IsNotEmpty({ message: '缺少 current 页码参数' }) 21 | @Transform(({ value }) => parseInt(value, 10)) 22 | current: number; 23 | 24 | @ApiProperty({ 25 | type: Number, 26 | description: '当前页条数', 27 | default: 10, 28 | }) 29 | @IsInt({ message: 'size 参数只能是 number 类型' }) 30 | @Min(1, { message: 'size 参数不能小于 1' }) 31 | @IsNotEmpty({ message: '缺少 size 页码参数' }) 32 | @Transform(({ value }) => parseInt(value, 10)) 33 | size: number; 34 | } 35 | -------------------------------------------------------------------------------- /server/src/dto/response.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-10 14:05:23 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-11 16:26:16 6 | * @Description: 全局响应体 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | 10 | import { RESPONSE_CODE, RESPONSE_MSG } from '@/enums'; 11 | 12 | export class ResponseDto { 13 | @ApiProperty({ 14 | type: Number, 15 | description: '业务状态码', 16 | default: RESPONSE_CODE.SUCCESS, 17 | }) 18 | code: number; 19 | 20 | @ApiProperty({ 21 | type: String, 22 | description: '业务信息', 23 | default: RESPONSE_MSG.SUCCESS, 24 | }) 25 | msg: string; 26 | 27 | @ApiProperty({ description: '业务数据' }) 28 | data?: any; 29 | 30 | @ApiProperty({ type: Number, description: '时间戳', default: 1720685424078 }) 31 | timestamp: number; 32 | } 33 | -------------------------------------------------------------------------------- /server/src/enums/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 响应码 3 | */ 4 | export enum RESPONSE_CODE { 5 | NOSUCCESS = -1, // 表示请求成功,但操作未成功 6 | SUCCESS = 200, // 请求成功 7 | BAD_REQUEST = 400, // 请求错误 8 | UNAUTHORIZED = 401, // 未授权 9 | FORBIDDEN = 403, // 禁止访问 10 | NOT_FOUND = 404, // 资源未找到 11 | INTERNAL_SERVER_ERROR = 500, // 服务器错误 12 | } 13 | 14 | /** 15 | * @description: 请求提示语 16 | */ 17 | export enum RESPONSE_MSG { 18 | SUCCESS = '请求成功', 19 | FAILURE = '请求失败', 20 | ERROR = '服务器错误', 21 | } 22 | 23 | /** 24 | * @description: 国际化语言 25 | */ 26 | export enum LOCALES { 27 | zhCN = 'zh-CN', // 中文 28 | enUS = 'en-US', // 英文 29 | jaJP = 'ja-JP', // 日文 30 | zhTW = 'zh-TW', // 繁体中文 31 | } 32 | 33 | /** 34 | * @description: 服务器推送事件 35 | */ 36 | export enum EVENTBUS_TYPE { 37 | MESSAGE_CREATE = 'message_create', // 消息创建 38 | } 39 | -------------------------------------------------------------------------------- /server/src/filter/all-exception.filter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-10 14:17:49 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-02 09:41:51 6 | * @Description: 全局异常过滤器 7 | */ 8 | import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common'; 9 | import { Response } from 'express'; 10 | 11 | import { responseMessage } from '@/utils'; 12 | 13 | // @Catch() 装饰器绑定所需的元数据到异常过滤器上。它告诉 Nest这个特定的过滤器正在寻找 14 | @Catch() 15 | export class AllExceptionsFilter implements ExceptionFilter { 16 | catch(exception: HttpException, host: ArgumentsHost) { 17 | // 获取上下文 18 | const ctx = host.switchToHttp(); 19 | // 获取响应体 20 | const response = ctx.getResponse(); 21 | // 获取状态码,判断是HTTP异常还是服务器异常 22 | const statusCode = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; 23 | // 自定义异常返回体 24 | response.status(statusCode).json(responseMessage(null, exception.message, statusCode)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/filter/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-10 14:17:49 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-10 15:48:42 6 | * @Description: http 异常过滤器 7 | */ 8 | import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'; 9 | import { Response } from 'express'; 10 | 11 | import { responseMessage } from '@/utils'; 12 | 13 | // @Catch() 装饰器绑定所需的元数据到异常过滤器上。它告诉 Nest这个特定的过滤器正在寻找 14 | @Catch(HttpException) 15 | export class HttpExceptionsFilter implements ExceptionFilter { 16 | catch(exception: HttpException, host: ArgumentsHost) { 17 | // 获取上下文 18 | const ctx = host.switchToHttp(); 19 | // 获取响应体 20 | const response = ctx.getResponse(); 21 | // 获取状态码 22 | const statusCode = exception.getStatus(); 23 | 24 | // 自定义异常返回体 25 | response.status(statusCode).json(responseMessage(null, exception.message, statusCode)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/src/interceptor/logger.interceptor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-07 09:48:27 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-07 10:16:31 6 | * @Description: LoggerInterceptor 日志拦截器 7 | */ 8 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 9 | import { Observable } from 'rxjs'; 10 | import { map } from 'rxjs/operators'; 11 | 12 | import { OperationLogService } from '@/modules/system-manage/operation-log/operation-log.service'; 13 | 14 | @Injectable() 15 | export class LoggerInterceptor implements NestInterceptor { 16 | constructor(private readonly operationLogService: OperationLogService) {} 17 | 18 | intercept(context: ExecutionContext, next: CallHandler): Observable { 19 | this.operationLogService.logAction(); 20 | return next.handle().pipe(map((data) => data)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/src/middleware/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-29 17:45:48 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-30 13:45:56 6 | * @Description: 全局日志中间件 7 | */ 8 | import { Injectable, Logger, NestMiddleware } from '@nestjs/common'; 9 | import dayjs from 'dayjs'; 10 | import { NextFunction, Request, Response } from 'express'; 11 | 12 | @Injectable() 13 | export class LoggerMiddleware implements NestMiddleware { 14 | private logger = new Logger(); 15 | use(req: Request, res: Response, next: NextFunction) { 16 | // 记录开始时间 17 | const start = Date.now(); 18 | // 获取请求信息 19 | const { method, originalUrl, ip, httpVersion, headers } = req; 20 | 21 | // 获取响应信息 22 | const { statusCode } = res; 23 | 24 | res.on('finish', () => { 25 | // 记录结束时间 26 | const end = Date.now(); 27 | // 计算时间差 28 | const duration = end - start; 29 | 30 | // 组装日志信息:[timestamp] [method] [url] HTTP/[httpVersion] [client IP] [status code] [response time]ms [user-agent] 31 | const logFormat = `${dayjs().valueOf()} ${method} ${originalUrl} HTTP/${httpVersion} ${ip} ${statusCode} ${duration}ms ${headers['user-agent']}`; 32 | 33 | // 根据状态码,进行日志类型区分 34 | if (statusCode >= 500) { 35 | this.logger.error(logFormat, originalUrl); 36 | } else if (statusCode >= 400) { 37 | this.logger.warn(logFormat, originalUrl); 38 | } else { 39 | this.logger.log(logFormat, originalUrl); 40 | } 41 | }); 42 | 43 | next(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/src/middleware/request.middleware.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-09-29 17:55:42 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-10-09 10:19:48 6 | * @Description: 全局请求拦截中间件 7 | */ 8 | import { NextFunction, Request, Response } from 'express'; 9 | 10 | import { responseMessage } from '@/utils'; 11 | export function requestMiddleware(req: Request, res: Response, next: NextFunction) { 12 | if ( 13 | req.method === 'GET' || 14 | req.url.includes('/auth/login') || 15 | req.url.includes('/auth/logout') || 16 | req.url.includes('/auth/juejin') || 17 | req.url.includes('/common/') 18 | ) { 19 | next(); 20 | } else { 21 | res.send(responseMessage(null, '演示系统,禁止操作!', -1)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/src/modules/administrative/message/dto/params-message.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-09-02 14:15:22 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-09-02 14:19:05 6 | * @Description: 请求参数 DTO 7 | */ 8 | 9 | import { ApiProperty } from '@nestjs/swagger'; 10 | import { Status } from '@prisma/client'; 11 | import { IsNumberString, IsOptional, IsUUID } from 'class-validator'; 12 | 13 | import { PaginatingDTO } from '@/dto/params.dto'; 14 | 15 | export class MessageParamsDto extends PaginatingDTO { 16 | @ApiProperty({ 17 | type: String, 18 | description: '作者 id', 19 | default: 'f45cd48b-e703-49db-91be-ae7f594e73e0', 20 | required: false, 21 | }) 22 | @IsOptional() 23 | @IsUUID('all', { message: 'userId 参数不正确' }) 24 | userId?: string; 25 | 26 | @ApiProperty({ 27 | type: String, 28 | description: '标题', 29 | default: '全红婵夺冠拉', 30 | required: false, 31 | }) 32 | title?: string; 33 | 34 | @ApiProperty({ 35 | enum: Status, 36 | description: '状态', 37 | default: Status.ACTIVE, 38 | required: false, 39 | }) 40 | status?: Status; 41 | 42 | @ApiProperty({ 43 | type: Number, 44 | description: '开始日期', 45 | default: 1721145600000, 46 | required: false, 47 | }) 48 | @IsOptional() 49 | @IsNumberString({}, { message: '开始日期必须是时间戳格式' }) 50 | startTime?: number; 51 | 52 | @ApiProperty({ 53 | type: Number, 54 | description: '结束日期', 55 | default: 1721318399999, 56 | required: false, 57 | }) 58 | @IsOptional() 59 | @IsNumberString({}, { message: '结束日期必须是时间戳格式' }) 60 | endTime?: number; 61 | } 62 | -------------------------------------------------------------------------------- /server/src/modules/administrative/message/dto/save-message.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-09-02 15:58:30 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-09-02 16:03:30 6 | * @Description: 保存消息公告 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { Status } from '@prisma/client'; 10 | import { IsNotEmpty } from 'class-validator'; 11 | 12 | export class SaveMessageDto { 13 | @ApiProperty({ 14 | type: String, 15 | description: '标题', 16 | default: '中秋节放假通知', 17 | }) 18 | @IsNotEmpty({ message: '标题必填' }) 19 | title: string; 20 | 21 | @ApiProperty({ 22 | type: String, 23 | description: '内容', 24 | default: '中秋节放假通知', 25 | }) 26 | @IsNotEmpty({ message: '内容必填' }) 27 | content: string; 28 | 29 | @ApiProperty({ 30 | enum: Status, 31 | description: '状态', 32 | example: Status.ACTIVE, 33 | }) 34 | status: Status; 35 | 36 | @ApiProperty({ 37 | type: Boolean, 38 | description: '是否置顶', 39 | default: false, 40 | }) 41 | pinned: boolean; 42 | } 43 | 44 | /** 45 | * @description: 创建已读信息 46 | */ 47 | export class SaveMessageReadDto { 48 | @ApiProperty({ 49 | type: String, 50 | description: '消息 id', 51 | default: 'b64c3c94-5312-45f2-aa8f-fca11a04263a', 52 | }) 53 | @IsNotEmpty({ message: '消息 id 必填' }) 54 | id: string; 55 | } 56 | -------------------------------------------------------------------------------- /server/src/modules/administrative/message/message.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-09-02 16:10:16 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-09-02 16:10:27 6 | * @Description: MessageModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | 10 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 11 | import { OperationLogModule } from '@/modules/system-manage/operation-log/operation-log.module'; 12 | 13 | import { MessageController } from './message.controller'; 14 | import { MessageService } from './message.service'; 15 | 16 | @Module({ 17 | imports: [PrismaModule, OperationLogModule], 18 | controllers: [MessageController], 19 | providers: [MessageService], 20 | exports: [MessageService], 21 | }) 22 | export class MessageModule { } 23 | -------------------------------------------------------------------------------- /server/src/modules/administrative/organazation/dto/params-organazation.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-11 16:44:40 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-16 10:28:13 6 | * @Description: 请求参数 DTO 7 | */ 8 | 9 | import { ApiProperty } from '@nestjs/swagger'; 10 | 11 | export class OrganazationParamsDto { 12 | @ApiProperty({ 13 | type: String, 14 | description: '组织名称', 15 | required: false, 16 | default: '阿里巴巴', 17 | }) 18 | name?: string; 19 | 20 | @ApiProperty({ 21 | type: String, 22 | description: '组织编码', 23 | required: false, 24 | default: 'Alibaba', 25 | }) 26 | code?: string; 27 | } 28 | -------------------------------------------------------------------------------- /server/src/modules/administrative/organazation/dto/response-organazation.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-11 16:19:40 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-22 15:13:48 6 | * @Description: 响应体 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import type { Organization } from '@prisma/client'; 10 | 11 | import { ResponseDto } from '@/dto/response.dto'; 12 | 13 | /** 14 | * @description: 组织管理列表响应体结构 Dto 15 | */ 16 | export class ResponseOrganizationDto extends ResponseDto { 17 | @ApiProperty({ 18 | type: Array, 19 | description: '响应体', 20 | default: { 21 | records: [ 22 | { 23 | id: '1db25530-7407-45ad-ad47-e9dda010e5e8', 24 | name: '阿里巴巴', 25 | code: 'ALI_001', 26 | parentId: null, 27 | sort: 1, 28 | description: 29 | '阿里巴巴集团控股有限公司(简称:阿里巴巴集团)是马云带领下的18位创始人于1999年在浙江省杭州市创立的公司。', 30 | createdAt: '2024-07-11T08:30:52.100Z', 31 | updatedAt: '2024-07-11T08:30:52.100Z', 32 | }, 33 | ], 34 | }, 35 | }) 36 | data: Organization[]; 37 | } 38 | 39 | /** 40 | * @description: 创建/更新/删除组织数据 Dto 41 | */ 42 | export class ResponseSaveOrganizationDto extends ResponseDto { 43 | @ApiProperty({ 44 | type: Object, 45 | description: '响应体', 46 | default: { 47 | id: '1db25530-7407-45ad-ad47-e9dda010e5e8', 48 | name: '阿里巴巴', 49 | code: 'ALI_001', 50 | parentId: null, 51 | sort: 1, 52 | description: 53 | '阿里巴巴集团控股有限公司(简称:阿里巴巴集团)是马云带领下的18位创始人于1999年在浙江省杭州市创立的公司。', 54 | createdAt: '2024-07-11T08:30:52.100Z', 55 | updatedAt: '2024-07-11T08:30:52.100Z', 56 | }, 57 | }) 58 | data: Organization; 59 | } 60 | -------------------------------------------------------------------------------- /server/src/modules/administrative/organazation/dto/save-organazation.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-10 13:39:42 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-22 11:40:09 6 | * @Description: 保存组织数据 Dto 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { IsNotEmpty, IsNumber, IsOptional, IsUUID } from 'class-validator'; 10 | 11 | export class SaveOrganazationDto { 12 | @ApiProperty({ 13 | type: String, 14 | description: '父级id', 15 | default: '0c01ef7d-2f6f-440a-b642-62564d41f473', 16 | required: false, 17 | }) 18 | @IsOptional() 19 | @IsUUID('all', { message: 'parentId 参数不正确' }) 20 | parentId?: string; 21 | 22 | @ApiProperty({ 23 | type: String, 24 | description: '组织名称', 25 | default: '阿里巴巴', 26 | }) 27 | @IsNotEmpty({ message: '组织名称必填' }) 28 | name: string; 29 | 30 | @ApiProperty({ 31 | type: String, 32 | description: '组织编码', 33 | default: 'Alibaba', 34 | }) 35 | @IsNotEmpty({ message: '组织岗位必填' }) 36 | code: string; 37 | 38 | @ApiProperty({ 39 | type: Number, 40 | description: '排序', 41 | default: 1, 42 | }) 43 | @IsNumber( 44 | {}, 45 | { 46 | message: '排序必须为数字', 47 | }, 48 | ) 49 | sort: number; 50 | 51 | @ApiProperty({ 52 | type: String, 53 | description: '组织描述', 54 | required: false, 55 | default: '阿里巴巴集团控股有限公司(简称:阿里巴巴集团)是马云带领下的18位创始人于1999年在浙江省杭州市创立的公司。', 56 | }) 57 | description?: string; 58 | } 59 | -------------------------------------------------------------------------------- /server/src/modules/administrative/organazation/organazation.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-12 14:34:56 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-07 10:01:22 6 | * @Description: OrganazationModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | 10 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 11 | import { OperationLogModule } from '@/modules/system-manage/operation-log/operation-log.module'; 12 | 13 | import { OrganazationController } from './organazation.controller'; 14 | import { OrganazationService } from './organazation.service'; 15 | 16 | @Module({ 17 | imports: [PrismaModule, OperationLogModule], 18 | controllers: [OrganazationController], 19 | providers: [OrganazationService], 20 | exports: [OrganazationService], 21 | }) 22 | export class OrganazationModule { } 23 | -------------------------------------------------------------------------------- /server/src/modules/administrative/post-manage/dto/params-post.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-11 16:44:40 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-17 15:23:42 6 | * @Description: 请求参数 DTO 7 | */ 8 | 9 | import { ApiProperty } from '@nestjs/swagger'; 10 | import { IsNumberString, IsOptional, IsUUID } from 'class-validator'; 11 | 12 | export class PostParamsDto { 13 | @ApiProperty({ 14 | type: String, 15 | description: '岗位名称', 16 | required: false, 17 | default: '前端工程师', 18 | }) 19 | name?: string; 20 | 21 | @ApiProperty({ 22 | type: String, 23 | description: '所属组织', 24 | default: 'f45cd48b-e703-49db-91be-ae7f594e73e0', 25 | required: false, 26 | }) 27 | @IsOptional() 28 | @IsUUID('all', { message: 'orgId 参数不正确' }) 29 | orgId?: string; 30 | 31 | @ApiProperty({ 32 | type: Number, 33 | description: '开始日期', 34 | default: 1721145600000, 35 | required: false, 36 | }) 37 | @IsOptional() 38 | @IsNumberString({}, { message: '开始日期必须是时间戳格式' }) 39 | startTime?: number; 40 | 41 | @ApiProperty({ 42 | type: Number, 43 | description: '结束日期', 44 | default: 1721318399999, 45 | required: false, 46 | }) 47 | @IsOptional() 48 | @IsNumberString({}, { message: '结束日期必须是时间戳格式' }) 49 | endTime?: number; 50 | } 51 | -------------------------------------------------------------------------------- /server/src/modules/administrative/post-manage/dto/save-post.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-10 13:39:42 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-17 15:19:57 6 | * @Description: 保存岗位数据 Dto 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { IsNotEmpty, IsNumber, IsOptional, IsUUID } from 'class-validator'; 10 | 11 | export class SavePostDto { 12 | @ApiProperty({ 13 | type: String, 14 | description: '父级id', 15 | default: '0c01ef7d-2f6f-440a-b642-62564d41f473', 16 | required: false, 17 | }) 18 | @IsOptional() 19 | @IsUUID('all', { message: 'parentId 参数不正确' }) 20 | parentId?: string; 21 | 22 | @ApiProperty({ 23 | type: String, 24 | description: '岗位名称', 25 | default: '前端工程师', 26 | }) 27 | @IsNotEmpty({ message: '岗位名称必填' }) 28 | name: string; 29 | 30 | @ApiProperty({ 31 | type: String, 32 | description: '组织id', 33 | default: 'f45cd48b-e703-49db-91be-ae7f594e73e0', 34 | }) 35 | @IsUUID('all', { message: 'orgId 参数不正确' }) 36 | orgId: string; 37 | 38 | @ApiProperty({ 39 | type: Number, 40 | description: '排序', 41 | default: 1, 42 | }) 43 | @IsNumber( 44 | {}, 45 | { 46 | message: '排序必须为数字', 47 | }, 48 | ) 49 | sort: number; 50 | 51 | @ApiProperty({ 52 | type: String, 53 | description: '岗位描述', 54 | default: 55 | '前端工程师是互联网时代软件产品研发中不可缺少的一种专业研发角色。从狭义上讲,前端工程师使用 HTML、CSS、JavaScript 等专业技能和工具将产品UI设计稿实现成网站产品,涵盖用户PC端、移动端网页,处理视觉和交互问题。', 56 | required: false, 57 | }) 58 | description?: string; 59 | } 60 | -------------------------------------------------------------------------------- /server/src/modules/administrative/post-manage/post-manage.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-12 14:34:56 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-16 16:20:21 6 | * @Description: PostManageModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | 10 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 11 | import { OperationLogModule } from '@/modules/system-manage/operation-log/operation-log.module'; 12 | 13 | import { PostManageController } from './post-manage.controller'; 14 | import { PostManageService } from './post-manage.service'; 15 | 16 | @Module({ 17 | imports: [PrismaModule, OperationLogModule], 18 | controllers: [PostManageController], 19 | providers: [PostManageService], 20 | exports: [PostManageService], 21 | }) 22 | export class PostManageModule { } 23 | -------------------------------------------------------------------------------- /server/src/modules/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-11 10:04:27 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-16 17:01:44 6 | * @Description: AuthModule 7 | */ 8 | import { HttpModule } from '@nestjs/axios'; 9 | import { Module } from '@nestjs/common'; 10 | import { JwtModule } from '@nestjs/jwt'; 11 | import { PassportModule } from '@nestjs/passport'; 12 | 13 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 14 | import { OperationLogModule } from '@/modules/system-manage/operation-log/operation-log.module'; 15 | 16 | import { AuthController } from './auth.controller'; 17 | import { AuthService } from './auth.service'; 18 | import { JwtStrategy } from './jwt.strategy'; 19 | 20 | @Module({ 21 | imports: [ 22 | PrismaModule, 23 | PassportModule.register({ defaultStrategy: 'jwt' }), 24 | JwtModule.register({ 25 | secret: process.env.JWT_SECRET, 26 | signOptions: { expiresIn: '15m' }, // 这里设置访问 token 的过期时间 27 | }), 28 | HttpModule.register({ 29 | timeout: 5000, 30 | maxRedirects: 5, 31 | }), 32 | OperationLogModule, 33 | ], 34 | controllers: [AuthController], 35 | providers: [AuthService, JwtStrategy], 36 | exports: [AuthService], 37 | }) 38 | export class AuthModule { } 39 | -------------------------------------------------------------------------------- /server/src/modules/auth/dto/params-auth.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-01 15:35:28 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-20 15:42:13 6 | * @Description: 请求参数 DTO 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { IsNotEmpty } from 'class-validator'; 10 | 11 | /** 12 | * @description: 用户登录参数 13 | */ 14 | export class LoginParamsDto { 15 | @ApiProperty({ 16 | type: String, 17 | description: '用户名', 18 | default: 'admin', 19 | }) 20 | @IsNotEmpty({ message: '用户名必填' }) 21 | userName: string; 22 | 23 | @ApiProperty({ 24 | type: String, 25 | description: '密码', 26 | default: 'abc123456', 27 | }) 28 | @IsNotEmpty({ message: '密码必填' }) 29 | password: string; 30 | 31 | @ApiProperty({ 32 | type: String, 33 | description: '验证码', 34 | default: '16', 35 | }) 36 | @IsNotEmpty({ message: '验证码必填' }) 37 | captchaCode: string; 38 | } 39 | 40 | /** 41 | * @description: 掘金文章列表 42 | */ 43 | export class juejinParamsDto { 44 | @ApiProperty({ 45 | type: String, 46 | description: '用户id', 47 | default: '1917147257534279', 48 | }) 49 | @IsNotEmpty({ message: '用户id必填' }) 50 | user_id: string; 51 | 52 | @ApiProperty({ 53 | type: Number, 54 | description: '文章类型', 55 | default: 2, 56 | }) 57 | @IsNotEmpty({ message: '文章类型必填' }) 58 | sort_type: number; 59 | 60 | @ApiProperty({ 61 | type: String, 62 | description: '文章偏移量', 63 | default: '5', 64 | }) 65 | @IsNotEmpty({ message: '文章偏移量必填' }) 66 | cursor: string; 67 | } 68 | -------------------------------------------------------------------------------- /server/src/modules/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-01 16:32:15 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-05 14:06:24 6 | * @Description: JWT 鉴权模块 7 | */ 8 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 9 | import { PassportStrategy } from '@nestjs/passport'; 10 | import { Request } from 'express'; 11 | import { ExtractJwt, Strategy } from 'passport-jwt'; 12 | 13 | import { PrismaService } from '@/modules/prisma/prisma.service'; 14 | 15 | @Injectable() 16 | export class JwtStrategy extends PassportStrategy(Strategy) { 17 | constructor(private prisma: PrismaService) { 18 | super({ 19 | // 提供从请求中提取 JWT 的方法。我们将使用在 API 请求的授权头中提供token的标准方法 20 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 21 | // 为了明确起见,我们选择默认的 false 设置, 22 | // 它将确保 JWT 没有过期的责任委托给 Passport 模块。 23 | // 这意味着,如果我们的路由提供了一个过期的 JWT ,请求将被拒绝,并发送 401 未经授权的响应。Passport 会自动为我们办理 24 | ignoreExpiration: false, 25 | // 使用权宜的选项来提供对称的秘密来签署令牌 26 | secretOrKey: process.env.JWT_SECRET, 27 | passReqToCallback: true, 28 | }); 29 | } 30 | 31 | async validate(req: Request, payload: CommonType.TokenPayload) { 32 | // 获取当前 token 33 | const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); 34 | // 缺少令牌 35 | if (!token) { 36 | throw new UnauthorizedException('未登录'); 37 | } 38 | const user = await this.prisma.user.findUnique({ 39 | where: { 40 | id: payload.sub, 41 | }, 42 | }); 43 | if (!user) { 44 | throw new UnauthorizedException('token令牌非法,请重新登录'); 45 | } 46 | return payload; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/src/modules/file-upload/dto/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-29 14:43:48 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-31 15:21:52 6 | * @Description: 上传文件 Dto 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | 10 | import { ResponseDto } from '@/dto/response.dto'; 11 | 12 | export class FileUploadDto { 13 | @ApiProperty({ 14 | type: String, 15 | description: '文件流', 16 | format: 'binary', 17 | }) 18 | file: BinaryData; 19 | } 20 | 21 | /** 22 | * @description: 单个文件上传 23 | */ 24 | export class SingleFileResponseDto extends ResponseDto { 25 | @ApiProperty({ 26 | type: String, 27 | description: '单个文件上传', 28 | default: { 29 | fieldname: 'file', 30 | originalname: '1.jpg', 31 | encoding: '7bit', 32 | mimetype: 'image/jpeg', 33 | destination: './upload/image/2024-07', 34 | filename: '07614707-436a-49b3-b8e3-1fcbd1398600.jpeg', 35 | path: 'http://localhost:3000/static/image/2024-07/07614707-436a-49b3-b8e3-1fcbd1398600.jpeg', 36 | size: 40232, 37 | }, 38 | }) 39 | data: Record; 40 | } 41 | -------------------------------------------------------------------------------- /server/src/modules/file-upload/file-upload.controller.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-29 14:24:19 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-11-08 11:02:12 6 | * @Description: FileUploadController 7 | */ 8 | import { Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common'; 9 | import { AuthGuard } from '@nestjs/passport'; 10 | import { FileInterceptor } from '@nestjs/platform-express'; 11 | import { ApiBearerAuth, ApiBody, ApiConsumes, ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger'; 12 | 13 | import { responseMessage } from '@/utils'; 14 | 15 | import { FileUploadDto, SingleFileResponseDto } from './dto'; 16 | 17 | @ApiTags('文件上传') 18 | @ApiHeader({ 19 | name: 'Authorization', 20 | required: true, 21 | description: 'token令牌', 22 | }) 23 | @ApiBearerAuth() 24 | @Controller('upload') 25 | export class FileUploadController { 26 | /** 27 | * @description: 上传单个文件 28 | */ 29 | @UseGuards(AuthGuard('jwt')) 30 | @UseInterceptors(FileInterceptor('file')) 31 | @Post('single-file') 32 | @ApiConsumes('multipart/form-data') 33 | @ApiOkResponse({ type: SingleFileResponseDto }) 34 | @ApiBody({ 35 | description: '单个文件上传', 36 | type: FileUploadDto, 37 | }) 38 | uploadFile(@UploadedFile() file: Express.Multer.File): CommonType.Response { 39 | // 判断环境获取域名 40 | const hostname = process.env.NODE_ENV === 'production' ? process.env.DOMAIN_NAME : process.env.DEV_DOMAIN_NAME; 41 | file.path = `${hostname}/static${file.path.replace(/\\/g, '/').replace(/upload/g, '')}`; 42 | return responseMessage(file); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/src/modules/file-upload/file-upload.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-29 14:20:28 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-29 14:20:41 6 | * @Description: FileUploadService 7 | */ 8 | import { Injectable } from '@nestjs/common'; 9 | 10 | @Injectable() 11 | export class FileUploadService { } 12 | -------------------------------------------------------------------------------- /server/src/modules/prisma/prisma.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-10 13:47:29 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-10 13:47:40 6 | * @Description: PrismaModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | 10 | import { PrismaService } from './prisma.service'; 11 | 12 | @Module({ 13 | providers: [PrismaService], 14 | exports: [PrismaService], 15 | }) 16 | export class PrismaModule { } 17 | -------------------------------------------------------------------------------- /server/src/modules/prisma/prisma.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-10 13:46:51 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-30 09:23:26 6 | * @Description: PrismaService 7 | */ 8 | import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; 9 | import { PrismaClient } from '@prisma/client'; 10 | 11 | @Injectable() 12 | export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { 13 | constructor() { 14 | super({ 15 | log: ['query', 'info', 'warn', 'error'], // 这里设置日志级别 16 | }); 17 | } 18 | async onModuleInit() { 19 | await this.$connect(); // 在模块初始化时连接到数据库 20 | } 21 | 22 | async onModuleDestroy() { 23 | await this.$disconnect(); // 在应用程序关闭时断开与数据库的连 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/internalization/dto/params-internalization.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-14 17:52:40 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-09-09 18:08:26 6 | * @Description: 请求参数 DTO 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { IsNumberString, IsOptional } from 'class-validator'; 10 | 11 | export class InternalizationParamsDto { 12 | @ApiProperty({ 13 | type: String, 14 | description: '多语言字段', 15 | required: false, 16 | default: 'menu', 17 | }) 18 | name?: string; 19 | 20 | @ApiProperty({ 21 | type: String, 22 | description: '中文', 23 | required: false, 24 | default: '首页', 25 | }) 26 | zhCN?: string; 27 | 28 | @ApiProperty({ 29 | type: Number, 30 | description: '开始日期', 31 | default: 1721145600000, 32 | required: false, 33 | }) 34 | @IsOptional() 35 | @IsNumberString({}, { message: '开始日期必须是时间戳格式' }) 36 | startTime?: number; 37 | 38 | @ApiProperty({ 39 | type: Number, 40 | description: '结束日期', 41 | default: 1721318399999, 42 | required: false, 43 | }) 44 | @IsOptional() 45 | @IsNumberString({}, { message: '结束日期必须是时间戳格式' }) 46 | endTime?: number; 47 | } 48 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/internalization/dto/response-internalization.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-15 10:02:55 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-22 15:26:31 6 | * @Description: 响应体 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import type { Internalization } from '@prisma/client'; 10 | 11 | import { ResponseDto } from '@/dto/response.dto'; 12 | 13 | /** 14 | * @description: 国际化列表响应体结构 Dto 15 | */ 16 | export class ResponseInternalizationDto extends ResponseDto { 17 | @ApiProperty({ 18 | type: Array, 19 | description: '响应体', 20 | default: { 21 | records: [ 22 | { 23 | id: '9ec5117e-370f-4785-8ea4-42fc72399be2', 24 | name: 'updateTitle', 25 | parentId: '54d1a5bd-0275-4a27-a613-63f679a50d4d', 26 | zhCN: '系统版本更新通知', 27 | enUS: 'System version update notification', 28 | jaJP: 'システム更新のお知らせです', 29 | zhTW: '系統版本更新通知', 30 | createdAt: '2024-08-15T02:59:31.025Z', 31 | updatedAt: '2024-08-15T02:59:31.025Z', 32 | }, 33 | ], 34 | }, 35 | }) 36 | data: Internalization[]; 37 | } 38 | 39 | /** 40 | * @description: 创建/更新/删除国际化数据 Dto 41 | */ 42 | export class ResponseSaveInternalizationDto extends ResponseDto { 43 | @ApiProperty({ 44 | type: Object, 45 | description: '响应体', 46 | default: { 47 | id: '9ec5117e-370f-4785-8ea4-42fc72399be2', 48 | name: 'updateTitle', 49 | parentId: '54d1a5bd-0275-4a27-a613-63f679a50d4d', 50 | zhCN: '系统版本更新通知', 51 | enUS: 'System version update notification', 52 | jaJP: 'システム更新のお知らせです', 53 | zhTW: '系統版本更新通知', 54 | createdAt: '2024-08-15T02:59:31.025Z', 55 | updatedAt: '2024-08-15T02:59:31.025Z', 56 | }, 57 | }) 58 | data: Internalization; 59 | } 60 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/internalization/dto/save-internalization.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-15 10:44:35 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-15 11:01:13 6 | * @Description: 保存国际化数据 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; 10 | 11 | export class SaveInternalizationDto { 12 | @ApiProperty({ 13 | type: String, 14 | description: '国际化字段', 15 | default: 'login', 16 | }) 17 | @IsNotEmpty({ message: '国际化字段必填' }) 18 | name: string; 19 | 20 | @ApiProperty({ 21 | type: String, 22 | description: '父级id', 23 | default: '0c01ef7d-2f6f-440a-b642-62564d41f473', 24 | required: false, 25 | }) 26 | @IsOptional() 27 | @IsUUID('all', { message: 'parentId 参数不正确' }) 28 | parentId?: string; 29 | 30 | @ApiProperty({ 31 | type: String, 32 | description: '中文', 33 | default: '统版本更新通知', 34 | required: false, 35 | }) 36 | zhCN?: string; 37 | 38 | @ApiProperty({ 39 | type: String, 40 | description: '英文', 41 | default: 'System version update notification', 42 | required: false, 43 | }) 44 | enUS?: string; 45 | 46 | @ApiProperty({ 47 | type: String, 48 | description: '日文', 49 | default: 'システム更新のお知らせです', 50 | required: false, 51 | }) 52 | jaJP?: string; 53 | 54 | @ApiProperty({ 55 | type: String, 56 | description: '繁体中文', 57 | default: '系統版本更新通知', 58 | required: false, 59 | }) 60 | zhTW?: string; 61 | } 62 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/internalization/internalization.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-15 10:54:25 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-15 10:55:12 6 | * @Description: InternalizationModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | 10 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 11 | import { OperationLogModule } from '@/modules/system-manage/operation-log/operation-log.module'; 12 | 13 | import { InternalizationController } from './internalization.controller'; 14 | import { InternalizationService } from './internalization.service'; 15 | 16 | @Module({ 17 | imports: [PrismaModule, OperationLogModule], 18 | controllers: [InternalizationController], 19 | providers: [InternalizationService], 20 | exports: [InternalizationService], 21 | }) 22 | export class InternalizationModule { } 23 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/menu-manage/dto/params-menu.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-19 16:13:35 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-19 16:13:48 6 | * @Description: 请求参数 DTO 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { IsNumberString, IsOptional } from 'class-validator'; 10 | 11 | export class MenuParamsDto { 12 | @ApiProperty({ 13 | type: String, 14 | description: '路由名称', 15 | required: false, 16 | default: 'home', 17 | }) 18 | name?: string; 19 | 20 | @ApiProperty({ 21 | type: Number, 22 | description: '开始日期', 23 | default: 1721145600000, 24 | required: false, 25 | }) 26 | @IsOptional() 27 | @IsNumberString({}, { message: '开始日期必须是时间戳格式' }) 28 | startTime?: number; 29 | 30 | @ApiProperty({ 31 | type: Number, 32 | description: '结束日期', 33 | default: 1721318399999, 34 | required: false, 35 | }) 36 | @IsOptional() 37 | @IsNumberString({}, { message: '结束日期必须是时间戳格式' }) 38 | endTime?: number; 39 | } 40 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/menu-manage/menu-manage.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-19 17:01:36 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-19 17:02:13 6 | * @Description: MenuManageModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | 10 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 11 | import { OperationLogModule } from '@/modules/system-manage/operation-log/operation-log.module'; 12 | 13 | import { MenuManageController } from './menu-manage.controller'; 14 | import { MenuManageService } from './menu-manage.service'; 15 | 16 | @Module({ 17 | imports: [PrismaModule, OperationLogModule], 18 | controllers: [MenuManageController], 19 | providers: [MenuManageService], 20 | exports: [MenuManageService], 21 | }) 22 | export class MenuManageModule { } 23 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/operation-log/dto/params-log.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-07 17:43:29 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-07 17:45:52 6 | * @Description: 请求参数 DTO 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { Method } from '@prisma/client'; 10 | import { IsNumberString, IsOptional, IsUUID } from 'class-validator'; 11 | 12 | import { PaginatingDTO } from '@/dto/params.dto'; 13 | 14 | export class LogParamsDto extends PaginatingDTO { 15 | @ApiProperty({ 16 | type: String, 17 | description: '操作人', 18 | default: 'f45cd48b-e703-49db-91be-ae7f594e73e0', 19 | required: false, 20 | }) 21 | @IsOptional() 22 | @IsUUID('all', { message: 'userId 参数不正确' }) 23 | userId?: string; 24 | 25 | @ApiProperty({ 26 | enum: Method, 27 | description: '方法', 28 | default: Method.GET, 29 | required: false, 30 | }) 31 | method?: Method; 32 | 33 | @ApiProperty({ 34 | type: Number, 35 | description: '开始日期', 36 | default: 1721145600000, 37 | required: false, 38 | }) 39 | @IsOptional() 40 | @IsNumberString({}, { message: '开始日期必须是时间戳格式' }) 41 | startTime?: number; 42 | 43 | @ApiProperty({ 44 | type: Number, 45 | description: '结束日期', 46 | default: 1721318399999, 47 | required: false, 48 | }) 49 | @IsOptional() 50 | @IsNumberString({}, { message: '结束日期必须是时间戳格式' }) 51 | endTime?: number; 52 | } 53 | 54 | /** 55 | * @description: 删除操作日志 56 | */ 57 | export class DelLogsDto { 58 | @ApiProperty({ 59 | type: [String], 60 | description: 'id 集合', 61 | default: ['f45cd48b-e703-49db-91be-ae7f594e73e0', 'fa0fc96c-6c01-459d-b904-c6f65ec369b5'], 62 | }) 63 | ids: string[]; 64 | } 65 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/operation-log/operation-log.controller.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-06 11:06:30 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-09 18:14:41 6 | * @Description: OperationLogController 7 | */ 8 | import { Body, Controller, Delete, Get, Query, UseGuards } from '@nestjs/common'; 9 | import { AuthGuard } from '@nestjs/passport'; 10 | import { ApiBearerAuth, ApiHeader, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; // swagger 接口文档 11 | 12 | import { DelLogsDto, LogParamsDto } from './dto/params-log.dto'; 13 | import { ResponseDelLogDto, ResponseLogDto } from './dto/response-log.dto'; 14 | import { OperationLogService } from './operation-log.service'; 15 | 16 | @ApiTags('系统管理-操作日志') 17 | @ApiHeader({ 18 | name: 'Authorization', 19 | required: true, 20 | description: 'token令牌', 21 | }) 22 | @ApiBearerAuth() 23 | @Controller('system/operation-log') 24 | @UseGuards(AuthGuard('jwt')) 25 | export class OperationLogController { 26 | constructor(private readonly operationLogService: OperationLogService) { } 27 | 28 | /** 29 | * @description: 查询操作日志列表 30 | */ 31 | @Get() 32 | @ApiOkResponse({ type: ResponseLogDto }) 33 | @ApiOperation({ summary: '获取操作日志列表' }) 34 | findAll(@Query() params: LogParamsDto) { 35 | return this.operationLogService.findAll(params); 36 | } 37 | 38 | /** 39 | * @description: 删除操作日志 40 | */ 41 | @Delete() 42 | @ApiOkResponse({ type: ResponseDelLogDto }) 43 | @ApiOperation({ summary: '删除操作日志' }) 44 | remove(@Body() body: DelLogsDto) { 45 | return this.operationLogService.remove(body.ids); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/operation-log/operation-log.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-06 11:06:38 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-06 11:10:15 6 | * @Description: OperationLogModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | import { HttpModule } from '@nestjs/axios'; 10 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 11 | 12 | import { OperationLogController } from './operation-log.controller'; 13 | import { OperationLogService } from './operation-log.service'; 14 | 15 | @Module({ 16 | imports: [ 17 | HttpModule.register({ 18 | timeout: 5000, 19 | maxRedirects: 5, 20 | }), 21 | PrismaModule 22 | ], 23 | controllers: [OperationLogController], 24 | providers: [OperationLogService], 25 | exports: [OperationLogService], 26 | }) 27 | export class OperationLogModule { } 28 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/role-manage/dto/params-role.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-22 10:19:37 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-22 10:21:37 6 | * @Description: 请求参数 DTO 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { IsNumberString, IsOptional } from 'class-validator'; 10 | 11 | import { PaginatingDTO } from '@/dto/params.dto'; 12 | 13 | export class RoleParamsDto extends PaginatingDTO { 14 | @ApiProperty({ 15 | type: String, 16 | description: '角色名称', 17 | default: '超级管理员', 18 | required: false, 19 | }) 20 | name?: string; 21 | 22 | @ApiProperty({ 23 | type: String, 24 | description: '角色编码', 25 | default: 'Super Admin', 26 | required: false, 27 | }) 28 | code?: string; 29 | 30 | @ApiProperty({ 31 | type: Number, 32 | description: '开始日期', 33 | default: 1721145600000, 34 | required: false, 35 | }) 36 | @IsOptional() 37 | @IsNumberString({}, { message: '开始日期必须是时间戳格式' }) 38 | startTime?: number; 39 | 40 | @ApiProperty({ 41 | type: Number, 42 | description: '结束日期', 43 | default: 1721318399999, 44 | required: false, 45 | }) 46 | @IsOptional() 47 | @IsNumberString({}, { message: '结束日期必须是时间戳格式' }) 48 | endTime?: number; 49 | } 50 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/role-manage/dto/save-role.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-22 11:05:11 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-22 17:18:14 6 | * @Description: 保存角色数据 Dto 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; 10 | 11 | export class SaveRoleDto { 12 | @ApiProperty({ 13 | type: String, 14 | description: '角色名称', 15 | default: '超级管理员', 16 | }) 17 | @IsNotEmpty({ message: '角色名称必填' }) 18 | name: string; 19 | 20 | @ApiProperty({ 21 | type: String, 22 | description: '角色编码', 23 | default: '超级管理员', 24 | }) 25 | @IsNotEmpty({ message: '角色编码必填' }) 26 | code: string; 27 | 28 | @ApiProperty({ 29 | type: Number, 30 | description: '排序', 31 | default: 1, 32 | }) 33 | @IsNumber( 34 | {}, 35 | { 36 | message: '排序必须为数字', 37 | }, 38 | ) 39 | sort: number; 40 | 41 | @ApiProperty({ 42 | type: String, 43 | description: '角色描述', 44 | default: '拥有系统全部权限', 45 | required: false, 46 | }) 47 | description?: string; 48 | 49 | @ApiProperty({ 50 | type: [Object], 51 | description: '菜单权限集合', 52 | default: [{ menuId: 'dea4e038-592f-4532-ac86-c7de25a3416c', actions: ['add', 'edit'] }], 53 | isArray: true, 54 | }) 55 | @IsArray({ 56 | message: '菜单权限必须是数组类型', 57 | }) 58 | menus: { menuId: string; actions: string[] }[]; 59 | } 60 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/role-manage/role-manage.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-08-22 11:01:12 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-22 11:02:09 6 | * @Description: RoleManageModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | 10 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 11 | import { OperationLogModule } from '@/modules/system-manage/operation-log/operation-log.module'; 12 | 13 | import { RoleManageService } from './role.manage.service'; 14 | import { RoleManageController } from './role-manage.controller'; 15 | 16 | @Module({ 17 | imports: [PrismaModule, OperationLogModule], 18 | controllers: [RoleManageController], 19 | providers: [RoleManageService], 20 | exports: [RoleManageService], 21 | }) 22 | export class RoleManageModule { } 23 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/user-manage/dto/params-user.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-18 13:48:58 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-30 14:51:56 6 | * @Description: 请求参数 DTO 7 | */ 8 | import { ApiProperty } from '@nestjs/swagger'; 9 | import { Status } from '@prisma/client'; 10 | 11 | import { PaginatingDTO } from '@/dto/params.dto'; 12 | 13 | export class UserParamsDto extends PaginatingDTO { 14 | @ApiProperty({ 15 | type: String, 16 | description: '用户名', 17 | required: false, 18 | default: 'admin', 19 | }) 20 | userName?: string; 21 | 22 | @ApiProperty({ 23 | enum: Status, 24 | description: '状态', 25 | default: Status.ACTIVE, 26 | required: false, 27 | }) 28 | status?: Status; 29 | } 30 | -------------------------------------------------------------------------------- /server/src/modules/system-manage/user-manage/user-manage.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-18 14:26:58 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-08-07 09:59:24 6 | * @Description: UserManageModule 7 | */ 8 | import { Module } from '@nestjs/common'; 9 | 10 | import { AuthModule } from '@/modules/auth/auth.module'; 11 | import { PrismaModule } from '@/modules/prisma/prisma.module'; 12 | import { OperationLogModule } from '@/modules/system-manage/operation-log/operation-log.module'; 13 | 14 | import { UserManageController } from './user-manage.controller'; 15 | import { UserManageService } from './user-manage.service'; 16 | 17 | @Module({ 18 | imports: [PrismaModule, AuthModule, OperationLogModule], 19 | controllers: [UserManageController], 20 | providers: [UserManageService], 21 | exports: [UserManageService], 22 | }) 23 | export class UserManageModule { } 24 | -------------------------------------------------------------------------------- /server/src/pipe/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-07-11 17:09:14 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-07-11 17:21:53 6 | * @Description:请求参数校验 7 | */ 8 | import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common'; 9 | import { plainToClass } from 'class-transformer'; 10 | import { validate } from 'class-validator'; 11 | 12 | @Injectable() 13 | export class ValidationPipe implements PipeTransform { 14 | async transform(value: any, { metatype }: ArgumentMetadata) { 15 | if (!metatype || !this.toValidate(metatype)) { 16 | // 如果没有传入验证规则,则不验证,直接返回数据 17 | return value; 18 | } 19 | // 将对象转换为 Class 来验证 20 | const object = plainToClass(metatype, value); 21 | const errors = await validate(object); 22 | if (errors.length > 0) { 23 | const msg = Object.values(errors[0].constraints)[0]; // 只需要取第一个错误信息并返回即可 24 | // 自定义校验返回格式 25 | throw new BadRequestException(`参数校验失败: ${msg}`); 26 | } 27 | return value; 28 | } 29 | private toValidate(metatype: any): boolean { 30 | const types: any[] = [String, Boolean, Number, Array, Object]; 31 | return !types.includes(metatype); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /server/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false, 20 | "esModuleInterop": true, 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/upload/image/2024-07/6f6e22a3-1e6a-4aa3-bbe9-6b2b0e6ea5aa.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/server/upload/image/2024-07/6f6e22a3-1e6a-4aa3-bbe9-6b2b0e6ea5aa.jpeg -------------------------------------------------------------------------------- /server/upload/image/2024-07/cc9e77ee-cf84-48e8-a9d0-dc3e9d21224c.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/server/upload/image/2024-07/cc9e77ee-cf84-48e8-a9d0-dc3e9d21224c.jpeg -------------------------------------------------------------------------------- /server/upload/image/2024-08/f92269b2-77b7-48ef-83b9-e8bd4970c304.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/server/upload/image/2024-08/f92269b2-77b7-48ef-83b9-e8bd4970c304.jpeg -------------------------------------------------------------------------------- /server/upload/image/2024-09/2126ab07-c104-4fe3-a6c2-1c1d72223614.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/server/upload/image/2024-09/2126ab07-c104-4fe3-a6c2-1c1d72223614.jpeg -------------------------------------------------------------------------------- /server/upload/image/2024-09/72840555-ee20-4679-a54d-4b39c622567d.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/server/upload/image/2024-09/72840555-ee20-4679-a54d-4b39c622567d.jpeg -------------------------------------------------------------------------------- /server/upload/image/2024-09/a370df3c-4d3d-4e2d-81ac-25790e2a1789.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/server/upload/image/2024-09/a370df3c-4d3d-4e2d-81ac-25790e2a1789.jpeg -------------------------------------------------------------------------------- /server/upload/image/2024-09/ac7b73c5-3c4e-41c4-94a8-21393e5682e4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/server/upload/image/2024-09/ac7b73c5-3c4e-41c4-94a8-21393e5682e4.jpeg -------------------------------------------------------------------------------- /server/upload/image/2024-09/fcdab14e-29d4-43e3-8630-9cf064646f35.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/server/upload/image/2024-09/fcdab14e-29d4-43e3-8630-9cf064646f35.jpeg -------------------------------------------------------------------------------- /server/webpack-hmr.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | const nodeExternals = require('webpack-node-externals'); 3 | const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); 4 | 5 | module.exports = function (options, webpack) { 6 | return { 7 | ...options, 8 | entry: ['webpack/hot/poll?100', options.entry], 9 | externals: [ 10 | nodeExternals({ 11 | allowlist: ['webpack/hot/poll?100'], 12 | }), 13 | ], 14 | plugins: [ 15 | ...options.plugins, 16 | new webpack.HotModuleReplacementPlugin(), 17 | new webpack.WatchIgnorePlugin({ 18 | paths: [/\.js$/, /\.d\.ts$/], 19 | }), 20 | new RunScriptWebpackPlugin({ name: options.output.filename }), 21 | ], 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /web/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 10 4 | -------------------------------------------------------------------------------- /web/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=false 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /web/.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_PREVIEW=true 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /web/.env.preview: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=true 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /web/.gitattributes: -------------------------------------------------------------------------------- 1 | public/* linguist-vendored 2 | 3 | # Automatically normalize line endings (to LF) for all text-based files. 4 | * text=auto eol=lf 5 | 6 | # Declare files that will always have CRLF line endings on checkout. 7 | *.{cmd,[cC][mM][dD]} text eol=crlf 8 | *.{bat,[bB][aA][tT]} text eol=crlf 9 | 10 | # Denote all files that are truly binary and should not be modified. 11 | *.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /web/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.js": "eslint --fix", 3 | "*.{css,less}": "stylelint --fix" 4 | } -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": false, 4 | "singleQuote": true, 5 | "prettier.spaceBeforeFunctionParen": true, 6 | "trailingComma": "none" 7 | } 8 | -------------------------------------------------------------------------------- /web/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10.15.0 4 | cache: yarn 5 | script: 6 | - yarn 7 | - yarn run lint --no-fix && yarn run build 8 | -------------------------------------------------------------------------------- /web/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anan Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /web/babel.config.js: -------------------------------------------------------------------------------- 1 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) 2 | const IS_PREVIEW = process.env.VUE_APP_PREVIEW === 'true' 3 | 4 | const plugins = [] 5 | if (IS_PROD && !IS_PREVIEW) { 6 | // 去除日志的插件, 7 | plugins.push('transform-remove-console') 8 | } 9 | 10 | // lazy load ant-design-vue 11 | // if your use import on Demand, Use this code 12 | plugins.push(['import', { 13 | 'libraryName': 'ant-design-vue', 14 | 'libraryDirectory': 'es', 15 | 'style': true // `style: true` 会加载 less 文件 16 | }]) 17 | 18 | module.exports = { 19 | presets: [ 20 | '@vue/cli-plugin-babel/preset', 21 | [ 22 | '@babel/preset-env', 23 | { 24 | 'useBuiltIns': 'entry', 25 | 'corejs': 3 26 | } 27 | ] 28 | ], 29 | plugins 30 | } 31 | -------------------------------------------------------------------------------- /web/commitlint.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * feat:新增功能 3 | * fix:bug 修复 4 | * docs:文档更新 5 | * style:不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑) 6 | * refactor:重构代码(既没有新增功能,也没有修复 bug) 7 | * perf:性能, 体验优化 8 | * test:新增测试用例或是更新现有测试 9 | * build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交 10 | * ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交 11 | * chore:不属于以上类型的其他类型,比如构建流程, 依赖管理 12 | * revert:回滚某个更早之前的提交 13 | */ 14 | 15 | module.exports = { 16 | extends: ['@commitlint/config-conventional'], 17 | rules: { 18 | 'type-enum': [ 19 | 2, 20 | 'always', 21 | ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert'] 22 | ], 23 | 'subject-full-stop': [0, 'never'], 24 | 'subject-case': [0, 'never'] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["src/*"] 7 | } 8 | }, 9 | "exclude": ["node_modules", "dist"], 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/public/favicon.ico -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 54 | -------------------------------------------------------------------------------- /web/src/api/administrative/message.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 获取消息公告列表 5 | */ 6 | export function getMessageList(parameter) { 7 | return request({ 8 | url: '/administrative/message', 9 | method: 'get', 10 | params: parameter 11 | }) 12 | } 13 | 14 | /** 15 | * @description: 创建消息公告 16 | */ 17 | export function addMessage(parameter) { 18 | return request({ 19 | url: '/administrative/message', 20 | method: 'post', 21 | data: parameter 22 | }) 23 | } 24 | 25 | /** 26 | * @description: 更新消息公告 27 | */ 28 | export function updateMessage({ id, ...parameter }) { 29 | return request({ 30 | url: `/administrative/message/${id}`, 31 | method: 'put', 32 | data: parameter 33 | }) 34 | } 35 | 36 | /** 37 | * @description: 删除消息公告 38 | */ 39 | export function delMessage(id) { 40 | return request({ 41 | url: `/administrative/message/${id}`, 42 | method: 'delete' 43 | }) 44 | } 45 | 46 | /** 47 | * @description: 修改置顶状态 48 | */ 49 | export function changePinned(id) { 50 | return request({ 51 | url: `/administrative/message/${id}`, 52 | method: 'patch' 53 | }) 54 | } 55 | 56 | /** 57 | * @description: 创建已读消息 58 | */ 59 | export function createMessageRead(parameter) { 60 | return request({ 61 | url: 'administrative/message/createMessageRead', 62 | method: 'post', 63 | data: parameter 64 | }) 65 | } 66 | 67 | /** 68 | * @description: 请求未读条数 69 | */ 70 | export function getUnreadCount() { 71 | return request({ 72 | url: '/administrative/message/unread/count', 73 | method: 'get' 74 | }) 75 | } 76 | 77 | /** 78 | * @description: 获取未读消息公告列表 79 | */ 80 | export function getUnreadMessageList(parameter) { 81 | return request({ 82 | url: '/administrative/message/unread/list', 83 | method: 'get', 84 | params: parameter 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /web/src/api/administrative/organization.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 获取组织列表 5 | */ 6 | export function getOrgList(parameter) { 7 | return request({ 8 | url: '/administrative/organazation', 9 | method: 'get', 10 | params: parameter 11 | }) 12 | } 13 | 14 | /** 15 | * @description: 创建组织 16 | */ 17 | export function addOrg(parameter) { 18 | return request({ 19 | url: '/administrative/organazation', 20 | method: 'post', 21 | data: parameter 22 | }) 23 | } 24 | 25 | /** 26 | * @description: 更新组织 27 | */ 28 | export function updateOrg({ id, ...parameter }) { 29 | return request({ 30 | url: `/administrative/organazation/${id}`, 31 | method: 'put', 32 | data: parameter 33 | }) 34 | } 35 | 36 | /** 37 | * @description: 删除组织 38 | */ 39 | export function delOrg(id) { 40 | return request({ 41 | url: `/administrative/organazation/${id}`, 42 | method: 'delete' 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /web/src/api/administrative/post.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 获取岗位列表 5 | */ 6 | export function getPostList(parameter) { 7 | return request({ 8 | url: '/administrative/post-manage', 9 | method: 'get', 10 | params: parameter 11 | }) 12 | } 13 | 14 | /** 15 | * @description: 创建岗位 16 | */ 17 | export function addPost(parameter) { 18 | return request({ 19 | url: '/administrative/post-manage', 20 | method: 'post', 21 | data: parameter 22 | }) 23 | } 24 | 25 | /** 26 | * @description: 更新岗位 27 | */ 28 | export function updatePost({ id, ...parameter }) { 29 | return request({ 30 | url: `/administrative/post-manage/${id}`, 31 | method: 'put', 32 | data: parameter 33 | }) 34 | } 35 | 36 | /** 37 | * @description: 删除岗位 38 | */ 39 | export function delPost(id) { 40 | return request({ 41 | url: `/administrative/post-manage/${id}`, 42 | method: 'delete' 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /web/src/api/auth/index.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 用户登录 5 | */ 6 | export function login(parameter) { 7 | return request({ 8 | url: '/auth/login', 9 | method: 'post', 10 | data: parameter, 11 | skipErrorHandler: true 12 | }) 13 | } 14 | 15 | /** 16 | * @description: 获取图形验证码 17 | */ 18 | export function generateVerifCode() { 19 | return request({ 20 | url: '/auth/captcha', 21 | method: 'get' 22 | }) 23 | } 24 | 25 | /** 26 | * @description: 获取用户信息 27 | */ 28 | export function getUserInfo() { 29 | return request({ 30 | url: '/auth/getUserInfo', 31 | method: 'get' 32 | }) 33 | } 34 | 35 | /** 36 | * @description: 注销登录 37 | */ 38 | export function logout() { 39 | return request({ 40 | url: 'auth/logout', 41 | method: 'post' 42 | }) 43 | } 44 | 45 | /** 46 | * @description: 获取国际化层级数据 47 | */ 48 | export function getLocales() { 49 | return request({ 50 | url: 'auth/getLocales', 51 | method: 'get' 52 | }) 53 | } 54 | 55 | /** 56 | * @description: 获取动态路由 57 | */ 58 | export function getDynamicRoutes() { 59 | return request({ 60 | url: 'auth/getDynamicRoutes', 61 | method: 'get' 62 | }) 63 | } 64 | 65 | /** 66 | * @description: 获取掘金列表 67 | */ 68 | export function getJuejinList(parameter) { 69 | return request({ 70 | url: 'auth/juejin', 71 | method: 'post', 72 | data: parameter 73 | }) 74 | } 75 | 76 | /** 77 | * @description: 文件上传 78 | */ 79 | export function uploadFile(parameter) { 80 | return request({ 81 | url: 'upload/single-file', 82 | method: 'post', 83 | data: parameter, 84 | headers: { 85 | 'Content-Type': 'multipart/form-data' 86 | } 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /web/src/api/system-manage/internationalization.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 获取国际化列表 5 | */ 6 | export function getInternalizationList(parameter) { 7 | return request({ 8 | url: '/system/internalization', 9 | method: 'get', 10 | params: parameter 11 | }) 12 | } 13 | 14 | /** 15 | * @description: 创建国际化 16 | */ 17 | export function addInternalization(parameter) { 18 | return request({ 19 | url: '/system/internalization', 20 | method: 'post', 21 | data: parameter 22 | }) 23 | } 24 | 25 | /** 26 | * @description: 更新国际化 27 | */ 28 | export function updateInternalization({ id, ...parameter }) { 29 | return request({ 30 | url: `/system/internalization/${id}`, 31 | method: 'put', 32 | data: parameter 33 | }) 34 | } 35 | 36 | /** 37 | * @description: 删除国际化 38 | */ 39 | export function delInternalization(id) { 40 | return request({ 41 | url: `/system/internalization/${id}`, 42 | method: 'delete' 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /web/src/api/system-manage/menu-manage.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 获取菜单管理列表 5 | */ 6 | export function getMenuList(parameter) { 7 | return request({ 8 | url: '/system/menu-manage', 9 | method: 'get', 10 | params: parameter 11 | }) 12 | } 13 | 14 | /** 15 | * @description: 创建菜单 16 | */ 17 | export function addMenu(parameter) { 18 | return request({ 19 | url: '/system/menu-manage', 20 | method: 'post', 21 | data: parameter 22 | }) 23 | } 24 | 25 | /** 26 | * @description: 更新菜单 27 | */ 28 | export function updateMenu({ id, ...parameter }) { 29 | return request({ 30 | url: `/system/menu-manage/${id}`, 31 | method: 'put', 32 | data: parameter 33 | }) 34 | } 35 | 36 | /** 37 | * @description: 删除菜单 38 | */ 39 | export function delMenu(id) { 40 | return request({ 41 | url: `/system/menu-manage/${id}`, 42 | method: 'delete' 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /web/src/api/system-manage/operation-log.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 获取操作日志列表 5 | */ 6 | export function getLogList(parameter) { 7 | return request({ 8 | url: '/system/operation-log', 9 | method: 'get', 10 | params: parameter 11 | }) 12 | } 13 | 14 | /** 15 | * @description: 批量删除日志 16 | */ 17 | export function delRole(parameter) { 18 | return request({ 19 | url: `/system/operation-log`, 20 | method: 'delete', 21 | data: parameter 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /web/src/api/system-manage/role-manage.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 获取角色管理列表 5 | */ 6 | export function getRoleList(parameter) { 7 | return request({ 8 | url: '/system/role-manage', 9 | method: 'get', 10 | params: parameter 11 | }) 12 | } 13 | 14 | /** 15 | * @description: 创建角色 16 | */ 17 | export function addRole(parameter) { 18 | return request({ 19 | url: '/system/role-manage', 20 | method: 'post', 21 | data: parameter 22 | }) 23 | } 24 | 25 | /** 26 | * @description: 更新角色 27 | */ 28 | export function updateRole({ id, ...parameter }) { 29 | return request({ 30 | url: `/system/role-manage/${id}`, 31 | method: 'put', 32 | data: parameter 33 | }) 34 | } 35 | 36 | /** 37 | * @description: 删除角色 38 | */ 39 | export function delRole(id) { 40 | return request({ 41 | url: `/system/role-manage/${id}`, 42 | method: 'delete' 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /web/src/api/system-manage/user-manage.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * @description: 获取用户管理列表 5 | */ 6 | export function getUserList(parameter) { 7 | return request({ 8 | url: '/system/user-manage', 9 | method: 'get', 10 | params: parameter 11 | }) 12 | } 13 | 14 | /** 15 | * @description: 创建用户 16 | */ 17 | export function addUser(parameter) { 18 | return request({ 19 | url: '/system/user-manage', 20 | method: 'post', 21 | data: parameter 22 | }) 23 | } 24 | 25 | /** 26 | * @description: 更新用户 27 | */ 28 | export function updateUser({ id, ...parameter }) { 29 | return request({ 30 | url: `/system/user-manage/${id}`, 31 | method: 'put', 32 | data: parameter 33 | }) 34 | } 35 | 36 | /** 37 | * @description: 删除用户 38 | */ 39 | export function delUser(id) { 40 | return request({ 41 | url: `/system/user-manage/${id}`, 42 | method: 'delete' 43 | }) 44 | } 45 | 46 | /** 47 | * @description: 更新用户信息 48 | */ 49 | export function updateUserTags(parameter) { 50 | return request({ 51 | url: `/system/user-manage/updateUserTags`, 52 | method: 'patch', 53 | data: parameter 54 | }) 55 | } 56 | 57 | /** 58 | * @description: 修改账户密码 59 | */ 60 | export function changeUserPassword(parameter) { 61 | return request({ 62 | url: `/system/user-manage/changePassword`, 63 | method: 'patch', 64 | data: parameter 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /web/src/assets/icons/about.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/administrative.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/antd.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/captcha.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/colorthief.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/eye-dropper.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/features.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/lazyload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/locale.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/log.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/organization.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/pickr.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/post.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/viewer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/icons/waterfall.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/1.jpg -------------------------------------------------------------------------------- /web/src/assets/img/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/10.jpg -------------------------------------------------------------------------------- /web/src/assets/img/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/11.jpg -------------------------------------------------------------------------------- /web/src/assets/img/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/12.jpg -------------------------------------------------------------------------------- /web/src/assets/img/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/13.jpg -------------------------------------------------------------------------------- /web/src/assets/img/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/14.jpg -------------------------------------------------------------------------------- /web/src/assets/img/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/15.jpg -------------------------------------------------------------------------------- /web/src/assets/img/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/16.jpg -------------------------------------------------------------------------------- /web/src/assets/img/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/17.jpg -------------------------------------------------------------------------------- /web/src/assets/img/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/18.jpg -------------------------------------------------------------------------------- /web/src/assets/img/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/19.jpg -------------------------------------------------------------------------------- /web/src/assets/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/2.jpg -------------------------------------------------------------------------------- /web/src/assets/img/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/20.jpg -------------------------------------------------------------------------------- /web/src/assets/img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/3.jpg -------------------------------------------------------------------------------- /web/src/assets/img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/4.jpg -------------------------------------------------------------------------------- /web/src/assets/img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/5.jpg -------------------------------------------------------------------------------- /web/src/assets/img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/6.jpg -------------------------------------------------------------------------------- /web/src/assets/img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/7.jpg -------------------------------------------------------------------------------- /web/src/assets/img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/8.jpg -------------------------------------------------------------------------------- /web/src/assets/img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/img/9.jpg -------------------------------------------------------------------------------- /web/src/assets/office/test.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/office/test.docx -------------------------------------------------------------------------------- /web/src/assets/office/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/office/test.pdf -------------------------------------------------------------------------------- /web/src/assets/office/test.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/office/test.xlsx -------------------------------------------------------------------------------- /web/src/assets/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/vue2-admin/8dc89105c1dca62404a1f8237c9b17314e6b27e7/web/src/assets/reset.png -------------------------------------------------------------------------------- /web/src/components/AvatarList/Item.jsx: -------------------------------------------------------------------------------- 1 | import { Avatar, Tooltip } from 'ant-design-vue' 2 | import PropTypes from 'ant-design-vue/es/_util/vue-types' 3 | import { getSlotOptions } from 'ant-design-vue/lib/_util/props-util' 4 | import { warning } from 'ant-design-vue/lib/vc-util/warning' 5 | 6 | export const AvatarListItemProps = { 7 | tips: PropTypes.string, 8 | src: PropTypes.string.def('') 9 | } 10 | 11 | const Item = { 12 | __ANT_AVATAR_CHILDREN: true, 13 | name: 'AvatarListItem', 14 | props: AvatarListItemProps, 15 | created () { 16 | warning(getSlotOptions(this.$parent).__ANT_AVATAR_LIST, 'AvatarListItem must be a subcomponent of AvatarList') 17 | }, 18 | render () { 19 | const size = this.$parent.size === 'mini' ? 'small' : this.$parent.size 20 | const AvatarDom = 21 | return (this.tips && {AvatarDom}) || 22 | } 23 | } 24 | 25 | export default Item 26 | -------------------------------------------------------------------------------- /web/src/components/AvatarList/index.js: -------------------------------------------------------------------------------- 1 | import Item from './Item' 2 | import AvatarList from './List' 3 | 4 | export { AvatarList, Item as AvatarListItem } 5 | 6 | export default AvatarList 7 | -------------------------------------------------------------------------------- /web/src/components/AvatarList/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list"; 4 | @avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item"; 5 | 6 | .@{avatar-list-prefix-cls} { 7 | display: inline-block; 8 | 9 | ul { 10 | display: inline-block; 11 | padding: 0; 12 | margin: 0 0 0 8px; 13 | font-size: 0; 14 | list-style: none; 15 | } 16 | } 17 | 18 | .@{avatar-list-item-prefix-cls} { 19 | display: inline-block; 20 | width: @avatar-size-base; 21 | height: @avatar-size-base; 22 | margin-left: -8px; 23 | font-size: @font-size-base; 24 | 25 | :global { 26 | .ant-avatar { 27 | cursor: pointer; 28 | border: 1px solid #fff; 29 | } 30 | } 31 | 32 | &.large { 33 | width: @avatar-size-lg; 34 | height: @avatar-size-lg; 35 | } 36 | 37 | &.small { 38 | width: @avatar-size-sm; 39 | height: @avatar-size-sm; 40 | } 41 | 42 | &.mini { 43 | width: 20px; 44 | height: 20px; 45 | 46 | :global { 47 | .ant-avatar { 48 | width: 20px; 49 | height: 20px; 50 | line-height: 20px; 51 | 52 | .ant-avatar-string { 53 | font-size: 12px; 54 | line-height: 18px; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /web/src/components/Charts/MiniArea.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /web/src/components/Charts/MiniBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /web/src/components/Charts/MiniProgress.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | 37 | 76 | -------------------------------------------------------------------------------- /web/src/components/Charts/chart.less: -------------------------------------------------------------------------------- 1 | .antv-chart-mini { 2 | position: relative; 3 | width: 100%; 4 | 5 | .chart-wrapper { 6 | position: absolute; 7 | bottom: -28px; 8 | width: 100%; 9 | 10 | /* margin: 0 -5px; 11 | overflow: hidden; */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /web/src/components/Editor/QuillEditor.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 62 | 63 | 80 | -------------------------------------------------------------------------------- /web/src/components/GlobalFooter/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 44 | -------------------------------------------------------------------------------- /web/src/components/GlobalHeader/DetailModal.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 48 | 49 | 72 | -------------------------------------------------------------------------------- /web/src/components/GlobalHeader/FullScreen.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /web/src/components/GlobalHeader/RightContent.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 53 | -------------------------------------------------------------------------------- /web/src/components/IconSelector/README.md: -------------------------------------------------------------------------------- 1 | IconSelector 2 | ==== 3 | 4 | > 图标选择组件,常用于为某一个数据设定一个图标时使用 5 | > eg: 设定菜单列表时,为每个菜单设定一个图标 6 | 7 | 该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装 8 | 9 | 10 | 11 | ### 使用方式 12 | 13 | ```vue 14 | 19 | 20 | 39 | ``` 40 | 41 | 42 | 43 | ### 事件 44 | 45 | 46 | | 名称 | 说明 | 类型 | 默认值 | 47 | | ------ | -------------------------- | ------ | ------ | 48 | | change | 当改变了 `icon` 选中项触发 | String | - | -------------------------------------------------------------------------------- /web/src/components/MultiTab/events.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | export default new Vue() 3 | -------------------------------------------------------------------------------- /web/src/components/MultiTab/index.js: -------------------------------------------------------------------------------- 1 | import './index.less' 2 | 3 | import events from './events' 4 | import MultiTab from './MultiTab' 5 | 6 | const api = { 7 | /** 8 | * open new tab on route fullPath 9 | * @param config 10 | */ 11 | open: function (config) { 12 | events.$emit('open', config) 13 | }, 14 | rename: function (key, name) { 15 | events.$emit('rename', { key: key, name: name }) 16 | }, 17 | /** 18 | * close current page 19 | */ 20 | closeCurrentPage: function () { 21 | this.close() 22 | }, 23 | /** 24 | * close route fullPath tab 25 | * @param config 26 | */ 27 | close: function (config) { 28 | events.$emit('close', config) 29 | } 30 | } 31 | 32 | MultiTab.install = function (Vue) { 33 | if (Vue.prototype.$multiTab) { 34 | return 35 | } 36 | api.instance = events 37 | Vue.prototype.$multiTab = api 38 | Vue.component('multi-tab', MultiTab) 39 | } 40 | 41 | export default MultiTab 42 | -------------------------------------------------------------------------------- /web/src/components/MultiTab/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab"; 4 | @multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper"; 5 | 6 | /* 7 | .topmenu .@{multi-tab-prefix-cls} { 8 | max-width: 1200px; 9 | margin: -23px auto 24px auto; 10 | } 11 | */ 12 | .@{multi-tab-prefix-cls} { 13 | position: absolute; 14 | left:0; 15 | top:64px; 16 | width:100%; 17 | background: #fff; 18 | border-bottom: 1px solid #d8dce5; 19 | box-shadow: 0 1px 3px 0 rgba(0,0,0,.12), 0 0 3px 0 rgba(0,0,0,.04); 20 | } 21 | 22 | // .topmenu .@{multi-tab-wrapper-prefix-cls} { 23 | // max-width: 1200px; 24 | // margin: 0 auto; 25 | // } 26 | 27 | .topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} { 28 | max-width: 100%; 29 | margin: 0 auto; 30 | } 31 | -------------------------------------------------------------------------------- /web/src/components/NProgress/nprogress.less: -------------------------------------------------------------------------------- 1 | @import url('../index.less'); 2 | 3 | /* Make clicks pass-through */ 4 | #nprogress { 5 | pointer-events: none; 6 | } 7 | 8 | #nprogress .bar { 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | z-index: 1031; 13 | width: 100%; 14 | height: 2px; 15 | background: @primary-color; 16 | } 17 | 18 | /* Fancy blur effect */ 19 | #nprogress .peg { 20 | position: absolute; 21 | right: 0; 22 | display: block; 23 | width: 100px; 24 | height: 100%; 25 | opacity: 1; 26 | transform: rotate(3deg) translate(0, -4px); 27 | transform: rotate(3deg) translate(0, -4px); 28 | transform: rotate(3deg) translate(0, -4px); 29 | box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color; 30 | } 31 | 32 | /* Remove these to get rid of the spinner */ 33 | #nprogress .spinner { 34 | position: fixed; 35 | top: 15px; 36 | right: 15px; 37 | z-index: 1031; 38 | display: block; 39 | } 40 | 41 | #nprogress .spinner-icon { 42 | width: 18px; 43 | height: 18px; 44 | box-sizing: border-box; 45 | border: solid 2px transparent; 46 | border-top-color: @primary-color; 47 | border-left-color: @primary-color; 48 | border-radius: 50%; 49 | animation: nprogress-spinner 400ms linear infinite; 50 | animation: nprogress-spinner 400ms linear infinite; 51 | } 52 | 53 | .nprogress-custom-parent { 54 | position: relative; 55 | overflow: hidden; 56 | } 57 | 58 | .nprogress-custom-parent #nprogress .spinner, 59 | .nprogress-custom-parent #nprogress .bar { 60 | position: absolute; 61 | } 62 | 63 | @keyframes nprogress-spinner { 64 | 0% { transform: rotate(0deg); } 65 | 100% { transform: rotate(360deg); } 66 | } 67 | @keyframes nprogress-spinner { 68 | 0% { transform: rotate(0deg); } 69 | 100% { transform: rotate(360deg); } 70 | } 71 | -------------------------------------------------------------------------------- /web/src/components/SelectLang/index.jsx: -------------------------------------------------------------------------------- 1 | import './index.less' 2 | 3 | import { Dropdown, Icon, Menu } from 'ant-design-vue' 4 | 5 | import localIcon from '@/core/icons' 6 | import { i18nRender } from '@/locales' 7 | import i18nMixin from '@/store/i18n-mixin' 8 | 9 | const locales = ['zh-CN', 'en-US', 'ja-JP', 'zh-TW'] 10 | const languageLabels = { 11 | 'zh-CN': '简体中文', 12 | 'en-US': 'English', 13 | 'ja-JP': '日本语', 14 | 'zh-TW': '繁體中文' 15 | } 16 | // eslint-disable-next-line 17 | const languageIcons = { 18 | 'zh-CN': '🇨🇳', 19 | 'en-US': '🇺🇸', 20 | 'ja-JP': '🇯🇵', 21 | 'zh-TW': '🇭🇰' 22 | } 23 | 24 | const SelectLang = { 25 | props: { 26 | prefixCls: { 27 | type: String, 28 | default: 'ant-pro-drop-down' 29 | } 30 | }, 31 | name: 'SelectLang', 32 | mixins: [i18nMixin], 33 | render () { 34 | const { prefixCls } = this 35 | const changeLang = ({ key }) => { 36 | this.setLang(key) 37 | } 38 | const langMenu = ( 39 | 40 | {locales.map(locale => ( 41 | 42 | 43 | {languageIcons[locale]} 44 | {' '} 45 | {languageLabels[locale]} 46 | 47 | ))} 48 | 49 | ) 50 | return ( 51 | 52 | 53 | 54 | 55 | 56 | ) 57 | } 58 | } 59 | 60 | export default SelectLang 61 | -------------------------------------------------------------------------------- /web/src/components/SelectLang/index.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default'; 2 | 3 | @header-menu-prefix-cls: ~'@{ant-prefix}-pro-header-menu'; 4 | @header-drop-down-prefix-cls: ~'@{ant-prefix}-pro-drop-down'; 5 | 6 | .@{header-menu-prefix-cls} { 7 | .anticon { 8 | margin-right: 8px; 9 | } 10 | 11 | .ant-dropdown-menu-item { 12 | min-width: 160px; 13 | } 14 | } 15 | 16 | .@{header-drop-down-prefix-cls} { 17 | line-height: @layout-header-height; 18 | vertical-align: top; 19 | cursor: pointer; 20 | 21 | > i { 22 | font-size: 20px !important; 23 | transform: none !important; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/src/components/Trend/Trend.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /web/src/components/Trend/index.js: -------------------------------------------------------------------------------- 1 | import Trend from './Trend.vue' 2 | 3 | export default Trend 4 | -------------------------------------------------------------------------------- /web/src/components/Trend/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @trend-prefix-cls: ~"@{ant-pro-prefix}-trend"; 4 | 5 | .@{trend-prefix-cls} { 6 | display: inline-block; 7 | font-size: @font-size-base; 8 | line-height: 22px; 9 | 10 | .up, 11 | .down { 12 | position: relative; 13 | top: 1px; 14 | margin-left: 4px; 15 | 16 | i { 17 | font-size: 12px; 18 | transform: scale(.83); 19 | } 20 | } 21 | 22 | .item-text { 23 | display: inline-block; 24 | margin-left: 8px; 25 | color: rgb(0 0 0 / 85%); 26 | } 27 | 28 | .up { 29 | color: @red-6; 30 | } 31 | 32 | .down { 33 | top: -1px; 34 | color: @green-6; 35 | } 36 | 37 | &.reverse-color .up { 38 | color: @green-6; 39 | } 40 | 41 | &.reverse-color .down { 42 | color: @red-6; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /web/src/components/Trend/index.md: -------------------------------------------------------------------------------- 1 | # Trend 趋势标记 2 | 3 | 趋势符号,标记上升和下降趋势。通常用绿色代表“好”,红色代表“不好”,股票涨跌场景除外。 4 | 5 | 6 | 7 | 引用方式: 8 | 9 | ```javascript 10 | import Trend from '@/components/Trend' 11 | 12 | export default { 13 | components: { 14 | Trend 15 | } 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 22 | 23 | ```html 24 | 5% 25 | ``` 26 | 或 27 | ```html 28 | 29 | 工资 30 | 5% 31 | 32 | ``` 33 | 或 34 | ```html 35 | 5% 36 | ``` 37 | 38 | 39 | ## API 40 | 41 | | 参数 | 说明 | 类型 | 默认值 | 42 | |----------|------------------------------------------|-------------|-------| 43 | | flag | 上升下降标识:`up|down` | string | - | 44 | | reverseColor | 颜色反转 | Boolean | false | 45 | 46 | -------------------------------------------------------------------------------- /web/src/components/index.js: -------------------------------------------------------------------------------- 1 | import AvatarList from '@/components/AvatarList' 2 | import ChartCard from '@/components/Charts/ChartCard' 3 | import MiniArea from '@/components/Charts/MiniArea' 4 | import MiniBar from '@/components/Charts/MiniBar' 5 | import MiniProgress from '@/components/Charts/MiniProgress' 6 | import MultiTab from '@/components/MultiTab' 7 | import STable from '@/components/Table' 8 | import Trend from '@/components/Trend' 9 | 10 | export { AvatarList, ChartCard, MiniArea, MiniBar, MiniProgress, MultiTab, STable, Trend } 11 | -------------------------------------------------------------------------------- /web/src/components/index.less: -------------------------------------------------------------------------------- 1 | @import "~ant-design-vue/lib/style/index"; 2 | 3 | // The prefix to use on all css classes from ant-pro. 4 | @ant-pro-prefix : ant-pro; 5 | @ant-global-sider-zindex : 106; 6 | @ant-global-header-zindex : 105; -------------------------------------------------------------------------------- /web/src/config/defaultSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目默认配置项 3 | * primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage 4 | * navTheme - sidebar theme ['dark', 'light'] 两种主题 5 | * colorWeak - 色盲模式 6 | * layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局 7 | * fixedHeader - 固定 Header : boolean 8 | * fixSiderbar - 固定左侧菜单栏 : boolean 9 | * contentWidth - 内容区布局: 流式 | 固定 10 | * 11 | * storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage) 12 | * 13 | */ 14 | 15 | export default { 16 | navTheme: 'light', // theme for nav menu 17 | primaryColor: '#1890FF', // '#F5222D', // primary color of ant design 18 | layout: 'sidemenu', // nav menu position: `sidemenu` or `topmenu` 19 | contentWidth: 'Fluid', // layout of content: `Fluid` or `Fixed`, only works when layout is topmenu 20 | fixedHeader: true, // sticky header 21 | fixSiderbar: true, // sticky siderbar 22 | colorWeak: false, 23 | multiTab: true, 24 | menu: { 25 | locale: true 26 | }, 27 | title: 'Vue2 Admin', 28 | pwa: false, 29 | iconfontUrl: '', 30 | production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true' 31 | } 32 | -------------------------------------------------------------------------------- /web/src/config/router.config.js: -------------------------------------------------------------------------------- 1 | import { BasicLayout, UserLayout } from '@/layouts' 2 | 3 | const RouteView = { 4 | name: 'RouteView', 5 | render: (h) => h('router-view') 6 | } 7 | 8 | // 前端路由表 9 | export const constantRouterComponents = { 10 | // 基础页面 layout 必须引入 11 | BasicLayout: BasicLayout, 12 | RouteView: RouteView 13 | } 14 | 15 | export const asyncRouterMap = [] 16 | 17 | /** 18 | * 基础路由 19 | * @type { *[] } 20 | */ 21 | export const constantRouterMap = [ 22 | { 23 | path: '/user', 24 | component: UserLayout, 25 | redirect: '/user/login', 26 | hidden: true, 27 | children: [ 28 | { 29 | path: 'login', 30 | name: 'login', 31 | component: () => import(/* webpackChunkName: "user" */ '@/views/user/Login') 32 | }, 33 | { 34 | path: 'recover', 35 | name: 'recover', 36 | component: undefined 37 | } 38 | ] 39 | }, 40 | 41 | { 42 | path: '/404', 43 | component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404') 44 | } 45 | ] 46 | -------------------------------------------------------------------------------- /web/src/constant/action.js: -------------------------------------------------------------------------------- 1 | import { i18nRender } from '@/locales' 2 | 3 | import { I18nGlobal } from './i18n' 4 | /** 5 | * @description: 按钮权限 6 | */ 7 | export const ActionMap = { 8 | add: 'add', 9 | edit: 'edit', 10 | delete: 'delete', 11 | search: 'search', 12 | batchDelete: 'batchDelete' 13 | } 14 | 15 | /** 16 | * @description: 按钮权限列项 17 | */ 18 | export const ActionOptions = [ 19 | { value: ActionMap.add, label: i18nRender(I18nGlobal.Add) }, 20 | { value: ActionMap.edit, label: i18nRender(I18nGlobal.Edit) }, 21 | { value: ActionMap.delete, label: i18nRender(I18nGlobal.Delete) }, 22 | { value: ActionMap.search, label: i18nRender(I18nGlobal.Search) }, 23 | { value: ActionMap.batchDelete, label: i18nRender(I18nGlobal.BatchDelete) } 24 | ] 25 | -------------------------------------------------------------------------------- /web/src/constant/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 请求状态码 3 | */ 4 | export const RequestCode = { 5 | NoSuccess: -1, // 表示请求成功,但操作未成功 6 | Success: 200, // 表示请求成功 7 | BadRequest: 400, // 表示客户端发送的请求有错误 8 | Unauthorized: 401, // 表示客户端未提供身份验证凭据或身份验证凭据不正确 9 | NotFound: 404, // 表示服务器无法找到请求的资源 10 | Error: 500 // 表示服务器内部错误 11 | } 12 | 13 | /** 14 | * @description: 公共文案 15 | */ 16 | export const CommonText = { 17 | Null: '--' 18 | } 19 | 20 | /** 21 | * @description: 布尔 22 | */ 23 | export const Flag = { 24 | True: true, 25 | False: false 26 | } 27 | 28 | /** 29 | * @description: 状态 30 | */ 31 | export const Status = { 32 | Active: 'ACTIVE', 33 | Inactive: 'INACTIVE' 34 | } 35 | 36 | /** 37 | * @description: 性别 38 | */ 39 | export const Sex = { 40 | Male: 'MALE', 41 | Female: 'FEMALE' 42 | } 43 | 44 | /** @description: 请求方法 */ 45 | export const MethodOptions = [ 46 | { label: 'POST', value: 'POST', key: 'green' }, 47 | { label: 'PUT', value: 'PUT', key: 'orange' }, 48 | { label: 'PATCH', value: 'PATCH', key: 'purple' }, 49 | { label: 'DELETE', value: 'DELETE', key: 'red' } 50 | ] 51 | -------------------------------------------------------------------------------- /web/src/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | import storage from 'store' 2 | 3 | import defaultSettings from '@/config/defaultSettings' 4 | import store from '@/store' 5 | import { 6 | ACCESS_TOKEN, 7 | APP_LANGUAGE, 8 | TOGGLE_COLOR, 9 | TOGGLE_CONTENT_WIDTH, 10 | TOGGLE_FIXED_HEADER, 11 | TOGGLE_FIXED_SIDEBAR, 12 | TOGGLE_HIDE_HEADER, 13 | TOGGLE_LAYOUT, 14 | TOGGLE_MULTI_TAB, 15 | TOGGLE_NAV_THEME, 16 | TOGGLE_WEAK 17 | } from '@/store/mutation-types' 18 | 19 | export default function Initializer() { 20 | store.commit(TOGGLE_LAYOUT, storage.get(TOGGLE_LAYOUT, defaultSettings.layout)) 21 | store.commit(TOGGLE_FIXED_HEADER, storage.get(TOGGLE_FIXED_HEADER, defaultSettings.fixedHeader)) 22 | store.commit(TOGGLE_FIXED_SIDEBAR, storage.get(TOGGLE_FIXED_SIDEBAR, defaultSettings.fixSiderbar)) 23 | store.commit(TOGGLE_CONTENT_WIDTH, storage.get(TOGGLE_CONTENT_WIDTH, defaultSettings.contentWidth)) 24 | store.commit(TOGGLE_HIDE_HEADER, storage.get(TOGGLE_HIDE_HEADER, defaultSettings.autoHideHeader)) 25 | store.commit(TOGGLE_NAV_THEME, storage.get(TOGGLE_NAV_THEME, defaultSettings.navTheme)) 26 | store.commit(TOGGLE_WEAK, storage.get(TOGGLE_WEAK, defaultSettings.colorWeak)) 27 | store.commit(TOGGLE_COLOR, storage.get(TOGGLE_COLOR, defaultSettings.primaryColor)) 28 | store.commit(TOGGLE_MULTI_TAB, storage.get(TOGGLE_MULTI_TAB, defaultSettings.multiTab)) 29 | store.commit('SET_TOKEN', storage.get(ACCESS_TOKEN)) 30 | 31 | store.dispatch('setLang', storage.get(APP_LANGUAGE, 'zh-CN')) 32 | // last step 33 | } 34 | -------------------------------------------------------------------------------- /web/src/core/directives/action.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | /** 4 | * Action 权限指令 5 | * 指令用法: 6 | * - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下: 7 | * 添加用户 8 | * 删除用户 9 | * 修改 10 | * 11 | * - 当前用户没有权限时,组件上使用了该指令则会被隐藏 12 | * - 当后台权限跟 pro 提供的模式不同时,只需要针对这里的权限过滤进行修改即可 13 | * 14 | * @see https://github.com/vueComponent/ant-design-vue-pro/pull/53 15 | */ 16 | const action = { 17 | inserted: function (el, binding, vnode) { 18 | const actionName = binding.arg 19 | const roles = store.getters.roles 20 | const elVal = vnode.context.$route.meta.permission 21 | const permissionId = elVal instanceof String && [elVal] || elVal 22 | roles.permissions.forEach(p => { 23 | if (!permissionId.includes(p.permissionId)) { 24 | return 25 | } 26 | if (p.actionList && !p.actionList.includes(actionName)) { 27 | el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none') 28 | } 29 | }) 30 | } 31 | } 32 | 33 | export default action 34 | -------------------------------------------------------------------------------- /web/src/core/directives/copy.js: -------------------------------------------------------------------------------- 1 | // 思路: 2 | // 1、动态创建 textarea 标签,并设置 readOnly 属性及移出可视区域 3 | // 2、将要复制的值赋给 textarea 标签的 value 属性,并插入到 body 4 | // 3、选中值 textarea 并复制 5 | // 4、将 body 中插入的 textarea 移除 6 | // 5、在第一次调用时绑定事件,在解绑时移除事件 7 | import { message } from 'ant-design-vue' 8 | const copy = { 9 | bind(el, { value }) { 10 | el.$value = value 11 | el.handler = () => { 12 | if (!el.$value) { 13 | // 值为空的时候,给出提示。可根据项目UI仔细设计 14 | message.warning('无复制内容') 15 | return 16 | } 17 | // 动态创建 textarea 标签 18 | const textarea = document.createElement('textarea') 19 | // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域 20 | textarea.readOnly = 'readonly' 21 | textarea.style.position = 'absolute' 22 | textarea.style.left = '-9999px' 23 | // 将要 copy 的值赋给 textarea 标签的 value 属性 24 | textarea.value = el.$value 25 | // 将 textarea 插入到 body 中 26 | document.body.appendChild(textarea) 27 | // 选中值并复制 28 | textarea.select() 29 | const result = document.execCommand('Copy') 30 | if (result) { 31 | message.success('复制成功') // 可根据项目UI仔细设计 32 | } 33 | document.body.removeChild(textarea) 34 | } 35 | // 绑定点击事件,就是所谓的一键 copy 啦 36 | el.addEventListener('click', el.handler) 37 | }, 38 | // 当传进来的值更新的时候触发 39 | componentUpdated(el, { value }) { 40 | el.$value = value 41 | }, 42 | // 指令与元素解绑的时候,移除事件绑定 43 | unbind(el) { 44 | el.removeEventListener('click', el.handler) 45 | } 46 | } 47 | 48 | export default copy 49 | -------------------------------------------------------------------------------- /web/src/core/directives/debounce.js: -------------------------------------------------------------------------------- 1 | // 需求:防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。 2 | // 思路: 3 | // 1、定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。 4 | // 2、将事件绑定在 click 方法上。 5 | const debounce = { 6 | inserted: function (el, binding) { 7 | const { callback, time } = binding.value 8 | let timer 9 | el.addEventListener('click', () => { 10 | if (timer) { 11 | clearTimeout(timer) 12 | } 13 | timer = setTimeout(() => { 14 | callback() 15 | }, time) 16 | }) 17 | } 18 | } 19 | 20 | export default debounce 21 | -------------------------------------------------------------------------------- /web/src/core/directives/draggable.js: -------------------------------------------------------------------------------- 1 | // 思路: 2 | // 1、设置需要拖拽的元素为相对定位,其父元素为绝对定位。 3 | // 2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。 4 | // 3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值 5 | // 4、鼠标松开(onmouseup)时完成一次拖拽 6 | 7 | const dragable = { 8 | bind(el) { 9 | el.style.cssText += ';cursor:move;' 10 | el.style.cssText += ';left:0px;top:0px;' 11 | 12 | // 兼容获取dom元素样式属性 13 | const getStyle = (function () { 14 | if (window.document.currentStyle) { 15 | return (dom, attr) => dom.currentStyle[attr] 16 | } else { 17 | return (dom, attr) => getComputedStyle(dom, false)[attr] 18 | } 19 | })() 20 | 21 | el.onmousedown = (e) => { 22 | // 鼠标按下,计算当前元素距离可视区的距离 23 | const disX = e.clientX - el.offsetLeft 24 | const disY = e.clientY - el.offsetTop 25 | 26 | // 获取到的值带px 正则匹配替换 27 | let styL = getStyle(el, 'left') 28 | // let styT = getStyle(el, 'top') 29 | 30 | if (styL.includes('%')) { 31 | styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100) 32 | // styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100) 33 | } else { 34 | styL = +styL.replace(/\px/g, '') 35 | // styT = +styT.replace(/\px/g, '') 36 | } 37 | 38 | document.onmousemove = function (e) { 39 | // 通过事件委托,计算移动的距离 40 | const left = e.clientX - disX 41 | const top = e.clientY - disY 42 | 43 | // 移动当前元素 44 | el.style.cssText += `;left:${left}px;top:${top}px;` 45 | } 46 | 47 | document.onmouseup = function (e) { 48 | document.onmousemove = null 49 | document.onmouseup = null 50 | } 51 | } 52 | 53 | // 拖拽还原 54 | // el.onmouseup = () => { 55 | // el.style.cssText += `;position: relative;top: 0;left: 0;`; 56 | // }; 57 | }, 58 | unbind(el) { 59 | el.onmousedown = null 60 | } 61 | } 62 | 63 | export default dragable 64 | -------------------------------------------------------------------------------- /web/src/core/directives/emoji.js: -------------------------------------------------------------------------------- 1 | // 需求:根据正则表达式,设计自定义处理表单输入规则的指令,下面以禁止输入表情和特殊字符为例。 2 | const findEle = (parent, type) => { 3 | return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type) 4 | } 5 | 6 | const trigger = (el, type) => { 7 | const e = document.createEvent('HTMLEvents') 8 | e.initEvent(type, true, true) 9 | el.dispatchEvent(e) 10 | } 11 | 12 | const emoji = { 13 | bind: function (el, binding, vnode) { 14 | // 正则规则可根据需求自定义 15 | var regRule = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\s/g 16 | const $inp = findEle(el, 'input') 17 | el.$inp = $inp 18 | $inp.handle = function () { 19 | const val = $inp.value 20 | $inp.value = val.replace(regRule, '') 21 | 22 | trigger($inp, 'input') 23 | } 24 | $inp.addEventListener('keyup', $inp.handle) 25 | }, 26 | unbind: function (el) { 27 | el.$inp.removeEventListener('keyup', el.$inp.handle) 28 | } 29 | } 30 | 31 | export default emoji 32 | -------------------------------------------------------------------------------- /web/src/core/directives/index.js: -------------------------------------------------------------------------------- 1 | // 批量注册Vue指令 2 | import action from './action' 3 | import copy from './copy' 4 | import debounce from './debounce' 5 | import draggable from './draggable' 6 | import emoji from './emoji' 7 | import LazyLoad from './lazyLoad' 8 | import longpress from './longpress' 9 | import permission from './permission' 10 | import throllte from './throllte' 11 | import waterMarker from './waterMarker' 12 | import wave from './wave' 13 | // 自定义指令 14 | const directives = { 15 | action, 16 | copy, 17 | longpress, 18 | debounce, 19 | throllte, 20 | emoji, 21 | LazyLoad, 22 | permission, 23 | waterMarker, 24 | draggable, 25 | wave 26 | } 27 | 28 | export default { 29 | install(Vue) { 30 | Object.keys(directives).forEach((key) => { 31 | Vue.directive(key, directives[key]) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/src/core/directives/longpress.js: -------------------------------------------------------------------------------- 1 | // 需求:实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件 2 | // 思路: 3 | // 1、创建一个计时器, 2 秒后执行函数 4 | // 2、当用户按下按钮时触发 mousedown 事件,启动计时器;用户松开按钮时调用 mouseout 事件。 5 | // 3、如果 mouseup 事件 2 秒内被触发,就清除计时器,当作一个普通的点击事件 6 | // 4、如果计时器没有在 2 秒内清除,则判定为一次长按,可以执行关联的函数。 7 | // 5、在移动端要考虑 touchstart,touchend 事件 8 | const longpress = { 9 | bind: function (el, binding, vNode) { 10 | const { callback, time } = binding.value 11 | if (typeof callback !== 'function') { 12 | throw Error('callback must be a function') 13 | } 14 | // 定义变量 15 | let pressTimer = null 16 | // 创建计时器( 2秒后执行函数 ) 17 | const start = (e) => { 18 | if (e.type === 'click' && e.button !== 0) { 19 | return 20 | } 21 | if (pressTimer === null) { 22 | pressTimer = setTimeout(() => { 23 | handler() 24 | }, time) 25 | } 26 | } 27 | // 取消计时器 28 | const cancel = (e) => { 29 | if (pressTimer !== null) { 30 | clearTimeout(pressTimer) 31 | pressTimer = null 32 | } 33 | } 34 | // 运行函数 35 | const handler = (e) => { 36 | callback(e) 37 | } 38 | // 添加事件监听器 39 | el.addEventListener('mousedown', start) 40 | el.addEventListener('touchstart', start) 41 | // 取消计时器 42 | el.addEventListener('click', cancel) 43 | el.addEventListener('mouseout', cancel) 44 | el.addEventListener('touchend', cancel) 45 | el.addEventListener('touchcancel', cancel) 46 | }, 47 | // 当传进来的值更新的时候触发 48 | componentUpdated(el, { value }) { 49 | el.$value = value 50 | }, 51 | // 指令与元素解绑的时候,移除事件绑定 52 | unbind(el) { 53 | el.removeEventListener('click', el.handler) 54 | } 55 | } 56 | 57 | export default longpress 58 | -------------------------------------------------------------------------------- /web/src/core/directives/permission.js: -------------------------------------------------------------------------------- 1 | // 需求:自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。 2 | // 思路: 3 | // 1、自定义一个权限数组 4 | // 2、判断用户的权限是否在这个数组内,如果是则显示,否则则移除 Dom 5 | function checkArray(key) { 6 | const arr = ['1', '2', '3', '4'] 7 | return arr.includes(key) 8 | } 9 | 10 | const permission = { 11 | inserted: function (el, binding) { 12 | const permission = binding.value // 获取到 v-permission的值 13 | if (permission) { 14 | const hasPermission = checkArray(permission) 15 | if (!hasPermission) { 16 | // 没有权限 移除Dom元素 17 | el.parentNode && el.parentNode.removeChild(el) 18 | } 19 | } 20 | } 21 | } 22 | 23 | // const permission = { 24 | // inserted: function (el, binding) { 25 | // let permission = binding.value // 获取到 v-permission的值 26 | // if (!permission) { 27 | // el.parentNode && el.parentNode.removeChild(el) 28 | // } 29 | // }, 30 | // } 31 | 32 | export default permission 33 | -------------------------------------------------------------------------------- /web/src/core/directives/throllte.js: -------------------------------------------------------------------------------- 1 | // 需求:一段时间内首次触发时立即执行,此时间段内再次触发不再执行。 2 | // 思路: 3 | // 1、记录上次触发事件,每次触发与指定的时间段比较。 4 | // 2、将事件绑定在 click 方法上。 5 | const throllte = { 6 | inserted: function (el, binding) { 7 | const { callback, time } = binding.value 8 | el.addEventListener('click', () => { 9 | const nowTime = new Date().getTime() 10 | if (!el.preTime || nowTime - el.preTime > time) { 11 | el.preTime = nowTime 12 | callback() 13 | } 14 | }) 15 | } 16 | } 17 | export default throllte 18 | -------------------------------------------------------------------------------- /web/src/core/directives/waterMarker.js: -------------------------------------------------------------------------------- 1 | // 思路: 2 | // 1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。 3 | // 2、将其设置为背景图片,从而实现页面或组件水印效果 4 | function addWaterMarker(str, parentNode, font, textColor) { 5 | // 水印文字,父元素,字体,文字颜色 6 | var can = document.createElement('canvas') 7 | parentNode.appendChild(can) 8 | can.width = 200 9 | can.height = 150 10 | can.style.display = 'none' 11 | var cans = can.getContext('2d') 12 | cans.rotate((-20 * Math.PI) / 180) 13 | cans.font = font || '16px Microsoft JhengHei' 14 | cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)' 15 | cans.textAlign = 'left' 16 | cans.textBaseline = 'Middle' 17 | cans.fillText(str, can.width / 10, can.height / 2) 18 | parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')' 19 | } 20 | 21 | const waterMarker = { 22 | bind: function (el, binding) { 23 | addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor) 24 | } 25 | } 26 | 27 | export default waterMarker 28 | -------------------------------------------------------------------------------- /web/src/core/icons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom icon list 3 | * All icons are loaded here for easy management 4 | * @see https://vue.ant.design/components/icon/#Custom-Font-Icon 5 | * 6 | * 自定义图标加载表 7 | * 所有图标均从这里加载,方便管理 8 | */ 9 | import AboutIcon from '@/assets/icons/about.svg?inline' 10 | import AdministrativeIcon from '@/assets/icons/administrative.svg?inline' 11 | import AntdIcon from '@/assets/icons/antd.svg?inline' 12 | import CaptchaIcon from '@/assets/icons/captcha.svg?inline' 13 | import ColorthiefIcon from '@/assets/icons/colorthief.svg?inline' 14 | import DashboardIcon from '@/assets/icons/dashboard.svg?inline' 15 | import DirectiveIcon from '@/assets/icons/directive.svg?inline' 16 | import EyeDropperIcon from '@/assets/icons/eye-dropper.svg?inline' 17 | import FeaturesIcon from '@/assets/icons/features.svg?inline' 18 | import InternationalizationIcon from '@/assets/icons/internationalization.svg?inline' 19 | import LazyloadIcon from '@/assets/icons/lazyload.svg?inline' 20 | import LocaleIcon from '@/assets/icons/locale.svg?inline' 21 | import LogIcon from '@/assets/icons/log.svg?inline' 22 | import NestjsIcon from '@/assets/icons/nestjs.svg?inline' 23 | import OrganizationIcon from '@/assets/icons/organization.svg?inline' 24 | import PickrIcon from '@/assets/icons/pickr.svg?inline' 25 | import PostIcon from '@/assets/icons/post.svg?inline' 26 | import SwiperIcon from '@/assets/icons/swiper.svg?inline' 27 | import ViewerIcon from '@/assets/icons/viewer.svg?inline' 28 | import VueIcon from '@/assets/icons/vue.svg?inline' 29 | import WaterfallIcon from '@/assets/icons/waterfall.svg?inline' 30 | 31 | export default { 32 | DashboardIcon, 33 | InternationalizationIcon, 34 | LocaleIcon, 35 | OrganizationIcon, 36 | PostIcon, 37 | AdministrativeIcon, 38 | VueIcon, 39 | AntdIcon, 40 | NestjsIcon, 41 | LogIcon, 42 | AboutIcon, 43 | FeaturesIcon, 44 | CaptchaIcon, 45 | DirectiveIcon, 46 | LazyloadIcon, 47 | WaterfallIcon, 48 | ViewerIcon, 49 | PickrIcon, 50 | ColorthiefIcon, 51 | EyeDropperIcon, 52 | SwiperIcon 53 | } 54 | -------------------------------------------------------------------------------- /web/src/core/use.js: -------------------------------------------------------------------------------- 1 | import 'ant-design-vue/dist/antd.less' 2 | // import '@/components/use' 3 | import './directives/action' 4 | 5 | // base library 6 | import Antd from 'ant-design-vue' 7 | import Viser from 'viser-vue' 8 | import Vue from 'vue' 9 | // ext library 10 | import VueClipboard from 'vue-clipboard2' 11 | import VueCropper from 'vue-cropper' 12 | 13 | import MultiTab from '@/components/MultiTab' 14 | import PageLoading from '@/components/PageLoading' 15 | 16 | VueClipboard.config.autoSetContainer = true 17 | 18 | Vue.use(Antd) 19 | Vue.use(Viser) 20 | Vue.use(MultiTab) 21 | Vue.use(PageLoading) 22 | Vue.use(VueClipboard) 23 | Vue.use(VueCropper) 24 | 25 | process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] WARNING: Antd now use fulled imported.') 26 | -------------------------------------------------------------------------------- /web/src/layouts/BasicLayout.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default.less'; 2 | 3 | .ant-pro-global-header-index-right { 4 | margin-right: 8px; 5 | 6 | &.ant-pro-global-header-index-dark { 7 | .ant-pro-global-header-index-action { 8 | color: hsl(0deg 0% 100% / 85%); 9 | 10 | &:hover { 11 | background: #1890ff; 12 | } 13 | } 14 | } 15 | 16 | .ant-pro-account-avatar { 17 | .antd-pro-global-header-index-avatar { 18 | margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0; 19 | margin-right: 8px; 20 | color: @primary-color; 21 | vertical-align: top; 22 | background: rgb(255 255 255 / 85%); 23 | } 24 | } 25 | 26 | .menu { 27 | .anticon { 28 | margin-right: 8px; 29 | } 30 | 31 | .ant-dropdown-menu-item { 32 | min-width: 100px; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/src/layouts/BlankLayout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /web/src/layouts/PageView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /web/src/layouts/RouteView.vue: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /web/src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import BasicLayout from './BasicLayout' 2 | import BlankLayout from './BlankLayout' 3 | import PageView from './PageView' 4 | import RouteView from './RouteView' 5 | import UserLayout from './UserLayout' 6 | 7 | export { BasicLayout, BlankLayout, PageView, RouteView, UserLayout } 8 | -------------------------------------------------------------------------------- /web/src/locales/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | 4 | Vue.use(VueI18n) 5 | 6 | export const defaultLang = 'zh-CN' 7 | 8 | const i18n = new VueI18n({ 9 | silentTranslationWarn: true, 10 | locale: defaultLang, 11 | fallbackLocale: defaultLang 12 | }) 13 | 14 | export function setI18nLanguage(lang) { 15 | i18n.locale = lang 16 | document.querySelector('html').setAttribute('lang', lang) 17 | return lang 18 | } 19 | 20 | export function i18nRender(key) { 21 | return i18n.t(`${key}`) 22 | } 23 | 24 | export default i18n 25 | -------------------------------------------------------------------------------- /web/src/main.js: -------------------------------------------------------------------------------- 1 | // with polyfills 2 | import 'core-js/stable' 3 | import 'regenerator-runtime/runtime' 4 | import './core/lazy_use' // use lazy load components 5 | import './permission' // permission control 6 | import './global.less' // global style 7 | import 'zm-tree-org/lib/zm-tree-org.css' 8 | import 'swiper/css/swiper.css' 9 | 10 | import ProLayout, { PageHeaderWrapper } from '@ant-design-vue/pro-layout' 11 | import Vue from 'vue' 12 | import ZmTreeOrg from 'zm-tree-org' 13 | 14 | import themePluginConfig from '../config/themePluginConfig' 15 | import App from './App.vue' 16 | import bootstrap from './core/bootstrap' 17 | import i18n from './locales' 18 | import router from './router' 19 | import store from './store/' 20 | import { VueAxios } from './utils/request' 21 | 22 | Vue.config.productionTip = false 23 | 24 | // mount axios to `Vue.$http` and `this.$http` 25 | Vue.use(VueAxios) 26 | Vue.use(ZmTreeOrg) 27 | // use pro-layout components 28 | Vue.component('pro-layout', ProLayout) 29 | Vue.component('page-container', PageHeaderWrapper) 30 | Vue.component('page-header-wrapper', PageHeaderWrapper) 31 | 32 | window.umi_plugin_ant_themeVar = themePluginConfig.theme 33 | 34 | new Vue({ 35 | router, 36 | store, 37 | i18n, 38 | // init localstorage, vuex, Logo message 39 | created: bootstrap, 40 | render: (h) => h(App) 41 | }).$mount('#app') 42 | -------------------------------------------------------------------------------- /web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import { constantRouterMap } from '@/config/router.config' 5 | 6 | // hack router push callback 7 | const originalPush = Router.prototype.push 8 | Router.prototype.push = function push(location, onResolve, onReject) { 9 | if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject) 10 | return originalPush.call(this, location).catch((err) => err) 11 | } 12 | 13 | Vue.use(Router) 14 | 15 | const createRouter = () => 16 | new Router({ 17 | mode: 'history', 18 | routes: constantRouterMap 19 | }) 20 | 21 | const router = createRouter() 22 | 23 | // 定义一个resetRouter 方法,在退出登录后或token过期后 需要重新登录时,调用即可 24 | export function resetRouter() { 25 | const newRouter = createRouter() 26 | router.matcher = newRouter.matcher 27 | } 28 | 29 | export default router 30 | -------------------------------------------------------------------------------- /web/src/store/app-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const baseMixin = { 4 | computed: { 5 | ...mapState({ 6 | layout: state => state.app.layout, 7 | navTheme: state => state.app.theme, 8 | primaryColor: state => state.app.color, 9 | colorWeak: state => state.app.weak, 10 | fixedHeader: state => state.app.fixedHeader, 11 | fixedSidebar: state => state.app.fixedSidebar, 12 | contentWidth: state => state.app.contentWidth, 13 | autoHideHeader: state => state.app.autoHideHeader, 14 | 15 | isMobile: state => state.app.isMobile, 16 | sideCollapsed: state => state.app.sideCollapsed, 17 | multiTab: state => state.app.multiTab 18 | }), 19 | isTopMenu () { 20 | return this.layout === 'topmenu' 21 | } 22 | }, 23 | methods: { 24 | isSideMenu () { 25 | return !this.isTopMenu 26 | } 27 | } 28 | } 29 | 30 | export { 31 | baseMixin 32 | } 33 | -------------------------------------------------------------------------------- /web/src/store/device-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const deviceMixin = { 4 | computed: { 5 | ...mapState({ 6 | isMobile: state => state.app.isMobile 7 | }) 8 | } 9 | } 10 | 11 | export { deviceMixin } 12 | -------------------------------------------------------------------------------- /web/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | isMobile: (state) => state.app.isMobile, 3 | lang: (state) => state.app.lang, 4 | theme: (state) => state.app.theme, 5 | color: (state) => state.app.color, 6 | token: (state) => state.user.token, 7 | avatar: (state) => state.user.avatar, 8 | nickname: (state) => state.user.name, 9 | roles: (state) => state.user.roles, 10 | userInfo: (state) => state.user.userInfo, 11 | addRouters: (state) => state.permission.addRouters, 12 | multiTab: (state) => state.app.multiTab 13 | } 14 | 15 | export default getters 16 | -------------------------------------------------------------------------------- /web/src/store/i18n-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const i18nMixin = { 4 | computed: { 5 | ...mapState({ 6 | currentLang: state => state.app.lang 7 | }) 8 | }, 9 | methods: { 10 | setLang (lang) { 11 | this.$store.dispatch('setLang', lang) 12 | } 13 | } 14 | } 15 | 16 | export default i18nMixin 17 | -------------------------------------------------------------------------------- /web/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import getters from './getters' 5 | import app from './modules/app' 6 | // dynamic router permission control (Experimental) 7 | // 动态路由模式(api请求后端生成) 8 | import permission from './modules/async-router' 9 | // default router permission control 10 | // 默认路由模式为静态路由 (router.config.js) 11 | // import permission from './modules/static-router' 12 | import user from './modules/user' 13 | 14 | Vue.use(Vuex) 15 | 16 | export default new Vuex.Store({ 17 | modules: { 18 | app, 19 | user, 20 | permission 21 | }, 22 | state: {}, 23 | mutations: {}, 24 | actions: {}, 25 | getters 26 | }) 27 | -------------------------------------------------------------------------------- /web/src/store/modules/async-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 向后端请求用户的菜单,动态生成路由 3 | */ 4 | import { constantRouterMap } from '@/config/router.config' 5 | import { generatorDynamicRouter } from '@/router/generator-routers' 6 | 7 | const permission = { 8 | state: { 9 | routers: constantRouterMap, 10 | addRouters: [] 11 | }, 12 | mutations: { 13 | SET_ROUTERS: (state, routers) => { 14 | state.addRouters = routers 15 | state.routers = constantRouterMap.concat(routers) 16 | } 17 | }, 18 | actions: { 19 | GenerateRoutes({ commit }, data) { 20 | return new Promise((resolve, reject) => { 21 | const { token } = data 22 | generatorDynamicRouter(token) 23 | .then((routers) => { 24 | commit('SET_ROUTERS', routers) 25 | resolve() 26 | }) 27 | .catch((e) => { 28 | reject(e) 29 | }) 30 | }) 31 | } 32 | } 33 | } 34 | 35 | export default permission 36 | -------------------------------------------------------------------------------- /web/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'Access-Token' 2 | 3 | export const SIDEBAR_TYPE = 'sidebar_type' 4 | export const TOGGLE_MOBILE_TYPE = 'is_mobile' 5 | export const TOGGLE_NAV_THEME = 'nav_theme' 6 | export const TOGGLE_LAYOUT = 'layout' 7 | export const TOGGLE_FIXED_HEADER = 'fixed_header' 8 | export const TOGGLE_FIXED_SIDEBAR = 'fixed_sidebar' 9 | export const TOGGLE_CONTENT_WIDTH = 'content_width' 10 | export const TOGGLE_HIDE_HEADER = 'auto_hide_header' 11 | export const TOGGLE_COLOR = 'color' 12 | export const TOGGLE_WEAK = 'weak' 13 | export const TOGGLE_MULTI_TAB = 'multi_tab' 14 | export const APP_LANGUAGE = 'app_language' 15 | export const USER_INFO = 'userInfo' 16 | 17 | export const CONTENT_WIDTH_TYPE = { 18 | Fluid: 'Fluid', 19 | Fixed: 'Fixed' 20 | } 21 | 22 | export const NAV_THEME = { 23 | LIGHT: 'light', 24 | DARK: 'dark' 25 | } 26 | -------------------------------------------------------------------------------- /web/src/utils/axios.js: -------------------------------------------------------------------------------- 1 | const VueAxios = { 2 | vm: {}, 3 | // eslint-disable-next-line no-unused-vars 4 | install (Vue, instance) { 5 | if (this.installed) { 6 | return 7 | } 8 | this.installed = true 9 | 10 | if (!instance) { 11 | // eslint-disable-next-line no-console 12 | console.error('You have to install axios') 13 | return 14 | } 15 | 16 | Vue.axios = instance 17 | 18 | Object.defineProperties(Vue.prototype, { 19 | axios: { 20 | get: function get () { 21 | return instance 22 | } 23 | }, 24 | $http: { 25 | get: function get () { 26 | return instance 27 | } 28 | } 29 | }) 30 | } 31 | } 32 | 33 | export { 34 | VueAxios 35 | } 36 | -------------------------------------------------------------------------------- /web/src/utils/bus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | const bus = new Vue() 3 | export default bus 4 | -------------------------------------------------------------------------------- /web/src/utils/domUtil.js: -------------------------------------------------------------------------------- 1 | import config from '@/config/defaultSettings' 2 | 3 | export const setDocumentTitle = function (title) { 4 | document.title = title 5 | const ua = navigator.userAgent 6 | // eslint-disable-next-line 7 | const regex = /\bMicroMessenger\/([\d\.]+)/ 8 | if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) { 9 | const i = document.createElement('iframe') 10 | i.src = '/favicon.ico' 11 | i.style.display = 'none' 12 | i.onload = function () { 13 | setTimeout(function () { 14 | i.remove() 15 | }, 9) 16 | } 17 | document.body.appendChild(i) 18 | } 19 | } 20 | 21 | export const domTitle = config.title 22 | -------------------------------------------------------------------------------- /web/src/utils/index.js: -------------------------------------------------------------------------------- 1 | import { random, sampleSize } from 'lodash-es' 2 | /** @description: 生成随机颜色 */ 3 | export const randomColor = (min = 0, max = 255) => { 4 | // 生成三个介于 0 到 255 之间的随机数作为 RGB 的值 5 | const r = random(min, max) 6 | const g = random(min, max) 7 | const b = random(min, max) 8 | return `rgb(${r},${g},${b})` 9 | } 10 | 11 | /** @description: 验证码字符 */ 12 | export const codeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' 13 | 14 | /** 15 | * @param {number} size 随机获取几张图片数组,默认获取随机一张图片 16 | * @description: 获取 /assets/img 路径下随机图片 17 | */ 18 | export const getRandomImg = (size = 1) => { 19 | const requireContext = require.context('@/assets/img', false, /\.jpg$/) 20 | // 匹配该目录下所有的图片 21 | const images = [] 22 | for (let i = 1; i <= 20; i += 1) { 23 | const imgPath = `./${i}.jpg` 24 | const img = requireContext(imgPath).default || requireContext(imgPath) // 尝试两种方式 25 | if (img) { 26 | images.push(img) 27 | } 28 | } 29 | // 获取图片集合 30 | const result = sampleSize(images, size) 31 | return result.length === 1 ? result[0] : result 32 | } 33 | 34 | /** 35 | * @param {number} count 36 | * @description: 生成随机的汉字数组 37 | */ 38 | export const generateRandomHanziArray = (count = 1) => { 39 | const minCode = 0x4e00 // 汉字 Unicode 范围的最小值 40 | const maxCode = 0x9fff // 汉字 Unicode 范围的最大值 41 | 42 | const hanziArray = [] 43 | for (let i = 0; i < count; i += 1) { 44 | const randomCode = random(minCode, maxCode) 45 | hanziArray.push(String.fromCodePoint(randomCode)) 46 | } 47 | 48 | return hanziArray 49 | } 50 | -------------------------------------------------------------------------------- /web/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /web/src/views/about/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 54 | -------------------------------------------------------------------------------- /web/src/views/administrative/organization/components/HeaderSearch.vue: -------------------------------------------------------------------------------- 1 | 29 | 53 | -------------------------------------------------------------------------------- /web/src/views/dashboard/components/BlogLog.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 59 | -------------------------------------------------------------------------------- /web/src/views/dashboard/components/OperationEffect.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 57 | -------------------------------------------------------------------------------- /web/src/views/dashboard/components/PageView.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 46 | -------------------------------------------------------------------------------- /web/src/views/dashboard/components/PaymentNumber.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 46 | -------------------------------------------------------------------------------- /web/src/views/dashboard/components/ProjectNews.vue: -------------------------------------------------------------------------------- 1 | 20 | 49 | -------------------------------------------------------------------------------- /web/src/views/dashboard/components/SaleCard.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 59 | -------------------------------------------------------------------------------- /web/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 60 | -------------------------------------------------------------------------------- /web/src/views/exception/403.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /web/src/views/exception/404.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /web/src/views/exception/500.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /web/src/views/features/draggable/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 49 | 71 | -------------------------------------------------------------------------------- /web/src/views/features/eye-dropper/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 44 | -------------------------------------------------------------------------------- /web/src/views/features/lazyload/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 52 | -------------------------------------------------------------------------------- /web/src/views/features/pickr/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 52 | -------------------------------------------------------------------------------- /web/src/views/features/print/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 45 | -------------------------------------------------------------------------------- /web/src/views/features/swiper/components/coverflow-swiper.vue: -------------------------------------------------------------------------------- 1 | 9 | 45 | 56 | -------------------------------------------------------------------------------- /web/src/views/features/swiper/components/cube-swiper.vue: -------------------------------------------------------------------------------- 1 | 9 | 42 | 53 | -------------------------------------------------------------------------------- /web/src/views/features/swiper/components/fade-swiper.vue: -------------------------------------------------------------------------------- 1 | 11 | 46 | 69 | -------------------------------------------------------------------------------- /web/src/views/features/swiper/components/flip-swiper.vue: -------------------------------------------------------------------------------- 1 | 11 | 42 | 53 | -------------------------------------------------------------------------------- /web/src/views/features/viewer/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 28 | 42 | -------------------------------------------------------------------------------- /web/src/views/features/vue-office/components/vue-office-docx.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 54 | -------------------------------------------------------------------------------- /web/src/views/features/vue-office/components/vue-office-pdf.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 50 | -------------------------------------------------------------------------------- /web/src/views/features/vue-office/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 39 | -------------------------------------------------------------------------------- /web/src/views/system-manage/menu-manage/components/HeaderSearch.vue: -------------------------------------------------------------------------------- 1 | 29 | 53 | -------------------------------------------------------------------------------- /web/src/views/system-manage/role-manage/components/FormDrawer.vue: -------------------------------------------------------------------------------- 1 | 37 | 58 | -------------------------------------------------------------------------------- /web/src/views/user-center/components/PersonSetting.vue: -------------------------------------------------------------------------------- 1 | 16 | 45 | -------------------------------------------------------------------------------- /web/src/views/user-center/components/constant.js: -------------------------------------------------------------------------------- 1 | export const TabsKey = { 2 | basicSetting: 'basicSetting', 3 | securitySetting: 'securitySetting', 4 | changePassword: 'changePassword' 5 | } 6 | -------------------------------------------------------------------------------- /web/src/views/user-center/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 24 | --------------------------------------------------------------------------------