From 23cec8f081746a414acaeca892f697c03d24d29d Mon Sep 17 00:00:00 2001 From: ktianc <2445667550@qq.com> Date: Fri, 16 Sep 2022 12:32:51 +0800 Subject: [PATCH] frist --- .gitattributes | 4 + .gitignore | 22 + README.en.md | 37 - README.md | 122 +- kinit-admin/.env.base | 11 + kinit-admin/.env.dev | 23 + kinit-admin/.env.gitee | 23 + kinit-admin/.env.pro | 23 + kinit-admin/.env.test | 23 + kinit-admin/.eslintignore | 8 + kinit-admin/.eslintrc.js | 69 + kinit-admin/.github/workflows/auto-merge.yml | 130 ++ .../workflows/reademe-contributors.yml | 25 + kinit-admin/.github/workflows/release.yml | 18 + kinit-admin/.gitignore | 8 + kinit-admin/.husky/commit-msg | 4 + kinit-admin/.husky/lintstagedrc.js | 9 + kinit-admin/.husky/pre-commit | 8 + kinit-admin/.prettierignore | 10 + kinit-admin/.stylelintignore | 6 + kinit-admin/.vscode/extensions.json | 3 + kinit-admin/.vscode/settings.json | 19 + kinit-admin/CHANGELOG.md | 643 ++++++++++ kinit-admin/LICENSE | 21 + kinit-admin/README.md | 240 ++++ kinit-admin/README.zh-CN.md | 240 ++++ kinit-admin/commitlint.config.js | 28 + kinit-admin/index.html | 142 +++ kinit-admin/mock/_createProductionServer.ts | 18 + kinit-admin/mock/analysis/index.ts | 89 ++ kinit-admin/mock/dict/index.ts | 63 + kinit-admin/mock/role/index.ts | 534 ++++++++ kinit-admin/mock/table/index.ts | 137 ++ kinit-admin/mock/user/index.ts | 93 ++ kinit-admin/mock/workplace/index.ts | 172 +++ kinit-admin/package.json | 119 ++ kinit-admin/plop/component/component.hbs | 11 + kinit-admin/plop/component/index.hbs | 3 + kinit-admin/plop/component/prompt.js | 38 + kinit-admin/plop/view/prompt.js | 37 + kinit-admin/plop/view/view.hbs | 7 + kinit-admin/plopfile.js | 7 + kinit-admin/postcss.config.js | 5 + kinit-admin/prettier.config.js | 19 + kinit-admin/public/favicon.ico | Bin 0 -> 4286 bytes kinit-admin/public/logo.png | Bin 0 -> 9193 bytes kinit-admin/src/App.vue | 63 + kinit-admin/src/api/common/index.ts | 11 + .../src/api/dashboard/analysis/index.ts | 23 + .../src/api/dashboard/analysis/types.ts | 22 + .../src/api/dashboard/workplace/index.ts | 22 + .../src/api/dashboard/workplace/types.ts | 30 + kinit-admin/src/api/login/index.ts | 31 + kinit-admin/src/api/login/types.ts | 12 + kinit-admin/src/api/table/index.ts | 18 + kinit-admin/src/api/table/types.ts | 9 + kinit-admin/src/assets/imgs/avatar.jpg | Bin 0 -> 6264 bytes kinit-admin/src/assets/imgs/logo.png | Bin 0 -> 9193 bytes kinit-admin/src/assets/svgs/403.svg | 1 + kinit-admin/src/assets/svgs/404.svg | 1 + kinit-admin/src/assets/svgs/500.svg | 1 + kinit-admin/src/assets/svgs/icon.svg | 1 + kinit-admin/src/assets/svgs/login-bg.svg | 1 + kinit-admin/src/assets/svgs/login-box-bg.svg | 1 + kinit-admin/src/assets/svgs/message.svg | 1 + kinit-admin/src/assets/svgs/money.svg | 1 + kinit-admin/src/assets/svgs/peoples.svg | 1 + kinit-admin/src/assets/svgs/shopping.svg | 1 + kinit-admin/src/components/Backtop/index.ts | 3 + .../src/components/Backtop/src/Backtop.vue | 15 + .../src/components/Breadcrumb/index.ts | 3 + .../components/Breadcrumb/src/Breadcrumb.vue | 127 ++ .../src/components/Breadcrumb/src/helper.ts | 31 + kinit-admin/src/components/Collapse/index.ts | 3 + .../src/components/Collapse/src/Collapse.vue | 35 + .../src/components/ConfigGlobal/index.ts | 3 + .../ConfigGlobal/src/ConfigGlobal.vue | 62 + .../src/components/ContentDetailWrap/index.ts | 3 + .../src/ContentDetailWrap.vue | 59 + .../src/components/ContentWrap/index.ts | 3 + .../ContentWrap/src/ContentWrap.vue | 33 + .../src/components/ContextMenu/index.ts | 10 + .../ContextMenu/src/ContextMenu.vue | 72 ++ kinit-admin/src/components/CountTo/index.ts | 3 + .../src/components/CountTo/src/CountTo.vue | 180 +++ .../src/components/Descriptions/index.ts | 3 + .../Descriptions/src/Descriptions.vue | 139 ++ kinit-admin/src/components/Dialog/index.ts | 3 + .../src/components/Dialog/src/Dialog.vue | 118 ++ kinit-admin/src/components/Echart/index.ts | 3 + .../src/components/Echart/src/Echart.vue | 113 ++ kinit-admin/src/components/Editor/index.ts | 8 + .../src/components/Editor/src/Editor.vue | 138 ++ kinit-admin/src/components/Error/index.ts | 3 + .../src/components/Error/src/Error.vue | 58 + kinit-admin/src/components/Footer/index.ts | 3 + .../src/components/Footer/src/Footer.vue | 22 + kinit-admin/src/components/Form/index.ts | 14 + kinit-admin/src/components/Form/src/Form.vue | 299 +++++ .../src/components/Form/src/componentMap.ts | 48 + .../Form/src/components/useRenderCheckbox.tsx | 20 + .../Form/src/components/useRenderRadio.tsx | 20 + .../Form/src/components/useRenderSelect.tsx | 48 + kinit-admin/src/components/Form/src/helper.ts | 148 +++ kinit-admin/src/components/Form/src/types.ts | 15 + kinit-admin/src/components/Highlight/index.ts | 3 + .../components/Highlight/src/Highlight.vue | 65 + kinit-admin/src/components/Icon/index.ts | 3 + kinit-admin/src/components/Icon/src/Icon.vue | 78 ++ .../src/components/ImageViewer/index.ts | 33 + .../ImageViewer/src/ImageViewer.vue | 34 + .../src/components/ImageViewer/src/types.ts | 9 + kinit-admin/src/components/Infotip/index.ts | 3 + .../src/components/Infotip/src/Infotip.vue | 52 + .../src/components/InputPassword/index.ts | 3 + .../InputPassword/src/InputPassword.vue | 152 +++ .../src/components/LocaleDropdown/index.ts | 3 + .../LocaleDropdown/src/LocaleDropdown.vue | 52 + kinit-admin/src/components/Logo/index.ts | 3 + kinit-admin/src/components/Logo/src/Logo.vue | 85 ++ kinit-admin/src/components/Menu/index.ts | 3 + kinit-admin/src/components/Menu/src/Menu.vue | 299 +++++ .../Menu/src/components/useRenderMenuItem.tsx | 59 + .../src/components/useRenderMenuTitle.tsx | 23 + kinit-admin/src/components/Menu/src/helper.ts | 55 + kinit-admin/src/components/Qrcode/index.ts | 3 + .../src/components/Qrcode/src/Qrcode.vue | 251 ++++ .../src/components/Screenfull/index.ts | 3 + .../components/Screenfull/src/Screenfull.vue | 30 + kinit-admin/src/components/Search/index.ts | 3 + .../src/components/Search/src/Search.vue | 139 ++ kinit-admin/src/components/Setting/index.ts | 3 + .../src/components/Setting/src/Setting.vue | 301 +++++ .../src/components/ColorRadioPicker.vue | 65 + .../src/components/InterfaceDisplay.vue | 202 +++ .../src/components/LayoutRadioPicker.vue | 171 +++ .../src/components/SizeDropdown/index.ts | 3 + .../SizeDropdown/src/SizeDropdown.vue | 39 + kinit-admin/src/components/Sticky/index.ts | 3 + .../src/components/Sticky/src/Sticky.vue | 141 +++ kinit-admin/src/components/TabMenu/index.ts | 3 + .../src/components/TabMenu/src/TabMenu.vue | 226 ++++ .../src/components/TabMenu/src/helper.ts | 52 + kinit-admin/src/components/Table/index.ts | 11 + .../src/components/Table/src/Table.vue | 297 +++++ .../src/components/Table/src/helper.ts | 8 + kinit-admin/src/components/Table/src/types.ts | 24 + kinit-admin/src/components/TagsView/index.ts | 3 + .../src/components/TagsView/src/TagsView.vue | 579 +++++++++ .../src/components/TagsView/src/helper.ts | 21 + .../src/components/ThemeSwitch/index.ts | 3 + .../ThemeSwitch/src/ThemeSwitch.vue | 41 + kinit-admin/src/components/UserInfo/index.ts | 3 + .../src/components/UserInfo/src/UserInfo.vue | 67 + kinit-admin/src/components/index.ts | 6 + kinit-admin/src/config/app.ts | 106 ++ kinit-admin/src/config/axios/config.ts | 46 + kinit-admin/src/config/axios/index.ts | 33 + kinit-admin/src/config/axios/service.ts | 70 + kinit-admin/src/config/locale.ts | 32 + kinit-admin/src/directives/index.ts | 10 + .../src/directives/permission/hasPermi.ts | 47 + kinit-admin/src/hooks/event/useScrollTo.ts | 62 + kinit-admin/src/hooks/web/useCache.ts | 17 + kinit-admin/src/hooks/web/useConfigGlobal.ts | 9 + kinit-admin/src/hooks/web/useCrudSchemas.ts | 251 ++++ kinit-admin/src/hooks/web/useDesign.ts | 18 + kinit-admin/src/hooks/web/useEmitt.ts | 23 + kinit-admin/src/hooks/web/useForm.ts | 91 ++ kinit-admin/src/hooks/web/useI18n.ts | 52 + kinit-admin/src/hooks/web/useIcon.ts | 7 + kinit-admin/src/hooks/web/useIntro.ts | 47 + kinit-admin/src/hooks/web/useLocale.ts | 35 + kinit-admin/src/hooks/web/useNProgress.ts | 34 + kinit-admin/src/hooks/web/usePageLoading.ts | 18 + kinit-admin/src/hooks/web/useTable.ts | 191 +++ kinit-admin/src/hooks/web/useTimeAgo.ts | 48 + kinit-admin/src/hooks/web/useTitle.ts | 25 + kinit-admin/src/hooks/web/useValidator.ts | 64 + kinit-admin/src/hooks/web/useWatermark.ts | 55 + kinit-admin/src/layout/Layout.vue | 78 ++ kinit-admin/src/layout/components/AppView.vue | 52 + .../src/layout/components/ToolHeader.vue | 83 ++ .../src/layout/components/useRenderLayout.tsx | 263 ++++ kinit-admin/src/locales/en.ts | 441 +++++++ kinit-admin/src/locales/zh-CN.ts | 437 +++++++ kinit-admin/src/main.ts | 55 + kinit-admin/src/permission.ts | 82 ++ kinit-admin/src/plugins/animate.css/index.ts | 1 + kinit-admin/src/plugins/echarts/index.ts | 41 + kinit-admin/src/plugins/elementPlus/index.ts | 18 + kinit-admin/src/plugins/svgIcon/index.ts | 3 + kinit-admin/src/plugins/vueI18n/helper.ts | 3 + kinit-admin/src/plugins/vueI18n/index.ts | 42 + kinit-admin/src/plugins/windi.css/index.ts | 3 + kinit-admin/src/router/index.ts | 569 +++++++++ kinit-admin/src/store/index.ts | 13 + kinit-admin/src/store/modules/app.ts | 184 +++ kinit-admin/src/store/modules/dict.ts | 38 + kinit-admin/src/store/modules/locale.ts | 35 + kinit-admin/src/store/modules/permission.ts | 84 ++ kinit-admin/src/store/modules/tagsView.ts | 141 +++ kinit-admin/src/styles/index.less | 2 + kinit-admin/src/styles/theme.less | 6 + kinit-admin/src/styles/var.css | 67 + kinit-admin/src/styles/variables.module.less | 10 + kinit-admin/src/utils/color.ts | 153 +++ kinit-admin/src/utils/domUtils.ts | 289 +++++ kinit-admin/src/utils/index.ts | 110 ++ kinit-admin/src/utils/is.ts | 105 ++ kinit-admin/src/utils/propTypes.ts | 29 + kinit-admin/src/utils/routerHelper.ts | 194 +++ kinit-admin/src/utils/tree.ts | 207 +++ kinit-admin/src/utils/tsxHelper.ts | 16 + kinit-admin/src/views/Authorization/Role.vue | 88 ++ kinit-admin/src/views/Authorization/User.vue | 88 ++ kinit-admin/src/views/Components/CountTo.vue | 100 ++ .../src/views/Components/Descriptions.vue | 142 +++ kinit-admin/src/views/Components/Dialog.vue | 147 +++ kinit-admin/src/views/Components/Echart.vue | 36 + .../src/views/Components/Editor/Editor.vue | 32 + .../src/views/Components/Form/DefaultForm.vue | 1127 +++++++++++++++++ .../src/views/Components/Form/RefForm.vue | 269 ++++ .../src/views/Components/Form/UseFormDemo.vue | 279 ++++ .../src/views/Components/Highlight.vue | 20 + kinit-admin/src/views/Components/Icon.vue | 62 + .../src/views/Components/ImageViewer.vue | 30 + kinit-admin/src/views/Components/Infotip.vue | 33 + .../src/views/Components/InputPassword.vue | 21 + kinit-admin/src/views/Components/Qrcode.vue | 108 ++ kinit-admin/src/views/Components/Search.vue | 226 ++++ kinit-admin/src/views/Components/Sticky.vue | 62 + .../views/Components/Table/DefaultTable.vue | 100 ++ .../src/views/Components/Table/RefTable.vue | 181 +++ .../views/Components/Table/UseTableDemo.vue | 180 +++ kinit-admin/src/views/Dashboard/Analysis.vue | 127 ++ kinit-admin/src/views/Dashboard/Workplace.vue | 291 +++++ .../views/Dashboard/components/PanelGroup.vue | 200 +++ .../src/views/Dashboard/echarts-data.ts | 310 +++++ kinit-admin/src/views/Error/403.vue | 17 + kinit-admin/src/views/Error/404.vue | 17 + kinit-admin/src/views/Error/500.vue | 17 + .../views/Example/Dialog/ExampleDialog.vue | 273 ++++ .../Example/Dialog/components/Detail.vue | 40 + .../views/Example/Dialog/components/Write.vue | 55 + .../src/views/Example/Page/ExampleAdd.vue | 52 + .../src/views/Example/Page/ExampleDetail.vue | 32 + .../src/views/Example/Page/ExampleEdit.vue | 64 + .../src/views/Example/Page/ExamplePage.vue | 164 +++ .../views/Example/Page/components/Detail.vue | 65 + .../views/Example/Page/components/Write.vue | 146 +++ kinit-admin/src/views/Guide/Guide.vue | 20 + kinit-admin/src/views/Level/Menu111.vue | 20 + kinit-admin/src/views/Level/Menu12.vue | 20 + kinit-admin/src/views/Level/Menu2.vue | 20 + kinit-admin/src/views/Login/Login.vue | 109 ++ .../src/views/Login/components/LoginForm.vue | 257 ++++ .../views/Login/components/RegisterForm.vue | 144 +++ .../src/views/Login/components/index.ts | 4 + kinit-admin/src/views/Redirect/Redirect.vue | 30 + .../src/views/hooks/useCrudSchemas.vue | 222 ++++ kinit-admin/src/views/hooks/useWatermark.vue | 32 + kinit-admin/stylelint.config.js | 231 ++++ kinit-admin/tsconfig.json | 39 + .../types/componentType/configGlobal.d.ts | 3 + .../types/componentType/contextMenu.d.ts | 7 + .../types/componentType/descriptions.d.ts | 11 + kinit-admin/types/componentType/form.d.ts | 97 ++ kinit-admin/types/componentType/icon.d.ts | 5 + kinit-admin/types/componentType/infotip.d.ts | 4 + .../types/componentType/localeDropdown.d.ts | 10 + kinit-admin/types/componentType/qrcode.d.ts | 9 + kinit-admin/types/componentType/table.d.ts | 36 + kinit-admin/types/components.d.ts | 7 + kinit-admin/types/custom-types.d.ts | 27 + kinit-admin/types/env.d.ts | 24 + kinit-admin/types/global.d.ts | 40 + kinit-admin/types/router.d.ts | 74 ++ kinit-admin/vite.config.ts | 152 +++ kinit-admin/windi.config.ts | 76 ++ kinit-api/.gitignore | 31 + kinit-api/README.md | 107 ++ kinit-api/alembic.ini | 100 ++ kinit-api/alembic/README | 1 + kinit-api/alembic/env.py | 96 ++ kinit-api/alembic/script.py.mako | 24 + .../alembic/versions/03e55ed3e858_update.py | 30 + .../alembic/versions/0cd7a858c5a5_update.py | 66 + .../alembic/versions/5e629ba5c3c8_update.py | 88 ++ .../alembic/versions/65797098992b_update.py | 111 ++ .../versions/a95a845d335f_生成迁移文件.py | 24 + .../versions/bc1417d96581_更新迁移文件.py | 46 + .../alembic/versions/d4898760c577_update.py | 28 + .../alembic/versions/ecb50546debd_update.py | 28 + .../alembic/versions/f2390b45da1d_update.py | 28 + kinit-api/application/__init__.py | 0 kinit-api/application/settings.py | 86 ++ kinit-api/application/urls.py | 16 + kinit-api/apps/__init__.py | 13 + kinit-api/apps/vadmin/__init__.py | 0 kinit-api/apps/vadmin/auth/__init__.py | 0 kinit-api/apps/vadmin/auth/crud.py | 144 +++ kinit-api/apps/vadmin/auth/models/__init__.py | 13 + kinit-api/apps/vadmin/auth/models/m2m.py | 28 + kinit-api/apps/vadmin/auth/models/menu.py | 38 + kinit-api/apps/vadmin/auth/models/role.py | 27 + kinit-api/apps/vadmin/auth/models/user.py | 67 + .../apps/vadmin/auth/schemas/__init__.py | 3 + kinit-api/apps/vadmin/auth/schemas/menu.py | 82 ++ kinit-api/apps/vadmin/auth/schemas/role.py | 54 + kinit-api/apps/vadmin/auth/schemas/user.py | 63 + kinit-api/apps/vadmin/auth/utils/__init__.py | 7 + kinit-api/apps/vadmin/auth/utils/auth_util.py | 64 + kinit-api/apps/vadmin/auth/utils/current.py | 108 ++ kinit-api/apps/vadmin/auth/utils/login.py | 97 ++ kinit-api/apps/vadmin/auth/views.py | 126 ++ kinit-api/apps/vadmin/record/__init__.py | 0 kinit-api/apps/vadmin/record/crud.py | 24 + .../apps/vadmin/record/models/__init__.py | 12 + kinit-api/apps/vadmin/record/models/login.py | 43 + .../apps/vadmin/record/models/operation.py | 47 + kinit-api/apps/vadmin/record/models/sms.py | 23 + .../apps/vadmin/record/schemas/__init__.py | 2 + kinit-api/apps/vadmin/record/schemas/login.py | 33 + kinit-api/apps/vadmin/record/schemas/sms.py | 30 + kinit-api/apps/vadmin/record/views.py | 38 + kinit-api/apps/vadmin/system/__init__.py | 0 kinit-api/apps/vadmin/system/crud.py | 42 + .../apps/vadmin/system/models/__init__.py | 1 + kinit-api/apps/vadmin/system/models/dict.py | 36 + .../apps/vadmin/system/schemas/__init__.py | 1 + kinit-api/apps/vadmin/system/schemas/dict.py | 60 + kinit-api/apps/vadmin/system/views.py | 104 ++ kinit-api/core/__init__.py | 0 kinit-api/core/crud.py | 216 ++++ kinit-api/core/database.py | 89 ++ kinit-api/core/dependencies.py | 30 + kinit-api/core/exception.py | 107 ++ kinit-api/core/logger.py | 28 + kinit-api/core/middleware.py | 43 + kinit-api/core/validator.py | 42 + kinit-api/db/__init__.py | 0 kinit-api/db/db_base.py | 23 + kinit-api/logs/.gitkeep | 0 kinit-api/main.py | 80 ++ kinit-api/requirements.txt | 47 + kinit-api/static/.gitkeep | 0 kinit-api/temp/.gitkeep | 0 kinit-api/utils/__init__.py | 0 kinit-api/utils/crawle.py | 27 + kinit-api/utils/response.py | 36 + kinit-api/utils/save_file.py | 22 + kinit-api/utils/status.py | 11 + 353 files changed, 24351 insertions(+), 74 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore delete mode 100644 README.en.md create mode 100644 kinit-admin/.env.base create mode 100644 kinit-admin/.env.dev create mode 100644 kinit-admin/.env.gitee create mode 100644 kinit-admin/.env.pro create mode 100644 kinit-admin/.env.test create mode 100644 kinit-admin/.eslintignore create mode 100644 kinit-admin/.eslintrc.js create mode 100644 kinit-admin/.github/workflows/auto-merge.yml create mode 100644 kinit-admin/.github/workflows/reademe-contributors.yml create mode 100644 kinit-admin/.github/workflows/release.yml create mode 100644 kinit-admin/.gitignore create mode 100644 kinit-admin/.husky/commit-msg create mode 100644 kinit-admin/.husky/lintstagedrc.js create mode 100644 kinit-admin/.husky/pre-commit create mode 100644 kinit-admin/.prettierignore create mode 100644 kinit-admin/.stylelintignore create mode 100644 kinit-admin/.vscode/extensions.json create mode 100644 kinit-admin/.vscode/settings.json create mode 100644 kinit-admin/CHANGELOG.md create mode 100644 kinit-admin/LICENSE create mode 100644 kinit-admin/README.md create mode 100644 kinit-admin/README.zh-CN.md create mode 100644 kinit-admin/commitlint.config.js create mode 100644 kinit-admin/index.html create mode 100644 kinit-admin/mock/_createProductionServer.ts create mode 100644 kinit-admin/mock/analysis/index.ts create mode 100644 kinit-admin/mock/dict/index.ts create mode 100644 kinit-admin/mock/role/index.ts create mode 100644 kinit-admin/mock/table/index.ts create mode 100644 kinit-admin/mock/user/index.ts create mode 100644 kinit-admin/mock/workplace/index.ts create mode 100644 kinit-admin/package.json create mode 100644 kinit-admin/plop/component/component.hbs create mode 100644 kinit-admin/plop/component/index.hbs create mode 100644 kinit-admin/plop/component/prompt.js create mode 100644 kinit-admin/plop/view/prompt.js create mode 100644 kinit-admin/plop/view/view.hbs create mode 100644 kinit-admin/plopfile.js create mode 100644 kinit-admin/postcss.config.js create mode 100644 kinit-admin/prettier.config.js create mode 100644 kinit-admin/public/favicon.ico create mode 100644 kinit-admin/public/logo.png create mode 100644 kinit-admin/src/App.vue create mode 100644 kinit-admin/src/api/common/index.ts create mode 100644 kinit-admin/src/api/dashboard/analysis/index.ts create mode 100644 kinit-admin/src/api/dashboard/analysis/types.ts create mode 100644 kinit-admin/src/api/dashboard/workplace/index.ts create mode 100644 kinit-admin/src/api/dashboard/workplace/types.ts create mode 100644 kinit-admin/src/api/login/index.ts create mode 100644 kinit-admin/src/api/login/types.ts create mode 100644 kinit-admin/src/api/table/index.ts create mode 100644 kinit-admin/src/api/table/types.ts create mode 100644 kinit-admin/src/assets/imgs/avatar.jpg create mode 100644 kinit-admin/src/assets/imgs/logo.png create mode 100644 kinit-admin/src/assets/svgs/403.svg create mode 100644 kinit-admin/src/assets/svgs/404.svg create mode 100644 kinit-admin/src/assets/svgs/500.svg create mode 100644 kinit-admin/src/assets/svgs/icon.svg create mode 100644 kinit-admin/src/assets/svgs/login-bg.svg create mode 100644 kinit-admin/src/assets/svgs/login-box-bg.svg create mode 100644 kinit-admin/src/assets/svgs/message.svg create mode 100644 kinit-admin/src/assets/svgs/money.svg create mode 100644 kinit-admin/src/assets/svgs/peoples.svg create mode 100644 kinit-admin/src/assets/svgs/shopping.svg create mode 100644 kinit-admin/src/components/Backtop/index.ts create mode 100644 kinit-admin/src/components/Backtop/src/Backtop.vue create mode 100644 kinit-admin/src/components/Breadcrumb/index.ts create mode 100644 kinit-admin/src/components/Breadcrumb/src/Breadcrumb.vue create mode 100644 kinit-admin/src/components/Breadcrumb/src/helper.ts create mode 100644 kinit-admin/src/components/Collapse/index.ts create mode 100644 kinit-admin/src/components/Collapse/src/Collapse.vue create mode 100644 kinit-admin/src/components/ConfigGlobal/index.ts create mode 100644 kinit-admin/src/components/ConfigGlobal/src/ConfigGlobal.vue create mode 100644 kinit-admin/src/components/ContentDetailWrap/index.ts create mode 100644 kinit-admin/src/components/ContentDetailWrap/src/ContentDetailWrap.vue create mode 100644 kinit-admin/src/components/ContentWrap/index.ts create mode 100644 kinit-admin/src/components/ContentWrap/src/ContentWrap.vue create mode 100644 kinit-admin/src/components/ContextMenu/index.ts create mode 100644 kinit-admin/src/components/ContextMenu/src/ContextMenu.vue create mode 100644 kinit-admin/src/components/CountTo/index.ts create mode 100644 kinit-admin/src/components/CountTo/src/CountTo.vue create mode 100644 kinit-admin/src/components/Descriptions/index.ts create mode 100644 kinit-admin/src/components/Descriptions/src/Descriptions.vue create mode 100644 kinit-admin/src/components/Dialog/index.ts create mode 100644 kinit-admin/src/components/Dialog/src/Dialog.vue create mode 100644 kinit-admin/src/components/Echart/index.ts create mode 100644 kinit-admin/src/components/Echart/src/Echart.vue create mode 100644 kinit-admin/src/components/Editor/index.ts create mode 100644 kinit-admin/src/components/Editor/src/Editor.vue create mode 100644 kinit-admin/src/components/Error/index.ts create mode 100644 kinit-admin/src/components/Error/src/Error.vue create mode 100644 kinit-admin/src/components/Footer/index.ts create mode 100644 kinit-admin/src/components/Footer/src/Footer.vue create mode 100644 kinit-admin/src/components/Form/index.ts create mode 100644 kinit-admin/src/components/Form/src/Form.vue create mode 100644 kinit-admin/src/components/Form/src/componentMap.ts create mode 100644 kinit-admin/src/components/Form/src/components/useRenderCheckbox.tsx create mode 100644 kinit-admin/src/components/Form/src/components/useRenderRadio.tsx create mode 100644 kinit-admin/src/components/Form/src/components/useRenderSelect.tsx create mode 100644 kinit-admin/src/components/Form/src/helper.ts create mode 100644 kinit-admin/src/components/Form/src/types.ts create mode 100644 kinit-admin/src/components/Highlight/index.ts create mode 100644 kinit-admin/src/components/Highlight/src/Highlight.vue create mode 100644 kinit-admin/src/components/Icon/index.ts create mode 100644 kinit-admin/src/components/Icon/src/Icon.vue create mode 100644 kinit-admin/src/components/ImageViewer/index.ts create mode 100644 kinit-admin/src/components/ImageViewer/src/ImageViewer.vue create mode 100644 kinit-admin/src/components/ImageViewer/src/types.ts create mode 100644 kinit-admin/src/components/Infotip/index.ts create mode 100644 kinit-admin/src/components/Infotip/src/Infotip.vue create mode 100644 kinit-admin/src/components/InputPassword/index.ts create mode 100644 kinit-admin/src/components/InputPassword/src/InputPassword.vue create mode 100644 kinit-admin/src/components/LocaleDropdown/index.ts create mode 100644 kinit-admin/src/components/LocaleDropdown/src/LocaleDropdown.vue create mode 100644 kinit-admin/src/components/Logo/index.ts create mode 100644 kinit-admin/src/components/Logo/src/Logo.vue create mode 100644 kinit-admin/src/components/Menu/index.ts create mode 100644 kinit-admin/src/components/Menu/src/Menu.vue create mode 100644 kinit-admin/src/components/Menu/src/components/useRenderMenuItem.tsx create mode 100644 kinit-admin/src/components/Menu/src/components/useRenderMenuTitle.tsx create mode 100644 kinit-admin/src/components/Menu/src/helper.ts create mode 100644 kinit-admin/src/components/Qrcode/index.ts create mode 100644 kinit-admin/src/components/Qrcode/src/Qrcode.vue create mode 100644 kinit-admin/src/components/Screenfull/index.ts create mode 100644 kinit-admin/src/components/Screenfull/src/Screenfull.vue create mode 100644 kinit-admin/src/components/Search/index.ts create mode 100644 kinit-admin/src/components/Search/src/Search.vue create mode 100644 kinit-admin/src/components/Setting/index.ts create mode 100644 kinit-admin/src/components/Setting/src/Setting.vue create mode 100644 kinit-admin/src/components/Setting/src/components/ColorRadioPicker.vue create mode 100644 kinit-admin/src/components/Setting/src/components/InterfaceDisplay.vue create mode 100644 kinit-admin/src/components/Setting/src/components/LayoutRadioPicker.vue create mode 100644 kinit-admin/src/components/SizeDropdown/index.ts create mode 100644 kinit-admin/src/components/SizeDropdown/src/SizeDropdown.vue create mode 100644 kinit-admin/src/components/Sticky/index.ts create mode 100644 kinit-admin/src/components/Sticky/src/Sticky.vue create mode 100644 kinit-admin/src/components/TabMenu/index.ts create mode 100644 kinit-admin/src/components/TabMenu/src/TabMenu.vue create mode 100644 kinit-admin/src/components/TabMenu/src/helper.ts create mode 100644 kinit-admin/src/components/Table/index.ts create mode 100644 kinit-admin/src/components/Table/src/Table.vue create mode 100644 kinit-admin/src/components/Table/src/helper.ts create mode 100644 kinit-admin/src/components/Table/src/types.ts create mode 100644 kinit-admin/src/components/TagsView/index.ts create mode 100644 kinit-admin/src/components/TagsView/src/TagsView.vue create mode 100644 kinit-admin/src/components/TagsView/src/helper.ts create mode 100644 kinit-admin/src/components/ThemeSwitch/index.ts create mode 100644 kinit-admin/src/components/ThemeSwitch/src/ThemeSwitch.vue create mode 100644 kinit-admin/src/components/UserInfo/index.ts create mode 100644 kinit-admin/src/components/UserInfo/src/UserInfo.vue create mode 100644 kinit-admin/src/components/index.ts create mode 100644 kinit-admin/src/config/app.ts create mode 100644 kinit-admin/src/config/axios/config.ts create mode 100644 kinit-admin/src/config/axios/index.ts create mode 100644 kinit-admin/src/config/axios/service.ts create mode 100644 kinit-admin/src/config/locale.ts create mode 100644 kinit-admin/src/directives/index.ts create mode 100644 kinit-admin/src/directives/permission/hasPermi.ts create mode 100644 kinit-admin/src/hooks/event/useScrollTo.ts create mode 100644 kinit-admin/src/hooks/web/useCache.ts create mode 100644 kinit-admin/src/hooks/web/useConfigGlobal.ts create mode 100644 kinit-admin/src/hooks/web/useCrudSchemas.ts create mode 100644 kinit-admin/src/hooks/web/useDesign.ts create mode 100644 kinit-admin/src/hooks/web/useEmitt.ts create mode 100644 kinit-admin/src/hooks/web/useForm.ts create mode 100644 kinit-admin/src/hooks/web/useI18n.ts create mode 100644 kinit-admin/src/hooks/web/useIcon.ts create mode 100644 kinit-admin/src/hooks/web/useIntro.ts create mode 100644 kinit-admin/src/hooks/web/useLocale.ts create mode 100644 kinit-admin/src/hooks/web/useNProgress.ts create mode 100644 kinit-admin/src/hooks/web/usePageLoading.ts create mode 100644 kinit-admin/src/hooks/web/useTable.ts create mode 100644 kinit-admin/src/hooks/web/useTimeAgo.ts create mode 100644 kinit-admin/src/hooks/web/useTitle.ts create mode 100644 kinit-admin/src/hooks/web/useValidator.ts create mode 100644 kinit-admin/src/hooks/web/useWatermark.ts create mode 100644 kinit-admin/src/layout/Layout.vue create mode 100644 kinit-admin/src/layout/components/AppView.vue create mode 100644 kinit-admin/src/layout/components/ToolHeader.vue create mode 100644 kinit-admin/src/layout/components/useRenderLayout.tsx create mode 100644 kinit-admin/src/locales/en.ts create mode 100644 kinit-admin/src/locales/zh-CN.ts create mode 100644 kinit-admin/src/main.ts create mode 100644 kinit-admin/src/permission.ts create mode 100644 kinit-admin/src/plugins/animate.css/index.ts create mode 100644 kinit-admin/src/plugins/echarts/index.ts create mode 100644 kinit-admin/src/plugins/elementPlus/index.ts create mode 100644 kinit-admin/src/plugins/svgIcon/index.ts create mode 100644 kinit-admin/src/plugins/vueI18n/helper.ts create mode 100644 kinit-admin/src/plugins/vueI18n/index.ts create mode 100644 kinit-admin/src/plugins/windi.css/index.ts create mode 100644 kinit-admin/src/router/index.ts create mode 100644 kinit-admin/src/store/index.ts create mode 100644 kinit-admin/src/store/modules/app.ts create mode 100644 kinit-admin/src/store/modules/dict.ts create mode 100644 kinit-admin/src/store/modules/locale.ts create mode 100644 kinit-admin/src/store/modules/permission.ts create mode 100644 kinit-admin/src/store/modules/tagsView.ts create mode 100644 kinit-admin/src/styles/index.less create mode 100644 kinit-admin/src/styles/theme.less create mode 100644 kinit-admin/src/styles/var.css create mode 100644 kinit-admin/src/styles/variables.module.less create mode 100644 kinit-admin/src/utils/color.ts create mode 100644 kinit-admin/src/utils/domUtils.ts create mode 100644 kinit-admin/src/utils/index.ts create mode 100644 kinit-admin/src/utils/is.ts create mode 100644 kinit-admin/src/utils/propTypes.ts create mode 100644 kinit-admin/src/utils/routerHelper.ts create mode 100644 kinit-admin/src/utils/tree.ts create mode 100644 kinit-admin/src/utils/tsxHelper.ts create mode 100644 kinit-admin/src/views/Authorization/Role.vue create mode 100644 kinit-admin/src/views/Authorization/User.vue create mode 100644 kinit-admin/src/views/Components/CountTo.vue create mode 100644 kinit-admin/src/views/Components/Descriptions.vue create mode 100644 kinit-admin/src/views/Components/Dialog.vue create mode 100644 kinit-admin/src/views/Components/Echart.vue create mode 100644 kinit-admin/src/views/Components/Editor/Editor.vue create mode 100644 kinit-admin/src/views/Components/Form/DefaultForm.vue create mode 100644 kinit-admin/src/views/Components/Form/RefForm.vue create mode 100644 kinit-admin/src/views/Components/Form/UseFormDemo.vue create mode 100644 kinit-admin/src/views/Components/Highlight.vue create mode 100644 kinit-admin/src/views/Components/Icon.vue create mode 100644 kinit-admin/src/views/Components/ImageViewer.vue create mode 100644 kinit-admin/src/views/Components/Infotip.vue create mode 100644 kinit-admin/src/views/Components/InputPassword.vue create mode 100644 kinit-admin/src/views/Components/Qrcode.vue create mode 100644 kinit-admin/src/views/Components/Search.vue create mode 100644 kinit-admin/src/views/Components/Sticky.vue create mode 100644 kinit-admin/src/views/Components/Table/DefaultTable.vue create mode 100644 kinit-admin/src/views/Components/Table/RefTable.vue create mode 100644 kinit-admin/src/views/Components/Table/UseTableDemo.vue create mode 100644 kinit-admin/src/views/Dashboard/Analysis.vue create mode 100644 kinit-admin/src/views/Dashboard/Workplace.vue create mode 100644 kinit-admin/src/views/Dashboard/components/PanelGroup.vue create mode 100644 kinit-admin/src/views/Dashboard/echarts-data.ts create mode 100644 kinit-admin/src/views/Error/403.vue create mode 100644 kinit-admin/src/views/Error/404.vue create mode 100644 kinit-admin/src/views/Error/500.vue create mode 100644 kinit-admin/src/views/Example/Dialog/ExampleDialog.vue create mode 100644 kinit-admin/src/views/Example/Dialog/components/Detail.vue create mode 100644 kinit-admin/src/views/Example/Dialog/components/Write.vue create mode 100644 kinit-admin/src/views/Example/Page/ExampleAdd.vue create mode 100644 kinit-admin/src/views/Example/Page/ExampleDetail.vue create mode 100644 kinit-admin/src/views/Example/Page/ExampleEdit.vue create mode 100644 kinit-admin/src/views/Example/Page/ExamplePage.vue create mode 100644 kinit-admin/src/views/Example/Page/components/Detail.vue create mode 100644 kinit-admin/src/views/Example/Page/components/Write.vue create mode 100644 kinit-admin/src/views/Guide/Guide.vue create mode 100644 kinit-admin/src/views/Level/Menu111.vue create mode 100644 kinit-admin/src/views/Level/Menu12.vue create mode 100644 kinit-admin/src/views/Level/Menu2.vue create mode 100644 kinit-admin/src/views/Login/Login.vue create mode 100644 kinit-admin/src/views/Login/components/LoginForm.vue create mode 100644 kinit-admin/src/views/Login/components/RegisterForm.vue create mode 100644 kinit-admin/src/views/Login/components/index.ts create mode 100644 kinit-admin/src/views/Redirect/Redirect.vue create mode 100644 kinit-admin/src/views/hooks/useCrudSchemas.vue create mode 100644 kinit-admin/src/views/hooks/useWatermark.vue create mode 100644 kinit-admin/stylelint.config.js create mode 100644 kinit-admin/tsconfig.json create mode 100644 kinit-admin/types/componentType/configGlobal.d.ts create mode 100644 kinit-admin/types/componentType/contextMenu.d.ts create mode 100644 kinit-admin/types/componentType/descriptions.d.ts create mode 100644 kinit-admin/types/componentType/form.d.ts create mode 100644 kinit-admin/types/componentType/icon.d.ts create mode 100644 kinit-admin/types/componentType/infotip.d.ts create mode 100644 kinit-admin/types/componentType/localeDropdown.d.ts create mode 100644 kinit-admin/types/componentType/qrcode.d.ts create mode 100644 kinit-admin/types/componentType/table.d.ts create mode 100644 kinit-admin/types/components.d.ts create mode 100644 kinit-admin/types/custom-types.d.ts create mode 100644 kinit-admin/types/env.d.ts create mode 100644 kinit-admin/types/global.d.ts create mode 100644 kinit-admin/types/router.d.ts create mode 100644 kinit-admin/vite.config.ts create mode 100644 kinit-admin/windi.config.ts create mode 100644 kinit-api/.gitignore create mode 100644 kinit-api/README.md create mode 100644 kinit-api/alembic.ini create mode 100644 kinit-api/alembic/README create mode 100644 kinit-api/alembic/env.py create mode 100644 kinit-api/alembic/script.py.mako create mode 100644 kinit-api/alembic/versions/03e55ed3e858_update.py create mode 100644 kinit-api/alembic/versions/0cd7a858c5a5_update.py create mode 100644 kinit-api/alembic/versions/5e629ba5c3c8_update.py create mode 100644 kinit-api/alembic/versions/65797098992b_update.py create mode 100644 kinit-api/alembic/versions/a95a845d335f_生成迁移文件.py create mode 100644 kinit-api/alembic/versions/bc1417d96581_更新迁移文件.py create mode 100644 kinit-api/alembic/versions/d4898760c577_update.py create mode 100644 kinit-api/alembic/versions/ecb50546debd_update.py create mode 100644 kinit-api/alembic/versions/f2390b45da1d_update.py create mode 100644 kinit-api/application/__init__.py create mode 100644 kinit-api/application/settings.py create mode 100644 kinit-api/application/urls.py create mode 100644 kinit-api/apps/__init__.py create mode 100644 kinit-api/apps/vadmin/__init__.py create mode 100644 kinit-api/apps/vadmin/auth/__init__.py create mode 100644 kinit-api/apps/vadmin/auth/crud.py create mode 100644 kinit-api/apps/vadmin/auth/models/__init__.py create mode 100644 kinit-api/apps/vadmin/auth/models/m2m.py create mode 100644 kinit-api/apps/vadmin/auth/models/menu.py create mode 100644 kinit-api/apps/vadmin/auth/models/role.py create mode 100644 kinit-api/apps/vadmin/auth/models/user.py create mode 100644 kinit-api/apps/vadmin/auth/schemas/__init__.py create mode 100644 kinit-api/apps/vadmin/auth/schemas/menu.py create mode 100644 kinit-api/apps/vadmin/auth/schemas/role.py create mode 100644 kinit-api/apps/vadmin/auth/schemas/user.py create mode 100644 kinit-api/apps/vadmin/auth/utils/__init__.py create mode 100644 kinit-api/apps/vadmin/auth/utils/auth_util.py create mode 100644 kinit-api/apps/vadmin/auth/utils/current.py create mode 100644 kinit-api/apps/vadmin/auth/utils/login.py create mode 100644 kinit-api/apps/vadmin/auth/views.py create mode 100644 kinit-api/apps/vadmin/record/__init__.py create mode 100644 kinit-api/apps/vadmin/record/crud.py create mode 100644 kinit-api/apps/vadmin/record/models/__init__.py create mode 100644 kinit-api/apps/vadmin/record/models/login.py create mode 100644 kinit-api/apps/vadmin/record/models/operation.py create mode 100644 kinit-api/apps/vadmin/record/models/sms.py create mode 100644 kinit-api/apps/vadmin/record/schemas/__init__.py create mode 100644 kinit-api/apps/vadmin/record/schemas/login.py create mode 100644 kinit-api/apps/vadmin/record/schemas/sms.py create mode 100644 kinit-api/apps/vadmin/record/views.py create mode 100644 kinit-api/apps/vadmin/system/__init__.py create mode 100644 kinit-api/apps/vadmin/system/crud.py create mode 100644 kinit-api/apps/vadmin/system/models/__init__.py create mode 100644 kinit-api/apps/vadmin/system/models/dict.py create mode 100644 kinit-api/apps/vadmin/system/schemas/__init__.py create mode 100644 kinit-api/apps/vadmin/system/schemas/dict.py create mode 100644 kinit-api/apps/vadmin/system/views.py create mode 100644 kinit-api/core/__init__.py create mode 100644 kinit-api/core/crud.py create mode 100644 kinit-api/core/database.py create mode 100644 kinit-api/core/dependencies.py create mode 100644 kinit-api/core/exception.py create mode 100644 kinit-api/core/logger.py create mode 100644 kinit-api/core/middleware.py create mode 100644 kinit-api/core/validator.py create mode 100644 kinit-api/db/__init__.py create mode 100644 kinit-api/db/db_base.py create mode 100644 kinit-api/logs/.gitkeep create mode 100644 kinit-api/main.py create mode 100644 kinit-api/requirements.txt create mode 100644 kinit-api/static/.gitkeep create mode 100644 kinit-api/temp/.gitkeep create mode 100644 kinit-api/utils/__init__.py create mode 100644 kinit-api/utils/crawle.py create mode 100644 kinit-api/utils/response.py create mode 100644 kinit-api/utils/save_file.py create mode 100644 kinit-api/utils/status.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..71c0ecd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.css linguist-language=Python +*.less linguist-language=Python +*.js linguist-language=Python +*.html linguist-language=Python diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bf1f92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Build and Release Folders +bin-debug/ +bin-release/ +[Oo]bj/ +[Bb]in/ + +# Other files and folders +.settings/ + +# Executables +*.swf +*.air +*.ipa +*.apk + +# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` +# should NOT be excluded as they contain compiler settings and other important +# information for Eclipse / Flash Builder. +docker_env/mysql/data/ +docker_env/redis/data/ +*/.idea +dvadmin-doc/docs/.vuepress/dist \ No newline at end of file diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 64dc395..0000000 --- a/README.en.md +++ /dev/null @@ -1,37 +0,0 @@ -# kinit - -#### Description -基于fastapi与vue3+typescript-vite3-element-plus的基础项目 -前端基于vue-element-plus-admin框架开发 - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 07bb71e..e46fca7 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,86 @@ -# kinit - -#### 介绍 +# kinit + +一天出租屋的下午,脑子里浮现出了多个画面,但是总是停留在了想,未行动,在娱乐。 + +这样的日子很不快乐,总想做点什么,但是总觉得做什么都很困难,不管了,我先做了再说。 + +因为热爱,所以拥抱未来! + + + +## 介绍 基于fastapi与vue3+typescript-vite3-element-plus的基础项目 -前端基于vue-element-plus-admin框架开发 - -#### 软件架构 -软件架构说明 - - -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) + +前端基于vue-element-plus-admin框架开发 + + + +这个项目主要包括项目的基础功能,用户权限管理,登录功能,简单的几个页面。 + +之后的项目将由在该项目的基础上开发。 + + + +开源项目参考: + +vue-element-plus-admin:https://gitee.com/kailong110120130/vue-element-plus-admin + +项目地址:https://gitee.com/ktianc/kinit + +## 软件架构 + +### 项目结构 + +| 项目名称 | 开发框架 | 标识 | 访问地址 | +| ------------ | --------------- | ----------- | -------- | +| 项目接口 | Fastapi 0.79 | kinit-api | | +| 后台管理系统 | Vue3 | kinit-admin | | +| 微信小程序 | Uni-app(Vue3) | kinit-wx | | + +### 项目分支 + +Git 仓库: + +| 分支 | 描述 | +| ------- | ---------------- | +| master | 主分支,开发分支 | +| release | 版本分支 | + +### 项目技术 + +| 分类 | 技术 | +| ---------------- | ---------------------------------------- | +| 数据库 | Mysql 8.0,Redis | +| 接口开发 | Fastapi 0.79 | +| 管理系统前端开发 | Vue3 + Vite3 + Typescript + Element Plus | +| 微信小程序开发 | Uni-app(Vue3)+ Uview 2.0 | + + +## 安装教程 + +1. xxxx +2. xxxx +3. xxxx + +## 使用说明 + +1. xxxx +2. xxxx +3. xxxx + +## 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + + +## 特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/kinit-admin/.env.base b/kinit-admin/.env.base new file mode 100644 index 0000000..8a8d91c --- /dev/null +++ b/kinit-admin/.env.base @@ -0,0 +1,11 @@ +# 环境 +NODE_ENV=development + +# 接口前缀 +VITE_API_BASEPATH=base + +# 打包路径 +VITE_BASE_PATH=/ + +# 标题 +VITE_APP_TITLE=ElementAdmin diff --git a/kinit-admin/.env.dev b/kinit-admin/.env.dev new file mode 100644 index 0000000..6a641a3 --- /dev/null +++ b/kinit-admin/.env.dev @@ -0,0 +1,23 @@ +# 环境 +NODE_ENV=production + +# 接口前缀 +VITE_API_BASEPATH=dev + +# 打包路径 +VITE_BASE_PATH=/dist-dev/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=true + +# 输出路径 +VITE_OUT_DIR=dist-dev + +# 标题 +VITE_APP_TITLE=ElementAdmin diff --git a/kinit-admin/.env.gitee b/kinit-admin/.env.gitee new file mode 100644 index 0000000..c55b595 --- /dev/null +++ b/kinit-admin/.env.gitee @@ -0,0 +1,23 @@ +# 环境 +NODE_ENV=production + +# 接口前缀 +VITE_API_BASEPATH=pro + +# 打包路径 +VITE_BASE_PATH=/vue-element-plus-admin/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 输出路径 +VITE_OUT_DIR=dist-pro + +# 标题 +VITE_APP_TITLE=ElementAdmin diff --git a/kinit-admin/.env.pro b/kinit-admin/.env.pro new file mode 100644 index 0000000..99a9480 --- /dev/null +++ b/kinit-admin/.env.pro @@ -0,0 +1,23 @@ +# 环境 +NODE_ENV=production + +# 接口前缀 +VITE_API_BASEPATH=pro + +# 打包路径 +VITE_BASE_PATH=/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 输出路径 +VITE_OUT_DIR=dist-pro + +# 标题 +VITE_APP_TITLE=ElementAdmin diff --git a/kinit-admin/.env.test b/kinit-admin/.env.test new file mode 100644 index 0000000..3f6ebe1 --- /dev/null +++ b/kinit-admin/.env.test @@ -0,0 +1,23 @@ +# 环境 +NODE_ENV=production + +# 接口前缀 +VITE_API_BASEPATH=test + +# 打包路径 +VITE_BASE_PATH=/dist-test/ + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=true + +# 输出路径 +VITE_OUT_DIR=dist-test + +# 标题 +VITE_APP_TITLE=ElementAdmin diff --git a/kinit-admin/.eslintignore b/kinit-admin/.eslintignore new file mode 100644 index 0000000..1e85c0f --- /dev/null +++ b/kinit-admin/.eslintignore @@ -0,0 +1,8 @@ +/build/ +/config/ +/dist/ +/*.js +/test/unit/coverage/ +/node_modules/* +/dist* +/src/main.ts diff --git a/kinit-admin/.eslintrc.js b/kinit-admin/.eslintrc.js new file mode 100644 index 0000000..2aec88b --- /dev/null +++ b/kinit-admin/.eslintrc.js @@ -0,0 +1,69 @@ +// @ts-check +const { defineConfig } = require('eslint-define-config') +module.exports = defineConfig({ + root: true, + env: { + browser: true, + node: true, + es6: true + }, + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020, + sourceType: 'module', + jsxPragma: 'React', + ecmaFeatures: { + jsx: true + } + }, + extends: [ + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + 'plugin:prettier/recommended' + ], + rules: { + 'vue/script-setup-uses-vars': 'error', + 'vue/no-reserved-component-names': 'off', + '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-empty-function': 'off', + 'vue/custom-event-name-casing': 'off', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'no-unused-vars': 'off', + 'space-before-function-paren': 'off', + + 'vue/attributes-order': 'off', + 'vue/one-component-per-file': 'off', + 'vue/html-closing-bracket-newline': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/multiline-html-element-content-newline': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/attribute-hyphenation': 'off', + 'vue/require-default-prop': 'off', + 'vue/require-explicit-emits': 'off', + 'vue/html-self-closing': [ + 'error', + { + html: { + void: 'always', + normal: 'never', + component: 'always' + }, + svg: 'always', + math: 'always' + } + ], + 'vue/multi-word-component-names': 'off', + 'vue/no-v-html': 'off' + } +}) diff --git a/kinit-admin/.github/workflows/auto-merge.yml b/kinit-admin/.github/workflows/auto-merge.yml new file mode 100644 index 0000000..33b6e00 --- /dev/null +++ b/kinit-admin/.github/workflows/auto-merge.yml @@ -0,0 +1,130 @@ +name: Automerge + +on: + pull_request: + types: + - labeled + - unlabeled + - synchronize + - opened + - edited + - ready_for_review + - reopened + - unlocked + pull_request_review: + types: + - submitted + status: {} + +jobs: + # 合并发布版本的 pr 到 master + auto-merge: + runs-on: ubuntu-latest + steps: + - name: Automerge + uses: 'pascalgn/automerge-action@v0.14.3' + env: + GITHUB_TOKEN: '${{ secrets.TOKEN }}' + MERGE_LABELS: '' + MERGE_FILTER_AUTHOR: 'kailong321200875' + + push-to-gh-pages: + needs: [auto-merge] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Pnpm + uses: pnpm/action-setup@v2 + with: + version: latest + + - name: use Node.js 16 + uses: actions/setup-node@v2.1.2 + with: + node-version: '16.x' + + - name: Set SSH Environment + env: + DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} + run: | + mkdir -p ~/.ssh/ + echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan github.com > ~/.ssh/known_hosts + chmod 700 ~/.ssh && chmod 600 ~/.ssh/* + git config --local user.email "321200875@qq.com" + git config --local user.name "kailong321200875" + + # 发布到 github + - name: Build Github + run: | + pnpm install --no-frozen-lockfile + pnpm run build:pro + + - name: Deploy Github + uses: peaceiris/actions-gh-pages@v3 + with: + deploy_key: ${{secrets.ACTIONS_DEPLOY_KEY}} + publish_branch: gh-pages + publish_dir: ./dist-pro + force_orphan: true + cname: element-plus-admin.cn + + push-to-gh-pages-gitee: + needs: [auto-merge] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Pnpm + uses: pnpm/action-setup@v2 + with: + version: latest + + - name: use Node.js 16 + uses: actions/setup-node@v2.1.2 + with: + node-version: '16.x' + + - name: Set SSH Environment + env: + DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} + run: | + mkdir -p ~/.ssh/ + echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan github.com > ~/.ssh/known_hosts + chmod 700 ~/.ssh && chmod 600 ~/.ssh/* + git config --local user.email "321200875@qq.com" + git config --local user.name "kailong321200875" + + - name: Build Gitee + run: | + pnpm install --no-frozen-lockfile + pnpm run build:gitee + + # 发布到 gitee + - name: Deploy Gitee + uses: peaceiris/actions-gh-pages@v3 + with: + deploy_key: ${{secrets.ACTIONS_DEPLOY_KEY}} + publish_branch: gh-pages-gitee + publish_dir: ./dist-pro + force_orphan: true + + - name: Sync Github Repos To Gitee # 名字随便起 + uses: Yikun/hub-mirror-action@v1.1 # 使用Yikun/hub-mirror-action + with: + src: github/kailong321200875 # 源端账户名(github) + dst: gitee/kailong110120130 # 目的端账户名(gitee) + dst_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} # SSH密钥对中的私钥 + dst_token: ${{ secrets.GITEE_TOKEN }} # Gitee账户的私人令牌 + account_type: user # 账户类型 + clone_style: 'https' # 使用https方式进行clone,也可以使用ssh + debug: true # 启用后会显示所有执行命令 + force_update: true # 启用后,强制同步,即强制覆盖目的端仓库 + static_list: 'vue-element-plus-admin' # 静态同步列表,在此填写需要同步的仓库名称,可填写多个 + timeout: '600s' # git超时设置,超时后会自动重试git操作 diff --git a/kinit-admin/.github/workflows/reademe-contributors.yml b/kinit-admin/.github/workflows/reademe-contributors.yml new file mode 100644 index 0000000..8cdb00f --- /dev/null +++ b/kinit-admin/.github/workflows/reademe-contributors.yml @@ -0,0 +1,25 @@ +on: + push: + branches: + - master + +jobs: + contrib-readme-en-job: + runs-on: ubuntu-latest + name: A job to automate contrib in readme + steps: + - name: Contribute List + uses: akhilmhdh/contributors-readme-action@v2.3.4 + env: + GITHUB_TOKEN: ${{ secrets.CONTRIBUTORS_TOKEN }} + + contrib-readme-job: + runs-on: ubuntu-latest + name: A job to automate contrib in readme.zh-CN + steps: + - name: Contribute List + uses: akhilmhdh/contributors-readme-action@v2.3.4 + with: + readme_path: README.zh-CN.md + env: + GITHUB_TOKEN: ${{ secrets.CONTRIBUTORS_TOKEN }} diff --git a/kinit-admin/.github/workflows/release.yml b/kinit-admin/.github/workflows/release.yml new file mode 100644 index 0000000..609f607 --- /dev/null +++ b/kinit-admin/.github/workflows/release.yml @@ -0,0 +1,18 @@ +on: + push: + branches: + - master + +name: Release + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: GoogleCloudPlatform/release-please-action@v3 + id: release + with: + token: ${{ secrets.TOKEN }} + release-type: node + package-name: standard-version + changelog-types: '[{"type": "types", "section":"Types", "hidden": false},{"type": "revert", "section":"Reverts", "hidden": false},{"type": "feat", "section": "Features", "hidden": false},{"type": "fix", "section": "Bug Fixes", "hidden": false},{"type": "improvement", "section": "Feature Improvements", "hidden": false},{"type": "docs", "section":"Docs", "hidden": false},{"type": "style", "section":"Styling", "hidden": false},{"type": "refactor", "section":"Code Refactoring", "hidden": false},{"type": "perf", "section":"Performance Improvements", "hidden": false},{"type": "test", "section":"Tests", "hidden": false},{"type": "build", "section":"Build System", "hidden": false},{"type": "ci", "section":"CI", "hidden":false}]' diff --git a/kinit-admin/.gitignore b/kinit-admin/.gitignore new file mode 100644 index 0000000..0b05e6a --- /dev/null +++ b/kinit-admin/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +/dist* +*-lock.* +pnpm-debug diff --git a/kinit-admin/.husky/commit-msg b/kinit-admin/.husky/commit-msg new file mode 100644 index 0000000..10cb551 --- /dev/null +++ b/kinit-admin/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +pnpm commitlint --edit "$1" diff --git a/kinit-admin/.husky/lintstagedrc.js b/kinit-admin/.husky/lintstagedrc.js new file mode 100644 index 0000000..02afbb2 --- /dev/null +++ b/kinit-admin/.husky/lintstagedrc.js @@ -0,0 +1,9 @@ +module.exports = { + '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], + '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'], + 'package.json': ['prettier --write'], + '*.vue': ['prettier --write', 'stylelint --fix'], + '*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write'], + '*.md': ['prettier --write'], + '*.hbs': ['prettier --write'] +} diff --git a/kinit-admin/.husky/pre-commit b/kinit-admin/.husky/pre-commit new file mode 100644 index 0000000..a6a2318 --- /dev/null +++ b/kinit-admin/.husky/pre-commit @@ -0,0 +1,8 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +[ -n "$CI" ] && exit 0 + +# Format and submit code according to lintstagedrc.js configuration +npm run ts:check +npm run lint:lint-staged diff --git a/kinit-admin/.prettierignore b/kinit-admin/.prettierignore new file mode 100644 index 0000000..15cf071 --- /dev/null +++ b/kinit-admin/.prettierignore @@ -0,0 +1,10 @@ +/node_modules/** +/dist/ +/dist* +/public/* +/docs/* +/vite.config.ts +/src/types/env.d.ts +/docs/**/* +/plop/**/* +CHANGELOG diff --git a/kinit-admin/.stylelintignore b/kinit-admin/.stylelintignore new file mode 100644 index 0000000..aa605b4 --- /dev/null +++ b/kinit-admin/.stylelintignore @@ -0,0 +1,6 @@ +/dist/* +/public/* +public/* +/dist* +/src/types/env.d.ts +/docs/**/* diff --git a/kinit-admin/.vscode/extensions.json b/kinit-admin/.vscode/extensions.json new file mode 100644 index 0000000..62a1b22 --- /dev/null +++ b/kinit-admin/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["vue.volar", "lokalise.i18n-ally"] +} diff --git a/kinit-admin/.vscode/settings.json b/kinit-admin/.vscode/settings.json new file mode 100644 index 0000000..8bafd70 --- /dev/null +++ b/kinit-admin/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "prettier.enable": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "[vue]": { + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + }, + "i18n-ally.localesPaths": ["src/locales"], + "i18n-ally.keystyle": "nested", + "i18n-ally.sortKeys": true, + "i18n-ally.namespace": false, + "i18n-ally.enabledParsers": ["ts"], + "i18n-ally.sourceLanguage": "en", + "i18n-ally.displayLanguage": "zh-CN", + "i18n-ally.enabledFrameworks": ["vue", "react"], + "god.tsconfig": "./tsconfig.json" +} diff --git a/kinit-admin/CHANGELOG.md b/kinit-admin/CHANGELOG.md new file mode 100644 index 0000000..e41e0c3 --- /dev/null +++ b/kinit-admin/CHANGELOG.md @@ -0,0 +1,643 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [1.6.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.2...v1.6.3) (2022-08-20) + + +### Bug Fixes + +* 修复重定向错误 ([89d03fd](https://github.com/kailong321200875/vue-element-plus-admin/commit/89d03fd067e7aca565ceb84ea9276f340bbfcb60)) + + +### Styling + +* 调整样式 ([d29e151](https://github.com/kailong321200875/vue-element-plus-admin/commit/d29e151f8a660031a685f6ef9f789532b1b7b58b)) + +## [1.6.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.1...v1.6.2) (2022-08-13) + + +### Styling + +* Misspelling ([c43e833](https://github.com/kailong321200875/vue-element-plus-admin/commit/c43e833582e4f14ac78b0683f1eb3bdeb9fb4821)) +* perfect tableDemo ([c589edd](https://github.com/kailong321200875/vue-element-plus-admin/commit/c589edd960b23ad0c8b56d2c7880b61014114d45)) + + +### Code Refactoring + +* refactor axios ([0980640](https://github.com/kailong321200875/vue-element-plus-admin/commit/0980640f65fc80e3e58ba49e98db10b7b1b80077)) + +## [1.6.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.0...v1.6.1) (2022-07-30) + + +### Bug Fixes + +* fix menu active bug ([ff59fc7](https://github.com/kailong321200875/vue-element-plus-admin/commit/ff59fc7e133202945360a7e210f9cdf3a4a89dd7)) +* Pie chart data not updated ([55d4ce7](https://github.com/kailong321200875/vue-element-plus-admin/commit/55d4ce7e35ff9a0c5590bc3589160cfd304d3ae5)) + + +### Performance Improvements + +* add static router ([55522b0](https://github.com/kailong321200875/vue-element-plus-admin/commit/55522b0661a8df3ad3c8afafcc9f8fcb162c5a00)) + + +### Styling + +* tagviews styles update ([bff7d93](https://github.com/kailong321200875/vue-element-plus-admin/commit/bff7d9370db7a9c171828721bb99643dac2f235d)) + +## [1.6.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.4...v1.6.0) (2022-07-18) + + +### Features + +* 添加按钮权限 ([7bef662](https://github.com/kailong321200875/vue-element-plus-admin/commit/7bef662db1e91aa8164e9f7a92de3fe4480a3c3b)) + + +### Performance Improvements + +* 移除md5依赖 ([a123097](https://github.com/kailong321200875/vue-element-plus-admin/commit/a123097f1f38eac45e945c7d3fdccafc16ea9b69)) + + +### Styling + +* 压缩图片尺寸 ([ae3c565](https://github.com/kailong321200875/vue-element-plus-admin/commit/ae3c5657b604ffbdae5ce3ce3603626ad4acc5e5)) + + +### Docs + +* vite2 to vite3 ([b3918b9](https://github.com/kailong321200875/vue-element-plus-admin/commit/b3918b9c3c5de4b48811ec95967851cfb3c231e1)) +* vite2 to vite3 ([aaf07de](https://github.com/kailong321200875/vue-element-plus-admin/commit/aaf07de77aa600332880a894faa35757f787c012)) + +## [1.6.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.4...v1.6.0) (2022-07-18) + + +### Features + +* 添加按钮权限 ([7bef662](https://github.com/kailong321200875/vue-element-plus-admin/commit/7bef662db1e91aa8164e9f7a92de3fe4480a3c3b)) + + +### Performance Improvements + +* 移除md5依赖 ([a123097](https://github.com/kailong321200875/vue-element-plus-admin/commit/a123097f1f38eac45e945c7d3fdccafc16ea9b69)) + + +### Styling + +* 压缩图片尺寸 ([ae3c565](https://github.com/kailong321200875/vue-element-plus-admin/commit/ae3c5657b604ffbdae5ce3ce3603626ad4acc5e5)) + + +### Docs + +* vite2 to vite3 ([b3918b9](https://github.com/kailong321200875/vue-element-plus-admin/commit/b3918b9c3c5de4b48811ec95967851cfb3c231e1)) +* vite2 to vite3 ([aaf07de](https://github.com/kailong321200875/vue-element-plus-admin/commit/aaf07de77aa600332880a894faa35757f787c012)) + +## [1.5.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.3...v1.5.4) (2022-07-16) + + +### Bug Fixes + +* fix build:test error ([14530cf](https://github.com/kailong321200875/vue-element-plus-admin/commit/14530cf790bfbbe37c72fa831f0376bbb4209e9d)) + +## [1.5.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.2...v1.5.3) (2022-07-01) + + +### Bug Fixes + +* fix useCrudSchemas not work ([0a855b9](https://github.com/kailong321200875/vue-element-plus-admin/commit/0a855b93e282dfe7863b3fce31dde5d7e0d3e2b6)) + +## [1.5.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.1...v1.5.2) (2022-07-01) + + +### Performance Improvements + +* add useCrudSchemas demo ([ca3ce54](https://github.com/kailong321200875/vue-element-plus-admin/commit/ca3ce54630b723d87415b14c642440d6734876ff)) + +## [1.5.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.5.0...v1.5.1) (2022-07-01) + + +### Bug Fixes + +* change showMainRoute to canTo ([5e292f8](https://github.com/kailong321200875/vue-element-plus-admin/commit/5e292f8a2b2ded7297a2a76893e113ac81517d23)) + + +### Performance Improvements + +* dialog combine with form ([34aefb6](https://github.com/kailong321200875/vue-element-plus-admin/commit/34aefb64ab9237521a1225925264818eebff9ad3)) +* dynamic options demo ([1acb4d7](https://github.com/kailong321200875/vue-element-plus-admin/commit/1acb4d7e8f449ba342699f1b4387ac2404a4c1fb)) +* dynamic options demo ([9a3b617](https://github.com/kailong321200875/vue-element-plus-admin/commit/9a3b6177aa0fbc99c86c5073a1c6c696e1eaf890)) +* useCrudSchemas cutom label ([7864d83](https://github.com/kailong321200875/vue-element-plus-admin/commit/7864d830e2134d814609e722b7bad1754ea9460e)) + +## [1.5.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.5...v1.5.0) (2022-06-25) + + +### Features + +* refactoring API ([37b7583](https://github.com/kailong321200875/vue-element-plus-admin/commit/37b75839a591648b145065432efb1dc8c7a3b917)) + + +### Bug Fixes + +* 修复axios已知问题 ([537af57](https://github.com/kailong321200875/vue-element-plus-admin/commit/537af57a0aaa24c88ebe75acf52dc0403a58b04b)) + + +### Performance Improvements + +* perf axios config ([39edd84](https://github.com/kailong321200875/vue-element-plus-admin/commit/39edd84023109a84683c21ea33e41bd024756520)) + +## [1.4.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.4...v1.4.5) (2022-06-09) + + +### Bug Fixes + +* fix tagsview not work ([d88e051](https://github.com/kailong321200875/vue-element-plus-admin/commit/d88e0514349e877f1c5280a48f9b1bd2bfd622bf)) +* fix tagsview not work ([1bf2d4c](https://github.com/kailong321200875/vue-element-plus-admin/commit/1bf2d4c77287fdca7ed1cb8c9998a53f1375dc6f)) + + +### Types + +* fix types error ([586486a](https://github.com/kailong321200875/vue-element-plus-admin/commit/586486a68d4bf2a024e50a79945b4007324f642d)) + +## [1.4.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.3...v1.4.4) (2022-06-06) + + +### Types + +* fix type error ([d66f12e](https://github.com/kailong321200875/vue-element-plus-admin/commit/d66f12e0e77f6acf485bae06509d9ea4abcd1eaa)) + +### [1.4.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.2...v1.4.3) (2022-06-01) + + +### Bug Fixes + +* multiple requests when pageSize change and currentPage isn't 1 ([f71a250](https://github.com/kailong321200875/vue-element-plus-admin/commit/f71a2503bc521c01e7102feecf4ec39a5224a6bb)) + +### [1.4.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.1...v1.4.2) (2022-05-15) + + +### Styling + +* fix dark mode bug ([2f9fd5d](https://github.com/kailong321200875/vue-element-plus-admin/commit/2f9fd5d21550d771ec12ae3540b975e2eebcd25b)) + +### [1.4.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.4.0...v1.4.1) (2022-05-12) + + +### Bug Fixes + +* the warning of VSCode extensions ([a368c21](https://github.com/kailong321200875/vue-element-plus-admin/commit/a368c21fb9c41f98f31f51586a2023076a8a9132)) + +## [1.4.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.3.2...v1.4.0) (2022-05-10) + + +### Features + +* add dark mode ([0758a6a](https://github.com/kailong321200875/vue-element-plus-admin/commit/0758a6a9d83170e53d45d39c3313e52ff5885746)) + +### [1.3.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.3.1...v1.3.2) (2022-05-07) + + +### Bug Fixes + +* fix the problem that the page is stuck in top mode ([8d01f48](https://github.com/kailong321200875/vue-element-plus-admin/commit/8d01f48d5098195b10c03b3cb3a0f485ebc9e018)) + +### [1.3.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.3.0...v1.3.1) (2022-05-06) + + +### Bug Fixes + +* spelling 'useRenderChcekbox' ([ee92f03](https://github.com/kailong321200875/vue-element-plus-admin/commit/ee92f039bea4307ccfb819728d3e2ed04fa00e03)) + +## [1.3.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.13...v1.3.0) (2022-04-26) + + +### Features + +* add useCrudSchemas hook ([00d947e](https://github.com/kailong321200875/vue-element-plus-admin/commit/00d947e2f81105194b0622d33768f999e37ad28a)) + + +### Bug Fixes + +* fix Table slot warning ([0eac05d](https://github.com/kailong321200875/vue-element-plus-admin/commit/0eac05d4f973ff7b15e00973f6e96595a3cd6d43)) + + +### Code Refactoring + +* refactor useAxios ([185f1e6](https://github.com/kailong321200875/vue-element-plus-admin/commit/185f1e6e210ecaac28ebfdee4198b7ca2eaa0933)) + + +### Build System + +* add url ([ab0f59a](https://github.com/kailong321200875/vue-element-plus-admin/commit/ab0f59ac91a077cf060923fa76e6d57e05d0b21b)) +* update plugins ([c475a61](https://github.com/kailong321200875/vue-element-plus-admin/commit/c475a610c19094034306f2dc665e240c7c117f87)) +* update plugins ([dfedbc7](https://github.com/kailong321200875/vue-element-plus-admin/commit/dfedbc74fdb2c819a96b6263849bdaab59b9e337)) + +### [1.2.13](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.12...v1.2.13) (2022-04-18) + + +### Performance Improvements + +* Editor component support v-model ([d77f8e3](https://github.com/kailong321200875/vue-element-plus-admin/commit/d77f8e334d77ee43c9ee0f411733f7397b278bc0)) + +### [1.2.12](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.11...v1.2.12) (2022-04-17) + + +### Bug Fixes + +* fixed spelling 'ElememtPlusSzie' ([5dbbc60](https://github.com/kailong321200875/vue-element-plus-admin/commit/5dbbc608640d93fe68fec6f58fdb30a43e02aada)) + +### [1.2.11](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.10...v1.2.11) (2022-04-14) + + +### Bug Fixes + +* fix the error reported by the Editor component ([7dc6d8a](https://github.com/kailong321200875/vue-element-plus-admin/commit/7dc6d8a9d7289bfaf27f972e9ca1773c0a1ddd7d)) +* fix the error reported by the Editor component ([90ef985](https://github.com/kailong321200875/vue-element-plus-admin/commit/90ef9856a0885fa812339cb7047ecc98b86c7b73)) + + +### Performance Improvements + +* add tagsViewIcon setting ([d395f03](https://github.com/kailong321200875/vue-element-plus-admin/commit/d395f03a57a9265f1d39b3220fc7c9b983efee30)) +* add uniqueopened setting ([b060319](https://github.com/kailong321200875/vue-element-plus-admin/commit/b0603199a5ae0ee923483dad449f49220d36f444)) + + +### Build System + +* update plugins ([2ee4954](https://github.com/kailong321200875/vue-element-plus-admin/commit/2ee49549e7b601af26ef5204f7648d271f3348f2)) + + +### Styling + +* add layout background color ([9b614fe](https://github.com/kailong321200875/vue-element-plus-admin/commit/9b614fe89288538197c50f164586aeed7836b7a8)) + +### [1.2.10](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.9...v1.2.10) (2022-04-12) + + +### Bug Fixes + +* fix bug ([327522f](https://github.com/kailong321200875/vue-element-plus-admin/commit/327522f2b73ae0e11f8ebbc39394b06029ce0b65)) + + +### Styling + +* modify the commitlint package manager ([ba7e722](https://github.com/kailong321200875/vue-element-plus-admin/commit/ba7e7224ab58612548519415f5429c32827a61de)) + +### [1.2.9](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.8...v1.2.9) (2022-04-12) + + +### Docs + +* update changlog ([e37273d](https://github.com/kailong321200875/vue-element-plus-admin/commit/e37273d95d29a3bb752604658d550264aacdc979)) + +### [1.2.8](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.7...v1.2.8) (2022-04-11) + +### Build System + +- update plugins ([00a573a](https://github.com/kailong321200875/vue-element-plus-admin/commit/00a573af3f455395b4ee2ab99a03f3103d466e9c)) + +### Docs + +- update changlog ([62fc183](https://github.com/kailong321200875/vue-element-plus-admin/commit/62fc1839fdff3a4d06a7db4cf3f8ce2cb9aee681)) + +### [1.2.7](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.6...v1.2.7) (2022-04-10) + +### Build System + +- update plugins ([f13a91d](https://github.com/kailong321200875/vue-element-plus-admin/commit/f13a91dd460b1dcdbd17aef723ab3ca2b01c34f0)) + +### Styling + +- .bhs code formatting ([57b2707](https://github.com/kailong321200875/vue-element-plus-admin/commit/57b27071e9a33423c46542a5d0e5d5c2e9a3b718)) + +### [1.2.6](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.5...v1.2.6) (2022-04-08) + +### Build System + +- update plugins ([d645892](https://github.com/kailong321200875/vue-element-plus-admin/commit/d645892cde2f7f43215a2ba1776ee94a322437bf)) + +### [1.2.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.4...v1.2.5) (2022-04-08) + +### Performance Improvements + +- add plop ([fa54a17](https://github.com/kailong321200875/vue-element-plus-admin/commit/fa54a1704ffd93f7b42dbeb1229bc4868d2d3a6a)) + +### Build System + +- update plugins ([18c6bd8](https://github.com/kailong321200875/vue-element-plus-admin/commit/18c6bd868622d954b51ea34e37516361ad4eb540)) + +### Styling + +- fix padding and background color ([f8c9d54](https://github.com/kailong321200875/vue-element-plus-admin/commit/f8c9d54687edafd92f5b61bf5288bb1188c73f01)) + +### [1.2.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.3...v1.2.4) (2022-04-06) + +### Bug Fixes + +- add Sticky props comment ([46133b3](https://github.com/kailong321200875/vue-element-plus-admin/commit/46133b3ff39d48d11cbcaa1f20a271118f48eb29)) +- fix bug ([179ca06](https://github.com/kailong321200875/vue-element-plus-admin/commit/179ca064ba8adbb3b063d9798ec1930ccc68e459)) +- fix remove unnecessary variables ([ca01cbf](https://github.com/kailong321200875/vue-element-plus-admin/commit/ca01cbfd98b63a0d76190fe8d43097fdc9df74e6)) +- fix style ([17c8fea](https://github.com/kailong321200875/vue-element-plus-admin/commit/17c8fea93811d9d9b708808484f5c907d761fcf1)) +- remove ContentDetailWrap style ([4ceaa9d](https://github.com/kailong321200875/vue-element-plus-admin/commit/4ceaa9d7816369d0dcaf3e18e4cdbbd6165cef88)) + +### [1.2.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.2...v1.2.3) (2022-03-31) + +### Bug Fixes + +- fix refresh with query ([e94020f](https://github.com/kailong321200875/vue-element-plus-admin/commit/e94020ff541a061599486c0003258f1dbf13aba8)) + +### [1.2.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.1...v1.2.2) (2022-03-30) + +### Bug Fixes + +- fix avatar height bug ([cd4ab76](https://github.com/kailong321200875/vue-element-plus-admin/commit/cd4ab767018941777174d7837045f5259d1cc403)) +- fix parmas to params ([2c7211c](https://github.com/kailong321200875/vue-element-plus-admin/commit/2c7211c89d7299ffc0a36bef8999b3c201dbaf4a)) + +### [1.2.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.2.0...v1.2.1) (2022-03-29) + +### Bug Fixes + +- fix invalid paging ([ad184ee](https://github.com/kailong321200875/vue-element-plus-admin/commit/ad184ee9c0619da36f1ca3f26e67f18f88488523)) + +### Build System + +- update plugins ([0c7276f](https://github.com/kailong321200875/vue-element-plus-admin/commit/0c7276feadaedef83e6a4ad9d457e26d408698a8)) + +## [1.2.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.14...v1.2.0) (2022-03-27) + +### Features + +- add hooks demo ([c43f39e](https://github.com/kailong321200875/vue-element-plus-admin/commit/c43f39efef296266c64cc24690717d07fa0bcb85)) +- add inputPassword demo ([8f8b126](https://github.com/kailong321200875/vue-element-plus-admin/commit/8f8b1260e75df6998ebea617f62d4ab6be81d721)) + +### Docs + +- update LICENSE ([69d3dcc](https://github.com/kailong321200875/vue-element-plus-admin/commit/69d3dcc7edf69e9b4e3042ddb11faa85ec7d39e2)) + +### Styling + +- modify the function name to make it more semantic ([046ae51](https://github.com/kailong321200875/vue-element-plus-admin/commit/046ae512f02df2d3f08134949b9376a061c1eef3)) +- update Footer component presentation ([d4a9ba3](https://github.com/kailong321200875/vue-element-plus-admin/commit/d4a9ba3aa6758b8aac18b30e1a6b9501baff826c)) +- update Icon demo ([8597122](https://github.com/kailong321200875/vue-element-plus-admin/commit/85971227cd3055ea280cf493c7c42b250c1515da)) + +### Tests + +- test push first commit ([a67bb48](https://github.com/kailong321200875/vue-element-plus-admin/commit/a67bb48f269651a2dcd01b9e33d10f20c42d76ee)) + +### Build System + +- update plugins ([9c13d92](https://github.com/kailong321200875/vue-element-plus-admin/commit/9c13d92b36a2a7c95b9edb7821367fc8f0ac6658)) +- update server port ([d2be8c1](https://github.com/kailong321200875/vue-element-plus-admin/commit/d2be8c1a307a3c5daf363bd7f1d21e574598de5c)) + +### [1.1.14](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.13...v1.1.14) (2022-03-22) + +### Bug Fixes + +- fix the bug that the form search function is invalid of the example-dialog page ([9ec30e7](https://github.com/kailong321200875/vue-element-plus-admin/commit/9ec30e719f89865497dbb1321be1df906f59f14e)) + +### [1.1.13](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.12...v1.1.13) (2022-03-17) + +### Build System + +- update plugins ([a2d0313](https://github.com/kailong321200875/vue-element-plus-admin/commit/a2d03137899f9b16fc1d4a09a23576cd74e7950e)) + +### [1.1.12](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.11...v1.1.12) (2022-03-15) + +### Build System + +- update plugins ([fee2252](https://github.com/kailong321200875/vue-element-plus-admin/commit/fee2252930b05b709d0c012e809568c4ed32bd89)) + +### [1.1.11](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.10...v1.1.11) (2022-03-15) + +### Bug Fixes + +- fix the problem of blank bar when toggle the TagsView component ([b1d9771](https://github.com/kailong321200875/vue-element-plus-admin/commit/b1d9771c750709fe45061d13299a85dbbd6ead25)) +- fix the problem that no reaction when copy setting config in http page ([61e0e33](https://github.com/kailong321200875/vue-element-plus-admin/commit/61e0e33c64d6a889fe6ed80d27a10cf8b201d21a)) + +### [1.1.10](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.9...v1.1.10) (2022-03-13) + +### Build System + +- update plugins ([0b525c8](https://github.com/kailong321200875/vue-element-plus-admin/commit/0b525c875075a28288e92243b205b337f85ab550)) + +### [1.1.9](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.8...v1.1.9) (2022-03-07) + +### Build System + +- update plugins ([1456fd4](https://github.com/kailong321200875/vue-element-plus-admin/commit/1456fd49ec9abbfe1f25aeadfe5fed54fec07394)) + +### [1.1.8](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.7...v1.1.8) (2022-03-07) + +### Docs + +- update changelog ([bf09441](https://github.com/kailong321200875/vue-element-plus-admin/commit/bf09441852e59b0d07d4949a33de75958696817f)) + +### [1.1.7](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.6...v1.1.7) (2022-03-06) + +### Styling + +- add labelMessage attribute to Form component ([8c42790](https://github.com/kailong321200875/vue-element-plus-admin/commit/8c427907843ccb2dfd882d27c1e8a894c5616487)) + +### [1.1.6](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.5...v1.1.6) (2022-03-04) + +### Bug Fixes + +- fix the problem that the tree data of Table component cannot be displayed ([bf83d3e](https://github.com/kailong321200875/vue-element-plus-admin/commit/bf83d3efbad9097f245c32cc07d1178580cec4e3)) + +### [1.1.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.4...v1.1.5) (2022-03-02) + +### Bug Fixes + +- fix the problem of tagsview error when loginout ([835d76a](https://github.com/kailong321200875/vue-element-plus-admin/commit/835d76ae87b950106f957976ebc8f6f2e8842ddf)) + +### Build System + +- update plugins ([de34bb1](https://github.com/kailong321200875/vue-element-plus-admin/commit/de34bb193d6c844dbc1cec38db5f61b3f95e19f2)) + +### Styling + +- fix tabMenu z-index bug ([8b3be02](https://github.com/kailong321200875/vue-element-plus-admin/commit/8b3be02368a1bddb7dc78f18adbea7f4ebfe75d6)) +- fix tags-view style bug ([ebff817](https://github.com/kailong321200875/vue-element-plus-admin/commit/ebff81777b9c0b839256b83e321ecbdbff25fc73)) + +### [1.1.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.3...v1.1.4) (2022-03-01) + +### CI + +- update workflow ([0490d18](https://github.com/kailong321200875/vue-element-plus-admin/commit/0490d18145cb0d9c4b066ab01a2c10cb527e38ba)) +- update workflow ([51f7bca](https://github.com/kailong321200875/vue-element-plus-admin/commit/51f7bca6034902b251d081ee383b0d796782d434)) + +### [1.1.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.2...v1.1.3) (2022-03-01) + +### CI + +- update workflow ([91cc5c5](https://github.com/kailong321200875/vue-element-plus-admin/commit/91cc5c595cadc5695d8f54bdc4922d8f04439f24)) + +### [1.1.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.1...v1.1.2) (2022-03-01) + +### Workflows + +- update workflow ([d9708aa](https://github.com/kailong321200875/vue-element-plus-admin/commit/d9708aae5bc0cb795bb0fbf8d17df753cc88ba1d)) + +### [1.1.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.1.0...v1.1.1) (2022-03-01) + +### Workflows + +- update workflow ([085328a](https://github.com/kailong321200875/vue-element-plus-admin/commit/085328aba8c4f356bf7915a6bbdc1ec4f46ceeda)) + +## [1.1.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.0.3...v1.1.0) (2022-03-01) + +### Features + +- 🎸 layout 三种布局重构完成 ([429e428](https://github.com/kailong321200875/vue-element-plus-admin/commit/429e42809cef33a33662e41ad50297217d128b8c)) +- 🎸 layout 布局重构 �[bd24b92](https://github.com/kailong321200875/vue-element-plus-admin/commit/bd24b92acb279343dbaf83b74f1ed2a3f57f1003)) +- 🎸 Table 组件重构完成并给出相应示 �[35879f8](https://github.com/kailong321200875/vue-element-plus-admin/commit/35879f8ecc0ffa76122a336e2eaa93ecfb408c1d)) +- 🎸 v0.0.4 发布 ([a58dc1b](https://github.com/kailong321200875/vue-element-plus-admin/commit/a58dc1b1c2774974782ef6d116b8805975b82b1c)) +- 🎸 初始化项 �[26d4c7c](https://github.com/kailong321200875/vue-element-plus-admin/commit/26d4c7c56894cf2031b3a7cce08d53c37f4a49e3)) +- 🎸 初版完成 ([5bfe4d2](https://github.com/kailong321200875/vue-element-plus-admin/commit/5bfe4d236fd9c2841da100f34c980b4572b67b20)) +- 🎸 新增 Detail 详情组件并给出相应示 �[e77a931](https://github.com/kailong321200875/vue-element-plus-admin/commit/e77a931ef2d2967a9717e27b187d68512c01284f)) +- 🎸 新增二维码组 �[85555ee](https://github.com/kailong321200875/vue-element-plus-admin/commit/85555eef7dc7d72cb701bdd81044ba8fb8e72acc)) +- 🎸 新增全局配置 ([f8405a6](https://github.com/kailong321200875/vue-element-plus-admin/commit/f8405a63c9b1288fbe95bae235b65a08e8fae8d2)) +- 🎸 新增固定 � 级菜单配 �[4c4903e](https://github.com/kailong321200875/vue-element-plus-admin/commit/4c4903e806c8818e320108cc3e5279d728061c29)) +- 🎸 新增权限管理及相关示例文 �[32b6583](https://github.com/kailong321200875/vue-element-plus-admin/commit/32b6583099646b2ee622ac7b35388468769b91b8)) +- 🎸 显示更多组建 � 发中 ([fa9f24d](https://github.com/kailong321200875/vue-element-plus-admin/commit/fa9f24d5da8d2e40d7c3661eabacb8f0474a7bf2)) +- 🎸 权限管理 � 发中 ([38f5211](https://github.com/kailong321200875/vue-element-plus-admin/commit/38f521174ba9eba750fee4516141d7a267f1c4ce)) +- 🎸 权限管理 � 发中 ([6d7ea66](https://github.com/kailong321200875/vue-element-plus-admin/commit/6d7ea6694d8299332018a6689bcd82502a9a552c)) +- 🎸 综合实例重构 �[5142e6e](https://github.com/kailong321200875/vue-element-plus-admin/commit/5142e6e323cb20c89a97398bf41d32c93ce42cad)) +- 🎸 重构 layout ([7ede021](https://github.com/kailong321200875/vue-element-plus-admin/commit/7ede02141e258ab4c88e9b4daad966513d4dbe68)) +- 🎸 重构 layout-classic 布局 ([29d9c98](https://github.com/kailong321200875/vue-element-plus-admin/commit/29d9c988605b822195900268da6bc3f3b0b9c770)) +- 🎸 重构 sider 组件 �[51313d7](https://github.com/kailong321200875/vue-element-plus-admin/commit/51313d7116c7ab2ded7e3a65514ea9ac413edecd)) +- Add analysis api ([83327ea](https://github.com/kailong321200875/vue-element-plus-admin/commit/83327ea763ebb233bb540513276ffa288fbcb4a1)) +- Add analysis demo ([cd06934](https://github.com/kailong321200875/vue-element-plus-admin/commit/cd069340fc5157535fdc82e792c6b6dce7d7a97e)) +- Add count-to demo ([d3fbd3a](https://github.com/kailong321200875/vue-element-plus-admin/commit/d3fbd3a06c3b802fc863b4dc8013122c14bd16f2)) +- Add Descriptions component and add Descriptions demo ([7ad46f8](https://github.com/kailong321200875/vue-element-plus-admin/commit/7ad46f828d626a87699cd4d3a959a5634577d580)) +- Add Dialog component and add dailog demo ([a18ad8f](https://github.com/kailong321200875/vue-element-plus-admin/commit/a18ad8f4a89b78c73e57d8d2543494243f656d05)) +- add doucment link ([53201ae](https://github.com/kailong321200875/vue-element-plus-admin/commit/53201ae97a425714871d99e8847a3672ba0d389f)) +- Add dynamic route ([9d926b2](https://github.com/kailong321200875/vue-element-plus-admin/commit/9d926b2760b75e1d8e71a68dc7ff6c5026223a43)) +- Add Editor component and add editor demo ([3fb3e8d](https://github.com/kailong321200875/vue-element-plus-admin/commit/3fb3e8da39d816bcf4aedb65d40c7052bdb6d8bf)) +- Add Error component ([7411dbc](https://github.com/kailong321200875/vue-element-plus-admin/commit/7411dbc9fd8f122187c86a11523b49c88cc71a8c)) +- Add example-dialog demo ([262f421](https://github.com/kailong321200875/vue-element-plus-admin/commit/262f4211cf53aef30a32f4b88e88fb1b9246ffcb)) +- Add example-page demo ([1492f91](https://github.com/kailong321200875/vue-element-plus-admin/commit/1492f9119aa2960cc05956218e6d151c8b316875)) +- Add form demo ([472f574](https://github.com/kailong321200875/vue-element-plus-admin/commit/472f574f42f8f31c4e6047043ac755ba5fb35b7b)) +- Add form demo ([e6f9580](https://github.com/kailong321200875/vue-element-plus-admin/commit/e6f95803316bb5df2d1060285c1d591a79340721)) +- Add form demo ([543156f](https://github.com/kailong321200875/vue-element-plus-admin/commit/543156f328350bd12e71a41c872e547e41cda7fe)) +- Add form demo ([7795d2a](https://github.com/kailong321200875/vue-element-plus-admin/commit/7795d2a4fe3dbc9849ddc7c1d3e2d9215dc66f56)) +- Add guide demo ([0832194](https://github.com/kailong321200875/vue-element-plus-admin/commit/0832194e6131051416edff7c2eac6b0a016ffd80)) +- Add highlight demo ([eb206b0](https://github.com/kailong321200875/vue-element-plus-admin/commit/eb206b0cc31ac7da3dfd8b3d4b874061c5c91d53)) +- Add Icon demo ([e4b7a76](https://github.com/kailong321200875/vue-element-plus-admin/commit/e4b7a769126d6f0fca424007c294ff229eefcb35)) +- Add ImageViewer component and add ImageViewer demo ([af9fc0a](https://github.com/kailong321200875/vue-element-plus-admin/commit/af9fc0a4aded3ec08746ddeaeabac4c3cfa9463d)) +- Add Infotip component ([e4b7a76](https://github.com/kailong321200875/vue-element-plus-admin/commit/e4b7a769126d6f0fca424007c294ff229eefcb35)) +- Add infotip demo ([dbf3b0f](https://github.com/kailong321200875/vue-element-plus-admin/commit/dbf3b0f5a333ccef524bbac825035b0c6dc78ec9)) +- Add Qrcode component and add qrcode demo ([535a31b](https://github.com/kailong321200875/vue-element-plus-admin/commit/535a31b35eb6a76589f602fd96dcf91f46f349b0)) +- Add Search component and add search demo ([33eca8a](https://github.com/kailong321200875/vue-element-plus-admin/commit/33eca8a97d59f5cc453e1a60ee81b1519527d0f1)) +- Add Table component and add useTable hook ([17e8e7c](https://github.com/kailong321200875/vue-element-plus-admin/commit/17e8e7cda9a009818f11cfa0429ce0f9adc00be5)) +- Add useScrollTo hook ([7d7fd9e](https://github.com/kailong321200875/vue-element-plus-admin/commit/7d7fd9ed646d2b68cec0547ad8e65b0404bb95bb)) +- Add useWatermark hook and add useWatermark demo ([d3fbd3a](https://github.com/kailong321200875/vue-element-plus-admin/commit/d3fbd3a06c3b802fc863b4dc8013122c14bd16f2)) +- Add workplace api ([cb558f8](https://github.com/kailong321200875/vue-element-plus-admin/commit/cb558f8af9dfef2ba2879f021db395ee79e8c8d4)) +- **Animate:** Add animate.css ([1436543](https://github.com/kailong321200875/vue-element-plus-admin/commit/1436543a5c599f651ed7805165ea83b9ebcddef5)) +- **Breadcrumbe:** Add Breadcrumb component ([4612e55](https://github.com/kailong321200875/vue-element-plus-admin/commit/4612e5544bcd626d686972e5cb874d0aa4af08b3)) +- **component:** Add CountTo component and Echart component ([e20fa76](https://github.com/kailong321200875/vue-element-plus-admin/commit/e20fa76cad0894a69fd04c81c2108faabf392684)) +- **component:** Add Footer component ([dad7330](https://github.com/kailong321200875/vue-element-plus-admin/commit/dad733063413c79eca61c6cb5ff671b35933a85f)) +- **component:** Add Footer component ([f81e996](https://github.com/kailong321200875/vue-element-plus-admin/commit/f81e996a426538aeaa2aa37a540395dcf360a09c)) +- **Component:** Add Highlight component ([c53fa56](https://github.com/kailong321200875/vue-element-plus-admin/commit/c53fa562e540447df082e35c7f26e56f2426e430)) +- **component:** Add namespace of class ([d847ccb](https://github.com/kailong321200875/vue-element-plus-admin/commit/d847ccb098edc72fe55c1f8459bf149453a3b73d)) +- **Component:** Setting component add copy button ([e496096](https://github.com/kailong321200875/vue-element-plus-admin/commit/e496096539e6a56b0761a625c9d59210facc5432)) +- **ContextMenu:** Add ContextMenu component ([349ac9d](https://github.com/kailong321200875/vue-element-plus-admin/commit/349ac9d3989d77e5246cecf0006dd8d83c489990)) +- Detail 组件重构完成 ([7f5ef99](https://github.com/kailong321200875/vue-element-plus-admin/commit/7f5ef99ccc32b03f7be21f70c333bb8e679c7d93)) +- Highlight 组件重构 ([34221f3](https://github.com/kailong321200875/vue-element-plus-admin/commit/34221f387f5e15a08cdc21edd76ce8d8c5c20fbc)) +- **hooks:** Add useIntro hook ([0832194](https://github.com/kailong321200875/vue-element-plus-admin/commit/0832194e6131051416edff7c2eac6b0a016ffd80)) +- **hooks:** Add useTimeAgo hook ([c53fa56](https://github.com/kailong321200875/vue-element-plus-admin/commit/c53fa562e540447df082e35c7f26e56f2426e430)) +- **I18n:** Add Ii8n ([3810b8c](https://github.com/kailong321200875/vue-element-plus-admin/commit/3810b8c3b26f86c27aa7db479dfb7b0d283d970f)) +- **Layout:** Add classic layout ([839b601](https://github.com/kailong321200875/vue-element-plus-admin/commit/839b6015b8e31bf70e6f0bf0608fa729b028729b)) +- **Layout:** Add cutMenu layout ([ff4dd3a](https://github.com/kailong321200875/vue-element-plus-admin/commit/ff4dd3afbf5c0c7a439c71b0c494b81e0f2c70d4)) +- **Layout:** Add topLeft layout ([71b1c5e](https://github.com/kailong321200875/vue-element-plus-admin/commit/71b1c5e10cade8d1c018d0c5f63c98ba9357bab8)) +- **LocaleDropdown:** Add LocaleDropdown Component ([3810b8c](https://github.com/kailong321200875/vue-element-plus-admin/commit/3810b8c3b26f86c27aa7db479dfb7b0d283d970f)) +- **Logo:** Add Logo component ([958edef](https://github.com/kailong321200875/vue-element-plus-admin/commit/958edefe7bc2bf3ae77520a5d885a9d47e8a37b9)) +- **mock:** Add mock ([3fc7d4d](https://github.com/kailong321200875/vue-element-plus-admin/commit/3fc7d4d39a72056fcf419fe19a9d41d90f945bad)) +- **router:** Add dynamic routing ([b218ccc](https://github.com/kailong321200875/vue-element-plus-admin/commit/b218ccc9cce2ce1363c4a21d22b4d69c43c7b2dc)) +- Search component add expand attribute and expandField attribute ([9b4b317](https://github.com/kailong321200875/vue-element-plus-admin/commit/9b4b31781765d31dec50acc40e2eed91401502d4)) +- **store:** Add localeStore ([3810b8c](https://github.com/kailong321200875/vue-element-plus-admin/commit/3810b8c3b26f86c27aa7db479dfb7b0d283d970f)) +- **store:** Add tagsView store ([349ac9d](https://github.com/kailong321200875/vue-element-plus-admin/commit/349ac9d3989d77e5246cecf0006dd8d83c489990)) +- Table 组件重构 ([07adefb](https://github.com/kailong321200875/vue-element-plus-admin/commit/07adefb89b7555280e6217e09cf81ba7aa5b93c2)) +- **TagsView:** Add TagsView component ([349ac9d](https://github.com/kailong321200875/vue-element-plus-admin/commit/349ac9d3989d77e5246cecf0006dd8d83c489990)) +- **useForm:** Add useForm ([357fc44](https://github.com/kailong321200875/vue-element-plus-admin/commit/357fc44e519c5829567c17f611fcaadee3f9f933)) +- **useNProgress:** Add useNProgress ([c5ab359](https://github.com/kailong321200875/vue-element-plus-admin/commit/c5ab3599c8ea001ff7831b72fefc9e274163fbbb)) +- **useTitle:** Add useTitle ([c5ab359](https://github.com/kailong321200875/vue-element-plus-admin/commit/c5ab3599c8ea001ff7831b72fefc9e274163fbbb)) +- **utils:** Add color utils ([71dfba2](https://github.com/kailong321200875/vue-element-plus-admin/commit/71dfba21c5bc0276689b5aecf0d75e53efdda09f)) +- **VForm:** Add VForm component ([448ac52](https://github.com/kailong321200875/vue-element-plus-admin/commit/448ac5293e48a03840df2bb0b399a8f02aae666e)) +- **VInputPassword:** Add VInputPassword Component ([a1bf7e9](https://github.com/kailong321200875/vue-element-plus-admin/commit/a1bf7e9b552f75d3b87c64904ac9e7c99fc936a5)) +- **Workplace:** Add wrokplace demo ([c53fa56](https://github.com/kailong321200875/vue-element-plus-admin/commit/c53fa562e540447df082e35c7f26e56f2426e430)) +- 综合实例、权限管理重 �[a4bd206](https://github.com/kailong321200875/vue-element-plus-admin/commit/a4bd2068a5d40d146b5b45cb3727ced990147b68)) +- 部分组件重构完成 ([3d96229](https://github.com/kailong321200875/vue-element-plus-admin/commit/3d9622978dc234ef12dbce63e18caf3440563aa0)) + +### Bug Fixes + +- 🐛 删除 Editor 双向绑定,改 �props 传参 ([c395e27](https://github.com/kailong321200875/vue-element-plus-admin/commit/c395e27f67af9f60b151a5484ab5a3c90c4c1d1e)) +- fix Form component setProps not work bug ([48ffc52](https://github.com/kailong321200875/vue-element-plus-admin/commit/48ffc52ca8fa26d8e6a5fa4b8b3001701a1f0732)) +- fix useScrollTo not work bug ([53201ae](https://github.com/kailong321200875/vue-element-plus-admin/commit/53201ae97a425714871d99e8847a3672ba0d389f)) +- 修复 tagsView 无动画效 �[0e3eb4b](https://github.com/kailong321200875/vue-element-plus-admin/commit/0e3eb4ba8b1503e1d221dfda59a3a0001dbdcb56)) + +### Performance Improvements + +- update useForm hook ([8a958cd](https://github.com/kailong321200875/vue-element-plus-admin/commit/8a958cd71d9afbd32b243aac0814bfa3281477cd)) + +### Code Refactoring + +- 💡 综合实例查看详情重构 ([9c26edd](https://github.com/kailong321200875/vue-element-plus-admin/commit/9c26edd5d599b5fb5a832fb547e3d95b6bfa9a98)) + +### Build System + +- Add conventional-changelog-cli plugin ([384485f](https://github.com/kailong321200875/vue-element-plus-admin/commit/384485f6994c6ac33abee506508ab9d35fe658a9)) +- Add conventional-github-releaser plugin ([3cd5c71](https://github.com/kailong321200875/vue-element-plus-admin/commit/3cd5c71899dde3ac3910aef0180d8b39fad51f1b)) +- Add standard-version plugin ([110ce25](https://github.com/kailong321200875/vue-element-plus-admin/commit/110ce257841648e29b247a0338624a188694b6e9)) +- Add vite-plugin-html plugin ([d5b6e2a](https://github.com/kailong321200875/vue-element-plus-admin/commit/d5b6e2a7770eb59aa32839f69da5be37397e3538)) +- delete useless plugin ([c756761](https://github.com/kailong321200875/vue-element-plus-admin/commit/c756761dfc3200156acb228474d3539197ef413b)) +- **pinia:** Add pinia ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- **types:** Add vue-types ([2c41826](https://github.com/kailong321200875/vue-element-plus-admin/commit/2c41826c572268b74a663a6966c548628ac7e280)) +- **unplugin-auto-import:** Delete unplugin-auto-import ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- **unplugin-vue-components:** Delete unplugin-vue-components ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- update plugin ([8d08bc6](https://github.com/kailong321200875/vue-element-plus-admin/commit/8d08bc6fc92258674abdd12834eaa4530ec276dd)) +- update plugins ([3c58042](https://github.com/kailong321200875/vue-element-plus-admin/commit/3c580420a20121845f02c0dd3caca5a74f06a89d)) +- **vite-plugin-style-import:** Add vite-plugin-style-import ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- **vite-plugin-vue-setup-extend:** Delete vite-plugin-vue-setup-extend ([2040500](https://github.com/kailong321200875/vue-element-plus-admin/commit/2040500af14d277a79f01eba5eca2a440203cecf)) +- 修改 vite 配置 ([9991fb4](https://github.com/kailong321200875/vue-element-plus-admin/commit/9991fb4e5c46b9e4016beaade7232e28dc272797)) +- 设置多语 �([45e879e](https://github.com/kailong321200875/vue-element-plus-admin/commit/45e879edeef677b6aa1d2cfe4dd8dc5b76c83c59)) +- 配置代码格式 �[ffdb556](https://github.com/kailong321200875/vue-element-plus-admin/commit/ffdb556a096db247306eae8eecc1b85718314cdd)) +- 集成基础配置 ([ced99de](https://github.com/kailong321200875/vue-element-plus-admin/commit/ced99de9b113a01d9d0b190f6d2c6adc983a3102)) +- 集成基础配置 ([5dbcf23](https://github.com/kailong321200875/vue-element-plus-admin/commit/5dbcf2397ccdec80c695c113f49e8aa9bb6d012c)) + +### Styling + +- 💄 优化 layout 样式 ([37ec378](https://github.com/kailong321200875/vue-element-plus-admin/commit/37ec378f0b2bf83d73ddf0e472aada6aab248f09)) +- 💄 微调样式 ([612b486](https://github.com/kailong321200875/vue-element-plus-admin/commit/612b48673c3389779ccfdd161e2ca80b21d265b2)) +- Add elNamespace ([d847ccb](https://github.com/kailong321200875/vue-element-plus-admin/commit/d847ccb098edc72fe55c1f8459bf149453a3b73d)) +- **appStore:** code style ([641ed68](https://github.com/kailong321200875/vue-element-plus-admin/commit/641ed684fefeb52e2f91e8baab7b610fc74c8d88)) +- **Breadcrumb:** fix Breadcrumb style bug ([8755c86](https://github.com/kailong321200875/vue-element-plus-admin/commit/8755c862b837d90a25b27c01fabe64abf81fc4a2)) +- **breadcrumb:** update disabled text color ([1522e92](https://github.com/kailong321200875/vue-element-plus-admin/commit/1522e925bae37cb9df4de2252d81f717788f4537)) +- change function to arrow function ([4612e55](https://github.com/kailong321200875/vue-element-plus-admin/commit/4612e5544bcd626d686972e5cb874d0aa4af08b3)) +- delete console.log ([49a6bfe](https://github.com/kailong321200875/vue-element-plus-admin/commit/49a6bfe9d81a40e2f5f15b68d7289e1787e89b54)) +- **Icon:** delete default color ([95a2bd8](https://github.com/kailong321200875/vue-element-plus-admin/commit/95a2bd884dd9846a56cda7c4c3ee4a41ce631b7c)) +- level demo style beautification ([dbf3b0f](https://github.com/kailong321200875/vue-element-plus-admin/commit/dbf3b0f5a333ccef524bbac825035b0c6dc78ec9)) +- lint code style ([b292419](https://github.com/kailong321200875/vue-element-plus-admin/commit/b2924190b8996e8208f951e3fadbcb09baddb8df)) +- **Login:** update login styles ([eb68f1d](https://github.com/kailong321200875/vue-element-plus-admin/commit/eb68f1d919e13c07b7d200e9aec53804b2a6dc7b)) +- modify menu z-index attribute ([0d7a778](https://github.com/kailong321200875/vue-element-plus-admin/commit/0d7a7781ce0b5e39f01355d3acdb3f364cabf76d)) +- **TagView:** Vertical center tag ([41281c4](https://github.com/kailong321200875/vue-element-plus-admin/commit/41281c4d541a2744e5df5dff2764cc85465b6a4c)) + +### Types + +- add ImportMetaEnv ([38e0257](https://github.com/kailong321200875/vue-element-plus-admin/commit/38e0257487e4138a74ad1bb4ee4ba004abcfaa12)) +- Adding BfFrom Component types ([8e036f5](https://github.com/kailong321200875/vue-element-plus-admin/commit/8e036f54b56ce8521eb8ec4b7ca21aa9c24f43f2)) +- **BfForm:** Adding BfForm types ([bc9195b](https://github.com/kailong321200875/vue-element-plus-admin/commit/bc9195b21eeb79629a82a04d90e2ac5aa6592928)) +- **BfForm:** Adding BfForm types ([184b468](https://github.com/kailong321200875/vue-element-plus-admin/commit/184b468cd41dcd1cdae11477b9ee2d6e17de1481)) +- **BfForm:** Adding BfForm types ([58cb24d](https://github.com/kailong321200875/vue-element-plus-admin/commit/58cb24d9f8a50be80b5ea793387d582a77a59137)) +- delete useless types ([3fc79c0](https://github.com/kailong321200875/vue-element-plus-admin/commit/3fc79c0ae7acd0929f47e33f96c8d45a90d8f762)) +- **VForm:** Adding VForm types ([7528fe6](https://github.com/kailong321200875/vue-element-plus-admin/commit/7528fe6da60368213d28d9f1b6310d02d3d53282)) + +### Docs + +- ✏️ 修改 readme ([8edb2a3](https://github.com/kailong321200875/vue-element-plus-admin/commit/8edb2a3493dca975036859b5d2c52afaa91f5dbb)) +- ✏️ 更新 readme ([62eeb55](https://github.com/kailong321200875/vue-element-plus-admin/commit/62eeb55330dd4af2a46801c7a19f38a3ef312bbf)) +- Add README.md ([21dcf88](https://github.com/kailong321200875/vue-element-plus-admin/commit/21dcf88ba31957bbdb50c6207d010650daab70fc)) +- Error modifying readme name ([25d5c84](https://github.com/kailong321200875/vue-element-plus-admin/commit/25d5c84e92e68aa72362a14f55aacd946fa5b1b2)) +- update description ([be6ff98](https://github.com/kailong321200875/vue-element-plus-admin/commit/be6ff9899b25cc00519210950d27ee56ac5112e6)) +- update description ([c15aa87](https://github.com/kailong321200875/vue-element-plus-admin/commit/c15aa8755c9c937512c7380a6d03c4d877ef4d87)) +- update README ([27979dc](https://github.com/kailong321200875/vue-element-plus-admin/commit/27979dc6def7d9d8cea62a08d49a6c828be2258b)) +- update README.md ([53201ae](https://github.com/kailong321200875/vue-element-plus-admin/commit/53201ae97a425714871d99e8847a3672ba0d389f)) +- update README.md ([c11823a](https://github.com/kailong321200875/vue-element-plus-admin/commit/c11823abd8a033e14b4c20d17ac941195d39bcfe)) +- 修改 README ([b79a567](https://github.com/kailong321200875/vue-element-plus-admin/commit/b79a56753df55976e749c4494266df052d315416)) diff --git a/kinit-admin/LICENSE b/kinit-admin/LICENSE new file mode 100644 index 0000000..9861118 --- /dev/null +++ b/kinit-admin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-present Archer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/kinit-admin/README.md b/kinit-admin/README.md new file mode 100644 index 0000000..731173f --- /dev/null +++ b/kinit-admin/README.md @@ -0,0 +1,240 @@ +


+ +[![license](https://img.shields.io/github/license/kailong321200875/vue-element-plus-admin.svg)](LICENSE) + +

vue-element-plus-admin

+
+ +**English** | [中文](./README.zh-CN.md) + +## Introduction + +vue-element-plus-admin is a free and open source middle and background template based on `element-plus`. Developed using the latest mainstream technologies such as `vue3`, `vite3` and `typescript`, the out of the box middle and background front-end solution can be used as the starting template of the project and learning reference. And always pay attention to the latest technological trends and update them as soon as possible. + +vue-element-plus-admin is positioned as a background integration scheme, which is not suitable for secondary development as a basic template. Because it integrates many functions that you may not use, it will cause a lot of code redundancy. If your project doesn't pay attention to this problem, you can also directly carry out secondary development based on it. + +If you need a basic template, please switch to the `tempalte` branch. `Tempalte` simply integrates some common layout functions such as layout and dynamic menu, which is more suitable for developers to carry out secondary development. + +## Feature + +- **State of The Art Development**:Use front-end front-end technology development such as Vue3/vite3 +- **TypeScript**: Application-level JavaScript language +- **Theming**: Configurable themes +- **International**:Built-in complete internationalization program +- **Mock Server** Built-in mock data scheme +- **Authority** Built-in complete dynamic routing permission generation scheme. +- **Component** Multiple commonly used components are encapsulated twice +- **Examples** Built-in rich examples + +## Preview + +- [vue-element-plus-admin](https://element-plus-admin.cn/) - Full version of the github site +- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - Full version of the gitee site + +account: **admin/admin test/test** + +`admin` account is used to simulate the control permission of the server, and render whatever the server returns + +`test` account is used to simulate the front-end control authority. The server only returns the menu key to be displayed, and the front-end performs matching rendering + +## Documentation + +[Document Github](https://element-plus-admin-doc.cn/) + +[Document Gitee](https://kailong110120130.gitee.io/vue-element-plus-admin-doc) + +## Preparation + +- [node](http://nodejs.org/) and [git](https://git-scm.com/) - Project development environment +- [Vite](https://vitejs.dev/) - Familiar with vite features +- [Vue3](https://v3.vuejs.org/) - Familiar with Vue basic syntax +- [TypeScript](https://www.typescriptlang.org/) - Familiar with the basic syntax of `TypeScript` +- [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax +- [Vue-Router-Next](https://next.router.vuejs.org/) - Familiar with the basic use of vue-router +- [Element-Plus](https://element-plus.org/) - Familiar with the basic use of element-plus +- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax + +## Install and use + +- Get the project code + +```bash +git clone https://github.com/kailong321200875/vue-element-plus-admin.git +``` + +- Installation dependencies + +```bash +cd vue-element-plus-admin + +pnpm install + +``` + +- run + +```bash +pnpm run dev +``` + +- build + +```bash +pnpm run build:pro +``` + +## Change Log + +[CHANGELOG](./CHANGELOG.md) + +## How to contribute + +You can [Raise an issue](https://github.com/kailong321200875/vue-element-plus-admin/issues/new) Or submit a Pull Request. + +**Pull Request:** + +1. Fork code +2. Create your own branch: `git checkout -b feat/xxxx` +3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` +4. Push your branch: `git push origin feat/xxxx` +5. submit `pull request` + +## Git Contribution submission specification + +- `feat` New features +- `fix` Fix bugs +- `docs` document +- `style` Format and style (changes that do not affect code operation) +- `refactor` Refactor +- `perf` Optimize related, such as improving performance and experience +- `test` Add test +- `build` Compilation related modifications, changes to project construction or dependencies +- `ci` Continuous integration modification +- `chore` Changes in the construction process or auxiliary tools +- `revert` Rollback to previous version +- `workflow` Workflow improvement +- `mod` Uncertain modification classification +- `wip` Under development +- `types` type + +## Browser support + +The `Chrome 80+` browser is recommended for local development + +Support modern browsers, not IE + +| [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| :-: | :-: | :-: | :-: | :-: | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | + +## License + +[MIT](./LICENSE) + +## Collaborators + + + + + + + + +
+ + niyg +
+ 福州-大雨 +
+
+ + git-Where +
+ 葉家男孩 +
+
+ + z6w6j6 +
+ Z6w6j6 +
+
+ + kailong321200875 +
+ Archer +
+
+ + +## Contributors + + + + + + + + + + + + + + +
+ + kailong321200875 +
+ Archer +
+
+ + kailong502431556 +
+ Kailong502431556 +
+
+ + xingyu4j +
+ Xingyu4j +
+
+ + snowords +
+ Snoword +
+
+ + huanghong1125 +
+ Huanghong +
+
+ + amifed +
+ Yangyu +
+
+ + WuYihui +
+ WuYihui +
+
+ + xiterjia +
+ Xiterjia +
+
+ + z6w6j6 +
+ Z6w6j6 +
+
+ diff --git a/kinit-admin/README.zh-CN.md b/kinit-admin/README.zh-CN.md new file mode 100644 index 0000000..b38dcfc --- /dev/null +++ b/kinit-admin/README.zh-CN.md @@ -0,0 +1,240 @@ +


+ +[![license](https://img.shields.io/github/license/kailong321200875/vue-element-plus-admin.svg)](LICENSE) + +

vue-element-plus-admin

+
+ +[English](./README.md) | **中文** + +## 介绍 + +vue-element-plus-admin 是一个基于 `element-plus` 免费开源的中后台模版。使用了最新的`vue3`,`vite3`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。 + +vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。 + +如需要基础模版,请切换到 `tempalte` 分支,`tempalte` 只简单集成了一些如:布局、动态菜单等常用布局功能,更适合开发者进行二次开发。 + +## 特性 + +- **最新技术栈**:使用 Vue3/vite3 等前端前沿技术开发 +- **TypeScript**: 应用程序级 JavaScript 的语言 +- **主题**: 可配置的主题 +- **国际化**:内置完善的国际化方案 +- **自定义数据** 内置 Mock 数据方案 +- **权限** 内置完善的动态路由权限生成方案 +- **组件** 二次封装了多个常用的组件 +- **示例** 内置丰富的示例 + +## 预览 + +- [vue-element-plus-admin](https://element-plus-admin.cn/) - 完整版 github 站点 +- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - 完整版 gitee 站点 + +帐号:**admin/admin test/test** + +`admin` 帐号用于模拟服务端控制权限,服务端返回什么就渲染什么 + +`test` 帐号用于模拟前端控制权限,服务端只返回需要显示的菜单 key,前端进行匹配渲染 + +## 文档 + +[文档地址 Github](https://element-plus-admin-doc.cn/) + +[文档地址 Gitee](https://kailong110120130.gitee.io/vue-element-plus-admin-doc) + +## 前序准备 + +- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) - 项目开发环境 +- [Vite](https://vitejs.dev/) - 熟悉 vite 特性 +- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法 +- [TypeScript](https://www.typescriptlang.org/) - 熟悉 `TypeScript` 基本语法 +- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法 +- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用 +- [Element-Plus](https://element-plus.org/) - element-plus 基本使用 +- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法 + +## 安装和使用 + +- 获取代码 + +```bash +git clone https://github.com/kailong321200875/vue-element-plus-admin.git +``` + +- 安装依赖 + +```bash +cd vue-element-plus-admin + +pnpm install + +``` + +- 运行 + +```bash +pnpm run dev +``` + +- 打包 + +```bash +pnpm run build:pro +``` + +## 更新日志 + +[更新日志](./CHANGELOG.md) + +## 如何贡献 + +你可以[提一个 issue](https://github.com/kailong321200875/vue-element-plus-admin/issues/new) 或者提交一个 Pull Request。 + +**Pull Request:** + +1. Fork 代码 +2. 创建自己的分支: `git checkout -b feat/xxxx` +3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'` +4. 推送您的分支: `git push origin feat/xxxx` +5. 提交 `pull request` + +## Git 贡献提交规范 + +- `feat` 新功能 +- `fix` 修补 bug +- `docs` 文档 +- `style` 格式、样式(不影响代码运行的变动) +- `refactor` 重构(即不是新增功能,也不是修改 BUG 的代码) +- `perf` 优化相关,比如提升性能、体验 +- `test` 添加测试 +- `build` 编译相关的修改,对项目构建或者依赖的改动 +- `ci` 持续集成修改 +- `chore` 构建过程或辅助工具的变动 +- `revert` 回滚到上一个版本 +- `workflow` 工作流改进 +- `mod` 不确定分类的修改 +- `wip` 开发中 +- `types` 类型 + +## 浏览器支持 + +本地开发推荐使用 `Chrome 80+` 浏览器 + +支持现代浏览器, 不支持 IE + +| [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| :-: | :-: | :-: | :-: | :-: | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | + +## 许可证 + +[MIT](./LICENSE) + +## 合作者 + + + + + + + + +
+ + niyg +
+ 福州-大雨 +
+
+ + git-Where +
+ 葉家男孩 +
+
+ + z6w6j6 +
+ Z6w6j6 +
+
+ + kailong321200875 +
+ Archer +
+
+ + +## 贡献者 + + + + + + + + + + + + + + +
+ + kailong321200875 +
+ Archer +
+
+ + kailong502431556 +
+ Kailong502431556 +
+
+ + xingyu4j +
+ Xingyu4j +
+
+ + snowords +
+ Snoword +
+
+ + huanghong1125 +
+ Huanghong +
+
+ + amifed +
+ Yangyu +
+
+ + WuYihui +
+ WuYihui +
+
+ + xiterjia +
+ Xiterjia +
+
+ + z6w6j6 +
+ Z6w6j6 +
+
+ diff --git a/kinit-admin/commitlint.config.js b/kinit-admin/commitlint.config.js new file mode 100644 index 0000000..78d145d --- /dev/null +++ b/kinit-admin/commitlint.config.js @@ -0,0 +1,28 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', // 新功能(feature) + 'fix', // 修补bug + 'docs', // 文档(documentation) + 'style', // 格式、样式(不影响代码运行的变动) + 'refactor', // 重构(即不是新增功能,也不是修改BUG的代码) + 'perf', // 优化相关,比如提升性能、体验 + 'test', // 添加测试 + 'ci', // 持续集成修改 + 'chore', // 构建过程或辅助工具的变动 + 'revert', // 回滚到上一个版本 + 'workflow', // 工作流改进 + 'mod', // 不确定分类的修改 + 'wip', // 开发中 + 'types', // 类型修改 + 'release' // 版本发布 + ] + ], + 'subject-full-stop': [0, 'never'], + 'subject-case': [0, 'never'] + } +} diff --git a/kinit-admin/index.html b/kinit-admin/index.html new file mode 100644 index 0000000..507d616 --- /dev/null +++ b/kinit-admin/index.html @@ -0,0 +1,142 @@ + + + + + + + <%= title %> + + +
+ +
+
+
+ +
<%= title %>
+
+
+
+
+
+
+
+
+ + + diff --git a/kinit-admin/mock/_createProductionServer.ts b/kinit-admin/mock/_createProductionServer.ts new file mode 100644 index 0000000..caa26bf --- /dev/null +++ b/kinit-admin/mock/_createProductionServer.ts @@ -0,0 +1,18 @@ +import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' + +const modules = import.meta.glob('./**/*.ts', { + import: 'default', + eager: true +}) + +const mockModules: any[] = [] +Object.keys(modules).forEach(async (key) => { + if (key.includes('_')) { + return + } + mockModules.push(...(modules[key] as any)) +}) + +export function setupProdMockServer() { + createProdMockServer(mockModules) +} diff --git a/kinit-admin/mock/analysis/index.ts b/kinit-admin/mock/analysis/index.ts new file mode 100644 index 0000000..d624219 --- /dev/null +++ b/kinit-admin/mock/analysis/index.ts @@ -0,0 +1,89 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +export default [ + // 分析页统计接口 + { + url: '/analysis/total', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: { + users: 102400, + messages: 81212, + moneys: 9280, + shoppings: 13600 + } + } + } + }, + // 用户来源 + { + url: '/analysis/userAccessSource', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { value: 1000, name: 'analysis.directAccess' }, + { value: 310, name: 'analysis.mailMarketing' }, + { value: 234, name: 'analysis.allianceAdvertising' }, + { value: 135, name: 'analysis.videoAdvertising' }, + { value: 1548, name: 'analysis.searchEngines' } + ] + } + } + }, + // 每周用户活跃量 + { + url: '/analysis/weeklyUserActivity', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { value: 13253, name: 'analysis.monday' }, + { value: 34235, name: 'analysis.tuesday' }, + { value: 26321, name: 'analysis.wednesday' }, + { value: 12340, name: 'analysis.thursday' }, + { value: 24643, name: 'analysis.friday' }, + { value: 1322, name: 'analysis.saturday' }, + { value: 1324, name: 'analysis.sunday' } + ] + } + } + }, + // 每月销售额 + { + url: '/analysis/monthlySales', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { estimate: 100, actual: 120, name: 'analysis.january' }, + { estimate: 120, actual: 82, name: 'analysis.february' }, + { estimate: 161, actual: 91, name: 'analysis.march' }, + { estimate: 134, actual: 154, name: 'analysis.april' }, + { estimate: 105, actual: 162, name: 'analysis.may' }, + { estimate: 160, actual: 140, name: 'analysis.june' }, + { estimate: 165, actual: 145, name: 'analysis.july' }, + { estimate: 114, actual: 250, name: 'analysis.august' }, + { estimate: 163, actual: 134, name: 'analysis.september' }, + { estimate: 185, actual: 56, name: 'analysis.october' }, + { estimate: 118, actual: 99, name: 'analysis.november' }, + { estimate: 123, actual: 123, name: 'analysis.december' } + ] + } + } + } +] as MockMethod[] diff --git a/kinit-admin/mock/dict/index.ts b/kinit-admin/mock/dict/index.ts new file mode 100644 index 0000000..b27f3b3 --- /dev/null +++ b/kinit-admin/mock/dict/index.ts @@ -0,0 +1,63 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +const dictObj: Recordable = { + importance: [ + { + value: 0, + label: 'tableDemo.commonly' + }, + { + value: 1, + label: 'tableDemo.good' + }, + { + value: 2, + label: 'tableDemo.important' + } + ] +} + +export default [ + // 字典接口 + { + url: '/dict/list', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: dictObj + } + } + }, + // 获取某个字典 + { + url: '/dict/one', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + label: 'test1', + value: 0 + }, + { + label: 'test2', + value: 1 + }, + { + label: 'test3', + value: 2 + } + ] + } + } + } +] as MockMethod[] diff --git a/kinit-admin/mock/role/index.ts b/kinit-admin/mock/role/index.ts new file mode 100644 index 0000000..aa43000 --- /dev/null +++ b/kinit-admin/mock/role/index.ts @@ -0,0 +1,534 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +const adminList = [ + { + path: '/dashboard', + component: '#', + redirect: '/dashboard/analysis', + name: 'Dashboard', + meta: { + title: 'router.dashboard', + icon: 'ant-design:dashboard-filled', + alwaysShow: true + }, + children: [ + { + path: 'analysis', + component: 'views/Dashboard/Analysis', + name: 'Analysis', + meta: { + title: 'router.analysis', + noCache: true + } + }, + { + path: 'workplace', + component: 'views/Dashboard/Workplace', + name: 'Workplace', + meta: { + title: 'router.workplace', + noCache: true + } + } + ] + }, + { + path: '/external-link', + component: '#', + meta: {}, + name: 'ExternalLink', + children: [ + { + path: 'https://element-plus-admin-doc.cn/', + name: 'DocumentLink', + meta: { + title: 'router.document', + icon: 'clarity:document-solid' + } + } + ] + }, + { + path: '/guide', + component: '#', + name: 'Guide', + meta: {}, + children: [ + { + path: 'index', + component: 'views/Guide/Guide', + name: 'GuideDemo', + meta: { + title: 'router.guide', + icon: 'cib:telegram-plane' + } + } + ] + }, + { + path: '/components', + component: '#', + redirect: '/components/form/default-form', + name: 'ComponentsDemo', + meta: { + title: 'router.component', + icon: 'bx:bxs-component', + alwaysShow: true + }, + children: [ + { + path: 'form', + component: '##', + name: 'Form', + meta: { + title: 'router.form', + alwaysShow: true + }, + children: [ + { + path: 'default-form', + component: 'views/Components/Form/DefaultForm', + name: 'DefaultForm', + meta: { + title: 'router.defaultForm' + } + }, + { + path: 'use-form', + component: 'views/Components/Form/UseFormDemo', + name: 'UseForm', + meta: { + title: 'UseForm' + } + }, + { + path: 'ref-form', + component: 'views/Components/Form/RefForm', + name: 'RefForm', + meta: { + title: 'RefForm' + } + } + ] + }, + { + path: 'table', + component: '##', + redirect: '/components/table/default-table', + name: 'TableDemo', + meta: { + title: 'router.table', + alwaysShow: true + }, + children: [ + { + path: 'default-table', + component: 'views/Components/Table/DefaultTable', + name: 'DefaultTable', + meta: { + title: 'router.defaultTable' + } + }, + { + path: 'use-table', + component: 'views/Components/Table/UseTableDemo', + name: 'UseTable', + meta: { + title: 'UseTable' + } + }, + { + path: 'ref-table', + component: 'views/Components/Table/RefTable', + name: 'RefTable', + meta: { + title: 'RefTable' + } + } + ] + }, + { + path: 'editor-demo', + component: '##', + redirect: '/components/editor-demo/editor', + name: 'EditorDemo', + meta: { + title: 'router.editor', + alwaysShow: true + }, + children: [ + { + path: 'editor', + component: 'views/Components/Editor/Editor', + name: 'Editor', + meta: { + title: 'router.richText' + } + } + ] + }, + { + path: 'search', + component: 'views/Components/Search', + name: 'Search', + meta: { + title: 'router.search' + } + }, + { + path: 'descriptions', + component: 'views/Components/Descriptions', + name: 'Descriptions', + meta: { + title: 'router.descriptions' + } + }, + { + path: 'image-viewer', + component: 'views/Components/ImageViewer', + name: 'ImageViewer', + meta: { + title: 'router.imageViewer' + } + }, + { + path: 'dialog', + component: 'views/Components/Dialog', + name: 'Dialog', + meta: { + title: 'router.dialog' + } + }, + { + path: 'icon', + component: 'views/Components/Icon', + name: 'Icon', + meta: { + title: 'router.icon' + } + }, + { + path: 'echart', + component: 'views/Components/Echart', + name: 'Echart', + meta: { + title: 'router.echart' + } + }, + { + path: 'count-to', + component: 'views/Components/CountTo', + name: 'CountTo', + meta: { + title: 'router.countTo' + } + }, + { + path: 'qrcode', + component: 'views/Components/Qrcode', + name: 'Qrcode', + meta: { + title: 'router.qrcode' + } + }, + { + path: 'highlight', + component: 'views/Components/Highlight', + name: 'Highlight', + meta: { + title: 'router.highlight' + } + }, + { + path: 'infotip', + component: 'views/Components/Infotip', + name: 'Infotip', + meta: { + title: 'router.infotip' + } + }, + { + path: 'input-password', + component: 'views/Components/InputPassword', + name: 'InputPassword', + meta: { + title: 'router.inputPassword' + } + }, + { + path: 'sticky', + component: 'views/Components/Sticky', + name: 'Sticky', + meta: { + title: 'router.sticky' + } + } + ] + }, + { + path: '/hooks', + component: '#', + redirect: '/hooks/useWatermark', + name: 'Hooks', + meta: { + title: 'hooks', + icon: 'ic:outline-webhook', + alwaysShow: true + }, + children: [ + { + path: 'useWatermark', + component: 'views/hooks/useWatermark', + name: 'UseWatermark', + meta: { + title: 'useWatermark' + } + }, + { + path: 'useCrudSchemas', + component: 'views/hooks/useCrudSchemas', + name: 'UseCrudSchemas', + meta: { + title: 'useCrudSchemas' + } + } + ] + }, + { + path: '/level', + component: '#', + redirect: '/level/menu1/menu1-1/menu1-1-1', + name: 'Level', + meta: { + title: 'router.level', + icon: 'carbon:skill-level-advanced' + }, + children: [ + { + path: 'menu1', + name: 'Menu1', + component: '##', + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: 'router.menu1' + }, + children: [ + { + path: 'menu1-1', + name: 'Menu11', + component: '##', + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: 'router.menu11', + alwaysShow: true + }, + children: [ + { + path: 'menu1-1-1', + name: 'Menu111', + component: 'views/Level/Menu111', + meta: { + title: 'router.menu111' + } + } + ] + }, + { + path: 'menu1-2', + name: 'Menu12', + component: 'views/Level/Menu12', + meta: { + title: 'router.menu12' + } + } + ] + }, + { + path: 'menu2', + name: 'Menu2Demo', + component: 'views/Level/Menu2', + meta: { + title: 'router.menu2' + } + } + ] + }, + { + path: '/example', + component: '#', + redirect: '/example/example-dialog', + name: 'Example', + meta: { + title: 'router.example', + icon: 'ep:management', + alwaysShow: true + }, + children: [ + { + path: 'example-dialog', + component: 'views/Example/Dialog/ExampleDialog', + name: 'ExampleDialog', + meta: { + title: 'router.exampleDialog' + } + }, + { + path: 'example-page', + component: 'views/Example/Page/ExamplePage', + name: 'ExamplePage', + meta: { + title: 'router.examplePage' + } + }, + { + path: 'example-add', + component: 'views/Example/Page/ExampleAdd', + name: 'ExampleAdd', + meta: { + title: 'router.exampleAdd', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example/example-page' + } + }, + { + path: 'example-edit', + component: 'views/Example/Page/ExampleEdit', + name: 'ExampleEdit', + meta: { + title: 'router.exampleEdit', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example/example-page' + } + }, + { + path: 'example-detail', + component: 'views/Example/Page/ExampleDetail', + name: 'ExampleDetail', + meta: { + title: 'router.exampleDetail', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example/example-page' + } + } + ] + }, + { + path: '/error', + component: '#', + redirect: '/error/404', + name: 'Error', + meta: { + title: 'router.errorPage', + icon: 'ci:error', + alwaysShow: true + }, + children: [ + { + path: '404-demo', + component: 'views/Error/404', + name: '404Demo', + meta: { + title: '404' + } + }, + { + path: '403-demo', + component: 'views/Error/403', + name: '403Demo', + meta: { + title: '403' + } + }, + { + path: '500-demo', + component: 'views/Error/500', + name: '500Demo', + meta: { + title: '500' + } + } + ] + } +] + +const testList: string[] = [ + '/dashboard', + '/dashboard/analysis', + '/dashboard/workplace', + 'external-link', + 'https://element-plus-admin-doc.cn/', + '/guide', + '/guide/index', + '/components', + '/components/form', + '/components/form/default-form', + '/components/form/use-form', + '/components/form/ref-form', + '/components/table', + '/components/table/default-table', + '/components/table/use-table', + '/components/table/ref-table', + '/components/editor-demo', + '/components/editor-demo/editor', + '/components/search', + '/components/descriptions', + '/components/image-viewer', + '/components/dialog', + '/components/icon', + '/components/echart', + '/components/count-to', + '/components/qrcode', + '/components/highlight', + '/components/infotip', + '/Components/InputPassword', + '/Components/Sticky', + '/hooks', + '/hooks/useWatermark', + '/hooks/useCrudSchemas', + '/level', + '/level/menu1', + '/level/menu1/menu1-1', + '/level/menu1/menu1-1/menu1-1-1', + '/level/menu1/menu1-2', + '/level/menu2', + '/example', + '/example/example-dialog', + '/example/example-page', + '/example/example-add', + '/example/example-edit', + '/example/example-detail', + '/error', + '/error/404-demo', + '/error/403-demo', + '/error/500-demo' +] + +export default [ + // 列表接口 + { + url: '/role/list', + method: 'get', + timeout, + response: ({ query }) => { + const { roleName } = query + return { + code: result_code, + data: roleName === 'admin' ? adminList : testList + } + } + } +] as MockMethod[] diff --git a/kinit-admin/mock/table/index.ts b/kinit-admin/mock/table/index.ts new file mode 100644 index 0000000..8caee83 --- /dev/null +++ b/kinit-admin/mock/table/index.ts @@ -0,0 +1,137 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' +import { toAnyString } from '@/utils' +import Mock from 'mockjs' + +const { result_code } = config + +const timeout = 1000 + +const count = 100 + +const baseContent = + '

I am testing data, I am testing data.

' + +let List: { + id: string + author: string + title: string + content: string + importance: number + display_time: string + pageviews: number +}[] = [] + +for (let i = 0; i < count; i++) { + List.push( + Mock.mock({ + id: toAnyString(), + // timestamp: +Mock.Random.date('T'), + author: '@first', + title: '@title(5, 10)', + content: baseContent, + importance: '@integer(1, 3)', + display_time: '@datetime', + pageviews: '@integer(300, 5000)' + // image_uri + }) + ) +} + +export default [ + // 列表接口 + { + url: '/example/list', + method: 'get', + timeout, + response: ({ query }) => { + const { title, pageIndex, pageSize } = query + const mockList = List.filter((item) => { + if (title && item.title.indexOf(title) < 0) return false + return true + }) + const pageList = mockList.filter( + (_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1) + ) + return { + code: result_code, + data: { + total: mockList.length, + list: pageList + } + } + } + }, + // 保存接口 + { + url: '/example/save', + method: 'post', + timeout, + response: ({ body }) => { + if (!body.id) { + List = [ + Object.assign(body, { + id: toAnyString() + }) + ].concat(List) + return { + code: result_code, + data: 'success' + } + } else { + List.map((item) => { + if (item.id === body.id) { + for (const key in item) { + item[key] = body[key] + } + } + }) + return { + code: result_code, + data: 'success' + } + } + } + }, + // 详情接口 + { + url: '/example/detail', + method: 'get', + response: ({ query }) => { + const { id } = query + for (const example of List) { + if (example.id === id) { + return { + code: result_code, + data: example + } + } + } + } + }, + // 删除接口 + { + url: '/example/delete', + method: 'post', + response: ({ body }) => { + const ids = body.ids + if (!ids) { + return { + code: '500', + message: '请选择需要删除的数据' + } + } else { + let i = List.length + while (i--) { + if (ids.indexOf(List[i].id) !== -1) { + List.splice(i, 1) + } + } + return { + code: result_code, + data: 'success' + } + } + } + } +] as MockMethod[] diff --git a/kinit-admin/mock/user/index.ts b/kinit-admin/mock/user/index.ts new file mode 100644 index 0000000..83bcd87 --- /dev/null +++ b/kinit-admin/mock/user/index.ts @@ -0,0 +1,93 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +const List: { + username: string + password: string + role: string + roleId: string + permissions: string | string[] +}[] = [ + { + username: 'admin', + password: 'admin', + role: 'admin', + roleId: '1', + permissions: ['*.*.*'] + }, + { + username: 'test', + password: 'test', + role: 'test', + roleId: '2', + permissions: ['example:dialog:create', 'example:dialog:delete'] + } +] + +export default [ + // 列表接口 + { + url: '/user/list', + method: 'get', + response: ({ query }) => { + const { username, pageIndex, pageSize } = query + + const mockList = List.filter((item) => { + if (username && item.username.indexOf(username) < 0) return false + return true + }) + const pageList = mockList.filter( + (_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1) + ) + + return { + code: result_code, + data: { + total: mockList.length, + list: pageList + } + } + } + }, + // 登录接口 + { + url: '/user/login', + method: 'post', + timeout, + response: ({ body }) => { + const data = body + let hasUser = false + for (const user of List) { + if (user.username === data.username && user.password === data.password) { + hasUser = true + return { + code: result_code, + data: user + } + } + } + if (!hasUser) { + return { + code: '500', + message: '账号或密码错误' + } + } + } + }, + // 退出接口 + { + url: '/user/loginOut', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: null + } + } + } +] as MockMethod[] diff --git a/kinit-admin/mock/workplace/index.ts b/kinit-admin/mock/workplace/index.ts new file mode 100644 index 0000000..3274a4c --- /dev/null +++ b/kinit-admin/mock/workplace/index.ts @@ -0,0 +1,172 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +export default [ + // 获取统计 + { + url: '/workplace/total', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: { + project: 40, + access: 2340, + todo: 10 + } + } + } + }, + // 获取项目 + { + url: '/workplace/project', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + name: 'Github', + icon: 'akar-icons:github-fill', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'Vue', + icon: 'logos:vue', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'Angular', + icon: 'logos:angular-icon', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'React', + icon: 'logos:react', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'Webpack', + icon: 'logos:webpack', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + }, + { + name: 'Vite', + icon: 'vscode-icons:file-type-vite', + message: 'workplace.introduction', + personal: 'Archer', + time: new Date() + } + ] + } + } + }, + // 获取动态 + { + url: '/workplace/dynamic', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + }, + { + keys: ['workplace.push', 'Github'], + time: new Date() + } + ] + } + } + }, + // 获取团队信息 + { + url: '/workplace/team', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + name: 'Github', + icon: 'akar-icons:github-fill' + }, + { + name: 'Vue', + icon: 'logos:vue' + }, + { + name: 'Angular', + icon: 'logos:angular-icon' + }, + { + name: 'React', + icon: 'logos:react' + }, + { + name: 'Webpack', + icon: 'logos:webpack' + }, + { + name: 'Vite', + icon: 'vscode-icons:file-type-vite' + } + ] + } + } + }, + // 获取指数 + { + url: '/workplace/radar', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { name: 'workplace.quote', max: 65, personal: 42, team: 50 }, + { name: 'workplace.contribution', max: 160, personal: 30, team: 140 }, + { name: 'workplace.hot', max: 300, personal: 20, team: 28 }, + { name: 'workplace.yield', max: 130, personal: 35, team: 35 }, + { name: 'workplace.follow', max: 100, personal: 80, team: 90 } + ] + } + } + } +] as MockMethod[] diff --git a/kinit-admin/package.json b/kinit-admin/package.json new file mode 100644 index 0000000..1ccd1a4 --- /dev/null +++ b/kinit-admin/package.json @@ -0,0 +1,119 @@ +{ + "name": "vue-element-plus-admin", + "version": "1.6.3", + "description": "一套基于vue3、element-plus、typesScript、vite3的后台集成方案。", + "author": "Archer <502431556@qq.com>", + "private": false, + "scripts": { + "i": "pnpm install", + "dev": "vite --mode base", + "ts:check": "vue-tsc --noEmit", + "build:pro": "vite build --mode pro", + "build:gitee": "vite build --mode gitee", + "build:dev": "npm run ts:check && vite build --mode dev", + "build:test": "npm run ts:check && vite build --mode test", + "serve:pro": "vite preview --mode pro", + "serve:dev": "vite preview --mode dev", + "serve:test": "vite preview --mode test", + "npm:check": "npx npm-check-updates", + "clean": "npx rimraf node_modules", + "clean:cache": "npx rimraf node_modules/.cache", + "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src", + "lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"", + "lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", + "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", + "prepare": "husky install", + "p": "plop" + }, + "dependencies": { + "@iconify/iconify": "^2.2.1", + "@vueuse/core": "^9.1.1", + "@wangeditor/editor": "^5.1.14", + "@wangeditor/editor-for-vue": "^5.1.10", + "@zxcvbn-ts/core": "^2.0.4", + "animate.css": "^4.1.1", + "axios": "^0.27.2", + "echarts": "^5.3.3", + "echarts-wordcloud": "^2.0.0", + "element-plus": "2.2.15", + "intro.js": "^6.0.0", + "lodash-es": "^4.17.21", + "mitt": "^3.0.0", + "mockjs": "^1.1.0", + "nprogress": "^0.2.0", + "pinia": "^2.0.21", + "pinia-plugin-persist": "^1.0.0", + "qrcode": "^1.5.1", + "qs": "^6.11.0", + "url": "^0.11.0", + "vue": "3.2.37", + "vue-i18n": "9.2.2", + "vue-router": "^4.1.5", + "vue-types": "^4.2.1", + "web-storage-cache": "^1.1.1" + }, + "devDependencies": { + "@commitlint/cli": "^17.1.2", + "@commitlint/config-conventional": "^17.1.0", + "@iconify/json": "^2.1.100", + "@intlify/vite-plugin-vue-i18n": "^6.0.1", + "@purge-icons/generated": "^0.9.0", + "@types/intro.js": "^5.1.0", + "@types/lodash-es": "^4.17.6", + "@types/node": "^18.7.13", + "@types/nprogress": "^0.2.0", + "@types/qrcode": "^1.5.0", + "@types/qs": "^6.9.7", + "@typescript-eslint/eslint-plugin": "^5.35.1", + "@typescript-eslint/parser": "^5.35.1", + "@vitejs/plugin-vue": "^3.0.3", + "@vitejs/plugin-vue-jsx": "^2.0.0", + "autoprefixer": "^10.4.8", + "eslint": "^8.23.0", + "eslint-config-prettier": "^8.5.0", + "eslint-define-config": "^1.6.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-vue": "^9.4.0", + "husky": "^8.0.1", + "less": "^4.1.3", + "lint-staged": "^13.0.3", + "plop": "^3.1.1", + "postcss": "^8.4.16", + "postcss-html": "^1.5.0", + "postcss-less": "^6.0.0", + "prettier": "^2.7.1", + "rimraf": "^3.0.2", + "rollup": "^2.78.1", + "stylelint": "^14.11.0", + "stylelint-config-html": "^1.1.0", + "stylelint-config-prettier": "^9.0.3", + "stylelint-config-recommended": "^9.0.0", + "stylelint-config-standard": "^28.0.0", + "stylelint-order": "^5.0.0", + "typescript": "4.8.2", + "unplugin-vue-macros": "^0.11.0", + "vite": "3.0.9", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-html": "^3.2.0", + "vite-plugin-mock": "^2.9.6", + "vite-plugin-purge-icons": "^0.9.0", + "vite-plugin-style-import": "2.0.0", + "vite-plugin-svg-icons": "^2.0.1", + "vite-plugin-windicss": "^1.8.7", + "vue-tsc": "^0.40.4", + "windicss": "^3.5.6", + "windicss-analysis": "^0.3.5" + }, + "engines": { + "node": ">= 14.18.0" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/kailong321200875/vue-element-plus-admin.git" + }, + "bugs": { + "url": "https://github.com/kailong321200875/vue-element-plus-admin/issues" + }, + "homepage": "https://github.com/kailong321200875/vue-element-plus-admin" +} diff --git a/kinit-admin/plop/component/component.hbs b/kinit-admin/plop/component/component.hbs new file mode 100644 index 0000000..21e5653 --- /dev/null +++ b/kinit-admin/plop/component/component.hbs @@ -0,0 +1,11 @@ + + + diff --git a/kinit-admin/plop/component/index.hbs b/kinit-admin/plop/component/index.hbs new file mode 100644 index 0000000..aca6f0a --- /dev/null +++ b/kinit-admin/plop/component/index.hbs @@ -0,0 +1,3 @@ +import {{ upperFirstName }} from './src/{{ upperFirstName }}.vue' + +export { {{ upperFirstName }} } diff --git a/kinit-admin/plop/component/prompt.js b/kinit-admin/plop/component/prompt.js new file mode 100644 index 0000000..98a852f --- /dev/null +++ b/kinit-admin/plop/component/prompt.js @@ -0,0 +1,38 @@ +const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1) + +module.exports = { + description: 'Create vue component', + prompts: [ + { + type: 'input', + name: 'name', + message: '请输入组件名称(Please enter the component name)' + } + ], + actions: (data) => { + const { name } = data + const upperFirstName = toUpperCase(name) + + const actions = [] + if (name) { + actions.push({ + type: 'add', + path: `./src/components/${upperFirstName}/src/${upperFirstName}.vue`, + templateFile: './plop/component/component.hbs', + data: { + name, + upperFirstName + } + }, { + type: 'add', + path: `./src/components/${upperFirstName}/index.ts`, + templateFile: './plop/component/index.hbs', + data: { + upperFirstName + } + }) + } + + return actions + } +} diff --git a/kinit-admin/plop/view/prompt.js b/kinit-admin/plop/view/prompt.js new file mode 100644 index 0000000..dbf324f --- /dev/null +++ b/kinit-admin/plop/view/prompt.js @@ -0,0 +1,37 @@ +const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1) + +module.exports = { + description: 'Create vue view', + prompts: [ + { + type: 'input', + name: 'path', + message: '请输入路径(Please enter a path)', + default: 'views' + }, + { + type: 'input', + name: 'name', + message: '请输入模块名称(Please enter module name)' + } + ], + actions: (data) => { + const { name, path } = data + const upperFirstName = toUpperCase(name) + + const actions = [] + if (name) { + actions.push({ + type: 'add', + path: `./src/${path}/${upperFirstName}.vue`, + templateFile: './plop/view/view.hbs', + data: { + name, + upperFirstName + } + }) + } + + return actions + } +} diff --git a/kinit-admin/plop/view/view.hbs b/kinit-admin/plop/view/view.hbs new file mode 100644 index 0000000..0ae9c15 --- /dev/null +++ b/kinit-admin/plop/view/view.hbs @@ -0,0 +1,7 @@ + + + diff --git a/kinit-admin/plopfile.js b/kinit-admin/plopfile.js new file mode 100644 index 0000000..72e6af2 --- /dev/null +++ b/kinit-admin/plopfile.js @@ -0,0 +1,7 @@ +const viewGenerator = require('./plop/view/prompt.js') +const componentGenerator = require('./plop/component/prompt.js') + +module.exports = function (plop) { + plop.setGenerator('view', viewGenerator) + plop.setGenerator('component', componentGenerator) +} diff --git a/kinit-admin/postcss.config.js b/kinit-admin/postcss.config.js new file mode 100644 index 0000000..961986e --- /dev/null +++ b/kinit-admin/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {} + } +} diff --git a/kinit-admin/prettier.config.js b/kinit-admin/prettier.config.js new file mode 100644 index 0000000..b46239f --- /dev/null +++ b/kinit-admin/prettier.config.js @@ -0,0 +1,19 @@ +module.exports = { + printWidth: 100, + tabWidth: 2, + useTabs: false, + semi: false, + vueIndentScriptAndStyle: false, + singleQuote: true, + quoteProps: 'as-needed', + bracketSpacing: true, + trailingComma: 'none', + jsxSingleQuote: false, + arrowParens: 'always', + insertPragma: false, + requirePragma: false, + proseWrap: 'never', + htmlWhitespaceSensitivity: 'strict', + endOfLine: 'auto', + rangeStart: 0 +} diff --git a/kinit-admin/public/favicon.ico b/kinit-admin/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5a7de08267811feb034bb0c54ab38ca417731aab GIT binary patch literal 4286 zcmcJTdr*|u8OGm?f3Tad7j|KHxq~xpVq;A@HfgF7QY~O)Go9%q6O&2POq*#slbPnf zwzf$$rcG^ZYDE+@N@~3q7I49+L1DRyKt#F7{U!nn%U;+^j39aYobU7VmKqZ!%ltTJ zSayHU`<%1G3`F1^k7e(&;Z`VJu(?k;=T7X%N)jqMq!dMyZeDC3iF!z6rw+67kxV&jDKh*=XMMH7BzC%0ZZgW^F*GR;m`LJ8Nxh;MnV_2 zo#Aeu7~L8}V>!_RD+rBki)NV^cA^7!L1e&FY)JDI!(TGDXW-mnR@`bh?a0EnGI=P9 zy>>$Z2m|CjGIt$Z+!8}0*)asCbu>FBggr}a!3yv^PwX`@<_>sE4e5ZqN9V>rmTRUF zbU(T+S`C+7@m92s-#UA`Fo4_`PSqdlNi_8Le1D!YO<=@vm4O z$%$c4iIKfW(ZR8ZbU?mO?%glC2lvBQG7OL4g2E>3CE|D(x!lD@#>|2OxWk0scT#t^ zS6+JEa8?X~2srB%*M&`SJoL{S_(0yf@}t;S27g&Z0Hpg)z+Z89e{|ZcvsAWtlnyU! zrooK~1ov;@oE;J_FsSek>;)U+*tyTdzLW6F6!^;ZVSuI_-@Jq7m6vJu4@{*G56tUW zpGZTSVx!<}kX-N$2;dyp7%$QPemsxlS=d(&zn~%_Fjb*XpK`!o{mnXPeP))mj8zZ^lWb$NF4{~3AH+q%ce-ErK%W3!iN7WAAnu?S-NPqStq)-K z^r;$jQ1|uDpfTeToz0w~uIE_Ym=?0w^$yVAEIfqUV+#(N3e zk2~QhGcbJDsny&3bvhLwfBl_1IF%8kGpjCBvvt~9zrr8d9#JR!m$M(Jd}05>Eb4nd zJ`&DTz3>To1b>z3MIY{C28N5L>h#j;Kcx$R41YcR4PV_s^-rhhbVi8kGeYX7r!U!S zQoWp`s}X+Qa=*iY4)hJLtt9w=1?Lsv?f#1(Q9@*K9WgR|GWA#mr*$@egH|Q{flqJW zfn&Lc>e2!ND{_P?Q{22wTP^vKVHzF$V&C_+Q16C#BfNFp?VUHBP>oj2B zhz=UjK_mQU{=dKMNf%Wv_mX3|SB)O*b?E`FSmwIXFFL5s)z(Cu`t>S;|CO%wi8Gz> zcD{3+@uDaWG)hk4Wq*?l=C;AD68_*>&6hq4$5NbB{u8&riVPuS;Bxqt{>2%98FcW< z-UZpz{a%6r-kQ#JN!+2ZHi^e>j@Xfb z(FH3R_9nI5>KFImbdI(Z5j@?YYO33 z34gFFoDTl5j|!g{5?GNTRI+4*l?*4In&hHK`i1Yt`Ne(~9lZ9R?`G5acM|n*SGN8s znFXUbf8oK55?7tV%W!hALlXom0foB5=@9%tUGVsTz3@jvj0}t(u4OpB%*i{yd6`D*|GZP1pkYzYvxb4teMYa#Zj_Lo>g|rUJiD`FX%!C-Qg-g2Nw4TGLa#a|M&na z8A`8a_#|~g>R_v;>eddvKnJhy{!TWvy`7|kyQ1Zf^VxYMQp~8#D(_{d@N%#l^n?dP zJ>eOEc0bbbz}_Ep9p1mVhmnEN1LG{D9z=$tSMNjqf-V*rZoG@=pcx%l%Ab3d&aIih z8eE{+mp&i+1G+xk}WiODJh<6N)JDmfKzAKZd|2|w0ncYeRMI&V=sBLj;JI14`X znA}4Vday6K$fZk1`Dluh#aSF*>g2+Oy}Ud24Cm@=8E1fdP!-j*CXKqY;$HSP>1S?o zig|DLDt@t+^XG}oKAp^97_-1JPiEbH%mN~Xwb-w{LX;}_oG4M?Bhm>5iNXX9qIrUK L_}7DgzT)vecywrn literal 0 HcmV?d00001 diff --git a/kinit-admin/public/logo.png b/kinit-admin/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b76c71b6ba9a0f73203fbf745fe93d240a3c7802 GIT binary patch literal 9193 zcmX9^2Ut@}(*{vMApwHYiXtvS)pOc)|-<~rTN`R?%w*ibFMc6?%(G~SRCdlq@OxuY$Fj>+gNgU7NpA{UkLOQhYancbKN2Oxx)dD zJoe;Ot`9Q|KS}$V9h(Yu^dpB%O}ljSN6eLYP~SdR)I2YGf-Q67RymG6eW{Nnr;{O# ze&Gl-e?$+++!6gOAcvrtBl?*`00UqSG+XwgVGxY2;{M={g286`Wwa?J8&-jD%$6%ZhOJZy%0r(kPNf&y< zjLmyYO?ym@d#5@BObvTX^?N6Xex}+z@}pknZ`(}Yb|E#pN7_9Q%V9mjADb{Z) z*0E;o;1p`vGPk$no7pn9H{jnk;5BUNTkL6@>kq5f9%9&2ajWuGtMZlX$r~I=8!K{^ z9Es~3iEEq*YirVVoQd-s32V!;UpV8}IO111<5rg*eCCW>;f!5bl=;LNv%DZv&K14P z6}2=kUB(r;$n{}yPO5}EVu3q+e&&ABqF5Do*c{LMxoKD-Z|E!!AR#kTlKH&vrY9sm z^1hwo4F+WLcF+WG(B!yS&h5a7+X3UFqM3ZCF@FCsKL1fZzY%`lVFBb2KXRBKIW)mv z%I`DOc0HkuHi2!r-w_B`LvLdP6N+opbPUWKTu>ni8AT->BP&}+H*aKcXm~(uay?<|=3TV0mzK#0Db)~!?))=Ccb%=dj5WW>H6!imxA>d>qiVN( z2}Mt!SJCbjcC-*x*ivXhqzbM1m1%xKLFq>jo^n_mRh9`&~a_aCcZc2v;XfKuU)Rbj(0fd zytDtt%ifF1V$ocCVq$W_Y|<>=>F21EVZ4x5+`5o%pRO*cNDjkjqq2d+Z8+f^ot&cn zOV3Hy;^A!TjxFZpW8hWnKbX8QiSBQ!Cwx}ao<^-FE4|KH2&c7sf#0kdwgVFul9^P5#1c_#ls%nE2OHg zZ#1uHvSedcHg+>!^U*#`q0Ew);5FV{_<}lSWFYbh&1d3X?UHVgS%zI3D6O)FYqfXT zuGgcZDIUN6Wpm7!J0EE|pXu?+og+H(q69ulZ$otFE7V9SIoI+Tj10Uy1H-$~r zYj4D(q%6}b2bMTqc^Hk>n2&8r41vKDDk*U$zTvjMV>u?i2E-~SmTfocqEYs}K`|Lb z#jfx601Zu%`4m{5<78evWFAGtDt2I7EXrdv8D4p4su$<`<`)bPQ^5UNuIzhDHYO5z zR(e{JHOz5$jKK<7BlPdf;FL5Dm&~>8OygKD_Xxtmjo;HA!_-#v&eQ_K!e^=alHB-f z`T7>VE4y^m7Q1*YJyOGZR?0dro?c+-%BLA7lQr%>)hIifwtG}`xB!pAc28r+t-tkdk4oO5@i*Rsdqd2Z=_$oeSeQDW-WT{dV8fPMPnXj0Fo=H#vmVK@vw* zM)xic|4NR^>qoTp^@U8cFhLHwd^Fm}R{c>jYK*(<3*6HkJYUdV{DgnJU0q*CV4+h> z#OQtKb9bQ`Yz%cQk#B`~t0mh<(bd1Xa9oe83qva>yklD2cID-k=WsSI-68s*0YOwO zI=EHl)#vg#7*!=&P<-_VmZh@9KXy1bb=CrK<15NrCwo}a#nWR7p}-ay-N_h8GoBSi ze#h)OGH>aFTbRQ;YOq2(f)Fnp%Rja($xYE3Bpt)fR_BE6o5J?$>(mDIr@eH?pL~vc zR>SbaGfvdhOMUA4fV5PjNwbmGLGeYy`iENg8*r>MwJ zfka#Rj!oT@;!&JT^VAWkHDTv$kwI-S(f0jGGuT<5_Iw4?vuI4nKs#<7(|EH%yCK(A z;t|-I;(@|dMmQPZn0{ZRF6Pk3y_ z^h+H;(G>;LHr7{$-UU&xnxE$4Xuj=VS<57@9Im8Fkx{xgHOa3s6K6~w)LAJ!GSTA^ zH}-{k$BDkYs?>X)x}U>i8xd_F6Ew!6ipJr zBi|I95Q>&**Z(PdZ1%L?$<^=|)U~`))^m@0ChU4Rn$T{jj(E_U@{1tSRHzRpN%#nD ze>~|D=0@%hsS-tIg(J^rtZD1zQfpOs?g+(^B5VQJZl!ZauN|K?)!Dikjze8Lai$q| z%dFc5U(k4R>own+o(19RMkl2gM!pDC`oHiVcV_ChuPS-wj246{$5#UXI`yz_OsGdu zH;zWhN2nF%PdPWTc7`-_E)dOQ-WGTHI#Z){AukV;f15Oiy}bKMKepF(K#bc+sV)n_ z=hCfrI-b4R<$SNcBxP2S6piuI5K4MFk^(Y%f@Ie^mxOx8`Qyc?7_x+p+2+&dEe|l) zcqami=<4f}Ft%InaG%-J&D3sCX*E^jH+hn;5P6W4E_=T=^8Hfa&~ zeIBgKl>NBQ%IxU+uj;Pv@Gr!*oZiECw62&ohDWgmovAfrX#%`u@LcY-T&*3|kbNa~ zDY^z(^;moq!Pzd zwn(Fw zEia`W{?|Q#_fE;F5*n4(BvB$|n+)2@YgU`W^P@j}9i3wSVTw!~{_mK9*RX#cI>zp`G~oKihga92dB|#qUOt+>10nv?-(f)P!Gt)Vq-)!CJASZQCT4K+&f7oCWBlGkEPF)yNvoXziXXn9R+7@+66a1_u zl169t&$4<9(L3pP?{c9&SSLBmKYR0jP%L@SI-0GD*v6eUKUA%Xqbi0viV}HNCg0v3Vl&I9G81zGPrv3Z;^2w!)P%Vw1 zzLNM42O-!kYr7kfQnR;3;kOQ+Jpa=`EYQ>fJLjs9cYIc72={##-ozb)d8 z)yM?yzE+9Tao9kb+n!7@`r8e_NT-5=_`TDmPF*e@|NBIGS=v4Wv5U``Ai}BaX?>yV z25jT=u6mu)56~AUfAinUYyFUKEfTnW55zhBtN^naZEI;teO5k{n!G4DP!u8OQvY|bvaWQn#JnSj{8 zp*mB#+_c|ws=&&oyFG|#6)qq|Tj^&6S&=WUF;E_{zj);G!=P&+kLI?Ym~D1 zi#2I2nr*KxbM#l(?~3R#o;cUtzU!m76bys4!ZAIoTYxwObmO0s*!6x2vHOI#ZJMj8 zH5XmZ0lDAbye!9p`mcWaqq`;}&ztJA$EjYa#3lv$^32iv+r0}-3un|5caA_Svp*N|a;xlZXIaZGk1wSgqto+M zn_J?G$X^E11FX6hZo#yU0=A=4s9i!U@zPqTsZS|!m$N-fE zx&i^YIc}7ma(5FdY)~pAn+-+#u)3=DGd*cw`O(es1&SCK%7AI|7wZQo#H`i~^W7>O zGwaOle7AemIq8LVPc}hJX9a|(t#0ImloQCkDw^3u=Q!N^J$=U%eN|wW-)(N`W7gtH zUB(<(AU&5YZMxW9hq%9i%%rN5h-)o;S6;ilwg~dN!<#AtaeoITzj;4@{edu2_Ya5E zuf|F#y2gWA8qtjA!lx%>`2neAh)G{IUoYpIuUYIR+_DaHgtx7QSTEgsq&xrGZC4v+ zGr2N`RRcUL$h;vu?Ic_IgRDoCUXwq=%|Dt)h}4u+t6s>bA!@_xXxQFZO)!mfPSkZ* zb&ucv{P-hQreCoBtpBJyw3fz7_PD;{(lXUaE+1Mvl+#6BS9LKIx1-7K%8(|BbQ9Tv z9BFy%ps%|y3VEV;pI|N?&xlVgboR00q7=nn1fCH_DB^YWJ}L$P^q$&@xuZ7S2gVwT zwqY`NtnK(;Fo>;`K4%O9JLa&wjP0K9jAUiiR}|lQ)l`6f#nyQy{h&T)J@&&Qb?bAi zSV{L{eqOhOPlsh8gc)V!?rr+u={)>xuus zHD{>&e<_H+@YiY9yzAi{8~L4j%kj`B#bzROzQV*eI9+#UKWvEGU<^{FWbOM7G~!V5 zdL}}7CWr1!>Y%;?6iqp-({XkvQ~!27lj9Ny94&E|7r6?rx4|i_WgX1U2wij~z8`Mc z^ND4VoWce>sE_iC{MSx7rKAY^FeT_N^4d2VzuBf&q{fJDhiJpgEbhAYO(>6+@YHqFlb@1CZGIjYnUw~C1o3i$~Y ze^Wcjtq9~Qk7qI++SbrK#g`0eqnGs+IeyH-@j3dqECv62$3jHpt@48r=`)hRgd>G!GW@p4lkjSMa^msYG{sJ0<9*rq7cbb)$U)V0KeN;H1!{$EPN$D8t$xO z9*$&LMJR3>U{s$oq-4YF?`flOgF-SkBM?JRXz~4{>(b#Ns0w?Jf)%HZY(v!lbh^49 zt$~M~Z_M||JR-F8taKM!5%QGuaVh(+?X)o+9ZIb0AYz##sleOG93^fOtP~yF_b)HA zpsrp>W8{LZRZ6z?#JV#BJ|KSVq`m^FI>P2q>du@01)G~vZ9W3o82HADSX3s7!x5e% zP;eeR8Pw%8z2$H;K18lm5{vD7pu<^kcuerp@<1#9=i%_89)7!wkWgFkVwr?7GFo8* z`>vD>%Zb5TD$Xp@l*dX|JuIYT?%1qc{th@lCM=fJV*P+l>5w{KjzgEm!q5JjE{2GR zrT#_km{;iC?w8Yu!22u{JSV!_{#Fxw?ok7876W~g9@M9Ire{Y%J3l;Y!KjG%S|7_4iL?oKqi~)(S>UROoX9n86)G<%DvaBc1qEe>N^B&Sa4}!j%i^I3JrG~g~7fbY@ z5KStNjqSD%TJfdNC3E@=BzCUMss}vhO&u?XX(8UUF0#i^#}84jHsTLQR&e6F#(hx2 zkI=A9gGP-p+)&9EY<)}IUI{h++Nthyp!6vH$>tzz!s{Y=<{}*^A$T#(u>>|Z^+QDV z4TSwSmL#|2M`bF*w&)=AdzEi!G5@UF;d4hC1`=zBy329FX(lXvwZ<26SO(;OMF#Q} z8m8k$-WCyO$q4{h5&YeS*j?p1Z-tS=#evh`ZE8XP@k_JqI+Ub-n-&vt*%jP0xd2ky zvH+WR$(Ig`nv#!L-p&o2twPO7EzOaLQeBd_n8JQk2<(31V;kg6X|`iPbsQHkL$zUQ zGopr))6vDN`tF0ef`FLVVQszK7`r?$PG;btoXY8in9u~SM`cj2w&4#k+uYj&4r+u| z%mZVfhZ!IPE7u0l@U}MWEJ;!P6H4Q;g&n_1?gwf*dtfda51gmT7St72pe&F6Rwn8} zJKyuboDJ|vrcg1JDH)fd1<9F#(17cOohu&$L0oI>+wKsWTC@K2F;9BfwvNI(q9A9~ z;vm+q)X%Ah=j>c<894Z%PT}7nyxb~l9HLo(C)6_wq{QxVz|Lt;J4Ic=IWd$>;3#!GvvglmJ z6rcXhtIp5?9u0oklY)0vfARYMbh#GWch@No&J_=#Y=sCJWjdqHpFI=UB-X}T#CVpj&4N6 zk4*_Xbm*R!B+rD{WSwvT;+%r=MTQR7{_q`g(04U!r2n)BS9;@h{+eB-GC(0vsk=tazYHtM@+ZZx4%g zQPZ%3st!Io!%D23Inuu@_{7RhC(AM%768pr@#11B8(0CFr!&TVmG&Eoh&(O^HWALE zYG_RizVOpvh4Qi&oj6?ocb(V%E4D)gb&(XnVIRN4sKfe-tyY?BDEZ?(5_OPqurkf+ zGa4R60igWO9d?Q`*ruI}gtx_tUL$uhTWRaIasNm^H!ct9&TrgRKQ*nz@SXd)2qOZ{ISBOILu?MRB8ppvWhu44^j}k2IR=a z%oduXWoL1ZNB!K!QGEtyZdV4y-6fSCVy%%BzoB3*pB-kmO1aeRs7sP0AGyB5Wn!5J zy!F^8qFRj@r*=X9Cutn#lINwnF3t91*v9V6nR=Ky(r3D7bG!PjTv?wA9GqhNIhOxMz^I_b zu?<|eo>%$>g1gjFpw)c;j2fqp9Z99|w0s++zlg&d?>!Fn7b5@!zGj}tEu8IkX>N~X zAB<0{Q>R&Q8*DBTBu#5HqDjv#;<0DmCdCh8Ijj46IHaKd_=(*fcNcx*s_P!^p53&F z&bl=O``E(cKoOFnGgxJFxZ1Ok!TG40{tT#C@q(HR^e`vqTTaxNX{k}=# z^(QAI7DM#6{_nyLiMI4D?4llLS7%lMwps&T ze&4)eu{EKw~nVMvLCE|-??Gti7f2`^{MLVv29jNgsxWal98hM`c|pm#)_ z+#Gg_6>>SYplZVf1qw3m7;uiKP&2?7eLf)*POMLpfHAFrtNI3R-yJpa9fAZ!d@z9h zK%-;Y_NWPi*mo}P$*&4^H2hkMhpk?rOYnnOwaqu{poh{{cE`5who}h{si-qJcq*p{ z@^#&l#y&NB*_+XxTDSSWA2l`Uz}e2n#jzRL#qX-in-K>YqYk#d-)j-_4beeq} z8HL&bhd1&?130&Tz|2;h!M?!(eDRrD((g}@rL`S4xgOVDfCWFj^sA2+Fz09d*5S2G zw0&@2*KNCNII|qGyCLd<@1K?m&(&_iui2kt&Z3g#fI_KCLeTF?ZbwE0$wRYgq5IOX!@=;xzwsY7%6q(CM3~%9` zbts{5U%U)(Y>$nHXb-PQomkm7M^3Ycmc#7yY$6J9137e2gRODlT2e`21=St* zRLLJ(ynq2dM~LRFyD$(rWdk+k;AHwBrj1|B!funhE1D5So_#X>I+JbGbY-(aRO!}? zgOm63?k=VPsC6EZu?e4)7tJ!`sE->&_h7-pO(| zjvU_mUxYqI>WnB}%FyTMb=h~d3_~~49SwXa%iTO%2MF4WTET$K> z_Tn2f=4`gQ&mdVEEh}A&>A2TQ#$!yx=Iz6KA>Ho-s`?DRjm7fa)xBV_L6LE`<bQpiFQV!gopI%fH*w5GCM6noHPRsd&bq?8aJf5 z{`pCih{*8jz~%7AOvk_XV4Qyz2e?1Ufz^#DviazrQLEjSl+?_SelIQl%goYknO92d zV{fPmEgEz>a1^!PtdSz?!DTt(Id~q@9njS9Gtngpt9-)sQt9hf*)EC8b0(W7DZ6?` zekZH#d8B(4me{omw9=`42=WiidW_O8>LmGb>}Xt^&?T?vL1WHfAYNsP7lV%X49Rd zE#9{Ue5pSY{(T37w9kW9TClkT-J3(W5>*w8~-xiMMou2P7qsd>5 zPy+ly!ti-Sxxw$-$YKTOmB@ea#b~fo%C=F z4kh+sALcSOQgUg4uUpwo1x$E)hmsdfb}0I)amP0dho&LG4gDACl8=;loj#MLdNA^jFF!?g5XBTGXb-r z97_?=wD*`%-WNq$3j>?iEB$ctK zoLuMa9bhapwu&-)H9%71zY2GGi;-fhifd7wW{+a%1N7NrN>i1RvMDVP_NZ&U195Mz z6D}lpm1yV?p2Vq*-ax@-hAMx~tcOMGFscM-2jb94Y z2!;%+O|us={Jj>Jf_N{<|D>6%aP|&74tSCa%xnu)ThdmquT=vS&7<~0@j-vWb|qG* X5_)J=A58xJn+`(LK;w&=?YsX2HuGA3 literal 0 HcmV?d00001 diff --git a/kinit-admin/src/App.vue b/kinit-admin/src/App.vue new file mode 100644 index 0000000..08d6b99 --- /dev/null +++ b/kinit-admin/src/App.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/kinit-admin/src/api/common/index.ts b/kinit-admin/src/api/common/index.ts new file mode 100644 index 0000000..3f78a52 --- /dev/null +++ b/kinit-admin/src/api/common/index.ts @@ -0,0 +1,11 @@ +import request from '@/config/axios' + +// 获取所有字典 +export const getDictApi = (): Promise => { + return request.get({ url: '/dict/list' }) +} + +// 模拟获取某个字典 +export const getDictOneApi = async (): Promise => { + return request.get({ url: '/dict/one' }) +} diff --git a/kinit-admin/src/api/dashboard/analysis/index.ts b/kinit-admin/src/api/dashboard/analysis/index.ts new file mode 100644 index 0000000..1f00ed0 --- /dev/null +++ b/kinit-admin/src/api/dashboard/analysis/index.ts @@ -0,0 +1,23 @@ +import request from '@/config/axios' +import type { + AnalysisTotalTypes, + UserAccessSource, + WeeklyUserActivity, + MonthlySales +} from './types' + +export const getCountApi = (): Promise> => { + return request.get({ url: '/analysis/total' }) +} + +export const getUserAccessSourceApi = (): Promise> => { + return request.get({ url: '/analysis/userAccessSource' }) +} + +export const getWeeklyUserActivityApi = (): Promise> => { + return request.get({ url: '/analysis/weeklyUserActivity' }) +} + +export const getMonthlySalesApi = (): Promise> => { + return request.get({ url: '/analysis/monthlySales' }) +} diff --git a/kinit-admin/src/api/dashboard/analysis/types.ts b/kinit-admin/src/api/dashboard/analysis/types.ts new file mode 100644 index 0000000..4a239f5 --- /dev/null +++ b/kinit-admin/src/api/dashboard/analysis/types.ts @@ -0,0 +1,22 @@ +export type AnalysisTotalTypes = { + users: number + messages: number + moneys: number + shoppings: number +} + +export type UserAccessSource = { + value: number + name: string +} + +export type WeeklyUserActivity = { + value: number + name: string +} + +export type MonthlySales = { + name: string + estimate: number + actual: number +} diff --git a/kinit-admin/src/api/dashboard/workplace/index.ts b/kinit-admin/src/api/dashboard/workplace/index.ts new file mode 100644 index 0000000..0880640 --- /dev/null +++ b/kinit-admin/src/api/dashboard/workplace/index.ts @@ -0,0 +1,22 @@ +import request from '@/config/axios' +import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types' + +export const getCountApi = (): Promise> => { + return request.get({ url: '/workplace/total' }) +} + +export const getProjectApi = (): Promise> => { + return request.get({ url: '/workplace/project' }) +} + +export const getDynamicApi = (): Promise> => { + return request.get({ url: '/workplace/dynamic' }) +} + +export const getTeamApi = (): Promise> => { + return request.get({ url: '/workplace/team' }) +} + +export const getRadarApi = (): Promise> => { + return request.get({ url: '/workplace/radar' }) +} diff --git a/kinit-admin/src/api/dashboard/workplace/types.ts b/kinit-admin/src/api/dashboard/workplace/types.ts new file mode 100644 index 0000000..da23e74 --- /dev/null +++ b/kinit-admin/src/api/dashboard/workplace/types.ts @@ -0,0 +1,30 @@ +export type WorkplaceTotal = { + project: number + access: number + todo: number +} + +export type Project = { + name: string + icon: string + message: string + personal: string + time: Date | number | string +} + +export type Dynamic = { + keys: string[] + time: Date | number | string +} + +export type Team = { + name: string + icon: string +} + +export type RadarData = { + personal: number + team: number + max: number + name: string +} diff --git a/kinit-admin/src/api/login/index.ts b/kinit-admin/src/api/login/index.ts new file mode 100644 index 0000000..04bcbda --- /dev/null +++ b/kinit-admin/src/api/login/index.ts @@ -0,0 +1,31 @@ +import request from '@/config/axios' +import type { UserType } from './types' + +interface RoleParams { + roleName: string +} + +export const loginApi = (data: UserType): Promise> => { + return request.post({ url: '/user/login', data }) +} + +export const loginOutApi = (): Promise => { + return request.get({ url: '/user/loginOut' }) +} + +export const getUserListApi = ({ params }: AxiosConfig) => { + return request.get<{ + total: number + list: UserType[] + }>({ url: '/user/list', params }) +} + +export const getAdminRoleApi = ( + params: RoleParams +): Promise> => { + return request.get({ url: '/role/list', params }) +} + +export const getTestRoleApi = (params: RoleParams): Promise> => { + return request.get({ url: '/role/list', params }) +} diff --git a/kinit-admin/src/api/login/types.ts b/kinit-admin/src/api/login/types.ts new file mode 100644 index 0000000..446a023 --- /dev/null +++ b/kinit-admin/src/api/login/types.ts @@ -0,0 +1,12 @@ +export type UserLoginType = { + username: string + password: string +} + +export type UserType = { + username: string + password: string + role: string + roleId: string + permissions: string | string[] +} diff --git a/kinit-admin/src/api/table/index.ts b/kinit-admin/src/api/table/index.ts new file mode 100644 index 0000000..bd4f0b9 --- /dev/null +++ b/kinit-admin/src/api/table/index.ts @@ -0,0 +1,18 @@ +import request from '@/config/axios' +import type { TableData } from './types' + +export const getTableListApi = (params: any): Promise => { + return request.get({ url: '/example/list', params }) +} + +export const saveTableApi = (data: Partial): Promise => { + return request.post({ url: '/example/save', data }) +} + +export const getTableDetApi = (id: string): Promise> => { + return request.get({ url: '/example/detail', params: { id } }) +} + +export const delTableListApi = (ids: string[] | number[]): Promise => { + return request.post({ url: '/example/delete', data: { ids } }) +} diff --git a/kinit-admin/src/api/table/types.ts b/kinit-admin/src/api/table/types.ts new file mode 100644 index 0000000..876c111 --- /dev/null +++ b/kinit-admin/src/api/table/types.ts @@ -0,0 +1,9 @@ +export type TableData = { + id: string + author: string + title: string + content: string + importance: number + display_time: string + pageviews: number +} diff --git a/kinit-admin/src/assets/imgs/avatar.jpg b/kinit-admin/src/assets/imgs/avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d46a70a46430e31744420865138cc7eedb8b77e1 GIT binary patch literal 6264 zcmbuDWmMGNx5t0O&^aUB-GkEIDM;r~(j~)yH1YsLHwXesHv>qAlqeFC(jYM)4I&{S zWpMHN-*xZ0ukYUH)p>E&=dAr*`<(r~UASEXAX@60>Hr7?0HC`BZkK^5fZ#3&2?&Xa z3CSR&5Hd=Nd-o_PS*U66l0}G_gZXX=!NmA^_{CtlauO19x)$0GwJnhL_Q-_DgqD_5 z(Es;<+g<>Q58MDZU=SOC1qFejpxb_c9ssbgz;}@U8z3wkFg7mU?Gk|h_X+@TAqs#X zia80nZ-z*uFcD zP)Y!!L(=9e+rcumPh+2YXE(J-QejRVpO0I!gpo~}BLMlcHz=;JP-D~&6C7Ho@}#Q6+@<03;Kb8wmX_-jgx(e>OVud(P*XF^Vl16sN3 zL9VSB`M_g%o1P)BrQX9$svj~GgHrVO^-|)7rKENg&rc-;oC!k)8e~5)HYqtZ2DASDJ8VEC2{_$OcaQB$S8L5D^GQ? z+s$*j6EqD70RKm}|D$LS78v_qHDf{U_Uv61dt8A`-ZyYSRaJk{$HGGBBjlXX+YC8V zKwC6zGPAqf`cyz$pIyrm(T+CDOVQ!GC`}aSwxBW1ad%G@#B25L_H>uJj_SdwyELe? z%Fe*gzZu@k_u1M3aj&S$SqNz`-S2$GASYKgNd~J_^|UX_DG1h-kMkwvwcS#nMa04V7ZUszQk&4N$!hAgCTqpAQhQTk8ON*f0ofdV2=AQy@IIWQN=Pws^DCr*gw;EVeJmQO)u{x?i}GaVh_sL#~;X+7GB*sV+YkAKOH z1g^{+L!LhAjVPHKFZ{%Zl7;fP!+=RU0=KZEB1ma_T2b`3fZ#yb9jF2X0D-ZvvGBpT z*tl5WJ1!7#2MWcx$0`I-!X;-DHZZh%$`YQ&E-0d0{DFeQJ6%-8$i8tZqKVR{uM@jzyiT0o z7c1-kPHi_&7&CHY^{doMimEv}v39dY#&Lgi=@(n{fNjE>B(JpcOwknSb2+tX++3cg z32t~n^3u%`AtMKa_^FzDw{tyaj}mT_SWwV&0ibLN*!>>|Owa%LbKvT4stf0)h_`1JuTz zwF(^utK=XF|Ct&@jJM8u8IOb7jQc*V9HTkqI4CC2!JgUU0{6MLFg9yAHpz3dDZx!@ z%o`8A+h+QIWx*?}R6xxZ68zM4BDx4){0`?SOX0o-f_=pGF+*6D2_s#3Xv5In#4PWP zm^HIklb(YU7j;~7_apAK8Th|gPZ1_+9E4lGMe)LS?_tSUeYcu}830qZ-I%ywSJLBQCSZ*o5 zCo0nC;jboG#KEqAdOUJ=+8r_QQkfjYgT#Y^(;!X6g@~ z_AifVbfl{4(J4zV*gr{36(ao(#RMe|fy1^js-iSUeI6-0np|F+Dw&E=9G_ecKU^vA zWN}f?U8B~?+h<|4H%<+0JW1%?Uve+}TEc~Fp?(8cl~Ve?ztZD{ z{~Twy!MMdpLAL%yl|zp!)gkn3(ZRQbSoH%#jIxSu*UBhKF_KY*!St}JmGuYDa}dV@ z_oh&%3MK5Gv1|}-V*SUam&DxL^F$R6P_Yf^%0k_9LI(d(DHl zPM&QTmtZpFK!M%pmuP3^N0zjRnhP%sU!^J~xher`D*M--%wWo?s)K_BH}(j((H1w? z7Z*GPd{)eY%@U4bUG2<~q*Uc((k`5%BAC@?DR(L0zLv)#<0>7~;bCt|0bO7%Ianpn zDn!%zLBCUU+O+z^aS_=Pt+MKzoWSkmc^{qLsu%`6;^r1aV{6ZqkAcYR77qj>|qEPS9${R0!P!M8cHKDuKPmO5kT`Ds^pNeal!XZCTLU=7b z{t^W-SpZzaE=Oecj8B7_iQm%=1)|lV(V(*nM^=yN!W*P~prKXZr*1bYLCYGHz^*?%h;PzP7OMlZ4Qx z{1%wAwuzq9MLM?!Mov+J?XDQ(a>oiy-+x zA5mNxTAM+pK^nSlVjC!DnfL{tR3swA>jL(F3!ReXLiLOu(mk)1Ad}H`k zDvyCLitK~}eGA+p|MqP!|JQHE(p$h(&Y~nosuYpdE>*XXA_t3$HF`8MP?;7g$AtFT z>28_cZ~Zc6)KBIYJ-A!6ezqZLJ0&`AJUW~N-q~jDs>2y!A+bVs>7Em=xlUaQHOtgA z)_5}rgeQT{prumS(l!aZ9s5J4+i-OWokXFjcY=$+q2NBgNtz7!HKOs$6>q+a$j}x8 z0|<95@%!M{&R^n|q_h5kYKUA9Y7M*SMm(G6<~Ls}3dg@m+*lS}DJj;lW>r*&VtU8t}8EzuB{!;QTLJr)#bnPeko9@_>%zJS|VkP zq(@GhDofDSCE1DSSyHNOve6_Tsc)O;jy!WK4{C5AfYY#(MCD_wj;l7OhwS&Owz;OQ z+Av;ow)s^N=-F9Ggp)!MI8FhVfZd9(Xa_%RD3J~3ahc1ri>JYDXehfQdP2SdhX6zX z06!G{F*%?|ff9Uo`jcl9VbGXN<*o?c9%Qcl!`HD!-*kV}U_C0F#y85{Nsrl#h%Ny0 zGhdHH-|}^l3G5c=HPMhdy0rPtsXd8UuT=QQhu-{%>ElrAU^o9n`5$7#;`4Lwj$|$k ze>h$|+(tcL=ESL?%JG zB%dfqN0c6Y{fo;V%L4S2J|Xw6vghC7DJdXikfoOoh5Z4nW!a5_)5XG&Jjql|qKKS|h#jfwPynLJ0 zBOycm3`flAZyQCqTfhJlF+<%mk4!n4g5%jj9OIo>Cr?ftGlPeo#N|c#7JhEmd z`*~xLs-WF2tr3RF9Z4y)J;@b>0iOk8DKtUaiRQ|+r4*)Ynp((I%| z6GaeKR9BXVY(8(q^c;0<6%VonVwDK@?IMeXA8xq@Gcn9G;@$#KYDGYlNZ?Z9+#(rJ z{`sQUY@6Y51Lq*;;-wYd(C_F0lm<*`M(e~&Octe=J*S6=E>mCK)cZ4l6~1}49`nAc zqVi>Keg?C8G^{SIKfT!5yNCo`Ap&)g_1B0rKJ7Focn>aKTVsHkx`_JMX{3tj$u>at zI0SBi$RkQF5%Pnj@VTx}*IL*ReCd#}IepcO#$zDEDdF=sdQab9>X{W|0q|qoCly&* zkrz3ljI^UWbrCq$Sal5;xgXQ{kL=wJWiAMvHHi4wlFVzedxwrHzxLmKU{l?0foUp! zxs?pYcFW}F(iw0GI%Y^X`%^z1YcYW+WS4A;aeK0y4$9x`n(=-AWoz<%9~9WzM3?tf zj8V%)_)lfghSRHxQx>3V&6H;~!$G~2k*)vyL+9h* zF6tK8_*R@WV+r44X30-hd~o-uDVBfaW4`imz^iwyWd5xC*;sP8E_^wJiksZA2vHjN z(CcFN<1gh-98!wbr?ZBtJBdz6GcqjloM9Rz&?okNLYl{Jqr7DcD~r*66j?E82c+Dv zRP0|fKL;#R`73fS-iECsC9$!Z+WMf)OGS(!6lwWBLZ5D;DR17`1pgDTscFN>6RZMW zIDEKJt(k8`A*6gX<>`7&e+#%B$D}~!7w)&!#?;>e4TY6BG8#Lqa1JL=OEVb(lY&hgHWmo}y^l9rQ@Ia0(-S;V1<%xVLlB}X8-sNxJojf$ zmiR_6G!Fs-?|u#=7(|?!Wprj1WZMv^t^#Mo{+YX41afAvNKDTy4qiJ#XO=Dwsu-*` z4Cq|_=(+NizBhTbH|&-)wGpvEpEMElu6=K6YU(XqYU(#nZ&KMM2{@=jDWN}zMHThr zGM5myaXMubt-3?=qw2uD`RkyMKOPa+ysoID^4sW=lUbRI15*91B2 zo6JX@O}Y1=6{<}TV}obhs!^(S1I1>71e{dFL_8$L8>!SV6_|+{nL|xI|rrLlSfjLJ4vr?#bx-ADLXnY zgilc@@O6GI!CNlI4s3moX%|TAUTsLKDDDg-)F{@VF+QmbF@Gg0^hGGjNuP~Jbxdt+ zd__@chTr1@HQmc4p09MYbO zez$-+ME&*W8Y&h_W&^6ePb80sWE(HDAh^ zi4~Lruc+lyf@zd+`#r0mg>i9E1YaGvm^(YcAKZ}u!yy2)i(^i{M->%1R<$YCVFrlXzFW1YXmrAb z)KGK^FX!mv^4#>o<|N&HW%_2uUyNKAuMMnSznr=&F${X=!u$>Fe=V55CBE}-!H_!> zMv2A14qNysIh$cP4!ekQ+J6kBzZGs_q2k1)JRjdyJZfZsyvu>XDZ}BE5g+WSz)jH! z{;YURbaD4rY^gw4mRqpF4Skk_XHWHujU|&Mcx!hSuHl)9T@*@G0!lNbZELl9Eukai zbh-8=6Z2KyBTXZ1-}`fraU%Ux)$%yF+9gRskBo9GIqpgGF<5AS>jg8XM;hx+BK&r! zE;rJg*Y;rabcZV?9Sw-Hcu8E@-LpjYEWy&GXL~VkR^a(MJgtdG{ojJ?4>5}t-j+P^+(<;A4L=tRZB~w^*Ys`;%i8B)xY+a}hH{7crjc+B=^6+Oo zH*Y&-{z2Uk)}URh-CaU3r=g>zo-K#=y=+}n#QG_*Ta{ap?J75@#D9$knHK|BvqUI^ z_46Fh)wAfUx!fAAsP2QLMmc}Qr)mFM_MdRY_n8)#DArG~RSr>3R@QnToH%m3_+J|q BLq7lj literal 0 HcmV?d00001 diff --git a/kinit-admin/src/assets/imgs/logo.png b/kinit-admin/src/assets/imgs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b76c71b6ba9a0f73203fbf745fe93d240a3c7802 GIT binary patch literal 9193 zcmX9^2Ut@}(*{vMApwHYiXtvS)pOc)|-<~rTN`R?%w*ibFMc6?%(G~SRCdlq@OxuY$Fj>+gNgU7NpA{UkLOQhYancbKN2Oxx)dD zJoe;Ot`9Q|KS}$V9h(Yu^dpB%O}ljSN6eLYP~SdR)I2YGf-Q67RymG6eW{Nnr;{O# ze&Gl-e?$+++!6gOAcvrtBl?*`00UqSG+XwgVGxY2;{M={g286`Wwa?J8&-jD%$6%ZhOJZy%0r(kPNf&y< zjLmyYO?ym@d#5@BObvTX^?N6Xex}+z@}pknZ`(}Yb|E#pN7_9Q%V9mjADb{Z) z*0E;o;1p`vGPk$no7pn9H{jnk;5BUNTkL6@>kq5f9%9&2ajWuGtMZlX$r~I=8!K{^ z9Es~3iEEq*YirVVoQd-s32V!;UpV8}IO111<5rg*eCCW>;f!5bl=;LNv%DZv&K14P z6}2=kUB(r;$n{}yPO5}EVu3q+e&&ABqF5Do*c{LMxoKD-Z|E!!AR#kTlKH&vrY9sm z^1hwo4F+WLcF+WG(B!yS&h5a7+X3UFqM3ZCF@FCsKL1fZzY%`lVFBb2KXRBKIW)mv z%I`DOc0HkuHi2!r-w_B`LvLdP6N+opbPUWKTu>ni8AT->BP&}+H*aKcXm~(uay?<|=3TV0mzK#0Db)~!?))=Ccb%=dj5WW>H6!imxA>d>qiVN( z2}Mt!SJCbjcC-*x*ivXhqzbM1m1%xKLFq>jo^n_mRh9`&~a_aCcZc2v;XfKuU)Rbj(0fd zytDtt%ifF1V$ocCVq$W_Y|<>=>F21EVZ4x5+`5o%pRO*cNDjkjqq2d+Z8+f^ot&cn zOV3Hy;^A!TjxFZpW8hWnKbX8QiSBQ!Cwx}ao<^-FE4|KH2&c7sf#0kdwgVFul9^P5#1c_#ls%nE2OHg zZ#1uHvSedcHg+>!^U*#`q0Ew);5FV{_<}lSWFYbh&1d3X?UHVgS%zI3D6O)FYqfXT zuGgcZDIUN6Wpm7!J0EE|pXu?+og+H(q69ulZ$otFE7V9SIoI+Tj10Uy1H-$~r zYj4D(q%6}b2bMTqc^Hk>n2&8r41vKDDk*U$zTvjMV>u?i2E-~SmTfocqEYs}K`|Lb z#jfx601Zu%`4m{5<78evWFAGtDt2I7EXrdv8D4p4su$<`<`)bPQ^5UNuIzhDHYO5z zR(e{JHOz5$jKK<7BlPdf;FL5Dm&~>8OygKD_Xxtmjo;HA!_-#v&eQ_K!e^=alHB-f z`T7>VE4y^m7Q1*YJyOGZR?0dro?c+-%BLA7lQr%>)hIifwtG}`xB!pAc28r+t-tkdk4oO5@i*Rsdqd2Z=_$oeSeQDW-WT{dV8fPMPnXj0Fo=H#vmVK@vw* zM)xic|4NR^>qoTp^@U8cFhLHwd^Fm}R{c>jYK*(<3*6HkJYUdV{DgnJU0q*CV4+h> z#OQtKb9bQ`Yz%cQk#B`~t0mh<(bd1Xa9oe83qva>yklD2cID-k=WsSI-68s*0YOwO zI=EHl)#vg#7*!=&P<-_VmZh@9KXy1bb=CrK<15NrCwo}a#nWR7p}-ay-N_h8GoBSi ze#h)OGH>aFTbRQ;YOq2(f)Fnp%Rja($xYE3Bpt)fR_BE6o5J?$>(mDIr@eH?pL~vc zR>SbaGfvdhOMUA4fV5PjNwbmGLGeYy`iENg8*r>MwJ zfka#Rj!oT@;!&JT^VAWkHDTv$kwI-S(f0jGGuT<5_Iw4?vuI4nKs#<7(|EH%yCK(A z;t|-I;(@|dMmQPZn0{ZRF6Pk3y_ z^h+H;(G>;LHr7{$-UU&xnxE$4Xuj=VS<57@9Im8Fkx{xgHOa3s6K6~w)LAJ!GSTA^ zH}-{k$BDkYs?>X)x}U>i8xd_F6Ew!6ipJr zBi|I95Q>&**Z(PdZ1%L?$<^=|)U~`))^m@0ChU4Rn$T{jj(E_U@{1tSRHzRpN%#nD ze>~|D=0@%hsS-tIg(J^rtZD1zQfpOs?g+(^B5VQJZl!ZauN|K?)!Dikjze8Lai$q| z%dFc5U(k4R>own+o(19RMkl2gM!pDC`oHiVcV_ChuPS-wj246{$5#UXI`yz_OsGdu zH;zWhN2nF%PdPWTc7`-_E)dOQ-WGTHI#Z){AukV;f15Oiy}bKMKepF(K#bc+sV)n_ z=hCfrI-b4R<$SNcBxP2S6piuI5K4MFk^(Y%f@Ie^mxOx8`Qyc?7_x+p+2+&dEe|l) zcqami=<4f}Ft%InaG%-J&D3sCX*E^jH+hn;5P6W4E_=T=^8Hfa&~ zeIBgKl>NBQ%IxU+uj;Pv@Gr!*oZiECw62&ohDWgmovAfrX#%`u@LcY-T&*3|kbNa~ zDY^z(^;moq!Pzd zwn(Fw zEia`W{?|Q#_fE;F5*n4(BvB$|n+)2@YgU`W^P@j}9i3wSVTw!~{_mK9*RX#cI>zp`G~oKihga92dB|#qUOt+>10nv?-(f)P!Gt)Vq-)!CJASZQCT4K+&f7oCWBlGkEPF)yNvoXziXXn9R+7@+66a1_u zl169t&$4<9(L3pP?{c9&SSLBmKYR0jP%L@SI-0GD*v6eUKUA%Xqbi0viV}HNCg0v3Vl&I9G81zGPrv3Z;^2w!)P%Vw1 zzLNM42O-!kYr7kfQnR;3;kOQ+Jpa=`EYQ>fJLjs9cYIc72={##-ozb)d8 z)yM?yzE+9Tao9kb+n!7@`r8e_NT-5=_`TDmPF*e@|NBIGS=v4Wv5U``Ai}BaX?>yV z25jT=u6mu)56~AUfAinUYyFUKEfTnW55zhBtN^naZEI;teO5k{n!G4DP!u8OQvY|bvaWQn#JnSj{8 zp*mB#+_c|ws=&&oyFG|#6)qq|Tj^&6S&=WUF;E_{zj);G!=P&+kLI?Ym~D1 zi#2I2nr*KxbM#l(?~3R#o;cUtzU!m76bys4!ZAIoTYxwObmO0s*!6x2vHOI#ZJMj8 zH5XmZ0lDAbye!9p`mcWaqq`;}&ztJA$EjYa#3lv$^32iv+r0}-3un|5caA_Svp*N|a;xlZXIaZGk1wSgqto+M zn_J?G$X^E11FX6hZo#yU0=A=4s9i!U@zPqTsZS|!m$N-fE zx&i^YIc}7ma(5FdY)~pAn+-+#u)3=DGd*cw`O(es1&SCK%7AI|7wZQo#H`i~^W7>O zGwaOle7AemIq8LVPc}hJX9a|(t#0ImloQCkDw^3u=Q!N^J$=U%eN|wW-)(N`W7gtH zUB(<(AU&5YZMxW9hq%9i%%rN5h-)o;S6;ilwg~dN!<#AtaeoITzj;4@{edu2_Ya5E zuf|F#y2gWA8qtjA!lx%>`2neAh)G{IUoYpIuUYIR+_DaHgtx7QSTEgsq&xrGZC4v+ zGr2N`RRcUL$h;vu?Ic_IgRDoCUXwq=%|Dt)h}4u+t6s>bA!@_xXxQFZO)!mfPSkZ* zb&ucv{P-hQreCoBtpBJyw3fz7_PD;{(lXUaE+1Mvl+#6BS9LKIx1-7K%8(|BbQ9Tv z9BFy%ps%|y3VEV;pI|N?&xlVgboR00q7=nn1fCH_DB^YWJ}L$P^q$&@xuZ7S2gVwT zwqY`NtnK(;Fo>;`K4%O9JLa&wjP0K9jAUiiR}|lQ)l`6f#nyQy{h&T)J@&&Qb?bAi zSV{L{eqOhOPlsh8gc)V!?rr+u={)>xuus zHD{>&e<_H+@YiY9yzAi{8~L4j%kj`B#bzROzQV*eI9+#UKWvEGU<^{FWbOM7G~!V5 zdL}}7CWr1!>Y%;?6iqp-({XkvQ~!27lj9Ny94&E|7r6?rx4|i_WgX1U2wij~z8`Mc z^ND4VoWce>sE_iC{MSx7rKAY^FeT_N^4d2VzuBf&q{fJDhiJpgEbhAYO(>6+@YHqFlb@1CZGIjYnUw~C1o3i$~Y ze^Wcjtq9~Qk7qI++SbrK#g`0eqnGs+IeyH-@j3dqECv62$3jHpt@48r=`)hRgd>G!GW@p4lkjSMa^msYG{sJ0<9*rq7cbb)$U)V0KeN;H1!{$EPN$D8t$xO z9*$&LMJR3>U{s$oq-4YF?`flOgF-SkBM?JRXz~4{>(b#Ns0w?Jf)%HZY(v!lbh^49 zt$~M~Z_M||JR-F8taKM!5%QGuaVh(+?X)o+9ZIb0AYz##sleOG93^fOtP~yF_b)HA zpsrp>W8{LZRZ6z?#JV#BJ|KSVq`m^FI>P2q>du@01)G~vZ9W3o82HADSX3s7!x5e% zP;eeR8Pw%8z2$H;K18lm5{vD7pu<^kcuerp@<1#9=i%_89)7!wkWgFkVwr?7GFo8* z`>vD>%Zb5TD$Xp@l*dX|JuIYT?%1qc{th@lCM=fJV*P+l>5w{KjzgEm!q5JjE{2GR zrT#_km{;iC?w8Yu!22u{JSV!_{#Fxw?ok7876W~g9@M9Ire{Y%J3l;Y!KjG%S|7_4iL?oKqi~)(S>UROoX9n86)G<%DvaBc1qEe>N^B&Sa4}!j%i^I3JrG~g~7fbY@ z5KStNjqSD%TJfdNC3E@=BzCUMss}vhO&u?XX(8UUF0#i^#}84jHsTLQR&e6F#(hx2 zkI=A9gGP-p+)&9EY<)}IUI{h++Nthyp!6vH$>tzz!s{Y=<{}*^A$T#(u>>|Z^+QDV z4TSwSmL#|2M`bF*w&)=AdzEi!G5@UF;d4hC1`=zBy329FX(lXvwZ<26SO(;OMF#Q} z8m8k$-WCyO$q4{h5&YeS*j?p1Z-tS=#evh`ZE8XP@k_JqI+Ub-n-&vt*%jP0xd2ky zvH+WR$(Ig`nv#!L-p&o2twPO7EzOaLQeBd_n8JQk2<(31V;kg6X|`iPbsQHkL$zUQ zGopr))6vDN`tF0ef`FLVVQszK7`r?$PG;btoXY8in9u~SM`cj2w&4#k+uYj&4r+u| z%mZVfhZ!IPE7u0l@U}MWEJ;!P6H4Q;g&n_1?gwf*dtfda51gmT7St72pe&F6Rwn8} zJKyuboDJ|vrcg1JDH)fd1<9F#(17cOohu&$L0oI>+wKsWTC@K2F;9BfwvNI(q9A9~ z;vm+q)X%Ah=j>c<894Z%PT}7nyxb~l9HLo(C)6_wq{QxVz|Lt;J4Ic=IWd$>;3#!GvvglmJ z6rcXhtIp5?9u0oklY)0vfARYMbh#GWch@No&J_=#Y=sCJWjdqHpFI=UB-X}T#CVpj&4N6 zk4*_Xbm*R!B+rD{WSwvT;+%r=MTQR7{_q`g(04U!r2n)BS9;@h{+eB-GC(0vsk=tazYHtM@+ZZx4%g zQPZ%3st!Io!%D23Inuu@_{7RhC(AM%768pr@#11B8(0CFr!&TVmG&Eoh&(O^HWALE zYG_RizVOpvh4Qi&oj6?ocb(V%E4D)gb&(XnVIRN4sKfe-tyY?BDEZ?(5_OPqurkf+ zGa4R60igWO9d?Q`*ruI}gtx_tUL$uhTWRaIasNm^H!ct9&TrgRKQ*nz@SXd)2qOZ{ISBOILu?MRB8ppvWhu44^j}k2IR=a z%oduXWoL1ZNB!K!QGEtyZdV4y-6fSCVy%%BzoB3*pB-kmO1aeRs7sP0AGyB5Wn!5J zy!F^8qFRj@r*=X9Cutn#lINwnF3t91*v9V6nR=Ky(r3D7bG!PjTv?wA9GqhNIhOxMz^I_b zu?<|eo>%$>g1gjFpw)c;j2fqp9Z99|w0s++zlg&d?>!Fn7b5@!zGj}tEu8IkX>N~X zAB<0{Q>R&Q8*DBTBu#5HqDjv#;<0DmCdCh8Ijj46IHaKd_=(*fcNcx*s_P!^p53&F z&bl=O``E(cKoOFnGgxJFxZ1Ok!TG40{tT#C@q(HR^e`vqTTaxNX{k}=# z^(QAI7DM#6{_nyLiMI4D?4llLS7%lMwps&T ze&4)eu{EKw~nVMvLCE|-??Gti7f2`^{MLVv29jNgsxWal98hM`c|pm#)_ z+#Gg_6>>SYplZVf1qw3m7;uiKP&2?7eLf)*POMLpfHAFrtNI3R-yJpa9fAZ!d@z9h zK%-;Y_NWPi*mo}P$*&4^H2hkMhpk?rOYnnOwaqu{poh{{cE`5who}h{si-qJcq*p{ z@^#&l#y&NB*_+XxTDSSWA2l`Uz}e2n#jzRL#qX-in-K>YqYk#d-)j-_4beeq} z8HL&bhd1&?130&Tz|2;h!M?!(eDRrD((g}@rL`S4xgOVDfCWFj^sA2+Fz09d*5S2G zw0&@2*KNCNII|qGyCLd<@1K?m&(&_iui2kt&Z3g#fI_KCLeTF?ZbwE0$wRYgq5IOX!@=;xzwsY7%6q(CM3~%9` zbts{5U%U)(Y>$nHXb-PQomkm7M^3Ycmc#7yY$6J9137e2gRODlT2e`21=St* zRLLJ(ynq2dM~LRFyD$(rWdk+k;AHwBrj1|B!funhE1D5So_#X>I+JbGbY-(aRO!}? zgOm63?k=VPsC6EZu?e4)7tJ!`sE->&_h7-pO(| zjvU_mUxYqI>WnB}%FyTMb=h~d3_~~49SwXa%iTO%2MF4WTET$K> z_Tn2f=4`gQ&mdVEEh}A&>A2TQ#$!yx=Iz6KA>Ho-s`?DRjm7fa)xBV_L6LE`<bQpiFQV!gopI%fH*w5GCM6noHPRsd&bq?8aJf5 z{`pCih{*8jz~%7AOvk_XV4Qyz2e?1Ufz^#DviazrQLEjSl+?_SelIQl%goYknO92d zV{fPmEgEz>a1^!PtdSz?!DTt(Id~q@9njS9Gtngpt9-)sQt9hf*)EC8b0(W7DZ6?` zekZH#d8B(4me{omw9=`42=WiidW_O8>LmGb>}Xt^&?T?vL1WHfAYNsP7lV%X49Rd zE#9{Ue5pSY{(T37w9kW9TClkT-J3(W5>*w8~-xiMMou2P7qsd>5 zPy+ly!ti-Sxxw$-$YKTOmB@ea#b~fo%C=F z4kh+sALcSOQgUg4uUpwo1x$E)hmsdfb}0I)amP0dho&LG4gDACl8=;loj#MLdNA^jFF!?g5XBTGXb-r z97_?=wD*`%-WNq$3j>?iEB$ctK zoLuMa9bhapwu&-)H9%71zY2GGi;-fhifd7wW{+a%1N7NrN>i1RvMDVP_NZ&U195Mz z6D}lpm1yV?p2Vq*-ax@-hAMx~tcOMGFscM-2jb94Y z2!;%+O|us={Jj>Jf_N{<|D>6%aP|&74tSCa%xnu)ThdmquT=vS&7<~0@j-vWb|qG* X5_)J=A58xJn+`(LK;w&=?YsX2HuGA3 literal 0 HcmV?d00001 diff --git a/kinit-admin/src/assets/svgs/403.svg b/kinit-admin/src/assets/svgs/403.svg new file mode 100644 index 0000000..4500596 --- /dev/null +++ b/kinit-admin/src/assets/svgs/403.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/404.svg b/kinit-admin/src/assets/svgs/404.svg new file mode 100644 index 0000000..5244d8d --- /dev/null +++ b/kinit-admin/src/assets/svgs/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/500.svg b/kinit-admin/src/assets/svgs/500.svg new file mode 100644 index 0000000..9c02092 --- /dev/null +++ b/kinit-admin/src/assets/svgs/500.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/icon.svg b/kinit-admin/src/assets/svgs/icon.svg new file mode 100644 index 0000000..7024bec --- /dev/null +++ b/kinit-admin/src/assets/svgs/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/login-bg.svg b/kinit-admin/src/assets/svgs/login-bg.svg new file mode 100644 index 0000000..bbe06c1 --- /dev/null +++ b/kinit-admin/src/assets/svgs/login-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/login-box-bg.svg b/kinit-admin/src/assets/svgs/login-box-bg.svg new file mode 100644 index 0000000..ab10040 --- /dev/null +++ b/kinit-admin/src/assets/svgs/login-box-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/message.svg b/kinit-admin/src/assets/svgs/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/kinit-admin/src/assets/svgs/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/money.svg b/kinit-admin/src/assets/svgs/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/kinit-admin/src/assets/svgs/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/peoples.svg b/kinit-admin/src/assets/svgs/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/kinit-admin/src/assets/svgs/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/assets/svgs/shopping.svg b/kinit-admin/src/assets/svgs/shopping.svg new file mode 100644 index 0000000..f395bc7 --- /dev/null +++ b/kinit-admin/src/assets/svgs/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kinit-admin/src/components/Backtop/index.ts b/kinit-admin/src/components/Backtop/index.ts new file mode 100644 index 0000000..96de88d --- /dev/null +++ b/kinit-admin/src/components/Backtop/index.ts @@ -0,0 +1,3 @@ +import Backtop from './src/Backtop.vue' + +export { Backtop } diff --git a/kinit-admin/src/components/Backtop/src/Backtop.vue b/kinit-admin/src/components/Backtop/src/Backtop.vue new file mode 100644 index 0000000..f5c2fc4 --- /dev/null +++ b/kinit-admin/src/components/Backtop/src/Backtop.vue @@ -0,0 +1,15 @@ + + + diff --git a/kinit-admin/src/components/Breadcrumb/index.ts b/kinit-admin/src/components/Breadcrumb/index.ts new file mode 100644 index 0000000..93ffe70 --- /dev/null +++ b/kinit-admin/src/components/Breadcrumb/index.ts @@ -0,0 +1,3 @@ +import Breadcrumb from './src/Breadcrumb.vue' + +export { Breadcrumb } diff --git a/kinit-admin/src/components/Breadcrumb/src/Breadcrumb.vue b/kinit-admin/src/components/Breadcrumb/src/Breadcrumb.vue new file mode 100644 index 0000000..bbbbc19 --- /dev/null +++ b/kinit-admin/src/components/Breadcrumb/src/Breadcrumb.vue @@ -0,0 +1,127 @@ + + + diff --git a/kinit-admin/src/components/Breadcrumb/src/helper.ts b/kinit-admin/src/components/Breadcrumb/src/helper.ts new file mode 100644 index 0000000..fb3ec19 --- /dev/null +++ b/kinit-admin/src/components/Breadcrumb/src/helper.ts @@ -0,0 +1,31 @@ +import { pathResolve } from '@/utils/routerHelper' +import type { RouteMeta } from 'vue-router' + +export const filterBreadcrumb = ( + routes: AppRouteRecordRaw[], + parentPath = '' +): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + + for (const route of routes) { + const meta = route?.meta as RouteMeta + if (meta.hidden && !meta.canTo) { + continue + } + + const data: AppRouteRecordRaw = + !meta.alwaysShow && route.children?.length === 1 + ? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) } + : { ...route } + + data.path = pathResolve(parentPath, data.path) + + if (data.children) { + data.children = filterBreadcrumb(data.children, data.path) + } + if (data) { + res.push(data) + } + } + return res +} diff --git a/kinit-admin/src/components/Collapse/index.ts b/kinit-admin/src/components/Collapse/index.ts new file mode 100644 index 0000000..73f65a3 --- /dev/null +++ b/kinit-admin/src/components/Collapse/index.ts @@ -0,0 +1,3 @@ +import Collapse from './src/Collapse.vue' + +export { Collapse } diff --git a/kinit-admin/src/components/Collapse/src/Collapse.vue b/kinit-admin/src/components/Collapse/src/Collapse.vue new file mode 100644 index 0000000..f397af5 --- /dev/null +++ b/kinit-admin/src/components/Collapse/src/Collapse.vue @@ -0,0 +1,35 @@ + + + diff --git a/kinit-admin/src/components/ConfigGlobal/index.ts b/kinit-admin/src/components/ConfigGlobal/index.ts new file mode 100644 index 0000000..dda2462 --- /dev/null +++ b/kinit-admin/src/components/ConfigGlobal/index.ts @@ -0,0 +1,3 @@ +import ConfigGlobal from './src/ConfigGlobal.vue' + +export { ConfigGlobal } diff --git a/kinit-admin/src/components/ConfigGlobal/src/ConfigGlobal.vue b/kinit-admin/src/components/ConfigGlobal/src/ConfigGlobal.vue new file mode 100644 index 0000000..985d5df --- /dev/null +++ b/kinit-admin/src/components/ConfigGlobal/src/ConfigGlobal.vue @@ -0,0 +1,62 @@ + + + diff --git a/kinit-admin/src/components/ContentDetailWrap/index.ts b/kinit-admin/src/components/ContentDetailWrap/index.ts new file mode 100644 index 0000000..1871cac --- /dev/null +++ b/kinit-admin/src/components/ContentDetailWrap/index.ts @@ -0,0 +1,3 @@ +import ContentDetailWrap from './src/ContentDetailWrap.vue' + +export { ContentDetailWrap } diff --git a/kinit-admin/src/components/ContentDetailWrap/src/ContentDetailWrap.vue b/kinit-admin/src/components/ContentDetailWrap/src/ContentDetailWrap.vue new file mode 100644 index 0000000..22aeb83 --- /dev/null +++ b/kinit-admin/src/components/ContentDetailWrap/src/ContentDetailWrap.vue @@ -0,0 +1,59 @@ + + + diff --git a/kinit-admin/src/components/ContentWrap/index.ts b/kinit-admin/src/components/ContentWrap/index.ts new file mode 100644 index 0000000..8c22cc8 --- /dev/null +++ b/kinit-admin/src/components/ContentWrap/index.ts @@ -0,0 +1,3 @@ +import ContentWrap from './src/ContentWrap.vue' + +export { ContentWrap } diff --git a/kinit-admin/src/components/ContentWrap/src/ContentWrap.vue b/kinit-admin/src/components/ContentWrap/src/ContentWrap.vue new file mode 100644 index 0000000..4538fc0 --- /dev/null +++ b/kinit-admin/src/components/ContentWrap/src/ContentWrap.vue @@ -0,0 +1,33 @@ + + + diff --git a/kinit-admin/src/components/ContextMenu/index.ts b/kinit-admin/src/components/ContextMenu/index.ts new file mode 100644 index 0000000..2a7c1f0 --- /dev/null +++ b/kinit-admin/src/components/ContextMenu/index.ts @@ -0,0 +1,10 @@ +import ContextMenu from './src/ContextMenu.vue' +import { ElDropdown } from 'element-plus' +import type { RouteLocationNormalizedLoaded } from 'vue-router' + +export interface ContextMenuExpose { + elDropdownMenuRef: ComponentRef + tagItem: RouteLocationNormalizedLoaded +} + +export { ContextMenu } diff --git a/kinit-admin/src/components/ContextMenu/src/ContextMenu.vue b/kinit-admin/src/components/ContextMenu/src/ContextMenu.vue new file mode 100644 index 0000000..5007121 --- /dev/null +++ b/kinit-admin/src/components/ContextMenu/src/ContextMenu.vue @@ -0,0 +1,72 @@ + + + diff --git a/kinit-admin/src/components/CountTo/index.ts b/kinit-admin/src/components/CountTo/index.ts new file mode 100644 index 0000000..2119f02 --- /dev/null +++ b/kinit-admin/src/components/CountTo/index.ts @@ -0,0 +1,3 @@ +import CountTo from './src/CountTo.vue' + +export { CountTo } diff --git a/kinit-admin/src/components/CountTo/src/CountTo.vue b/kinit-admin/src/components/CountTo/src/CountTo.vue new file mode 100644 index 0000000..cab8ea9 --- /dev/null +++ b/kinit-admin/src/components/CountTo/src/CountTo.vue @@ -0,0 +1,180 @@ + + + diff --git a/kinit-admin/src/components/Descriptions/index.ts b/kinit-admin/src/components/Descriptions/index.ts new file mode 100644 index 0000000..91b0eb4 --- /dev/null +++ b/kinit-admin/src/components/Descriptions/index.ts @@ -0,0 +1,3 @@ +import Descriptions from './src/Descriptions.vue' + +export { Descriptions } diff --git a/kinit-admin/src/components/Descriptions/src/Descriptions.vue b/kinit-admin/src/components/Descriptions/src/Descriptions.vue new file mode 100644 index 0000000..c59d74a --- /dev/null +++ b/kinit-admin/src/components/Descriptions/src/Descriptions.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/kinit-admin/src/components/Dialog/index.ts b/kinit-admin/src/components/Dialog/index.ts new file mode 100644 index 0000000..1655dad --- /dev/null +++ b/kinit-admin/src/components/Dialog/index.ts @@ -0,0 +1,3 @@ +import Dialog from './src/Dialog.vue' + +export { Dialog } diff --git a/kinit-admin/src/components/Dialog/src/Dialog.vue b/kinit-admin/src/components/Dialog/src/Dialog.vue new file mode 100644 index 0000000..4e030ad --- /dev/null +++ b/kinit-admin/src/components/Dialog/src/Dialog.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/kinit-admin/src/components/Echart/index.ts b/kinit-admin/src/components/Echart/index.ts new file mode 100644 index 0000000..4822092 --- /dev/null +++ b/kinit-admin/src/components/Echart/index.ts @@ -0,0 +1,3 @@ +import Echart from './src/Echart.vue' + +export { Echart } diff --git a/kinit-admin/src/components/Echart/src/Echart.vue b/kinit-admin/src/components/Echart/src/Echart.vue new file mode 100644 index 0000000..84f23da --- /dev/null +++ b/kinit-admin/src/components/Echart/src/Echart.vue @@ -0,0 +1,113 @@ + + + diff --git a/kinit-admin/src/components/Editor/index.ts b/kinit-admin/src/components/Editor/index.ts new file mode 100644 index 0000000..3fbf0a9 --- /dev/null +++ b/kinit-admin/src/components/Editor/index.ts @@ -0,0 +1,8 @@ +import Editor from './src/Editor.vue' +import { IDomEditor } from '@wangeditor/editor' + +export interface EditorExpose { + getEditorRef: () => Promise +} + +export { Editor } diff --git a/kinit-admin/src/components/Editor/src/Editor.vue b/kinit-admin/src/components/Editor/src/Editor.vue new file mode 100644 index 0000000..9254803 --- /dev/null +++ b/kinit-admin/src/components/Editor/src/Editor.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/kinit-admin/src/components/Error/index.ts b/kinit-admin/src/components/Error/index.ts new file mode 100644 index 0000000..a52c6f9 --- /dev/null +++ b/kinit-admin/src/components/Error/index.ts @@ -0,0 +1,3 @@ +import Error from './src/Error.vue' + +export { Error } diff --git a/kinit-admin/src/components/Error/src/Error.vue b/kinit-admin/src/components/Error/src/Error.vue new file mode 100644 index 0000000..8676a81 --- /dev/null +++ b/kinit-admin/src/components/Error/src/Error.vue @@ -0,0 +1,58 @@ + + + diff --git a/kinit-admin/src/components/Footer/index.ts b/kinit-admin/src/components/Footer/index.ts new file mode 100644 index 0000000..bd052e0 --- /dev/null +++ b/kinit-admin/src/components/Footer/index.ts @@ -0,0 +1,3 @@ +import Footer from './src/Footer.vue' + +export { Footer } diff --git a/kinit-admin/src/components/Footer/src/Footer.vue b/kinit-admin/src/components/Footer/src/Footer.vue new file mode 100644 index 0000000..e7a778d --- /dev/null +++ b/kinit-admin/src/components/Footer/src/Footer.vue @@ -0,0 +1,22 @@ + + + diff --git a/kinit-admin/src/components/Form/index.ts b/kinit-admin/src/components/Form/index.ts new file mode 100644 index 0000000..a9c5efc --- /dev/null +++ b/kinit-admin/src/components/Form/index.ts @@ -0,0 +1,14 @@ +import Form from './src/Form.vue' +import { ElForm } from 'element-plus' + +export interface FormExpose { + setValues: (data: Recordable) => void + setProps: (props: Recordable) => void + delSchema: (field: string) => void + addSchema: (formSchema: FormSchema, index?: number) => void + setSchema: (schemaProps: FormSetPropsType[]) => void + formModel: Recordable + getElFormRef: () => ComponentRef +} + +export { Form } diff --git a/kinit-admin/src/components/Form/src/Form.vue b/kinit-admin/src/components/Form/src/Form.vue new file mode 100644 index 0000000..fee05df --- /dev/null +++ b/kinit-admin/src/components/Form/src/Form.vue @@ -0,0 +1,299 @@ + + + diff --git a/kinit-admin/src/components/Form/src/componentMap.ts b/kinit-admin/src/components/Form/src/componentMap.ts new file mode 100644 index 0000000..822f64d --- /dev/null +++ b/kinit-admin/src/components/Form/src/componentMap.ts @@ -0,0 +1,48 @@ +import type { Component } from 'vue' +import { + ElCascader, + ElCheckboxGroup, + ElColorPicker, + ElDatePicker, + ElInput, + ElInputNumber, + ElRadioGroup, + ElRate, + ElSelect, + ElSelectV2, + ElSlider, + ElSwitch, + ElTimePicker, + ElTimeSelect, + ElTransfer, + ElAutocomplete, + ElDivider +} from 'element-plus' +import { InputPassword } from '@/components/InputPassword' +import { Editor } from '@/components/Editor' + +const componentMap: Recordable = { + Radio: ElRadioGroup, + Checkbox: ElCheckboxGroup, + CheckboxButton: ElCheckboxGroup, + Input: ElInput, + Autocomplete: ElAutocomplete, + InputNumber: ElInputNumber, + Select: ElSelect, + Cascader: ElCascader, + Switch: ElSwitch, + Slider: ElSlider, + TimePicker: ElTimePicker, + DatePicker: ElDatePicker, + Rate: ElRate, + ColorPicker: ElColorPicker, + Transfer: ElTransfer, + Divider: ElDivider, + TimeSelect: ElTimeSelect, + SelectV2: ElSelectV2, + RadioButton: ElRadioGroup, + InputPassword: InputPassword, + Editor: Editor +} + +export { componentMap } diff --git a/kinit-admin/src/components/Form/src/components/useRenderCheckbox.tsx b/kinit-admin/src/components/Form/src/components/useRenderCheckbox.tsx new file mode 100644 index 0000000..26ac32e --- /dev/null +++ b/kinit-admin/src/components/Form/src/components/useRenderCheckbox.tsx @@ -0,0 +1,20 @@ +import { ElCheckbox, ElCheckboxButton } from 'element-plus' +import { defineComponent } from 'vue' + +export const useRenderCheckbox = () => { + const renderChcekboxOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + const valueAlias = item?.componentProps?.optionsAlias?.valueField + const Com = (item.component === 'Checkbox' ? ElCheckbox : ElCheckboxButton) as ReturnType< + typeof defineComponent + > + return item?.componentProps?.options?.map((option) => { + return {option[valueAlias || 'label']} + }) + } + + return { + renderChcekboxOptions + } +} diff --git a/kinit-admin/src/components/Form/src/components/useRenderRadio.tsx b/kinit-admin/src/components/Form/src/components/useRenderRadio.tsx new file mode 100644 index 0000000..98a4778 --- /dev/null +++ b/kinit-admin/src/components/Form/src/components/useRenderRadio.tsx @@ -0,0 +1,20 @@ +import { ElRadio, ElRadioButton } from 'element-plus' +import { defineComponent } from 'vue' + +export const useRenderRadio = () => { + const renderRadioOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + const valueAlias = item?.componentProps?.optionsAlias?.valueField + const Com = (item.component === 'Radio' ? ElRadio : ElRadioButton) as ReturnType< + typeof defineComponent + > + return item?.componentProps?.options?.map((option) => { + return {option[valueAlias || 'label']} + }) + } + + return { + renderRadioOptions + } +} diff --git a/kinit-admin/src/components/Form/src/components/useRenderSelect.tsx b/kinit-admin/src/components/Form/src/components/useRenderSelect.tsx new file mode 100644 index 0000000..13793f5 --- /dev/null +++ b/kinit-admin/src/components/Form/src/components/useRenderSelect.tsx @@ -0,0 +1,48 @@ +import { ElOption, ElOptionGroup } from 'element-plus' +import { getSlot } from '@/utils/tsxHelper' +import { Slots } from 'vue' + +export const useRenderSelect = (slots: Slots) => { + // 渲染 select options + const renderSelectOptions = (item: FormSchema) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + return item?.componentProps?.options?.map((option) => { + if (option?.options?.length) { + return ( + + {() => { + return option?.options?.map((v) => { + return renderSelectOptionItem(item, v) + }) + }} + + ) + } else { + return renderSelectOptionItem(item, option) + } + }) + } + + // 渲染 select option item + const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => { + // 如果有别名,就取别名 + const labelAlias = item?.componentProps?.optionsAlias?.labelField + const valueAlias = item?.componentProps?.optionsAlias?.valueField + return ( + + {{ + default: () => + // option 插槽名规则,{field}-option + item?.componentProps?.optionsSlot + ? getSlot(slots, `${item.field}-option`, { item: option }) + : undefined + }} + + ) + } + + return { + renderSelectOptions + } +} diff --git a/kinit-admin/src/components/Form/src/helper.ts b/kinit-admin/src/components/Form/src/helper.ts new file mode 100644 index 0000000..fbd7959 --- /dev/null +++ b/kinit-admin/src/components/Form/src/helper.ts @@ -0,0 +1,148 @@ +import { useI18n } from '@/hooks/web/useI18n' +import type { Slots } from 'vue' +import { getSlot } from '@/utils/tsxHelper' +import { PlaceholderMoel } from './types' + +const { t } = useI18n() + +/** + * + * @param schema 对应组件数据 + * @returns 返回提示信息对象 + * @description 用于自动设置placeholder + */ +export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => { + const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword'] + const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect'] + if (textMap.includes(schema?.component as string)) { + return { + placeholder: t('common.inputText') + } + } + if (selectMap.includes(schema?.component as string)) { + // 一些范围选择器 + const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange'] + if ( + twoTextMap.includes( + (schema?.componentProps?.type || schema?.componentProps?.isRange) as string + ) + ) { + return { + startPlaceholder: t('common.startTimeText'), + endPlaceholder: t('common.endTimeText'), + rangeSeparator: '-' + } + } else { + return { + placeholder: t('common.selectText') + } + } + } + return {} +} + +/** + * + * @param col 内置栅格 + * @returns 返回栅格属性 + * @description 合并传入进来的栅格属性 + */ +export const setGridProp = (col: ColProps = {}): ColProps => { + const colProps: ColProps = { + // 如果有span,代表用户优先级更高,所以不需要默认栅格 + ...(col.span + ? {} + : { + xs: 24, + sm: 12, + md: 12, + lg: 12, + xl: 12 + }), + ...col + } + return colProps +} + +/** + * + * @param item 传入的组件属性 + * @returns 默认添加 clearable 属性 + */ +export const setComponentProps = (item: FormSchema): Recordable => { + const notNeedClearable = ['ColorPicker'] + const componentProps: Recordable = notNeedClearable.includes(item.component as string) + ? { ...item.componentProps } + : { + clearable: true, + ...item.componentProps + } + // 需要删除额外的属性 + delete componentProps?.slots + return componentProps +} + +/** + * + * @param slots 插槽 + * @param slotsProps 插槽属性 + * @param field 字段名 + */ +export const setItemComponentSlots = ( + slots: Slots, + slotsProps: Recordable = {}, + field: string +): Recordable => { + const slotObj: Recordable = {} + for (const key in slotsProps) { + if (slotsProps[key]) { + // 由于组件有可能重复,需要有一个唯一的前缀 + slotObj[key] = (data: Recordable) => { + return getSlot(slots, `${field}-${key}`, data) + } + } + } + return slotObj +} + +/** + * + * @param schema Form表单结构化数组 + * @param formModel FormMoel + * @returns FormMoel + * @description 生成对应的formModel + */ +export const initModel = (schema: FormSchema[], formModel: Recordable) => { + const model: Recordable = { ...formModel } + schema.map((v) => { + // 如果是hidden,就删除对应的值 + if (v.hidden) { + delete model[v.field] + } else if (v.component && v.component !== 'Divider') { + const hasField = Reflect.has(model, v.field) + // 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值 + model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : '' + } + }) + return model +} + +/** + * @param slots 插槽 + * @param field 字段名 + * @returns 返回FormIiem插槽 + */ +export const setFormItemSlots = (slots: Slots, field: string): Recordable => { + const slotObj: Recordable = {} + if (slots[`${field}-error`]) { + slotObj['error'] = (data: Recordable) => { + return getSlot(slots, `${field}-error`, data) + } + } + if (slots[`${field}-label`]) { + slotObj['label'] = (data: Recordable) => { + return getSlot(slots, `${field}-label`, data) + } + } + return slotObj +} diff --git a/kinit-admin/src/components/Form/src/types.ts b/kinit-admin/src/components/Form/src/types.ts new file mode 100644 index 0000000..fb650ba --- /dev/null +++ b/kinit-admin/src/components/Form/src/types.ts @@ -0,0 +1,15 @@ +export interface PlaceholderMoel { + placeholder?: string + startPlaceholder?: string + endPlaceholder?: string + rangeSeparator?: string +} + +export type FormProps = { + schema?: FormSchema[] + isCol?: boolean + model?: Recordable + autoSetPlaceholder?: boolean + isCustom?: boolean + labelWidth?: string | number +} & Recordable diff --git a/kinit-admin/src/components/Highlight/index.ts b/kinit-admin/src/components/Highlight/index.ts new file mode 100644 index 0000000..3e2d9ed --- /dev/null +++ b/kinit-admin/src/components/Highlight/index.ts @@ -0,0 +1,3 @@ +import Highlight from './src/Highlight.vue' + +export { Highlight } diff --git a/kinit-admin/src/components/Highlight/src/Highlight.vue b/kinit-admin/src/components/Highlight/src/Highlight.vue new file mode 100644 index 0000000..ef923a9 --- /dev/null +++ b/kinit-admin/src/components/Highlight/src/Highlight.vue @@ -0,0 +1,65 @@ + diff --git a/kinit-admin/src/components/Icon/index.ts b/kinit-admin/src/components/Icon/index.ts new file mode 100644 index 0000000..cf92784 --- /dev/null +++ b/kinit-admin/src/components/Icon/index.ts @@ -0,0 +1,3 @@ +import Icon from './src/Icon.vue' + +export { Icon } diff --git a/kinit-admin/src/components/Icon/src/Icon.vue b/kinit-admin/src/components/Icon/src/Icon.vue new file mode 100644 index 0000000..8c5dded --- /dev/null +++ b/kinit-admin/src/components/Icon/src/Icon.vue @@ -0,0 +1,78 @@ + + + diff --git a/kinit-admin/src/components/ImageViewer/index.ts b/kinit-admin/src/components/ImageViewer/index.ts new file mode 100644 index 0000000..3868135 --- /dev/null +++ b/kinit-admin/src/components/ImageViewer/index.ts @@ -0,0 +1,33 @@ +import ImageViewer from './src/ImageViewer.vue' +import { isClient } from '@/utils/is' +import { createVNode, render, VNode } from 'vue' +import { ImageViewerProps } from './src/types' + +let instance: Nullable = null + +export function createImageViewer(options: ImageViewerProps) { + if (!isClient) return + const { + urlList, + initialIndex = 0, + infinite = true, + hideOnClickModal = false, + appendToBody = false, + zIndex = 2000, + show = true + } = options + + const propsData: Partial = {} + const container = document.createElement('div') + propsData.urlList = urlList + propsData.initialIndex = initialIndex + propsData.infinite = infinite + propsData.hideOnClickModal = hideOnClickModal + propsData.appendToBody = appendToBody + propsData.zIndex = zIndex + propsData.show = show + + document.body.appendChild(container) + instance = createVNode(ImageViewer, propsData) + render(instance, container) +} diff --git a/kinit-admin/src/components/ImageViewer/src/ImageViewer.vue b/kinit-admin/src/components/ImageViewer/src/ImageViewer.vue new file mode 100644 index 0000000..02fdb00 --- /dev/null +++ b/kinit-admin/src/components/ImageViewer/src/ImageViewer.vue @@ -0,0 +1,34 @@ + + + diff --git a/kinit-admin/src/components/ImageViewer/src/types.ts b/kinit-admin/src/components/ImageViewer/src/types.ts new file mode 100644 index 0000000..1932d74 --- /dev/null +++ b/kinit-admin/src/components/ImageViewer/src/types.ts @@ -0,0 +1,9 @@ +export interface ImageViewerProps { + urlList?: string[] + zIndex?: number + initialIndex?: number + infinite?: boolean + hideOnClickModal?: boolean + appendToBody?: boolean + show?: boolean +} diff --git a/kinit-admin/src/components/Infotip/index.ts b/kinit-admin/src/components/Infotip/index.ts new file mode 100644 index 0000000..413fa5f --- /dev/null +++ b/kinit-admin/src/components/Infotip/index.ts @@ -0,0 +1,3 @@ +import Infotip from './src/Infotip.vue' + +export { Infotip } diff --git a/kinit-admin/src/components/Infotip/src/Infotip.vue b/kinit-admin/src/components/Infotip/src/Infotip.vue new file mode 100644 index 0000000..776341d --- /dev/null +++ b/kinit-admin/src/components/Infotip/src/Infotip.vue @@ -0,0 +1,52 @@ + + + diff --git a/kinit-admin/src/components/InputPassword/index.ts b/kinit-admin/src/components/InputPassword/index.ts new file mode 100644 index 0000000..1dcc38e --- /dev/null +++ b/kinit-admin/src/components/InputPassword/index.ts @@ -0,0 +1,3 @@ +import InputPassword from './src/InputPassword.vue' + +export { InputPassword } diff --git a/kinit-admin/src/components/InputPassword/src/InputPassword.vue b/kinit-admin/src/components/InputPassword/src/InputPassword.vue new file mode 100644 index 0000000..137ff41 --- /dev/null +++ b/kinit-admin/src/components/InputPassword/src/InputPassword.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/kinit-admin/src/components/LocaleDropdown/index.ts b/kinit-admin/src/components/LocaleDropdown/index.ts new file mode 100644 index 0000000..d02e640 --- /dev/null +++ b/kinit-admin/src/components/LocaleDropdown/index.ts @@ -0,0 +1,3 @@ +import LocaleDropdown from './src/LocaleDropdown.vue' + +export { LocaleDropdown } diff --git a/kinit-admin/src/components/LocaleDropdown/src/LocaleDropdown.vue b/kinit-admin/src/components/LocaleDropdown/src/LocaleDropdown.vue new file mode 100644 index 0000000..9040908 --- /dev/null +++ b/kinit-admin/src/components/LocaleDropdown/src/LocaleDropdown.vue @@ -0,0 +1,52 @@ + + + diff --git a/kinit-admin/src/components/Logo/index.ts b/kinit-admin/src/components/Logo/index.ts new file mode 100644 index 0000000..1c4224c --- /dev/null +++ b/kinit-admin/src/components/Logo/index.ts @@ -0,0 +1,3 @@ +import Logo from './src/Logo.vue' + +export { Logo } diff --git a/kinit-admin/src/components/Logo/src/Logo.vue b/kinit-admin/src/components/Logo/src/Logo.vue new file mode 100644 index 0000000..f7dffe2 --- /dev/null +++ b/kinit-admin/src/components/Logo/src/Logo.vue @@ -0,0 +1,85 @@ + + + diff --git a/kinit-admin/src/components/Menu/index.ts b/kinit-admin/src/components/Menu/index.ts new file mode 100644 index 0000000..a6ec696 --- /dev/null +++ b/kinit-admin/src/components/Menu/index.ts @@ -0,0 +1,3 @@ +import Menu from './src/Menu.vue' + +export { Menu } diff --git a/kinit-admin/src/components/Menu/src/Menu.vue b/kinit-admin/src/components/Menu/src/Menu.vue new file mode 100644 index 0000000..9fc7996 --- /dev/null +++ b/kinit-admin/src/components/Menu/src/Menu.vue @@ -0,0 +1,299 @@ + + + + + diff --git a/kinit-admin/src/components/Menu/src/components/useRenderMenuItem.tsx b/kinit-admin/src/components/Menu/src/components/useRenderMenuItem.tsx new file mode 100644 index 0000000..17a520a --- /dev/null +++ b/kinit-admin/src/components/Menu/src/components/useRenderMenuItem.tsx @@ -0,0 +1,59 @@ +import { ElSubMenu, ElMenuItem } from 'element-plus' +import type { RouteMeta } from 'vue-router' +import { hasOneShowingChild } from '../helper' +import { isUrl } from '@/utils/is' +import { useRenderMenuTitle } from './useRenderMenuTitle' +import { useDesign } from '@/hooks/web/useDesign' +import { pathResolve } from '@/utils/routerHelper' + +export const useRenderMenuItem = ( + // allRouters: AppRouteRecordRaw[] = [], + menuMode: 'vertical' | 'horizontal' +) => { + const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => { + return routers.map((v) => { + const meta = (v.meta ?? {}) as RouteMeta + if (!meta.hidden) { + const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v) + const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath(allRouters, v.path).join('/') + + const { renderMenuTitle } = useRenderMenuTitle() + + if ( + oneShowingChild && + (!onlyOneChild?.children || onlyOneChild?.noShowingChildren) && + !meta?.alwaysShow + ) { + return ( + + {{ + default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta) + }} + + ) + } else { + const { getPrefixCls } = useDesign() + + const preFixCls = getPrefixCls('menu-popper') + return ( + + {{ + title: () => renderMenuTitle(meta), + default: () => renderMenuItem(v.children!, fullPath) + }} + + ) + } + } + }) + } + + return { + renderMenuItem + } +} diff --git a/kinit-admin/src/components/Menu/src/components/useRenderMenuTitle.tsx b/kinit-admin/src/components/Menu/src/components/useRenderMenuTitle.tsx new file mode 100644 index 0000000..6f7caff --- /dev/null +++ b/kinit-admin/src/components/Menu/src/components/useRenderMenuTitle.tsx @@ -0,0 +1,23 @@ +import type { RouteMeta } from 'vue-router' +import { Icon } from '@/components/Icon' +import { useI18n } from '@/hooks/web/useI18n' + +export const useRenderMenuTitle = () => { + const renderMenuTitle = (meta: RouteMeta) => { + const { t } = useI18n() + const { title = 'Please set title', icon } = meta + + return icon ? ( + <> + + {t(title as string)} + + ) : ( + {t(title as string)} + ) + } + + return { + renderMenuTitle + } +} diff --git a/kinit-admin/src/components/Menu/src/helper.ts b/kinit-admin/src/components/Menu/src/helper.ts new file mode 100644 index 0000000..b483881 --- /dev/null +++ b/kinit-admin/src/components/Menu/src/helper.ts @@ -0,0 +1,55 @@ +import type { RouteMeta } from 'vue-router' +import { ref, unref } from 'vue' +import { findPath } from '@/utils/tree' + +type OnlyOneChildType = AppRouteRecordRaw & { noShowingChildren?: boolean } + +interface HasOneShowingChild { + oneShowingChild?: boolean + onlyOneChild?: OnlyOneChildType +} + +export const getAllParentPath = (treeData: T[], path: string) => { + const menuList = findPath(treeData, (n) => n.path === path) as AppRouteRecordRaw[] + return (menuList || []).map((item) => item.path) +} + +export const hasOneShowingChild = ( + children: AppRouteRecordRaw[] = [], + parent: AppRouteRecordRaw +): HasOneShowingChild => { + const onlyOneChild = ref() + + const showingChildren = children.filter((v) => { + const meta = (v.meta ?? {}) as RouteMeta + if (meta.hidden) { + return false + } else { + // Temp set(will be used if only has one showing child) + onlyOneChild.value = v + return true + } + }) + + // When there is only one child router, the child router is displayed by default + if (showingChildren.length === 1) { + return { + oneShowingChild: true, + onlyOneChild: unref(onlyOneChild) + } + } + + // Show parent if there are no child router to display + if (!showingChildren.length) { + onlyOneChild.value = { ...parent, path: '', noShowingChildren: true } + return { + oneShowingChild: true, + onlyOneChild: unref(onlyOneChild) + } + } + + return { + oneShowingChild: false, + onlyOneChild: unref(onlyOneChild) + } +} diff --git a/kinit-admin/src/components/Qrcode/index.ts b/kinit-admin/src/components/Qrcode/index.ts new file mode 100644 index 0000000..ce46161 --- /dev/null +++ b/kinit-admin/src/components/Qrcode/index.ts @@ -0,0 +1,3 @@ +import Qrcode from './src/Qrcode.vue' + +export { Qrcode } diff --git a/kinit-admin/src/components/Qrcode/src/Qrcode.vue b/kinit-admin/src/components/Qrcode/src/Qrcode.vue new file mode 100644 index 0000000..7a6ebf8 --- /dev/null +++ b/kinit-admin/src/components/Qrcode/src/Qrcode.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/kinit-admin/src/components/Screenfull/index.ts b/kinit-admin/src/components/Screenfull/index.ts new file mode 100644 index 0000000..faec2d8 --- /dev/null +++ b/kinit-admin/src/components/Screenfull/index.ts @@ -0,0 +1,3 @@ +import Screenfull from './src/Screenfull.vue' + +export { Screenfull } diff --git a/kinit-admin/src/components/Screenfull/src/Screenfull.vue b/kinit-admin/src/components/Screenfull/src/Screenfull.vue new file mode 100644 index 0000000..32a12e7 --- /dev/null +++ b/kinit-admin/src/components/Screenfull/src/Screenfull.vue @@ -0,0 +1,30 @@ + + + diff --git a/kinit-admin/src/components/Search/index.ts b/kinit-admin/src/components/Search/index.ts new file mode 100644 index 0000000..fcc6f16 --- /dev/null +++ b/kinit-admin/src/components/Search/index.ts @@ -0,0 +1,3 @@ +import Search from './src/Search.vue' + +export { Search } diff --git a/kinit-admin/src/components/Search/src/Search.vue b/kinit-admin/src/components/Search/src/Search.vue new file mode 100644 index 0000000..c548c53 --- /dev/null +++ b/kinit-admin/src/components/Search/src/Search.vue @@ -0,0 +1,139 @@ + + + diff --git a/kinit-admin/src/components/Setting/index.ts b/kinit-admin/src/components/Setting/index.ts new file mode 100644 index 0000000..b64c9ad --- /dev/null +++ b/kinit-admin/src/components/Setting/index.ts @@ -0,0 +1,3 @@ +import Setting from './src/Setting.vue' + +export { Setting } diff --git a/kinit-admin/src/components/Setting/src/Setting.vue b/kinit-admin/src/components/Setting/src/Setting.vue new file mode 100644 index 0000000..fd239f2 --- /dev/null +++ b/kinit-admin/src/components/Setting/src/Setting.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/kinit-admin/src/components/Setting/src/components/ColorRadioPicker.vue b/kinit-admin/src/components/Setting/src/components/ColorRadioPicker.vue new file mode 100644 index 0000000..bc40e10 --- /dev/null +++ b/kinit-admin/src/components/Setting/src/components/ColorRadioPicker.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/kinit-admin/src/components/Setting/src/components/InterfaceDisplay.vue b/kinit-admin/src/components/Setting/src/components/InterfaceDisplay.vue new file mode 100644 index 0000000..8050939 --- /dev/null +++ b/kinit-admin/src/components/Setting/src/components/InterfaceDisplay.vue @@ -0,0 +1,202 @@ + + + diff --git a/kinit-admin/src/components/Setting/src/components/LayoutRadioPicker.vue b/kinit-admin/src/components/Setting/src/components/LayoutRadioPicker.vue new file mode 100644 index 0000000..96b1dbe --- /dev/null +++ b/kinit-admin/src/components/Setting/src/components/LayoutRadioPicker.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/kinit-admin/src/components/SizeDropdown/index.ts b/kinit-admin/src/components/SizeDropdown/index.ts new file mode 100644 index 0000000..516488d --- /dev/null +++ b/kinit-admin/src/components/SizeDropdown/index.ts @@ -0,0 +1,3 @@ +import SizeDropdown from './src/SizeDropdown.vue' + +export { SizeDropdown } diff --git a/kinit-admin/src/components/SizeDropdown/src/SizeDropdown.vue b/kinit-admin/src/components/SizeDropdown/src/SizeDropdown.vue new file mode 100644 index 0000000..773ad62 --- /dev/null +++ b/kinit-admin/src/components/SizeDropdown/src/SizeDropdown.vue @@ -0,0 +1,39 @@ + + + diff --git a/kinit-admin/src/components/Sticky/index.ts b/kinit-admin/src/components/Sticky/index.ts new file mode 100644 index 0000000..5e1de45 --- /dev/null +++ b/kinit-admin/src/components/Sticky/index.ts @@ -0,0 +1,3 @@ +import Sticky from './src/Sticky.vue' + +export { Sticky } diff --git a/kinit-admin/src/components/Sticky/src/Sticky.vue b/kinit-admin/src/components/Sticky/src/Sticky.vue new file mode 100644 index 0000000..6906fbc --- /dev/null +++ b/kinit-admin/src/components/Sticky/src/Sticky.vue @@ -0,0 +1,141 @@ + + diff --git a/kinit-admin/src/components/TabMenu/index.ts b/kinit-admin/src/components/TabMenu/index.ts new file mode 100644 index 0000000..b5fd71c --- /dev/null +++ b/kinit-admin/src/components/TabMenu/index.ts @@ -0,0 +1,3 @@ +import TabMenu from './src/TabMenu.vue' + +export { TabMenu } diff --git a/kinit-admin/src/components/TabMenu/src/TabMenu.vue b/kinit-admin/src/components/TabMenu/src/TabMenu.vue new file mode 100644 index 0000000..061421b --- /dev/null +++ b/kinit-admin/src/components/TabMenu/src/TabMenu.vue @@ -0,0 +1,226 @@ + + + diff --git a/kinit-admin/src/components/TabMenu/src/helper.ts b/kinit-admin/src/components/TabMenu/src/helper.ts new file mode 100644 index 0000000..793f132 --- /dev/null +++ b/kinit-admin/src/components/TabMenu/src/helper.ts @@ -0,0 +1,52 @@ +import { getAllParentPath } from '@/components/Menu/src/helper' +import type { RouteMeta } from 'vue-router' +import { isUrl } from '@/utils/is' +import { cloneDeep } from 'lodash-es' +import { reactive } from 'vue' + +export type TabMapTypes = { + [key: string]: string[] +} + +export const tabPathMap = reactive({}) + +export const initTabMap = (routes: AppRouteRecordRaw[]) => { + for (const v of routes) { + const meta = (v.meta ?? {}) as RouteMeta + if (!meta?.hidden) { + tabPathMap[v.path] = [] + } + } +} + +export const filterMenusPath = ( + routes: AppRouteRecordRaw[], + allRoutes: AppRouteRecordRaw[] +): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + for (const v of routes) { + let data: Nullable = null + const meta = (v.meta ?? {}) as RouteMeta + if (!meta.hidden || meta.canTo) { + const allParentPaht = getAllParentPath(allRoutes, v.path) + + const fullPath = isUrl(v.path) ? v.path : allParentPaht.join('/') + + data = cloneDeep(v) + data.path = fullPath + if (v.children && data) { + data.children = filterMenusPath(v.children, allRoutes) + } + + if (data) { + res.push(data) + } + + if (allParentPaht.length && Reflect.has(tabPathMap, allParentPaht[0])) { + tabPathMap[allParentPaht[0]].push(fullPath) + } + } + } + + return res +} diff --git a/kinit-admin/src/components/Table/index.ts b/kinit-admin/src/components/Table/index.ts new file mode 100644 index 0000000..d734e21 --- /dev/null +++ b/kinit-admin/src/components/Table/index.ts @@ -0,0 +1,11 @@ +import Table from './src/Table.vue' +import { ElTable } from 'element-plus' + +export interface TableExpose { + setProps: (props: Recordable) => void + setColumn: (columnProps: TableSetPropsType[]) => void + selections: Recordable[] + elTableRef: ComponentRef +} + +export { Table } diff --git a/kinit-admin/src/components/Table/src/Table.vue b/kinit-admin/src/components/Table/src/Table.vue new file mode 100644 index 0000000..35905cb --- /dev/null +++ b/kinit-admin/src/components/Table/src/Table.vue @@ -0,0 +1,297 @@ + diff --git a/kinit-admin/src/components/Table/src/helper.ts b/kinit-admin/src/components/Table/src/helper.ts new file mode 100644 index 0000000..d8b34a8 --- /dev/null +++ b/kinit-admin/src/components/Table/src/helper.ts @@ -0,0 +1,8 @@ +export const setIndex = (reserveIndex: boolean, index: number, size: number, current: number) => { + const newIndex = index + 1 + if (reserveIndex) { + return size * (current - 1) + newIndex + } else { + return newIndex + } +} diff --git a/kinit-admin/src/components/Table/src/types.ts b/kinit-admin/src/components/Table/src/types.ts new file mode 100644 index 0000000..58ecd83 --- /dev/null +++ b/kinit-admin/src/components/Table/src/types.ts @@ -0,0 +1,24 @@ +export type TableProps = { + pageSize?: number + currentPage?: number + // 是否多选 + selection?: boolean + // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip, + showOverflowTooltip?: boolean + // 表头 + columns?: TableColumn[] + // 是否展示分页 + pagination?: Pagination | undefined + // 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key) + reserveSelection?: boolean + // 加载状态 + loading?: boolean + // 是否叠加索引 + reserveIndex?: boolean + // 对齐方式 + align?: 'left' | 'center' | 'right' + // 表头对齐方式 + headerAlign?: 'left' | 'center' | 'right' + data?: Recordable + expand?: boolean +} & Recordable diff --git a/kinit-admin/src/components/TagsView/index.ts b/kinit-admin/src/components/TagsView/index.ts new file mode 100644 index 0000000..30e604a --- /dev/null +++ b/kinit-admin/src/components/TagsView/index.ts @@ -0,0 +1,3 @@ +import TagsView from './src/TagsView.vue' + +export { TagsView } diff --git a/kinit-admin/src/components/TagsView/src/TagsView.vue b/kinit-admin/src/components/TagsView/src/TagsView.vue new file mode 100644 index 0000000..94afb8a --- /dev/null +++ b/kinit-admin/src/components/TagsView/src/TagsView.vue @@ -0,0 +1,579 @@ + + + + + diff --git a/kinit-admin/src/components/TagsView/src/helper.ts b/kinit-admin/src/components/TagsView/src/helper.ts new file mode 100644 index 0000000..22f6a50 --- /dev/null +++ b/kinit-admin/src/components/TagsView/src/helper.ts @@ -0,0 +1,21 @@ +import type { RouteMeta, RouteLocationNormalizedLoaded } from 'vue-router' +import { pathResolve } from '@/utils/routerHelper' + +export const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') => { + let tags: RouteLocationNormalizedLoaded[] = [] + routes.forEach((route) => { + const meta = route.meta as RouteMeta + const tagPath = pathResolve(parentPath, route.path) + if (meta?.affix) { + tags.push({ ...route, path: tagPath, fullPath: tagPath } as RouteLocationNormalizedLoaded) + } + if (route.children) { + const tempTags: RouteLocationNormalizedLoaded[] = filterAffixTags(route.children, tagPath) + if (tempTags.length >= 1) { + tags = [...tags, ...tempTags] + } + } + }) + + return tags +} diff --git a/kinit-admin/src/components/ThemeSwitch/index.ts b/kinit-admin/src/components/ThemeSwitch/index.ts new file mode 100644 index 0000000..823a276 --- /dev/null +++ b/kinit-admin/src/components/ThemeSwitch/index.ts @@ -0,0 +1,3 @@ +import ThemeSwitch from './src/ThemeSwitch.vue' + +export { ThemeSwitch } diff --git a/kinit-admin/src/components/ThemeSwitch/src/ThemeSwitch.vue b/kinit-admin/src/components/ThemeSwitch/src/ThemeSwitch.vue new file mode 100644 index 0000000..25221d1 --- /dev/null +++ b/kinit-admin/src/components/ThemeSwitch/src/ThemeSwitch.vue @@ -0,0 +1,41 @@ + + + diff --git a/kinit-admin/src/components/UserInfo/index.ts b/kinit-admin/src/components/UserInfo/index.ts new file mode 100644 index 0000000..c3a34ab --- /dev/null +++ b/kinit-admin/src/components/UserInfo/index.ts @@ -0,0 +1,3 @@ +import UserInfo from './src/UserInfo.vue' + +export { UserInfo } diff --git a/kinit-admin/src/components/UserInfo/src/UserInfo.vue b/kinit-admin/src/components/UserInfo/src/UserInfo.vue new file mode 100644 index 0000000..641fe01 --- /dev/null +++ b/kinit-admin/src/components/UserInfo/src/UserInfo.vue @@ -0,0 +1,67 @@ + + + diff --git a/kinit-admin/src/components/index.ts b/kinit-admin/src/components/index.ts new file mode 100644 index 0000000..4d030c3 --- /dev/null +++ b/kinit-admin/src/components/index.ts @@ -0,0 +1,6 @@ +import type { App } from 'vue' +import { Icon } from './Icon' + +export const setupGlobCom = (app: App): void => { + app.component('Icon', Icon) +} diff --git a/kinit-admin/src/config/app.ts b/kinit-admin/src/config/app.ts new file mode 100644 index 0000000..2c51654 --- /dev/null +++ b/kinit-admin/src/config/app.ts @@ -0,0 +1,106 @@ +import { useCache } from '@/hooks/web/useCache' + +const { wsCache } = useCache() + +export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu' + +export type ThemeTypes = { + elColorPrimary?: string + leftMenuBorderColor?: string + leftMenuBgColor?: string + leftMenuBgLightColor?: string + leftMenuBgActiveColor?: string + leftMenuCollapseBgActiveColor?: string + leftMenuTextColor?: string + leftMenuTextActiveColor?: string + logoTitleTextColor?: string + logoBorderColor?: string + topHeaderBgColor?: string + topHeaderTextColor?: string + topHeaderHoverColor?: string + topToolBorderColor?: string +} +export interface AppState { + breadcrumb: boolean + breadcrumbIcon: boolean + collapse: boolean + uniqueOpened: boolean + hamburger: boolean + screenfull: boolean + size: boolean + locale: boolean + tagsView: boolean + tagsViewIcon: boolean + logo: boolean + fixedHeader: boolean + greyMode: boolean + dynamicRouter: boolean + pageLoading: boolean + layout: LayoutType + title: string + userInfo: string + isDark: boolean + currentSize: ElememtPlusSize + sizeMap: ElememtPlusSize[] + mobile: boolean + footer: boolean + theme: ThemeTypes +} + +export const appModules: AppState = { + userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突 + sizeMap: ['default', 'large', 'small'], + mobile: false, // 是否是移动端 + title: import.meta.env.VITE_APP_TITLE, // 标题 + pageLoading: false, // 路由跳转loading + + breadcrumb: true, // 面包屑 + breadcrumbIcon: true, // 面包屑图标 + collapse: false, // 折叠菜单 + uniqueOpened: false, // 是否只保持一个子菜单的展开 + hamburger: true, // 折叠图标 + screenfull: true, // 全屏图标 + size: true, // 尺寸图标 + locale: true, // 多语言图标 + tagsView: true, // 标签页 + tagsViewIcon: true, // 是否显示标签图标 + logo: true, // logo + fixedHeader: true, // 固定toolheader + footer: true, // 显示页脚 + greyMode: false, // 是否开始灰色模式,用于特殊悼念日 + dynamicRouter: wsCache.get('dynamicRouter') || false, // 是否动态路由 + + layout: wsCache.get('layout') || 'classic', // layout布局 + isDark: wsCache.get('isDark') || false, // 是否是暗黑模式 + currentSize: wsCache.get('default') || 'default', // 组件尺寸 + theme: wsCache.get('theme') || { + // 主题色 + elColorPrimary: '#409eff', + // 左侧菜单边框颜色 + leftMenuBorderColor: 'inherit', + // 左侧菜单背景颜色 + leftMenuBgColor: '#001529', + // 左侧菜单浅色背景颜色 + leftMenuBgLightColor: '#0f2438', + // 左侧菜单选中背景颜色 + leftMenuBgActiveColor: 'var(--el-color-primary)', + // 左侧菜单收起选中背景颜色 + leftMenuCollapseBgActiveColor: 'var(--el-color-primary)', + // 左侧菜单字体颜色 + leftMenuTextColor: '#bfcbd9', + // 左侧菜单选中字体颜色 + leftMenuTextActiveColor: '#fff', + // logo字体颜色 + logoTitleTextColor: '#fff', + // logo边框颜色 + logoBorderColor: 'inherit', + // 头部背景颜色 + topHeaderBgColor: '#fff', + // 头部字体颜色 + topHeaderTextColor: 'inherit', + // 头部悬停颜色 + topHeaderHoverColor: '#f6f6f6', + // 头部边框颜色 + topToolBorderColor: '#eee' + } +} diff --git a/kinit-admin/src/config/axios/config.ts b/kinit-admin/src/config/axios/config.ts new file mode 100644 index 0000000..69c2fc0 --- /dev/null +++ b/kinit-admin/src/config/axios/config.ts @@ -0,0 +1,46 @@ +const config: { + base_url: { + base: string + dev: string + pro: string + test: string + } + result_code: number | string + default_headers: AxiosHeaders + request_timeout: number +} = { + /** + * api请求基础路径 + */ + base_url: { + // 开发环境接口前缀 + base: '', + + // 打包开发环境接口前缀 + dev: '', + + // 打包生产环境接口前缀 + pro: '', + + // 打包测试环境接口前缀 + test: '' + }, + + /** + * 接口成功返回状态码 + */ + result_code: '0000', + + /** + * 接口请求超时时间 + */ + request_timeout: 60000, + + /** + * 默认接口请求类型 + * 可选值:application/x-www-form-urlencoded multipart/form-data + */ + default_headers: 'application/json' +} + +export { config } diff --git a/kinit-admin/src/config/axios/index.ts b/kinit-admin/src/config/axios/index.ts new file mode 100644 index 0000000..ab71a45 --- /dev/null +++ b/kinit-admin/src/config/axios/index.ts @@ -0,0 +1,33 @@ +import { service } from './service' + +import { config } from './config' + +const { default_headers } = config + +const request = (option: any) => { + const { url, method, params, data, headersType, responseType } = option + return service({ + url: url, + method, + params, + data, + responseType: responseType, + headers: { + 'Content-Type': headersType || default_headers + } + }) +} +export default { + get: (option: any) => { + return request({ method: 'get', ...option }) as unknown as T + }, + post: (option: any) => { + return request({ method: 'post', ...option }) as unknown as T + }, + delete: (option: any) => { + return request({ method: 'delete', ...option }) as unknown as T + }, + put: (option: any) => { + return request({ method: 'put', ...option }) as unknown as T + } +} diff --git a/kinit-admin/src/config/axios/service.ts b/kinit-admin/src/config/axios/service.ts new file mode 100644 index 0000000..a3cacd9 --- /dev/null +++ b/kinit-admin/src/config/axios/service.ts @@ -0,0 +1,70 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' + +import qs from 'qs' + +import { config } from './config' + +import { ElMessage } from 'element-plus' + +const { result_code, base_url } = config + +export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH] + +// 创建axios实例 +const service: AxiosInstance = axios.create({ + baseURL: PATH_URL, // api 的 base_url + timeout: config.request_timeout // 请求超时时间 +}) + +// request拦截器 +service.interceptors.request.use( + (config: AxiosRequestConfig) => { + if ( + config.method === 'post' && + (config.headers as any)['Content-Type'] === 'application/x-www-form-urlencoded' + ) { + config.data = qs.stringify(config.data) + } + // get参数编码 + if (config.method === 'get' && config.params) { + let url = config.url as string + url += '?' + const keys = Object.keys(config.params) + for (const key of keys) { + if (config.params[key] !== void 0 && config.params[key] !== null) { + url += `${key}=${encodeURIComponent(config.params[key])}&` + } + } + url = url.substring(0, url.length - 1) + config.params = {} + config.url = url + } + return config + }, + (error: AxiosError) => { + // Do something with request error + console.log(error) // for debug + Promise.reject(error) + } +) + +// response 拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + if (response.config.responseType === 'blob') { + // 如果是文件流,直接过 + return response + } else if (response.data.code === result_code) { + return response.data + } else { + ElMessage.error(response.data.message) + } + }, + (error: AxiosError) => { + console.log('err' + error) // for debug + ElMessage.error(error.message) + return Promise.reject(error) + } +) + +export { service } diff --git a/kinit-admin/src/config/locale.ts b/kinit-admin/src/config/locale.ts new file mode 100644 index 0000000..c20a3d9 --- /dev/null +++ b/kinit-admin/src/config/locale.ts @@ -0,0 +1,32 @@ +import { useCache } from '@/hooks/web/useCache' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import en from 'element-plus/es/locale/lang/en' + +const { wsCache } = useCache() + +export const elLocaleMap = { + 'zh-CN': zhCn, + en: en +} +export interface LocaleState { + currentLocale: LocaleDropdownType + localeMap: LocaleDropdownType[] +} + +export const localeModules: LocaleState = { + currentLocale: { + lang: wsCache.get('lang') || 'zh-CN', + elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN'] + }, + // 多语言 + localeMap: [ + { + lang: 'zh-CN', + name: '简体中文' + }, + { + lang: 'en', + name: 'English' + } + ] +} diff --git a/kinit-admin/src/directives/index.ts b/kinit-admin/src/directives/index.ts new file mode 100644 index 0000000..11b1da8 --- /dev/null +++ b/kinit-admin/src/directives/index.ts @@ -0,0 +1,10 @@ +import type { App } from 'vue' +import { setupPermissionDirective } from './permission/hasPermi' + +/** + * 导出指令:v-xxx + * @methods hasPermi 按钮权限,用法: v-hasPermi + */ +export const setupPermission = (app: App) => { + setupPermissionDirective(app) +} diff --git a/kinit-admin/src/directives/permission/hasPermi.ts b/kinit-admin/src/directives/permission/hasPermi.ts new file mode 100644 index 0000000..0814c99 --- /dev/null +++ b/kinit-admin/src/directives/permission/hasPermi.ts @@ -0,0 +1,47 @@ +import type { App, Directive, DirectiveBinding } from 'vue' +import { useI18n } from '@/hooks/web/useI18n' +import { useCache } from '@/hooks/web/useCache' +import { intersection } from 'lodash-es' +import { isArray } from '@/utils/is' +import { useAppStoreWithOut } from '@/store/modules/app' + +const { t } = useI18n() +const { wsCache } = useCache() +const appStore = useAppStoreWithOut() + +// 全部权限 +const all_permission = ['*.*.*'] +const hasPermission = (value: string | string[]): boolean => { + const permissions = wsCache.get(appStore.getUserInfo).permissions as string[] + if (!value) { + throw new Error(t('permission.hasPermission')) + } + if (!isArray(value)) { + return permissions?.includes(value as string) + } + if (all_permission[0] === permissions[0]) { + return true + } + return (intersection(value, permissions) as string[]).length > 0 +} +function hasPermi(el: Element, binding: DirectiveBinding) { + const value = binding.value + + const flag = hasPermission(value) + if (!flag) { + el.parentNode?.removeChild(el) + } +} +const mounted = (el: Element, binding: DirectiveBinding) => { + hasPermi(el, binding) +} + +const permiDirective: Directive = { + mounted +} + +export const setupPermissionDirective = (app: App) => { + app.directive('hasPermi', permiDirective) +} + +export default permiDirective diff --git a/kinit-admin/src/hooks/event/useScrollTo.ts b/kinit-admin/src/hooks/event/useScrollTo.ts new file mode 100644 index 0000000..74fd673 --- /dev/null +++ b/kinit-admin/src/hooks/event/useScrollTo.ts @@ -0,0 +1,62 @@ +import { ref, unref } from 'vue' + +export interface ScrollToParams { + el: HTMLElement + to: number + position: string + duration?: number + callback?: () => void +} + +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2 + if (t < 1) { + return (c / 2) * t * t + b + } + t-- + return (-c / 2) * (t * (t - 2) - 1) + b +} +const move = (el: HTMLElement, position: string, amount: number) => { + el[position] = amount +} + +export function useScrollTo({ + el, + position = 'scrollLeft', + to, + duration = 500, + callback +}: ScrollToParams) { + const isActiveRef = ref(false) + const start = el[position] + const change = to - start + const increment = 20 + let currentTime = 0 + + function animateScroll() { + if (!unref(isActiveRef)) { + return + } + currentTime += increment + const val = easeInOutQuad(currentTime, start, change, duration) + move(el, position, val) + if (currentTime < duration && unref(isActiveRef)) { + requestAnimationFrame(animateScroll) + } else { + if (callback) { + callback() + } + } + } + + function run() { + isActiveRef.value = true + animateScroll() + } + + function stop() { + isActiveRef.value = false + } + + return { start: run, stop } +} diff --git a/kinit-admin/src/hooks/web/useCache.ts b/kinit-admin/src/hooks/web/useCache.ts new file mode 100644 index 0000000..b405ef3 --- /dev/null +++ b/kinit-admin/src/hooks/web/useCache.ts @@ -0,0 +1,17 @@ +/** + * 配置浏览器本地存储的方式,可直接存储对象数组。 + */ + +import WebStorageCache from 'web-storage-cache' + +type CacheType = 'sessionStorage' | 'localStorage' + +export const useCache = (type: CacheType = 'sessionStorage') => { + const wsCache: WebStorageCache = new WebStorageCache({ + storage: type + }) + + return { + wsCache + } +} diff --git a/kinit-admin/src/hooks/web/useConfigGlobal.ts b/kinit-admin/src/hooks/web/useConfigGlobal.ts new file mode 100644 index 0000000..59007d3 --- /dev/null +++ b/kinit-admin/src/hooks/web/useConfigGlobal.ts @@ -0,0 +1,9 @@ +import { inject } from 'vue' + +export const useConfigGlobal = () => { + const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes + + return { + configGlobal + } +} diff --git a/kinit-admin/src/hooks/web/useCrudSchemas.ts b/kinit-admin/src/hooks/web/useCrudSchemas.ts new file mode 100644 index 0000000..c15dd9c --- /dev/null +++ b/kinit-admin/src/hooks/web/useCrudSchemas.ts @@ -0,0 +1,251 @@ +import { reactive } from 'vue' +import { eachTree, treeMap, filter } from '@/utils/tree' +import { findIndex } from '@/utils' +import { useDictStoreWithOut } from '@/store/modules/dict' +import { useI18n } from '@/hooks/web/useI18n' +import type { AxiosPromise } from 'axios' + +export type CrudSchema = Omit & { + search?: CrudSearchParams + table?: CrudTableParams + form?: CrudFormParams + detail?: CrudDescriptionsParams + children?: CrudSchema[] +} + +type CrudSearchParams = { + // 是否显示在查询项 + show?: boolean + // 字典名称,会去取全局的字典 + dictName?: string + // 接口 + api?: () => Promise +} & Omit + +type CrudTableParams = { + // 是否显示表头 + show?: boolean +} & Omit + +type CrudFormParams = { + // 字典名称,会去取全局的字典 + dictName?: string + // 接口 + api?: () => Promise + // 是否显示表单项 + show?: boolean +} & Omit + +type CrudDescriptionsParams = { + // 是否显示表单项 + show?: boolean +} & Omit + +const dictStore = useDictStoreWithOut() + +const { t } = useI18n() + +interface AllSchemas { + searchSchema: FormSchema[] + tableColumns: TableColumn[] + formSchema: FormSchema[] + detailSchema: DescriptionsSchema[] +} + +// 过滤所有结构 +export const useCrudSchemas = ( + crudSchema: CrudSchema[] +): { + allSchemas: AllSchemas +} => { + // 所有结构数据 + const allSchemas = reactive({ + searchSchema: [], + tableColumns: [], + formSchema: [], + detailSchema: [] + }) + + const searchSchema = filterSearchSchema(crudSchema, allSchemas) + allSchemas.searchSchema = searchSchema || [] + + const tableColumns = filterTableSchema(crudSchema) + allSchemas.tableColumns = tableColumns || [] + + const formSchema = filterFormSchema(crudSchema, allSchemas) + allSchemas.formSchema = formSchema + + const detailSchema = filterDescriptionsSchema(crudSchema) + allSchemas.detailSchema = detailSchema + + return { + allSchemas + } +} + +// 过滤 Search 结构 +const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { + const searchSchema: FormSchema[] = [] + + // 获取字典列表队列 + const searchRequestTask: Array<() => Promise> = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.search?.show) { + const searchSchemaItem = { + // 默认为 input + component: schemaItem.search.component || 'Input', + componentProps: {}, + ...schemaItem.search, + field: schemaItem.field, + label: schemaItem.search?.label || schemaItem.label + } + + if (searchSchemaItem.dictName) { + // 如果有 dictName 则证明是从字典中获取数据 + const dictArr = dictStore.getDictObj[searchSchemaItem.dictName] + searchSchemaItem.componentProps!.options = filterOptions(dictArr) + } else if (searchSchemaItem.api) { + searchRequestTask.push(async () => { + const res = await (searchSchemaItem.api as () => AxiosPromise)() + if (res) { + const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => { + return v.field === searchSchemaItem.field + }) + if (index !== -1) { + allSchemas.searchSchema[index]!.componentProps!.options = filterOptions( + res, + searchSchemaItem.componentProps.optionsAlias?.labelField + ) + } + } + }) + } + + // 删除不必要的字段 + delete searchSchemaItem.show + delete searchSchemaItem.dictName + + searchSchema.push(searchSchemaItem) + } + }) + + for (const task of searchRequestTask) { + task() + } + + return searchSchema +} + +// 过滤 table 结构 +const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => { + const tableColumns = treeMap(crudSchema, { + conversion: (schema: CrudSchema) => { + if (schema?.table?.show !== false) { + return { + ...schema.table, + ...schema + } + } + } + }) + + // 第一次过滤会有 undefined 所以需要二次过滤 + return filter(tableColumns as TableColumn[], (data) => { + if (data.children === void 0) { + delete data.children + } + return !!data.field + }) +} + +// 过滤 form 结构 +const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { + const formSchema: FormSchema[] = [] + + // 获取字典列表队列 + const formRequestTask: Array<() => Promise> = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.form?.show !== false) { + const formSchemaItem = { + // 默认为 input + component: schemaItem?.form?.component || 'Input', + componentProps: {}, + ...schemaItem.form, + field: schemaItem.field, + label: schemaItem.search?.label || schemaItem.label + } + + if (formSchemaItem.dictName) { + // 如果有 dictName 则证明是从字典中获取数据 + const dictArr = dictStore.getDictObj[formSchemaItem.dictName] + formSchemaItem.componentProps!.options = filterOptions(dictArr) + } else if (formSchemaItem.api) { + formRequestTask.push(async () => { + const res = await (formSchemaItem.api as () => AxiosPromise)() + if (res) { + const index = findIndex(allSchemas.formSchema, (v: FormSchema) => { + return v.field === formSchemaItem.field + }) + if (index !== -1) { + allSchemas.formSchema[index]!.componentProps!.options = filterOptions( + res, + formSchemaItem.componentProps.optionsAlias?.labelField + ) + } + } + }) + } + + // 删除不必要的字段 + delete formSchemaItem.show + delete formSchemaItem.dictName + + formSchema.push(formSchemaItem) + } + }) + + for (const task of formRequestTask) { + task() + } + console.log(formSchema) + return formSchema +} + +// 过滤 descriptions 结构 +const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[] => { + const descriptionsSchema: FormSchema[] = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.detail?.show !== false) { + const descriptionsSchemaItem = { + ...schemaItem.detail, + field: schemaItem.field, + label: schemaItem.detail?.label || schemaItem.label + } + + // 删除不必要的字段 + delete descriptionsSchemaItem.show + + descriptionsSchema.push(descriptionsSchemaItem) + } + }) + + return descriptionsSchema +} + +// 给options添加国际化 +const filterOptions = (options: Recordable, labelField?: string) => { + return options.map((v: Recordable) => { + if (labelField) { + v['labelField'] = t(v.labelField) + } else { + v['label'] = t(v.label) + } + return v + }) +} diff --git a/kinit-admin/src/hooks/web/useDesign.ts b/kinit-admin/src/hooks/web/useDesign.ts new file mode 100644 index 0000000..1ec349f --- /dev/null +++ b/kinit-admin/src/hooks/web/useDesign.ts @@ -0,0 +1,18 @@ +import variables from '@/styles/variables.module.less' + +export const useDesign = () => { + const lessVariables = variables + + /** + * @param scope 类名 + * @returns 返回空间名-类名 + */ + const getPrefixCls = (scope: string) => { + return `${lessVariables.namespace}-${scope}` + } + + return { + variables: lessVariables, + getPrefixCls + } +} diff --git a/kinit-admin/src/hooks/web/useEmitt.ts b/kinit-admin/src/hooks/web/useEmitt.ts new file mode 100644 index 0000000..26b5d62 --- /dev/null +++ b/kinit-admin/src/hooks/web/useEmitt.ts @@ -0,0 +1,23 @@ +import mitt from 'mitt' +import { onBeforeUnmount } from 'vue' + +interface Option { + name: string // 事件名称 + callback: Fn // 回调 +} + +const emitter = mitt() + +export const useEmitt = (option?: Option) => { + if (option) { + emitter.on(option.name, option.callback) + + onBeforeUnmount(() => { + emitter.off(option.name) + }) + } + + return { + emitter + } +} diff --git a/kinit-admin/src/hooks/web/useForm.ts b/kinit-admin/src/hooks/web/useForm.ts new file mode 100644 index 0000000..21d2186 --- /dev/null +++ b/kinit-admin/src/hooks/web/useForm.ts @@ -0,0 +1,91 @@ +import type { Form, FormExpose } from '@/components/Form' +import type { ElForm } from 'element-plus' +import { ref, unref, nextTick } from 'vue' +import type { FormProps } from '@/components/Form/src/types' + +export const useForm = (props?: FormProps) => { + // From实例 + const formRef = ref() + + // ElForm实例 + const elFormRef = ref>() + + /** + * @param ref Form实例 + * @param elRef ElForm实例 + */ + const register = (ref: typeof Form & FormExpose, elRef: ComponentRef) => { + formRef.value = ref + elFormRef.value = elRef + } + + const getForm = async () => { + await nextTick() + const form = unref(formRef) + if (!form) { + console.error('The form is not registered. Please use the register method to register') + } + return form + } + + // 一些内置的方法 + const methods: { + setProps: (props: Recordable) => void + setValues: (data: Recordable) => void + getFormData: () => Promise + setSchema: (schemaProps: FormSetPropsType[]) => void + addSchema: (formSchema: FormSchema, index?: number) => void + delSchema: (field: string) => void + } = { + setProps: async (props: FormProps = {}) => { + const form = await getForm() + form?.setProps(props) + }, + + setValues: async (data: Recordable) => { + const form = await getForm() + form?.setValues(data) + }, + + /** + * @param schemaProps 需要设置的schemaProps + */ + setSchema: async (schemaProps: FormSetPropsType[]) => { + const form = await getForm() + form?.setSchema(schemaProps) + }, + + /** + * @param formSchema 需要新增数据 + * @param index 在哪里新增 + */ + addSchema: async (formSchema: FormSchema, index?: number) => { + const form = await getForm() + form?.addSchema(formSchema, index) + }, + + /** + * @param field 删除哪个数据 + */ + delSchema: async (field: string) => { + const form = await getForm() + form?.delSchema(field) + }, + + /** + * @returns form data + */ + getFormData: async (): Promise => { + const form = await getForm() + return form?.formModel as T + } + } + + props && methods.setProps(props) + + return { + register, + elFormRef, + methods + } +} diff --git a/kinit-admin/src/hooks/web/useI18n.ts b/kinit-admin/src/hooks/web/useI18n.ts new file mode 100644 index 0000000..f1e0e7b --- /dev/null +++ b/kinit-admin/src/hooks/web/useI18n.ts @@ -0,0 +1,52 @@ +import { i18n } from '@/plugins/vueI18n' + +type I18nGlobalTranslation = { + (key: string): string + (key: string, locale: string): string + (key: string, locale: string, list: unknown[]): string + (key: string, locale: string, named: Record): string + (key: string, list: unknown[]): string + (key: string, named: Record): string +} + +type I18nTranslationRestParameters = [string, any] + +const getKey = (namespace: string | undefined, key: string) => { + if (!namespace) { + return key + } + if (key.startsWith(namespace)) { + return key + } + return `${namespace}.${key}` +} + +export const useI18n = ( + namespace?: string +): { + t: I18nGlobalTranslation +} => { + const normalFn = { + t: (key: string) => { + return getKey(namespace, key) + } + } + + if (!i18n) { + return normalFn + } + + const { t, ...methods } = i18n.global + + const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => { + if (!key) return '' + if (!key.includes('.') && !namespace) return key + return (t as any)(getKey(namespace, key), ...(arg as I18nTranslationRestParameters)) + } + return { + ...methods, + t: tFn + } +} + +export const t = (key: string) => key diff --git a/kinit-admin/src/hooks/web/useIcon.ts b/kinit-admin/src/hooks/web/useIcon.ts new file mode 100644 index 0000000..b9a5ccb --- /dev/null +++ b/kinit-admin/src/hooks/web/useIcon.ts @@ -0,0 +1,7 @@ +import { h } from 'vue' +import type { VNode } from 'vue' +import { Icon } from '@/components/Icon' + +export const useIcon = (props: IconTypes): VNode => { + return h(Icon, props) +} diff --git a/kinit-admin/src/hooks/web/useIntro.ts b/kinit-admin/src/hooks/web/useIntro.ts new file mode 100644 index 0000000..85604df --- /dev/null +++ b/kinit-admin/src/hooks/web/useIntro.ts @@ -0,0 +1,47 @@ +import introJs from 'intro.js' +import { IntroJs, Step, Options } from 'intro.js' +import 'intro.js/introjs.css' +import { useI18n } from '@/hooks/web/useI18n' +import { useDesign } from '@/hooks/web/useDesign' + +export const useIntro = (setps?: Step[], options?: Options) => { + const { t } = useI18n() + + const { variables } = useDesign() + + const defaultSetps: Step[] = setps || [ + { + element: `#${variables.namespace}-menu`, + title: t('common.menu'), + intro: t('common.menuDes'), + position: 'right' + }, + { + element: `#${variables.namespace}-tool-header`, + title: t('common.tool'), + intro: t('common.toolDes'), + position: 'left' + }, + { + element: `#${variables.namespace}-tags-view`, + title: t('common.tagsView'), + intro: t('common.tagsViewDes'), + position: 'bottom' + } + ] + + const defaultOptions: Options = options || { + prevLabel: t('common.prevLabel'), + nextLabel: t('common.nextLabel'), + skipLabel: t('common.skipLabel'), + doneLabel: t('common.doneLabel') + } + + const introRef: IntroJs = introJs() + + introRef.addSteps(defaultSetps).setOptions(defaultOptions) + + return { + introRef + } +} diff --git a/kinit-admin/src/hooks/web/useLocale.ts b/kinit-admin/src/hooks/web/useLocale.ts new file mode 100644 index 0000000..c65070e --- /dev/null +++ b/kinit-admin/src/hooks/web/useLocale.ts @@ -0,0 +1,35 @@ +import { i18n } from '@/plugins/vueI18n' +import { useLocaleStoreWithOut } from '@/store/modules/locale' +import { setHtmlPageLang } from '@/plugins/vueI18n/helper' + +const setI18nLanguage = (locale: LocaleType) => { + const localeStore = useLocaleStoreWithOut() + + if (i18n.mode === 'legacy') { + i18n.global.locale = locale + } else { + ;(i18n.global.locale as any).value = locale + } + localeStore.setCurrentLocale({ + lang: locale + }) + setHtmlPageLang(locale) +} + +export const useLocale = () => { + // Switching the language will change the locale of useI18n + // And submit to configuration modification + const changeLocale = async (locale: LocaleType) => { + const globalI18n = i18n.global + + const langModule = await import(`../../locales/${locale}.ts`) + + globalI18n.setLocaleMessage(locale, langModule.default) + + setI18nLanguage(locale) + } + + return { + changeLocale + } +} diff --git a/kinit-admin/src/hooks/web/useNProgress.ts b/kinit-admin/src/hooks/web/useNProgress.ts new file mode 100644 index 0000000..28990e7 --- /dev/null +++ b/kinit-admin/src/hooks/web/useNProgress.ts @@ -0,0 +1,34 @@ +import { nextTick, unref } from 'vue' +import type { NProgressOptions } from 'nprogress' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import { useCssVar } from '@vueuse/core' + +const primaryColor = useCssVar('--el-color-primary', document.documentElement) + +export const useNProgress = () => { + NProgress.configure({ showSpinner: false } as NProgressOptions) + + const initColor = async () => { + await nextTick() + const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef + if (bar) { + bar.style.background = unref(primaryColor.value) + } + } + + initColor() + + const start = () => { + NProgress.start() + } + + const done = () => { + NProgress.done() + } + + return { + start, + done + } +} diff --git a/kinit-admin/src/hooks/web/usePageLoading.ts b/kinit-admin/src/hooks/web/usePageLoading.ts new file mode 100644 index 0000000..bb89457 --- /dev/null +++ b/kinit-admin/src/hooks/web/usePageLoading.ts @@ -0,0 +1,18 @@ +import { useAppStoreWithOut } from '@/store/modules/app' + +const appStore = useAppStoreWithOut() + +export const usePageLoading = () => { + const loadStart = () => { + appStore.setPageLoading(true) + } + + const loadDone = () => { + appStore.setPageLoading(false) + } + + return { + loadStart, + loadDone + } +} diff --git a/kinit-admin/src/hooks/web/useTable.ts b/kinit-admin/src/hooks/web/useTable.ts new file mode 100644 index 0000000..9a4c1b4 --- /dev/null +++ b/kinit-admin/src/hooks/web/useTable.ts @@ -0,0 +1,191 @@ +import { Table, TableExpose } from '@/components/Table' +import { ElTable, ElMessageBox, ElMessage } from 'element-plus' +import { ref, reactive, watch, computed, unref, nextTick } from 'vue' +import { get } from 'lodash-es' +import type { TableProps } from '@/components/Table/src/types' +import { useI18n } from '@/hooks/web/useI18n' + +const { t } = useI18n() + +interface TableResponse { + total: number + list: T[] + pageNumber: number + pageSize: number +} + +interface UseTableConfig { + getListApi: (option: any) => Promise>> + delListApi?: (option: any) => Promise + // 返回数据格式配置 + response: { + list: string + total?: string + } + props?: TableProps +} + +interface TableObject { + pageSize: number + currentPage: number + total: number + tableList: T[] + params: any + loading: boolean + currentRow: Nullable +} + +export const useTable = (config?: UseTableConfig) => { + const tableObject = reactive>({ + // 页数 + pageSize: 10, + // 当前页 + currentPage: 1, + // 总条数 + total: 10, + // 表格数据 + tableList: [], + // AxiosConfig 配置 + params: {}, + // 加载中 + loading: true, + // 当前行的数据 + currentRow: null + }) + + const paramsObj = computed(() => { + return { + ...tableObject.params, + pageSize: tableObject.pageSize, + pageIndex: tableObject.currentPage + } + }) + + watch( + () => tableObject.currentPage, + () => { + methods.getList() + } + ) + + watch( + () => tableObject.pageSize, + () => { + // 当前页不为1时,修改页数后会导致多次调用getList方法 + if (tableObject.currentPage === 1) { + methods.getList() + } else { + tableObject.currentPage = 1 + methods.getList() + } + } + ) + + // Table实例 + const tableRef = ref() + + // ElTable实例 + const elTableRef = ref>() + + const register = (ref: typeof Table & TableExpose, elRef: ComponentRef) => { + tableRef.value = ref + elTableRef.value = unref(elRef) + } + + const getTable = async () => { + await nextTick() + const table = unref(tableRef) + if (!table) { + console.error('The table is not registered. Please use the register method to register') + } + return table + } + + const delData = async (ids: string[] | number[]) => { + const res = await (config?.delListApi && config?.delListApi(ids)) + if (res) { + ElMessage.success(t('common.delSuccess')) + + // 计算出临界点 + const currentPage = + tableObject.total % tableObject.pageSize === ids.length || tableObject.pageSize === 1 + ? tableObject.currentPage > 1 + ? tableObject.currentPage - 1 + : tableObject.currentPage + : tableObject.currentPage + + tableObject.currentPage = currentPage + methods.getList() + } + } + + const methods = { + getList: async () => { + tableObject.loading = true + const res = await config?.getListApi(unref(paramsObj)).finally(() => { + tableObject.loading = false + }) + if (res) { + tableObject.tableList = get(res.data || {}, config?.response.list as string) + tableObject.total = get(res.data || {}, config?.response?.total as string) || 0 + } + }, + setProps: async (props: TableProps = {}) => { + const table = await getTable() + table?.setProps(props) + }, + setColumn: async (columnProps: TableSetPropsType[]) => { + const table = await getTable() + table?.setColumn(columnProps) + }, + getSelections: async () => { + const table = await getTable() + return (table?.selections || []) as T[] + }, + // 与Search组件结合 + setSearchParams: (data: Recordable) => { + tableObject.currentPage = 1 + tableObject.params = Object.assign(tableObject.params, { + pageSize: tableObject.pageSize, + pageIndex: tableObject.currentPage, + ...data + }) + methods.getList() + }, + // 删除数据 + delList: async (ids: string[] | number[], multiple: boolean, message = true) => { + const tableRef = await getTable() + if (multiple) { + if (!tableRef?.selections.length) { + ElMessage.warning(t('common.delNoData')) + return + } + } else { + if (!tableObject.currentRow) { + ElMessage.warning(t('common.delNoData')) + return + } + } + if (message) { + ElMessageBox.confirm(t('common.delMessage'), t('common.delWarning'), { + confirmButtonText: t('common.delOk'), + cancelButtonText: t('common.delCancel'), + type: 'warning' + }).then(async () => { + await delData(ids) + }) + } else { + await delData(ids) + } + } + } + + config?.props && methods.setProps(config.props) + + return { + register, + elTableRef, + tableObject, + methods + } +} diff --git a/kinit-admin/src/hooks/web/useTimeAgo.ts b/kinit-admin/src/hooks/web/useTimeAgo.ts new file mode 100644 index 0000000..6dc7123 --- /dev/null +++ b/kinit-admin/src/hooks/web/useTimeAgo.ts @@ -0,0 +1,48 @@ +import { useTimeAgo as useTimeAgoCore, UseTimeAgoMessages } from '@vueuse/core' +import { computed, unref } from 'vue' +import { useLocaleStoreWithOut } from '@/store/modules/locale' + +const TIME_AGO_MESSAGE_MAP: { + 'zh-CN': UseTimeAgoMessages + en: UseTimeAgoMessages +} = { + 'zh-CN': { + justNow: '刚刚', + past: (n) => (n.match(/\d/) ? `${n}前` : n), + future: (n) => (n.match(/\d/) ? `${n}后` : n), + month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`), + year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`), + day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`), + week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n} 周`), + hour: (n) => `${n} 小时`, + minute: (n) => `${n} 分钟`, + second: (n) => `${n} 秒` + }, + en: { + justNow: '刚刚', + past: (n) => (n.match(/\d/) ? `${n} ago` : n), + future: (n) => (n.match(/\d/) ? `in ${n}` : n), + month: (n, past) => + n === 1 ? (past ? 'last month' : 'next month') : `${n} month${n > 1 ? 's' : ''}`, + year: (n, past) => + n === 1 ? (past ? 'last year' : 'next year') : `${n} year${n > 1 ? 's' : ''}`, + day: (n, past) => (n === 1 ? (past ? 'yesterday' : 'tomorrow') : `${n} day${n > 1 ? 's' : ''}`), + week: (n, past) => + n === 1 ? (past ? 'last week' : 'next week') : `${n} week${n > 1 ? 's' : ''}`, + hour: (n) => `${n} hour${n > 1 ? 's' : ''}`, + minute: (n) => `${n} minute${n > 1 ? 's' : ''}`, + second: (n) => `${n} second${n > 1 ? 's' : ''}` + } +} + +export const useTimeAgo = (time: Date | number | string) => { + const localeStore = useLocaleStoreWithOut() + + const currentLocale = computed(() => localeStore.getCurrentLocale) + + const timeAgo = useTimeAgoCore(time, { + messages: TIME_AGO_MESSAGE_MAP[unref(currentLocale).lang] + }) + + return timeAgo +} diff --git a/kinit-admin/src/hooks/web/useTitle.ts b/kinit-admin/src/hooks/web/useTitle.ts new file mode 100644 index 0000000..d0eb188 --- /dev/null +++ b/kinit-admin/src/hooks/web/useTitle.ts @@ -0,0 +1,25 @@ +import { watch, ref } from 'vue' +import { isString } from '@/utils/is' +import { useAppStoreWithOut } from '@/store/modules/app' +import { useI18n } from '@/hooks/web/useI18n' + +const appStore = useAppStoreWithOut() + +export const useTitle = (newTitle?: string) => { + const { t } = useI18n() + const title = ref( + newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle + ) + + watch( + title, + (n, o) => { + if (isString(n) && n !== o && document) { + document.title = n + } + }, + { immediate: true } + ) + + return title +} diff --git a/kinit-admin/src/hooks/web/useValidator.ts b/kinit-admin/src/hooks/web/useValidator.ts new file mode 100644 index 0000000..a0d36c3 --- /dev/null +++ b/kinit-admin/src/hooks/web/useValidator.ts @@ -0,0 +1,64 @@ +import { useI18n } from '@/hooks/web/useI18n' + +const { t } = useI18n() + +type Callback = (error?: string | Error | undefined) => void + +interface LengthRange { + min: number + max: number + message: string +} + +export const useValidator = () => { + const required = (message?: string) => { + return { + required: true, + message: message || t('common.required') + } + } + + const lengthRange = (val: any, callback: Callback, options: LengthRange) => { + const { min, max, message } = options + if (val.length < min || val.length > max) { + callback(new Error(message)) + } else { + callback() + } + } + + const notSpace = (val: any, callback: Callback, message: string) => { + // 用户名不能有空格 + if (val.indexOf(' ') !== -1) { + callback(new Error(message)) + } else { + callback() + } + } + + const notSpecialCharacters = (val: any, callback: Callback, message: string) => { + // 密码不能是特殊字符 + if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) { + callback(new Error(message)) + } else { + callback() + } + } + + // 两个字符串是否想等 + const isEqual = (val1: string, val2: string, callback: Callback, message: string) => { + if (val1 === val2) { + callback() + } else { + callback(new Error(message)) + } + } + + return { + required, + lengthRange, + notSpace, + notSpecialCharacters, + isEqual + } +} diff --git a/kinit-admin/src/hooks/web/useWatermark.ts b/kinit-admin/src/hooks/web/useWatermark.ts new file mode 100644 index 0000000..4a31359 --- /dev/null +++ b/kinit-admin/src/hooks/web/useWatermark.ts @@ -0,0 +1,55 @@ +const domSymbol = Symbol('watermark-dom') + +export function useWatermark(appendEl: HTMLElement | null = document.body) { + let func: Fn = () => {} + const id = domSymbol.toString() + const clear = () => { + const domId = document.getElementById(id) + if (domId) { + const el = appendEl + el && el.removeChild(domId) + } + window.removeEventListener('resize', func) + } + const createWatermark = (str: string) => { + clear() + + const can = document.createElement('canvas') + can.width = 300 + can.height = 240 + + const cans = can.getContext('2d') + if (cans) { + cans.rotate((-20 * Math.PI) / 120) + cans.font = '15px Vedana' + cans.fillStyle = 'rgba(0, 0, 0, 0.15)' + cans.textAlign = 'left' + cans.textBaseline = 'middle' + cans.fillText(str, can.width / 20, can.height) + } + + const div = document.createElement('div') + div.id = id + div.style.pointerEvents = 'none' + div.style.top = '0px' + div.style.left = '0px' + div.style.position = 'absolute' + div.style.zIndex = '100000000' + div.style.width = document.documentElement.clientWidth + 'px' + div.style.height = document.documentElement.clientHeight + 'px' + div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat' + const el = appendEl + el && el.appendChild(div) + return id + } + + function setWatermark(str: string) { + createWatermark(str) + func = () => { + createWatermark(str) + } + window.addEventListener('resize', func) + } + + return { setWatermark, clear } +} diff --git a/kinit-admin/src/layout/Layout.vue b/kinit-admin/src/layout/Layout.vue new file mode 100644 index 0000000..324da7e --- /dev/null +++ b/kinit-admin/src/layout/Layout.vue @@ -0,0 +1,78 @@ + + + diff --git a/kinit-admin/src/layout/components/AppView.vue b/kinit-admin/src/layout/components/AppView.vue new file mode 100644 index 0000000..ace76b6 --- /dev/null +++ b/kinit-admin/src/layout/components/AppView.vue @@ -0,0 +1,52 @@ + + + diff --git a/kinit-admin/src/layout/components/ToolHeader.vue b/kinit-admin/src/layout/components/ToolHeader.vue new file mode 100644 index 0000000..091a25a --- /dev/null +++ b/kinit-admin/src/layout/components/ToolHeader.vue @@ -0,0 +1,83 @@ + + + diff --git a/kinit-admin/src/layout/components/useRenderLayout.tsx b/kinit-admin/src/layout/components/useRenderLayout.tsx new file mode 100644 index 0000000..9e5f744 --- /dev/null +++ b/kinit-admin/src/layout/components/useRenderLayout.tsx @@ -0,0 +1,263 @@ +import { computed } from 'vue' +import { useAppStore } from '@/store/modules/app' +import { Menu } from '@/components/Menu' +import { TabMenu } from '@/components/TabMenu' +import { TagsView } from '@/components/TagsView' +import { Logo } from '@/components/Logo' +import AppView from './AppView.vue' +import ToolHeader from './ToolHeader.vue' +import { ElScrollbar } from 'element-plus' +import { useDesign } from '@/hooks/web/useDesign' + +const { getPrefixCls } = useDesign() + +const prefixCls = getPrefixCls('layout') + +const appStore = useAppStore() + +const pageLoading = computed(() => appStore.getPageLoading) + +// 标签页 +const tagsView = computed(() => appStore.getTagsView) + +// 菜单折叠 +const collapse = computed(() => appStore.getCollapse) + +// logo +const logo = computed(() => appStore.logo) + +// 固定头部 +const fixedHeader = computed(() => appStore.getFixedHeader) + +// 是否是移动端 +const mobile = computed(() => appStore.getMobile) + +export const useRenderLayout = () => { + const renderClassic = () => { + return ( + <> +
+ {logo.value ? ( + + ) : undefined} + +
+
+ +
+ + + {tagsView.value ? ( + + ) : undefined} +
+ + +
+
+ + ) + } + + const renderTopLeft = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ +
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+
+ + ) + } + + const renderTop = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+ + ) + } + + const renderCutMenu = () => { + return ( + <> +
+ {logo.value ? : undefined} + + +
+
+ +
+ + {tagsView.value ? ( + + ) : undefined} + + + +
+
+ + ) + } + + return { + renderClassic, + renderTopLeft, + renderTop, + renderCutMenu + } +} diff --git a/kinit-admin/src/locales/en.ts b/kinit-admin/src/locales/en.ts new file mode 100644 index 0000000..6f6ec58 --- /dev/null +++ b/kinit-admin/src/locales/en.ts @@ -0,0 +1,441 @@ +export default { + common: { + inputText: 'Please input', + selectText: 'Please select', + startTimeText: 'Start time', + endTimeText: 'End time', + login: 'Login', + required: 'This is required', + loginOut: 'Login out', + document: 'Document', + reminder: 'Reminder', + loginOutMessage: 'Exit the system?', + back: 'Back', + ok: 'OK', + cancel: 'Cancel', + reload: 'Reload current', + closeTab: 'Close current', + closeTheLeftTab: 'Close left', + closeTheRightTab: 'Close right', + closeOther: 'Close other', + closeAll: 'Close all', + prevLabel: 'Prev', + nextLabel: 'Next', + skipLabel: 'Jump', + doneLabel: 'End', + menu: 'Menu', + menuDes: 'Menu bar rendered in routed structure', + collapse: 'Collapse', + collapseDes: 'Expand and zoom the menu bar', + tagsView: 'Tags view', + tagsViewDes: 'Used to record routing history', + tool: 'Tool', + toolDes: 'Used to set up custom systems', + query: 'Query', + reset: 'Reset', + shrink: 'Put away', + expand: 'Expand', + delMessage: 'Delete the selected data?', + delWarning: 'Warning', + delOk: 'OK', + delCancel: 'Cancel', + delNoData: 'Please select the data to delete', + delSuccess: 'Deleted successfully' + }, + error: { + noPermission: `Sorry, you don't have permission to access this page.`, + pageError: 'Sorry, the page you visited does not exist.', + networkError: 'Sorry, the server reported an error.', + returnToHome: 'Return to home' + }, + setting: { + projectSetting: 'Project setting', + theme: 'Theme', + layout: 'Layout', + systemTheme: 'System theme', + menuTheme: 'Menu theme', + interfaceDisplay: 'Interface display', + breadcrumb: 'Breadcrumb', + breadcrumbIcon: 'Breadcrumb icon', + collapseMenu: 'Collapse menu', + hamburgerIcon: 'Hamburger icon', + screenfullIcon: 'Screenfull icon', + sizeIcon: 'Size icon', + localeIcon: 'Locale icon', + tagsView: 'Tags view', + logo: 'Logo', + greyMode: 'Grey mode', + fixedHeader: 'Fixed header', + headerTheme: 'Header theme', + cutMenu: 'Cut Menu', + copy: 'Copy', + clearAndReset: 'Clear cache and reset', + copySuccess: 'Copy success', + copyFailed: 'Copy failed', + footer: 'Footer', + uniqueOpened: 'Unique opened', + tagsViewIcon: 'Tags view icon', + dynamicRouter: 'Dynamic router', + reExperienced: 'Please exit the login experience again' + }, + size: { + default: 'Default', + large: 'Large', + small: 'Small' + }, + login: { + welcome: 'Welcome to the system', + message: 'Backstage management system', + username: 'Username', + password: 'Password', + register: 'Register', + checkPassword: 'Confirm password', + login: 'Sign in', + otherLogin: 'Sign in with', + remember: 'Remember me', + hasUser: 'Existing account? Go to login', + forgetPassword: 'Forget password', + usernamePlaceholder: 'Please input username', + passwordPlaceholder: 'Please input password', + code: 'Verification code', + codePlaceholder: 'Please input verification code' + }, + router: { + login: 'Login', + level: 'Multi level menu', + menu: 'Menu', + menu1: 'Menu1', + menu11: 'Menu1-1', + menu111: 'Menu1-1-1', + menu12: 'Menu1-2', + menu2: 'Menu2', + dashboard: 'Dashboard', + analysis: 'Analysis', + workplace: 'Workplace', + guide: 'Guide', + component: 'Component', + icon: 'Icon', + echart: 'Echart', + countTo: 'Count to', + watermark: 'Watermark', + qrcode: 'Qrcode', + highlight: 'Highlight', + infotip: 'Infotip', + form: 'Form', + defaultForm: 'All examples', + search: 'Search', + table: 'Table', + defaultTable: 'Basic example', + editor: 'Editor', + richText: 'Rich text', + dialog: 'Dialog', + imageViewer: 'Image viewer', + descriptions: 'Descriptions', + example: 'Example', + exampleDialog: 'Example dialog', + examplePage: 'Example page', + exampleAdd: 'Example page - add', + exampleEdit: 'Example page - edit', + exampleDetail: 'Example page - detail', + errorPage: 'Error page', + authorization: 'Authorization', + user: 'User management', + role: 'Role management', + document: 'Document', + inputPassword: 'InputPassword', + sticky: 'Sticky' + }, + permission: { + hasPermission: 'Please set the operation permission value' + }, + analysis: { + newUser: 'New user', + unreadInformation: 'Unread information', + transactionAmount: 'Transaction amount', + totalShopping: 'Total Shopping', + monthlySales: 'Monthly sales', + userAccessSource: 'User access source', + january: 'January', + february: 'February', + march: 'March', + april: 'April', + may: 'May', + june: 'June', + july: 'July', + august: 'August', + september: 'September', + october: 'October', + november: 'November', + december: 'December', + estimate: 'Estimate', + actual: 'Actual', + directAccess: 'Airect access', + mailMarketing: 'Mail marketing', + allianceAdvertising: 'Alliance advertising', + videoAdvertising: 'Video advertising', + searchEngines: 'Search engines', + weeklyUserActivity: 'Weekly user activity', + activeQuantity: 'Active quantity', + monday: 'Monday', + tuesday: 'Tuesday', + wednesday: 'Wednesday', + thursday: 'Thursday', + friday: 'Friday', + saturday: 'Saturday', + sunday: 'Sunday' + }, + workplace: { + goodMorning: 'Good morning', + happyDay: 'Wish you happy every day!', + toady: `It's sunny today`, + project: 'Project', + access: 'Project access', + toDo: 'To do', + introduction: 'A serious introduction', + more: 'More', + shortcutOperation: 'Shortcut operation', + operation: 'Operation', + index: 'Index', + personal: 'Personal', + team: 'Team', + quote: 'Quote', + contribution: 'Contribution', + hot: 'Hot', + yield: 'Yield', + dynamic: 'Dynamic', + push: 'push', + pushCode: 'Archer push code to Github', + follow: 'Follow' + }, + formDemo: { + input: 'Input', + inputNumber: 'InputNumber', + default: 'Default', + icon: 'Icon', + mixed: 'Mixed', + textarea: 'Textarea', + slot: 'Slot', + position: 'Position', + autocomplete: 'Autocomplete', + select: 'Select', + selectGroup: 'Select Group', + selectV2: 'SelectV2', + cascader: 'Cascader', + switch: 'Switch', + rate: 'Rate', + colorPicker: 'Color Picker', + transfer: 'Transfer', + render: 'Render', + radio: 'Radio', + button: 'Button', + checkbox: 'Checkbox', + slider: 'Slider', + datePicker: 'Date Picker', + shortcuts: 'Shortcuts', + today: 'Today', + yesterday: 'Yesterday', + aWeekAgo: 'A week ago', + week: 'Week', + year: 'Year', + month: 'Month', + dates: 'Dates', + daterange: 'Date Range', + monthrange: 'Month Range', + dateTimePicker: 'DateTimePicker', + dateTimerange: 'Datetime Range', + timePicker: 'Time Picker', + timeSelect: 'Time Select', + inputPassword: 'input Password', + passwordStrength: 'Password Strength', + defaultForm: 'All examples', + formDes: + 'The secondary encapsulation of form components based on ElementPlus realizes data-driven and supports all Form parameters', + example: 'example', + operate: 'operate', + change: 'Change', + restore: 'Restore', + disabled: 'Disabled', + disablement: 'Disablement', + delete: 'Delete', + add: 'Add', + setValue: 'Set value', + resetValue: 'Reset value', + set: 'Set', + subitem: 'Subitem', + formValidation: 'Form validation', + verifyReset: 'Verify reset' + }, + guideDemo: { + guide: 'Guide', + start: 'Start', + message: + 'The guide page is very useful for some people who enter the project for the first time. You can briefly introduce the functions of the project. The boot page is based on intro js' + }, + iconDemo: { + icon: 'Icon', + localIcon: 'Local Icon', + iconify: 'Iconify component', + recommendedUse: 'Recommended use', + recommendeDes: + 'Iconify component basically contains all icons. You can query any icon you want. And packaging will only package the icons used.', + accessAddress: 'Access address' + }, + echartDemo: { + echart: 'Echart', + echartDes: + 'Based on the secondary packaging components of eckarts, the width is adaptive. The corresponding chart can be displayed by passing in the options and height attributes.' + }, + countToDemo: { + countTo: 'CountTo', + countToDes: + 'The transformation is based on vue-count-to and supports all vue-count-to parameters.', + suffix: 'Suffix', + prefix: 'Prefix', + separator: 'Separator', + duration: 'Duration', + endVal: 'End val', + startVal: 'Start val', + start: 'Start', + pause: 'Pause', + resume: 'Resume' + }, + watermarkDemo: { + watermark: 'Watermark', + createdWatermark: 'Created watermark', + clearWatermark: 'Clear watermark', + resetWatermark: 'Reset watermark' + }, + qrcodeDemo: { + qrcode: 'Qrcode', + qrcodeDes: 'Secondary packaging based on qrcode', + basicUsage: 'Basic usage', + imgTag: 'Img tag', + style: 'Style config', + click: 'Click event', + asynchronousContent: 'Asynchronous content', + invalid: 'Invalid', + logoConfig: 'Logo config', + logoStyle: 'Logo style', + size: 'size config' + }, + highlightDemo: { + highlight: 'Highlight', + message: 'The best time to plant a tree is ten years ago, followed by now.', + keys1: 'ten years ago', + keys2: 'now' + }, + infotipDemo: { + infotip: 'Infotip', + infotipDes: 'Secondary packaging of components based on Highlight', + title: 'matters needing attention' + }, + levelDemo: { + menu: 'Multi level menu cache' + }, + searchDemo: { + search: 'Search', + searchDes: + 'Based on the secondary encapsulation of form components, the functions of query and reset are realized', + operate: 'operate', + change: 'Change', + grid: 'grid', + button: 'Button', + restore: 'Restore', + inline: 'inline', + bottom: 'Bottom', + position: 'position', + left: 'left', + center: 'center', + right: 'right', + dynamicOptions: 'Dynamic options' + }, + stickyDemo: { + sticky: 'Sticky' + }, + tableDemo: { + table: 'Table', + tableDes: 'Secondary packaging of Table components based on ElementPlus', + index: 'Index', + title: 'Title', + author: 'Author', + displayTime: 'Display time', + importance: 'Importance', + pageviews: 'Pageviews', + action: 'Action', + important: 'Important', + good: 'Good', + commonly: 'Commonly', + operate: 'operate', + example: 'example', + show: 'Show', + hidden: 'Hidden', + pagination: 'pagination', + reserveIndex: 'Reserve index', + restoreIndex: 'Restore index', + showSelections: 'Show selections', + hiddenSelections: 'Restore selections', + showExpandedRows: 'Show expanded rows', + hiddenExpandedRows: 'Hidden expanded rows', + changeTitle: 'Change title', + header: 'Header', + selectAllNone: 'Select all / none' + }, + richText: { + richText: 'Rich text', + richTextDes: 'Secondary packaging based on wangeditor' + }, + dialogDemo: { + dialog: 'Dialog', + dialogDes: 'Secondary packaging of Dialog components based on ElementPlus', + open: 'Open', + close: 'Close', + combineWithForm: 'Combine with form', + submit: 'Submit' + }, + imageViewerDemo: { + open: 'Open', + imageViewer: 'Image viewer', + imageViewerDes: 'Secondary packaging of ImageViewer components based on ElementPlus' + }, + descriptionsDemo: { + descriptions: 'Descriptions', + descriptionsDes: 'Secondary packaging of Descriptions components based on ElementPlus', + username: 'Username', + nickName: 'NickName', + phone: 'Phone', + email: 'Email', + addr: 'Address', + form: 'Combined with Form component' + }, + exampleDemo: { + title: 'Title', + add: 'Add', + del: 'Delete', + edit: 'Edit', + author: 'Author', + displayTime: 'Display time', + importance: 'Importance', + pageviews: 'Pageviews', + important: 'Important', + content: 'Content', + save: 'Save', + detail: 'Detail' + }, + userDemo: { + title: 'User management', + message: + 'Because it is simulated data, only two accounts with different permissions are provided, which can be modified and combined by developers according to the actual situation.', + index: 'Index', + action: 'Action', + username: 'Username', + password: 'Password', + role: 'Role', + remark: 'Remark', + remarkMessage1: 'Back end control routing permission', + remarkMessage2: 'Front end control routing permission' + }, + inputPasswordDemo: { + title: 'InputPassword', + inputPasswordDes: 'Secondary packaging of Input components based on ElementPlus' + } +} diff --git a/kinit-admin/src/locales/zh-CN.ts b/kinit-admin/src/locales/zh-CN.ts new file mode 100644 index 0000000..7600880 --- /dev/null +++ b/kinit-admin/src/locales/zh-CN.ts @@ -0,0 +1,437 @@ +export default { + common: { + inputText: '请输入', + selectText: '请选择', + startTimeText: '开始时间', + endTimeText: '结束时间', + login: '登录', + required: '该项为必填项', + loginOut: '退出系统', + document: '项目文档', + reminder: '温馨提示', + loginOutMessage: '是否退出本系统?', + back: '返回', + ok: '确定', + cancel: '取消', + reload: '重新加载', + closeTab: '关闭标签页', + closeTheLeftTab: '关闭左侧标签页', + closeTheRightTab: '关闭右侧标签页', + closeOther: '关闭其他标签页', + closeAll: '关闭全部标签页', + prevLabel: '上一步', + nextLabel: '下一步', + skipLabel: '跳过', + doneLabel: '结束', + menu: '菜单', + menuDes: '以路由的结构渲染的菜单栏', + collapse: '展开缩收', + collapseDes: '展开和缩放菜单栏', + tagsView: '标签页', + tagsViewDes: '用于记录路由历史记录', + tool: '工具', + toolDes: '用于设置定制系统', + query: '查询', + reset: '重置', + shrink: '收起', + expand: '展开', + delMessage: '是否删除所选中数据?', + delWarning: '提示', + delOk: '确定', + delCancel: '取消', + delNoData: '请选择需要删除的数据', + delSuccess: '删除成功' + }, + error: { + noPermission: `抱歉,您无权访问此页面。`, + pageError: '抱歉,您访问的页面不存在。', + networkError: '抱歉,服务器报告错误。', + returnToHome: '返回首页' + }, + setting: { + projectSetting: '项目配置', + theme: '主题', + layout: '布局', + systemTheme: '系统主题', + menuTheme: '菜单主题', + interfaceDisplay: '界面显示', + breadcrumb: '面包屑', + breadcrumbIcon: '面包屑图标', + collapseMenu: '折叠菜单', + hamburgerIcon: '折叠图标', + screenfullIcon: '全屏图标', + sizeIcon: '尺寸图标', + localeIcon: '多语言图标', + tagsView: '标签页', + logo: '标志', + greyMode: '灰色模式', + fixedHeader: '固定头部', + headerTheme: '头部主题', + cutMenu: '切割菜单', + copy: '拷贝', + clearAndReset: '清除缓存并且重置', + copySuccess: '拷贝成功', + copyFailed: '拷贝失败', + footer: '页脚', + uniqueOpened: '菜单手风琴', + tagsViewIcon: '标签页图标', + dynamicRouter: '动态路由', + reExperienced: '请重新退出登录体验' + }, + size: { + default: '默认', + large: '大', + small: '小' + }, + login: { + welcome: '欢迎使用本系统', + message: '开箱即用的中后台管理系统', + username: '用户名', + password: '密码', + register: '注册', + checkPassword: '确认密码', + login: '登录', + otherLogin: '其他登录方式', + remember: '记住我', + hasUser: '已有账号?去登录', + forgetPassword: '忘记密码', + usernamePlaceholder: '请输入用户名', + passwordPlaceholder: '请输入密码', + code: '验证码', + codePlaceholder: '请输入验证码' + }, + router: { + login: '登录', + level: '多级菜单', + menu: '菜单', + menu1: '菜单1', + menu11: '菜单1-1', + menu111: '菜单1-1-1', + menu12: '菜单1-2', + menu2: '菜单2', + dashboard: '首页', + analysis: '分析页', + workplace: '工作台', + guide: '引导', + component: '组件', + icon: '图标', + echart: '图表', + countTo: '数字动画', + watermark: '水印', + qrcode: '二维码', + highlight: '高亮', + infotip: '信息提示', + form: '表单', + defaultForm: '全部示例', + search: '查询', + table: '表格', + defaultTable: '基础示例', + editor: '编辑器', + richText: '富文本', + dialog: '弹窗', + imageViewer: '图片预览', + descriptions: '描述', + example: '综合示例', + exampleDialog: '综合示例 - 弹窗', + examplePage: '综合示例 - 页面', + exampleAdd: '综合示例 - 新增', + exampleEdit: '综合示例 - 编辑', + exampleDetail: '综合示例 - 详情', + errorPage: '错误页面', + authorization: '权限管理', + user: '用户管理', + role: '角色管理', + document: '文档', + inputPassword: '密码输入框', + sticky: '黏性' + }, + permission: { + hasPermission: '请设置操作权限值' + }, + analysis: { + newUser: '新增用户', + unreadInformation: '未读消息', + transactionAmount: '成交金额', + totalShopping: '购物总量', + monthlySales: '每月销售额', + userAccessSource: '用户访问来源', + january: '一月', + february: '二月', + march: '三月', + april: '四月', + may: '五月', + june: '六月', + july: '七月', + august: '八月', + september: '九月', + october: '十月', + november: '十一月', + december: '十二月', + estimate: '预计', + actual: '实际', + directAccess: '直接访问', + mailMarketing: '邮件营销', + allianceAdvertising: '联盟广告', + videoAdvertising: '视频广告', + searchEngines: '搜索引擎', + weeklyUserActivity: '每周用户活跃量', + activeQuantity: '活跃量', + monday: '周一', + tuesday: '周二', + wednesday: '周三', + thursday: '周四', + friday: '周五', + saturday: '周六', + sunday: '周日' + }, + workplace: { + goodMorning: '早安', + happyDay: '祝你开心每一天!', + toady: '今日晴', + project: '项目数', + access: '项目访问', + toDo: '待办', + introduction: '一个正经的简介', + more: '更多', + shortcutOperation: '快捷操作', + operation: '操作', + index: '指数', + personal: '个人', + team: '团队', + quote: '引用', + contribution: '贡献', + hot: '热度', + yield: '产量', + dynamic: '动态', + push: '推送', + pushCode: 'Archer 推送 代码到 Github', + follow: '关注' + }, + formDemo: { + input: '输入框', + inputNumber: '数字输入框', + default: '默认', + icon: '图标', + mixed: '复合型', + textarea: '多行文本', + slot: '插槽', + position: '位置', + autocomplete: '自动补全', + select: '选择器', + selectGroup: '选项分组', + selectV2: '虚拟列表选择器', + cascader: '级联选择器', + switch: '开关', + rate: '评分', + colorPicker: '颜色选择器', + transfer: '穿梭框', + render: '渲染器', + radio: '单选框', + button: '按钮', + checkbox: '多选框', + slider: '滑块', + datePicker: '日期选择器', + shortcuts: '快捷选项', + today: '今天', + yesterday: '昨天', + aWeekAgo: '一周前', + week: '周', + year: '年', + month: '月', + dates: '日期', + daterange: '日期范围', + monthrange: '月份范围', + dateTimePicker: '日期时间选择器', + dateTimerange: '日期时间范围', + timePicker: '时间选择器', + timeSelect: '时间选择', + inputPassword: '密码输入框', + passwordStrength: '密码强度', + defaultForm: '全部示例', + formDes: '基于 ElementPlus 的 Form 组件二次封装,实现数据驱动,支持所有 Form 参数', + example: '示例', + operate: '操作', + change: '更改', + restore: '还原', + disabled: '禁用', + disablement: '解除禁用', + delete: '删除', + add: '添加', + setValue: '设置值', + resetValue: '重置值', + set: '设置', + subitem: '子项', + formValidation: '表单验证', + verifyReset: '验证重置' + }, + guideDemo: { + guide: '引导页', + start: '开始', + message: + '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。引导页基于 intro.js' + }, + iconDemo: { + icon: '图标', + localIcon: '本地图标', + iconify: 'Iconify组件', + recommendedUse: '推荐使用', + recommendeDes: + 'Iconify组件基本包含所有的图标,你可以查询到你想要的任何图标。并且打包只会打包所用到的图标。', + accessAddress: '访问地址' + }, + echartDemo: { + echart: '图表', + echartDes: + '基于 echarts 二次封装组件,自适应宽度,只需传入 options 与 height 属性即可展示对应的图表。' + }, + countToDemo: { + countTo: '数字动画', + countToDes: '基于 vue-count-to 进行改造,支持所有 vue-count-to 参数。', + suffix: '后缀', + prefix: '前缀', + separator: '分割符号', + duration: '持续时间', + endVal: '结束值', + startVal: '开始值', + start: '开始', + pause: '暂停', + resume: '继续' + }, + watermarkDemo: { + watermark: '水印', + createdWatermark: '创建水印', + clearWatermark: '清除水印', + resetWatermark: '重置水印' + }, + qrcodeDemo: { + qrcode: '二维码', + qrcodeDes: '基于 qrcode 二次封装', + basicUsage: '基础用法', + imgTag: 'img标签', + style: '样式配置', + click: '点击事件', + asynchronousContent: '异步内容', + invalid: '失效', + logoConfig: 'logo配置', + logoStyle: 'logo样式', + size: '大小配置' + }, + highlightDemo: { + highlight: '高亮', + message: '种一棵树最好的时间是十年前,其次就是现在。', + keys1: '十年前', + keys2: '现在' + }, + infotipDemo: { + infotip: '信息提示', + infotipDes: '基于 Highlight 组件二次封装', + title: '注意事项' + }, + levelDemo: { + menu: '多级菜单缓存' + }, + searchDemo: { + search: '查询', + searchDes: '基于 Form 组件二次封装,实现查询、重置功能', + operate: '操作', + change: '更改', + grid: '栅格', + button: '按钮', + restore: '还原', + inline: '内联', + bottom: '底部', + position: '位置', + left: '左', + center: '中', + right: '右', + dynamicOptions: '动态选项' + }, + stickyDemo: { + sticky: '黏性' + }, + tableDemo: { + table: '表格', + tableDes: '基于 ElementPlus 的 Table 组件二次封装', + index: '序号', + title: '标题', + author: '作者', + displayTime: '创建时间', + importance: '重要性', + pageviews: '阅读数', + action: '操作', + important: '重要', + good: '良好', + commonly: '一般', + operate: '操作', + example: '示例', + show: '显示', + hidden: '隐藏', + pagination: '分页', + reserveIndex: '叠加序号', + restoreIndex: '还原序号', + showSelections: '显示多选', + hiddenSelections: '隐藏多选', + showExpandedRows: '显示展开行', + hiddenExpandedRows: '隐藏展开行', + changeTitle: '修改标题', + header: '头部', + selectAllNone: '全选/全不选' + }, + richText: { + richText: '富文本', + richTextDes: '基于 wangeditor 二次封装' + }, + dialogDemo: { + dialog: '弹窗', + dialogDes: '基于 ElementPlus 的 Dialog 组件二次封装', + open: '打开', + close: '关闭', + combineWithForm: '与表单结合', + submit: '提交' + }, + imageViewerDemo: { + open: '打开', + imageViewer: '图片预览', + imageViewerDes: '基于 ElementPlus 的 ImageViewer 组件二次封装' + }, + descriptionsDemo: { + descriptions: '描述', + descriptionsDes: '基于 ElementPlus 的 Descriptions 组件二次封装', + username: '用户名', + nickName: '昵称', + phone: '联系电话', + email: '邮箱', + addr: '地址', + form: '与 Form 组件组合' + }, + exampleDemo: { + title: '标题', + add: '新增', + del: '删除', + edit: '编辑', + author: '作者', + displayTime: '创建时间', + importance: '重要性', + pageviews: '阅读数', + important: '重要', + content: '内容', + save: '保存', + detail: '详情' + }, + userDemo: { + title: '用户管理', + message: '由于是模拟数据,所以只提供了两种不同权限的帐号,开发者可根据实际情况自行改造结合。', + index: '序号', + action: '操作', + username: '用户名', + password: '密码', + role: '角色', + remark: '备注', + remarkMessage1: '后端控制路由权限', + remarkMessage2: '前端控制路由权限' + }, + inputPasswordDemo: { + title: '密码输入框', + inputPasswordDes: '基于 ElementPlus 的 Input 组件二次封装' + } +} diff --git a/kinit-admin/src/main.ts b/kinit-admin/src/main.ts new file mode 100644 index 0000000..f6bfd39 --- /dev/null +++ b/kinit-admin/src/main.ts @@ -0,0 +1,55 @@ +// 引入windi css +import '@/plugins/windi.css' + +// 导入全局的svg图标 +import '@/plugins/svgIcon' + +// 初始化多语言 +import { setupI18n } from '@/plugins/vueI18n' + +// 引入状态管理 +import { setupStore } from '@/store' + +// 全局组件 +import { setupGlobCom } from '@/components' + +// 引入element-plus +import { setupElementPlus } from '@/plugins/elementPlus' + +// 引入全局样式 +import '@/styles/index.less' + +// 引入动画 +import '@/plugins/animate.css' + +// 路由 +import { setupRouter } from './router' + +// 权限 +import { setupPermission } from './directives' + +import { createApp } from 'vue' + +import App from './App.vue' + +import './permission' +// 创建实例 +const setupAll = async () => { + const app = createApp(App) + + await setupI18n(app) + + setupStore(app) + + setupGlobCom(app) + + setupElementPlus(app) + + setupRouter(app) + + setupPermission(app) + + app.mount('#app') +} + +setupAll() diff --git a/kinit-admin/src/permission.ts b/kinit-admin/src/permission.ts new file mode 100644 index 0000000..7c7dc28 --- /dev/null +++ b/kinit-admin/src/permission.ts @@ -0,0 +1,82 @@ +import router from './router' +import { useAppStoreWithOut } from '@/store/modules/app' +import { useCache } from '@/hooks/web/useCache' +import type { RouteRecordRaw } from 'vue-router' +import { useTitle } from '@/hooks/web/useTitle' +import { useNProgress } from '@/hooks/web/useNProgress' +import { usePermissionStoreWithOut } from '@/store/modules/permission' +import { useDictStoreWithOut } from '@/store/modules/dict' +import { usePageLoading } from '@/hooks/web/usePageLoading' +import { getDictApi } from '@/api/common' + +const permissionStore = usePermissionStoreWithOut() + +const appStore = useAppStoreWithOut() + +const dictStore = useDictStoreWithOut() + +const { wsCache } = useCache() + +const { start, done } = useNProgress() + +const { loadStart, loadDone } = usePageLoading() + +const whiteList = ['/login'] // 不重定向白名单 + +router.beforeEach(async (to, from, next) => { + start() + loadStart() + if (wsCache.get(appStore.getUserInfo)) { + if (to.path === '/login') { + next({ path: '/' }) + } else { + if (permissionStore.getIsAddRouters) { + next() + return + } + + if (!dictStore.getIsSetDict) { + // 获取所有字典 + const res = await getDictApi() + if (res) { + dictStore.setDictObj(res.data) + dictStore.setIsSetDict(true) + } + } + + // 开发者可根据实际情况进行修改 + const roleRouters = wsCache.get('roleRouters') || [] + const userInfo = wsCache.get(appStore.getUserInfo) + + // 是否使用动态路由 + if (appStore.getDynamicRouter) { + userInfo.role === 'admin' + ? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[]) + : await permissionStore.generateRoutes('test', roleRouters as string[]) + } else { + await permissionStore.generateRoutes('none') + } + + permissionStore.getAddRouters.forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表 + }) + const redirectPath = from.query.redirect || to.path + const redirect = decodeURIComponent(redirectPath as string) + const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect } + permissionStore.setIsAddRouters(true) + next(nextData) + } + } else { + if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 + } + } +}) + +router.afterEach((to) => { + useTitle(to?.meta?.title as string) + done() // 结束Progress + loadDone() +}) diff --git a/kinit-admin/src/plugins/animate.css/index.ts b/kinit-admin/src/plugins/animate.css/index.ts new file mode 100644 index 0000000..3e93451 --- /dev/null +++ b/kinit-admin/src/plugins/animate.css/index.ts @@ -0,0 +1 @@ +import 'animate.css' diff --git a/kinit-admin/src/plugins/echarts/index.ts b/kinit-admin/src/plugins/echarts/index.ts new file mode 100644 index 0000000..34f756f --- /dev/null +++ b/kinit-admin/src/plugins/echarts/index.ts @@ -0,0 +1,41 @@ +import * as echarts from 'echarts/core' + +import { + BarChart, + LineChart, + PieChart, + MapChart, + PictorialBarChart, + RadarChart +} from 'echarts/charts' + +import { + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + LegendComponent +} from 'echarts/components' + +import { CanvasRenderer } from 'echarts/renderers' + +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + BarChart, + LineChart, + PieChart, + MapChart, + CanvasRenderer, + PictorialBarChart, + RadarChart +]) + +export default echarts diff --git a/kinit-admin/src/plugins/elementPlus/index.ts b/kinit-admin/src/plugins/elementPlus/index.ts new file mode 100644 index 0000000..a5362a1 --- /dev/null +++ b/kinit-admin/src/plugins/elementPlus/index.ts @@ -0,0 +1,18 @@ +import type { App } from 'vue' + +// 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题 +import { ElLoading, ElScrollbar } from 'element-plus' + +const plugins = [ElLoading] + +const components = [ElScrollbar] + +export const setupElementPlus = (app: App) => { + plugins.forEach((plugin) => { + app.use(plugin) + }) + + components.forEach((component) => { + app.component(component.name, component) + }) +} diff --git a/kinit-admin/src/plugins/svgIcon/index.ts b/kinit-admin/src/plugins/svgIcon/index.ts new file mode 100644 index 0000000..b5b7f70 --- /dev/null +++ b/kinit-admin/src/plugins/svgIcon/index.ts @@ -0,0 +1,3 @@ +import 'virtual:svg-icons-register' + +import '@purge-icons/generated' diff --git a/kinit-admin/src/plugins/vueI18n/helper.ts b/kinit-admin/src/plugins/vueI18n/helper.ts new file mode 100644 index 0000000..da6bc8c --- /dev/null +++ b/kinit-admin/src/plugins/vueI18n/helper.ts @@ -0,0 +1,3 @@ +export const setHtmlPageLang = (locale: LocaleType) => { + document.querySelector('html')?.setAttribute('lang', locale) +} diff --git a/kinit-admin/src/plugins/vueI18n/index.ts b/kinit-admin/src/plugins/vueI18n/index.ts new file mode 100644 index 0000000..f845b13 --- /dev/null +++ b/kinit-admin/src/plugins/vueI18n/index.ts @@ -0,0 +1,42 @@ +import type { App } from 'vue' +import { createI18n } from 'vue-i18n' +import { useLocaleStoreWithOut } from '@/store/modules/locale' +import type { I18n, I18nOptions } from 'vue-i18n' +import { setHtmlPageLang } from './helper' + +export let i18n: ReturnType + +const createI18nOptions = async (): Promise => { + const localeStore = useLocaleStoreWithOut() + const locale = localeStore.getCurrentLocale + const localeMap = localeStore.getLocaleMap + const defaultLocal = await import(`../../locales/${locale.lang}.ts`) + const message = defaultLocal.default ?? {} + + setHtmlPageLang(locale.lang) + + localeStore.setCurrentLocale({ + lang: locale.lang + // elLocale: elLocal + }) + + return { + legacy: false, + locale: locale.lang, + fallbackLocale: locale.lang, + messages: { + [locale.lang]: message + }, + availableLocales: localeMap.map((v) => v.lang), + sync: true, + silentTranslationWarn: true, + missingWarn: false, + silentFallbackWarn: true + } +} + +export const setupI18n = async (app: App) => { + const options = await createI18nOptions() + i18n = createI18n(options) as I18n + app.use(i18n) +} diff --git a/kinit-admin/src/plugins/windi.css/index.ts b/kinit-admin/src/plugins/windi.css/index.ts new file mode 100644 index 0000000..dbdfbbf --- /dev/null +++ b/kinit-admin/src/plugins/windi.css/index.ts @@ -0,0 +1,3 @@ +import 'virtual:windi.css' + +import 'virtual:windi-devtools' diff --git a/kinit-admin/src/router/index.ts b/kinit-admin/src/router/index.ts new file mode 100644 index 0000000..edb4b68 --- /dev/null +++ b/kinit-admin/src/router/index.ts @@ -0,0 +1,569 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import type { RouteRecordRaw } from 'vue-router' +import type { App } from 'vue' +import { Layout, getParentLayout } from '@/utils/routerHelper' +import { useI18n } from '@/hooks/web/useI18n' + +const { t } = useI18n() + +export const constantRouterMap: AppRouteRecordRaw[] = [ + { + path: '/', + component: Layout, + redirect: '/dashboard/analysis', + name: 'Root', + meta: { + hidden: true + } + }, + { + path: '/redirect', + component: Layout, + name: 'Redirect', + children: [ + { + path: '/redirect/:path(.*)', + name: 'Redirect', + component: () => import('@/views/Redirect/Redirect.vue'), + meta: {} + } + ], + meta: { + hidden: true, + noTagsView: true + } + }, + { + path: '/login', + component: () => import('@/views/Login/Login.vue'), + name: 'Login', + meta: { + hidden: true, + title: t('router.login'), + noTagsView: true + } + }, + { + path: '/404', + component: () => import('@/views/Error/404.vue'), + name: 'NoFind', + meta: { + hidden: true, + title: '404', + noTagsView: true + } + } +] + +export const asyncRouterMap: AppRouteRecordRaw[] = [ + { + path: '/dashboard', + component: Layout, + redirect: '/dashboard/analysis', + name: 'Dashboard', + meta: { + title: t('router.dashboard'), + icon: 'ant-design:dashboard-filled', + alwaysShow: true + }, + children: [ + { + path: 'analysis', + component: () => import('@/views/Dashboard/Analysis.vue'), + name: 'Analysis', + meta: { + title: t('router.analysis'), + noCache: true, + affix: true + } + }, + { + path: 'workplace', + component: () => import('@/views/Dashboard/Workplace.vue'), + name: 'Workplace', + meta: { + title: t('router.workplace'), + noCache: true + } + } + ] + }, + { + path: '/external-link', + component: Layout, + meta: {}, + name: 'ExternalLink', + children: [ + { + path: 'https://element-plus-admin-doc.cn/', + name: 'DocumentLink', + meta: { + title: t('router.document'), + icon: 'clarity:document-solid' + } + } + ] + }, + { + path: '/guide', + component: Layout, + name: 'Guide', + meta: {}, + children: [ + { + path: 'index', + component: () => import('@/views/Guide/Guide.vue'), + name: 'GuideDemo', + meta: { + title: t('router.guide'), + icon: 'cib:telegram-plane' + } + } + ] + }, + { + path: '/components', + component: Layout, + name: 'ComponentsDemo', + meta: { + title: t('router.component'), + icon: 'bx:bxs-component', + alwaysShow: true + }, + children: [ + { + path: 'form', + component: getParentLayout(), + redirect: '/components/form/default-form', + name: 'Form', + meta: { + title: t('router.form'), + alwaysShow: true + }, + children: [ + { + path: 'default-form', + component: () => import('@/views/Components/Form/DefaultForm.vue'), + name: 'DefaultForm', + meta: { + title: t('router.defaultForm') + } + }, + { + path: 'use-form', + component: () => import('@/views/Components/Form/UseFormDemo.vue'), + name: 'UseForm', + meta: { + title: 'UseForm' + } + }, + { + path: 'ref-form', + component: () => import('@/views/Components/Form/RefForm.vue'), + name: 'RefForm', + meta: { + title: 'RefForm' + } + } + ] + }, + { + path: 'table', + component: getParentLayout(), + redirect: '/components/table/default-table', + name: 'TableDemo', + meta: { + title: t('router.table'), + alwaysShow: true + }, + children: [ + { + path: 'default-table', + component: () => import('@/views/Components/Table/DefaultTable.vue'), + name: 'DefaultTable', + meta: { + title: t('router.defaultTable') + } + }, + { + path: 'use-table', + component: () => import('@/views/Components/Table/UseTableDemo.vue'), + name: 'UseTable', + meta: { + title: 'UseTable' + } + }, + { + path: 'ref-table', + component: () => import('@/views/Components/Table/RefTable.vue'), + name: 'RefTable', + meta: { + title: 'RefTable' + } + } + ] + }, + { + path: 'editor-demo', + component: getParentLayout(), + redirect: '/components/editor-demo/editor', + name: 'EditorDemo', + meta: { + title: t('router.editor'), + alwaysShow: true + }, + children: [ + { + path: 'editor', + component: () => import('@/views/Components/Editor/Editor.vue'), + name: 'Editor', + meta: { + title: t('router.richText') + } + } + ] + }, + { + path: 'search', + component: () => import('@/views/Components/Search.vue'), + name: 'Search', + meta: { + title: t('router.search') + } + }, + { + path: 'descriptions', + component: () => import('@/views/Components/Descriptions.vue'), + name: 'Descriptions', + meta: { + title: t('router.descriptions') + } + }, + { + path: 'image-viewer', + component: () => import('@/views/Components/ImageViewer.vue'), + name: 'ImageViewer', + meta: { + title: t('router.imageViewer') + } + }, + { + path: 'dialog', + component: () => import('@/views/Components/Dialog.vue'), + name: 'Dialog', + meta: { + title: t('router.dialog') + } + }, + { + path: 'icon', + component: () => import('@/views/Components/Icon.vue'), + name: 'Icon', + meta: { + title: t('router.icon') + } + }, + { + path: 'echart', + component: () => import('@/views/Components/Echart.vue'), + name: 'Echart', + meta: { + title: t('router.echart') + } + }, + { + path: 'count-to', + component: () => import('@/views/Components/CountTo.vue'), + name: 'CountTo', + meta: { + title: t('router.countTo') + } + }, + { + path: 'qrcode', + component: () => import('@/views/Components/Qrcode.vue'), + name: 'Qrcode', + meta: { + title: t('router.qrcode') + } + }, + { + path: 'highlight', + component: () => import('@/views/Components/Highlight.vue'), + name: 'Highlight', + meta: { + title: t('router.highlight') + } + }, + { + path: 'infotip', + component: () => import('@/views/Components/Infotip.vue'), + name: 'Infotip', + meta: { + title: t('router.infotip') + } + }, + { + path: 'input-password', + component: () => import('@/views/Components/InputPassword.vue'), + name: 'InputPassword', + meta: { + title: t('router.inputPassword') + } + }, + { + path: 'sticky', + component: () => import('@/views/Components/Sticky.vue'), + name: 'Sticky', + meta: { + title: t('router.sticky') + } + } + ] + }, + { + path: '/hooks', + component: Layout, + redirect: '/hooks/useWatermark', + name: 'Hooks', + meta: { + title: 'hooks', + icon: 'ic:outline-webhook', + alwaysShow: true + }, + children: [ + { + path: 'useWatermark', + component: () => import('@/views/hooks/useWatermark.vue'), + name: 'UseWatermark', + meta: { + title: 'useWatermark' + } + }, + { + path: 'useCrudSchemas', + component: () => import('@/views/hooks/useCrudSchemas.vue'), + name: 'UseCrudSchemas', + meta: { + title: 'useCrudSchemas' + } + } + ] + }, + { + path: '/level', + component: Layout, + redirect: '/level/menu1/menu1-1/menu1-1-1', + name: 'Level', + meta: { + title: t('router.level'), + icon: 'carbon:skill-level-advanced' + }, + children: [ + { + path: 'menu1', + name: 'Menu1', + component: getParentLayout(), + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: t('router.menu1') + }, + children: [ + { + path: 'menu1-1', + name: 'Menu11', + component: getParentLayout(), + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: t('router.menu11'), + alwaysShow: true + }, + children: [ + { + path: 'menu1-1-1', + name: 'Menu111', + component: () => import('@/views/Level/Menu111.vue'), + meta: { + title: t('router.menu111') + } + } + ] + }, + { + path: 'menu1-2', + name: 'Menu12', + component: () => import('@/views/Level/Menu12.vue'), + meta: { + title: t('router.menu12') + } + } + ] + }, + { + path: 'menu2', + name: 'Menu2', + component: () => import('@/views/Level/Menu2.vue'), + meta: { + title: t('router.menu2') + } + } + ] + }, + { + path: '/example', + component: Layout, + redirect: '/example/example-dialog', + name: 'Example', + meta: { + title: t('router.example'), + icon: 'ep:management', + alwaysShow: true + }, + children: [ + { + path: 'example-dialog', + component: () => import('@/views/Example/Dialog/ExampleDialog.vue'), + name: 'ExampleDialog', + meta: { + title: t('router.exampleDialog') + } + }, + { + path: 'example-page', + component: () => import('@/views/Example/Page/ExamplePage.vue'), + name: 'ExamplePage', + meta: { + title: t('router.examplePage') + } + }, + { + path: 'example-add', + component: () => import('@/views/Example/Page/ExampleAdd.vue'), + name: 'ExampleAdd', + meta: { + title: t('router.exampleAdd'), + noTagsView: true, + noCache: true, + hidden: true, + canTo: true, + activeMenu: '/example/example-page' + } + }, + { + path: 'example-edit', + component: () => import('@/views/Example/Page/ExampleEdit.vue'), + name: 'ExampleEdit', + meta: { + title: t('router.exampleEdit'), + noTagsView: true, + noCache: true, + hidden: true, + canTo: true, + activeMenu: '/example/example-page' + } + }, + { + path: 'example-detail', + component: () => import('@/views/Example/Page/ExampleDetail.vue'), + name: 'ExampleDetail', + meta: { + title: t('router.exampleDetail'), + noTagsView: true, + noCache: true, + hidden: true, + canTo: true, + activeMenu: '/example/example-page' + } + } + ] + }, + { + path: '/error', + component: Layout, + redirect: '/error/404', + name: 'Error', + meta: { + title: t('router.errorPage'), + icon: 'ci:error', + alwaysShow: true + }, + children: [ + { + path: '404-demo', + component: () => import('@/views/Error/404.vue'), + name: '404Demo', + meta: { + title: '404' + } + }, + { + path: '403-demo', + component: () => import('@/views/Error/403.vue'), + name: '403Demo', + meta: { + title: '403' + } + }, + { + path: '500-demo', + component: () => import('@/views/Error/500.vue'), + name: '500Demo', + meta: { + title: '500' + } + } + ] + } + // { + // path: '/authorization', + // component: Layout, + // redirect: '/authorization/user', + // name: 'Authorization', + // meta: { + // title: t('router.authorization'), + // icon: 'eos-icons:role-binding', + // alwaysShow: true + // }, + // children: [ + // { + // path: 'user', + // component: () => import('@/views/Authorization/User.vue'), + // name: 'User', + // meta: { + // title: t('router.user') + // } + // }, + // { + // path: 'role', + // component: () => import('@/views/Authorization/Role.vue'), + // name: 'Role', + // meta: { + // title: t('router.role') + // } + // } + // ] + // } +] + +const router = createRouter({ + history: createWebHashHistory(), + strict: true, + routes: constantRouterMap as RouteRecordRaw[], + scrollBehavior: () => ({ left: 0, top: 0 }) +}) + +export const resetRouter = (): void => { + const resetWhiteNameList = ['Redirect', 'Login', 'NoFind', 'Root'] + router.getRoutes().forEach((route) => { + const { name } = route + if (name && !resetWhiteNameList.includes(name as string)) { + router.hasRoute(name) && router.removeRoute(name) + } + }) +} + +export const setupRouter = (app: App) => { + app.use(router) +} + +export default router diff --git a/kinit-admin/src/store/index.ts b/kinit-admin/src/store/index.ts new file mode 100644 index 0000000..4038068 --- /dev/null +++ b/kinit-admin/src/store/index.ts @@ -0,0 +1,13 @@ +import type { App } from 'vue' +import { createPinia } from 'pinia' +import piniaPluginPersist from 'pinia-plugin-persist' + +const store = createPinia() + +store.use(piniaPluginPersist) + +export const setupStore = (app: App) => { + app.use(store) +} + +export { store } diff --git a/kinit-admin/src/store/modules/app.ts b/kinit-admin/src/store/modules/app.ts new file mode 100644 index 0000000..beb6884 --- /dev/null +++ b/kinit-admin/src/store/modules/app.ts @@ -0,0 +1,184 @@ +import { defineStore } from 'pinia' +import { store } from '../index' +import { useCache } from '@/hooks/web/useCache' +import { appModules } from '@/config/app' +import type { AppState, LayoutType, ThemeTypes } from '@/config/app' +import { setCssVar, humpToUnderline } from '@/utils' +import { ElMessage } from 'element-plus' + +const { wsCache } = useCache() + +export const useAppStore = defineStore({ + id: 'app', + state: (): AppState => appModules, + persist: { + enabled: true + }, + getters: { + getBreadcrumb(): boolean { + return this.breadcrumb + }, + getBreadcrumbIcon(): boolean { + return this.breadcrumbIcon + }, + getCollapse(): boolean { + return this.collapse + }, + getUniqueOpened(): boolean { + return this.uniqueOpened + }, + getHamburger(): boolean { + return this.hamburger + }, + getScreenfull(): boolean { + return this.screenfull + }, + getSize(): boolean { + return this.size + }, + getLocale(): boolean { + return this.locale + }, + getTagsView(): boolean { + return this.tagsView + }, + getTagsViewIcon(): boolean { + return this.tagsViewIcon + }, + getLogo(): boolean { + return this.logo + }, + getFixedHeader(): boolean { + return this.fixedHeader + }, + getGreyMode(): boolean { + return this.greyMode + }, + getDynamicRouter(): boolean { + return this.dynamicRouter + }, + getPageLoading(): boolean { + return this.pageLoading + }, + getLayout(): LayoutType { + return this.layout + }, + getTitle(): string { + return this.title + }, + getUserInfo(): string { + return this.userInfo + }, + getIsDark(): boolean { + return this.isDark + }, + getCurrentSize(): ElememtPlusSize { + return this.currentSize + }, + getSizeMap(): ElememtPlusSize[] { + return this.sizeMap + }, + getMobile(): boolean { + return this.mobile + }, + getTheme(): ThemeTypes { + return this.theme + }, + getFooter(): boolean { + return this.footer + } + }, + actions: { + setBreadcrumb(breadcrumb: boolean) { + this.breadcrumb = breadcrumb + }, + setBreadcrumbIcon(breadcrumbIcon: boolean) { + this.breadcrumbIcon = breadcrumbIcon + }, + setCollapse(collapse: boolean) { + this.collapse = collapse + }, + setUniqueOpened(uniqueOpened: boolean) { + this.uniqueOpened = uniqueOpened + }, + setHamburger(hamburger: boolean) { + this.hamburger = hamburger + }, + setScreenfull(screenfull: boolean) { + this.screenfull = screenfull + }, + setSize(size: boolean) { + this.size = size + }, + setLocale(locale: boolean) { + this.locale = locale + }, + setTagsView(tagsView: boolean) { + this.tagsView = tagsView + }, + setTagsViewIcon(tagsViewIcon: boolean) { + this.tagsViewIcon = tagsViewIcon + }, + setLogo(logo: boolean) { + this.logo = logo + }, + setFixedHeader(fixedHeader: boolean) { + this.fixedHeader = fixedHeader + }, + setGreyMode(greyMode: boolean) { + this.greyMode = greyMode + }, + setDynamicRouter(dynamicRouter: boolean) { + wsCache.set('dynamicRouter', dynamicRouter) + this.dynamicRouter = dynamicRouter + }, + setPageLoading(pageLoading: boolean) { + this.pageLoading = pageLoading + }, + setLayout(layout: LayoutType) { + if (this.mobile && layout !== 'classic') { + ElMessage.warning('移动端模式下不支持切换其他布局') + return + } + this.layout = layout + wsCache.set('layout', this.layout) + }, + setTitle(title: string) { + this.title = title + }, + setIsDark(isDark: boolean) { + this.isDark = isDark + if (this.isDark) { + document.documentElement.classList.add('dark') + document.documentElement.classList.remove('light') + } else { + document.documentElement.classList.add('light') + document.documentElement.classList.remove('dark') + } + wsCache.set('isDark', this.isDark) + }, + setCurrentSize(currentSize: ElememtPlusSize) { + this.currentSize = currentSize + wsCache.set('currentSize', this.currentSize) + }, + setMobile(mobile: boolean) { + this.mobile = mobile + }, + setTheme(theme: ThemeTypes) { + this.theme = Object.assign(this.theme, theme) + wsCache.set('theme', this.theme) + }, + setCssVarTheme() { + for (const key in this.theme) { + setCssVar(`--${humpToUnderline(key)}`, this.theme[key]) + } + }, + setFooter(footer: boolean) { + this.footer = footer + } + } +}) + +export const useAppStoreWithOut = () => { + return useAppStore(store) +} diff --git a/kinit-admin/src/store/modules/dict.ts b/kinit-admin/src/store/modules/dict.ts new file mode 100644 index 0000000..446b925 --- /dev/null +++ b/kinit-admin/src/store/modules/dict.ts @@ -0,0 +1,38 @@ +import { defineStore } from 'pinia' +import { store } from '../index' + +export interface DictState { + isSetDict: boolean + dictObj: Recordable +} + +export const useDictStore = defineStore({ + id: 'dict', + state: (): DictState => ({ + isSetDict: false, + dictObj: {} + }), + persist: { + enabled: true + }, + getters: { + getDictObj(): Recordable { + return this.dictObj + }, + getIsSetDict(): boolean { + return this.isSetDict + } + }, + actions: { + setDictObj(dictObj: Recordable) { + this.dictObj = dictObj + }, + setIsSetDict(isSetDict: boolean) { + this.isSetDict = isSetDict + } + } +}) + +export const useDictStoreWithOut = () => { + return useDictStore(store) +} diff --git a/kinit-admin/src/store/modules/locale.ts b/kinit-admin/src/store/modules/locale.ts new file mode 100644 index 0000000..4b7d064 --- /dev/null +++ b/kinit-admin/src/store/modules/locale.ts @@ -0,0 +1,35 @@ +import { defineStore } from 'pinia' +import { store } from '../index' +import { useCache } from '@/hooks/web/useCache' +import { localeModules, elLocaleMap } from '@/config/locale' +import type { LocaleState } from '@/config/locale' + +const { wsCache } = useCache() + +export const useLocaleStore = defineStore({ + id: 'locales', + state: (): LocaleState => localeModules, + persist: { + enabled: true + }, + getters: { + getCurrentLocale(): LocaleDropdownType { + return this.currentLocale + }, + getLocaleMap(): LocaleDropdownType[] { + return this.localeMap + } + }, + actions: { + setCurrentLocale(localeMap: LocaleDropdownType) { + // this.locale = Object.assign(this.locale, localeMap) + this.currentLocale.lang = localeMap?.lang + this.currentLocale.elLocale = elLocaleMap[localeMap?.lang] + wsCache.set('lang', localeMap?.lang) + } + } +}) + +export const useLocaleStoreWithOut = () => { + return useLocaleStore(store) +} diff --git a/kinit-admin/src/store/modules/permission.ts b/kinit-admin/src/store/modules/permission.ts new file mode 100644 index 0000000..22ca1a5 --- /dev/null +++ b/kinit-admin/src/store/modules/permission.ts @@ -0,0 +1,84 @@ +import { defineStore } from 'pinia' +import { asyncRouterMap, constantRouterMap } from '@/router' +import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper' +import { store } from '../index' +import { cloneDeep } from 'lodash-es' + +export interface PermissionState { + routers: AppRouteRecordRaw[] + addRouters: AppRouteRecordRaw[] + isAddRouters: boolean + menuTabRouters: AppRouteRecordRaw[] +} + +export const usePermissionStore = defineStore({ + id: 'permission', + state: (): PermissionState => ({ + routers: [], + addRouters: [], + isAddRouters: false, + menuTabRouters: [] + }), + persist: { + enabled: true + }, + getters: { + getRouters(): AppRouteRecordRaw[] { + return this.routers + }, + getAddRouters(): AppRouteRecordRaw[] { + return flatMultiLevelRoutes(cloneDeep(this.addRouters)) + }, + getIsAddRouters(): boolean { + return this.isAddRouters + }, + getMenuTabRouters(): AppRouteRecordRaw[] { + return this.menuTabRouters + } + }, + actions: { + generateRoutes( + type: 'admin' | 'test' | 'none', + routers?: AppCustomRouteRecordRaw[] | string[] + ): Promise { + return new Promise((resolve) => { + let routerMap: AppRouteRecordRaw[] = [] + if (type === 'admin') { + // 模拟后端过滤菜单 + routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[]) + } else if (type === 'test') { + // 模拟前端过滤菜单 + routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[]) + } else { + // 直接读取静态路由表 + routerMap = cloneDeep(asyncRouterMap) + } + // 动态路由,404一定要放到最后面 + this.addRouters = routerMap.concat([ + { + path: '/:path(.*)*', + redirect: '/404', + name: '404Page', + meta: { + hidden: true, + breadcrumb: false + } + } + ]) + // 渲染菜单的所有路由 + this.routers = cloneDeep(constantRouterMap).concat(routerMap) + resolve() + }) + }, + setIsAddRouters(state: boolean): void { + this.isAddRouters = state + }, + setMenuTabRouters(routers: AppRouteRecordRaw[]): void { + this.menuTabRouters = routers + } + } +}) + +export const usePermissionStoreWithOut = () => { + return usePermissionStore(store) +} diff --git a/kinit-admin/src/store/modules/tagsView.ts b/kinit-admin/src/store/modules/tagsView.ts new file mode 100644 index 0000000..33da805 --- /dev/null +++ b/kinit-admin/src/store/modules/tagsView.ts @@ -0,0 +1,141 @@ +import router from '@/router' +import type { RouteLocationNormalizedLoaded } from 'vue-router' +import { getRawRoute } from '@/utils/routerHelper' +import { defineStore } from 'pinia' +import { store } from '../index' +import { findIndex } from '@/utils' + +export interface TagsViewState { + visitedViews: RouteLocationNormalizedLoaded[] + cachedViews: Set +} + +export const useTagsViewStore = defineStore({ + id: 'tagsView', + state: (): TagsViewState => ({ + visitedViews: [], + cachedViews: new Set() + }), + getters: { + getVisitedViews(): RouteLocationNormalizedLoaded[] { + return this.visitedViews + }, + getCachedViews(): string[] { + return Array.from(this.cachedViews) + } + }, + actions: { + // 新增缓存和tag + addView(view: RouteLocationNormalizedLoaded): void { + this.addVisitedView(view) + this.addCachedView() + }, + // 新增tag + addVisitedView(view: RouteLocationNormalizedLoaded) { + if (this.visitedViews.some((v) => v.path === view.path)) return + if (view.meta?.noTagsView) return + this.visitedViews.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ) + }, + // 新增缓存 + addCachedView() { + const cacheMap: Set = new Set() + for (const v of this.visitedViews) { + const item = getRawRoute(v) + const needCache = !item.meta?.noCache + if (!needCache) { + continue + } + const name = item.name as string + cacheMap.add(name) + } + if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString()) + return + this.cachedViews = cacheMap + }, + // 删除某个 + delView(view: RouteLocationNormalizedLoaded) { + this.delVisitedView(view) + this.addCachedView() + }, + // 删除tag + delVisitedView(view: RouteLocationNormalizedLoaded) { + for (const [i, v] of this.visitedViews.entries()) { + if (v.path === view.path) { + this.visitedViews.splice(i, 1) + break + } + } + }, + // 删除缓存 + delCachedView() { + const route = router.currentRoute.value + const index = findIndex(this.getCachedViews, (v) => v === route.name) + if (index > -1) { + this.cachedViews.delete(this.getCachedViews[index]) + } + }, + // 删除所有缓存和tag + delAllViews() { + this.delAllVisitedViews() + this.addCachedView() + }, + // 删除所有tag + delAllVisitedViews() { + // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix) + this.visitedViews = [] + }, + // 删除其他 + delOthersViews(view: RouteLocationNormalizedLoaded) { + this.delOthersVisitedViews(view) + this.addCachedView() + }, + // 删除其他tag + delOthersVisitedViews(view: RouteLocationNormalizedLoaded) { + this.visitedViews = this.visitedViews.filter((v) => { + return v?.meta?.affix || v.path === view.path + }) + }, + // 删除左侧 + delLeftViews(view: RouteLocationNormalizedLoaded) { + const index = findIndex( + this.visitedViews, + (v) => v.path === view.path + ) + if (index > -1) { + this.visitedViews = this.visitedViews.filter((v, i) => { + return v?.meta?.affix || v.path === view.path || i > index + }) + this.addCachedView() + } + }, + // 删除右侧 + delRightViews(view: RouteLocationNormalizedLoaded) { + const index = findIndex( + this.visitedViews, + (v) => v.path === view.path + ) + if (index > -1) { + this.visitedViews = this.visitedViews.filter((v, i) => { + return v?.meta?.affix || v.path === view.path || i < index + }) + this.addCachedView() + } + }, + updateVisitedView(view: RouteLocationNormalizedLoaded) { + for (let v of this.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + } + } +}) + +export const useTagsViewStoreWithOut = () => { + return useTagsViewStore(store) +} diff --git a/kinit-admin/src/styles/index.less b/kinit-admin/src/styles/index.less new file mode 100644 index 0000000..c023316 --- /dev/null +++ b/kinit-admin/src/styles/index.less @@ -0,0 +1,2 @@ +@import './var.css'; +@import 'element-plus/theme-chalk/dark/css-vars.css'; diff --git a/kinit-admin/src/styles/theme.less b/kinit-admin/src/styles/theme.less new file mode 100644 index 0000000..39b03b3 --- /dev/null +++ b/kinit-admin/src/styles/theme.less @@ -0,0 +1,6 @@ +// .text-color { +// color: var(--el-text-color-regular); +// } +// .dark .dark\:text-color { +// color: rgba(255, 255, 255, var(--dark-text-color)); +// } diff --git a/kinit-admin/src/styles/var.css b/kinit-admin/src/styles/var.css new file mode 100644 index 0000000..0d7d0df --- /dev/null +++ b/kinit-admin/src/styles/var.css @@ -0,0 +1,67 @@ +:root { + --dark-bg-color: #293146; + + /* left menu start */ + --left-menu-border-color: '#eee'; + + --left-menu-max-width: 200px; + + --left-menu-min-width: 64px; + + --left-menu-bg-color: #001529; + + --left-menu-bg-light-color: #0f2438; + + --left-menu-bg-active-color: var(--el-color-primary); + + --left-menu-text-color: #bfcbd9; + + --left-menu-text-active-color: #fff; + + --left-menu-collapse-bg-active-color: var(--el-color-primary); + /* left menu end */ + + /* logo start */ + --logo-height: 50px; + + --logo-title-text-color: #fff; + + --logo-border-color: 'inherit'; + /* logo end */ + + /* header start */ + --top-header-bg-color: '#fff'; + + --top-header-text-color: 'inherit'; + + --top-header-hover-color: #f6f6f6; + + --top-tool-height: var(--logo-height); + + --top-tool-p-x: 0; + + --top-tool-border-color: #eee; + + --tags-view-height: 35px; + + --tags-view-border-color: #eee; + /* header start */ + + /* tab menu start */ + --tab-menu-max-width: 80px; + + --tab-menu-min-width: 30px; + + --tab-menu-collapse-height: 36px; + + --tab-menu-border-color: #eee; + /* tab menu end */ + + --app-content-padding: 20px; + + --app-content-bg-color: #f5f7f9; + + --app-footer-height: 50px; + + --transition-time-02: 0.2s; +} diff --git a/kinit-admin/src/styles/variables.module.less b/kinit-admin/src/styles/variables.module.less new file mode 100644 index 0000000..e97028d --- /dev/null +++ b/kinit-admin/src/styles/variables.module.less @@ -0,0 +1,10 @@ +// 命名空间 +@namespace: v; +// el命名空间 +@elNamespace: el; + +// 导出变量 +:export { + namespace: @namespace; + elNamespace: @elNamespace; +} diff --git a/kinit-admin/src/utils/color.ts b/kinit-admin/src/utils/color.ts new file mode 100644 index 0000000..6888583 --- /dev/null +++ b/kinit-admin/src/utils/color.ts @@ -0,0 +1,153 @@ +/** + * 判断是否 十六进制颜色值. + * 输入形式可为 #fff000 #f00 + * + * @param String color 十六进制颜色值 + * @return Boolean + */ +export const isHexColor = (color: string) => { + const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/ + return reg.test(color) +} + +/** + * RGB 颜色值转换为 十六进制颜色值. + * r, g, 和 b 需要在 [0, 255] 范围内 + * + * @return String 类似#ff00ff + * @param r + * @param g + * @param b + */ +export const rgbToHex = (r: number, g: number, b: number) => { + // tslint:disable-next-line:no-bitwise + const hex = ((r << 16) | (g << 8) | b).toString(16) + return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex +} + +/** + * Transform a HEX color to its RGB representation + * @param {string} hex The color to transform + * @returns The RGB representation of the passed color + */ +export const hexToRGB = (hex: string, opacity?: number) => { + let sHex = hex.toLowerCase() + if (isHexColor(hex)) { + if (sHex.length === 4) { + let sColorNew = '#' + for (let i = 1; i < 4; i += 1) { + sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1)) + } + sHex = sColorNew + } + const sColorChange: number[] = [] + for (let i = 1; i < 7; i += 2) { + sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2))) + } + return opacity + ? 'RGBA(' + sColorChange.join(',') + ',' + opacity + ')' + : 'RGB(' + sColorChange.join(',') + ')' + } + return sHex +} + +export const colorIsDark = (color: string) => { + if (!isHexColor(color)) return + const [r, g, b] = hexToRGB(color) + .replace(/(?:\(|\)|rgb|RGB)*/g, '') + .split(',') + .map((item) => Number(item)) + return r * 0.299 + g * 0.578 + b * 0.114 < 192 +} + +/** + * Darkens a HEX color given the passed percentage + * @param {string} color The color to process + * @param {number} amount The amount to change the color by + * @returns {string} The HEX representation of the processed color + */ +export const darken = (color: string, amount: number) => { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color + amount = Math.trunc((255 * amount) / 100) + return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight( + color.substring(2, 4), + amount + )}${subtractLight(color.substring(4, 6), amount)}` +} + +/** + * Lightens a 6 char HEX color according to the passed percentage + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed color represented as HEX + */ +export const lighten = (color: string, amount: number) => { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color + amount = Math.trunc((255 * amount) / 100) + return `#${addLight(color.substring(0, 2), amount)}${addLight( + color.substring(2, 4), + amount + )}${addLight(color.substring(4, 6), amount)}` +} + +/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */ +/** + * Sums the passed percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +const addLight = (color: string, amount: number) => { + const cc = parseInt(color, 16) + amount + const c = cc > 255 ? 255 : cc + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` +} + +/** + * Calculates luminance of an rgb color + * @param {number} r red + * @param {number} g green + * @param {number} b blue + */ +const luminanace = (r: number, g: number, b: number) => { + const a = [r, g, b].map((v) => { + v /= 255 + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4) + }) + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722 +} + +/** + * Calculates contrast between two rgb colors + * @param {string} rgb1 rgb color 1 + * @param {string} rgb2 rgb color 2 + */ +const contrast = (rgb1: string[], rgb2: number[]) => { + return ( + (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / + (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05) + ) +} + +/** + * Determines what the best text color is (black or white) based con the contrast with the background + * @param hexColor - Last selected color by the user + */ +export const calculateBestTextColor = (hexColor: string) => { + const rgbColor = hexToRGB(hexColor.substring(1)) + const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]) + + return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF' +} + +/** + * Subtracts the indicated percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +const subtractLight = (color: string, amount: number) => { + const cc = parseInt(color, 16) - amount + const c = cc < 0 ? 0 : cc + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` +} diff --git a/kinit-admin/src/utils/domUtils.ts b/kinit-admin/src/utils/domUtils.ts new file mode 100644 index 0000000..dbc1989 --- /dev/null +++ b/kinit-admin/src/utils/domUtils.ts @@ -0,0 +1,289 @@ +import { isServer } from './is' +const ieVersion = isServer ? 0 : Number((document as any).documentMode) +const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g +const MOZ_HACK_REGEXP = /^moz([A-Z])/ + +export interface ViewportOffsetResult { + left: number + top: number + right: number + bottom: number + rightIncludeBody: number + bottomIncludeBody: number +} + +/* istanbul ignore next */ +const trim = function (string: string) { + return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '') +} + +/* istanbul ignore next */ +const camelCase = function (name: string) { + return name + .replace(SPECIAL_CHARS_REGEXP, function (_, __, letter, offset) { + return offset ? letter.toUpperCase() : letter + }) + .replace(MOZ_HACK_REGEXP, 'Moz$1') +} + +/* istanbul ignore next */ +export function hasClass(el: Element, cls: string) { + if (!el || !cls) return false + if (cls.indexOf(' ') !== -1) { + throw new Error('className should not contain space.') + } + if (el.classList) { + return el.classList.contains(cls) + } else { + return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1 + } +} + +/* istanbul ignore next */ +export function addClass(el: Element, cls: string) { + if (!el) return + let curClass = el.className + const classes = (cls || '').split(' ') + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i] + if (!clsName) continue + + if (el.classList) { + el.classList.add(clsName) + } else if (!hasClass(el, clsName)) { + curClass += ' ' + clsName + } + } + if (!el.classList) { + el.className = curClass + } +} + +/* istanbul ignore next */ +export function removeClass(el: Element, cls: string) { + if (!el || !cls) return + const classes = cls.split(' ') + let curClass = ' ' + el.className + ' ' + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i] + if (!clsName) continue + + if (el.classList) { + el.classList.remove(clsName) + } else if (hasClass(el, clsName)) { + curClass = curClass.replace(' ' + clsName + ' ', ' ') + } + } + if (!el.classList) { + el.className = trim(curClass) + } +} + +export function getBoundingClientRect(element: Element): DOMRect | number { + if (!element || !element.getBoundingClientRect) { + return 0 + } + return element.getBoundingClientRect() +} + +/** + * 获取当前元素的left、top偏移 + * left:元素最左侧距离文档左侧的距离 + * top:元素最顶端距离文档顶端的距离 + * right:元素最右侧距离文档右侧的距离 + * bottom:元素最底端距离文档底端的距离 + * rightIncludeBody:元素最左侧距离文档右侧的距离 + * bottomIncludeBody:元素最底端距离文档最底部的距离 + * + * @description: + */ +export function getViewportOffset(element: Element): ViewportOffsetResult { + const doc = document.documentElement + + const docScrollLeft = doc.scrollLeft + const docScrollTop = doc.scrollTop + const docClientLeft = doc.clientLeft + const docClientTop = doc.clientTop + + const pageXOffset = window.pageXOffset + const pageYOffset = window.pageYOffset + + const box = getBoundingClientRect(element) + + const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect + + const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0) + const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0) + const offsetLeft = retLeft + pageXOffset + const offsetTop = rectTop + pageYOffset + + const left = offsetLeft - scrollLeft + const top = offsetTop - scrollTop + + const clientWidth = window.document.documentElement.clientWidth + const clientHeight = window.document.documentElement.clientHeight + return { + left: left, + top: top, + right: clientWidth - rectWidth - left, + bottom: clientHeight - rectHeight - top, + rightIncludeBody: clientWidth - left, + bottomIncludeBody: clientHeight - top + } +} + +/* istanbul ignore next */ +export const on = function ( + element: HTMLElement | Document | Window, + event: string, + handler: EventListenerOrEventListenerObject +): void { + if (element && event && handler) { + element.addEventListener(event, handler, false) + } +} + +/* istanbul ignore next */ +export const off = function ( + element: HTMLElement | Document | Window, + event: string, + handler: any +): void { + if (element && event && handler) { + element.removeEventListener(event, handler, false) + } +} + +/* istanbul ignore next */ +export const once = function (el: HTMLElement, event: string, fn: EventListener): void { + const listener = function (this: any, ...args: unknown[]) { + if (fn) { + // @ts-ignore + fn.apply(this, args) + } + off(el, event, listener) + } + on(el, event, listener) +} + +/* istanbul ignore next */ +export const getStyle = + ieVersion < 9 + ? function (element: Element | any, styleName: string) { + if (isServer) return + if (!element || !styleName) return null + styleName = camelCase(styleName) + if (styleName === 'float') { + styleName = 'styleFloat' + } + try { + switch (styleName) { + case 'opacity': + try { + return element.filters.item('alpha').opacity / 100 + } catch (e) { + return 1.0 + } + default: + return element.style[styleName] || element.currentStyle + ? element.currentStyle[styleName] + : null + } + } catch (e) { + return element.style[styleName] + } + } + : function (element: Element | any, styleName: string) { + if (isServer) return + if (!element || !styleName) return null + styleName = camelCase(styleName) + if (styleName === 'float') { + styleName = 'cssFloat' + } + try { + const computed = (document as any).defaultView.getComputedStyle(element, '') + return element.style[styleName] || computed ? computed[styleName] : null + } catch (e) { + return element.style[styleName] + } + } + +/* istanbul ignore next */ +export function setStyle(element: Element | any, styleName: any, value: any) { + if (!element || !styleName) return + + if (typeof styleName === 'object') { + for (const prop in styleName) { + if (Object.prototype.hasOwnProperty.call(styleName, prop)) { + setStyle(element, prop, styleName[prop]) + } + } + } else { + styleName = camelCase(styleName) + if (styleName === 'opacity' && ieVersion < 9) { + element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')' + } else { + element.style[styleName] = value + } + } +} + +/* istanbul ignore next */ +export const isScroll = (el: Element, vertical: any) => { + if (isServer) return + + const determinedDirection = vertical !== null || vertical !== undefined + const overflow = determinedDirection + ? vertical + ? getStyle(el, 'overflow-y') + : getStyle(el, 'overflow-x') + : getStyle(el, 'overflow') + + return overflow.match(/(scroll|auto)/) +} + +/* istanbul ignore next */ +export const getScrollContainer = (el: Element, vertical?: any) => { + if (isServer) return + + let parent: any = el + while (parent) { + if ([window, document, document.documentElement].includes(parent)) { + return window + } + if (isScroll(parent, vertical)) { + return parent + } + parent = parent.parentNode + } + + return parent +} + +/* istanbul ignore next */ +export const isInContainer = (el: Element, container: any) => { + if (isServer || !el || !container) return false + + const elRect = el.getBoundingClientRect() + let containerRect + + if ([window, document, document.documentElement, null, undefined].includes(container)) { + containerRect = { + top: 0, + right: window.innerWidth, + bottom: window.innerHeight, + left: 0 + } + } else { + containerRect = container.getBoundingClientRect() + } + + return ( + elRect.top < containerRect.bottom && + elRect.bottom > containerRect.top && + elRect.right > containerRect.left && + elRect.left < containerRect.right + ) +} diff --git a/kinit-admin/src/utils/index.ts b/kinit-admin/src/utils/index.ts new file mode 100644 index 0000000..246de69 --- /dev/null +++ b/kinit-admin/src/utils/index.ts @@ -0,0 +1,110 @@ +// import type { Plugin } from 'vue' + +/** + * + * @param component 需要注册的组件 + * @param alias 组件别名 + * @returns any + */ +export const withInstall = (component: T, alias?: string) => { + const comp = component as any + comp.install = (app: any) => { + app.component(comp.name || comp.displayName, component) + if (alias) { + app.config.globalProperties[alias] = component + } + } + return component as T & Plugin +} + +/** + * @param str 需要转下划线的驼峰字符串 + * @returns 字符串下划线 + */ +export const humpToUnderline = (str: string): string => { + return str.replace(/([A-Z])/g, '-$1').toLowerCase() +} + +/** + * @param str 需要转驼峰的下划线字符串 + * @returns 字符串驼峰 + */ +export const underlineToHump = (str: string): string => { + if (!str) return '' + return str.replace(/\-(\w)/g, (_, letter: string) => { + return letter.toUpperCase() + }) +} + +export const setCssVar = (prop: string, val: any, dom = document.documentElement) => { + dom.style.setProperty(prop, val) +} + +/** + * 查找数组对象的某个下标 + * @param {Array} ary 查找的数组 + * @param {Functon} fn 判断的方法 + */ +// eslint-disable-next-line +export const findIndex = (ary: Array, fn: Fn): number => { + if (ary.findIndex) { + return ary.findIndex(fn) + } + let index = -1 + ary.some((item: T, i: number, ary: Array) => { + const ret: T = fn(item, i, ary) + if (ret) { + index = i + return ret + } + }) + return index +} + +export const trim = (str: string) => { + return str.replace(/(^\s*)|(\s*$)/g, '') +} + +/** + * @param {Date | number | string} time 需要转换的时间 + * @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss + */ +export function formatTime(time: Date | number | string, fmt: string) { + if (!time) return '' + else { + const date = new Date(time) + const o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'H+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + S: date.getMilliseconds() + } + if (/(y+)/.test(fmt)) { + fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) + } + for (const k in o) { + if (new RegExp('(' + k + ')').test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) + ) + } + } + return fmt + } +} + +/** + * 生成随机字符串 + */ +export function toAnyString() { + const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => { + const r: number = (Math.random() * 16) | 0 + const v: number = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString() + }) + return str +} diff --git a/kinit-admin/src/utils/is.ts b/kinit-admin/src/utils/is.ts new file mode 100644 index 0000000..3752985 --- /dev/null +++ b/kinit-admin/src/utils/is.ts @@ -0,0 +1,105 @@ +// copy to vben-admin + +const toString = Object.prototype.toString + +export const is = (val: unknown, type: string) => { + return toString.call(val) === `[object ${type}]` +} + +export const isDef = (val?: T): val is T => { + return typeof val !== 'undefined' +} + +export const isUnDef = (val?: T): val is T => { + return !isDef(val) +} + +export const isObject = (val: any): val is Record => { + return val !== null && is(val, 'Object') +} + +export const isEmpty = (val: T): val is T => { + if (isArray(val) || isString(val)) { + return val.length === 0 + } + + if (val instanceof Map || val instanceof Set) { + return val.size === 0 + } + + if (isObject(val)) { + return Object.keys(val).length === 0 + } + + return false +} + +export const isDate = (val: unknown): val is Date => { + return is(val, 'Date') +} + +export const isNull = (val: unknown): val is null => { + return val === null +} + +export const isNullAndUnDef = (val: unknown): val is null | undefined => { + return isUnDef(val) && isNull(val) +} + +export const isNullOrUnDef = (val: unknown): val is null | undefined => { + return isUnDef(val) || isNull(val) +} + +export const isNumber = (val: unknown): val is number => { + return is(val, 'Number') +} + +export const isPromise = (val: unknown): val is Promise => { + return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch) +} + +export const isString = (val: unknown): val is string => { + return is(val, 'String') +} + +export const isFunction = (val: unknown): val is Function => { + return typeof val === 'function' +} + +export const isBoolean = (val: unknown): val is boolean => { + return is(val, 'Boolean') +} + +export const isRegExp = (val: unknown): val is RegExp => { + return is(val, 'RegExp') +} + +export const isArray = (val: any): val is Array => { + return val && Array.isArray(val) +} + +export const isWindow = (val: any): val is Window => { + return typeof window !== 'undefined' && is(val, 'Window') +} + +export const isElement = (val: unknown): val is Element => { + return isObject(val) && !!val.tagName +} + +export const isMap = (val: unknown): val is Map => { + return is(val, 'Map') +} + +export const isServer = typeof window === 'undefined' + +export const isClient = !isServer + +export const isUrl = (path: string): boolean => { + const reg = + /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/ + return reg.test(path) +} + +export const isDark = (): boolean => { + return window.matchMedia('(prefers-color-scheme: dark)').matches +} diff --git a/kinit-admin/src/utils/propTypes.ts b/kinit-admin/src/utils/propTypes.ts new file mode 100644 index 0000000..5d685f3 --- /dev/null +++ b/kinit-admin/src/utils/propTypes.ts @@ -0,0 +1,29 @@ +import { createTypes, VueTypesInterface, VueTypeValidableDef } from 'vue-types' +import { CSSProperties } from 'vue' + +// 自定义扩展vue-types +type PropTypes = VueTypesInterface & { + readonly style: VueTypeValidableDef +} + +const propTypes = createTypes({ + func: undefined, + bool: undefined, + string: undefined, + number: undefined, + object: undefined, + integer: undefined +}) as PropTypes + +// 需要自定义扩展的类型 +// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method +propTypes.extend([ + { + name: 'style', + getter: true, + type: [String, Object], + default: undefined + } +]) + +export { propTypes } diff --git a/kinit-admin/src/utils/routerHelper.ts b/kinit-admin/src/utils/routerHelper.ts new file mode 100644 index 0000000..bc80677 --- /dev/null +++ b/kinit-admin/src/utils/routerHelper.ts @@ -0,0 +1,194 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import type { + Router, + RouteLocationNormalized, + RouteRecordNormalized, + RouteMeta, + RouteRecordRaw +} from 'vue-router' +import { isUrl } from '@/utils/is' +import { omit, cloneDeep } from 'lodash-es' + +const modules = import.meta.glob('../views/**/*.{vue,tsx}') + +/* Layout */ +export const Layout = () => import('@/layout/Layout.vue') + +export const getParentLayout = () => { + return () => + new Promise((resolve) => { + resolve({ + name: 'ParentLayout' + }) + }) +} + +export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormalized => { + if (!route) return route + const { matched, ...opt } = route + return { + ...opt, + matched: (matched + ? matched.map((item) => ({ + meta: item.meta, + name: item.name, + path: item.path + })) + : undefined) as RouteRecordNormalized[] + } +} + +// 前端控制路由生成 +export const generateRoutesFn1 = ( + routes: AppRouteRecordRaw[], + keys: string[], + basePath = '/' +): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + + for (const route of routes) { + const meta = route.meta as RouteMeta + // skip some route + if (meta.hidden && !meta.canTo) { + continue + } + + let data: Nullable = null + + let onlyOneChild: Nullable = null + if (route.children && route.children.length === 1 && !meta.alwaysShow) { + onlyOneChild = ( + isUrl(route.children[0].path) + ? route.children[0].path + : pathResolve(pathResolve(basePath, route.path), route.children[0].path) + ) as string + } + + // 开发者可以根据实际情况进行扩展 + for (const item of keys) { + // 通过路径去匹配 + if (isUrl(item) && (onlyOneChild === item || route.path === item)) { + data = Object.assign({}, route) + } else { + const routePath = pathResolve(basePath, onlyOneChild || route.path) + if (routePath === item || meta.followRoute === item) { + data = Object.assign({}, route) + } + } + } + + // recursive child routes + if (route.children && data) { + data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path)) + } + if (data) { + res.push(data as AppRouteRecordRaw) + } + } + return res +} + +// 后端控制路由生成 +export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => { + const res: AppRouteRecordRaw[] = [] + + for (const route of routes) { + const data: AppRouteRecordRaw = { + path: route.path, + name: route.name, + redirect: route.redirect, + meta: route.meta + } + if (route.component) { + const comModule = modules[`../${route.component}.vue`] || modules[`../${route.component}.tsx`] + const component = route.component as string + if (!comModule && !component.includes('#')) { + console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`) + } else { + // 动态加载路由文件,可根据实际情况进行自定义逻辑 + data.component = + component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule + } + } + // recursive child routes + if (route.children) { + data.children = generateRoutesFn2(route.children) + } + res.push(data as AppRouteRecordRaw) + } + return res +} + +export const pathResolve = (parentPath: string, path: string) => { + if (isUrl(path)) return path + const childPath = path.startsWith('/') || !path ? path : `/${path}` + return `${parentPath}${childPath}`.replace(/\/\//g, '/') +} + +// 路由降级 +export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => { + const modules: AppRouteRecordRaw[] = cloneDeep(routes) + for (let index = 0; index < modules.length; index++) { + const route = modules[index] + if (!isMultipleRoute(route)) { + continue + } + promoteRouteLevel(route) + } + return modules +} + +// 层级是否大于2 +const isMultipleRoute = (route: AppRouteRecordRaw) => { + if (!route || !Reflect.has(route, 'children') || !route.children?.length) { + return false + } + + const children = route.children + + let flag = false + for (let index = 0; index < children.length; index++) { + const child = children[index] + if (child.children?.length) { + flag = true + break + } + } + return flag +} + +// 生成二级路由 +const promoteRouteLevel = (route: AppRouteRecordRaw) => { + let router: Router | null = createRouter({ + routes: [route as RouteRecordRaw], + history: createWebHashHistory() + }) + + const routes = router.getRoutes() + addToChildren(routes, route.children || [], route) + router = null + + route.children = route.children?.map((item) => omit(item, 'children')) +} + +// 添加所有子菜单 +const addToChildren = ( + routes: RouteRecordNormalized[], + children: AppRouteRecordRaw[], + routeModule: AppRouteRecordRaw +) => { + for (let index = 0; index < children.length; index++) { + const child = children[index] + const route = routes.find((item) => item.name === child.name) + if (!route) { + continue + } + routeModule.children = routeModule.children || [] + if (!routeModule.children.find((item) => item.name === route.name)) { + routeModule.children?.push(route as unknown as AppRouteRecordRaw) + } + if (child.children?.length) { + addToChildren(routes, child.children, routeModule) + } + } +} diff --git a/kinit-admin/src/utils/tree.ts b/kinit-admin/src/utils/tree.ts new file mode 100644 index 0000000..fe0207d --- /dev/null +++ b/kinit-admin/src/utils/tree.ts @@ -0,0 +1,207 @@ +interface TreeHelperConfig { + id: string + children: string + pid: string +} +const DEFAULT_CONFIG: TreeHelperConfig = { + id: 'id', + children: 'children', + pid: 'pid' +} + +const getConfig = (config: Partial) => Object.assign({}, DEFAULT_CONFIG, config) + +// tree from list +export const listToTree = (list: any[], config: Partial = {}): T[] => { + const conf = getConfig(config) as TreeHelperConfig + const nodeMap = new Map() + const result: T[] = [] + const { id, children, pid } = conf + + for (const node of list) { + node[children] = node[children] || [] + nodeMap.set(node[id], node) + } + for (const node of list) { + const parent = nodeMap.get(node[pid]) + ;(parent ? parent.children : result).push(node) + } + return result +} + +export const treeToList = (tree: any, config: Partial = {}): T => { + config = getConfig(config) + const { children } = config + const result: any = [...tree] + for (let i = 0; i < result.length; i++) { + if (!result[i][children!]) continue + result.splice(i + 1, 0, ...result[i][children!]) + } + return result +} + +export const findNode = ( + tree: any, + func: Fn, + config: Partial = {} +): T | null => { + config = getConfig(config) + const { children } = config + const list = [...tree] + for (const node of list) { + if (func(node)) return node + node[children!] && list.push(...node[children!]) + } + return null +} + +export const findNodeAll = ( + tree: any, + func: Fn, + config: Partial = {} +): T[] => { + config = getConfig(config) + const { children } = config + const list = [...tree] + const result: T[] = [] + for (const node of list) { + func(node) && result.push(node) + node[children!] && list.push(...node[children!]) + } + return result +} + +export const findPath = ( + tree: any, + func: Fn, + config: Partial = {} +): T | T[] | null => { + config = getConfig(config) + const path: T[] = [] + const list = [...tree] + const visitedSet = new Set() + const { children } = config + while (list.length) { + const node = list[0] + if (visitedSet.has(node)) { + path.pop() + list.shift() + } else { + visitedSet.add(node) + node[children!] && list.unshift(...node[children!]) + path.push(node) + if (func(node)) { + return path + } + } + } + return null +} + +export const findPathAll = (tree: any, func: Fn, config: Partial = {}) => { + config = getConfig(config) + const path: any[] = [] + const list = [...tree] + const result: any[] = [] + const visitedSet = new Set(), + { children } = config + while (list.length) { + const node = list[0] + if (visitedSet.has(node)) { + path.pop() + list.shift() + } else { + visitedSet.add(node) + node[children!] && list.unshift(...node[children!]) + path.push(node) + func(node) && result.push([...path]) + } + } + return result +} + +export const filter = ( + tree: T[], + func: (n: T) => boolean, + config: Partial = {} +): T[] => { + config = getConfig(config) + const children = config.children as string + function listFilter(list: T[]) { + return list + .map((node: any) => ({ ...node })) + .filter((node) => { + node[children] = node[children] && listFilter(node[children]) + return func(node) || (node[children] && node[children].length) + }) + } + return listFilter(tree) +} + +export const forEach = ( + tree: T[], + func: (n: T) => any, + config: Partial = {} +): void => { + config = getConfig(config) + const list: any[] = [...tree] + const { children } = config + for (let i = 0; i < list.length; i++) { + // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 + if (func(list[i])) { + return + } + children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]) + } +} + +/** + * @description: Extract tree specified structure + */ +export const treeMap = ( + treeData: T[], + opt: { children?: string; conversion: Fn } +): T[] => { + return treeData.map((item) => treeMapEach(item, opt)) +} + +/** + * @description: Extract tree specified structure + */ +export const treeMapEach = ( + data: any, + { children = 'children', conversion }: { children?: string; conversion: Fn } +) => { + const haveChildren = Array.isArray(data[children]) && data[children].length > 0 + const conversionData = conversion(data) || {} + if (haveChildren) { + return { + ...conversionData, + [children]: data[children].map((i: number) => + treeMapEach(i, { + children, + conversion + }) + ) + } + } else { + return { + ...conversionData + } + } +} + +/** + * 递归遍历树结构 + * @param treeDatas 树 + * @param callBack 回调 + * @param parentNode 父节点 + */ +export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => { + treeDatas.forEach((element) => { + const newNode = callBack(element, parentNode) || element + if (element.children) { + eachTree(element.children, callBack, newNode) + } + }) +} diff --git a/kinit-admin/src/utils/tsxHelper.ts b/kinit-admin/src/utils/tsxHelper.ts new file mode 100644 index 0000000..6087fa3 --- /dev/null +++ b/kinit-admin/src/utils/tsxHelper.ts @@ -0,0 +1,16 @@ +import { Slots } from 'vue' +import { isFunction } from '@/utils/is' + +export const getSlot = (slots: Slots, slot = 'default', data?: Recordable) => { + // Reflect.has 判断一个对象是否存在某个属性 + if (!slots || !Reflect.has(slots, slot)) { + return null + } + if (!isFunction(slots[slot])) { + console.error(`${slot} is not a function!`) + return null + } + const slotFn = slots[slot] + if (!slotFn) return null + return slotFn(data) +} diff --git a/kinit-admin/src/views/Authorization/Role.vue b/kinit-admin/src/views/Authorization/Role.vue new file mode 100644 index 0000000..9ca4568 --- /dev/null +++ b/kinit-admin/src/views/Authorization/Role.vue @@ -0,0 +1,88 @@ + + + diff --git a/kinit-admin/src/views/Authorization/User.vue b/kinit-admin/src/views/Authorization/User.vue new file mode 100644 index 0000000..9ca4568 --- /dev/null +++ b/kinit-admin/src/views/Authorization/User.vue @@ -0,0 +1,88 @@ + + + diff --git a/kinit-admin/src/views/Components/CountTo.vue b/kinit-admin/src/views/Components/CountTo.vue new file mode 100644 index 0000000..4c906b9 --- /dev/null +++ b/kinit-admin/src/views/Components/CountTo.vue @@ -0,0 +1,100 @@ + + + diff --git a/kinit-admin/src/views/Components/Descriptions.vue b/kinit-admin/src/views/Components/Descriptions.vue new file mode 100644 index 0000000..e85408d --- /dev/null +++ b/kinit-admin/src/views/Components/Descriptions.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/kinit-admin/src/views/Components/Dialog.vue b/kinit-admin/src/views/Components/Dialog.vue new file mode 100644 index 0000000..a5d2425 --- /dev/null +++ b/kinit-admin/src/views/Components/Dialog.vue @@ -0,0 +1,147 @@ + + + diff --git a/kinit-admin/src/views/Components/Echart.vue b/kinit-admin/src/views/Components/Echart.vue new file mode 100644 index 0000000..da3493c --- /dev/null +++ b/kinit-admin/src/views/Components/Echart.vue @@ -0,0 +1,36 @@ + + + diff --git a/kinit-admin/src/views/Components/Editor/Editor.vue b/kinit-admin/src/views/Components/Editor/Editor.vue new file mode 100644 index 0000000..9dde431 --- /dev/null +++ b/kinit-admin/src/views/Components/Editor/Editor.vue @@ -0,0 +1,32 @@ + + + diff --git a/kinit-admin/src/views/Components/Form/DefaultForm.vue b/kinit-admin/src/views/Components/Form/DefaultForm.vue new file mode 100644 index 0000000..238dd06 --- /dev/null +++ b/kinit-admin/src/views/Components/Form/DefaultForm.vue @@ -0,0 +1,1127 @@ + + + + + diff --git a/kinit-admin/src/views/Components/Form/RefForm.vue b/kinit-admin/src/views/Components/Form/RefForm.vue new file mode 100644 index 0000000..f6e7137 --- /dev/null +++ b/kinit-admin/src/views/Components/Form/RefForm.vue @@ -0,0 +1,269 @@ + + + diff --git a/kinit-admin/src/views/Components/Form/UseFormDemo.vue b/kinit-admin/src/views/Components/Form/UseFormDemo.vue new file mode 100644 index 0000000..61d87d8 --- /dev/null +++ b/kinit-admin/src/views/Components/Form/UseFormDemo.vue @@ -0,0 +1,279 @@ + + + diff --git a/kinit-admin/src/views/Components/Highlight.vue b/kinit-admin/src/views/Components/Highlight.vue new file mode 100644 index 0000000..96baedc --- /dev/null +++ b/kinit-admin/src/views/Components/Highlight.vue @@ -0,0 +1,20 @@ + + + diff --git a/kinit-admin/src/views/Components/Icon.vue b/kinit-admin/src/views/Components/Icon.vue new file mode 100644 index 0000000..7ec3273 --- /dev/null +++ b/kinit-admin/src/views/Components/Icon.vue @@ -0,0 +1,62 @@ + + + diff --git a/kinit-admin/src/views/Components/ImageViewer.vue b/kinit-admin/src/views/Components/ImageViewer.vue new file mode 100644 index 0000000..d732a4e --- /dev/null +++ b/kinit-admin/src/views/Components/ImageViewer.vue @@ -0,0 +1,30 @@ + + + diff --git a/kinit-admin/src/views/Components/Infotip.vue b/kinit-admin/src/views/Components/Infotip.vue new file mode 100644 index 0000000..49cff12 --- /dev/null +++ b/kinit-admin/src/views/Components/Infotip.vue @@ -0,0 +1,33 @@ + + + diff --git a/kinit-admin/src/views/Components/InputPassword.vue b/kinit-admin/src/views/Components/InputPassword.vue new file mode 100644 index 0000000..bfae7e5 --- /dev/null +++ b/kinit-admin/src/views/Components/InputPassword.vue @@ -0,0 +1,21 @@ + + + diff --git a/kinit-admin/src/views/Components/Qrcode.vue b/kinit-admin/src/views/Components/Qrcode.vue new file mode 100644 index 0000000..6b7d792 --- /dev/null +++ b/kinit-admin/src/views/Components/Qrcode.vue @@ -0,0 +1,108 @@ + + + diff --git a/kinit-admin/src/views/Components/Search.vue b/kinit-admin/src/views/Components/Search.vue new file mode 100644 index 0000000..206d69e --- /dev/null +++ b/kinit-admin/src/views/Components/Search.vue @@ -0,0 +1,226 @@ + + + diff --git a/kinit-admin/src/views/Components/Sticky.vue b/kinit-admin/src/views/Components/Sticky.vue new file mode 100644 index 0000000..44fd2b9 --- /dev/null +++ b/kinit-admin/src/views/Components/Sticky.vue @@ -0,0 +1,62 @@ + + + diff --git a/kinit-admin/src/views/Components/Table/DefaultTable.vue b/kinit-admin/src/views/Components/Table/DefaultTable.vue new file mode 100644 index 0000000..e9a9217 --- /dev/null +++ b/kinit-admin/src/views/Components/Table/DefaultTable.vue @@ -0,0 +1,100 @@ + + + diff --git a/kinit-admin/src/views/Components/Table/RefTable.vue b/kinit-admin/src/views/Components/Table/RefTable.vue new file mode 100644 index 0000000..3874d49 --- /dev/null +++ b/kinit-admin/src/views/Components/Table/RefTable.vue @@ -0,0 +1,181 @@ + + + diff --git a/kinit-admin/src/views/Components/Table/UseTableDemo.vue b/kinit-admin/src/views/Components/Table/UseTableDemo.vue new file mode 100644 index 0000000..9df25f1 --- /dev/null +++ b/kinit-admin/src/views/Components/Table/UseTableDemo.vue @@ -0,0 +1,180 @@ + + + diff --git a/kinit-admin/src/views/Dashboard/Analysis.vue b/kinit-admin/src/views/Dashboard/Analysis.vue new file mode 100644 index 0000000..4ce6f27 --- /dev/null +++ b/kinit-admin/src/views/Dashboard/Analysis.vue @@ -0,0 +1,127 @@ + + + diff --git a/kinit-admin/src/views/Dashboard/Workplace.vue b/kinit-admin/src/views/Dashboard/Workplace.vue new file mode 100644 index 0000000..16aa010 --- /dev/null +++ b/kinit-admin/src/views/Dashboard/Workplace.vue @@ -0,0 +1,291 @@ + + + diff --git a/kinit-admin/src/views/Dashboard/components/PanelGroup.vue b/kinit-admin/src/views/Dashboard/components/PanelGroup.vue new file mode 100644 index 0000000..da9c278 --- /dev/null +++ b/kinit-admin/src/views/Dashboard/components/PanelGroup.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/kinit-admin/src/views/Dashboard/echarts-data.ts b/kinit-admin/src/views/Dashboard/echarts-data.ts new file mode 100644 index 0000000..dead29c --- /dev/null +++ b/kinit-admin/src/views/Dashboard/echarts-data.ts @@ -0,0 +1,310 @@ +import { EChartsOption } from 'echarts' +import { EChartsOption as EChartsWordOption } from 'echarts-wordcloud' +import { useI18n } from '@/hooks/web/useI18n' + +const { t } = useI18n() + +export const lineOptions: EChartsOption = { + title: { + text: t('analysis.monthlySales'), + left: 'center' + }, + xAxis: { + data: [ + t('analysis.january'), + t('analysis.february'), + t('analysis.march'), + t('analysis.april'), + t('analysis.may'), + t('analysis.june'), + t('analysis.july'), + t('analysis.august'), + t('analysis.september'), + t('analysis.october'), + t('analysis.november'), + t('analysis.december') + ], + boundaryGap: false, + axisTick: { + show: false + } + }, + grid: { + left: 20, + right: 20, + bottom: 20, + top: 80, + containLabel: true + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross' + }, + padding: [5, 10] + }, + yAxis: { + axisTick: { + show: false + } + }, + legend: { + data: [t('analysis.estimate'), t('analysis.actual')], + top: 50 + }, + series: [ + { + name: t('analysis.estimate'), + smooth: true, + type: 'line', + data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123], + animationDuration: 2800, + animationEasing: 'cubicInOut' + }, + { + name: t('analysis.actual'), + smooth: true, + type: 'line', + itemStyle: {}, + data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123], + animationDuration: 2800, + animationEasing: 'quadraticOut' + } + ] +} + +export const pieOptions: EChartsOption = { + title: { + text: t('analysis.userAccessSource'), + left: 'center' + }, + tooltip: { + trigger: 'item', + formatter: '{a}
{b} : {c} ({d}%)' + }, + legend: { + orient: 'vertical', + left: 'left', + data: [ + t('analysis.directAccess'), + t('analysis.mailMarketing'), + t('analysis.allianceAdvertising'), + t('analysis.videoAdvertising'), + t('analysis.searchEngines') + ] + }, + series: [ + { + name: t('analysis.userAccessSource'), + type: 'pie', + radius: '55%', + center: ['50%', '60%'], + data: [ + { value: 335, name: t('analysis.directAccess') }, + { value: 310, name: t('analysis.mailMarketing') }, + { value: 234, name: t('analysis.allianceAdvertising') }, + { value: 135, name: t('analysis.videoAdvertising') }, + { value: 1548, name: t('analysis.searchEngines') } + ] + } + ] +} + +export const barOptions: EChartsOption = { + title: { + text: t('analysis.weeklyUserActivity'), + left: 'center' + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + grid: { + left: 50, + right: 20, + bottom: 20 + }, + xAxis: { + type: 'category', + data: [ + t('analysis.monday'), + t('analysis.tuesday'), + t('analysis.wednesday'), + t('analysis.thursday'), + t('analysis.friday'), + t('analysis.saturday'), + t('analysis.sunday') + ], + axisTick: { + alignWithLabel: true + } + }, + yAxis: { + type: 'value' + }, + series: [ + { + name: t('analysis.activeQuantity'), + data: [13253, 34235, 26321, 12340, 24643, 1322, 1324], + type: 'bar' + } + ] +} + +export const radarOption: EChartsOption = { + legend: { + data: [t('workplace.personal'), t('workplace.team')] + }, + radar: { + // shape: 'circle', + indicator: [ + { name: t('workplace.quote'), max: 65 }, + { name: t('workplace.contribution'), max: 160 }, + { name: t('workplace.hot'), max: 300 }, + { name: t('workplace.yield'), max: 130 }, + { name: t('workplace.follow'), max: 100 } + ] + }, + series: [ + { + name: `xxx${t('workplace.index')}`, + type: 'radar', + data: [ + { + value: [42, 30, 20, 35, 80], + name: t('workplace.personal') + }, + { + value: [50, 140, 290, 100, 90], + name: t('workplace.team') + } + ] + } + ] +} + +export const wordOptions: EChartsWordOption = { + series: [ + { + type: 'wordCloud', + gridSize: 2, + sizeRange: [12, 50], + rotationRange: [-90, 90], + shape: 'pentagon', + width: 600, + height: 400, + drawOutOfBound: true, + textStyle: { + color: function () { + return ( + 'rgb(' + + [ + Math.round(Math.random() * 160), + Math.round(Math.random() * 160), + Math.round(Math.random() * 160) + ].join(',') + + ')' + ) + } + }, + emphasis: { + textStyle: { + shadowBlur: 10, + shadowColor: '#333' + } + }, + data: [ + { + name: 'Sam S Club', + value: 10000, + textStyle: { + color: 'black' + }, + emphasis: { + textStyle: { + color: 'red' + } + } + }, + { + name: 'Macys', + value: 6181 + }, + { + name: 'Amy Schumer', + value: 4386 + }, + { + name: 'Jurassic World', + value: 4055 + }, + { + name: 'Charter Communications', + value: 2467 + }, + { + name: 'Chick Fil A', + value: 2244 + }, + { + name: 'Planet Fitness', + value: 1898 + }, + { + name: 'Pitch Perfect', + value: 1484 + }, + { + name: 'Express', + value: 1112 + }, + { + name: 'Home', + value: 965 + }, + { + name: 'Johnny Depp', + value: 847 + }, + { + name: 'Lena Dunham', + value: 582 + }, + { + name: 'Lewis Hamilton', + value: 555 + }, + { + name: 'KXAN', + value: 550 + }, + { + name: 'Mary Ellen Mark', + value: 462 + }, + { + name: 'Farrah Abraham', + value: 366 + }, + { + name: 'Rita Ora', + value: 360 + }, + { + name: 'Serena Williams', + value: 282 + }, + { + name: 'NCAA baseball tournament', + value: 273 + }, + { + name: 'Point Break', + value: 265 + } + ] + } + ] +} diff --git a/kinit-admin/src/views/Error/403.vue b/kinit-admin/src/views/Error/403.vue new file mode 100644 index 0000000..615c940 --- /dev/null +++ b/kinit-admin/src/views/Error/403.vue @@ -0,0 +1,17 @@ + + + diff --git a/kinit-admin/src/views/Error/404.vue b/kinit-admin/src/views/Error/404.vue new file mode 100644 index 0000000..0a3fbbe --- /dev/null +++ b/kinit-admin/src/views/Error/404.vue @@ -0,0 +1,17 @@ + + + diff --git a/kinit-admin/src/views/Error/500.vue b/kinit-admin/src/views/Error/500.vue new file mode 100644 index 0000000..ddb5097 --- /dev/null +++ b/kinit-admin/src/views/Error/500.vue @@ -0,0 +1,17 @@ + + + diff --git a/kinit-admin/src/views/Example/Dialog/ExampleDialog.vue b/kinit-admin/src/views/Example/Dialog/ExampleDialog.vue new file mode 100644 index 0000000..afc629d --- /dev/null +++ b/kinit-admin/src/views/Example/Dialog/ExampleDialog.vue @@ -0,0 +1,273 @@ + + + diff --git a/kinit-admin/src/views/Example/Dialog/components/Detail.vue b/kinit-admin/src/views/Example/Dialog/components/Detail.vue new file mode 100644 index 0000000..0b280b7 --- /dev/null +++ b/kinit-admin/src/views/Example/Dialog/components/Detail.vue @@ -0,0 +1,40 @@ + + + diff --git a/kinit-admin/src/views/Example/Dialog/components/Write.vue b/kinit-admin/src/views/Example/Dialog/components/Write.vue new file mode 100644 index 0000000..6cd960b --- /dev/null +++ b/kinit-admin/src/views/Example/Dialog/components/Write.vue @@ -0,0 +1,55 @@ + + + diff --git a/kinit-admin/src/views/Example/Page/ExampleAdd.vue b/kinit-admin/src/views/Example/Page/ExampleAdd.vue new file mode 100644 index 0000000..a29413d --- /dev/null +++ b/kinit-admin/src/views/Example/Page/ExampleAdd.vue @@ -0,0 +1,52 @@ + + + diff --git a/kinit-admin/src/views/Example/Page/ExampleDetail.vue b/kinit-admin/src/views/Example/Page/ExampleDetail.vue new file mode 100644 index 0000000..39f170f --- /dev/null +++ b/kinit-admin/src/views/Example/Page/ExampleDetail.vue @@ -0,0 +1,32 @@ + + + diff --git a/kinit-admin/src/views/Example/Page/ExampleEdit.vue b/kinit-admin/src/views/Example/Page/ExampleEdit.vue new file mode 100644 index 0000000..6818e9c --- /dev/null +++ b/kinit-admin/src/views/Example/Page/ExampleEdit.vue @@ -0,0 +1,64 @@ + + + diff --git a/kinit-admin/src/views/Example/Page/ExamplePage.vue b/kinit-admin/src/views/Example/Page/ExamplePage.vue new file mode 100644 index 0000000..cbe3886 --- /dev/null +++ b/kinit-admin/src/views/Example/Page/ExamplePage.vue @@ -0,0 +1,164 @@ + + + diff --git a/kinit-admin/src/views/Example/Page/components/Detail.vue b/kinit-admin/src/views/Example/Page/components/Detail.vue new file mode 100644 index 0000000..b89ed4d --- /dev/null +++ b/kinit-admin/src/views/Example/Page/components/Detail.vue @@ -0,0 +1,65 @@ + + + diff --git a/kinit-admin/src/views/Example/Page/components/Write.vue b/kinit-admin/src/views/Example/Page/components/Write.vue new file mode 100644 index 0000000..641d66b --- /dev/null +++ b/kinit-admin/src/views/Example/Page/components/Write.vue @@ -0,0 +1,146 @@ + + + diff --git a/kinit-admin/src/views/Guide/Guide.vue b/kinit-admin/src/views/Guide/Guide.vue new file mode 100644 index 0000000..38a710a --- /dev/null +++ b/kinit-admin/src/views/Guide/Guide.vue @@ -0,0 +1,20 @@ + + + diff --git a/kinit-admin/src/views/Level/Menu111.vue b/kinit-admin/src/views/Level/Menu111.vue new file mode 100644 index 0000000..015d0f8 --- /dev/null +++ b/kinit-admin/src/views/Level/Menu111.vue @@ -0,0 +1,20 @@ + + + diff --git a/kinit-admin/src/views/Level/Menu12.vue b/kinit-admin/src/views/Level/Menu12.vue new file mode 100644 index 0000000..8e2725d --- /dev/null +++ b/kinit-admin/src/views/Level/Menu12.vue @@ -0,0 +1,20 @@ + + + diff --git a/kinit-admin/src/views/Level/Menu2.vue b/kinit-admin/src/views/Level/Menu2.vue new file mode 100644 index 0000000..72e2abb --- /dev/null +++ b/kinit-admin/src/views/Level/Menu2.vue @@ -0,0 +1,20 @@ + + + diff --git a/kinit-admin/src/views/Login/Login.vue b/kinit-admin/src/views/Login/Login.vue new file mode 100644 index 0000000..25170cb --- /dev/null +++ b/kinit-admin/src/views/Login/Login.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/kinit-admin/src/views/Login/components/LoginForm.vue b/kinit-admin/src/views/Login/components/LoginForm.vue new file mode 100644 index 0000000..5bceb8a --- /dev/null +++ b/kinit-admin/src/views/Login/components/LoginForm.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/kinit-admin/src/views/Login/components/RegisterForm.vue b/kinit-admin/src/views/Login/components/RegisterForm.vue new file mode 100644 index 0000000..799e9b8 --- /dev/null +++ b/kinit-admin/src/views/Login/components/RegisterForm.vue @@ -0,0 +1,144 @@ + + + diff --git a/kinit-admin/src/views/Login/components/index.ts b/kinit-admin/src/views/Login/components/index.ts new file mode 100644 index 0000000..d08be96 --- /dev/null +++ b/kinit-admin/src/views/Login/components/index.ts @@ -0,0 +1,4 @@ +import LoginForm from './LoginForm.vue' +import RegisterForm from './RegisterForm.vue' + +export { LoginForm, RegisterForm } diff --git a/kinit-admin/src/views/Redirect/Redirect.vue b/kinit-admin/src/views/Redirect/Redirect.vue new file mode 100644 index 0000000..0b7af7b --- /dev/null +++ b/kinit-admin/src/views/Redirect/Redirect.vue @@ -0,0 +1,30 @@ + + diff --git a/kinit-admin/src/views/hooks/useCrudSchemas.vue b/kinit-admin/src/views/hooks/useCrudSchemas.vue new file mode 100644 index 0000000..afac0d7 --- /dev/null +++ b/kinit-admin/src/views/hooks/useCrudSchemas.vue @@ -0,0 +1,222 @@ + + + diff --git a/kinit-admin/src/views/hooks/useWatermark.vue b/kinit-admin/src/views/hooks/useWatermark.vue new file mode 100644 index 0000000..88029c0 --- /dev/null +++ b/kinit-admin/src/views/hooks/useWatermark.vue @@ -0,0 +1,32 @@ + + + diff --git a/kinit-admin/stylelint.config.js b/kinit-admin/stylelint.config.js new file mode 100644 index 0000000..d400572 --- /dev/null +++ b/kinit-admin/stylelint.config.js @@ -0,0 +1,231 @@ +module.exports = { + root: true, + plugins: ['stylelint-order'], + customSyntax: 'postcss-html', + extends: ['stylelint-config-standard', 'stylelint-config-prettier'], + rules: { + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global', 'deep'] + } + ], + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin'] + } + ], + 'no-empty-source': null, + 'named-grid-areas-no-invalid': null, + 'unicode-bom': 'never', + 'no-descending-specificity': null, + 'font-family-no-missing-generic-family-keyword': null, + 'declaration-colon-space-after': 'always-single-line', + 'declaration-colon-space-before': 'never', + 'declaration-block-trailing-semicolon': null, + 'rule-empty-line-before': [ + 'always', + { + ignore: ['after-comment', 'first-nested'] + } + ], + 'unit-no-unknown': [ + true, + { + ignoreUnits: ['rpx'] + } + ], + 'order/order': [ + [ + 'dollar-variables', + 'custom-properties', + 'at-rules', + 'declarations', + { + type: 'at-rule', + name: 'supports' + }, + { + type: 'at-rule', + name: 'media' + }, + 'rules' + ], + { + severity: 'warning' + } + ], + // Specify the alphabetical order of the attributes in the declaration block + 'order/properties-order': [ + 'position', + 'top', + 'right', + 'bottom', + 'left', + 'z-index', + 'display', + 'float', + 'width', + 'height', + 'max-width', + 'max-height', + 'min-width', + 'min-height', + 'padding', + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left', + 'margin', + 'margin-top', + 'margin-right', + 'margin-bottom', + 'margin-left', + 'margin-collapse', + 'margin-top-collapse', + 'margin-right-collapse', + 'margin-bottom-collapse', + 'margin-left-collapse', + 'overflow', + 'overflow-x', + 'overflow-y', + 'clip', + 'clear', + 'font', + 'font-family', + 'font-size', + 'font-smoothing', + 'osx-font-smoothing', + 'font-style', + 'font-weight', + 'hyphens', + 'src', + 'line-height', + 'letter-spacing', + 'word-spacing', + 'color', + 'text-align', + 'text-decoration', + 'text-indent', + 'text-overflow', + 'text-rendering', + 'text-size-adjust', + 'text-shadow', + 'text-transform', + 'word-break', + 'word-wrap', + 'white-space', + 'vertical-align', + 'list-style', + 'list-style-type', + 'list-style-position', + 'list-style-image', + 'pointer-events', + 'cursor', + 'background', + 'background-attachment', + 'background-color', + 'background-image', + 'background-position', + 'background-repeat', + 'background-size', + 'border', + 'border-collapse', + 'border-top', + 'border-right', + 'border-bottom', + 'border-left', + 'border-color', + 'border-image', + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color', + 'border-spacing', + 'border-style', + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style', + 'border-width', + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width', + 'border-radius', + 'border-top-right-radius', + 'border-bottom-right-radius', + 'border-bottom-left-radius', + 'border-top-left-radius', + 'border-radius-topright', + 'border-radius-bottomright', + 'border-radius-bottomleft', + 'border-radius-topleft', + 'content', + 'quotes', + 'outline', + 'outline-offset', + 'opacity', + 'filter', + 'visibility', + 'size', + 'zoom', + 'transform', + 'box-align', + 'box-flex', + 'box-orient', + 'box-pack', + 'box-shadow', + 'box-sizing', + 'table-layout', + 'animation', + 'animation-delay', + 'animation-duration', + 'animation-iteration-count', + 'animation-name', + 'animation-play-state', + 'animation-timing-function', + 'animation-fill-mode', + 'transition', + 'transition-delay', + 'transition-duration', + 'transition-property', + 'transition-timing-function', + 'background-clip', + 'backface-visibility', + 'resize', + 'appearance', + 'user-select', + 'interpolation-mode', + 'direction', + 'marks', + 'page', + 'set-link-source', + 'unicode-bidi', + 'speak' + ] + }, + ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], + overrides: [ + { + files: ['*.vue', '**/*.vue', '*.html', '**/*.html'], + extends: ['stylelint-config-recommended', 'stylelint-config-html'], + rules: { + 'keyframes-name-pattern': null, + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['deep', 'global'] + } + ], + 'selector-pseudo-element-no-unknown': [ + true, + { + ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'] + } + ] + } + } + ] +} diff --git a/kinit-admin/tsconfig.json b/kinit-admin/tsconfig.json new file mode 100644 index 0000000..492e9f0 --- /dev/null +++ b/kinit-admin/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "baseUrl": ".", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "strictFunctionTypes": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "experimentalDecorators": true, + "noImplicitAny": false, + "skipLibCheck": true, + "paths": { + "@/*": ["src/*"] + }, + "types": [ + "@intlify/vite-plugin-vue-i18n/client", + "vite/client", + "element-plus/global", + "@types/intro.js", + "@types/qrcode", + "vite-plugin-svg-icons/client", + "unplugin-vue-macros/macros-global" + ], + "typeRoots": ["./node_modules/@types/", "./types"] + }, + "include": ["src/**/*", "types/**/*.d.ts", "mock/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/kinit-admin/types/componentType/configGlobal.d.ts b/kinit-admin/types/componentType/configGlobal.d.ts new file mode 100644 index 0000000..6a0bb1a --- /dev/null +++ b/kinit-admin/types/componentType/configGlobal.d.ts @@ -0,0 +1,3 @@ +declare interface ConfigGlobalTypes { + size?: ElememtPlusSize +} diff --git a/kinit-admin/types/componentType/contextMenu.d.ts b/kinit-admin/types/componentType/contextMenu.d.ts new file mode 100644 index 0000000..3f9cac3 --- /dev/null +++ b/kinit-admin/types/componentType/contextMenu.d.ts @@ -0,0 +1,7 @@ +declare type contextMenuSchema = { + disabled?: boolean + divided?: boolean + icon?: string + label: string + command?: (item: contextMenuSchema) => viod +} diff --git a/kinit-admin/types/componentType/descriptions.d.ts b/kinit-admin/types/componentType/descriptions.d.ts new file mode 100644 index 0000000..c695706 --- /dev/null +++ b/kinit-admin/types/componentType/descriptions.d.ts @@ -0,0 +1,11 @@ +declare interface DescriptionsSchema { + span?: number // 占多少分 + field: string // 字段名 + label?: string // label名 + width?: string | number + minWidth?: string | number + align?: 'left' | 'center' | 'right' + labelAlign?: 'left' | 'center' | 'right' + className?: string + labelClassName?: string +} diff --git a/kinit-admin/types/componentType/form.d.ts b/kinit-admin/types/componentType/form.d.ts new file mode 100644 index 0000000..b598acb --- /dev/null +++ b/kinit-admin/types/componentType/form.d.ts @@ -0,0 +1,97 @@ +import type { CSSProperties } from 'vue' + +declare global { + declare type ComponentName = + | 'Radio' + | 'RadioButton' + | 'Checkbox' + | 'CheckboxButton' + | 'Input' + | 'Autocomplete' + | 'InputNumber' + | 'Select' + | 'Cascader' + | 'Switch' + | 'Slider' + | 'TimePicker' + | 'DatePicker' + | 'Rate' + | 'ColorPicker' + | 'Transfer' + | 'Divider' + | 'TimeSelect' + | 'SelectV2' + | 'InputPassword' + | 'Editor' + + declare type ColProps = { + span?: number + xs?: number + sm?: number + md?: number + lg?: number + xl?: number + tag?: string + } + + declare type FormValueType = string | number | string[] | number[] | boolean | undefined | null + + declare type FormItemProps = { + labelWidth?: string | number + required?: boolean + rules?: Recordable + error?: string + showMessage?: boolean + inlineMessage?: boolean + style?: CSSProperties + } + + declare type ComponentOptions = { + label?: string + value?: FormValueType + disabled?: boolean + key?: string | number + children?: ComponentOptions[] + options?: ComponentOptions[] + } & Recordable + + declare type ComponentOptionsAlias = { + labelField?: string + valueField?: string + } + + declare type ComponentProps = { + optionsAlias?: ComponentOptionsAlias + options?: ComponentOptions[] + optionsSlot?: boolean + } & Recordable + + declare type FormSchema = { + // 唯一值 + field: string + // 标题 + label?: string + // 提示 + labelMessage?: string + // col组件属性 + colProps?: ColProps + // 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档 + componentProps?: { slots?: Recordable } & ComponentProps + // formItem组件属性 + formItemProps?: FormItemProps + // 渲染的组件 + component?: ComponentName + // 初始值 + value?: FormValueType + // 是否隐藏 + hidden?: boolean + // 远程加载下拉项 + api?: () => AxiosPromise + } + + declare type FormSetPropsType = { + field: string + path: string + value: any + } +} diff --git a/kinit-admin/types/componentType/icon.d.ts b/kinit-admin/types/componentType/icon.d.ts new file mode 100644 index 0000000..cc24543 --- /dev/null +++ b/kinit-admin/types/componentType/icon.d.ts @@ -0,0 +1,5 @@ +declare interface IconTypes { + size?: number + color?: string + icon: string +} diff --git a/kinit-admin/types/componentType/infotip.d.ts b/kinit-admin/types/componentType/infotip.d.ts new file mode 100644 index 0000000..9c2338a --- /dev/null +++ b/kinit-admin/types/componentType/infotip.d.ts @@ -0,0 +1,4 @@ +declare interface TipSchema { + label: string + keys?: string[] +} diff --git a/kinit-admin/types/componentType/localeDropdown.d.ts b/kinit-admin/types/componentType/localeDropdown.d.ts new file mode 100644 index 0000000..bbe8510 --- /dev/null +++ b/kinit-admin/types/componentType/localeDropdown.d.ts @@ -0,0 +1,10 @@ +declare interface Language { + el: Recordable + name: string +} + +declare interface LocaleDropdownType { + lang: LocaleType + name?: string + elLocale?: Language +} diff --git a/kinit-admin/types/componentType/qrcode.d.ts b/kinit-admin/types/componentType/qrcode.d.ts new file mode 100644 index 0000000..369334f --- /dev/null +++ b/kinit-admin/types/componentType/qrcode.d.ts @@ -0,0 +1,9 @@ +declare interface QrcodeLogo { + src?: string + logoSize?: number + bgColor?: string + borderSize?: number + crossOrigin?: string + borderRadius?: number + logoRadius?: number +} diff --git a/kinit-admin/types/componentType/table.d.ts b/kinit-admin/types/componentType/table.d.ts new file mode 100644 index 0000000..fe2100c --- /dev/null +++ b/kinit-admin/types/componentType/table.d.ts @@ -0,0 +1,36 @@ +declare type TableColumn = { + field: string + label?: string + children?: TableColumn[] +} & Recordable + +declare type TableSlotDefault = { + row: Recordable + column: TableColumn + $index: number +} & Recordable + +declare interface Pagination { + small?: boolean + background?: boolean + pageSize?: number + defaultPageSize?: number + total?: number + pageCount?: number + pagerCount?: number + currentPage?: number + defaultCurrentPage?: number + layout?: string + pageSizes?: number[] + popperClass?: string + prevText?: string + nextText?: string + disabled?: boolean + hideOnSinglePage?: boolean +} + +declare interface TableSetPropsType { + field: string + path: string + value: any +} diff --git a/kinit-admin/types/components.d.ts b/kinit-admin/types/components.d.ts new file mode 100644 index 0000000..85db566 --- /dev/null +++ b/kinit-admin/types/components.d.ts @@ -0,0 +1,7 @@ +declare module 'vue' { + export interface GlobalComponents { + Icon: typeof import('../components/Icon/src/Icon.vue')['default'] + } +} + +export {} diff --git a/kinit-admin/types/custom-types.d.ts b/kinit-admin/types/custom-types.d.ts new file mode 100644 index 0000000..3ef553c --- /dev/null +++ b/kinit-admin/types/custom-types.d.ts @@ -0,0 +1,27 @@ +import { SlateDescendant } from '@wangeditor/editor' + +declare module 'slate' { + interface CustomTypes { + // 扩展 text + Text: { + text: string + bold?: boolean + italic?: boolean + code?: boolean + through?: boolean + underline?: boolean + sup?: boolean + sub?: boolean + color?: string + bgColor?: string + fontSize?: string + fontFamily?: string + } + + // 扩展 Element 的 type 属性 + Element: { + type: string + children: SlateDescendant[] + } + } +} diff --git a/kinit-admin/types/env.d.ts b/kinit-admin/types/env.d.ts new file mode 100644 index 0000000..1364a47 --- /dev/null +++ b/kinit-admin/types/env.d.ts @@ -0,0 +1,24 @@ +/// + +declare module '*.vue' { + import { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} + +interface ImportMetaEnv { + readonly VITE_APP_TITLE: string + readonly VITE_API_BASEPATH: string + readonly VITE_BASE_PATH: string + readonly VITE_DROP_DEBUGGER: string + readonly VITE_DROP_CONSOLE: string + readonly VITE_SOURCEMAP: string + readonly VITE_OUT_DIR: string +} + +declare global { + interface ImportMeta { + readonly env: ImportMetaEnv + } +} diff --git a/kinit-admin/types/global.d.ts b/kinit-admin/types/global.d.ts new file mode 100644 index 0000000..04c753a --- /dev/null +++ b/kinit-admin/types/global.d.ts @@ -0,0 +1,40 @@ +declare interface Fn { + (...arg: T[]): T +} + +declare type Nullable = T | null + +declare type ElRef = Nullable + +declare type ElememtPlusSize = 'default' | 'small' | 'large' + +declare type ElementPlusInfoType = 'success' | 'info' | 'warning' | 'danger' + +declare type Recordable = Record + +declare type ComponentRef = InstanceType + +declare type LocaleType = 'zh-CN' | 'en' + +declare type AxiosHeaders = + | 'application/json' + | 'application/x-www-form-urlencoded' + | 'multipart/form-data' + +declare type AxiosMethod = 'get' | 'post' | 'delete' | 'put' + +declare type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream' + +declare interface AxiosConfig { + params?: any + data?: any + url?: string + method?: AxiosMethod + headersType?: string + responseType?: AxiosResponseType +} + +declare interface IResponse { + code: string + data: T extends any ? T : T & any +} diff --git a/kinit-admin/types/router.d.ts b/kinit-admin/types/router.d.ts new file mode 100644 index 0000000..00c7443 --- /dev/null +++ b/kinit-admin/types/router.d.ts @@ -0,0 +1,74 @@ +import type { RouteRecordRaw } from 'vue-router' +import { defineComponent } from 'vue' + +/** +* redirect: noredirect 当设置 noredirect 的时候该路由在面包屑导航中不可被点击 +* name:'router-name' 设定路由的名字,一定要填写不然使用时会出现各种问题 +* meta : { + hidden: true 当设置 true 的时候该路由不会再侧边栏出现 如404,login等页面(默认 false) + + alwaysShow: true 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式, + 只有一个时,会将那个子路由当做根路由显示在侧边栏, + 若你想不管路由下面的 children 声明的个数都显示你的根路由, + 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则, + 一直显示根路由(默认 false) + + title: 'title' 设置该路由在侧边栏和面包屑中展示的名字 + + icon: 'svg-name' 设置该路由的图标 + + noCache: true 如果设置为true,则不会被 缓存(默认 false) + + breadcrumb: false 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true) + + affix: true 如果设置为true,则会一直固定在tag项中(默认 false) + + noTagsView: true 如果设置为true,则不会出现在tag中(默认 false) + + activeMenu: '/dashboard' 显示高亮的路由路径 + + followAuth: '/dashboard' 跟随哪个路由进行权限过滤 + + canTo: true 设置为true即使hidden为true,也依然可以进行路由跳转(默认 false) + } +**/ +declare module 'vue-router' { + interface RouteMeta extends Record { + hidden?: boolean + alwaysShow?: boolean + title?: string + icon?: string + noCache?: boolean + breadcrumb?: boolean + affix?: boolean + activeMenu?: string + noTagsView?: boolean + followAuth?: string + canTo?: boolean + } +} + +type Component = + | ReturnType + | (() => Promise) + | (() => Promise) + +declare global { + declare interface AppRouteRecordRaw extends Omit { + name: string + meta: RouteMeta + component?: Component | string + children?: AppRouteRecordRaw[] + props?: Recordable + fullPath?: string + } + + declare interface AppCustomRouteRecordRaw extends Omit { + name: string + meta: RouteMeta + component: string + path: string + redirect: string + children?: AppCustomRouteRecordRaw[] + } +} diff --git a/kinit-admin/vite.config.ts b/kinit-admin/vite.config.ts new file mode 100644 index 0000000..50a6af0 --- /dev/null +++ b/kinit-admin/vite.config.ts @@ -0,0 +1,152 @@ +import { resolve } from 'path' +import { loadEnv } from 'vite' +import type { UserConfig, ConfigEnv } from 'vite' +import Vue from '@vitejs/plugin-vue' +import WindiCSS from 'vite-plugin-windicss' +import VueJsx from '@vitejs/plugin-vue-jsx' +import EslintPlugin from 'vite-plugin-eslint' +import VueI18n from '@intlify/vite-plugin-vue-i18n' +import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import' +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' +import PurgeIcons from 'vite-plugin-purge-icons' +import { viteMockServe } from 'vite-plugin-mock' +import { createHtmlPlugin } from 'vite-plugin-html' +import VueMarcos from 'unplugin-vue-macros/vite' + +// https://vitejs.dev/config/ +const root = process.cwd() + +function pathResolve(dir: string) { + return resolve(root, '.', dir) +} + +export default ({ command, mode }: ConfigEnv): UserConfig => { + let env = {} as any + const isBuild = command === 'build' + if (!isBuild) { + env = loadEnv((process.argv[3] === '--mode' ? process.argv[4] : process.argv[3]), root) + } else { + env = loadEnv(mode, root) + } + return { + base: env.VITE_BASE_PATH, + plugins: [ + Vue(), + VueJsx(), + WindiCSS(), + createStyleImportPlugin({ + resolves: [ElementPlusResolve()], + libs: [{ + libraryName: 'element-plus', + esModule: true, + resolveStyle: (name) => { + return `element-plus/es/components/${name.substring(3)}/style/css` + } + }] + }), + EslintPlugin({ + cache: false, + include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件 + }), + VueI18n({ + runtimeOnly: true, + compositionOnly: true, + include: [resolve(__dirname, 'src/locales/**')] + }), + createSvgIconsPlugin({ + iconDirs: [pathResolve('src/assets/svgs')], + symbolId: 'icon-[dir]-[name]', + svgoOptions: true + }), + PurgeIcons(), + viteMockServe({ + ignore: /^\_/, + mockPath: 'mock', + localEnabled: !isBuild, + prodEnabled: isBuild, + injectCode: ` + import { setupProdMockServer } from '../mock/_createProductionServer' + + setupProdMockServer() + ` + }), + VueMarcos(), + createHtmlPlugin({ + inject: { + data: { + title: env.VITE_APP_TITLE, + injectScript: ``, + } + } + }) + ], + + css: { + preprocessorOptions: { + less: { + additionalData: '@import "./src/styles/variables.module.less";', + javascriptEnabled: true + } + } + }, + resolve: { + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.less', '.css'], + alias: [ + { + find: 'vue-i18n', + replacement: 'vue-i18n/dist/vue-i18n.cjs.js' + }, + { + find: /\@\//, + replacement: `${pathResolve('src')}/` + } + ] + }, + build: { + minify: 'terser', + outDir: env.VITE_OUT_DIR || 'dist', + sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false, + // brotliSize: false, + terserOptions: { + compress: { + drop_debugger: env.VITE_DROP_DEBUGGER === 'true', + drop_console: env.VITE_DROP_CONSOLE === 'true' + } + } + }, + server: { + port: 4000, + proxy: { + // 选项写法 + '/api': { + target: 'http://127.0.0.1:8000', + changeOrigin: true, + rewrite: path => path.replace(/^\/api/, '') + } + }, + hmr: { + overlay: false + }, + host: '0.0.0.0' + }, + optimizeDeps: { + include: [ + 'vue', + 'vue-router', + 'vue-types', + 'element-plus/es/locale/lang/zh-cn', + 'element-plus/es/locale/lang/en', + '@iconify/iconify', + '@vueuse/core', + 'axios', + 'qs', + 'echarts', + 'echarts-wordcloud', + 'intro.js', + 'qrcode', + '@wangeditor/editor', + '@wangeditor/editor-for-vue' + ] + } + } +} diff --git a/kinit-admin/windi.config.ts b/kinit-admin/windi.config.ts new file mode 100644 index 0000000..de72177 --- /dev/null +++ b/kinit-admin/windi.config.ts @@ -0,0 +1,76 @@ +import { defineConfig } from 'windicss/helpers' +import plugin from 'windicss/plugin' + +function range(size, startAt = 1) { + return Array.from(Array(size).keys()).map((i) => i + startAt) +} + +export default defineConfig({ + extract: { + include: ['src/**/*.{vue,html,jsx,tsx}'], + exclude: ['node_modules', '.git'] + }, + darkMode: 'class', + attributify: false, + theme: { + extend: { + backgroundColor: { + // 暗黑背景色 + 'v-dark': 'var(--dark-bg-color)' + } + // screens: { + // sm: '768px', + // md: '992px', + // lg: '1200px', + // xl: '1920px' + // } + } + // height: { + // ...range(50).map((i) => `h-${i}px`) + // }, + // margin: { + // // ...range(50).map((i) => `mt-${i}px`), + // // ...range(50).map((i) => `mr-${i}px`), + // // ...range(50).map((i) => `mb-${i}px`), + // // ...range(50).map((i) => `ml-${i}px`) + // } + }, + plugins: [ + plugin(({ addComponents }) => { + const obj = {} + range(50).map((i) => { + obj[`.border-top-${i}`] = { + borderTopWidth: `${i}px` + } + obj[`.border-left-${i}`] = { + borderLeftWidth: `${i}px` + } + obj[`.border-right-${i}`] = { + borderRightWidth: `${i}px` + } + obj[`.border-bottom-${i}`] = { + borderBottomWidth: `${i}px` + } + }) + addComponents({ + '.hover-tigger': { + display: 'flex', + height: '100%', + padding: '1px 10px 0', + cursor: 'pointer', + alignItems: 'center', + transition: 'background var(--transition-time-02)', + '&:hover': { + backgroundColor: 'var(--top-header-hover-color)' + } + }, + '.dark .hover-tigger': { + '&:hover': { + backgroundColor: 'var(--el-bg-color-overlay)' + } + }, + ...obj + }) + }) + ] +}) diff --git a/kinit-api/.gitignore b/kinit-api/.gitignore new file mode 100644 index 0000000..d0c8bef --- /dev/null +++ b/kinit-api/.gitignore @@ -0,0 +1,31 @@ +# Editor directories and files +.idea/ +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +logs/* +!logs/.gitkeep +temp/* +!temp/.gitkeep +!static/.gitkeep + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject +*.db +.DS_Store +__pycache__ +!migrations/__init__.py +*.pyc \ No newline at end of file diff --git a/kinit-api/README.md b/kinit-api/README.md new file mode 100644 index 0000000..c3057b3 --- /dev/null +++ b/kinit-api/README.md @@ -0,0 +1,107 @@ +# 初始 FastAPI 项目 + +fastapi 源代码:https://github.com/tiangolo/fastapi + +fastapi 中文文档:https://fastapi.tiangolo.com/zh/ + +fastapi 更新说明:https://fastapi.tiangolo.com/zh/release-notes/ + +pydantic 官方文档:https://pydantic-docs.helpmanual.io/ + +pydantic 数据模型代码生成器官方文档 (Json -> Pydantic):https://koxudaxi.github.io/datamodel-code-generator/ + +SQLAlchemy-Utils:https://sqlalchemy-utils.readthedocs.io/en/latest/ + + +## 项目结构 + +使用的是仿照 Django 项目结构: + +- alembic:数据库迁移配置目录 +- application:主项目配置目录,也存放了主路由文件 + - settings.py:主项目配置文件 + - urls.py:主路由文件 +- apps:项目的app存放目录 +- core:核心文件目录 + - database.py:关系型数据库核心配置 + - exception.py:异常处理 + - logger:日志处理核心配置 + - middleware.py:中间件核心配置 +- db:ORM模型基类 +- logs:日志目录 +- static:静态资源存放目录 +- tests:测试接口文件目录 +- utils:封装的一些工具类目录 +- main.py:主程序入口文件 +- alembic.ini:数据库迁移配置文件 + +## 开发环境 + +开发语言:Python 3.8 + +开发框架:Fastapi 0.73.0 + +## 使用 + +``` +# 拉取代码 +git clone https://gitee.com/ktianc/create_fastapi.git + +cd create_fastapi/ + +# 安装依赖库 +pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ + +# 第三方源: + +1. 阿里源: https://mirrors.aliyun.com/pypi/simple/ +``` + +### 运行启动 + +``` +# 命令行运行(开发模式) +uvicorn main:app --host=127.0.0.1 --port=9000 --reload + +# 或者直接运行main文件 +python main.py +``` + +在线文档地址(在配置文件里面设置路径或者关闭) + +``` +http://127.0.0.1:9000/docs +``` + +Git更新ignore文件直接修改gitignore是不会生效的,需要先去掉已经托管的文件,修改完成之后再重新添加并提交。 +``` +第一步: +git rm -r --cached . +去掉已经托管的文件 + +第二步: +修改自己的igonre文件内容 + +第三步: +git add . +git commit -m "clear cached" +``` + +执行数据库迁移命令(终端执行) + +```python +# 初次生成迁移文件 +alembic revision -m "生成迁移文件" + +# 通过该命令可以将模型迁移到数据库 +alembic upgrade head + +# 如果有更新,则可以使用这个命令再次生成迁移文件,初次也可以使用 +alembic revision --autogenerate -m "update" +# --autogenerate:自动将当前模型的修改,生成迁移脚本。 + +# 通过该命令可以将模型迁移到数据库 +alembic upgrade head +``` + +生成迁移文件后,会在alembic迁移目录中的version目录中多个迁移文件 \ No newline at end of file diff --git a/kinit-api/alembic.ini b/kinit-api/alembic.ini new file mode 100644 index 0000000..e29e1d5 --- /dev/null +++ b/kinit-api/alembic.ini @@ -0,0 +1,100 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. Valid values are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # default: use os.pathsep + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = mysql+pymysql://root:Ktianc123@rm-bp181adf0phw2o0r05o.mysql.rds.aliyuncs.com/kinit + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/kinit-api/alembic/README b/kinit-api/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/kinit-api/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/kinit-api/alembic/env.py b/kinit-api/alembic/env.py new file mode 100644 index 0000000..9e18fc8 --- /dev/null +++ b/kinit-api/alembic/env.py @@ -0,0 +1,96 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +import os +import sys + +from core.database import Model + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +# target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +# 添加当前项目路径到环境变量 +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(BASE_DIR) + +# 导入项目中的基本映射类,与 需要迁移的 ORM 模型 +from apps.vadmin.auth.models import * +from apps.vadmin.system.models import * +from apps.vadmin.record.models import * + +# 修改配置中的参数 +target_metadata = Model.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + compare_type=True, # 检查字段类型 + compare_server_default=True # 比较默认值 + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/kinit-api/alembic/script.py.mako b/kinit-api/alembic/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/kinit-api/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/kinit-api/alembic/versions/03e55ed3e858_update.py b/kinit-api/alembic/versions/03e55ed3e858_update.py new file mode 100644 index 0000000..b96f95d --- /dev/null +++ b/kinit-api/alembic/versions/03e55ed3e858_update.py @@ -0,0 +1,30 @@ +"""update + +Revision ID: 03e55ed3e858 +Revises: 5e629ba5c3c8 +Create Date: 2022-08-13 10:41:38.008004 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '03e55ed3e858' +down_revision = '5e629ba5c3c8' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('vadmin_auth_menu', sa.Column('disabled', sa.Boolean(), nullable=True, comment='是否禁用')) + op.drop_column('vadmin_auth_menu', 'status') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('vadmin_auth_menu', sa.Column('status', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True, comment='是否可用')) + op.drop_column('vadmin_auth_menu', 'disabled') + # ### end Alembic commands ### diff --git a/kinit-api/alembic/versions/0cd7a858c5a5_update.py b/kinit-api/alembic/versions/0cd7a858c5a5_update.py new file mode 100644 index 0000000..d192b19 --- /dev/null +++ b/kinit-api/alembic/versions/0cd7a858c5a5_update.py @@ -0,0 +1,66 @@ +"""update + +Revision ID: 0cd7a858c5a5 +Revises: bc1417d96581 +Create Date: 2022-08-08 12:39:32.344065 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0cd7a858c5a5' +down_revision = 'bc1417d96581' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('vadmin_system_dict_type', + sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'), + sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('dict_name', sa.String(length=50), nullable=False, comment='字典名称'), + sa.Column('dict_type', sa.String(length=50), nullable=False, comment='字典类型'), + sa.Column('status', sa.Boolean(), nullable=True, comment='字典状态,是否可用'), + sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'), + sa.PrimaryKeyConstraint('id'), + comment='字典类型表' + ) + op.create_index(op.f('ix_vadmin_system_dict_type_dict_name'), 'vadmin_system_dict_type', ['dict_name'], unique=False) + op.create_index(op.f('ix_vadmin_system_dict_type_dict_type'), 'vadmin_system_dict_type', ['dict_type'], unique=False) + op.create_index(op.f('ix_vadmin_system_dict_type_id'), 'vadmin_system_dict_type', ['id'], unique=True) + op.create_table('vadmin_system_dict_details', + sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'), + sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('dict_label', sa.String(length=50), nullable=False, comment='字典标签'), + sa.Column('dict_value', sa.String(length=50), nullable=False, comment='字典键值'), + sa.Column('status', sa.Boolean(), nullable=True, comment='字典状态,是否可用'), + sa.Column('is_default', sa.Boolean(), nullable=True, comment='是否默认'), + sa.Column('sort', sa.Integer(), nullable=True, comment='字典排序'), + sa.Column('dict_type_id', sa.Integer(), nullable=True, comment='关联字典类型'), + sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'), + sa.ForeignKeyConstraint(['dict_type_id'], ['vadmin_system_dict_type.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + comment='字典详情表' + ) + op.create_index(op.f('ix_vadmin_system_dict_details_dict_label'), 'vadmin_system_dict_details', ['dict_label'], unique=False) + op.create_index(op.f('ix_vadmin_system_dict_details_dict_value'), 'vadmin_system_dict_details', ['dict_value'], unique=False) + op.create_index(op.f('ix_vadmin_system_dict_details_id'), 'vadmin_system_dict_details', ['id'], unique=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_vadmin_system_dict_details_id'), table_name='vadmin_system_dict_details') + op.drop_index(op.f('ix_vadmin_system_dict_details_dict_value'), table_name='vadmin_system_dict_details') + op.drop_index(op.f('ix_vadmin_system_dict_details_dict_label'), table_name='vadmin_system_dict_details') + op.drop_table('vadmin_system_dict_details') + op.drop_index(op.f('ix_vadmin_system_dict_type_id'), table_name='vadmin_system_dict_type') + op.drop_index(op.f('ix_vadmin_system_dict_type_dict_type'), table_name='vadmin_system_dict_type') + op.drop_index(op.f('ix_vadmin_system_dict_type_dict_name'), table_name='vadmin_system_dict_type') + op.drop_table('vadmin_system_dict_type') + # ### end Alembic commands ### diff --git a/kinit-api/alembic/versions/5e629ba5c3c8_update.py b/kinit-api/alembic/versions/5e629ba5c3c8_update.py new file mode 100644 index 0000000..799848a --- /dev/null +++ b/kinit-api/alembic/versions/5e629ba5c3c8_update.py @@ -0,0 +1,88 @@ +"""update + +Revision ID: 5e629ba5c3c8 +Revises: 65797098992b +Create Date: 2022-08-08 17:25:54.291997 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '5e629ba5c3c8' +down_revision = '65797098992b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('vadmin_record_login', + sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'), + sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('telephone', sa.String(length=50), nullable=False, comment='手机号'), + sa.Column('status', sa.Boolean(), nullable=True, comment='是否登录成功'), + sa.Column('ip', sa.String(length=50), nullable=True, comment='登陆地址'), + sa.Column('address', sa.String(length=50), nullable=True, comment='登陆地点'), + sa.Column('browser', sa.String(length=50), nullable=True, comment='浏览器'), + sa.Column('system', sa.String(length=50), nullable=True, comment='操作系统'), + sa.Column('response', sa.TEXT(), nullable=True, comment='响应信息'), + sa.Column('request', sa.TEXT(), nullable=True, comment='请求信息'), + sa.PrimaryKeyConstraint('id'), + comment='登录记录表' + ) + op.create_index(op.f('ix_vadmin_record_login_id'), 'vadmin_record_login', ['id'], unique=True) + op.create_index(op.f('ix_vadmin_record_login_telephone'), 'vadmin_record_login', ['telephone'], unique=False) + op.create_table('vadmin_record_operation', + sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'), + sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('user', sa.Integer(), nullable=True, comment='操作人'), + sa.Column('status', sa.Boolean(), nullable=True, comment='操作结果状态'), + sa.Column('ip', sa.String(length=50), nullable=True, comment='登陆地址'), + sa.Column('address', sa.String(length=50), nullable=True, comment='登陆地点'), + sa.Column('browser', sa.String(length=50), nullable=True, comment='浏览器'), + sa.Column('system', sa.String(length=50), nullable=True, comment='操作系统'), + sa.Column('response', sa.TEXT(), nullable=True, comment='响应信息'), + sa.Column('request', sa.TEXT(), nullable=True, comment='请求信息'), + sa.Column('request_api', sa.String(length=255), nullable=True, comment='请求接口'), + sa.Column('request_method', sa.String(length=255), nullable=True, comment='请求方式'), + sa.ForeignKeyConstraint(['user'], ['vadmin_auth_user.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + comment='操作记录表' + ) + op.create_index(op.f('ix_vadmin_record_operation_id'), 'vadmin_record_operation', ['id'], unique=True) + op.create_table('vadmin_record_sms_send', + sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'), + sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('user_id', sa.Integer(), nullable=True, comment='操作人'), + sa.Column('status', sa.Boolean(), nullable=True, comment='发送状态'), + sa.Column('content', sa.String(length=255), nullable=True, comment='发送内容'), + sa.Column('telephone', sa.String(length=11), nullable=True, comment='目标手机号'), + sa.Column('desc', sa.String(length=255), nullable=True, comment='失败描述'), + sa.Column('scene', sa.String(length=50), nullable=True, comment='发送场景'), + sa.ForeignKeyConstraint(['user_id'], ['vadmin_auth_user.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + comment='短信发送记录表' + ) + op.create_index(op.f('ix_vadmin_record_sms_send_id'), 'vadmin_record_sms_send', ['id'], unique=True) + op.add_column('vadmin_auth_menu', sa.Column('order', sa.Integer(), nullable=True, comment='排序')) + op.drop_column('vadmin_auth_menu', 'order_num') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('vadmin_auth_menu', sa.Column('order_num', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True, comment='排序')) + op.drop_column('vadmin_auth_menu', 'order') + op.drop_index(op.f('ix_vadmin_record_sms_send_id'), table_name='vadmin_record_sms_send') + op.drop_table('vadmin_record_sms_send') + op.drop_index(op.f('ix_vadmin_record_operation_id'), table_name='vadmin_record_operation') + op.drop_table('vadmin_record_operation') + op.drop_index(op.f('ix_vadmin_record_login_telephone'), table_name='vadmin_record_login') + op.drop_index(op.f('ix_vadmin_record_login_id'), table_name='vadmin_record_login') + op.drop_table('vadmin_record_login') + # ### end Alembic commands ### diff --git a/kinit-api/alembic/versions/65797098992b_update.py b/kinit-api/alembic/versions/65797098992b_update.py new file mode 100644 index 0000000..eaacdce --- /dev/null +++ b/kinit-api/alembic/versions/65797098992b_update.py @@ -0,0 +1,111 @@ +"""update + +Revision ID: 65797098992b +Revises: f2390b45da1d +Create Date: 2022-08-08 15:13:27.266162 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '65797098992b' +down_revision = 'f2390b45da1d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('vadmin_auth_menu', + sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'), + sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('title', sa.String(length=50), nullable=False, comment='名称'), + sa.Column('icon', sa.String(length=50), nullable=True, comment='菜单图标'), + sa.Column('component', sa.String(length=50), nullable=True, comment='前端组件地址'), + sa.Column('path', sa.String(length=50), nullable=True, comment='前端路由地址'), + sa.Column('status', sa.Boolean(), nullable=True, comment='是否可用'), + sa.Column('hidden', sa.Boolean(), nullable=True, comment='是否显示'), + sa.Column('order_num', sa.Integer(), nullable=True, comment='排序'), + sa.Column('menu_type', sa.String(length=8), nullable=True, comment='菜单类型'), + sa.Column('parent_id', sa.Integer(), nullable=True, comment='父菜单'), + sa.Column('perms', sa.String(length=50), nullable=True, comment='权限标识'), + sa.ForeignKeyConstraint(['parent_id'], ['vadmin_auth_menu.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + comment='菜单表' + ) + op.create_index(op.f('ix_vadmin_auth_menu_id'), 'vadmin_auth_menu', ['id'], unique=True) + op.create_index(op.f('ix_vadmin_auth_menu_perms'), 'vadmin_auth_menu', ['perms'], unique=False) + op.create_index(op.f('ix_vadmin_auth_menu_title'), 'vadmin_auth_menu', ['title'], unique=False) + op.create_table('vadmin_auth_role', + sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'), + sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('name', sa.String(length=50), nullable=False, comment='名称'), + sa.Column('role_key', sa.String(length=50), nullable=False, comment='权限字符'), + sa.Column('is_active', sa.Boolean(), nullable=True, comment='是否可用'), + sa.Column('index', sa.Integer(), nullable=True, comment='排序'), + sa.Column('desc', sa.String(length=255), nullable=True, comment='描述'), + sa.Column('is_admin', sa.Boolean(), nullable=True, comment='是否为超级角色'), + sa.PrimaryKeyConstraint('id'), + comment='角色表' + ) + op.create_index(op.f('ix_vadmin_auth_role_id'), 'vadmin_auth_role', ['id'], unique=True) + op.create_index(op.f('ix_vadmin_auth_role_name'), 'vadmin_auth_role', ['name'], unique=False) + op.create_index(op.f('ix_vadmin_auth_role_role_key'), 'vadmin_auth_role', ['role_key'], unique=False) + op.create_table('vadmin_auth_role_menus', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False, comment='主键ID'), + sa.Column('role_id', sa.Integer(), nullable=False), + sa.Column('menu_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['menu_id'], ['vadmin_auth_menu.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['role_id'], ['vadmin_auth_role.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id', 'role_id', 'menu_id') + ) + op.create_index(op.f('ix_vadmin_auth_role_menus_id'), 'vadmin_auth_role_menus', ['id'], unique=True) + op.create_table('vadmin_auth_user_roles', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False, comment='主键ID'), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('role_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['role_id'], ['vadmin_auth_role.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['vadmin_auth_user.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id', 'user_id', 'role_id') + ) + op.create_index(op.f('ix_vadmin_auth_user_roles_id'), 'vadmin_auth_user_roles', ['id'], unique=True) + op.add_column('vadmin_auth_user', sa.Column('avatar', sa.String(length=500), nullable=True, comment='头像')) + op.add_column('vadmin_auth_user', sa.Column('gender', sa.String(length=8), nullable=True, comment='性别')) + op.add_column('vadmin_auth_user', sa.Column('is_active', sa.Boolean(), nullable=True, comment='是否可用')) + op.add_column('vadmin_auth_user', sa.Column('is_cancel', sa.Boolean(), nullable=True, comment='是否注销')) + op.add_column('vadmin_auth_user', sa.Column('is_reset_password', sa.Boolean(), nullable=True, comment='是否已经重置密码,没有重置的,登陆系统后必须重置密码')) + op.add_column('vadmin_auth_user', sa.Column('last_ip', sa.String(length=50), nullable=True, comment='最后一次登录IP')) + op.add_column('vadmin_auth_user', sa.Column('last_login', sa.DateTime(), nullable=True, comment='最近一次登录时间')) + op.drop_column('vadmin_auth_user', 'wechat') + op.drop_column('vadmin_auth_user', 'qq') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('vadmin_auth_user', sa.Column('qq', mysql.VARCHAR(length=50), nullable=True, comment='QQ')) + op.add_column('vadmin_auth_user', sa.Column('wechat', mysql.VARCHAR(length=50), nullable=True, comment='微信')) + op.drop_column('vadmin_auth_user', 'last_login') + op.drop_column('vadmin_auth_user', 'last_ip') + op.drop_column('vadmin_auth_user', 'is_reset_password') + op.drop_column('vadmin_auth_user', 'is_cancel') + op.drop_column('vadmin_auth_user', 'is_active') + op.drop_column('vadmin_auth_user', 'gender') + op.drop_column('vadmin_auth_user', 'avatar') + op.drop_index(op.f('ix_vadmin_auth_user_roles_id'), table_name='vadmin_auth_user_roles') + op.drop_table('vadmin_auth_user_roles') + op.drop_index(op.f('ix_vadmin_auth_role_menus_id'), table_name='vadmin_auth_role_menus') + op.drop_table('vadmin_auth_role_menus') + op.drop_index(op.f('ix_vadmin_auth_role_role_key'), table_name='vadmin_auth_role') + op.drop_index(op.f('ix_vadmin_auth_role_name'), table_name='vadmin_auth_role') + op.drop_index(op.f('ix_vadmin_auth_role_id'), table_name='vadmin_auth_role') + op.drop_table('vadmin_auth_role') + op.drop_index(op.f('ix_vadmin_auth_menu_title'), table_name='vadmin_auth_menu') + op.drop_index(op.f('ix_vadmin_auth_menu_perms'), table_name='vadmin_auth_menu') + op.drop_index(op.f('ix_vadmin_auth_menu_id'), table_name='vadmin_auth_menu') + op.drop_table('vadmin_auth_menu') + # ### end Alembic commands ### diff --git a/kinit-api/alembic/versions/a95a845d335f_生成迁移文件.py b/kinit-api/alembic/versions/a95a845d335f_生成迁移文件.py new file mode 100644 index 0000000..48adfe5 --- /dev/null +++ b/kinit-api/alembic/versions/a95a845d335f_生成迁移文件.py @@ -0,0 +1,24 @@ +"""生成迁移文件 + +Revision ID: a95a845d335f +Revises: +Create Date: 2022-07-06 21:56:31.321170 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a95a845d335f' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/kinit-api/alembic/versions/bc1417d96581_更新迁移文件.py b/kinit-api/alembic/versions/bc1417d96581_更新迁移文件.py new file mode 100644 index 0000000..14a7f75 --- /dev/null +++ b/kinit-api/alembic/versions/bc1417d96581_更新迁移文件.py @@ -0,0 +1,46 @@ +"""更新迁移文件 + +Revision ID: bc1417d96581 +Revises: a95a845d335f +Create Date: 2022-07-06 21:56:41.154567 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bc1417d96581' +down_revision = 'a95a845d335f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('vadmin_auth_user', + sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'), + sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('telephone', sa.String(length=11), nullable=False, comment='手机号'), + sa.Column('name', sa.String(length=20), nullable=False, comment='姓名'), + sa.Column('nickname', sa.String(length=20), nullable=True, comment='昵称'), + sa.Column('wechat', sa.String(length=50), nullable=True, comment='微信'), + sa.Column('qq', sa.String(length=50), nullable=True, comment='QQ'), + sa.Column('password', sa.String(length=255), nullable=True, comment='密码'), + sa.PrimaryKeyConstraint('id'), + comment='用户表' + ) + op.create_index(op.f('ix_vadmin_auth_user_id'), 'vadmin_auth_user', ['id'], unique=True) + op.create_index(op.f('ix_vadmin_auth_user_name'), 'vadmin_auth_user', ['name'], unique=False) + op.create_index(op.f('ix_vadmin_auth_user_telephone'), 'vadmin_auth_user', ['telephone'], unique=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_vadmin_auth_user_telephone'), table_name='vadmin_auth_user') + op.drop_index(op.f('ix_vadmin_auth_user_name'), table_name='vadmin_auth_user') + op.drop_index(op.f('ix_vadmin_auth_user_id'), table_name='vadmin_auth_user') + op.drop_table('vadmin_auth_user') + # ### end Alembic commands ### diff --git a/kinit-api/alembic/versions/d4898760c577_update.py b/kinit-api/alembic/versions/d4898760c577_update.py new file mode 100644 index 0000000..b2edb47 --- /dev/null +++ b/kinit-api/alembic/versions/d4898760c577_update.py @@ -0,0 +1,28 @@ +"""update + +Revision ID: d4898760c577 +Revises: 03e55ed3e858 +Create Date: 2022-08-13 11:40:09.131602 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd4898760c577' +down_revision = '03e55ed3e858' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('vadmin_auth_menu', sa.Column('redirect', sa.String(length=100), nullable=True, comment='重定向地址')) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('vadmin_auth_menu', 'redirect') + # ### end Alembic commands ### diff --git a/kinit-api/alembic/versions/ecb50546debd_update.py b/kinit-api/alembic/versions/ecb50546debd_update.py new file mode 100644 index 0000000..cdad8ec --- /dev/null +++ b/kinit-api/alembic/versions/ecb50546debd_update.py @@ -0,0 +1,28 @@ +"""update + +Revision ID: ecb50546debd +Revises: d4898760c577 +Create Date: 2022-09-15 20:59:40.938777 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ecb50546debd' +down_revision = 'd4898760c577' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('vadmin_auth_menu', sa.Column('title_zh', sa.String(length=50), nullable=True, comment='中文名称')) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('vadmin_auth_menu', 'title_zh') + # ### end Alembic commands ### diff --git a/kinit-api/alembic/versions/f2390b45da1d_update.py b/kinit-api/alembic/versions/f2390b45da1d_update.py new file mode 100644 index 0000000..048fa09 --- /dev/null +++ b/kinit-api/alembic/versions/f2390b45da1d_update.py @@ -0,0 +1,28 @@ +"""update + +Revision ID: f2390b45da1d +Revises: 0cd7a858c5a5 +Create Date: 2022-08-08 13:36:03.625827 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f2390b45da1d' +down_revision = '0cd7a858c5a5' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/kinit-api/application/__init__.py b/kinit-api/application/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/application/settings.py b/kinit-api/application/settings.py new file mode 100644 index 0000000..3933a5e --- /dev/null +++ b/kinit-api/application/settings.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/19 15:47 +# @File : settings.py +# @IDE : PyCharm +# @desc : 主配置文件 + +import os +from fastapi.security import OAuth2PasswordBearer + +# 安全警告: 不要在生产中打开调试运行! +DEBUG = True + +# 项目根目录 +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +TEMP_DIR = os.path.join(BASE_DIR, "temp") + +"""是否开启登录认证""" +OAUTH_ENABLE = True +"""登录认证视图""" +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login/", auto_error=False) if OAUTH_ENABLE else lambda: "" +"""安全的随机密钥,该密钥将用于对 JWT 令牌进行签名""" +SECRET_KEY = 'vgb0tnl9d58+6n-6h-ea&u^1#s0ccp!794=kbvqacjq75vzps$' +"""用于设定 JWT 令牌签名算法""" +ALGORITHM = "HS256" +"""令牌过期时间,9999分钟""" +ACCESS_TOKEN_EXPIRE_MINUTES = 9999 + +"""安全的随机密钥,该密钥将用于对校区标识进行签名""" +SYSTEM_KEY = "0CoJUm6Qywm6ts68" + +""" +挂载静态目录,并添加路由访问,此路由不会在接口文档中显示 +STATIC_ENABLE:是否启用静态目录 +STATIC_URL:路由访问 +STATIC_ROOT:静态文件目录相对路径 +官方文档:https://fastapi.tiangolo.com/tutorial/static-files/ +""" +STATIC_ENABLE = True +STATIC_URL = "/static" +STATIC_ROOT = "./static" + + +""" +跨域解决 +详细解释:https://cloud.tencent.com/developer/article/1886114 +官方文档:https://fastapi.tiangolo.com/tutorial/cors/ +""" +# 是否启用跨域 +CORS_ORIGIN_ENABLE = True +# 只允许访问的域名列表,* 代表所有 +ALLOW_ORIGINS = ["*"] +# 是否支持携带 cookie +ALLOW_CREDENTIALS = True +# 设置允许跨域的http方法,比如 get、post、put等。 +ALLOW_METHODS = ["*"] +# 允许携带的headers,可以用来鉴别来源等作用。 +ALLOW_HEADERS = ["*"] + +""" +数据库配置项 +连接引擎官方文档:https://www.osgeo.cn/sqlalchemy/core/engines.html +""" +if DEBUG: + # 测试库 + SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:Ktianc123@rm-bp181adf0phw2o0r05o.mysql.rds.aliyuncs.com:3306/kinit" + SQLALCHEMY_DATABASE_TYPE = "mysql" +else: + # 正式库 + SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:Ktianc123@rm-bp181adf0phw2o0r05o.mysql.rds.aliyuncs.com:3306/kinit" + SQLALCHEMY_DATABASE_TYPE = "mysql" + + +""" +中间件配置 +""" +MIDDLEWARES = [ + "core.middleware.register_middleware", +] + +""" +默认配置 +""" +# 默认密码,"0" 默认为手机号后六位 +DEFAULT_PASSWORD = "0" diff --git a/kinit-api/application/urls.py b/kinit-api/application/urls.py new file mode 100644 index 0000000..f1f79e6 --- /dev/null +++ b/kinit-api/application/urls.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/19 15:47 +# @File : urls.py +# @IDE : PyCharm +# @desc : 路由文件 + +from apps import * + +# 引入应用中的路由 +urlpatterns = [ + {"ApiRouter": auth_app, "prefix": "/auth", "tags": ["系统认证"]}, + {"ApiRouter": vadmin_auth_app, "prefix": "/vadmin/auth", "tags": ["权限管理"]}, + {"ApiRouter": vadmin_system_app, "prefix": "/vadmin/system", "tags": ["系统管理"]}, + {"ApiRouter": vadmin_record_app, "prefix": "/vadmin/record", "tags": ["记录管理"]}, +] \ No newline at end of file diff --git a/kinit-api/apps/__init__.py b/kinit-api/apps/__init__.py new file mode 100644 index 0000000..af74462 --- /dev/null +++ b/kinit-api/apps/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/2/24 10:19 +# @File : __init__.py +# @IDE : PyCharm +# @desc : 简要说明 + + +from apps.vadmin.auth.utils.login import app as auth_app +from apps.vadmin.auth.views import app as vadmin_auth_app +from apps.vadmin.system.views import app as vadmin_system_app +from apps.vadmin.record.views import app as vadmin_record_app diff --git a/kinit-api/apps/vadmin/__init__.py b/kinit-api/apps/vadmin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/apps/vadmin/auth/__init__.py b/kinit-api/apps/vadmin/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/apps/vadmin/auth/crud.py b/kinit-api/apps/vadmin/auth/crud.py new file mode 100644 index 0000000..16fa007 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/crud.py @@ -0,0 +1,144 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/2/24 10:21 +# @File : crud.py +# @IDE : PyCharm +# @desc : 增删改查 +from typing import List + +from sqlalchemy import select +from core.crud import DalBase +from sqlalchemy.ext.asyncio import AsyncSession +from . import models, schemas +from application import settings + + +class UserDal(DalBase): + + def __init__(self, db: AsyncSession): + super(UserDal, self).__init__(db, models.VadminUser, schemas.UserSimpleOut) + + async def create_data(self, data: schemas.UserIn, return_obj: bool = False, options: list = None, schema=None): + """ + 创建用户 + """ + password = data.telephone[5:12] if settings.DEFAULT_PASSWORD == "0" else settings.DEFAULT_PASSWORD + data.password = self.model.get_password_hash(password) + obj = await super(UserDal, self).create_data(data.dict(exclude={"role_ids"}), True, options, schema) + for data_id in data.role_ids: + obj.roles.append(await RoleDal(db=self.db).get_data(data_id=data_id)) + if options: + obj = await self.get_data(obj.id, options=options) + if return_obj: + return obj + if schema: + return schema.from_orm(obj).dict() + return self.out_dict(obj) + + +class RoleDal(DalBase): + + def __init__(self, db: AsyncSession): + super(RoleDal, self).__init__(db, models.VadminRole, schemas.RoleSimpleOut) + + async def get_role_menu_tree(self, role_id: int): + role = await self.get_data(role_id, options=[self.model.menus]) + return [i.id for i in role.menus] + + async def get_select_datas(self): + """获取选择数据,全部数据""" + sql = select(self.model) + queryset = await self.db.execute(sql) + return [schemas.RoleSelectOut.from_orm(i).dict() for i in queryset.scalars().all()] + + +class MenuDal(DalBase): + + def __init__(self, db: AsyncSession): + super(MenuDal, self).__init__(db, models.VadminMenu, schemas.MenuSimpleOut) + + async def get_tree_list(self): + """ + 获取菜单树列表 + """ + sql = select(self.model) + queryset = await self.db.execute(sql) + menus = queryset.scalars().all() + roots = filter(lambda i: not i.parent_id, menus) + return self.generate_tree_list(menus, roots) + + async def get_routers(self, user: models.VadminUser): + """ + 获取路由表 + export interface Menu { + name: string; // 菜单名 + icon?: string; // 菜单图标,如果没有,则会尝试使用route.meta.icon + path: string; // 菜单路径 + disabled?: boolean; // 是否禁用 + children?: Menu[]; // 子菜单 + tag: { // 菜单标签设置 + dot: boolean; // 为true则显示小圆点 + content: string'; // 内容 + type: 'error' | 'primary' | 'warn' | 'success'; // 类型 + }; + hideMenu?: boolean; // 是否隐藏菜单 + } + """ + if any([i.is_admin for i in user.roles]): + sql = select(self.model).where(self.model.disabled == False, self.model.menu_type != "2") + queryset = await self.db.execute(sql) + menus = queryset.scalars().all() + else: + menus = set() + for role in user.roles: + role_obj = await RoleDal(self.db).get_data(role.id, options=[models.VadminRole.menus]) + for menu in role_obj.menus: + if not menu.disabled and menu.menu_type != "2": + menus.add(menu) + roots = filter(lambda i: not i.parent_id, menus) + return self.generate_router_tree(menus, roots) + + async def get_treeselect(self): + """获取菜单树列表信息""" + sql = select(self.model) + queryset = await self.db.execute(sql) + return [schemas.TreeselectOut.from_orm(i).dict() for i in queryset.scalars().all()] + + def generate_router_tree(self, menus: List[models.VadminMenu], nodes: filter) -> list: + """ + 生成路由数 + + menus: 总菜单列表 + nodes:节点菜单列表 + """ + data = [] + for root in nodes: + router = schemas.RouterOut.from_orm(root) + router.name = router.path.split("/")[-1].capitalize() + router.meta = schemas.Meta(title=root.title, icon=root.icon) + if root.menu_type == "0": + sons = filter(lambda i: i.parent_id == root.id, menus) + router.children = self.generate_router_tree(menus, sons) + data.append(router.dict()) + return data + + def generate_tree_list(self, menus: List[models.VadminMenu], nodes: filter) -> list: + """ + 生成菜单树 + + menus: 总菜单列表 + nodes:每层节点菜单列表 + """ + data = [] + for root in nodes: + router = schemas.TreeListOut.from_orm(root) + if root.menu_type == "0" or root.menu_type == "1": + sons = filter(lambda i: i.parent_id == root.id, menus) + router.children = self.generate_tree_list(menus, sons) + data.append(router.dict()) + return data + + + + diff --git a/kinit-api/apps/vadmin/auth/models/__init__.py b/kinit-api/apps/vadmin/auth/models/__init__.py new file mode 100644 index 0000000..d0e2ad3 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/models/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/7/7 13:41 +# @File : __init__.py +# @IDE : PyCharm +# @desc : 简要说明 + + +from .m2m import vadmin_user_roles, vadmin_role_menus +from .menu import VadminMenu +from .role import VadminRole +from .user import VadminUser \ No newline at end of file diff --git a/kinit-api/apps/vadmin/auth/models/m2m.py b/kinit-api/apps/vadmin/auth/models/m2m.py new file mode 100644 index 0000000..5e95430 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/models/m2m.py @@ -0,0 +1,28 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/7/7 13:41 +# @File : m2m.py +# @IDE : PyCharm +# @desc : 关联中间表 + +from db.db_base import Model +from sqlalchemy import Column, Table, Integer, ForeignKey, INT + + +vadmin_user_roles = Table( + 'vadmin_auth_user_roles', + Model.metadata, + Column("id", INT, primary_key=True, unique=True, comment='主键ID', index=True, autoincrement=True), + Column('user_id', Integer, ForeignKey('vadmin_auth_user.id', ondelete='CASCADE'), primary_key=True), + Column('role_id', Integer, ForeignKey('vadmin_auth_role.id', ondelete='CASCADE'), primary_key=True), +) + + +vadmin_role_menus = Table( + 'vadmin_auth_role_menus', + Model.metadata, + Column("id", INT, primary_key=True, unique=True, comment='主键ID', index=True, autoincrement=True), + Column('role_id', Integer, ForeignKey('vadmin_auth_role.id', ondelete='CASCADE'), primary_key=True), + Column('menu_id', Integer, ForeignKey('vadmin_auth_menu.id', ondelete='CASCADE'), primary_key=True), +) diff --git a/kinit-api/apps/vadmin/auth/models/menu.py b/kinit-api/apps/vadmin/auth/models/menu.py new file mode 100644 index 0000000..f947363 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/models/menu.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/7/7 13:41 +# @File : menu.py +# @IDE : PyCharm +# @desc : 菜单模型 + + +from sqlalchemy.orm import relationship +from .m2m import vadmin_role_menus +from db.db_base import BaseModel +from sqlalchemy import Column, String, Boolean, Integer, ForeignKey + + +class VadminMenu(BaseModel): + __tablename__ = "vadmin_auth_menu" + __table_args__ = ({'comment': '菜单表'}) + + # class MenuTypes(Enum): + # dir = "0" + # menu = "1" + # button = "2" + + title = Column(String(50), index=True, nullable=False, comment="名称") + title_zh = Column(String(50), comment="中文名称") # 选择框时使用 + icon = Column(String(50), comment="菜单图标") + redirect = Column(String(100), comment="重定向地址") + component = Column(String(50), comment="前端组件地址") + path = Column(String(50), comment="前端路由地址") + disabled = Column(Boolean, default=False, comment="是否禁用") + hidden = Column(Boolean, default=False, comment="是否显示") + order = Column(Integer, comment="排序") + menu_type = Column(String(8), comment="菜单类型") + parent_id = Column(ForeignKey("vadmin_auth_menu.id", ondelete='CASCADE'), comment="父菜单") + perms = Column(String(50), comment="权限标识", unique=False, nullable=True, index=True) + + roles = relationship("VadminRole", back_populates='menus', secondary=vadmin_role_menus) diff --git a/kinit-api/apps/vadmin/auth/models/role.py b/kinit-api/apps/vadmin/auth/models/role.py new file mode 100644 index 0000000..a16b9d1 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/models/role.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/7/7 13:41 +# @File : role.py +# @IDE : PyCharm +# @desc : 角色模型 + +from sqlalchemy.orm import relationship +from db.db_base import BaseModel +from sqlalchemy import Column, String, Boolean, Integer +from .m2m import vadmin_user_roles, vadmin_role_menus + + +class VadminRole(BaseModel): + __tablename__ = "vadmin_auth_role" + __table_args__ = ({'comment': '角色表'}) + + name = Column(String(50), index=True, nullable=False, comment="名称") + role_key = Column(String(50), index=True, nullable=False, comment="权限字符") + is_active = Column(Boolean, default=True, comment="是否可用") + index = Column(Integer, comment="排序") + desc = Column(String(255), comment="描述") + is_admin = Column(Boolean, comment="是否为超级角色", default=False) + + users = relationship("VadminUser", back_populates='roles', secondary=vadmin_user_roles) + menus = relationship("VadminMenu", back_populates='roles', secondary=vadmin_role_menus) diff --git a/kinit-api/apps/vadmin/auth/models/user.py b/kinit-api/apps/vadmin/auth/models/user.py new file mode 100644 index 0000000..d65dee8 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/models/user.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/7/7 13:41 +# @File : user.py +# @IDE : PyCharm +# @desc : 用户模型 + +import datetime +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import relationship +from db.db_base import BaseModel +from sqlalchemy import Column, String, Boolean, DateTime +from passlib.context import CryptContext +from .m2m import vadmin_user_roles + +pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') + + +class VadminUser(BaseModel): + __tablename__ = "vadmin_auth_user" + __table_args__ = ({'comment': '用户表'}) + + avatar = Column(String(500), nullable=True, comment='头像') + telephone = Column(String(11), nullable=False, index=True, comment="手机号", unique=True) + name = Column(String(50), index=True, nullable=False, comment="姓名") + nickname = Column(String(50), nullable=True, comment="昵称") + password = Column(String(255), nullable=True, comment="密码") + gender = Column(String(8), nullable=True, comment="性别") + is_active = Column(Boolean, default=True, comment="是否可用") + is_cancel = Column(Boolean, default=False, comment="是否注销") + is_reset_password = Column(Boolean, default=False, comment="是否已经重置密码,没有重置的,登陆系统后必须重置密码") + last_ip = Column(String(50), nullable=True, comment="最后一次登录IP") + last_login = Column(DateTime, nullable=True, comment="最近一次登录时间") + + roles = relationship("VadminRole", back_populates='users', secondary=vadmin_user_roles) + + # generate hash password + @staticmethod + def get_password_hash(password: str) -> str: + return pwd_context.hash(password) + + # verify login password + @staticmethod + def verify_password(password: str, hashed_password: str) -> bool: + return pwd_context.verify(password, hashed_password) + + async def update_login_info(self, db: AsyncSession, last_ip: str): + """ + 更新当前登录信息 + :param db: 数据库 + :param last_ip: 最近一次登录 IP + :return: + """ + self.last_ip = last_ip + self.last_login = datetime.datetime.now() + await db.flush() + + async def is_admin(self) -> bool: + """ + 获取该用户是否拥有最高权限 + + 以最高权限为准 + + :return: + """ + return any([i.is_admin for i in self.roles]) diff --git a/kinit-api/apps/vadmin/auth/schemas/__init__.py b/kinit-api/apps/vadmin/auth/schemas/__init__.py new file mode 100644 index 0000000..3bd960a --- /dev/null +++ b/kinit-api/apps/vadmin/auth/schemas/__init__.py @@ -0,0 +1,3 @@ +from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd +from .role import Role, RoleOut, RoleIn, RoleSelectOut, RoleSimpleOut +from .menu import Menu, MenuSimpleOut, RouterOut, Meta, TreeselectOut, TreeListOut diff --git a/kinit-api/apps/vadmin/auth/schemas/menu.py b/kinit-api/apps/vadmin/auth/schemas/menu.py new file mode 100644 index 0000000..3a5726e --- /dev/null +++ b/kinit-api/apps/vadmin/auth/schemas/menu.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:19 +# @File : role.py +# @IDE : PyCharm +# @desc : pydantic 模型,用于数据库序列化操作 + +# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 + + +from typing import Optional, List +from pydantic import BaseModel, Field, validator +from core.validator import ValiDatetime + + +class Menu(BaseModel): + title: str + title_zh: Optional[str] = None + icon: Optional[str] = None + component: str + path: str + disabled: bool = False + hidden: bool = False + order: Optional[int] = None + perms: Optional[str] = None + parent_id: Optional[int] = None + menu_type: str + + +class MenuSimpleOut(Menu): + id: int + create_datetime: ValiDatetime + update_datetime: ValiDatetime + + class Config: + orm_mode = True + + +class Meta(BaseModel): + title: str + icon: Optional[str] = None + + +# 路由展示 +class RouterOut(BaseModel): + name: Optional[str] = None + component: str + path: str + redirect: Optional[str] = None + perms: Optional[str] = None + meta: Optional[Meta] = None + disabled: bool = False + hidden: bool = Field(False, alias='hideMenu') + children: List['RouterOut'] = [] + + class Config: + orm_mode = True + + +RouterOut.update_forward_refs() + + +# 采单树列表,选用权限展示使用 +class TreeselectOut(BaseModel): + id: int + label: str = Field(alias='title') + order: Optional[int] = 1 + parent_id: Optional[int] = None + + class Config: + orm_mode = True + + +class TreeListOut(MenuSimpleOut): + children: List['TreeListOut'] = [] + + class Config: + orm_mode = True + + +RouterOut.update_forward_refs() diff --git a/kinit-api/apps/vadmin/auth/schemas/role.py b/kinit-api/apps/vadmin/auth/schemas/role.py new file mode 100644 index 0000000..b185063 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/schemas/role.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:19 +# @File : role.py +# @IDE : PyCharm +# @desc : pydantic 模型,用于数据库序列化操作 + +# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 + + +from typing import Optional, List +from pydantic import BaseModel +from core.validator import ValiDatetime +from .menu import MenuSimpleOut + + +class Role(BaseModel): + name: str + is_active: bool = True + index: Optional[int] = None + desc: Optional[str] = None + role_key: str + is_admin: bool = False + + +class RoleSimpleOut(Role): + id: int + create_datetime: ValiDatetime + update_datetime: ValiDatetime + + class Config: + orm_mode = True + + +class RoleOut(RoleSimpleOut): + menus: Optional[List[MenuSimpleOut]] = [] + + class Config: + orm_mode = True + + +class RoleIn(Role): + menus: Optional[List[int]] = [] + + +class RoleSelectOut(BaseModel): + id: int + name: str + is_active: bool + role_key: str + + class Config: + orm_mode = True diff --git a/kinit-api/apps/vadmin/auth/schemas/user.py b/kinit-api/apps/vadmin/auth/schemas/user.py new file mode 100644 index 0000000..db8cca4 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/schemas/user.py @@ -0,0 +1,63 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:19 +# @File : user.py +# @IDE : PyCharm +# @desc : pydantic 模型,用于数据库序列化操作 + +# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 + + +from typing import List, Optional +from pydantic import BaseModel, validator +from core.validator import vali_telephone, ValiDatetime +from .role import RoleSimpleOut + + +class User(BaseModel): + name: str + telephone: str + nickname: Optional[str] = None + avatar: Optional[str] = None + is_active: Optional[bool] = True + is_cancel: Optional[bool] = False + gender: Optional[str] = "0" + + # validators + _normalize_telephone = validator('telephone', allow_reuse=True)(vali_telephone) + + +class UserIn(User): + role_ids: Optional[List[int]] = [] + password: Optional[str] = "" + + +class UserSimpleOut(User): + id: int + update_datetime: ValiDatetime + create_datetime: ValiDatetime + + class Config: + orm_mode = True + + +class UserOut(UserSimpleOut): + roles: Optional[List[RoleSimpleOut]] = [] + + class Config: + orm_mode = True + + +class UserUpdate(BaseModel): + name: str + nickname: Optional[str] = None + is_active: Optional[bool] = True + is_cancel: Optional[bool] = False + gender: Optional[str] = "0" + role_ids: Optional[List[int]] = [] + + +class ResetPwd(BaseModel): + password: str + password_two: str diff --git a/kinit-api/apps/vadmin/auth/utils/__init__.py b/kinit-api/apps/vadmin/auth/utils/__init__.py new file mode 100644 index 0000000..11b947c --- /dev/null +++ b/kinit-api/apps/vadmin/auth/utils/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/8/8 11:02 +# @File : __init__.py +# @IDE : PyCharm +# @desc : 简要说明 diff --git a/kinit-api/apps/vadmin/auth/utils/auth_util.py b/kinit-api/apps/vadmin/auth/utils/auth_util.py new file mode 100644 index 0000000..2352a1d --- /dev/null +++ b/kinit-api/apps/vadmin/auth/utils/auth_util.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/8/8 11:02 +# @File : auth_util.py +# @IDE : PyCharm +# @desc : 简要说明 + +from datetime import datetime, timedelta +from typing import Optional, List +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession +from core.database import db_getter +from application import settings +from jose import jwt +from apps.vadmin.auth import crud, models +from pydantic import BaseModel, validator +from core.validator import vali_telephone + + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): + """ + 创建一个生成新的访问令牌的工具函数。 + """ + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=60) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt + + +class LoginForm(BaseModel): + telephone: str + password: str + + # validators + _normalize_telephone = validator('telephone', allow_reuse=True)(vali_telephone) + + +async def authenticate_user(data: LoginForm, db: AsyncSession = Depends(db_getter)): + """验证用户密码""" + result = { + "status": False, + "user": None, + "msg": None, + "data": data, + "db": db + } + user = await crud.UserDal(db).get_data(telephone=data.telephone, return_none=True, options=[models.VadminUser.roles]) + if not user: + result["msg"] = "该手机号不存在!" + elif not models.VadminUser.verify_password(data.password, user.password): + result["msg"] = "手机号或密码不正确!" + elif not user.is_active: + result["msg"] = "此手机号已被冻结!" + elif user.is_cancel: + result["msg"] = "此手机号已被注销!" + elif user: + result["status"] = True + result["user"] = user + return result diff --git a/kinit-api/apps/vadmin/auth/utils/current.py b/kinit-api/apps/vadmin/auth/utils/current.py new file mode 100644 index 0000000..66ecd27 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/utils/current.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/24 16:44 +# @File : current.py +# @IDE : PyCharm +# @desc : 获取认证后的信息工具 + + +from fastapi import Depends +from pydantic import BaseModel +from starlette import status +from sqlalchemy.ext.asyncio import AsyncSession +from core.database import db_getter +from application import settings +from jose import JWTError, jwt +from apps.vadmin.auth import crud, models +from core.exception import CustomException + + +async def get_user_permissions(user: models.VadminUser, db: AsyncSession): + """ + 获取跟进系统用户所有权限列表 + """ + roles = [] + for i in user.roles: + if i.is_admin: + return ["*:*:*"] + roles.append(i.id) + permissions = set() + for data_id in roles: + role_obj = await crud.RoleDal(db).get_data(data_id, options=[models.VadminUser]) + for menu in role_obj.menus: + if menu.perms and menu.status: + permissions.add(menu.perms) + return list(permissions) + + +class Auth(BaseModel): + user: models.VadminUser = None + db: AsyncSession + + class Config: + arbitrary_types_allowed = True + + +async def login_auth(token: str = Depends(settings.oauth2_scheme), db: AsyncSession = Depends(db_getter)): + """ + 更新 login_auth 以接收 JWT 令牌。 + + 解码接收到的令牌,对其进行校验,然后返回当前用户。 + + 如果令牌无效,立即返回一个 HTTP 错误。 + """ + if not settings.OAUTH_ENABLE: + return Auth(db=db) + if not token: + raise CustomException(msg="请先登录!", code=status.HTTP_403_FORBIDDEN) + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + telephone: str = payload.get("sub") + if telephone is None: + raise CustomException(msg="无效 Token!", code=status.HTTP_403_FORBIDDEN) + except JWTError: + raise CustomException(msg="无效 Token!", code=status.HTTP_403_FORBIDDEN) + user = await crud.UserDal(db).get_data(telephone=telephone, return_none=True) + if user is None: + raise CustomException(msg="用户不存在!", code=status.HTTP_404_NOT_FOUND) + elif not user.is_active: + raise CustomException(msg="用户已被冻结!", code=status.HTTP_403_FORBIDDEN) + elif user.is_cancel: + raise CustomException(msg="用户已被注销!", code=status.HTTP_403_FORBIDDEN) + return Auth(user=user, db=db) + + +class AdminAuth(BaseModel): + admin: models.VadminUser + db: AsyncSession + + class Config: + arbitrary_types_allowed = True + + +async def full_admin(token: str = Depends(settings.oauth2_scheme), db: AsyncSession = Depends(db_getter)): + """ + 更新 full_user 以接收 JWT 令牌。 + + 解码接收到的令牌,对其进行校验,然后返回当前用户。 + + 如果令牌无效,立即返回一个 HTTP 错误。 + """ + if not token: + raise CustomException(msg="请先登录!", code=status.HTTP_403_FORBIDDEN) + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + telephone: str = payload.get("sub") + if telephone is None: + raise CustomException(msg="无效 Token!", code=status.HTTP_403_FORBIDDEN) + except JWTError: + raise CustomException(msg="无效 Token!", code=status.HTTP_403_FORBIDDEN) + admin = await crud.UserDal(db).get_data(telephone=telephone, return_none=True, + options=[models.VadminUser.roles, "roles.menus"]) + if admin is None: + raise CustomException(msg="用户不存在!", code=status.HTTP_404_NOT_FOUND) + elif not admin.is_active: + raise CustomException(msg="用户已被冻结!", code=status.HTTP_403_FORBIDDEN) + elif admin.is_cancel: + raise CustomException(msg="用户已被注销!", code=status.HTTP_403_FORBIDDEN) + return AdminAuth(admin=admin, db=db) \ No newline at end of file diff --git a/kinit-api/apps/vadmin/auth/utils/login.py b/kinit-api/apps/vadmin/auth/utils/login.py new file mode 100644 index 0000000..8cfd957 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/utils/login.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/24 16:44 +# @File : views.py +# @IDE : PyCharm +# @desc : 安全认证视图 + + +""" +JWT 表示 「JSON Web Tokens」。https://jwt.io/ + +它是一个将 JSON 对象编码为密集且没有空格的长字符串的标准。 + +通过这种方式,你可以创建一个有效期为 1 周的令牌。然后当用户第二天使用令牌重新访问时,你知道该用户仍然处于登入状态。 +一周后令牌将会过期,用户将不会通过认证,必须再次登录才能获得一个新令牌。 + +我们需要安装 python-jose 以在 Python 中生成和校验 JWT 令牌:pip install python-jose[cryptography] + +PassLib 是一个用于处理哈希密码的很棒的 Python 包。它支持许多安全哈希算法以及配合算法使用的实用程序。推荐的算法是 「Bcrypt」:pip install passlib[bcrypt] +""" +import json +from datetime import timedelta +from fastapi import APIRouter, Depends, Request +from utils.response import SuccessResponse, ErrorResponse +from application import settings +from .auth_util import authenticate_user, create_access_token +from apps.vadmin.record.models import VadminLoginRecord +from apps.vadmin.auth.crud import RoleDal, MenuDal +from apps.vadmin.auth.models import VadminRole +from .current import AdminAuth, full_admin + +app = APIRouter() + + +@app.post("/login/", summary="登录") +async def login_for_access_token(request: Request, data: dict = Depends(authenticate_user)): + if not data.get("status", False): + await VadminLoginRecord.create_login_record(telephone=data["data"].telephone, status=data.get("status"), + request=request, response=data.get("msg"), db=data.get("db")) + return ErrorResponse(msg=data.get("msg")) + user = data.get("user") + access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + + access_token = create_access_token( + data={"sub": user.telephone}, expires_delta=access_token_expires + ) + result = { + "access_token": access_token, + "token_type": "bearer", + "is_reset_password": user.is_reset_password, + "user": { + "id": user.id, + "telephone": user.telephone, + "name": user.name, + "nickname": user.nickname, + "avatar": user.avatar, + "roles": [{"name": i.name, "value": i.role_key} for i in user.roles] + } + } + await VadminLoginRecord.create_login_record(telephone=user.telephone, status=data.get("status"), request=request, + response=json.dumps(result), db=data.get("db")) + return SuccessResponse(result) + + +@app.get("/getUserInfo/", summary="获取当前登录用户基本信息") +async def get_user_info(auth: AdminAuth = Depends(full_admin)): + result = { + "id": auth.admin.id, + "telephone": auth.admin.telephone, + "name": auth.admin.name, + "nickname": auth.admin.nickname, + "avatar": auth.admin.avatar, + "roles": [{"name": i.name, "value": i.role_key} for i in auth.admin.roles] + } + return SuccessResponse(result) + + +@app.get("/getPermCode/", summary="获取当前登录用户所有权限") +async def get_perm_code(auth: AdminAuth = Depends(full_admin)): + roles = [] + for i in auth.admin.roles: + if i.is_admin: + return SuccessResponse(["*:*:*"]) + roles.append(i.id) + permissions = set() + for data_id in roles: + role_obj = await RoleDal(auth.db).get_data(data_id, options=[VadminRole.menus]) + for menu in role_obj.menus: + if menu.perms and menu.status: + permissions.add(menu.perms) + return SuccessResponse(list(permissions)) + + +@app.get("/getMenuList/", summary="获取当前用户菜单树") +async def get_menu_list(auth: AdminAuth = Depends(full_admin)): + datas = await MenuDal(auth.db).get_routers(auth.admin) + return SuccessResponse(datas) diff --git a/kinit-api/apps/vadmin/auth/views.py b/kinit-api/apps/vadmin/auth/views.py new file mode 100644 index 0000000..8d68d29 --- /dev/null +++ b/kinit-api/apps/vadmin/auth/views.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/2/24 17:02 +# @File : views.py +# @IDE : PyCharm +# @desc : 简要说明 + +from typing import Optional +from fastapi import APIRouter, Depends, Query +from utils.response import SuccessResponse +from . import schemas, crud, models +from core.dependencies import paging, id_list, Params +from apps.vadmin.auth.utils.current import login_auth, Auth + +app = APIRouter() + + +########################################################### +# 用户管理 +########################################################### +@app.get("/users/", summary="获取用户列表") +async def get_users(params: Params = Depends(paging), auth: Auth = Depends(login_auth)): + datas = await crud.UserDal(auth.db).get_datas(params.page, params.limit) + count = await crud.UserDal(auth.db).get_count() + return SuccessResponse(datas, count=count) + + +@app.post("/users/", summary="创建用户") +async def create_user(data: schemas.UserIn, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.UserDal(auth.db).create_data(data=data)) + + +@app.delete("/users/", summary="批量删除用户") +async def delete_users(ids: list = Depends(id_list), auth: Auth = Depends(login_auth)): + await crud.UserDal(auth.db).delete_datas(ids=ids) + return SuccessResponse("删除成功") + + +@app.put("/users/{data_id}/", summary="更新用户基本信息") +async def put_user(data_id: int, data: schemas.User, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.UserDal(auth.db).put_data(data_id, data)) + + +########################################################### +# 角色管理 +########################################################### +@app.get("/roles/", summary="获取角色列表") +async def get_roles(params: Params = Depends(paging), auth: Auth = Depends(login_auth), + name: Optional[str] = Query(None, title="角色名称", description="查询角色名称")): + datas = await crud.RoleDal(auth.db).get_datas(params.page, params.limit, name=name) + count = await crud.RoleDal(auth.db).get_count(name=name) + return SuccessResponse(datas, count=count) + + +@app.post("/roles/", summary="创建角色信息") +async def create_role(role: schemas.RoleIn, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.RoleDal(auth.db).create_data(data=role)) + + +@app.delete("/roles/", summary="批量删除角色") +async def delete_roles(ids: list = Depends(id_list), auth: Auth = Depends(login_auth)): + await crud.RoleDal(auth.db).delete_datas(ids) + return SuccessResponse("删除成功") + + +@app.put("/roles/{data_id}/", summary="更新角色信息") +async def put_role(data_id: int, data: schemas.RoleIn, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.RoleDal(auth.db).put_data(data_id, data)) + + +@app.get("/roles/{data_id}/", summary="获取角色信息") +async def get_role(data_id: int, auth: Auth = Depends(login_auth)): + model = models.VadminRole + options = [model.menus] + schema = schemas.RoleOut + return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, options, schema)) + + +########################################################### +# 菜单管理 +########################################################### +@app.get("/menus/", summary="获取菜单列表") +async def get_menus(auth: Auth = Depends(login_auth)): + datas = await crud.MenuDal(auth.db).get_tree_list() + return SuccessResponse(datas) + + +@app.get("/menus/treeselect/", summary="获取菜单列表树信息,角色权限使用") +async def get_menus_treeselect(auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.MenuDal(auth.db).get_treeselect()) + + +@app.post("/menus/", summary="创建菜单信息") +async def create_menu(menu: schemas.Menu, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.MenuDal(auth.db).create_data(data=menu)) + + +@app.delete("/menus/", summary="批量删除菜单") +async def delete_menus(ids: list = Depends(id_list), auth: Auth = Depends(login_auth)): + await crud.MenuDal(auth.db).delete_datas(ids) + return SuccessResponse("删除成功") + + +@app.delete("/menus/", summary="批量删除菜单") +async def delete_menus(ids: list = Depends(id_list), auth: Auth = Depends(login_auth)): + await crud.MenuDal(auth.db).delete_datas(ids=ids) + return SuccessResponse("删除成功") + + +@app.put("/menus/{data_id}/", summary="更新菜单信息") +async def put_menus(data_id: int, data: schemas.Menu, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.MenuDal(auth.db).put_data(data_id, data)) + + +@app.get("/menus/{data_id}/", summary="获取菜单信息") +async def put_menus(data_id: int, auth: Auth = Depends(login_auth)): + schema = schemas.MenuSimpleOut + return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, None, schema)) + + +@app.get("/role/menus/tree/{role_id}/", summary="获取菜单列表树信息以及角色菜单权限ID,角色权限使用") +async def get_role_menu_tree(role_id: int, auth: Auth = Depends(login_auth)): + treeselect = await crud.MenuDal(auth.db).get_treeselect() + role_menu_tree = await crud.RoleDal(auth.db).get_role_menu_tree(role_id) + return SuccessResponse({"role_menu_tree": role_menu_tree, "menus": treeselect}) diff --git a/kinit-api/apps/vadmin/record/__init__.py b/kinit-api/apps/vadmin/record/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/apps/vadmin/record/crud.py b/kinit-api/apps/vadmin/record/crud.py new file mode 100644 index 0000000..7f0b20a --- /dev/null +++ b/kinit-api/apps/vadmin/record/crud.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:18 +# @File : crud.py +# @IDE : PyCharm +# @desc : 数据库 增删改查操作 + +# sqlalchemy 查询操作:https://segmentfault.com/a/1190000016767008 +# sqlalchemy 关联查询:https://www.jianshu.com/p/dfad7c08c57a +# sqlalchemy 关联查询详细:https://blog.csdn.net/u012324798/article/details/103940527 +from sqlalchemy.ext.asyncio import AsyncSession +from . import models, schemas +from core.crud import DalBase + + +class LoginRecordDal(DalBase): + def __init__(self, db: AsyncSession): + super(LoginRecordDal, self).__init__(db, models.VadminLoginRecord, schemas.LoginRecordSimpleOut) + + +class SMSSendRecordDal(DalBase): + def __init__(self, db: AsyncSession): + super(SMSSendRecordDal, self).__init__(db, models.VadminSMSSendRecord, schemas.SMSSendRecordSimpleOut) diff --git a/kinit-api/apps/vadmin/record/models/__init__.py b/kinit-api/apps/vadmin/record/models/__init__.py new file mode 100644 index 0000000..2bc7cbd --- /dev/null +++ b/kinit-api/apps/vadmin/record/models/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/2/14 21:11 +# @File : __init__.py.py +# @IDE : PyCharm +# @desc : xpath例子 + + +from .login import VadminLoginRecord +from .operation import VadminOperationRecord +from .sms import VadminSMSSendRecord diff --git a/kinit-api/apps/vadmin/record/models/login.py b/kinit-api/apps/vadmin/record/models/login.py new file mode 100644 index 0000000..5c263fd --- /dev/null +++ b/kinit-api/apps/vadmin/record/models/login.py @@ -0,0 +1,43 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/7/7 13:41 +# @File : login.py +# @IDE : PyCharm +# @desc : 登录记录模型 + + +from sqlalchemy.ext.asyncio import AsyncSession +from db.db_base import BaseModel +from sqlalchemy import Column, String, Boolean, TEXT +from fastapi import Request +from user_agents import parse + + +class VadminLoginRecord(BaseModel): + __tablename__ = "vadmin_record_login" + __table_args__ = ({'comment': '登录记录表'}) + + telephone = Column(String(50), index=True, nullable=False, comment="手机号") + status = Column(Boolean, default=True, comment="是否登录成功") + ip = Column(String(50), comment="登陆地址") + address = Column(String(50), comment="登陆地点") + browser = Column(String(50), comment="浏览器") + system = Column(String(50), comment="操作系统") + response = Column(TEXT, comment="响应信息") + request = Column(TEXT, comment="请求信息") + + @classmethod + async def create_login_record(cls, telephone: str, status: bool, request: Request, response: str + , db: AsyncSession): + """ + 创建登录记录 + :return: + """ + user_agent = parse(request.headers.get("user-agent")) + system = f"{user_agent.os.family} {user_agent.os.version_string}" + browser = f"{user_agent.browser.family} {user_agent.browser.version_string}" + obj = VadminLoginRecord(telephone=telephone, status=status, ip=request.client.host, browser=browser, + system=system, response=response, request=str(request.__dict__)) + db.add(obj) + await db.flush() diff --git a/kinit-api/apps/vadmin/record/models/operation.py b/kinit-api/apps/vadmin/record/models/operation.py new file mode 100644 index 0000000..9c42f09 --- /dev/null +++ b/kinit-api/apps/vadmin/record/models/operation.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/7/7 13:41 +# @File : operation.py +# @IDE : PyCharm +# @desc : 操作记录模型 + + +from sqlalchemy.ext.asyncio import AsyncSession +from user_agents import parse +from apps.vadmin.auth import models +from db.db_base import BaseModel +from sqlalchemy import Column, String, Boolean, TEXT, ForeignKey +from fastapi import Request + + +class VadminOperationRecord(BaseModel): + __tablename__ = "vadmin_record_operation" + __table_args__ = ({'comment': '操作记录表'}) + + user = Column(ForeignKey("vadmin_auth_user.id", ondelete='CASCADE'), comment="操作人") + status = Column(Boolean, default=True, comment="操作结果状态") + ip = Column(String(50), comment="登陆地址") + address = Column(String(50), comment="登陆地点") + browser = Column(String(50), comment="浏览器") + system = Column(String(50), comment="操作系统") + response = Column(TEXT, comment="响应信息") + request = Column(TEXT, comment="请求信息") + request_api = Column(String(255), comment="请求接口") + request_method = Column(String(255), comment="请求方式") + + @classmethod + async def create_operation_record(cls, user: models.VadminUser, status: bool, request: Request, response: str, + db: AsyncSession): + """ + 创建操作记录 + :return: + """ + user_agent = parse(request.headers.get("user-agent")) + system = f"{user_agent.os.family} {user_agent.os.version_string}" + browser = f"{user_agent.browser.family} {user_agent.browser.version_string}" + obj = VadminOperationRecord(user=user, status=status, ip=request.client.host, browser=browser, system=system, + response=response, request=str(request.__dict__), request_method=request.method, + request_api=request.url) + db.add(obj) + await db.flush() diff --git a/kinit-api/apps/vadmin/record/models/sms.py b/kinit-api/apps/vadmin/record/models/sms.py new file mode 100644 index 0000000..f7d911e --- /dev/null +++ b/kinit-api/apps/vadmin/record/models/sms.py @@ -0,0 +1,23 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/3/21 17:36 +# @File : sms.py +# @IDE : PyCharm +# @desc : 短信发送记录模型 + + +from db.db_base import BaseModel +from sqlalchemy import Column, String, Boolean, ForeignKey + + +class VadminSMSSendRecord(BaseModel): + __tablename__ = "vadmin_record_sms_send" + __table_args__ = ({'comment': '短信发送记录表'}) + + user_id = Column(ForeignKey("vadmin_auth_user.id", ondelete='CASCADE'), comment="操作人") + status = Column(Boolean, default=True, comment="发送状态") + content = Column(String(255), comment="发送内容") + telephone = Column(String(11), comment="目标手机号") + desc = Column(String(255), comment="失败描述") + scene = Column(String(50), comment="发送场景") diff --git a/kinit-api/apps/vadmin/record/schemas/__init__.py b/kinit-api/apps/vadmin/record/schemas/__init__.py new file mode 100644 index 0000000..bee5d1a --- /dev/null +++ b/kinit-api/apps/vadmin/record/schemas/__init__.py @@ -0,0 +1,2 @@ +from .login import LoginRecord, LoginRecordSimpleOut +from .sms import SMSSendRecord, SMSSendRecordSimpleOut diff --git a/kinit-api/apps/vadmin/record/schemas/login.py b/kinit-api/apps/vadmin/record/schemas/login.py new file mode 100644 index 0000000..cf9cc63 --- /dev/null +++ b/kinit-api/apps/vadmin/record/schemas/login.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:19 +# @File : login.py +# @IDE : PyCharm +# @desc : pydantic 模型,用于数据库序列化操作 + +# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 + + +from typing import Optional, List +from pydantic import BaseModel +from core.validator import ValiDatetime + + +class LoginRecord(BaseModel): + telephone: str + status: bool + ip: Optional[str] = None + address: Optional[str] = None + browser: Optional[str] = None + system: Optional[str] = None + response: Optional[str] = None + + +class LoginRecordSimpleOut(LoginRecord): + id: int + create_datetime: ValiDatetime + update_datetime: ValiDatetime + + class Config: + orm_mode = True diff --git a/kinit-api/apps/vadmin/record/schemas/sms.py b/kinit-api/apps/vadmin/record/schemas/sms.py new file mode 100644 index 0000000..b5459ad --- /dev/null +++ b/kinit-api/apps/vadmin/record/schemas/sms.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/3/21 17:54 +# @File : sms.py +# @IDE : PyCharm +# @desc : 简要说明 + + +from typing import Optional, List +from pydantic import BaseModel +from core.validator import ValiDatetime + + +class SMSSendRecord(BaseModel): + telephone: str + status: bool = True + user_id: Optional[int] = None + content: Optional[str] = None + desc: Optional[str] = None + scene: Optional[str] = None + + +class SMSSendRecordSimpleOut(SMSSendRecord): + id: int + create_datetime: ValiDatetime + update_datetime: ValiDatetime + + class Config: + orm_mode = True diff --git a/kinit-api/apps/vadmin/record/views.py b/kinit-api/apps/vadmin/record/views.py new file mode 100644 index 0000000..b29c692 --- /dev/null +++ b/kinit-api/apps/vadmin/record/views.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/24 16:44 +# @File : views.py +# @IDE : PyCharm +# @desc : 主要接口文件 + +from typing import Optional +from fastapi import APIRouter, Depends, Query +from core.dependencies import paging, Params +from utils.response import SuccessResponse +from . import crud +from apps.vadmin.auth.utils.current import login_auth, Auth + +app = APIRouter() + + +########################################################### +# 登录日志管理 +########################################################### +@app.get("/login/", summary="获取登录日志列表") +async def get_users(params: Params = Depends(paging), auth: Auth = Depends(login_auth), + telephone: Optional[str] = Query(None, title="手机号", description="查询手机号")): + datas = await crud.LoginRecordDal(auth.db).\ + get_datas(params.page, params.limit, telephone=telephone, order="desc") + count = await crud.LoginRecordDal(auth.db).get_count(telephone=telephone) + return SuccessResponse(datas, count=count) + + +########################################################### +# 短信发送管理 +########################################################### +@app.get("/sms/send/list/", summary="获取短信发送列表") +async def get_sms_send_list(params: Params = Depends(paging), auth: Auth = Depends(login_auth), + telephone: Optional[str] = Query(None, title="手机号", description="查询手机号")): + datas = await crud.SMSSendRecordDal(auth.db).get_datas(params.page, params.limit, telephone=telephone, order="desc") + count = await crud.SMSSendRecordDal(auth.db).get_count(telephone=telephone) + return SuccessResponse(datas, count=count) diff --git a/kinit-api/apps/vadmin/system/__init__.py b/kinit-api/apps/vadmin/system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/apps/vadmin/system/crud.py b/kinit-api/apps/vadmin/system/crud.py new file mode 100644 index 0000000..8eb4717 --- /dev/null +++ b/kinit-api/apps/vadmin/system/crud.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:18 +# @File : crud.py +# @IDE : PyCharm +# @desc : 数据库 增删改查操作 + +# sqlalchemy 查询操作:https://segmentfault.com/a/1190000016767008 +# sqlalchemy 关联查询:https://www.jianshu.com/p/dfad7c08c57a +# sqlalchemy 关联查询详细:https://blog.csdn.net/u012324798/article/details/103940527 +from typing import List +from sqlalchemy.ext.asyncio import AsyncSession +from . import models, schemas +from core.crud import DalBase + + +class DictTypeDal(DalBase): + + def __init__(self, db: AsyncSession): + super(DictTypeDal, self).__init__(db, models.VadminDictType, schemas.DictTypeSimpleOut) + + async def get_dicts_details(self, dict_types: List[str]) -> dict: + """ + 获取多个字典类型下的字典元素列表 + """ + data = {} + for dict_type in dict_types: + dict_data = await DictTypeDal(self.db).\ + get_data(dict_type=dict_type, return_none=True, options=[self.model.details]) + if not dict_data: + data[dict_type] = [] + continue + else: + data[dict_type] = [schemas.DictDetailsSimpleOut.from_orm(i).dict() for i in dict_data.details] + return data + + +class DictDetailsDal(DalBase): + + def __init__(self, db: AsyncSession): + super(DictDetailsDal, self).__init__(db, models.VadminDictDetails, schemas.DictDetailsSimpleOut) diff --git a/kinit-api/apps/vadmin/system/models/__init__.py b/kinit-api/apps/vadmin/system/models/__init__.py new file mode 100644 index 0000000..8ff9a44 --- /dev/null +++ b/kinit-api/apps/vadmin/system/models/__init__.py @@ -0,0 +1 @@ +from .dict import VadminDictType, VadminDictDetails diff --git a/kinit-api/apps/vadmin/system/models/dict.py b/kinit-api/apps/vadmin/system/models/dict.py new file mode 100644 index 0000000..3541ae2 --- /dev/null +++ b/kinit-api/apps/vadmin/system/models/dict.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/7/7 13:41 +# @File : user.py +# @IDE : PyCharm +# @desc : 系统字典模型 + +from sqlalchemy.orm import relationship +from db.db_base import BaseModel +from sqlalchemy import Column, String, Boolean, ForeignKey, Integer + + +class VadminDictType(BaseModel): + __tablename__ = "vadmin_system_dict_type" + __table_args__ = ({'comment': '字典类型表'}) + + dict_name = Column(String(50), index=True, nullable=False, comment="字典名称") + dict_type = Column(String(50), index=True, nullable=False, comment="字典类型") + status = Column(Boolean, default=True, comment="字典状态,是否可用") + remark = Column(String(255), comment="备注") + details = relationship("VadminDictDetails", back_populates="dict_type") + + +class VadminDictDetails(BaseModel): + __tablename__ = "vadmin_system_dict_details" + __table_args__ = ({'comment': '字典详情表'}) + + dict_label = Column(String(50), index=True, nullable=False, comment="字典标签") + dict_value = Column(String(50), index=True, nullable=False, comment="字典键值") + status = Column(Boolean, default=True, comment="字典状态,是否可用") + is_default = Column(Boolean, default=False, comment="是否默认") + sort = Column(Integer, comment="字典排序") + dict_type_id = Column(Integer, ForeignKey("vadmin_system_dict_type.id", ondelete='CASCADE'), comment="关联字典类型") + dict_type = relationship("VadminDictType", foreign_keys=dict_type_id, back_populates="details") + remark = Column(String(255), comment="备注") diff --git a/kinit-api/apps/vadmin/system/schemas/__init__.py b/kinit-api/apps/vadmin/system/schemas/__init__.py new file mode 100644 index 0000000..242ff44 --- /dev/null +++ b/kinit-api/apps/vadmin/system/schemas/__init__.py @@ -0,0 +1 @@ +from .dict import DictType, DictDetails, DictTypeSimpleOut, DictDetailsSimpleOut diff --git a/kinit-api/apps/vadmin/system/schemas/dict.py b/kinit-api/apps/vadmin/system/schemas/dict.py new file mode 100644 index 0000000..ea31ef5 --- /dev/null +++ b/kinit-api/apps/vadmin/system/schemas/dict.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:19 +# @File : dict.py +# @IDE : PyCharm +# @desc : pydantic 模型,用于数据库序列化操作 + +# pydantic 验证数据:https://blog.csdn.net/qq_44291044/article/details/104693526 + + +from typing import Optional, List +from pydantic import BaseModel +from core.validator import ValiDatetime + + +class DictType(BaseModel): + dict_name: str + dict_type: str + status: Optional[bool] = True + remark: Optional[str] = None + + class Config: + # 示例参数值,会默认显示在接口文档中,example为固定写法 + schema_extra = { + "example": { + "dict_name": "用户性别", + "dict_type": "sys_user_sex", + "status": True, + "remark": "性别选择" + } + } + + +class DictTypeSimpleOut(DictType): + id: int + create_datetime: ValiDatetime + update_datetime: ValiDatetime + + class Config: + orm_mode = True + + +class DictDetails(BaseModel): + dict_label: str + dict_value: str + status: Optional[bool] = True + is_default: Optional[bool] = False + remark: Optional[str] = None + sort: Optional[str] = None + dict_data: int + + +class DictDetailsSimpleOut(DictDetails): + id: int + create_datetime: ValiDatetime + update_datetime: ValiDatetime + + class Config: + orm_mode = True diff --git a/kinit-api/apps/vadmin/system/views.py b/kinit-api/apps/vadmin/system/views.py new file mode 100644 index 0000000..6820116 --- /dev/null +++ b/kinit-api/apps/vadmin/system/views.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/24 16:44 +# @File : views.py +# @IDE : PyCharm +# @desc : 主要接口文件 + +from typing import Optional, List +from fastapi import APIRouter, Depends, Query, Body +from utils.response import SuccessResponse, ErrorResponse +from . import schemas, crud +from core.dependencies import paging, id_list, Params +from apps.vadmin.auth.utils.current import login_auth, Auth + +app = APIRouter() + + +########################################################### +# 字典类型管理 +########################################################### +@app.get("/dictTypes/", summary="获取字典类型列表") +async def get_dict_types(params: Params = Depends(paging), auth: Auth = Depends(login_auth), + dict_name: Optional[str] = Query(None, title="字典名称", description="查询字典名称")): + datas = await crud.DictTypeDal(auth.db).get_datas(params.page, params.limit, dict_name=dict_name) + count = await crud.DictTypeDal(auth.db).get_count(dict_name=dict_name) + return SuccessResponse(datas, count=count) + + +@app.post("/dictTypes/", summary="创建字典类型") +async def create_dict_types(data: schemas.DictType, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.DictTypeDal(auth.db).create_data(data=data)) + + +@app.delete("/dictTypes/", summary="批量删除字典类型") +async def delete_dict_types(ids: list = Depends(id_list), auth: Auth = Depends(login_auth)): + await crud.DictTypeDal(auth.db).delete_datas(ids=ids) + return SuccessResponse("删除成功") + + +@app.put("/dictTypes/{data_id}/", summary="更新字典类型") +async def put_dict_types(data_id: int, data: schemas.DictType, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.DictTypeDal(auth.db).put_data(data_id, data)) + + +@app.get("/dictTypes/{data_id}/", summary="获取字典类型详细") +async def get_dict_type(data_id: int, auth: Auth = Depends(login_auth)): + schema = schemas.DictTypeSimpleOut + return SuccessResponse(await crud.DictTypeDal(auth.db).get_data(data_id, None, schema)) + + +@app.post("/dictTypes/details/", summary="获取多个字典类型下的字典元素列表") +async def post_dicts_details(auth: Auth = Depends(login_auth), + dict_types: List[str] = Body(None, title="字典元素列表", description="查询字典元素列表")): + datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types) + return SuccessResponse(datas) + + +########################################################### +# 字典元素管理 +########################################################### +@app.post("/dict/details/", summary="创建字典元素") +async def create_dict_details(data: schemas.DictDetails, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.DictDetailsDal(auth.db).create_data(data=data)) + + +@app.get("/dict/details/", summary="获取单个字典类型下的字典元素列表,分页") +async def get_dict_details(params: Params = Depends(paging), auth: Auth = Depends(login_auth), + dict_type_id: Optional[str] = Query(..., title="查询字典类型", description="查询字典类型"), + dict_label: Optional[str] = Query(None, title="查询字典标签", description="查询字典标签")): + type_obj = await crud.DictTypeDal(auth.db).get_data(dict_type_id=dict_type_id, return_none=True) + if not type_obj: + return ErrorResponse(msg="未找到字典类型!") + datas = await crud.DictDetailsDal(auth.db).\ + get_datas(params.page, params.limit, dict_label=dict_label, dict_type_id=type_obj.id) + count = await crud.DictDetailsDal(auth.db).get_count(dict_label=dict_label, dict_type_id=type_obj.id) + return SuccessResponse(datas, count=count) + + +@app.delete("/dict/details/", summary="批量删除字典元素") +async def delete_dict_details(ids: list = Depends(id_list), auth: Auth = Depends(login_auth)): + await crud.DictDetailsDal(auth.db).delete_datas(ids) + return SuccessResponse("删除成功") + + +@app.put("/dict/details/{data_id}/", summary="更新字典元素") +async def put_dict_details(data_id: int, data: schemas.DictDetails, auth: Auth = Depends(login_auth)): + return SuccessResponse(await crud.DictDetailsDal(auth.db).put_data(data_id, data)) + + +@app.get("/dict/details/{data_id}/", summary="获取字典元素详情") +async def get_dict_detail(data_id: int, auth: Auth = Depends(login_auth)): + schema = schemas.DictDetailsSimpleOut + return SuccessResponse(await crud.DictDetailsDal(auth.db).get_data(data_id, None, schema)) + + +########################################################### +# 默认配置 +########################################################### +@app.get("/config/default/{key}/", summary="获取系统默认配置") +def system_default_config(key: str): + data = { + "sys.user.initPassword": "123456" + } + return SuccessResponse(data=data.get(key, None)) diff --git a/kinit-api/core/__init__.py b/kinit-api/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/core/crud.py b/kinit-api/core/crud.py new file mode 100644 index 0000000..e0af60d --- /dev/null +++ b/kinit-api/core/crud.py @@ -0,0 +1,216 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:18 +# @File : crud.py +# @IDE : PyCharm +# @desc : 数据库 增删改查操作 + +# sqlalchemy 查询操作:https://segmentfault.com/a/1190000016767008 +# SQLAlchemy lazy load和eager load: https://www.jianshu.com/p/dfad7c08c57a +# Mysql中内连接,左连接和右连接的区别总结:https://www.cnblogs.com/restartyang/articles/9080993.html +# SQLAlchemy join 内连接 +# selectinload 官方文档: +# https://www.osgeo.cn/sqlalchemy/orm/loading_relationships.html?highlight=selectinload#sqlalchemy.orm.selectinload + +from typing import List +from fastapi import HTTPException +from fastapi.encoders import jsonable_encoder +from sqlalchemy import func, delete, and_ +from sqlalchemy.future import select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload +from starlette import status +from core.logger import logger +from sqlalchemy.sql.selectable import Select + + +class DalBase: + + def __init__(self, db: AsyncSession, model, schema, key_models: dict = None): + self.db = db + self.model = model + self.schema = schema + self.key_models = key_models + + async def get_data(self, data_id: int = None, options: list = None, schema=None, keys: dict = None, **kwargs): + """ + 获取单个数据,默认使用 ID 查询,否则使用关键词查询 + + :param data_id: + :param keys: 外键字段查询,内连接 + :param options: 指示应使用select在预加载中加载给定的属性。 + :param schema: 指定使用的序列化对象 + :param kwargs: 关键词参数, + :param kwargs: order,排序,默认正序,为 desc 是倒叙 + :param kwargs: return_none,是否返回空 None,否认 抛出异常,默认抛出异常 + """ + order = kwargs.get("order", None) + return_none = kwargs.get("return_none", False) + keys_exist = False + if keys: + for key, value in keys.items(): + if value and isinstance(value, dict): + for k, v in value.items(): + if v: + keys_exist = True + break + kwargs_exist = False + if kwargs: + for key, value in kwargs.items(): + if key != "order" and key != "return_none" and value and getattr(self.model, key, None): + kwargs_exist = True + break + data = None + if data_id or kwargs_exist or keys_exist: + sql = select(self.model).where(self.model.id == data_id) if data_id else select(self.model) + sql = self.add_filter_condition(sql, keys, options, **kwargs) + if order and order == "desc": + sql = sql.order_by(self.model.create_datetime.desc()) + queryset = await self.db.execute(sql) + data = queryset.scalars().first() + if not data and return_none: + return None + if data and schema: + return schema.from_orm(data).dict() + if data: + return data + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未找到此数据") + + async def get_datas(self, page: int = 1, limit: int = 10, keys: dict = None, options: list = None + , schema=None, **kwargs): + """ + 获取数据列表 + + :param page: 页码 + :param limit: 当前页数据量 + :param keys: 外键字段查询 + :param options: 指示应使用select在预加载中加载给定的属性。 + :param schema: 指定使用的序列化对象 + :param kwargs: order,排序,默认正序,为 desc 是倒叙 + :param kwargs: order_field,排序字段 + :param kwargs: return_objs,是否返回对象 + :param kwargs: start_sql,初始 sql + """ + order = kwargs.get("order", None) + order_field = kwargs.get("order_field", None) + return_objs = kwargs.get("return_objs", False) + start_sql = kwargs.get("start_sql", None) + sql = self.add_filter_condition(start_sql if isinstance(start_sql, Select) else select(self.model), keys, options, **kwargs) + if order_field and order == "desc": + sql = sql.order_by(getattr(self.model, order_field).desc(), self.model.id.desc()) + elif order_field: + sql = sql.order_by(getattr(self.model, order_field), self.model.id) + elif order == "desc": + sql = sql.order_by(self.model.id.desc()) + if limit != 0: + sql = sql.offset((page - 1) * limit).limit(limit) + queryset = await self.db.execute(sql) + if return_objs: + return queryset.scalars().all() + if schema: + return [schema.from_orm(i).dict() for i in queryset.scalars().all()] + return [self.out_dict(i) for i in queryset.scalars().all()] + + async def get_count(self, keys: dict = None, **kwargs): + """获取数据总数""" + sql = select(func.count(self.model.id).label('total')) + sql = self.add_filter_condition(sql, keys, **kwargs) + queryset = await self.db.execute(sql) + return queryset.one()['total'] + + async def create_data(self, data, return_obj: bool = False, options: list = None, schema=None): + """创建数据""" + if isinstance(data, dict): + obj = self.model(**data) + else: + obj = self.model(**data.dict()) + self.db.add(obj) + await self.db.flush() + await self.db.refresh(obj) + if options: + obj = await self.get_data(obj.id, options=options) + if return_obj: + return obj + if schema: + return schema.from_orm(obj).dict() + return self.out_dict(obj) + + async def put_data(self, data_id: int, data, return_obj: bool = False, options: list = None, schema=None): + """ + 更新单个数据 + """ + obj = await self.get_data(data_id, options=options) + obj_dict = jsonable_encoder(data) + for key, value in obj_dict.items(): + setattr(obj, key, value) + await self.db.flush() + await self.db.refresh(obj) + if return_obj: + return obj + if schema: + return schema.from_orm(obj).dict() + return self.out_dict(obj) + + async def delete_datas(self, ids: List[int]): + """ + 删除多个数据 + """ + for data_id in ids: + await self.db.execute(delete(self.model).where(self.model.id == data_id)) + + def add_filter_condition(self, sql: select, keys: dict = None, options: list = None, **kwargs) -> select: + """ + 添加过滤条件,以及内连接过滤条件 + :param sql: + :param keys: 外键字段查询,内连接 + :param options: 指示应使用select在预加载中加载给定的属性。 + :param kwargs: 关键词参数 + """ + if keys and self.key_models: + for key, value in keys.items(): + model = self.key_models.get(key) + if model: + sql = sql.join(model) + for v_key, v_value in value.items(): + if v_value is not None and v_value != "": + v_attr = getattr(model, v_key, None) + if not v_attr: + continue + if isinstance(v_value, tuple): + if v_value[0] == "date": + # 根据日期查询, 关键函数是:func.time_format和func.date_format + sql = sql.where(func.date_format(v_attr, "%Y-%m-%d") == v_value[1]) + elif v_value[0] == "like": + sql = sql.where(v_attr.like(f"%{v_value[1]}%")) + else: + sql = sql.where(v_attr == v_value) + else: + logger.error(f"外键查询报错:{key}模型不存在,无法进行下一步查询。") + elif keys and not self.key_models: + logger.error(f"外键查询报错:key_models 外键模型无配置项,无法进行下一步查询。") + for field in kwargs: + value = kwargs.get(field) + if value is not None and value != "": + attr = getattr(self.model, field, None) + if not attr: + continue + if isinstance(value, tuple): + if value[0] == "date": + # 根据日期查询, 关键函数是:func.time_format和func.date_format + sql = sql.where(func.date_format(attr, "%Y-%m-%d") == value[1]) + elif value[0] == "like": + sql = sql.where(attr.like(f"%{value[1]}%")) + else: + sql = sql.where(attr == value) + if options: + sql = sql.options(*[selectinload(i) for i in options]) + return sql + + def out_dict(self, data): + """ + 序列化 + :param data: + :return: + """ + return self.schema.from_orm(data).dict() diff --git a/kinit-api/core/database.py b/kinit-api/core/database.py new file mode 100644 index 0000000..e9da9bc --- /dev/null +++ b/kinit-api/core/database.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:19 +# @File : database.py +# @IDE : PyCharm +# @desc : SQLAlchemy 部分 + +""" +导入 SQLAlchemy 部分 +安装: pip install sqlalchemy +中文文档:https://www.osgeo.cn/sqlalchemy/ +""" +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.ext.declarative import declared_attr, declarative_base +from sqlalchemy.orm import sessionmaker +from application.settings import SQLALCHEMY_DATABASE_URL, DEBUG, SQLALCHEMY_DATABASE_TYPE + + +def create_async_engine_session(database_url: str, database_type: str = "mysql"): + """ + 创建数据库会话 + + 相关配置文档:https://docs.sqlalchemy.org/en/14/core/engines.html#database-urls + + database_url dialect+driver://username:password@host:port/database + max_overflow 超过连接池大小外最多创建的连接 + pool_size=5, # 连接池大小 + pool_timeout=20, # 池中没有连接最多等待的时间,否则报错 + pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置) + + :param database_type: 数据库类型 + :param database_url: 数据库地址 + :return: + """ + engine = create_async_engine( + database_url + , echo=False + , pool_pre_ping=True + , pool_recycle=3600 + , future=True + , max_overflow=5 + , connect_args={"check_same_thread": False, "timeout": 30} if database_type == "sqlite3" else {} + ) + return sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=True, class_=AsyncSession) + + +class Base: + """将表名改为小写""" + + @declared_attr + def __tablename__(self): + # 如果有自定义表名就取自定义,没有就取小写类名 + table_name = self.__tablename__ + if not table_name: + model_name = self.__name__ + ls = [] + for index, char in enumerate(model_name): + if char.isupper() and index != 0: + ls.append("_") + ls.append(char) + table_name = "".join(ls).lower() + return table_name + + +""" +创建基本映射类 +稍后,我们将继承该类,创建每个 ORM 模型 +""" +Model = declarative_base(name='Model', cls=Base) + +""" 附上两个SQLAlchemy教程 + +Python3+SQLAlchemy+Sqlite3实现ORM教程 + https://www.cnblogs.com/jiangxiaobo/p/12350561.html + +SQLAlchemy基础知识 Autoflush和Autocommit + https://www.jianshu.com/p/b219c3dd4d1e +""" + + +async def db_getter() -> AsyncSession: + """ + 获取主数据库 + + 数据库依赖项,它将在单个请求中使用,然后在请求完成后将其关闭。 + """ + async with create_async_engine_session(SQLALCHEMY_DATABASE_URL, SQLALCHEMY_DATABASE_TYPE)() as session: + async with session.begin(): + yield session diff --git a/kinit-api/core/dependencies.py b/kinit-api/core/dependencies.py new file mode 100644 index 0000000..bbbf2c3 --- /dev/null +++ b/kinit-api/core/dependencies.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/8/8 14:18 +# @File : dependencies.py +# @IDE : PyCharm +# @desc : 常用依赖项 + +from typing import List +from fastapi import Body +from pydantic import BaseModel + + +class Params(BaseModel): + page: int + limit: int + + +async def paging(page: int = 1, limit: int = 10) -> Params: + """ + 分页依赖项 + """ + return Params(page=page, limit=limit) + + +async def id_list(ids: List[int] = Body(None, title="ID 列表")) -> list: + """ + id 列表 + """ + return ids diff --git a/kinit-api/core/exception.py b/kinit-api/core/exception.py new file mode 100644 index 0000000..3240045 --- /dev/null +++ b/kinit-api/core/exception.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/19 15:47 +# @File : exception.py +# @IDE : PyCharm +# @desc : 全局异常处理 + +from fastapi.responses import JSONResponse +from starlette.exceptions import HTTPException as StarletteHTTPException +from fastapi.exceptions import RequestValidationError +from starlette import status +from fastapi import Request +from fastapi.encoders import jsonable_encoder +from fastapi import FastAPI +from core.logger import logger + + +class CustomException(Exception): + def __init__(self, msg: str, code: int): + self.msg = msg + self.code = code + + +def register_exception(app: FastAPI): + """ + 异常捕捉 + """ + + @app.exception_handler(CustomException) + async def custom_exception_handler(request: Request, exc: CustomException): + """ + 自定义异常 + """ + return JSONResponse( + status_code=200, + content={"message": exc.msg, "code": exc.code}, + ) + + @app.exception_handler(StarletteHTTPException) + async def unicorn_exception_handler(request: Request, exc: StarletteHTTPException): + """ + 重写HTTPException异常处理器 + """ + print("捕捉到重写HTTPException异常异常:unicorn_exception_handler") + logger.error(exc.detail) + print(exc.detail) + return JSONResponse( + status_code=200, + content={ + "code": status.HTTP_400_BAD_REQUEST, + "message": exc.detail, + } + ) + + @app.exception_handler(RequestValidationError) + async def validation_exception_handler(request: Request, exc: RequestValidationError): + """ + 重写请求验证异常处理器 + """ + print("捕捉到重写请求验证异常异常:validation_exception_handler") + logger.error(exc.errors()) + print(exc.errors()) + return JSONResponse( + status_code=200, + content=jsonable_encoder( + { + "message": exc.errors()[0].get("msg") + , "body": exc.body + , "code": status.HTTP_400_BAD_REQUEST + } + ), + ) + + @app.exception_handler(ValueError) + async def value_exception_handler(request: Request, exc: ValueError): + """ + 捕获值异常 + """ + print("捕捉到值异常:value_exception_handler") + logger.error(exc.__str__()) + print(exc.__str__()) + return JSONResponse( + status_code=200, + content=jsonable_encoder( + { + "message": exc.__str__() + , "code": status.HTTP_400_BAD_REQUEST + } + ), + ) + + @app.exception_handler(Exception) + async def all_exception_handler(request: Request, exc: Exception): + """ + 捕获全部异常 + """ + print("捕捉到全局异常:all_exception_handler") + logger.error(exc.__str__()) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=jsonable_encoder( + { + "message": "接口异常!" + , "code": status.HTTP_500_INTERNAL_SERVER_ERROR + } + ), + ) diff --git a/kinit-api/core/logger.py b/kinit-api/core/logger.py new file mode 100644 index 0000000..bf0645c --- /dev/null +++ b/kinit-api/core/logger.py @@ -0,0 +1,28 @@ +import os +import time +from loguru import logger +from application.settings import BASE_DIR + +""" +# 日志简单配置 +# 具体其他配置 可自行参考 https://github.com/Delgan/loguru +""" + +# 移除控制台输出 +logger.remove(handler_id=None) + +log_path = os.path.join(BASE_DIR, 'logs') +if not os.path.exists(log_path): + os.mkdir(log_path) + +log_path_info = os.path.join(log_path, f'info_{time.strftime("%Y-%m-%d")}.log') +log_path_error = os.path.join(log_path, f'error_{time.strftime("%Y-%m-%d")}.log') + +info = logger.add(log_path_info, rotation="00:00", retention="3 days", enqueue=True, encoding="UTF-8", level="INFO") +error = logger.add(log_path_error, rotation="00:00", retention="3 days", enqueue=True, encoding="UTF-8", level="ERROR") + + +if __name__ == '__main__': + print(BASE_DIR) + logger.info("hell") + logger.error("hell") diff --git a/kinit-api/core/middleware.py b/kinit-api/core/middleware.py new file mode 100644 index 0000000..c621bc0 --- /dev/null +++ b/kinit-api/core/middleware.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/19 15:47 +# @File : middleware.py +# @IDE : PyCharm +# @desc : 中间件 + +""" +官方文档——中间件:https://fastapi.tiangolo.com/tutorial/middleware/ +官方文档——高级中间件:https://fastapi.tiangolo.com/advanced/middleware/ +""" + + +import time +from fastapi import Request, Response +from core.logger import logger +from fastapi import FastAPI + + +# 记录请求日志 +def write_request_log(request: Request, response: Response): + http_version = f"http/{request.scope['http_version']}" + content_length = response.raw_headers[0][1] + process_time = response.headers["X-Process-Time"] + content = f"basehttp.log_message: '{request.method} {request.url} {http_version}' {response.status_code} {response.charset} {content_length} {process_time}" + logger.info(content) + + +def register_middleware(app: FastAPI): + """ + 记录请求日志中间件 + :param app: + :return: + """ + + @app.middleware("http") + async def request_log_middleware(request: Request, call_next): + start_time = time.time() + response = await call_next(request) + process_time = time.time() - start_time + response.headers["X-Process-Time"] = str(process_time) + write_request_log(request, response) + return response diff --git a/kinit-api/core/validator.py b/kinit-api/core/validator.py new file mode 100644 index 0000000..9ee1471 --- /dev/null +++ b/kinit-api/core/validator.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:19 +# @File : validator.py +# @IDE : PyCharm +# @desc : pydantic 模型重用验证器 + +""" +官方文档:https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators +""" + +import re + + +def vali_telephone(value: str) -> str: + """ + 手机号验证器 + :param value: 手机号 + :return: 手机号 + """ + if not value or len(value) != 11 or not value.isdigit(): + raise ValueError("请输入正确手机号") + + REGEX_TELEPHONE = r'^1(3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8[0-9]|9[0-9])\d{8}$' + + if not re.match(REGEX_TELEPHONE, value): + raise ValueError("请输入正确手机号") + + return value + + +class ValiDatetime(str): + + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, v): + return v.strftime("%Y-%m-%d %H:%M:%S") + diff --git a/kinit-api/db/__init__.py b/kinit-api/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/db/db_base.py b/kinit-api/db/db_base.py new file mode 100644 index 0000000..c7f060a --- /dev/null +++ b/kinit-api/db/db_base.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/18 22:19 +# @File : db_base.py +# @IDE : PyCharm +# @desc : 数据库公共 ORM 模型 + + +from core.database import Model +from sqlalchemy import Column, DateTime, Integer, func + + +# 使用命令:alembic init alembic 初始化迁移数据库环境 +# 这时会生成alembic文件夹 和 alembic.ini文件 +class BaseModel(Model): + """ + 公共 ORM 模型,基表 + """ + __abstract__ = True + + id = Column(Integer, primary_key=True, unique=True, comment='主键ID', index=True) + create_datetime = Column(DateTime, server_default=func.now(), comment='创建时间') + update_datetime = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间') \ No newline at end of file diff --git a/kinit-api/logs/.gitkeep b/kinit-api/logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/main.py b/kinit-api/main.py new file mode 100644 index 0000000..bd22bf4 --- /dev/null +++ b/kinit-api/main.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/10/19 15:47 +# @File : main.py +# @IDE : PyCharm +# @desc : 主程序入口 + +from fastapi import FastAPI +import uvicorn +from starlette.middleware.cors import CORSMiddleware +from application import settings +from application import urls +from starlette.staticfiles import StaticFiles # 依赖安装:pip install aiofiles +import importlib +from core.logger import logger +from core.exception import register_exception + +""" +其他配置: +docs_url:配置交互文档的路由地址,如果禁用则为None,默认为 /docs +redoc_url: 配置 Redoc 文档的路由地址,如果禁用则为None,默认为 /redoc +openapi_url:配置接口文件json数据文件路由地址,如果禁用则为None,默认为/openapi.json +""" +app = FastAPI( + title="Kinit", + description="初始项目,故事来源于,有一次我去面试,当时面试官给的一道题是让我使用Django+Vue写出一个客户信息列表的CRUD," + "里面给出的信息还是蛮复杂的,当时写了接近一下午,最后还没过,哈哈哈哈。写现在的这个初始项目也是为了真的再次遇到这种情况," + "我就可以很好的很快速的完成了。也能当领导安排新的项目,能够及时启动项目,不用再搭建脚手架了。", + version="1.0.0", +) + +""" +添加中间件 +""" +for middle in settings.MIDDLEWARES: + try: + # 动态导入模块 + middle_pag = importlib.import_module(middle[0:middle.rindex(".")]) + getattr(middle_pag, middle[middle.rindex(".")+1:])(app) + except ModuleNotFoundError: + logger.error(f"AttributeError:导入中间件失败,未找到该模块:{middle}") + except AttributeError: + logger.error(f"ModuleNotFoundError:导入中间件失败,未找到该模块下的方法:{middle}") + +""" +全局异常捕捉处理 +""" +register_exception(app) + +""" +跨域解决 +""" +if settings.CORS_ORIGIN_ENABLE: + app.add_middleware( + CORSMiddleware, + allow_origins=settings.ALLOW_ORIGINS, + allow_credentials=settings.ALLOW_CREDENTIALS, + allow_methods=settings.ALLOW_METHODS, + allow_headers=settings.ALLOW_HEADERS) + +""" +挂在静态目录 +""" +if settings.STATIC_ENABLE: + app.mount(settings.STATIC_URL, app=StaticFiles(directory=settings.STATIC_ROOT)) + +""" +引入应用中的路由 +""" +for url in urls.urlpatterns: + app.include_router(url["ApiRouter"], prefix=url["prefix"], tags=url["tags"]) + +if __name__ == '__main__': + """ + # 启动项目 + # reload:自动重载项目 + # debug:调试 + # workers:启动几个进程 + """ + uvicorn.run(app='main:app', host="0.0.0.0", port=9000, reload=True, debug=True, workers=1) \ No newline at end of file diff --git a/kinit-api/requirements.txt b/kinit-api/requirements.txt new file mode 100644 index 0000000..bc56e8d --- /dev/null +++ b/kinit-api/requirements.txt @@ -0,0 +1,47 @@ +alembic==1.7.5 +anyio==3.5.0 +asgiref==3.5.0 +asyncmy==0.2.5 +certifi==2022.6.15 +cffi==1.15.1 +charset-normalizer==2.0.12 +click==8.0.3 +colorama==0.4.4 +cryptography==37.0.4 +ecdsa==0.18.0 +fastapi==0.73.0 +greenlet==1.1.2 +gunicorn==20.1.0 +h11==0.13.0 +httptools==0.3.0 +idna==3.3 +importlib-metadata==4.10.1 +importlib-resources==5.4.0 +loguru==0.5.3 +Mako==1.1.6 +MarkupSafe==2.0.1 +orjson==3.6.6 +passlib==1.7.4 +pyasn1==0.4.8 +pycparser==2.21 +pydantic==1.9.0 +PyMySQL==1.0.2 +python-dotenv==0.19.2 +python-jose==3.3.0 +PyYAML==6.0 +requests==2.28.0 +rsa==4.9 +six==1.16.0 +sniffio==1.2.0 +SQLAlchemy==1.4.31 +SQLAlchemy-Utils==0.38.3 +starlette==0.17.1 +typing-extensions==4.0.1 +ua-parser==0.15.0 +urllib3==1.26.9 +user-agents==2.2.0 +uvicorn==0.17.0.post1 +watchgod==0.7 +websockets==10.1 +win32-setctime==1.1.0 +zipp==3.7.0 diff --git a/kinit-api/static/.gitkeep b/kinit-api/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/temp/.gitkeep b/kinit-api/temp/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/utils/__init__.py b/kinit-api/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kinit-api/utils/crawle.py b/kinit-api/utils/crawle.py new file mode 100644 index 0000000..99dda1b --- /dev/null +++ b/kinit-api/utils/crawle.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/2/24 17:02 +# @File : crawle.py +# @IDE : PyCharm +# @desc : 爬虫 + +import requests +from core.logger import logger + + +def get_schedule_data(): + """ + 获取足球赛程表 + """ + logger.info("开始获取足球赛程") + url = "https://webapi.sporttery.cn/gateway/jc/football/getMatchCalculatorV1.qry?poolCode=hhad,had&channel=c" + res = requests.get(url) + if res.status_code != 200: + logger.error("获取足球赛程失败!") + return False + data = res.json() + if data.get("errorCode") != "0": + logger.error("获取足球赛程失败!") + return False + return data.get("value").get("matchInfoList") diff --git a/kinit-api/utils/response.py b/kinit-api/utils/response.py new file mode 100644 index 0000000..4d5af95 --- /dev/null +++ b/kinit-api/utils/response.py @@ -0,0 +1,36 @@ +# 依赖安装:pip install orjson +from fastapi.responses import ORJSONResponse as Response +from fastapi import status as http_status +from utils import status as http + + +class SuccessResponse(Response): + """ + 成功响应 + """ + def __init__(self, data=None, msg="success", code=http.HTTP_SUCCESS, status=http_status.HTTP_200_OK + , **kwargs): + self.data = { + "code": code, + "message": msg, + "result": data, + "type": "success", + } + self.data.update(kwargs) + super().__init__(content=self.data, status_code=status) + + +class ErrorResponse(Response): + """ + 失败响应 + """ + def __init__(self, msg=None, code=http.HTTP_ERROR, status=http_status.HTTP_400_BAD_REQUEST + , **kwargs): + self.data = { + "code": code, + "message": msg, + "result": [], + "type": "error", + } + self.data.update(kwargs) + super().__init__(content=self.data, status_code=status) diff --git a/kinit-api/utils/save_file.py b/kinit-api/utils/save_file.py new file mode 100644 index 0000000..e6aea2b --- /dev/null +++ b/kinit-api/utils/save_file.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2021/12/5 8:45 +# @File : save_file.py +# @IDE : PyCharm +# @desc : 保存图片到本地 + +from datetime import datetime +import os +from application.settings import TEMP_DIR + + +def save_tmp_file(file, data): + """ + 保存临时文件 + """ + date = datetime.strftime(datetime.now(), "%Y%m%d") + file_dir = os.path.join(TEMP_DIR, date) + if not os.path.exists(file_dir): + os.mkdir(file_dir) + with open(os.path.join(file_dir, file.filename), "wb") as f: + f.write(data) \ No newline at end of file diff --git a/kinit-api/utils/status.py b/kinit-api/utils/status.py new file mode 100644 index 0000000..68ec4d6 --- /dev/null +++ b/kinit-api/utils/status.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# @version : 1.0 +# @Creaet Time : 2022/8/10 22:20 +# @File : status.py +# @IDE : PyCharm +# @desc : 简要说明 + + +HTTP_SUCCESS = 0 +HTTP_ERROR = 1