├── public ├── robots.txt ├── favicon.ico ├── img │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── msapplication-icon-144x144.png │ │ ├── android-chrome-maskable-192x192.png │ │ └── safari-pinned-tab.svg ├── tinymce │ ├── skins │ │ ├── fonts │ │ │ └── tinymce-mobile.woff │ │ └── content.mobile.min.css │ └── README.md └── index.html ├── .browserslistrc ├── .eslintignore ├── src ├── @types │ ├── js-module.d.ts │ ├── index.d.ts │ └── vue-proptery.d.ts ├── assets │ ├── 401-images │ │ └── 401.gif │ ├── 404-images │ │ ├── 404.png │ │ └── 404-cloud.png │ ├── images │ │ ├── home │ │ │ ├── logo.png │ │ │ ├── user.png │ │ │ ├── messages.png │ │ │ ├── visits.png │ │ │ ├── purchases.png │ │ │ └── shoppings.png │ │ └── login │ │ │ ├── night.mp4 │ │ │ └── video-cover.jpeg │ ├── custom-theme │ │ └── fonts │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ └── iconfont │ │ └── iconfont.css ├── directives │ ├── index.ts │ ├── permission │ │ └── index.ts │ ├── waves │ │ ├── waves.css │ │ └── index.ts │ └── clipboard │ │ └── index.ts ├── styles │ ├── _mixins.scss │ ├── element-variables.scss.d.ts │ ├── _variables.scss.d.ts │ ├── _svgicon.scss │ ├── element-variables.scss │ ├── _variables.scss │ └── _transition.scss ├── components │ ├── header_search │ │ └── Index.vue │ ├── tinymce │ │ └── config.ts │ ├── hamburger │ │ └── Index.vue │ ├── right_panel │ │ └── Index.vue │ ├── charts │ │ └── mixins │ │ │ └── resize.ts │ ├── screenfull │ │ └── Index.vue │ ├── dropzone │ │ └── Index.vue │ ├── lang_select │ │ └── Index.vue │ └── github-corner │ │ └── Index.vue ├── model │ ├── articleList.ts │ ├── rootObject.ts │ ├── routesModel.ts │ ├── articleModel.ts │ ├── getRolesModel.ts │ └── userModel.ts ├── config │ ├── default │ │ ├── whitelist.ts │ │ ├── index.ts │ │ ├── vue.custom.config.js │ │ ├── layout.ts │ │ ├── net.config.ts │ │ └── theme.config.ts │ ├── index.ts │ └── customConfig.ts ├── constant │ ├── network.ts │ ├── headers.ts │ ├── key.ts │ └── settings.ts ├── store │ ├── modules │ │ ├── permission │ │ │ ├── mutation-types.ts │ │ │ ├── action-types.ts │ │ │ ├── state.ts │ │ │ ├── mutations.ts │ │ │ ├── index.ts │ │ │ └── actions.ts │ │ ├── settings │ │ │ ├── mutation-types.ts │ │ │ ├── action-types.ts │ │ │ ├── state.ts │ │ │ ├── actions.ts │ │ │ ├── mutations.ts │ │ │ └── index.ts │ │ ├── app │ │ │ ├── mutation-types.ts │ │ │ ├── action-types.ts │ │ │ ├── state.ts │ │ │ ├── index.ts │ │ │ ├── mutations.ts │ │ │ └── actions.ts │ │ ├── user │ │ │ ├── mutation-types.ts │ │ │ ├── action-types.ts │ │ │ ├── state.ts │ │ │ ├── index.ts │ │ │ └── mutations.ts │ │ └── tagsview │ │ │ ├── state.ts │ │ │ ├── mutation-types.ts │ │ │ ├── action-types.ts │ │ │ └── index.ts │ └── index.ts ├── plugins │ ├── i18n.ts │ ├── index.ts │ └── element.ts ├── views │ ├── example │ │ ├── components │ │ │ ├── Dropdown │ │ │ │ ├── index.ts │ │ │ │ ├── SourceUrl.vue │ │ │ │ ├── Comment.vue │ │ │ │ └── Platform.vue │ │ │ └── Warning.vue │ │ ├── Create.vue │ │ └── Edit.vue │ ├── nested │ │ ├── menu1 │ │ │ ├── menu1-1 │ │ │ │ └── Index.vue │ │ │ ├── menu1-3 │ │ │ │ └── Index.vue │ │ │ ├── menu1-2 │ │ │ │ ├── menu1-2-1 │ │ │ │ │ └── Index.vue │ │ │ │ ├── menu1-2-2 │ │ │ │ │ └── Index.vue │ │ │ │ └── Index.vue │ │ │ └── Index.vue │ │ └── menu2 │ │ │ └── Index.vue │ ├── user-manager │ │ └── login │ │ │ └── model │ │ │ └── loginModel.ts │ ├── permission │ │ ├── role │ │ │ └── editRole.ts │ │ ├── Page.vue │ │ └── components │ │ │ └── SwitchRoles.vue │ ├── redirect │ │ └── Index.vue │ ├── charts │ │ ├── BarChartDemo.vue │ │ ├── LineChartDemo.vue │ │ └── MixedChatDemo.vue │ ├── pdf │ │ └── Index.vue │ ├── table │ │ └── dynamic-table │ │ │ ├── Index.vue │ │ │ └── components │ │ │ ├── UnfixedHeaderTable.vue │ │ │ └── FixedHeaderTable.vue │ ├── dashboard │ │ ├── Index.vue │ │ └── admin │ │ │ └── components │ │ │ └── UpdateTimeline.vue │ ├── excel │ │ ├── components │ │ │ ├── AutoWidthOption.vue │ │ │ ├── FilenameOption.vue │ │ │ └── BookTypeOption.vue │ │ └── UploadExcel.vue │ ├── guide │ │ ├── steps.ts │ │ └── Index.vue │ ├── profile │ │ └── components │ │ │ ├── Timeline.vue │ │ │ └── Account.vue │ ├── components-demo │ │ ├── DraggableListDemo.vue │ │ ├── DropzoneDemo.vue │ │ ├── DraggableSelectDemo.vue │ │ ├── AvatarUploadDemo.vue │ │ ├── DraggableKanBanDemo.vue │ │ └── TinymceModulesDemo.vue │ └── clipboard │ │ └── Index.vue ├── utils │ ├── loading.ts │ ├── https.ts │ ├── ase.ts │ ├── zip.ts │ ├── permission.ts │ ├── clipboard.ts │ ├── validate.ts │ ├── cookies.ts │ ├── scroll_to.ts │ └── storage.ts ├── layout │ ├── components │ │ ├── index.ts │ │ ├── side_bar │ │ │ └── SidebarItemLink.vue │ │ └── AppMain.vue │ └── resize.ts ├── router │ ├── constantModules │ │ └── userManager.ts │ ├── permissionModules │ │ ├── tab.ts │ │ ├── guide.ts │ │ ├── theme.ts │ │ ├── clipboard.ts │ │ ├── profile.ts │ │ ├── pdf.ts │ │ ├── errorPage.ts │ │ ├── zip.ts │ │ ├── example.ts │ │ ├── excel.ts │ │ ├── charts.ts │ │ ├── table.ts │ │ ├── permission.ts │ │ └── nested.ts │ └── index.ts ├── shims-vue.d.ts ├── App.vue ├── main.ts ├── apis │ ├── user.ts │ ├── roles.ts │ └── articles.ts └── locales │ └── index.ts ├── .env.dev.serve ├── .env.prod.serve ├── .env.test.serve ├── IMAGE └── QQ.JPG ├── .env.dev.build ├── .env.test.build ├── .travis.yml ├── mock ├── constant.ts ├── utils │ └── logger.ts ├── controller │ ├── type.d.ts │ ├── role.ts │ └── user.ts ├── tsconfig.json ├── type.d.ts ├── middleware │ └── resultHandler.ts ├── mockdb │ ├── role.ts │ └── userList.ts ├── router.ts ├── requestDecorator.ts └── mock.ts ├── .env.prod.build ├── .editorconfig ├── jest.config.js ├── tests ├── e2e │ ├── .eslintrc.js │ ├── specs │ │ └── test.js │ ├── support │ │ ├── index.js │ │ └── commands.js │ └── plugins │ │ └── index.js └── unit │ └── example.spec.ts ├── docker ├── Dockerfile └── default.conf ├── k8smanifests ├── svc.yaml ├── ingress.yaml └── deploy.yaml ├── .gitignore ├── .github └── workflows │ └── deploy.yml ├── babel.config.js ├── tsconfig.json ├── LICENSE ├── vue.config.js └── .eslintrc.js /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/config 2 | src/utils/bideo.js 3 | vue.config.js 4 | mock/ -------------------------------------------------------------------------------- /src/@types/js-module.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'html-docx-js/dist/html-docx' 2 | -------------------------------------------------------------------------------- /.env.dev.serve: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_BASE_API = 'http://localhost:3300' -------------------------------------------------------------------------------- /.env.prod.serve: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_BASE_API = 'http://localhost:3300' -------------------------------------------------------------------------------- /.env.test.serve: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_BASE_API = 'http://localhost:3300' -------------------------------------------------------------------------------- /IMAGE/QQ.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/IMAGE/QQ.JPG -------------------------------------------------------------------------------- /.env.dev.build: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_BASE_API = 'https://admin-tmpl-mock.rencaiyoujia.cn' 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.env.test.build: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_BASE_API = 'https://admin-tmpl-mock-test.rencaiyoujia.com' 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 10 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /mock/constant.ts: -------------------------------------------------------------------------------- 1 | export const BASE_PATH_MAP = Symbol('path_map'); 2 | export const ROUTER_MAP = Symbol('route_map'); -------------------------------------------------------------------------------- /.env.prod.build: -------------------------------------------------------------------------------- 1 | 2 | NODE_ENV=production 3 | VUE_APP_BASE_API = 'https://admin-tmpl-mock-test.rencaiyoujia.cn' 4 | 5 | -------------------------------------------------------------------------------- /src/assets/401-images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/401-images/401.gif -------------------------------------------------------------------------------- /src/assets/404-images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/404-images/404.png -------------------------------------------------------------------------------- /src/assets/images/home/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/images/home/logo.png -------------------------------------------------------------------------------- /src/assets/images/home/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/images/home/user.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/404-images/404-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/404-images/404-cloud.png -------------------------------------------------------------------------------- /src/assets/images/home/messages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/images/home/messages.png -------------------------------------------------------------------------------- /src/assets/images/home/visits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/images/home/visits.png -------------------------------------------------------------------------------- /src/assets/images/login/night.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/images/login/night.mp4 -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/images/home/purchases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/images/home/purchases.png -------------------------------------------------------------------------------- /src/assets/images/home/shoppings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/images/home/shoppings.png -------------------------------------------------------------------------------- /src/assets/images/login/video-cover.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/images/login/video-cover.jpeg -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | export * from './clipboard' 2 | export * from './el-draggable-dialog' 3 | export * from './permission' 4 | export * from './waves' 5 | -------------------------------------------------------------------------------- /src/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | /* Mixins */ 2 | @mixin clearfix { 3 | &:after { 4 | content: ""; 5 | display: table; 6 | clear: both; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/tinymce/skins/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/tinymce/skins/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /src/assets/custom-theme/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/custom-theme/fonts/element-icons.ttf -------------------------------------------------------------------------------- /src/assets/custom-theme/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/src/assets/custom-theme/fonts/element-icons.woff -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 1em; height: 1em; 3 | vertical-align: -0.15em; 4 | fill: currentColor; 5 | overflow: hidden; 6 | } -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainManGO/vue3-composition-admin/HEAD/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /mock/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import log4js from 'log4js' 2 | 3 | const log = log4js.getLogger('default'); 4 | export const errlog = log4js.getLogger('err'); 5 | export default log 6 | -------------------------------------------------------------------------------- /src/styles/element-variables.scss.d.ts: -------------------------------------------------------------------------------- 1 | export interface ScssVariables { 2 | theme: string 3 | } 4 | 5 | export const variables: ScssVariables 6 | 7 | export default variables 8 | -------------------------------------------------------------------------------- /src/components/header_search/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'cypress' 4 | ], 5 | env: { 6 | mocha: true, 7 | 'cypress/globals': true 8 | }, 9 | rules: { 10 | strict: 'off' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.14-alpine 2 | 3 | ARG PROJECT_DIR="dist" 4 | 5 | COPY --chown=nginx:nginx ${PROJECT_DIR} /usr/share/nginx/html/release 6 | COPY default.conf /etc/nginx/conf.d/ 7 | WORKDIR /usr/share/nginx/html/release 8 | -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/model/articleList.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2021-01-21 16:53:48 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-21 16:54:50 7 | */ 8 | export interface ArticleList { 9 | total: number 10 | items: T[] 11 | } 12 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js + TypeScript App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /src/config/default/whitelist.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 白名单 3 | * @Author: ZY 4 | * @Date: 2020-12-28 09:38:57 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-28 14:07:34 7 | */ 8 | 9 | const whiteList = ['/login', '/auth-redirect'] 10 | export default whiteList -------------------------------------------------------------------------------- /src/constant/network.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 网络配置常量 3 | * @Author: ZY 4 | * @Date: 2020-12-08 13:34:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-08 13:39:56 7 | */ 8 | 9 | export enum InfoShowType{ 10 | LOG, 11 | NOTIFICATION, 12 | TOAST 13 | } 14 | -------------------------------------------------------------------------------- /src/store/modules/permission/mutation-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-25 14:28:43 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-25 14:32:35 7 | */ 8 | export enum PermissionMutationType{ 9 | SET_ROUTES = 'SET_ROUTES' 10 | } 11 | -------------------------------------------------------------------------------- /src/store/modules/permission/action-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-25 14:33:12 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-25 14:33:38 7 | */ 8 | export enum PermissionActionType{ 9 | ACTION_SET_ROUTES = 'ACTION_SET_ROUTES' 10 | } 11 | -------------------------------------------------------------------------------- /src/plugins/i18n.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: i18n国际化 3 | * @Author: ZY 4 | * @Date: 2020-12-19 11:53:48 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-23 10:08:34 7 | */ 8 | import i18n from '@/locales' 9 | export default function loadComponent(app: any) { 10 | app.use(i18n) 11 | } 12 | -------------------------------------------------------------------------------- /src/store/modules/settings/mutation-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app mutations type 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-25 10:43:53 7 | */ 8 | 9 | export enum SettingsMutationTypes { 10 | CHANGE_SETTING = 'CHANGE_SETTING', 11 | } 12 | -------------------------------------------------------------------------------- /k8smanifests/svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: ${NAME} 5 | namespace: ${NAMESPACE} 6 | spec: 7 | ports: 8 | - name: http 9 | port: ${SERVER_PORT} 10 | protocol: TCP 11 | targetPort: ${SERVER_PORT} 12 | selector: 13 | app: ${NAME} 14 | type: ClusterIP 15 | -------------------------------------------------------------------------------- /src/store/modules/settings/action-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app actions type 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-25 10:55:13 7 | */ 8 | 9 | export enum SettingsActionTypes { 10 | ACTION_CHANGE_SETTING = 'ACTION_CHANGE_SETTING', 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/_variables.scss.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-21 14:38:13 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-21 15:00:34 7 | */ 8 | export interface ScssVariables { 9 | menuBg: string 10 | menuText: string 11 | menuActiveText: string 12 | } 13 | 14 | export const variables: IScssVariables 15 | 16 | export default variables 17 | -------------------------------------------------------------------------------- /src/views/example/components/Dropdown/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: scy😊 4 | * @Date: 2021-01-15 08:51:38 5 | * @LastEditors: scy😊 6 | * @LastEditTime: 2021-01-15 10:47:47 7 | */ 8 | export { default as CommentDropdown } from './Comment.vue' 9 | export { default as PlatformDropdown } from './Platform.vue' 10 | export { default as SourceUrlDropdown } from './SourceUrl.vue' 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | pnpm-debug.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | props: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/views/example/Create.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-1/Index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /src/views/user-manager/login/model/loginModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-29 09:12:24 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-29 09:13:23 7 | */ 8 | // { 9 | // "code": 0, 10 | // "msg": "success", 11 | // "data": { 12 | // "accessToken": "admin-token" 13 | // } 14 | // } 15 | 16 | export interface LoginModel { 17 | accessToken: string 18 | } 19 | -------------------------------------------------------------------------------- /src/config/default/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 默认配置(vue配置 | 主题配置 | 网络配置) 3 | * @Author: ZY 4 | * @Date: 2020-12-08 09:41:05 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-25 10:39:14 7 | */ 8 | 9 | export const netConfig = import('./net.config') 10 | export const settingConfig = import('./setting.config') 11 | export const themeConfig = import('./theme.config') 12 | export const layoutSettings = import('./layout') 13 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-08 09:45:01 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-09 11:18:25 7 | */ 8 | // 默认配置 9 | import { netConfig, settingConfig, themeConfig } from './default' 10 | // 自定义配置 11 | import customConfig from './customConfig' 12 | 13 | // 导出配置(自定义配置优先级高) 14 | export default Object.assign({}, netConfig, settingConfig, themeConfig, customConfig) 15 | -------------------------------------------------------------------------------- /src/model/rootObject.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-29 09:05:40 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-29 09:07:23 7 | */ 8 | 9 | // { 10 | // "code": 0, 11 | // "msg": "success", 12 | // "data": { 13 | // "accessToken": "admin-token" 14 | // } 15 | // } 16 | 17 | export interface RootObject{ 18 | code: number 19 | msg: string 20 | data: T 21 | } 22 | -------------------------------------------------------------------------------- /src/store/modules/app/mutation-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app mutations type 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-23 10:38:43 7 | */ 8 | 9 | export enum AppMutationTypes { 10 | TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR', 11 | CLOSE_SIDEBAR = 'CLOSE_SIDEBAR', 12 | TOGGLE_DEVICE = 'TOGGLE_DEVICE', 13 | SET_LANGUAGE = 'SET_LANGUAGE', 14 | SET_SIZE = 'SET_SIZE', 15 | } 16 | -------------------------------------------------------------------------------- /src/constant/headers.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-08 09:50:38 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-08 11:47:10 7 | */ 8 | 9 | export enum ContentType{ 10 | FORM = 'application/x-www-form-urlencoded', 11 | JSON = 'application/json; charset=utf-8' 12 | } 13 | 14 | export enum Device{ 15 | IOS = 'iOS', 16 | ANDROID = 'Android', 17 | WINDOWS = 'Windows', 18 | PC='PC' 19 | } 20 | -------------------------------------------------------------------------------- /src/store/modules/user/mutation-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: user mutations type 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-28 11:33:53 7 | */ 8 | 9 | export enum UserMutationTypes { 10 | SET_TOKEN = 'SET_TOKEN', 11 | SET_NAME = 'SET_NAME', 12 | SET_AVATAR = 'SET_AVATAR', 13 | SET_INTRODUCTION = 'SET_INTRODUCTION', 14 | SET_ROLES = 'SET_ROLES', 15 | SET_EMAIL = 'SET_EMAIL', 16 | } 17 | -------------------------------------------------------------------------------- /src/@types/index.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 项目类型声明 3 | * @Author: ZY 4 | * @Date: 2020-12-19 10:33:47 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-19 16:05:14 7 | */ 8 | 9 | declare module '*.svg' 10 | declare module '*.png' 11 | declare module '*.jpg' 12 | declare module '*.jpeg' 13 | declare module '*.gif' 14 | declare module '*.bmp' 15 | declare module '*.tiff' 16 | declare module '*.yaml' 17 | declare module '*.json' 18 | declare module 'vue-count-to' 19 | -------------------------------------------------------------------------------- /src/store/modules/permission/state.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-25 14:15:18 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-26 13:57:51 7 | */ 8 | 9 | import { RouteRecordRaw } from 'vue-router' 10 | 11 | export interface PermissionState { 12 | routes: RouteRecordRaw[] 13 | dynamicRoutes: RouteRecordRaw[] 14 | } 15 | 16 | export const state: PermissionState = { 17 | routes: [], 18 | dynamicRoutes: [] 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/loading.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 全局loading 3 | * @Author: ZY 4 | * @Date: 2021-01-20 15:02:11 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-20 15:09:36 7 | */ 8 | 9 | import { ElLoading } from 'element-plus' 10 | 11 | export default function() { 12 | const loading = (title: string) => { 13 | const loadingInstance = ElLoading.service({ text: title }) 14 | return loadingInstance 15 | } 16 | 17 | return { 18 | loading 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/@types/vue-proptery.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-25 09:50:16 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-30 11:23:35 7 | */ 8 | import { ElMessage } from 'element-plus' 9 | declare module '@vue/runtime-core' { 10 | interface ComponentCustomProperties { 11 | $message: ElMessage 12 | } 13 | } 14 | 15 | declare module 'vue-router' { 16 | interface RouteMeta { 17 | roles?: string[] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/store/modules/user/action-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app actions type 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-25 10:55:13 7 | */ 8 | 9 | export enum UserActionTypes { 10 | ACTION_LOGIN = 'ACTION_LOGIN', 11 | ACTION_RESET_TOKEN = 'ACTION_RESET_TOKEN', 12 | ACTION_GET_USER_INFO = 'ACTION_GET_USER_INFO', 13 | ACTION_CHANGE_ROLES = 'ACTION_CHANGE_ROLES', 14 | ACTION_LOGIN_OUT = 'ACTION_LOGIN_OUT', 15 | } 16 | -------------------------------------------------------------------------------- /src/layout/components/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-17 15:43:21 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-24 10:40:32 7 | */ 8 | export { default as AppMain } from './AppMain.vue' 9 | export { default as Navbar } from './navigation_bar/Index.vue' 10 | export { default as Settings } from './settings/Index.vue' 11 | export { default as Sidebar } from './side_bar/Index.vue' 12 | export { default as TagsView } from './tags_view/Index.vue' 13 | -------------------------------------------------------------------------------- /src/views/nested/menu2/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /src/store/modules/app/action-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app actions type 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-23 14:02:25 7 | */ 8 | 9 | export enum AppActionTypes { 10 | ACTION_TOGGLE_SIDEBAR = 'ACTION_TOGGLE_SIDEBAR', 11 | ACTION_CLOSE_SIDEBAR = 'ACTION_CLOSE_SIDEBAR', 12 | ACTION_TOGGLE_DEVICE = 'ACTION_TOGGLE_DEVICE', 13 | ACTION_SET_LANGUAGE = 'ACTION_SET_LANGUAGE', 14 | ACTION_SET_SIZE = 'ACTION_SET_SIZE', 15 | } 16 | -------------------------------------------------------------------------------- /src/config/customConfig.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 自定义配置 3 | * @Author: ZY 4 | * @Date: 2020-12-08 14:26:07 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-08 15:21:33 7 | */ 8 | 9 | import { NetworkConfig } from './default/net.config' 10 | import { Theme } from './default/theme.config' 11 | import { Settings } from './default/setting.config' 12 | 13 | type CustomConfig = NetworkConfig & Theme & Settings 14 | 15 | const customConfig: CustomConfig = { 16 | 17 | } 18 | 19 | export default customConfig 20 | -------------------------------------------------------------------------------- /src/views/example/Edit.vue: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /src/router/constantModules/userManager.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 用户管理相关路由管理 3 | * @Author: ZY 4 | * @Date: 2020-12-10 16:12:54 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-21 18:04:55 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | const UserManagerRouter: Array = [ 10 | { 11 | path: '/login', 12 | name: 'Login', 13 | component: () => import(/* webpackChunkName: "userManager" */'@/views/user-manager/login/Index.vue') 14 | } 15 | ] 16 | export default UserManagerRouter 17 | -------------------------------------------------------------------------------- /src/model/routesModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2021-01-12 16:39:17 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-12 16:41:49 7 | */ 8 | 9 | export interface Meta { 10 | hidden: boolean 11 | } 12 | 13 | export interface Children { 14 | path: string 15 | component: string 16 | } 17 | 18 | export interface Route { 19 | path: string 20 | component: string 21 | meta: Meta 22 | children: Children[] 23 | } 24 | 25 | export interface Routes { 26 | routes: Route[] 27 | } 28 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-3/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /src/constant/key.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-17 16:06:56 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-17 16:35:33 7 | */ 8 | 9 | class Keys { 10 | static sidebarStatusKey = 'vue3-typescript-admin-sidebarStatusKey' 11 | static languageKey = 'vue3-typescript-admin-languageKey' 12 | static sizeKey = 'vue3-typescript-admin-sizeKey' 13 | static tokenKey = 'vue3-typescript-admin-access-token' 14 | static aseKey = 'vue3-typescript-admin-ase-key' 15 | } 16 | 17 | export default Keys 18 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/menu1-2-1/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/menu1-2-2/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /mock/controller/type.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-28 15:13:48 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-12 15:22:27 7 | */ 8 | 9 | 10 | export interface UserBean { 11 | id: number 12 | username: string 13 | password: string 14 | name: string 15 | email: string 16 | phone: string 17 | avatar: string 18 | introduction: string 19 | roles: string[] 20 | } 21 | export interface RoleBean { 22 | key: string 23 | name: string 24 | description: string 25 | routes: any 26 | } -------------------------------------------------------------------------------- /src/model/articleModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: scy😊 4 | * @Date: 2021-01-20 11:46:27 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-21 17:33:29 7 | */ 8 | export interface ArticleModel { 9 | id: number 10 | status: string 11 | title: string 12 | abstractContent: string 13 | fullContent: string 14 | sourceURL: string 15 | imageURL: string 16 | timestamp: number 17 | platforms: string[] 18 | disableComment: boolean 19 | importance: number 20 | author: string 21 | reviewer: string 22 | type: string 23 | pageviews: number 24 | } 25 | -------------------------------------------------------------------------------- /src/store/modules/tagsview/state.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: tags-view state 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-06 14:43:49 7 | */ 8 | import { RouteLocationNormalized } from 'vue-router' 9 | 10 | export interface TagView extends Partial { 11 | title?: string 12 | } 13 | 14 | export interface TagsViewState { 15 | visitedViews: TagView[] 16 | cachedViews: (string | undefined)[] 17 | } 18 | 19 | export const state: TagsViewState = { 20 | visitedViews: [], 21 | cachedViews: [] 22 | } 23 | -------------------------------------------------------------------------------- /public/tinymce/skins/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} 8 | -------------------------------------------------------------------------------- /src/store/modules/user/state.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-28 11:26:33 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-28 13:34:08 7 | */ 8 | import { getToken } from '@/utils/cookies' 9 | 10 | export interface UserState{ 11 | token: string 12 | name: string 13 | avatar: string 14 | introduction: string 15 | roles: string[] 16 | email: string 17 | } 18 | 19 | export const state: UserState = { 20 | token: getToken() || '', 21 | name: '', 22 | avatar: '', 23 | introduction: '', 24 | roles: [], 25 | email: '' 26 | } 27 | -------------------------------------------------------------------------------- /src/styles/_svgicon.scss: -------------------------------------------------------------------------------- 1 | /* Recommended css code for vue-svgicon */ 2 | .svg-icon { 3 | display: inline-block; 4 | width: 16px; 5 | height: 16px; 6 | color: inherit; 7 | fill: none; 8 | stroke: currentColor; 9 | vertical-align: -0.15em; 10 | } 11 | 12 | .svg-fill { 13 | fill: currentColor; 14 | stroke: none; 15 | } 16 | 17 | .svg-up { 18 | transform: rotate(0deg); 19 | } 20 | 21 | .svg-right { 22 | transform: rotate(90deg); 23 | } 24 | 25 | .svg-down { 26 | transform: rotate(180deg); 27 | } 28 | 29 | .svg-left { 30 | transform: rotate(-90deg); 31 | } 32 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 加载插件文件 3 | * @Author: ZY 4 | * @Date: 2020-12-19 11:53:00 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-23 16:49:30 7 | */ 8 | import { createApp } from 'vue' 9 | 10 | /** 11 | * @description 加载所有 Plugins 12 | * @param {ReturnType} app 整个应用的实例 13 | */ 14 | export function loadAllPlugins(app: ReturnType) { 15 | const files = require.context('.', true, /\.ts$/) 16 | files.keys().forEach(key => { 17 | if (typeof files(key).default === 'function') { 18 | if (key !== './index.ts') files(key).default(app) 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: WJM 4 | * @Date: 2021-01-08 08:50:30 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-21 18:13:40 7 | */ 8 | declare module '*.vue' { 9 | import type { DefineComponent } from 'vue' 10 | const component: DefineComponent<{}, {}, any> 11 | export default component 12 | } 13 | 14 | declare module '*.gif' { 15 | export const gif: any 16 | } 17 | 18 | // TODO: remove this part after vue-count-to has its typescript file 19 | declare module 'vue-count-to' 20 | // TODO: remove this part after vue-image-crop-upload has its typescript file 21 | declare module 'vue-image-crop-upload' 22 | -------------------------------------------------------------------------------- /src/utils/https.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: axios 封装网络请求 3 | * @Author: ZY 4 | * @Date: 2020-12-28 14:45:32 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-25 20:01:32 7 | */ 8 | 9 | import { useStore } from '@/store' 10 | import HttpClient, { HttpClientConfig } from 'axios-mapper' 11 | import networkConfig from '@/config/default/net.config' 12 | const https = (hasToken: Boolean = true) => { 13 | const config: HttpClientConfig = { 14 | baseURL: networkConfig.host, 15 | headers: { 16 | token: hasToken ? useStore().state.user.token : '' 17 | } 18 | } 19 | return new HttpClient(config) 20 | } 21 | 22 | export default https 23 | -------------------------------------------------------------------------------- /src/utils/ase.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description:AES加密 3 | * @Autor: ZY 4 | * @Date: 2020-11-04 13:33:44 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-28 16:50:17 7 | */ 8 | import { AES, mode, pad, enc } from 'crypto-ts' 9 | import Keys from '@/constant/key' 10 | 11 | export default class VAES { 12 | static encrypt(text: string | null): string | null { 13 | return AES.encrypt(text ?? '', Keys.aseKey, { mode: mode.ECB, padding: pad.PKCS7 }).toString() 14 | } 15 | 16 | static decrypt(text: string | null): string | null { 17 | return AES.decrypt(text ?? '', Keys.aseKey, { mode: mode.ECB, padding: pad.PKCS7 }).toString(enc.Utf8) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/views/nested/menu1/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 23 | 24 | 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build And Deploy vue3-composition-admin 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2.3.1 14 | with: 15 | persist-credentials: false 16 | 17 | - name: Install and Build 18 | run: | 19 | yarn 20 | yarn build:prod 21 | 22 | - name: Deploy 23 | uses: peaceiris/actions-gh-pages@v3 24 | with: 25 | deploy_key: ${{ secrets.VUE3_ACCESS_TOKEN }} 26 | publish_dir: docs/.vuepress/dist 27 | -------------------------------------------------------------------------------- /src/utils/zip.ts: -------------------------------------------------------------------------------- 1 | import { saveAs } from 'file-saver' 2 | import JSZip from 'jszip' 3 | 4 | export const exportTxt2Zip = (th: string[], jsonData: any, txtName = 'file', zipName = 'file') => { 5 | const zip = new JSZip() 6 | const data = jsonData 7 | let txtData = `${th}\r\n` 8 | data.forEach((row: any) => { 9 | let tempStr = '' 10 | tempStr = row.toString() 11 | txtData += `${tempStr}\r\n` 12 | }) 13 | zip.file(`${txtName}.txt`, txtData) 14 | zip.generateAsync({ 15 | type: 'blob' 16 | }).then((blob: Blob) => { 17 | saveAs(blob, `${zipName}.zip`) 18 | }, (err: Error) => { 19 | alert('Zip export failed: ' + err.message) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /src/model/getRolesModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 角色模型 3 | * @Author: ZY 4 | * @Date: 2021-01-12 16:49:16 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-12 16:52:09 7 | */ 8 | 9 | export interface Meta { 10 | hidden: boolean 11 | } 12 | 13 | export interface Children { 14 | path: string 15 | component: string 16 | } 17 | 18 | export interface Route { 19 | path: string 20 | component: string 21 | meta: Meta 22 | children: Children[] 23 | } 24 | 25 | export interface Item { 26 | key: string 27 | name: string 28 | description: string 29 | routes: Route[] 30 | } 31 | 32 | export interface RolesModels { 33 | total: number 34 | items: Item[] 35 | } 36 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 24 | 25 | 32 | -------------------------------------------------------------------------------- /src/utils/permission.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2021-01-12 10:20:08 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-12 10:20:45 7 | */ 8 | import { useStore } from '@/store' 9 | 10 | export const checkPermission = (value: string[]): boolean => { 11 | if (value && value instanceof Array && value.length > 0) { 12 | const roles = useStore().state.user.roles 13 | const permissionRoles = value 14 | const hasPermission = roles.some(role => { 15 | return permissionRoles.includes(role) 16 | }) 17 | return hasPermission 18 | } else { 19 | console.error('need roles! Like v-permission="[\'admin\',\'editor\']"') 20 | return false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/constant/settings.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-08 10:28:32 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-07 08:40:47 7 | */ 8 | 9 | export enum Language{ 10 | En='en', 11 | Zh='zh-cn', 12 | } 13 | 14 | export enum Environment{ 15 | Development = 'development', 16 | Production = 'production' 17 | } 18 | 19 | export enum RouterSource{ 20 | Frontend = 'frontend', 21 | Backend = 'backend' 22 | } 23 | 24 | export enum RouterMode{ 25 | Hash = 'hash', 26 | HISTORY = 'history' 27 | } 28 | 29 | export enum TokenStorageName{ 30 | LocalStorage = 'localStorage', 31 | SessionStorage = 'sessionStorage', 32 | Cookie = 'cookie' 33 | } 34 | -------------------------------------------------------------------------------- /src/store/modules/tagsview/mutation-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app mutations type 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-30 11:37:33 7 | */ 8 | 9 | export enum TagsMutationTypes { 10 | ADD_VISITED_VIEW = 'ADD_VISITED_VIEW', 11 | ADD_CACHED_VIEW = 'ADD_CACHED_VIEW', 12 | DEL_VISITED_VIEW = 'DEL_VISITED_VIEW', 13 | DEL_CACHED_VIEW = 'DEL_CACHED_VIEW', 14 | DEL_OTHERS_VISITED_VIEWS = 'DEL_OTHERS_VISITED_VIEWS', 15 | DEL_OTHERS_CACHED_VIEWS = 'DEL_OTHERS_CACHED_VIEWS', 16 | DEL_ALL_VISITED_VIEWS = 'DEL_ALL_VISITED_VIEWS', 17 | DEL_ALL_CACHED_VIEWS = 'DEL_ALL_CACHED_VIEWS', 18 | UPDATE_VISITED_VIEW = 'UPDATE_VISITED_VIEW', 19 | } 20 | -------------------------------------------------------------------------------- /src/views/example/components/Warning.vue: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /src/views/permission/role/editRole.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 编辑Role 操作抽离 3 | * @Author: ZY 4 | * @Date: 2021-01-13 09:43:17 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-13 13:57:40 7 | */ 8 | 9 | import { RouteRecordRaw } from 'vue-router' 10 | export default function() { 11 | const flattenRoutes = (routes: RouteRecordRaw[]) => { 12 | let data: RouteRecordRaw[] = [] 13 | routes.forEach(route => { 14 | data.push(route) 15 | if (route.children) { 16 | const temp = flattenRoutes(route.children) 17 | if (temp.length > 0) { 18 | data = [...data, ...temp] 19 | } 20 | } 21 | }) 22 | return data 23 | } 24 | 25 | return { 26 | flattenRoutes 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/tinymce/README.md: -------------------------------------------------------------------------------- 1 | # Tinymce 2 | 3 | ## Docs 4 | 5 | Check [Vue integration doc for Tinymce](https://www.tiny.cloud/docs/integrations/vue/#installingthetinymcevuejsintegrationusingnpm). 6 | 7 | ## Language 8 | 9 | Resources under `langs` folder are copied from [tinymce language package](https://www.tiny.cloud/get-tiny/language-packages). 10 | 11 | ## Skin 12 | 13 | First download latest tinymce release from [offical website](https://www.tiny.cloud/get-tiny/self-hosted/) , resources under `skins` folder are copied from `skins/ui/oxide`. 14 | 15 | ## Emojis 16 | 17 | First download latest tinymce release from [offical website](https://www.tiny.cloud/get-tiny/self-hosted/), `emojis.min.js` file is copied from `plugins/emoticons/js`. 18 | -------------------------------------------------------------------------------- /src/router/permissionModules/tab.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description:分栏 3 | * @Author: scy 4 | * @Date: 2021-01-08 19:21:46 5 | * @LastEditors: scy 6 | * @LastEditTime: 2021-01-21 21:22:52 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const tabRouter: Array = [ 12 | { 13 | path: '/tab', 14 | component: Layout, 15 | children: [ 16 | { 17 | path: 'index', 18 | component: () => import(/* webpackChunkName: "tab" */ '@/views/tab/Index.vue'), 19 | name: 'Tab', 20 | meta: { 21 | title: 'tab', 22 | icon: '#icontab' 23 | } 24 | } 25 | ] 26 | } 27 | ] 28 | 29 | export default tabRouter 30 | -------------------------------------------------------------------------------- /src/views/redirect/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 30 | -------------------------------------------------------------------------------- /src/store/modules/tagsview/action-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app actions type 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-11 11:18:02 7 | */ 8 | 9 | export enum TagsActionTypes { 10 | ACTION_ADD_VIEW = 'ACTION_ADD_VIEW', 11 | ACTION_ADD_VISITED_VIEW = 'ACTION_ADD_VISITED_VIEW', 12 | ACTION_DEL_VIEW = 'ACTION_DEL_VIEW', 13 | ACTION_DEL_OTHER_VIEW = 'ACTION_DEL_OTHER_VIEW', 14 | ACTION_DEL_CACHED_VIEW = 'ACTION_DEL_CACHED_VIEW', 15 | ACTION_OTHER_VIEWS = 'ACTION_OTHER_VIEWS', 16 | ACTION_DEL_ALL_VIEWS = 'ACTION_DEL_ALL_VIEWS', 17 | ACTION_DEL_ALL_CACHED_VIEWS = 'ACTION_DEL_ALL_CACHED_VIEWS', 18 | ACTION_UPDATE_VISITED_VIEW = 'ACTION_UPDATE_VISITED_VIEW', 19 | } 20 | -------------------------------------------------------------------------------- /src/views/charts/BarChartDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 16 | 17 | 26 | 27 | 34 | -------------------------------------------------------------------------------- /src/router/permissionModules/guide.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 引导页 3 | * @Author: ZY 4 | * @Date: 2021-01-21 20:13:03 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-21 21:24:27 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const GuideRouter: Array = [ 12 | { 13 | path: '/guide', 14 | component: Layout, 15 | children: [ 16 | { 17 | path: 'index', 18 | component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/Index.vue'), 19 | name: 'Guide', 20 | meta: { 21 | title: 'guide', 22 | icon: '#iconguide' 23 | } 24 | } 25 | ] 26 | } 27 | ] 28 | 29 | export default GuideRouter 30 | -------------------------------------------------------------------------------- /src/views/charts/LineChartDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 16 | 17 | 26 | 27 | 34 | -------------------------------------------------------------------------------- /src/views/charts/MixedChatDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 16 | 17 | 26 | 27 | 34 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /src/config/default/vue.custom.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-09 11:15:59 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-25 17:41:57 7 | */ 8 | const vueDefaultConfig = { 9 | publicPath: '/', 10 | outputDir: 'dist', 11 | assetsDir: 'static', 12 | lintOnSave: true, 13 | transpileDependencies: ['vue-echarts', 'resize-detector'], 14 | //webpack 配置的项目名称 15 | title: 'vue3-ts-composition-admin-template', 16 | titleSeparator: ' - ', 17 | titleReverse: false, 18 | devPort: '9999', 19 | abbreviation: 'vt2at', 20 | providePlugin: {}, 21 | build7z: false, 22 | startMessage:'欢迎使用vue3-ts-composition-admin-template,使用composition API和TS 玩转最潮技术' 23 | } 24 | 25 | module.exports = vueDefaultConfig 26 | -------------------------------------------------------------------------------- /src/router/permissionModules/theme.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 换肤 3 | * @Author: scy 4 | * @Date: 2021-01-21 20:13:03 5 | * @LastEditors: scy 6 | * @LastEditTime: 2021-01-21 21:24:27 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const ThemeRouter: Array = [ 12 | { 13 | path: '/theme', 14 | component: Layout, 15 | redirect: 'noredirect', 16 | children: [ 17 | { 18 | path: 'index', 19 | component: () => import(/* webpackChunkName: "theme" */ '@/views/theme/Index.vue'), 20 | name: 'Theme', 21 | meta: { 22 | title: 'theme', 23 | icon: '#iconhuanfu' 24 | } 25 | } 26 | ] 27 | } 28 | ] 29 | 30 | export default ThemeRouter 31 | -------------------------------------------------------------------------------- /src/utils/clipboard.ts: -------------------------------------------------------------------------------- 1 | import Clipboard from 'clipboard' 2 | import { ElMessage } from 'element-plus' 3 | export const clipboardSuccess = () => 4 | ElMessage({ 5 | message: 'Copy successfully', 6 | type: 'success', 7 | duration: 1500 8 | }) 9 | 10 | export const clipboardError = () => 11 | ElMessage({ 12 | message: 'Copy failed', 13 | type: 'error' 14 | }) 15 | 16 | export const handleClipboard = (text: string, event: MouseEvent) => { 17 | const clipboard = new Clipboard(event.target as Element, { 18 | text: () => text 19 | }) 20 | clipboard.on('success', () => { 21 | clipboardSuccess() 22 | clipboard.destroy() 23 | }) 24 | clipboard.on('error', () => { 25 | clipboardError() 26 | clipboard.destroy() 27 | }); 28 | (clipboard as any).onClick(event) 29 | } 30 | -------------------------------------------------------------------------------- /src/views/pdf/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 23 | 24 | 35 | -------------------------------------------------------------------------------- /src/router/permissionModules/clipboard.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 粘贴板 3 | * @Author: scy 4 | * @Date: 2021-01-21 20:13:03 5 | * @LastEditors: scy 6 | * @LastEditTime: 2021-01-21 21:24:27 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const ProfileRouter: Array = [ 12 | { 13 | path: '/clipboard', 14 | component: Layout, 15 | redirect: 'noredirect', 16 | children: [ 17 | { 18 | path: 'index', 19 | component: () => import(/* webpackChunkName: "clipboard" */ '@/views/clipboard/Index.vue'), 20 | name: 'Clipboard', 21 | meta: { 22 | title: 'clipboard', 23 | icon: '#iconcopy' 24 | } 25 | } 26 | ] 27 | } 28 | ] 29 | 30 | export default ProfileRouter 31 | -------------------------------------------------------------------------------- /src/store/modules/app/state.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app state 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-23 10:30:35 7 | */ 8 | import { getSidebarStatus, getSize } from '@/utils/cookies' 9 | import { getLocale } from '@/locales' 10 | 11 | export enum DeviceType { 12 | Mobile, 13 | Desktop, 14 | } 15 | 16 | export interface AppState { 17 | device: DeviceType 18 | sidebar: { 19 | opened: boolean 20 | withoutAnimation: boolean 21 | } 22 | language: string 23 | size: string 24 | } 25 | 26 | export const state: AppState = { 27 | device: DeviceType.Desktop, 28 | sidebar: { 29 | opened: getSidebarStatus() !== 'closed', 30 | withoutAnimation: false 31 | }, 32 | language: getLocale(), 33 | size: getSize() || 'medium' 34 | } 35 | -------------------------------------------------------------------------------- /src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /* Element Variables */ 2 | 3 | // Override Element UI variables 4 | $--color-primary: #1890ff; 5 | $--color-success: #13ce66; 6 | $--color-warning: #ffba00; 7 | $--color-danger: #ff4949; 8 | $--color-info: #5d5d5d; 9 | $--button-font-weight: 400; 10 | $--color-text-regular: #1f2d3d; 11 | $--border-color-light: #dfe4ed; 12 | $--border-color-lighter: #e6ebf5; 13 | $--table-border: 1px solid#dfe6ec; 14 | 15 | // Icon font path, required 16 | $--font-path: '~element-plus/lib/theme-chalk/fonts'; 17 | 18 | // Apply overrided variables in Element UI 19 | @import "~element-plus/packages/theme-chalk/src/index"; 20 | 21 | // The :export directive is the magic sauce for webpack 22 | // https://mattferderer.com/use-sass-variables-in-typescript-and-javascript 23 | :export { 24 | theme: $--color-primary; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/validate.ts: -------------------------------------------------------------------------------- 1 | export const isValidUsername = (str: string) => ['admin', 'editor'].indexOf(str.trim()) >= 0 2 | 3 | export const isExternal = (path: string) => /^(https?:|mailto:|tel:)/.test(path) 4 | 5 | export const isArray = (arg: any) => { 6 | if (typeof Array.isArray === 'undefined') { 7 | return Object.prototype.toString.call(arg) === '[object Array]' 8 | } 9 | return Array.isArray(arg) 10 | } 11 | 12 | export const isValidURL = (url: string) => { 13 | const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ 14 | return reg.test(url) 15 | } 16 | -------------------------------------------------------------------------------- /mock/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "./dist", 5 | "module": "commonjs", 6 | "target": "esnext", 7 | "allowSyntheticDefaultImports": true, 8 | "importHelpers": true, 9 | "strict": false, 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitAny": true, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "noUnusedParameters": true, 16 | "noUnusedLocals": true, 17 | "noImplicitReturns": true, 18 | "experimentalDecorators": true, 19 | "emitDecoratorMetadata": true, 20 | "allowJs": true, 21 | "sourceMap": true, 22 | }, 23 | "include": [ 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules", 29 | "dist" 30 | ] 31 | } -------------------------------------------------------------------------------- /mock/type.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-10 09:51:09 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-23 10:53:46 7 | */ 8 | import {Context, Next} from "koa" 9 | 10 | // type PlainObject = { [P: string]: any }; 11 | type PlainObject = Record; 12 | type ParamObject = Record; 13 | type MysqlResult = { 14 | affectedRows?: number; 15 | insertId?: string; 16 | } 17 | 18 | type PathMeta = { 19 | name: string; 20 | path: string; 21 | } 22 | 23 | type RouteMeta = { 24 | name: string; 25 | method: string; 26 | path: string; 27 | isVerify: boolean; 28 | } 29 | 30 | type MiddleWare = (...arg: any[]) => (ctx: Context, next?: Next) => Promise; 31 | 32 | export { 33 | MysqlResult, 34 | PlainObject, 35 | RouteMeta, 36 | MiddleWare, 37 | PathMeta, 38 | ParamObject 39 | } -------------------------------------------------------------------------------- /src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | 3 | // Base color 4 | $blue:#324157; 5 | $light-blue:#3A71A8; 6 | $red:#C03639; 7 | $pink: #E65D6E; 8 | $green: #30B08F; 9 | $tiffany: #4AB7BD; 10 | $yellow:#FEC171; 11 | $panGreen: #30B08F; 12 | 13 | // Sidebar 14 | $sideBarWidth:300px; 15 | $subMenuBg:#1f2d3d; 16 | $subMenuHover:#001528; 17 | $subMenuActiveText:#f4f4f5; 18 | $menuBg:#fff; 19 | $menuText:#162B64; 20 | $menuActiveText:#435EBE; // Also see settings.sidebarTextTheme 21 | 22 | // Login page 23 | $lightGray: #eee; 24 | $darkGray:#889aa4; 25 | $loginBg: #2d3a4b; 26 | $loginCursorColor: #fff; 27 | 28 | // The :export directive is the magic sauce for webpack 29 | // https://mattferderer.com/use-sass-variables-in-typescript-and-javascript 30 | :export { 31 | menuBg: $menuBg; 32 | menuText: $menuText; 33 | menuActiveText: $menuActiveText; 34 | } 35 | -------------------------------------------------------------------------------- /src/store/modules/permission/mutations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-25 14:28:12 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-25 15:03:37 7 | */ 8 | import { MutationTree } from 'vuex' 9 | import { PermissionState } from './state' 10 | import { PermissionMutationType } from './mutation-types' 11 | import { RouteRecordRaw } from 'vue-router' 12 | import { constantRoutes } from '@/router' 13 | 14 | export type Mutations = { 15 | [PermissionMutationType.SET_ROUTES](state: S, routes: RouteRecordRaw[]): void 16 | } 17 | 18 | export const mutations: MutationTree & Mutations = { 19 | [PermissionMutationType.SET_ROUTES](state: PermissionState, routes: RouteRecordRaw[]) { 20 | state.routes = constantRoutes.concat(routes) 21 | state.dynamicRoutes = routes 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /k8smanifests/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | annotations: 5 | kubernetes.io/ingress.class: nginx 6 | nginx.ingress.kubernetes.io/client-body-buffer-size: 128k 7 | nginx.ingress.kubernetes.io/proxy-body-size: 100m 8 | nginx.ingress.kubernetes.io/proxy-buffer-size: 4k 9 | nginx.ingress.kubernetes.io/proxy-connect-timeout: "5" 10 | nginx.ingress.kubernetes.io/proxy-read-timeout: "1800" 11 | nginx.ingress.kubernetes.io/proxy-send-timeout: "1800" 12 | name: ${NAME} 13 | namespace: ${NAMESPACE} 14 | spec: 15 | rules: 16 | - host: ${INGRESS_HOST} 17 | http: 18 | paths: 19 | - backend: 20 | serviceName: ${NAME} 21 | servicePort: ${SERVER_PORT} 22 | path: / 23 | tls: 24 | - hosts: 25 | - ${INGRESS_HOST} 26 | secretName: ${INGRESS_TLS_SECRET} 27 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | 19 | 38 | -------------------------------------------------------------------------------- /src/directives/permission/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 权限指令 3 | * @Author: ZY 4 | * @Date: 2020-12-28 10:39:21 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-28 13:46:23 7 | */ 8 | import { useStore } from '@/store' 9 | import { Directive } from 'vue' 10 | 11 | export const permission: Directive = { 12 | mounted(el, binding) { 13 | const { value } = binding 14 | const roles = useStore().state.user.roles 15 | if (value && value instanceof Array && value.length > 0) { 16 | const permissionRoles = value 17 | const hasPermission = roles.some((role: any) => { 18 | return permissionRoles.includes(role) 19 | }) 20 | if (!hasPermission) { 21 | el.style.display = 'none' 22 | } 23 | } else { 24 | throw new Error('need roles! Like v-permission="[\'admin\',\'editor\']"') 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/router/permissionModules/profile.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 个人中心 3 | * @Author: scy 4 | * @Date: 2021-01-21 20:13:03 5 | * @LastEditors: scy 6 | * @LastEditTime: 2021-01-21 21:24:27 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const ProfileRouter: Array = [ 12 | { 13 | path: '/profile', 14 | component: Layout, 15 | redirect: '/profile/index', 16 | meta: { hidden: true }, 17 | children: [ 18 | { 19 | path: 'index', 20 | component: () => import(/* webpackChunkName: "profile" */ '@/views/profile/Index.vue'), 21 | name: 'Profile', 22 | meta: { 23 | title: 'profile', 24 | icon: 'user', 25 | noCache: true 26 | } 27 | } 28 | ] 29 | } 30 | ] 31 | 32 | export default ProfileRouter 33 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <%= htmlWebpackPlugin.options.title %> 16 | 17 | 18 | 19 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /src/store/modules/settings/state.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app state 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-20 16:59:15 7 | */ 8 | 9 | import elementVariables from '@/styles/element-variables.scss' 10 | import layoutSettings from '@/config/default/layout' 11 | export interface SettingsState { 12 | theme: string 13 | fixedHeader: boolean 14 | showSettings: boolean 15 | showTagsView: boolean 16 | showSidebarLogo: boolean 17 | sidebarTextTheme: boolean 18 | } 19 | 20 | export const state: SettingsState = { 21 | theme: elementVariables.theme, 22 | fixedHeader: layoutSettings.fixedHeader, 23 | showSettings: layoutSettings.showSettings, 24 | showTagsView: layoutSettings.showTagsView, 25 | showSidebarLogo: layoutSettings.showSidebarLogo, 26 | sidebarTextTheme: layoutSettings.sidebarTextTheme 27 | } 28 | -------------------------------------------------------------------------------- /src/views/permission/Page.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | 36 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: ZY 4 | * @Date: 2020-12-07 10:30:20 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-27 19:20:07 7 | */ 8 | import { createApp, Directive } from 'vue' 9 | import App from './App.vue' 10 | // import './pwa/registerServiceWorker' 11 | import router from './router' 12 | import { store } from './store' 13 | import { loadAllPlugins } from './plugins' 14 | import '@/assets/iconfont/iconfont.css' 15 | import '@/styles/index.scss' 16 | import 'normalize.css' 17 | import * as directives from '@/directives' 18 | import '@/permission' 19 | 20 | const app = createApp(App) 21 | // 加载所有插件 22 | loadAllPlugins(app) 23 | 24 | console.log(process.env.VUE_APP_BASE_API) 25 | 26 | // 自定义指令 27 | Object.keys(directives).forEach(key => { 28 | app.directive(key, (directives as { [key: string ]: Directive })[key]) 29 | }) 30 | 31 | app.use(store).use(router).mount('#app') 32 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-07 10:30:20 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-05 17:11:54 7 | */ 8 | module.exports = { 9 | presets: [ 10 | '@vue/cli-plugin-babel/preset' 11 | ], 12 | env: { 13 | development: { 14 | // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). 15 | // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. 16 | // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html 17 | plugins: [ 18 | 'dynamic-import-node' 19 | // [ 20 | // 'component', 21 | // { 22 | // libraryName: 'element-plus', 23 | // styleLibraryName: 'theme-chalk' 24 | // } 25 | // ] 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/config/default/layout.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 布局配置 3 | * @Author: ZY 4 | * @Date: 2020-12-25 10:31:11 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-04 16:55:16 7 | */ 8 | 9 | interface LayoutSettings { 10 | // Controls settings panel display 11 | showSettings: boolean 12 | // Controls tagsview display 13 | showTagsView: boolean 14 | // Controls siderbar logo display 15 | showSidebarLogo: boolean 16 | // If true, will fix the header component 17 | fixedHeader: boolean 18 | // If true, will change active text color for sidebar based on theme 19 | sidebarTextTheme: boolean 20 | } 21 | 22 | // You can customize below settings :) 23 | const layoutSettings: LayoutSettings = { 24 | showSettings: true, 25 | showTagsView: true, 26 | fixedHeader: false, 27 | showSidebarLogo: true, 28 | sidebarTextTheme: true, 29 | } 30 | 31 | export default layoutSettings -------------------------------------------------------------------------------- /src/directives/waves/waves.css: -------------------------------------------------------------------------------- 1 | .waves-ripple { 2 | position: absolute; 3 | border-radius: 100%; 4 | background-color: rgba(0, 0, 0, 0.15); 5 | background-clip: padding-box; 6 | pointer-events: none; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | -webkit-transform: scale(0); 12 | -ms-transform: scale(0); 13 | transform: scale(0); 14 | opacity: 1; 15 | } 16 | 17 | .waves-ripple.z-active { 18 | opacity: 0; 19 | -webkit-transform: scale(2); 20 | -ms-transform: scale(2); 21 | transform: scale(2); 22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out; 25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; 26 | } -------------------------------------------------------------------------------- /src/router/permissionModules/pdf.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: pdf打印 3 | * @Author: scy 4 | * @Date: 2021-01-21 20:13:03 5 | * @LastEditors: scy😊 6 | * @LastEditTime: 2021-01-25 11:36:25 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const PdfRouter: Array = [ 12 | { 13 | path: '/pdf', 14 | component: Layout, 15 | redirect: '/pdf/index', 16 | children: [ 17 | { 18 | path: 'index', 19 | component: () => import(/* webpackChunkName: "pdf" */ '@/views/pdf/Index.vue'), 20 | name: 'PDF', 21 | meta: { 22 | title: 'pdf', 23 | icon: '#iconpdf' 24 | } 25 | } 26 | ] 27 | }, 28 | { 29 | path: '/pdf-download-example', 30 | component: () => import(/* webpackChunkName: "pdf" */ '@/views/pdf/Download.vue'), 31 | meta: { hidden: true } 32 | } 33 | ] 34 | 35 | export default PdfRouter 36 | -------------------------------------------------------------------------------- /src/utils/cookies.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: cookies封装 3 | * @Author: ZY 4 | * @Date: 2020-12-17 16:06:33 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-17 17:23:14 7 | */ 8 | import Keys from '@/constant/key' 9 | import Cookies from 'js-cookie' 10 | 11 | export const getSidebarStatus = () => Cookies.get(Keys.sidebarStatusKey) 12 | export const setSidebarStatus = (sidebarStatus: string) => Cookies.set(Keys.sidebarStatusKey, sidebarStatus) 13 | 14 | export const getLanguage = () => Cookies.get(Keys.languageKey) 15 | export const setLanguage = (language: string) => Cookies.set(Keys.languageKey, language) 16 | 17 | export const getSize = () => Cookies.get(Keys.sizeKey) 18 | export const setSize = (size: string) => Cookies.set(Keys.sizeKey, size) 19 | 20 | export const getToken = () => Cookies.get(Keys.tokenKey) 21 | export const setToken = (token: string) => Cookies.set(Keys.tokenKey, token) 22 | export const removeToken = () => Cookies.remove(Keys.tokenKey) 23 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "suppressImplicitAnyIndexErrors":true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "types": [ 17 | "node", 18 | "webpack-env", 19 | "jest" 20 | ], 21 | "paths": { 22 | "@/*": [ 23 | "src/*" 24 | ] 25 | }, 26 | "lib": [ 27 | "esnext", 28 | "dom", 29 | "dom.iterable", 30 | "scripthost" 31 | ] 32 | }, 33 | "include": [ 34 | "src/**/*.ts", 35 | "src/**/*.tsx", 36 | "src/**/*.vue", 37 | "tests/**/*.ts", 38 | "tests/**/*.tsx" 39 | ], 40 | "exclude": [ 41 | "node_modules" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /mock/middleware/resultHandler.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: ZY 4 | * @Date: 2020-11-11 13:59:28 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-29 09:04:03 7 | */ 8 | import log from '../utils/logger' 9 | import { MiddleWare } from '../type' 10 | 11 | export type ResultInfo = { 12 | code:number; 13 | msg?:string; 14 | data?:any; 15 | err?:any 16 | } 17 | 18 | /** 19 | * 执行结果 handler 用来给每个响应对象包装响应码等 20 | */ 21 | export const ResultHandler: MiddleWare = () => async (ctx, next) => { 22 | const r :ResultInfo= {code:0}; 23 | try { 24 | const data:any = await next(); 25 | r.code = 0; 26 | r.msg = 'success' 27 | r.data = data; 28 | } catch (err) { 29 | log.error(err); 30 | log.error('xxx'+err.statusCode); 31 | r.code = err.statusCode 32 | switch (err.statusCode) { 33 | case 102: 34 | r.msg = "用户不存在"; 35 | break; 36 | 37 | default: 38 | break; 39 | } 40 | } 41 | ctx.body = r; 42 | }; -------------------------------------------------------------------------------- /src/apis/user.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 用户相关接口 3 | * @Author: ZY 4 | * @Date: 2020-12-28 14:40:50 5 | * @LastEditors: scy😊 6 | * @LastEditTime: 2021-01-20 10:17:09 7 | */ 8 | import { RootObject } from '@/model/rootObject' 9 | import { UserInfoModel, Users } from '@/model/userModel' 10 | import https from '@/utils/https' 11 | import { LoginModel } from '@/views/user-manager/login/model/loginModel' 12 | import { RequestParams, ContentType, Method } from 'axios-mapper' 13 | 14 | export const loginRequest = (userInfo: RequestParams) => { 15 | return https(false).request>('user/login', Method.POST, userInfo, ContentType.json) 16 | } 17 | 18 | export const userInfoRequest = () => { 19 | return https().request>('user/userInfo', Method.GET, undefined, ContentType.form) 20 | } 21 | 22 | export const getUsers = (user: any) => { 23 | return https().request>('user/getUsers', Method.GET, user, ContentType.form) 24 | } 25 | -------------------------------------------------------------------------------- /src/styles/_transition.scss: -------------------------------------------------------------------------------- 1 | /* Global transition */ 2 | // See https://vuejs.org/v2/guide/transitions.html for detail 3 | 4 | // fade 5 | .fade-enter-active, 6 | .fade-leave-active { 7 | transition: opacity 0.28s; 8 | } 9 | 10 | .fade-enter, 11 | .fade-leave-active { 12 | opacity: 0; 13 | } 14 | 15 | // fade-transform 16 | .fade-transform-leave-active, 17 | .fade-transform-enter-active { 18 | transition: all .5s; 19 | } 20 | 21 | .fade-transform-enter { 22 | opacity: 0; 23 | transform: translateX(-30px); 24 | } 25 | 26 | .fade-transform-leave-to { 27 | opacity: 0; 28 | transform: translateX(30px); 29 | } 30 | 31 | // breadcrumb 32 | .breadcrumb-enter-active, 33 | .breadcrumb-leave-active { 34 | transition: all .5s; 35 | } 36 | 37 | .breadcrumb-enter, 38 | .breadcrumb-leave-active { 39 | opacity: 0; 40 | transform: translateX(20px); 41 | } 42 | 43 | .breadcrumb-move { 44 | transition: all .5s; 45 | } 46 | 47 | .breadcrumb-leave-active { 48 | position: absolute; 49 | } 50 | -------------------------------------------------------------------------------- /src/router/permissionModules/errorPage.ts: -------------------------------------------------------------------------------- 1 | 2 | import { RouteRecordRaw } from 'vue-router' 3 | import Layout from '@/layout/Index.vue' 4 | 5 | const ExampleRouter: Array = [ 6 | { 7 | path: '/error', 8 | component: Layout, 9 | redirect: 'noredirect', 10 | meta: { 11 | title: 'errorPages', 12 | icon: '#icon404' 13 | }, 14 | children: [ 15 | { 16 | path: '401', 17 | component: () => import(/* webpackChunkName: "error-page-401" */ '@/views/error-page/401.vue'), 18 | name: 'Page401', 19 | meta: { 20 | title: 'page401', 21 | noCache: true 22 | } 23 | }, 24 | { 25 | path: '404', 26 | component: () => import(/* webpackChunkName: "error-page-404" */ '@/views/error-page/404.vue'), 27 | name: 'Page404', 28 | meta: { 29 | title: 'page404', 30 | noCache: true 31 | } 32 | } 33 | ] 34 | } 35 | ] 36 | 37 | export default ExampleRouter 38 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable arrow-body-style */ 2 | // https://docs.cypress.io/guides/guides/plugins-guide.html 3 | 4 | // if you need a custom webpack configuration you can uncomment the following import 5 | // and then use the `file:preprocessor` event 6 | // as explained in the cypress docs 7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples 8 | 9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */ 10 | // const webpack = require('@cypress/webpack-preprocessor') 11 | 12 | module.exports = (on, config) => { 13 | // on('file:preprocessor', webpack({ 14 | // webpackOptions: require('@vue/cli-service/webpack.config'), 15 | // watchOptions: {} 16 | // })) 17 | 18 | return Object.assign({}, config, { 19 | fixturesFolder: 'tests/e2e/fixtures', 20 | integrationFolder: 'tests/e2e/specs', 21 | screenshotsFolder: 'tests/e2e/screenshots', 22 | videosFolder: 'tests/e2e/videos', 23 | supportFile: 'tests/e2e/support/index.js' 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/model/userModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-29 15:57:00 5 | * @LastEditors: scy😊 6 | * @LastEditTime: 2021-01-15 09:33:11 7 | */ 8 | 9 | // { 10 | // "code": 0, 11 | // "msg": "success", 12 | // "data": { 13 | // "id": 0, 14 | // "username": "admin", 15 | // "password": "any", 16 | // "name": "Super Admin", 17 | // "avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif", 18 | // "introduction": "I am a super administrator", 19 | // "email": "admin@test.com", 20 | // "phone": "1234567890", 21 | // "roles": [ 22 | // "admin" 23 | // ] 24 | // } 25 | // } 26 | 27 | export interface UserInfoModel { 28 | id: number 29 | username: string 30 | password: string 31 | name: string 32 | avatar: string 33 | introduction: string 34 | email: string 35 | phone: string 36 | roles: string[] 37 | } 38 | 39 | export interface Users { 40 | items: any 41 | } 42 | -------------------------------------------------------------------------------- /src/router/permissionModules/zip.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: scy😊 4 | * @Date: 2021-01-25 11:22:42 5 | * @LastEditors: scy😊 6 | * @LastEditTime: 2021-01-25 11:23:53 7 | */ 8 | 9 | /* 10 | * @Description: pdf打印 11 | * @Author: scy 12 | * @Date: 2021-01-21 20:13:03 13 | * @LastEditors: scy 14 | * @LastEditTime: 2021-01-21 21:24:27 15 | */ 16 | import { RouteRecordRaw } from 'vue-router' 17 | import Layout from '@/layout/Index.vue' 18 | 19 | const ZipeRouter: Array = [ 20 | { 21 | path: '/zip', 22 | component: Layout, 23 | redirect: '/zip/download', 24 | meta: { 25 | title: 'zip', 26 | icon: '#iconzip', 27 | alwaysShow: true // will always show the root menu 28 | }, 29 | children: [ 30 | { 31 | path: 'download', 32 | component: () => import(/* webpackChunkName: "zip" */ '@/views/zip/Index.vue'), 33 | name: 'ExportZip', 34 | meta: { title: 'exportZip' } 35 | } 36 | ] 37 | } 38 | ] 39 | export default ZipeRouter 40 | -------------------------------------------------------------------------------- /mock/controller/role.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 角色权限 3 | * @Author: ZY 4 | * @Date: 2021-01-12 15:20:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-12 16:45:25 7 | */ 8 | 9 | import roles,{routes} from "../mockdb/role"; 10 | import faker from "faker"; 11 | import { post, prefix ,get,put,del} from "../requestDecorator"; 12 | 13 | @prefix('/roles') 14 | export default class Roles { 15 | 16 | @get('/getRoles') 17 | async getRoles() { 18 | return { 19 | total: roles.length, 20 | items: roles 21 | } 22 | } 23 | 24 | @put('/createRole') 25 | async createRole(){ 26 | return { 27 | key: faker.random.number({ min: 3, max: 10000 }) 28 | } 29 | } 30 | 31 | @post('/updateRole') 32 | async updateRole(ctx:any){ 33 | const {role} = ctx.request.body 34 | return { 35 | role 36 | } 37 | } 38 | 39 | @del('/deleteRole') 40 | async deleteRole(){ 41 | return 'delete success' 42 | } 43 | 44 | @get('/getRoutes') 45 | async getRoutes() { 46 | return { 47 | routes 48 | } 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/components/tinymce/config.ts: -------------------------------------------------------------------------------- 1 | // Import plugins that you want to use 2 | // Detail plugins list see: https://www.tiny.cloud/apps/#core-plugins 3 | // Custom builds see: https://www.tiny.cloud/get-tiny/custom-builds/ 4 | export const plugins = ['advlist anchor autolink autoresize autosave charmap code codesample directionality emoticons fullpage fullscreen help hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textpattern visualblocks visualchars wordcount'] 5 | 6 | // Here is the list of toolbar control components 7 | // Details see: https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols 8 | export const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample help', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons charmap forecolor backcolor fullpage fullscreen'] 9 | -------------------------------------------------------------------------------- /src/config/default/net.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-08 09:45:25 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-26 12:27:00 7 | */ 8 | 9 | import { ContentType, Device } from '@/constant/headers' 10 | import { InfoShowType } from '@/constant/network' 11 | import settings from "./setting.config"; 12 | interface Headers{ 13 | token: string 14 | contentType: string 15 | version: string 16 | device: Device 17 | } 18 | 19 | const _header: Headers = { 20 | token: '', 21 | contentType: ContentType.JSON, 22 | version: settings.version ?? '1.0', 23 | device: Device.PC 24 | } 25 | 26 | export interface NetworkConfig{ 27 | host?: string 28 | timeout?: number 29 | loading?: false 30 | errorShowType?: InfoShowType 31 | header?: {} 32 | } 33 | 34 | const networkConfig: NetworkConfig = { 35 | host: process.env.VUE_APP_BASE_API, 36 | timeout: 10000, 37 | loading: false, 38 | errorShowType: InfoShowType.LOG, 39 | header: _header 40 | } 41 | 42 | export default networkConfig 43 | -------------------------------------------------------------------------------- /src/layout/components/side_bar/SidebarItemLink.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 52 | -------------------------------------------------------------------------------- /src/views/table/dynamic-table/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 21 | 22 | 42 | -------------------------------------------------------------------------------- /src/views/dashboard/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | 42 | -------------------------------------------------------------------------------- /src/components/hamburger/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 44 | 45 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 rcyj 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. 22 | -------------------------------------------------------------------------------- /src/views/excel/components/AutoWidthOption.vue: -------------------------------------------------------------------------------- 1 | 8 | 27 | 28 | 54 | -------------------------------------------------------------------------------- /docker/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | client_max_body_size 100m; 6 | client_body_buffer_size 128k; 7 | proxy_connect_timeout 5; 8 | proxy_send_timeout 1800; 9 | proxy_read_timeout 1800; 10 | proxy_buffer_size 4k; 11 | proxy_buffers 4 32k; 12 | proxy_busy_buffers_size 64k; 13 | proxy_temp_file_write_size 64k; 14 | auth_basic "status"; 15 | 16 | location / { 17 | root /usr/share/nginx/html/release; 18 | index index.html index.htm; 19 | try_files $uri $uri/ /index.html; #VUE项目,配置路由(必须) 20 | } 21 | 22 | location ^~ /assessh5 { 23 | alias /usr/share/nginx/html/release; # inflow uni-app H5编译文件的目录,index.html所在目录 24 | try_files $uri $uri/ /index.html last; 25 | index index.html index.htm; 26 | } 27 | 28 | # location /inflow { 29 | # try_files $uri $uri/ /inflow/index.html; 30 | # root /usr/share/nginx/html/inflow/; 31 | # index index.html; 32 | # } 33 | 34 | error_page 500 502 503 504 /50x.html; 35 | location = /50x.html { 36 | root /usr/share/nginx/html; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mock/controller/user.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 用户相关接口 3 | * @Author: ZY 4 | * @Date: 2020-12-28 09:46:46 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-23 15:54:34 7 | */ 8 | 9 | import { post, prefix ,get} from "../requestDecorator"; 10 | import userList from "../mockdb/userList"; 11 | import * as Koa from 'koa'; 12 | @prefix('/user') 13 | export default class User { 14 | 15 | @post('/login') 16 | async login(ctx:any) { 17 | const {username} = ctx.request.body 18 | for (const user of userList) { 19 | if (user.username === username) { 20 | return { 21 | accessToken: username + '-token' 22 | } 23 | } 24 | } 25 | return ctx.throw(401); 26 | } 27 | 28 | @get('/userInfo') 29 | async getUserInfo(ctx:Koa.Context){ 30 | let token = ctx.request.header.token 31 | return token === 'admin-token' ? userList[0] : userList[1] 32 | } 33 | 34 | @get('/getUsers') 35 | async getUsers(ctx:any){ 36 | const { name } = ctx.query 37 | const users = userList.filter(user => { 38 | const lowerCaseName = user.name.toLowerCase() 39 | return !(name && lowerCaseName.indexOf((name as string).toLowerCase()) < 0) 40 | }) 41 | return{ 42 | code: 20000, 43 | data: { 44 | items: users 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/apis/roles.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 权限先关接口 3 | * @Author: ZY 4 | * @Date: 2021-01-12 16:37:09 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-13 16:50:20 7 | */ 8 | 9 | import { RolesModels } from '@/model/getRolesModel' 10 | import { RootObject } from '@/model/rootObject' 11 | import { Routes } from '@/model/routesModel' 12 | import https from '@/utils/https' 13 | import { RequestParams, ContentType, Method } from 'axios-mapper' 14 | 15 | export const getRoutes = () => { 16 | return https().request>('roles/getRoutes', Method.GET, undefined, ContentType.form) 17 | } 18 | 19 | export const getRoles = () => { 20 | return https().request>('roles/getRoles', Method.GET, undefined, ContentType.form) 21 | } 22 | 23 | export const delRole = (id: number) => { 24 | return https().request>('roles/deleteRole', Method.DELETE, { id }, ContentType.form) 25 | } 26 | 27 | export const updateRole = (id: number, data: any) => { 28 | return https().request>(`roles/updateRole/${id}`, Method.POST, data, ContentType.form) 29 | } 30 | 31 | export const createRole = (role: RequestParams) => { 32 | return https().request>('roles/createRole', Method.PUT, role, ContentType.form) 33 | } 34 | -------------------------------------------------------------------------------- /src/views/permission/components/SwitchRoles.vue: -------------------------------------------------------------------------------- 1 | 8 | 20 | 42 | -------------------------------------------------------------------------------- /src/views/excel/components/FilenameOption.vue: -------------------------------------------------------------------------------- 1 | 8 | 22 | 23 | 54 | -------------------------------------------------------------------------------- /src/locales/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-18 15:23:57 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-20 11:12:08 7 | */ 8 | import { createI18n } from 'vue-i18n' // import from runtime only 9 | 10 | import { getLanguage } from '@/utils/cookies' 11 | 12 | import elementEnLocale from 'element-plus/lib/locale/lang/en' 13 | import elementZhLocale from 'element-plus/lib/locale/lang/zh-cn' 14 | 15 | // User defined lang 16 | import enLocale from './en' 17 | import zhLocale from './zh-cn' 18 | 19 | const messages = { 20 | en: { 21 | ...enLocale, 22 | ...elementEnLocale 23 | }, 24 | 'zh-cn': { 25 | ...zhLocale, 26 | ...elementZhLocale 27 | } 28 | } 29 | 30 | export const getLocale = () => { 31 | const cookieLanguage = getLanguage() 32 | if (cookieLanguage) { 33 | return cookieLanguage 34 | } 35 | const language = navigator.language.toLowerCase() 36 | const locales = Object.keys(messages) 37 | for (const locale of locales) { 38 | if (language.indexOf(locale) > -1) { 39 | return locale 40 | } 41 | } 42 | 43 | // Default language is english 44 | return 'zh' 45 | } 46 | 47 | const i18n = createI18n({ 48 | locale: getLocale(), 49 | messages: messages 50 | }) 51 | 52 | export default i18n 53 | -------------------------------------------------------------------------------- /src/store/modules/settings/actions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app actions 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-26 14:19:03 7 | */ 8 | import { ActionTree, ActionContext } from 'vuex' 9 | 10 | // eslint-disable-next-line import/no-cycle 11 | import { RootState } from '@/store' 12 | import { SettingsState } from './state' 13 | import { Mutations } from './mutations' 14 | import { SettingsMutationTypes } from './mutation-types' 15 | import { SettingsActionTypes } from './action-types' 16 | type AugmentedActionContext = { 17 | commit( 18 | key: K, 19 | payload: Parameters[1], 20 | ): ReturnType 21 | } & Omit, 'commit'> 22 | 23 | export interface Actions { 24 | [SettingsActionTypes.ACTION_CHANGE_SETTING]( 25 | { commit }: AugmentedActionContext, 26 | payload: {key: string, value: any} 27 | ): void 28 | } 29 | 30 | export const actions: ActionTree & Actions = { 31 | [SettingsActionTypes.ACTION_CHANGE_SETTING]( 32 | { commit }: AugmentedActionContext, 33 | payload: {key: string, value: any} 34 | ) { 35 | commit(SettingsMutationTypes.CHANGE_SETTING, payload) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mock/mockdb/role.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 角色列表 3 | * @Author: ZY 4 | * @Date: 2021-01-12 15:25:09 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-12 16:18:19 7 | */ 8 | 9 | import { RoleBean } from "controller/type"; 10 | import { asyncRoutes, constantRoutes } from './routes' 11 | 12 | export const routes = [...constantRoutes, ...asyncRoutes] 13 | 14 | const roles: RoleBean[] = [ 15 | { 16 | key: 'admin', 17 | name: 'admin', 18 | description: 'Super Administrator. Have access to view all pages.', 19 | routes: routes 20 | }, 21 | { 22 | key: 'editor', 23 | name: 'editor', 24 | description: 'Normal Editor. Can see all pages except permission page', 25 | routes: routes.filter(i => i.path !== '/permission') // Just a mock 26 | }, 27 | { 28 | key: 'visitor', 29 | name: 'visitor', 30 | description: 'Just a visitor. Can only see the home page and the document page', 31 | routes: [{ 32 | path: '', 33 | redirect: 'dashboard', 34 | children: [ 35 | { 36 | path: 'dashboard', 37 | name: 'Dashboard', 38 | meta: { title: 'dashboard', icon: 'dashboard' } 39 | } 40 | ] 41 | }] 42 | } 43 | ] 44 | export default roles -------------------------------------------------------------------------------- /src/views/guide/steps.ts: -------------------------------------------------------------------------------- 1 | const steps = [ 2 | { 3 | element: '#hamburger-container', 4 | popover: { 5 | title: 'Hamburger', 6 | description: 'Open && Close sidebar', 7 | position: 'bottom' 8 | } 9 | }, 10 | { 11 | element: '#breadcrumb-container', 12 | popover: { 13 | title: 'Breadcrumb', 14 | description: 'Indicate the current page location', 15 | position: 'bottom' 16 | } 17 | }, 18 | { 19 | element: '#header-search', 20 | popover: { 21 | title: 'Page Search', 22 | description: 'Page search, quick navigation', 23 | position: 'left' 24 | } 25 | }, 26 | { 27 | element: '#screenfull', 28 | popover: { 29 | title: 'Screenfull', 30 | description: 'Set the page into fullscreen', 31 | position: 'left' 32 | } 33 | }, 34 | { 35 | element: '#size-select', 36 | popover: { 37 | title: 'Switch Size', 38 | description: 'Switch the system size', 39 | position: 'left' 40 | } 41 | }, 42 | { 43 | element: '#tags-view-container', 44 | popover: { 45 | title: 'Tags view', 46 | description: 'The history of the page you visited', 47 | position: 'bottom' 48 | }, 49 | padding: 0 50 | } 51 | ] 52 | 53 | export default steps 54 | -------------------------------------------------------------------------------- /src/views/guide/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 26 | 27 | 57 | -------------------------------------------------------------------------------- /src/store/modules/settings/mutations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app Mutations 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-20 17:12:28 7 | */ 8 | import { MutationTree } from 'vuex' 9 | import { SettingsState } from './state' 10 | import { SettingsMutationTypes } from './mutation-types' 11 | 12 | export type Mutations = { 13 | [SettingsMutationTypes.CHANGE_SETTING](state: S, payload: { key: string, value: any }): void 14 | 15 | } 16 | 17 | export const mutations: MutationTree & Mutations = { 18 | [SettingsMutationTypes.CHANGE_SETTING](state: SettingsState, payload: { key: string, value: any }) { 19 | const { key, value } = payload 20 | switch (key) { 21 | case 'theme': 22 | state.theme = value 23 | break 24 | case 'fixedHeader': 25 | state.fixedHeader = value 26 | break 27 | case 'showSettings': 28 | state.showSettings = value 29 | break 30 | case 'showSidebarLogo': 31 | state.showSidebarLogo = value 32 | break 33 | case 'showTagsView': 34 | state.showTagsView = value 35 | break 36 | case 'sidebarTextTheme': 37 | state.sidebarTextTheme = value 38 | break 39 | default: 40 | break 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/store/modules/app/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app moudle 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-23 11:51:40 7 | */ 8 | import { 9 | Store as VuexStore, 10 | CommitOptions, 11 | DispatchOptions, 12 | Module 13 | } from 'vuex' 14 | 15 | // TODO: How to surpass cyclical dependency linting errors cleanly? 16 | import { RootState } from '@/store' 17 | import { state } from './state' 18 | import { mutations, Mutations } from './mutations' 19 | import { actions, Actions } from './actions' 20 | import type { AppState } from './state' 21 | 22 | export { AppState } 23 | 24 | export type AppStore = Omit, 'getters' | 'commit' | 'dispatch'> 25 | & { 26 | commit[1]>( 27 | key: K, 28 | payload: P, 29 | options?: CommitOptions 30 | ): ReturnType 31 | } & { 32 | dispatch( 33 | key: K, 34 | payload: Parameters[1], 35 | options?: DispatchOptions 36 | ): ReturnType 37 | }; 38 | export const store: Module = { 39 | state, 40 | mutations, 41 | actions 42 | // TODO: With namespaced option turned on, having problem how to use dispatch with action types... 43 | // But without it, a bigger store might have clashes in namings 44 | // namespaced: true, 45 | } 46 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/components/UpdateTimeline.vue: -------------------------------------------------------------------------------- 1 | 8 | 45 | 46 | 55 | 56 | 59 | -------------------------------------------------------------------------------- /src/store/modules/user/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: setting moudle 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-05 15:39:26 7 | */ 8 | import { 9 | Store as VuexStore, 10 | CommitOptions, 11 | DispatchOptions, 12 | Module 13 | } from 'vuex' 14 | 15 | // TODO: How to surpass cyclical dependency linting errors cleanly? 16 | import { RootState } from '@/store' 17 | import { state } from './state' 18 | import { mutations, Mutations } from './mutations' 19 | import { actions, Actions } from './actions' 20 | import type { UserState } from './state' 21 | 22 | export { UserState } 23 | 24 | export type UserStore = Omit, 'getters' | 'commit' | 'dispatch'> 25 | & { 26 | commit[1]>( 27 | key: K, 28 | payload: P, 29 | options?: CommitOptions 30 | ): ReturnType 31 | } & { 32 | dispatch( 33 | key: K, 34 | payload: Parameters[1], 35 | options?: DispatchOptions 36 | ): ReturnType 37 | }; 38 | export const store: Module = { 39 | state, 40 | mutations, 41 | actions 42 | // TODO: With namespaced option turned on, having problem how to use dispatch with action types... 43 | // But without it, a bigger store might have clashes in namings 44 | // namespaced: true, 45 | } 46 | -------------------------------------------------------------------------------- /src/store/modules/tagsview/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app moudle 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-05 15:39:49 7 | */ 8 | import { 9 | Store as VuexStore, 10 | CommitOptions, 11 | DispatchOptions, 12 | Module 13 | } from 'vuex' 14 | 15 | // TODO: How to surpass cyclical dependency linting errors cleanly? 16 | import { RootState } from '@/store' 17 | import { state } from './state' 18 | import { mutations, Mutations } from './mutations' 19 | import { actions, Actions } from './actions' 20 | import type { TagsViewState } from './state' 21 | 22 | export { TagsViewState } 23 | 24 | export type TagsStore = Omit, 'getters' | 'commit' | 'dispatch'> 25 | & { 26 | commit[1]>( 27 | key: K, 28 | payload: P, 29 | options?: CommitOptions 30 | ): ReturnType 31 | } & { 32 | dispatch( 33 | key: K, 34 | payload: Parameters[1], 35 | options?: DispatchOptions 36 | ): ReturnType 37 | }; 38 | export const store: Module = { 39 | state, 40 | mutations, 41 | actions 42 | // TODO: With namespaced option turned on, having problem how to use dispatch with action types... 43 | // But without it, a bigger store might have clashes in namings 44 | // namespaced: true, 45 | } 46 | -------------------------------------------------------------------------------- /src/store/modules/settings/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: setting moudle 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-26 14:20:15 7 | */ 8 | import { 9 | Store as VuexStore, 10 | CommitOptions, 11 | DispatchOptions, 12 | Module 13 | } from 'vuex' 14 | 15 | // TODO: How to surpass cyclical dependency linting errors cleanly? 16 | import { RootState } from '@/store' 17 | import { state } from './state' 18 | import { mutations, Mutations } from './mutations' 19 | import { actions, Actions } from './actions' 20 | import type { SettingsState } from './state' 21 | 22 | export { SettingsState } 23 | 24 | export type SettingStore = Omit, 'getters' | 'commit' | 'dispatch'> 25 | & { 26 | commit[1]>( 27 | key: K, 28 | payload: P, 29 | options?: CommitOptions 30 | ): ReturnType 31 | } & { 32 | dispatch( 33 | key: K, 34 | payload: Parameters[1], 35 | options?: DispatchOptions 36 | ): ReturnType 37 | }; 38 | export const store: Module = { 39 | state, 40 | mutations, 41 | actions 42 | // TODO: With namespaced option turned on, having problem how to use dispatch with action types... 43 | // But without it, a bigger store might have clashes in namings 44 | // namespaced: true, 45 | } 46 | -------------------------------------------------------------------------------- /src/views/excel/components/BookTypeOption.vue: -------------------------------------------------------------------------------- 1 | 8 | 25 | 26 | 62 | -------------------------------------------------------------------------------- /src/store/modules/permission/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: permission moudle 3 | * @Author: ZY 4 | * @Date: 2020-12-26 13:45:52 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-26 14:20:31 7 | */ 8 | 9 | import { 10 | Store as VuexStore, 11 | CommitOptions, 12 | DispatchOptions, 13 | Module 14 | } from 'vuex' 15 | 16 | // TODO: How to surpass cyclical dependency linting errors cleanly? 17 | import { RootState } from '@/store' 18 | import { mutations, Mutations } from './mutations' 19 | import { actions, Actions } from './actions' 20 | import type { PermissionState } from './state' 21 | import { state } from './state' 22 | 23 | export { PermissionState } 24 | 25 | export type PermissionStore = Omit, 'getters' | 'commit' | 'dispatch'> 26 | & { 27 | commit[1]>( 28 | key: K, 29 | payload: P, 30 | options?: CommitOptions 31 | ): ReturnType 32 | } & { 33 | dispatch( 34 | key: K, 35 | payload: Parameters[1], 36 | options?: DispatchOptions 37 | ): ReturnType 38 | }; 39 | export const store: Module = { 40 | state, 41 | mutations, 42 | actions 43 | // TODO: With namespaced option turned on, having problem how to use dispatch with action types... 44 | // But without it, a bigger store might have clashes in namings 45 | // namespaced: true, 46 | } 47 | -------------------------------------------------------------------------------- /src/views/example/components/Dropdown/SourceUrl.vue: -------------------------------------------------------------------------------- 1 | 8 | 37 | 38 | 62 | -------------------------------------------------------------------------------- /mock/mockdb/userList.ts: -------------------------------------------------------------------------------- 1 | import { UserBean } from "controller/type"; 2 | 3 | /* 4 | * @Description: 所有的用户列表 5 | * @Author: ZY 6 | * @Date: 2020-12-28 14:58:13 7 | * @LastEditors: ZY 8 | * @LastEditTime: 2020-12-28 15:16:58 9 | */ 10 | import faker from 'faker' 11 | 12 | const userList: UserBean[] = [ 13 | { 14 | id: 0, 15 | username: 'admin', 16 | password: 'any', 17 | name: 'Super Admin', 18 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 19 | introduction: 'I am a super administrator', 20 | email: 'admin@test.com', 21 | phone: '1234567890', 22 | roles: ['admin'], 23 | }, 24 | { 25 | id: 1, 26 | username: 'editor', 27 | password: 'any', 28 | name: 'Normal Editor', 29 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 30 | introduction: 'I am an editor', 31 | email: 'editor@test.com', 32 | phone: '1234567890', 33 | roles: ['editor'], 34 | } 35 | ] 36 | 37 | const userCount = 100 38 | 39 | for (let i = 2; i < userCount; i++) { 40 | userList.push({ 41 | id: i, 42 | username: 'user_' + faker.random.alphaNumeric(9), 43 | password: faker.random.alphaNumeric(20), 44 | name: faker.name.findName(), 45 | avatar: faker.image.imageUrl(), 46 | introduction: faker.lorem.sentence(20), 47 | email: faker.internet.email(), 48 | phone: faker.phone.phoneNumber(), 49 | roles: ['visitor'] 50 | }) 51 | } 52 | 53 | export default userList -------------------------------------------------------------------------------- /src/views/profile/components/Timeline.vue: -------------------------------------------------------------------------------- 1 | 8 | 25 | 26 | 59 | -------------------------------------------------------------------------------- /mock/router.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-10 10:09:06 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-10 14:41:18 7 | */ 8 | import 'reflect-metadata' 9 | import fs from 'fs' 10 | import path from 'path' 11 | import {ROUTER_MAP, BASE_PATH_MAP} from './constant' 12 | import {RouteMeta, PathMeta} from './type' 13 | import Router from 'koa-router' 14 | 15 | const addRouter = (router: Router) => { 16 | const ctrPath = path.join(__dirname, 'controller'); 17 | const modules: ObjectConstructor[] = []; 18 | // 扫描controller文件夹,收集所有controller 19 | fs.readdirSync(ctrPath).forEach(name => { 20 | if (/^[^.]+\.(t|j)s$/.test(name)) { 21 | modules.push(require(path.join(ctrPath, name)).default) 22 | } 23 | }); 24 | 25 | // 结合meta数据添加路由 和 验证 26 | modules.forEach(m => { 27 | const routerMap: RouteMeta[] = Reflect.getMetadata(ROUTER_MAP, m, 'method') || []; 28 | const basePathMap: PathMeta[] = Reflect.getMetadata(BASE_PATH_MAP, m) || []; 29 | const basePath:PathMeta = basePathMap.pop(); 30 | if (routerMap.length) { 31 | const ctr = new m(); 32 | routerMap.forEach(route => { 33 | // const {name, method, path, isVerify} = route; 34 | const {name, method, path} = route; 35 | const newPath:String = basePath ? (basePath.path + path) : path; 36 | // router[method](newPath, jwt(newPath, isVerify), ctr[name]); 37 | router[method](newPath, ctr[name]); 38 | }) 39 | } 40 | }) 41 | } 42 | 43 | export default addRouter -------------------------------------------------------------------------------- /src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 20 | 21 | 43 | 44 | 70 | -------------------------------------------------------------------------------- /src/router/permissionModules/example.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description:文章综合实例 3 | * @Author: scy 4 | * @Date: 2021-01-21 20:32:56 5 | * @LastEditors: scy 6 | * @LastEditTime: 2021-01-21 21:21:28 7 | */ 8 | 9 | import { RouteRecordRaw } from 'vue-router' 10 | import Layout from '@/layout/Index.vue' 11 | 12 | const ExampleRouter: Array = [ 13 | { 14 | path: '/example', 15 | component: Layout, 16 | redirect: '/example/list', 17 | meta: { 18 | title: 'example', 19 | icon: '#iconexample' 20 | }, 21 | children: [ 22 | { 23 | path: 'create', 24 | component: () => import(/* webpackChunkName: "example-create" */ '@/views/example/Create.vue'), 25 | name: 'CreateArticle', 26 | meta: { 27 | title: 'createArticle', 28 | icon: 'edit' 29 | } 30 | }, 31 | { 32 | path: 'edit/:id(\\d+)', 33 | component: () => import(/* webpackChunkName: "example-edit" */ '@/views/example/Edit.vue'), 34 | name: 'EditArticle', 35 | meta: { 36 | title: 'editArticle', 37 | noCache: true, 38 | activeMenu: '/example/list', 39 | hidden: true 40 | } 41 | }, 42 | { 43 | path: 'list', 44 | component: () => import(/* webpackChunkName: "example-list" */ '@/views/example/List.vue'), 45 | name: 'ArticleList', 46 | meta: { 47 | title: 'articleList', 48 | icon: 'list' 49 | } 50 | } 51 | ] 52 | } 53 | ] 54 | 55 | export default ExampleRouter 56 | -------------------------------------------------------------------------------- /src/store/modules/user/mutations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app Mutations 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-28 11:45:02 7 | */ 8 | import { MutationTree } from 'vuex' 9 | import { UserState } from './state' 10 | import { UserMutationTypes } from './mutation-types' 11 | 12 | export type Mutations = { 13 | [UserMutationTypes.SET_TOKEN](state: S, token: string): void 14 | [UserMutationTypes.SET_NAME](state: S, name: string): void 15 | [UserMutationTypes.SET_AVATAR](state: S, avatar: string): void 16 | [UserMutationTypes.SET_INTRODUCTION](state: S, introduction: string): void 17 | [UserMutationTypes.SET_ROLES](state: S, roles: string[]): void 18 | [UserMutationTypes.SET_EMAIL](state: S, email: string): void 19 | } 20 | 21 | export const mutations: MutationTree & Mutations = { 22 | [UserMutationTypes.SET_TOKEN](state: UserState, token: string) { 23 | state.token = token 24 | }, 25 | 26 | [UserMutationTypes.SET_NAME](state: UserState, name: string) { 27 | state.name = name 28 | }, 29 | 30 | [UserMutationTypes.SET_AVATAR](state: UserState, avatar: string) { 31 | state.avatar = avatar 32 | }, 33 | 34 | [UserMutationTypes.SET_INTRODUCTION](state: UserState, introduction: string) { 35 | state.introduction = introduction 36 | }, 37 | 38 | [UserMutationTypes.SET_ROLES](state: UserState, roles: string[]) { 39 | state.roles = roles 40 | }, 41 | 42 | [UserMutationTypes.SET_EMAIL](state: UserState, email: string) { 43 | state.email = email 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/components/right_panel/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | 28 | 51 | 52 | 74 | -------------------------------------------------------------------------------- /src/views/components-demo/DraggableListDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 29 | 58 | -------------------------------------------------------------------------------- /src/router/permissionModules/excel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: excel相关 3 | * @Author: scy 4 | * @Date: 2021-01-21 20:13:03 5 | * @LastEditors: scy 6 | * @LastEditTime: 2021-01-21 21:24:27 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const ExcelRouter: Array = [ 12 | 13 | { 14 | path: '/excel', 15 | component: Layout, 16 | redirect: '/excel/export-excel', 17 | meta: { 18 | title: 'excel', 19 | icon: '#iconexcel' 20 | }, 21 | children: [ 22 | { 23 | path: 'export-excel', 24 | component: () => import(/* webpackChunkName: "export-excel" */ '@/views/excel/ExportExcel.vue'), 25 | name: 'ExportExcel', 26 | meta: { title: 'exportExcel' } 27 | }, 28 | { 29 | path: 'export-selected-excel', 30 | component: () => import(/* webpackChunkName: "select-excel" */ '@/views/excel/SelectExcel.vue'), 31 | name: 'SelectExcel', 32 | meta: { title: 'selectExcel' } 33 | }, 34 | { 35 | path: 'export-merge-header', 36 | component: () => import(/* webpackChunkName: "merge-header" */ '@/views/excel/MergeHeader.vue'), 37 | name: 'MergeHeader', 38 | meta: { title: 'mergeHeader' } 39 | }, 40 | { 41 | path: 'upload-excel', 42 | component: () => import(/* webpackChunkName: "upload-excel" */ '@/views/excel/UploadExcel.vue'), 43 | name: 'UploadExcel', 44 | meta: { title: 'uploadExcel' } 45 | } 46 | ] 47 | } 48 | ] 49 | 50 | export default ExcelRouter 51 | -------------------------------------------------------------------------------- /src/router/permissionModules/charts.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2021-01-08 19:21:46 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-21 21:26:26 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const chartsRouter: Array = [ 12 | { 13 | path: '/charts', 14 | component: Layout, 15 | redirect: 'noredirect', 16 | name: 'Charts', 17 | meta: { 18 | title: 'charts', 19 | icon: '#iconchart1' 20 | }, 21 | children: [ 22 | { 23 | path: 'bar-chart', 24 | component: () => 25 | import( 26 | /* webpackChunkName: "BarChart" */ '@/views/charts/BarChartDemo.vue' 27 | ), 28 | name: 'BarChartDemo', 29 | meta: { 30 | title: 'barChart', 31 | noCache: true 32 | } 33 | }, 34 | { 35 | path: 'line-chart', 36 | component: () => 37 | import( 38 | /* webpackChunkName: "LineChart" */ '@/views/charts/LineChartDemo.vue' 39 | ), 40 | name: 'LineChartDemo', 41 | meta: { 42 | title: 'lineChart', 43 | noCache: true 44 | } 45 | }, 46 | { 47 | path: 'mixed-chart', 48 | component: () => 49 | import( 50 | /* webpackChunkName: "MixedChat" */ '@/views/charts/MixedChatDemo.vue' 51 | ), 52 | name: 'MixedChartDemo', 53 | meta: { 54 | title: 'mixedChart', 55 | noCache: true 56 | } 57 | } 58 | ] 59 | } 60 | ] 61 | 62 | export default chartsRouter 63 | -------------------------------------------------------------------------------- /src/router/permissionModules/table.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description:表格相关 3 | * @Author: scy 4 | * @Date: 2021-01-08 19:21:46 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-26 13:37:09 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const tableRouter: Array = [ 12 | { 13 | path: '/table', 14 | component: Layout, 15 | redirect: '/table/complex-table', 16 | name: 'Table', 17 | meta: { 18 | title: 'table', 19 | icon: '#icontable' 20 | }, 21 | children: [ 22 | { 23 | path: 'dynamic-table', 24 | component: () => import(/* webpackChunkName: "dynamic-table" */ '@/views/table/dynamic-table/Index.vue'), 25 | name: 'ComplexTable', 26 | meta: { title: 'complexTable' } 27 | }, 28 | { 29 | path: 'Draggable_table', 30 | component: () => import(/* webpackChunkName: "draggable-table" */ '@/views/table/DraggableTable.vue'), 31 | name: 'DraggableTable', 32 | meta: { title: 'draggableTable' } 33 | }, 34 | { 35 | path: 'Inline_edit_table', 36 | component: () => import(/* webpackChunkName: "inline-edit-table" */ '@/views/table/InlineEditTable.vue'), 37 | name: 'InlineEditTable', 38 | meta: { title: 'inlineEditTable' } 39 | }, 40 | { 41 | path: 'Complex_table', 42 | component: () => import(/* webpackChunkName: "complex-table" */ '@/views/table/ComplexTable.vue'), 43 | name: 'DynamicTable', 44 | meta: { title: 'dynamicTable' } 45 | } 46 | ] 47 | } 48 | ] 49 | 50 | export default tableRouter 51 | -------------------------------------------------------------------------------- /src/views/example/components/Dropdown/Comment.vue: -------------------------------------------------------------------------------- 1 | 8 | 38 | 39 | 66 | -------------------------------------------------------------------------------- /src/views/excel/UploadExcel.vue: -------------------------------------------------------------------------------- 1 | 8 | 29 | 30 | 62 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: st 3 | * @Author: ZY 4 | * @Date: 2020-12-07 10:30:20 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-08 20:46:07 7 | */ 8 | import { createStore, createLogger } from 'vuex' 9 | // import createPersistedState from 'vuex-persistedstate' 10 | import { store as app, AppStore, AppState } from '@/store/modules/app' 11 | import { store as settings, SettingStore, SettingsState } from '@/store/modules/settings' 12 | import { store as permission, PermissionStore, PermissionState } from '@/store/modules/permission' 13 | import { store as user, UserStore, UserState } from '@/store/modules/user' 14 | import { store as tagViews, TagsStore, TagsViewState } from '@/store/modules/tagsview' 15 | 16 | export interface RootState { 17 | app: AppState 18 | settings: SettingsState 19 | permission: PermissionState 20 | user: UserState 21 | tagViews: TagsViewState 22 | } 23 | 24 | export type Store = AppStore> & SettingStore> 25 | & PermissionStore> & UserStore> 26 | & TagsStore> 27 | 28 | // Plug in logger when in development environment 29 | const debug = process.env.NODE_ENV !== 'production' 30 | const plugins = debug ? [createLogger({})] : [] 31 | // Plug in session storage based persistence 32 | // plugins.push(createPersistedState({ storage: window.sessionStorage })) 33 | 34 | export const store = createStore({ 35 | plugins, 36 | modules: { 37 | app, 38 | settings, 39 | permission, 40 | user, 41 | tagViews 42 | } 43 | }) 44 | 45 | export function useStore(): Store { 46 | return store as Store 47 | } 48 | -------------------------------------------------------------------------------- /src/config/default/theme.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 主题配置 3 | * @Author: ZY 4 | * @Date: 2020-12-08 09:45:59 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-09 10:16:28 7 | */ 8 | // 布局枚举 9 | enum Layout{ 10 | Horizontal, 11 | Vertical, 12 | Gallery, 13 | Comprehensive, 14 | Common 15 | } 16 | // 主题分类 17 | enum ThemeName{ 18 | Default, 19 | Ocean, 20 | Green, 21 | Glory, 22 | White 23 | } 24 | 25 | export interface Theme{ 26 | // 布局种类 horizontal vertical gallery comprehensive common 27 | layout?: Layout 28 | // 主题名称 default ocean green glory white 29 | themeName?: ThemeName 30 | // 是否固定头部 31 | fixedHeader?: boolean 32 | // 是否显示顶部进度条 33 | showProgressBar?: boolean 34 | // 是否显示多标签页 35 | showTabsBar?: boolean 36 | // 是否显示语言选择组件 37 | showLanguage?: boolean 38 | // 是否显示刷新组件 39 | showRefresh?: boolean 40 | // 是否显示搜索组件 41 | showSearch?: boolean 42 | // 是否显示主题组件 43 | showTheme?: boolean 44 | // 是否显示通知组件 45 | showNotice?: boolean 46 | // 是否显示全屏组件 47 | showFullScreen?: boolean 48 | } 49 | 50 | const themeConfig: Theme = { 51 | // 布局种类 horizontal vertical gallery comprehensive common 52 | layout: Layout.Horizontal, 53 | // 主题名称 default ocean green glory white 54 | themeName: ThemeName.Default, 55 | // 是否固定头部 56 | fixedHeader: true, 57 | // 是否显示顶部进度条 58 | showProgressBar: true, 59 | // 是否显示多标签页 60 | showTabsBar: true, 61 | // 是否显示语言选择组件 62 | showLanguage: true, 63 | // 是否显示刷新组件 64 | showRefresh: true, 65 | // 是否显示搜索组件 66 | showSearch: true, 67 | // 是否显示主题组件 68 | showTheme: true, 69 | // 是否显示通知组件 70 | showNotice: true, 71 | // 是否显示全屏组件 72 | showFullScreen: true 73 | } 74 | 75 | export default themeConfig 76 | -------------------------------------------------------------------------------- /src/plugins/element.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: element 组件 3 | * @Author: ZY 4 | * @Date: 2020-12-25 08:45:49 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-26 13:25:55 7 | */ 8 | 9 | // import { 10 | // ElButton, 11 | // ElSelect, 12 | // ElBreadcrumb, 13 | // ElBreadcrumbItem, 14 | // ElIcon, 15 | // ElDropdown, 16 | // ElDropdownMenu, 17 | // ElDropdownItem, 18 | // ElPopover, 19 | // ElMessage, 20 | // ElForm, 21 | // ElFormItem, 22 | // ElLoading, 23 | // ElInput, 24 | // ElTooltip, 25 | // ElDialog, 26 | // ElScrollbar, 27 | // ElMenu, 28 | // ElMenuItem, 29 | // ElSubmenu 30 | // } from 'element-plus' 31 | 32 | /** 33 | * 系统的全局设置size,全部加载方便设置。 34 | * 如需按需加载: 35 | * 1.放开注释 36 | * 2.引入babel-plugin-component库 37 | * 3.放开babel.config 注释 38 | */ 39 | import ElementPlus from 'element-plus' 40 | import 'element-plus/lib/theme-chalk/index.css' 41 | import i18n from '@/locales' 42 | import { useStore } from '@/store' 43 | export default function loadComponent(app: any) { 44 | app.use(ElementPlus, { size: useStore().state.app.size, i18n: i18n.global.t }) 45 | // app.use(ElButton) 46 | // app.use(ElSelect) 47 | // app.use(ElBreadcrumb) 48 | // app.use(ElBreadcrumbItem) 49 | // app.use(ElIcon) 50 | // app.use(ElDropdown) 51 | // app.use(ElDropdownMenu) 52 | // app.use(ElDropdownItem) 53 | // app.use(ElPopover) 54 | // app.use(ElForm) 55 | // app.use(ElFormItem) 56 | // app.use(ElLoading) 57 | // app.use(ElInput) 58 | // app.use(ElTooltip) 59 | // app.use(ElDialog) 60 | // app.use(ElScrollbar) 61 | // app.use(ElMenu) 62 | // app.use(ElSubmenu) 63 | // app.use(ElMenuItem) 64 | 65 | // app.config.globalProperties.$message = ElMessage 66 | } 67 | -------------------------------------------------------------------------------- /src/views/components-demo/DropzoneDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 29 | 30 | 55 | 56 | 63 | -------------------------------------------------------------------------------- /mock/requestDecorator.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata' 2 | import { ROUTER_MAP ,BASE_PATH_MAP} from './constant' 3 | 4 | /** 5 | * @desc 生成 http method 装饰器 6 | * @param {string} method - http method,如 get、post、head 7 | * @return Decorator - 装饰器 8 | */ 9 | function createMethodDecorator(method: string) { 10 | // 装饰器接收路由 path 作为参数 11 | return function httpMethodDecorator(path: string) { 12 | return (proto: any, name: string) => { 13 | const target = proto.constructor; 14 | const routeMap = Reflect.getMetadata(ROUTER_MAP, target, 'method') || []; 15 | routeMap.push({ name, method, path }); 16 | Reflect.defineMetadata(ROUTER_MAP, routeMap, target, 'method'); 17 | }; 18 | }; 19 | } 20 | 21 | /** 22 | * 创建类路径装饰器 23 | * @param className 24 | */ 25 | function createClassDecorator() { 26 | // 装饰器接收路由 path 作为参数 27 | return function httpMethodDecorator(basePath: string):ClassDecorator { 28 | return (proto: any) => { 29 | const target = proto; 30 | const pathMap = Reflect.getMetadata(BASE_PATH_MAP, target) || []; 31 | pathMap.push({path:basePath}); 32 | Reflect.defineMetadata(BASE_PATH_MAP, pathMap, target); 33 | }; 34 | }; 35 | } 36 | 37 | // 路径前缀 38 | export const prefix = createClassDecorator() 39 | 40 | // 导出 http method 装饰器 41 | export const post = createMethodDecorator('post'); 42 | 43 | export const get = createMethodDecorator('get'); 44 | 45 | export const del = createMethodDecorator('del'); 46 | 47 | export const put = createMethodDecorator('put'); 48 | 49 | export const patch = createMethodDecorator('patch'); 50 | 51 | export const options = createMethodDecorator('options'); 52 | 53 | export const head = createMethodDecorator('head'); 54 | 55 | export const all = createMethodDecorator('all'); -------------------------------------------------------------------------------- /src/views/table/dynamic-table/components/UnfixedHeaderTable.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 70 | -------------------------------------------------------------------------------- /src/views/clipboard/Index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 60 | -------------------------------------------------------------------------------- /src/router/permissionModules/permission.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2021-01-08 19:32:52 5 | * @LastEditors: scy😊 6 | * @LastEditTime: 2021-01-23 15:55:22 7 | */ 8 | 9 | import { RouteRecordRaw } from 'vue-router' 10 | import Layout from '@/layout/Index.vue' 11 | 12 | const permissionRouter: Array = [ 13 | { 14 | path: '/permission', 15 | component: Layout, 16 | redirect: '/permission/directive', 17 | meta: { 18 | title: 'permission', 19 | icon: '#iconquanxian', 20 | roles: ['admin', 'editor'], // you can set roles in root nav 21 | alwaysShow: true // will always show the root menu 22 | }, 23 | children: [ 24 | { 25 | path: 'page', 26 | component: () => import(/* webpackChunkName: "permission-page" */ '@/views/permission/Page.vue'), 27 | name: 'PagePermission', 28 | meta: { 29 | title: 'pagePermission', 30 | roles: ['admin'] // or you can only set roles in sub nav 31 | } 32 | }, 33 | { 34 | path: 'directive', 35 | component: () => import(/* webpackChunkName: "permission-directive" */ '@/views/permission/Directive.vue'), 36 | name: 'DirectivePermission', 37 | meta: { 38 | title: 'directivePermission' 39 | // if do not set roles, means: this page does not require permission 40 | } 41 | }, 42 | { 43 | path: 'role', 44 | component: () => import(/* webpackChunkName: "permission-role" */ '@/views/permission/role/Role.vue'), 45 | name: 'RolePermission', 46 | meta: { 47 | title: 'rolePermission', 48 | roles: ['admin'] 49 | } 50 | } 51 | ] 52 | } 53 | ] 54 | export default permissionRouter 55 | -------------------------------------------------------------------------------- /k8smanifests/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: ${NAME} 6 | name: ${NAME} 7 | namespace: ${NAMESPACE} 8 | spec: 9 | replicas: ${REPLICAS} 10 | selector: 11 | matchLabels: 12 | app: ${NAME} 13 | strategy: 14 | rollingUpdate: 15 | maxSurge: 1 16 | maxUnavailable: 0 17 | type: RollingUpdate 18 | template: 19 | metadata: 20 | labels: 21 | app: ${NAME} 22 | spec: 23 | containers: 24 | - name: ${NAME} 25 | image: ${DOCKERIMAGE} 26 | imagePullPolicy: IfNotPresent 27 | ports: 28 | - name: http 29 | containerPort: ${SERVER_PORT} 30 | protocol: TCP 31 | livenessProbe: 32 | failureThreshold: 3 33 | httpGet: 34 | path: / 35 | port: ${SERVER_PORT} 36 | scheme: HTTP 37 | periodSeconds: 5 38 | successThreshold: 1 39 | timeoutSeconds: 3 40 | readinessProbe: 41 | failureThreshold: 3 42 | httpGet: 43 | path: / 44 | port: ${SERVER_PORT} 45 | scheme: HTTP 46 | periodSeconds: 5 47 | successThreshold: 1 48 | timeoutSeconds: 3 49 | startupProbe: 50 | httpGet: 51 | path: / 52 | port: ${SERVER_PORT} 53 | scheme: HTTP 54 | failureThreshold: 60 55 | periodSeconds: 10 56 | timeoutSeconds: 3 57 | resources: 58 | requests: 59 | cpu: 100m 60 | memory: 128Mi 61 | limits: 62 | cpu: "1" 63 | memory: 1500Mi 64 | imagePullSecrets: 65 | - name: aliyun-registry-secret 66 | - name: aliyun-registry-vpc-secret 67 | -------------------------------------------------------------------------------- /mock/mock.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-09 17:02:35 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-25 20:02:35 7 | */ 8 | 9 | import Koa, { Context } from "koa"; 10 | import koaBody from "koa-body"; 11 | import koaRouter from "koa-router"; 12 | import addRouter from "./router"; 13 | import logger from "koa-logger"; 14 | import log4js from "log4js"; 15 | import {ResultHandler} from './middleware/resultHandler' 16 | import chalk from "chalk"; 17 | import cors from "koa2-cors"; 18 | 19 | 20 | const app = new Koa(); 21 | app.use(cors()); 22 | const router = new koaRouter(); 23 | 24 | const port = 3300; 25 | const log4 = log4js.getLogger(); 26 | log4.level = "debug"; 27 | 28 | //日志打印 29 | app.use(logger(info => { 30 | log4.debug(info); 31 | })); 32 | 33 | app.use(koaBody()); 34 | 35 | app.use( async (ctx,next)=>{ 36 | await next() 37 | // log4.debug(chalk.green('请求路径: ') + ctx.request.url); 38 | log4.debug(chalk.green('请求body: ') + JSON.stringify(ctx.request.body)); 39 | log4.debug(chalk.green('返回数据: ')+ JSON.stringify(ctx.body)); 40 | }) 41 | 42 | app.use(ResultHandler()); 43 | //加载路由 44 | addRouter(router); 45 | //启动路由 46 | app.use(router.routes()).use(router.allowedMethods()); 47 | 48 | app.use(async (ctx: Context) => { 49 | log4.error(`404 ${ctx.message} : ${ctx.href}`); 50 | ctx.status = 404; 51 | ctx.body = "404! api not found !"; 52 | }); 53 | 54 | // koa already had middleware to deal with the error, just register the error event 55 | app.on("error", (err, ctx: Context) => { 56 | log4.error(err); //log all errors 57 | ctx.status = 500; 58 | if (ctx.app.env !== "development") { 59 | //throw the error to frontEnd when in the develop mode 60 | ctx.res.end(err.stack); //finish the response 61 | } 62 | }); 63 | 64 | 65 | app.listen(port, () => { 66 | log4.debug("mock server running at: http://localhost:%d", port); 67 | }); 68 | -------------------------------------------------------------------------------- /src/views/components-demo/DraggableSelectDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 35 | 78 | -------------------------------------------------------------------------------- /src/components/charts/mixins/resize.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: echarts 自适应问题 3 | * @Author: ZY 4 | * @Date: 2021-01-14 15:11:11 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-14 16:34:24 7 | */ 8 | 9 | import { ref } from 'vue' 10 | export default function() { 11 | const chart = ref() 12 | const sidebarElm = ref() 13 | 14 | const chartResizeHandler = () => { 15 | if (chart.value) { 16 | chart.value.resize() 17 | } 18 | } 19 | 20 | const sidebarResizeHandler = (e: TransitionEvent) => { 21 | if (e.propertyName === 'width') { 22 | chartResizeHandler() 23 | } 24 | } 25 | 26 | const initResizeEvent = () => { 27 | window.addEventListener('resize', chartResizeHandler) 28 | } 29 | 30 | const destroyResizeEvent = () => { 31 | window.removeEventListener('resize', chartResizeHandler) 32 | } 33 | 34 | const initSidebarResizeEvent = () => { 35 | sidebarElm.value = document.getElementsByClassName('sidebar-container')[0] 36 | if (sidebarElm.value) { 37 | sidebarElm.value.addEventListener('transitionend', sidebarResizeHandler as EventListener) 38 | } 39 | } 40 | 41 | const destroySidebarResizeEvent = () => { 42 | if (sidebarElm.value) { 43 | sidebarElm.value.removeEventListener('transitionend', sidebarResizeHandler as EventListener) 44 | } 45 | } 46 | 47 | const mounted = () => { 48 | initResizeEvent() 49 | initSidebarResizeEvent() 50 | } 51 | 52 | const beforeDestroy = () => { 53 | destroyResizeEvent() 54 | destroySidebarResizeEvent() 55 | } 56 | 57 | const activated = () => { 58 | initResizeEvent() 59 | initSidebarResizeEvent() 60 | } 61 | 62 | const deactivated = () => { 63 | destroyResizeEvent() 64 | destroySidebarResizeEvent() 65 | } 66 | 67 | return { 68 | chart, 69 | mounted, 70 | beforeDestroy, 71 | activated, 72 | deactivated 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/store/modules/app/mutations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app Mutations 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-24 09:55:30 7 | */ 8 | import { MutationTree } from 'vuex' 9 | import { AppState, DeviceType } from './state' 10 | import { AppMutationTypes } from './mutation-types' 11 | import { setSidebarStatus, setLanguage, setSize } from '@/utils/cookies' 12 | 13 | export type Mutations = { 14 | [AppMutationTypes.TOGGLE_SIDEBAR](state: S, withoutAnimation: boolean): void 15 | [AppMutationTypes.CLOSE_SIDEBAR](state: S, withoutAnimation: boolean): void 16 | [AppMutationTypes.TOGGLE_DEVICE](state: S, device: DeviceType): void 17 | [AppMutationTypes.SET_LANGUAGE](state: S, language: string): void 18 | [AppMutationTypes.SET_SIZE](state: S, size: string): void 19 | 20 | } 21 | 22 | export const mutations: MutationTree & Mutations = { 23 | [AppMutationTypes.TOGGLE_SIDEBAR](state: AppState, withoutAnimation: boolean) { 24 | state.sidebar.opened = !state.sidebar.opened 25 | state.sidebar.withoutAnimation = withoutAnimation 26 | if (state.sidebar.opened) { 27 | setSidebarStatus('opened') 28 | } else { 29 | setSidebarStatus('closed') 30 | } 31 | }, 32 | 33 | [AppMutationTypes.CLOSE_SIDEBAR](state: AppState, withoutAnimation: boolean) { 34 | state.sidebar.opened = false 35 | state.sidebar.withoutAnimation = withoutAnimation 36 | setSidebarStatus('closed') 37 | }, 38 | 39 | [AppMutationTypes.TOGGLE_DEVICE](state: AppState, device: DeviceType) { 40 | state.device = device 41 | }, 42 | 43 | [AppMutationTypes.SET_LANGUAGE](state: AppState, language: string) { 44 | state.language = language 45 | setLanguage(state.language) 46 | }, 47 | 48 | [AppMutationTypes.SET_SIZE](state: AppState, size: string) { 49 | state.size = size 50 | setSize(state.size) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/components/screenfull/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 37 | 38 | 83 | -------------------------------------------------------------------------------- /src/apis/articles.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description:表格数据接口 3 | * @Autor: scy😊 4 | * @Date: 2021-01-12 11:31:47 5 | * @LastEditors: scy😊 6 | * @LastEditTime: 2021-01-23 17:15:26 7 | */ 8 | import https from '@/utils/https' 9 | import { RootObject } from '@/model/rootObject' 10 | import { ContentType, Method } from 'axios-mapper' 11 | import { ArticleModel } from '@/model/articleModel' 12 | import { ArticleList } from '@/model/articleList' 13 | 14 | export const defaultArticleModel: ArticleModel = { 15 | id: 0, 16 | status: 'draft', 17 | title: '', 18 | fullContent: '', 19 | abstractContent: '', 20 | sourceURL: '', 21 | imageURL: '', 22 | timestamp: 0, 23 | platforms: ['a-platform'], 24 | disableComment: false, 25 | importance: 0, 26 | author: '', 27 | reviewer: '', 28 | type: '', 29 | pageviews: 0 30 | } 31 | 32 | export const getArticles = (params: any) => { 33 | return https().request>>('article/articles', Method.POST, params, ContentType.json) 34 | } 35 | 36 | export const getArticle = (params: any) => { 37 | return https().request>('article/articleInfo', Method.GET, params, ContentType.form) 38 | } 39 | 40 | export const createArticle = (data: any) => { 41 | return https().request>('article/createArticle', Method.POST, data, ContentType.json) 42 | } 43 | 44 | export const updateArticle = (params: any) => { 45 | return https().request>('article/updateArticle', Method.POST, params, ContentType.json) 46 | } 47 | 48 | export const deleteArticle = (id: number) => { 49 | return https().request>(`articles/${id}`, Method.POST, undefined, ContentType.json) 50 | } 51 | 52 | export interface Pageviews{ 53 | pageviews: any 54 | } 55 | 56 | export const getPageviews = (params: any) => { 57 | return https().request>('pageviews', Method.GET, params, ContentType.json) 58 | } 59 | -------------------------------------------------------------------------------- /src/directives/waves/index.ts: -------------------------------------------------------------------------------- 1 | import './waves.css' 2 | import { Directive } from 'vue' 3 | 4 | export const waves: Directive = { 5 | beforeMount(el, binding) { 6 | el.addEventListener('click', (e: any) => { 7 | const customOpts = Object.assign({}, binding.value) 8 | const opts = Object.assign({ 9 | ele: el, // 波纹作用元素 10 | type: 'hit', // hit 点击位置扩散 center中心点扩展 11 | color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 12 | }, customOpts) 13 | const target: HTMLElement = opts.ele 14 | if (target) { 15 | target.style.position = 'relative' 16 | target.style.overflow = 'hidden' 17 | const rect = target.getBoundingClientRect() 18 | let ripple = target.querySelector('.waves-ripple') as HTMLElement 19 | if (!ripple) { 20 | ripple = document.createElement('span') 21 | ripple.className = 'waves-ripple' 22 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' 23 | target.appendChild(ripple) 24 | } else { 25 | ripple.className = 'waves-ripple' 26 | } 27 | switch (opts.type) { 28 | case 'center': 29 | ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px' 30 | ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px' 31 | break 32 | default: 33 | ripple.style.top = 34 | (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || 35 | document.body.scrollTop) + 'px' 36 | ripple.style.left = 37 | (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || 38 | document.body.scrollLeft) + 'px' 39 | } 40 | ripple.style.backgroundColor = opts.color 41 | ripple.className = 'waves-ripple z-active' 42 | return false 43 | } 44 | }, false) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description:配置文件 3 | * @Author: ZY 4 | * @Date: 2020-12-07 11:41:22 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-27 15:17:29 7 | */ 8 | const { resolve } = require('path') 9 | const path = require('path') 10 | const WebpackBar = require('webpackbar'); 11 | const dayjs = require('dayjs') 12 | const time = dayjs().format('YYYY-M-D HH:mm:ss') 13 | process.env.VUE_APP_UPDATE_TIME = time 14 | const { 15 | publicPath, 16 | assetsDir, 17 | outputDir, 18 | lintOnSave, 19 | transpileDependencies, 20 | title, 21 | devPort, 22 | } = require('./src/config/default/vue.custom.config') 23 | module.exports = { 24 | publicPath, 25 | assetsDir, 26 | outputDir, 27 | lintOnSave, 28 | transpileDependencies, 29 | devServer: { 30 | hot: true, 31 | port: devPort, 32 | open: true, 33 | noInfo: false, 34 | overlay: { 35 | warnings: true, 36 | errors: true, 37 | }, 38 | }, 39 | pluginOptions: { 40 | 'style-resources-loader': { 41 | preProcessor: 'scss', 42 | patterns: [ 43 | path.resolve(__dirname, 'src/styles/_variables.scss'), 44 | path.resolve(__dirname, 'src/styles/_mixins.scss'), 45 | ] 46 | } 47 | }, 48 | configureWebpack(){ 49 | return { 50 | resolve:{ 51 | alias:{ 52 | '@':resolve('src'), 53 | '*':resolve(''), 54 | 'Assets':resolve('src/assets') 55 | } 56 | }, 57 | module:{ 58 | rules: [ 59 | { 60 | test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files 61 | loader: '@intlify/vue-i18n-loader', 62 | include: [ // Use `Rule.include` to specify the files of locale messages to be pre-compiled 63 | path.resolve(__dirname, 'src/lang') 64 | ] 65 | }, 66 | ], 67 | }, 68 | plugins:[ 69 | new WebpackBar({ 70 | name:title, 71 | }) 72 | ] 73 | } 74 | }, 75 | } 76 | -------------------------------------------------------------------------------- /src/views/profile/components/Account.vue: -------------------------------------------------------------------------------- 1 | 8 | 26 | 27 | 78 | -------------------------------------------------------------------------------- /src/views/example/components/Dropdown/Platform.vue: -------------------------------------------------------------------------------- 1 | 8 | 37 | 38 | 77 | -------------------------------------------------------------------------------- /src/components/dropzone/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 31 | 59 | 76 | -------------------------------------------------------------------------------- /src/utils/scroll_to.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: scy😊 4 | * @Date: 2021-01-13 09:08:13 5 | * @LastEditors: scy😊 6 | * @LastEditTime: 2021-01-13 09:08:14 7 | */ 8 | const easeInOutQuad = (t: number, b: number, c: number, d: number) => { 9 | t /= d / 2 10 | if (t < 1) { 11 | return c / 2 * t * t + b 12 | } 13 | t-- 14 | return -c / 2 * (t * (t - 2) - 1) + b 15 | } 16 | 17 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts 18 | const requestAnimFrame = (function() { 19 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || (window as any).mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } 20 | })() 21 | 22 | // Because it's so fucking difficult to detect the scrolling element, just move them all 23 | const move = (amount: number) => { 24 | document.documentElement.scrollTop = amount; 25 | (document.body.parentNode as HTMLElement).scrollTop = amount 26 | document.body.scrollTop = amount 27 | } 28 | 29 | const position = () => { 30 | return document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop 31 | } 32 | 33 | export const scrollTo = (to: number, duration: number, callback?: Function) => { 34 | const start = position() 35 | const change = to - start 36 | const increment = 20 37 | let currentTime = 0 38 | duration = (typeof (duration) === 'undefined') ? 500 : duration 39 | const animateScroll = function() { 40 | // increment the time 41 | currentTime += increment 42 | // find the value with the quadratic in-out easing function 43 | const val = easeInOutQuad(currentTime, start, change, duration) 44 | // move the document.body 45 | move(val) 46 | // do the animation unless its over 47 | if (currentTime < duration) { 48 | requestAnimFrame(animateScroll) 49 | } else { 50 | if (callback && typeof (callback) === 'function') { 51 | // the animation is done so lets callback 52 | callback() 53 | } 54 | } 55 | } 56 | animateScroll() 57 | } 58 | -------------------------------------------------------------------------------- /src/directives/clipboard/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 粘贴板 3 | * @Author: ZY 4 | * @Date: 2020-12-28 10:39:21 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-28 13:58:35 7 | */ 8 | // Inspired by https://github.com/Inndy/vue-clipboard2 9 | import Clipboard from 'clipboard' 10 | import { Directive } from 'vue' 11 | 12 | if (!Clipboard) { 13 | throw new Error('you should npm install `clipboard` --save at first ') 14 | } 15 | 16 | let successCallback: Function | null 17 | let errorCallback: Function | null 18 | let clipboardInstance: Clipboard | null 19 | 20 | export const clipboard: Directive = { 21 | beforeMount(el, binding) { 22 | if (binding.arg === 'success') { 23 | successCallback = binding.value 24 | } else if (binding.arg === 'error') { 25 | errorCallback = binding.value 26 | } else { 27 | clipboardInstance = new Clipboard(el, { 28 | text() { return binding.value }, 29 | action() { return binding.arg === 'cut' ? 'cut' : 'copy' } 30 | }) 31 | clipboardInstance.on('success', e => { 32 | const callback = successCallback 33 | callback && callback(e) 34 | }) 35 | clipboardInstance.on('error', e => { 36 | const callback = errorCallback 37 | callback && callback(e) 38 | }) 39 | } 40 | }, 41 | 42 | beforeUpdate(el, binding) { 43 | if (binding.arg === 'success') { 44 | successCallback = binding.value 45 | } else if (binding.arg === 'error') { 46 | errorCallback = binding.value 47 | } else { 48 | clipboardInstance = new Clipboard(el, { 49 | text() { return binding.value }, 50 | action() { return binding.arg === 'cut' ? 'cut' : 'copy' } 51 | }) 52 | } 53 | }, 54 | 55 | beforeUnmount(_, binding) { 56 | if (binding.arg === 'success') { 57 | successCallback = null 58 | } else if (binding.arg === 'error') { 59 | errorCallback = null 60 | } else { 61 | if (clipboardInstance) { 62 | clipboardInstance.destroy() 63 | } 64 | clipboardInstance = null 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-07 10:30:20 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-27 20:10:59 7 | */ 8 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const constantFiles = require.context('./constantModules', true, /\.ts$/) 12 | let constantModules: Array = [] 13 | constantFiles.keys().forEach((key) => { 14 | if (key === './index.ts') return 15 | constantModules = constantModules.concat(constantFiles(key).default) 16 | }) 17 | 18 | const asyncFiles = require.context('./permissionModules', true, /\.ts$/) 19 | let permissionModules: Array = [] 20 | asyncFiles.keys().forEach((key) => { 21 | if (key === './index.ts') return 22 | permissionModules = permissionModules.concat(asyncFiles(key).default) 23 | }) 24 | 25 | export const constantRoutes: Array = [ 26 | { 27 | path: '/redirect', 28 | component: Layout, 29 | meta: { hidden: true }, 30 | children: [ 31 | { 32 | path: '/redirect/:path(.*)', 33 | component: () => import(/* webpackChunkName: "redirect" */ '@/views/redirect/Index.vue') 34 | } 35 | ] 36 | }, 37 | { 38 | path: '/', 39 | component: Layout, 40 | redirect: '/dashboard', 41 | children: [ 42 | { 43 | path: 'dashboard', 44 | component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/Index.vue'), 45 | name: 'Dashboard', 46 | meta: { 47 | title: 'dashboard', 48 | icon: '#icondashboard', 49 | affix: true 50 | } 51 | } 52 | ] 53 | }, 54 | ...constantModules 55 | ] 56 | 57 | export const asyncRoutes: Array = [ 58 | ...permissionModules 59 | ] 60 | const router = createRouter({ 61 | history: createWebHashHistory(), 62 | routes: constantRoutes 63 | }) 64 | 65 | export function resetRouter() { 66 | const newRouter = router; 67 | (router as any).matcher = (newRouter as any).matcher // reset router 68 | } 69 | 70 | export default router 71 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: ZY 4 | * @Date: 2020-12-07 10:30:20 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-07 11:00:37 7 | */ 8 | module.exports = { 9 | root: true, 10 | env: { 11 | node: true 12 | }, 13 | extends: [ 14 | 'plugin:vue/vue3-strongly-recommended', 15 | '@vue/standard', 16 | '@vue/typescript/recommended' 17 | ], 18 | parserOptions: { 19 | ecmaVersion: 2020 20 | }, 21 | rules: { 22 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 23 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 24 | '@typescript-eslint/ban-types': 'off', 25 | '@typescript-eslint/explicit-module-boundary-types': 'off', 26 | '@typescript-eslint/member-delimiter-style': ['error', 27 | { 28 | multiline: { 29 | delimiter: 'none' 30 | }, 31 | singleline: { 32 | delimiter: 'comma' 33 | } 34 | }], 35 | '@typescript-eslint/no-explicit-any': 'off', 36 | 'space-before-function-paren': ['error', 'never'], 37 | 'vue/array-bracket-spacing': 'error', 38 | 'vue/arrow-spacing': 'error', 39 | 'vue/block-spacing': 'error', 40 | 'vue/brace-style': 'error', 41 | 'vue/camelcase': 'error', 42 | 'vue/comma-dangle': 'error', 43 | 'vue/component-name-in-template-casing': 'error', 44 | 'vue/eqeqeq': 'error', 45 | 'vue/key-spacing': 'error', 46 | 'vue/match-component-file-name': 'error', 47 | 'vue/object-curly-spacing': 'error', 48 | 'no-useless-escape': 'off', 49 | '@typescript-eslint/no-this-alias': [ 50 | 'error', 51 | { 52 | allowDestructuring: true, // Allow `const { props, state } = this`; false by default 53 | allowedNames: ['self'] // Allow `const self = this`; `[]` by default 54 | } 55 | ], 56 | 'vue/attribute-hyphenation': 'off', 57 | 'vue/custom-event-name-casing': 'off' 58 | }, 59 | overrides: [ 60 | { 61 | files: [ 62 | '**/__tests__/*.{j,t}s?(x)', 63 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 64 | ], 65 | env: { 66 | jest: true 67 | } 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /src/layout/resize.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 根绝大小变化重新布局 3 | * @Author: ZY 4 | * @Date: 2020-12-17 15:37:56 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-28 16:29:49 7 | */ 8 | // refer to Bootstrap's responsive design 9 | 10 | import { useStore } from '@/store' 11 | import { AppActionTypes } from '@/store/modules/app/action-types' 12 | import { DeviceType } from '@/store/modules/app/state' 13 | import { computed, watch } from 'vue' 14 | import { useRoute } from 'vue-router' 15 | const store = useStore() 16 | const WIDTH = 992 // refer to Bootstrap's responsive design 17 | 18 | export default function() { 19 | const device = computed(() => { 20 | return store.state.app.device 21 | }) 22 | 23 | const sidebar = computed(() => { 24 | return store.state.app.sidebar 25 | }) 26 | 27 | const currentRoute = useRoute() 28 | const watchRouter = watch(() => currentRoute.name, () => { 29 | if (store.state.app.device === DeviceType.Mobile && store.state.app.sidebar.opened) { 30 | store.dispatch(AppActionTypes.ACTION_CLOSE_SIDEBAR, false) 31 | } 32 | }) 33 | 34 | const isMobile = () => { 35 | const rect = document.body.getBoundingClientRect() 36 | return rect.width - 1 < WIDTH 37 | } 38 | 39 | const resizeMounted = () => { 40 | if (isMobile()) { 41 | store.dispatch(AppActionTypes.ACTION_TOGGLE_DEVICE, DeviceType.Mobile) 42 | store.dispatch(AppActionTypes.ACTION_CLOSE_SIDEBAR, true) 43 | } 44 | } 45 | 46 | const resizeHandler = () => { 47 | if (!document.hidden) { 48 | store.dispatch(AppActionTypes.ACTION_TOGGLE_DEVICE, isMobile() ? DeviceType.Mobile : DeviceType.Desktop) 49 | if (isMobile()) { 50 | store.dispatch(AppActionTypes.ACTION_CLOSE_SIDEBAR, true) 51 | } 52 | } 53 | } 54 | const addEventListenerOnResize = () => { 55 | window.addEventListener('resize', resizeHandler) 56 | } 57 | 58 | const removeEventListenerResize = () => { 59 | window.removeEventListener('resize', resizeHandler) 60 | } 61 | 62 | return { 63 | device, 64 | sidebar, 65 | resizeMounted, 66 | addEventListenerOnResize, 67 | removeEventListenerResize, 68 | watchRouter 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description:全局存储类封装(AES加解密) 3 | * @Autor: ZY 4 | * @Date: 2020-11-04 11:51:01 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-28 16:57:57 7 | */ 8 | 9 | import VAES from './ase' 10 | import Cookies from 'js-cookie' 11 | 12 | export enum StorageType{ 13 | local, 14 | session, 15 | cookie 16 | } 17 | 18 | interface VStorageInterface{ 19 | rcSetItem(type: StorageType, key: string, value: T): void 20 | rcGetItem(type: StorageType, key: string): string | null 21 | rcRemoveItem(type: StorageType, key: string): void 22 | } 23 | 24 | class VStorage implements VStorageInterface { 25 | private static instance: VStorage 26 | static shared() { 27 | if (!this.instance) { 28 | this.instance = new VStorage() 29 | } 30 | return this.instance 31 | } 32 | 33 | /** 34 | * @description: 本地保存数据AES加密处理 35 | * @param {StorageType} type localStorage 和 sessionStorage 选择 36 | * @param {string} key 37 | * @param {T} value 38 | * @return {*} 39 | */ 40 | rcSetItem(type: StorageType = StorageType.local, key: string, value: T) { 41 | const valueJson = JSON.stringify(value) 42 | const valueAes = VAES.encrypt(valueJson) as string 43 | if (type === StorageType.local) { 44 | localStorage.setItem(key, valueAes) 45 | } else if (type === StorageType.session) { 46 | sessionStorage.setItem(key, valueAes) 47 | } else { 48 | Cookies.set(key, valueAes) 49 | } 50 | } 51 | 52 | rcGetItem(type: StorageType = StorageType.local, key: string): any { 53 | if (type === StorageType.local) { 54 | return VAES.decrypt(localStorage.getItem(key)) 55 | } else if (type === StorageType.session) { 56 | return VAES.decrypt(sessionStorage.getItem(key)) 57 | } else { 58 | return VAES.decrypt(Cookies.get(key) ?? '') 59 | } 60 | } 61 | 62 | rcRemoveItem(type: StorageType = StorageType.local, key: string) { 63 | if (type === StorageType.local) { 64 | localStorage.removeItem(key) 65 | } else if (type === StorageType.session) { 66 | sessionStorage.removeItem(key) 67 | } else { 68 | Cookies.remove(key) 69 | } 70 | } 71 | } 72 | 73 | export default VStorage.shared() 74 | -------------------------------------------------------------------------------- /src/store/modules/permission/actions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: ZY 4 | * @Date: 2020-12-25 15:03:52 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-11 21:05:18 7 | */ 8 | 9 | import { ActionTree, ActionContext } from 'vuex' 10 | import { RootState } from '@/store' 11 | import { PermissionState } from './state' 12 | import { Mutations } from './mutations' 13 | import { PermissionMutationType } from './mutation-types' 14 | import { PermissionActionType } from './action-types' 15 | import { asyncRoutes } from '@/router' 16 | import { RouteRecordRaw } from 'vue-router' 17 | 18 | type AugmentedActionContext = { 19 | commit( 20 | key: K, 21 | payload: Parameters[1], 22 | ): ReturnType 23 | } & Omit, 'commit'> 24 | 25 | const hasPermission = (roles: string[], route: RouteRecordRaw) => { 26 | if (route.meta && route.meta.roles) { 27 | return roles.some(role => { 28 | if (route.meta?.roles !== undefined) { 29 | return route.meta.roles.includes(role) 30 | } 31 | }) 32 | } else { 33 | return true 34 | } 35 | } 36 | 37 | export const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => { 38 | const res: RouteRecordRaw[] = [] 39 | routes.forEach(route => { 40 | const r = { ...route } 41 | if (hasPermission(roles, r)) { 42 | if (r.children) { 43 | r.children = filterAsyncRoutes(r.children, roles) 44 | } 45 | res.push(r) 46 | } 47 | }) 48 | return res 49 | } 50 | 51 | export interface Actions { 52 | [PermissionActionType.ACTION_SET_ROUTES]( 53 | { commit }: AugmentedActionContext 54 | , roles: string[]): void 55 | } 56 | 57 | export const actions: ActionTree & Actions = { 58 | [PermissionActionType.ACTION_SET_ROUTES]( 59 | { commit }: AugmentedActionContext 60 | , roles: string[]) { 61 | let accessedRoutes 62 | if (roles.includes('admin')) { 63 | accessedRoutes = asyncRoutes 64 | } else { 65 | accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) 66 | } 67 | commit(PermissionMutationType.SET_ROUTES, accessedRoutes) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/lang_select/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 33 | 34 | 78 | 79 | 84 | -------------------------------------------------------------------------------- /src/store/modules/app/actions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: app actions 3 | * @Author: ZY 4 | * @Date: 2020-12-23 10:25:37 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2020-12-23 14:29:18 7 | */ 8 | import { ActionTree, ActionContext } from 'vuex' 9 | 10 | // eslint-disable-next-line import/no-cycle 11 | import { RootState } from '@/store' 12 | import { AppState, DeviceType } from './state' 13 | import { Mutations } from './mutations' 14 | import { AppMutationTypes } from './mutation-types' 15 | import { AppActionTypes } from './action-types' 16 | type AugmentedActionContext = { 17 | commit( 18 | key: K, 19 | payload: Parameters[1], 20 | ): ReturnType 21 | } & Omit, 'commit'> 22 | 23 | export interface Actions { 24 | [AppActionTypes.ACTION_TOGGLE_SIDEBAR]( 25 | { commit }: AugmentedActionContext, 26 | withoutAnimation: boolean 27 | ): void 28 | [AppActionTypes.ACTION_CLOSE_SIDEBAR]( 29 | { commit }: AugmentedActionContext, 30 | withoutAnimation: boolean 31 | ): void 32 | [AppActionTypes.ACTION_TOGGLE_DEVICE]( 33 | { commit }: AugmentedActionContext, 34 | device: DeviceType 35 | ): void 36 | [AppActionTypes.ACTION_SET_LANGUAGE]( 37 | { commit }: AugmentedActionContext, 38 | language: string 39 | ): void 40 | [AppActionTypes.ACTION_SET_SIZE]( 41 | { commit }: AugmentedActionContext, 42 | size: string 43 | ): void 44 | } 45 | 46 | export const actions: ActionTree & Actions = { 47 | [AppActionTypes.ACTION_TOGGLE_SIDEBAR]({ commit }, withoutAnimation: boolean) { 48 | commit(AppMutationTypes.TOGGLE_SIDEBAR, withoutAnimation) 49 | }, 50 | [AppActionTypes.ACTION_CLOSE_SIDEBAR]({ commit }, withoutAnimation: boolean) { 51 | commit(AppMutationTypes.CLOSE_SIDEBAR, withoutAnimation) 52 | }, 53 | [AppActionTypes.ACTION_TOGGLE_DEVICE]({ commit }, device: DeviceType) { 54 | commit(AppMutationTypes.TOGGLE_DEVICE, device) 55 | }, 56 | [AppActionTypes.ACTION_SET_LANGUAGE]({ commit }, language: string) { 57 | commit(AppMutationTypes.SET_LANGUAGE, language) 58 | }, 59 | [AppActionTypes.ACTION_SET_SIZE]({ commit }, size: string) { 60 | commit(AppMutationTypes.SET_SIZE, size) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/views/components-demo/AvatarUploadDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | 42 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /src/views/components-demo/DraggableKanBanDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 33 | 34 | 65 | 66 | 85 | 95 | -------------------------------------------------------------------------------- /src/views/table/dynamic-table/components/FixedHeaderTable.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 82 | -------------------------------------------------------------------------------- /src/views/components-demo/TinymceModulesDemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 33 | 34 | 66 | 67 | 72 | -------------------------------------------------------------------------------- /src/components/github-corner/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 37 | 38 | 44 | 45 | 77 | -------------------------------------------------------------------------------- /src/router/permissionModules/nested.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Autor: WJM 4 | * @Date: 2021-01-18 14:46:51 5 | * @LastEditors: ZY 6 | * @LastEditTime: 2021-01-21 21:22:25 7 | */ 8 | import { RouteRecordRaw } from 'vue-router' 9 | import Layout from '@/layout/Index.vue' 10 | 11 | const nestedRouter: Array = [{ 12 | path: '/nested', 13 | component: Layout, 14 | redirect: 'noredirect', 15 | name: 'Nested', 16 | meta: { 17 | title: 'nested', 18 | icon: '#iconnested' 19 | }, 20 | children: [ 21 | { 22 | path: 'menu1', 23 | component: () => import(/* webpackChunkName: "menu1" */ '@/views/nested/menu1/Index.vue'), 24 | name: 'Menu1', 25 | meta: { title: 'menu1', noCache: true }, 26 | children: [ 27 | { 28 | path: 'menu1-1', 29 | component: () => import(/* webpackChunkName: "menu1-1" */ '@/views/nested/menu1/menu1-1/Index.vue'), 30 | name: 'Menu1-1', 31 | meta: { title: 'menu1-1' } 32 | }, 33 | { 34 | path: 'menu1-2', 35 | component: () => import(/* webpackChunkName: "menu1-2" */ '@/views/nested/menu1/menu1-2/Index.vue'), 36 | name: 'Menu1-2', 37 | meta: { title: 'menu1-2' }, 38 | children: [ 39 | { 40 | path: 'menu1-2-1', 41 | component: () => import(/* webpackChunkName: "menu1-2-1" */ '@/views/nested/menu1/menu1-2/menu1-2-1/Index.vue'), 42 | name: 'Menu1-2-1', 43 | meta: { title: 'menu1-2-1' } 44 | }, 45 | { 46 | path: 'menu1-2-2', 47 | component: () => import(/* webpackChunkName: "menu1-2-2" */ '@/views/nested/menu1/menu1-2/menu1-2-2/Index.vue'), 48 | name: 'Menu1-2-2', 49 | meta: { title: 'menu1-2-2' } 50 | } 51 | ] 52 | }, 53 | { 54 | path: 'menu1-3', 55 | component: () => import(/* webpackChunkName: "menu1-3" */ '@/views/nested/menu1/menu1-3/Index.vue'), 56 | name: 'Menu1-3', 57 | meta: { title: 'menu1-3' } 58 | } 59 | ] 60 | }, 61 | { 62 | path: 'menu2', 63 | component: () => import(/* webpackChunkName: "menu2" */ '@/views/nested/menu2/Index.vue'), 64 | name: 'Menu2', 65 | meta: { title: 'menu2' } 66 | } 67 | ] 68 | }] 69 | 70 | export default nestedRouter 71 | --------------------------------------------------------------------------------