├── .dockerignore ├── .env ├── .env.development ├── .env.production ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .vscode ├── admin.code-snippets ├── extensions.json └── settings.json ├── Dockerfile ├── LICENSE ├── README-cn.md ├── README.md ├── build ├── utils.ts └── vite │ ├── plugin │ ├── compress.ts │ ├── index.ts │ └── uno.ts │ └── server.ts ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── api │ ├── basic.ts │ ├── config │ │ └── dict.ts │ ├── log │ │ └── login.ts │ └── system │ │ ├── dept.ts │ │ ├── job.ts │ │ ├── menu.ts │ │ ├── profession.ts │ │ ├── role.ts │ │ └── user.ts ├── assets │ ├── icons │ │ ├── config.svg │ │ ├── dashboard.svg │ │ ├── documentation.svg │ │ ├── log.svg │ │ └── system.svg │ ├── images │ │ └── logo.png │ └── svg │ │ ├── login-bg.svg │ │ ├── net-error.svg │ │ ├── no-data.svg │ │ ├── page-not-found.svg │ │ ├── server-error.svg │ │ ├── user-default-avatar.svg │ │ └── without-permission.svg ├── components │ ├── Application │ │ ├── index.ts │ │ └── src │ │ │ ├── AppDarkModeToggle.vue │ │ │ ├── AppLocalePicker.vue │ │ │ ├── AppLogo.vue │ │ │ ├── AppProvider.vue │ │ │ └── useAppContext.ts │ ├── Button │ │ ├── index.ts │ │ └── src │ │ │ ├── PopConfirmButton.vue │ │ │ └── typing.ts │ ├── ContextMenu │ │ ├── index.ts │ │ └── src │ │ │ ├── ContextMenu.vue │ │ │ ├── createContextMenu.ts │ │ │ └── typing.ts │ ├── CountTo │ │ ├── index.ts │ │ └── src │ │ │ └── CountTo.vue │ ├── Dialog │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicDialog.vue │ │ │ ├── components │ │ │ ├── DialogClose.vue │ │ │ └── DialogFooter.vue │ │ │ ├── composables │ │ │ └── useDialog.ts │ │ │ ├── props.ts │ │ │ └── typing.ts │ ├── Drawer │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicDrawer.vue │ │ │ ├── components │ │ │ └── DrawerFooter.vue │ │ │ ├── composables │ │ │ └── useDrawer.ts │ │ │ ├── props.ts │ │ │ └── typing.ts │ ├── Form │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicForm.vue │ │ │ ├── components │ │ │ ├── FormAction.vue │ │ │ └── FormItem.tsx │ │ │ ├── composables │ │ │ ├── useForm.ts │ │ │ ├── useFormContext.ts │ │ │ ├── useFormEvents.ts │ │ │ └── useFormValues.ts │ │ │ ├── functional │ │ │ ├── createFormDialog.tsx │ │ │ └── createFormDrawer.tsx │ │ │ ├── props.ts │ │ │ └── typing.ts │ ├── Heading │ │ ├── index.ts │ │ └── src │ │ │ └── Heading.vue │ ├── Icon │ │ ├── data │ │ │ └── icon.data.ts │ │ ├── index.ts │ │ └── src │ │ │ └── IconPicker.vue │ ├── Page │ │ ├── index.ts │ │ └── src │ │ │ ├── PageHeader.vue │ │ │ └── PageWrapper.vue │ ├── Table │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicTable.vue │ │ │ ├── components │ │ │ ├── TableAction.vue │ │ │ ├── TableColumn.tsx │ │ │ ├── TableFooter.vue │ │ │ ├── TableHeader.vue │ │ │ └── settings │ │ │ │ ├── FullscreenSetting.vue │ │ │ │ ├── RedoSetting.vue │ │ │ │ └── SizeSetting.vue │ │ │ ├── composables │ │ │ ├── useColumns.ts │ │ │ ├── useCurrentRow.ts │ │ │ ├── useDataSource.ts │ │ │ ├── useLoading.ts │ │ │ ├── usePagination.ts │ │ │ ├── useRowSelection.ts │ │ │ ├── useTable.ts │ │ │ ├── useTableContext.ts │ │ │ ├── useTableEvents.ts │ │ │ ├── useTableExpand.ts │ │ │ └── useTableHeight.ts │ │ │ ├── const.ts │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── action.ts │ │ │ ├── column.ts │ │ │ ├── pagination.ts │ │ │ └── table.ts │ └── registerGlobalComp.ts ├── composables │ ├── core │ │ ├── onMountedOrActivated.ts │ │ ├── useAppInject.ts │ │ ├── useContext.ts │ │ ├── useDesign.ts │ │ ├── usePermission.ts │ │ └── useTransl.ts │ ├── event │ │ ├── useBreakpoint.ts │ │ ├── useEventListener.ts │ │ └── useWindowSizeFn.ts │ ├── setting │ │ ├── useHeaderSetting.ts │ │ ├── useMenuSetting.ts │ │ ├── useRootSetting.ts │ │ └── useTransitionSetting.ts │ └── web │ │ ├── useContentHeight.ts │ │ ├── useECharts.ts │ │ ├── useFullContent.ts │ │ ├── useGo.ts │ │ ├── useMessage.ts │ │ ├── useRedo.ts │ │ ├── useScript.ts │ │ └── useTitle.ts ├── directives │ ├── permission.ts │ └── setupGlobalDirectives.ts ├── enums │ ├── appEnum.ts │ ├── breakpointEnum.ts │ ├── cacheEnum.ts │ ├── httpEnum.ts │ ├── menuEnum.ts │ ├── pageEnum.ts │ ├── roleEnum.ts │ └── typeEnum.ts ├── layouts │ ├── default │ │ ├── content │ │ │ ├── getTransitionName.ts │ │ │ ├── index.vue │ │ │ └── useLayoutHeight.ts │ │ ├── feature │ │ │ └── index.vue │ │ ├── footer │ │ │ └── index.vue │ │ ├── header │ │ │ ├── components │ │ │ │ ├── FullScreen.vue │ │ │ │ ├── Redo.vue │ │ │ │ ├── UserDropdown.vue │ │ │ │ └── index.ts │ │ │ └── index.vue │ │ ├── index.vue │ │ ├── menu │ │ │ ├── components │ │ │ │ ├── MenuItem.vue │ │ │ │ └── Trigger.vue │ │ │ └── index.vue │ │ ├── setting │ │ │ ├── components │ │ │ │ ├── MenuModePicker.vue │ │ │ │ ├── SelectItem.vue │ │ │ │ ├── SettingFooter.vue │ │ │ │ ├── SwitchItem.vue │ │ │ │ └── ThemeColorPicker.vue │ │ │ └── index.vue │ │ └── sidebar │ │ │ ├── LayoutSidebar.vue │ │ │ └── index.vue │ ├── empty │ │ └── index.vue │ └── iframe │ │ └── index.vue ├── locales │ ├── helper.ts │ ├── lang │ │ ├── en.ts │ │ ├── en │ │ │ ├── common.ts │ │ │ ├── component.ts │ │ │ ├── layout.ts │ │ │ └── routes.ts │ │ ├── zh_CN.ts │ │ └── zh_CN │ │ │ ├── common.ts │ │ │ ├── component.ts │ │ │ ├── layout.ts │ │ │ └── routes.ts │ ├── setupI18n.ts │ ├── typing.ts │ └── useLocale.ts ├── logics │ ├── initAppConfig.ts │ └── theme │ │ ├── updateBackground.ts │ │ ├── updateColorWeak.ts │ │ ├── updateDarkMode.ts │ │ ├── updateGrayMode.ts │ │ └── updateTheme.ts ├── main.ts ├── router │ ├── contants.ts │ ├── guard │ │ ├── httpGuard.ts │ │ ├── index.ts │ │ ├── messageGuard.ts │ │ ├── permissionGuard.ts │ │ ├── progressGuard.ts │ │ └── stateGuard.ts │ ├── helper │ │ ├── defineModule.ts │ │ └── routeHelper.ts │ ├── index.ts │ ├── routes │ │ ├── basic.ts │ │ ├── index.ts │ │ └── modules │ │ │ └── system.ts │ └── typing.ts ├── settings │ ├── componentSetting.ts │ ├── designSetting.ts │ ├── encryptionSetting.ts │ ├── localeSetting.ts │ ├── projectSetting.ts │ └── siteSetting.ts ├── stores │ ├── index.ts │ └── modules │ │ ├── app.ts │ │ ├── locale.ts │ │ ├── permission.ts │ │ └── user.ts ├── styles │ ├── element-ui.scss │ ├── index.scss │ ├── mixins.scss │ ├── public.scss │ ├── transition.scss │ └── var.scss ├── utils │ ├── auth.ts │ ├── cache │ │ ├── index.ts │ │ ├── memory.ts │ │ └── storage.ts │ ├── cipher.ts │ ├── color.ts │ ├── date.ts │ ├── dom.ts │ ├── env.ts │ ├── helper │ │ ├── tree.ts │ │ └── tsx.ts │ ├── http │ │ └── axios │ │ │ ├── axiosCanceler.ts │ │ │ ├── axiosRetry.ts │ │ │ ├── axiosTransform.ts │ │ │ ├── helper.ts │ │ │ ├── index.ts │ │ │ └── sAxios.ts │ ├── index.ts │ ├── is.ts │ ├── lib │ │ └── echarts.ts │ ├── log.ts │ └── uuid.ts └── views │ ├── basic │ ├── account │ │ ├── Account.vue │ │ ├── BasicProfile.vue │ │ └── SecureSetting.vue │ ├── dashboard │ │ └── Dashboard.vue │ ├── exception │ │ └── Exception.vue │ ├── login │ │ ├── AccountLoginForm.vue │ │ └── Login.vue │ └── redirect │ │ └── Redirect.vue │ ├── config │ └── dict │ │ ├── DictValueType.ts │ │ ├── columns.tsx │ │ ├── index.vue │ │ └── schemas.tsx │ ├── log │ └── login │ │ ├── columns.tsx │ │ └── index.vue │ └── system │ ├── dept │ ├── columns.tsx │ ├── index.vue │ └── schemas.tsx │ ├── job │ ├── columns.tsx │ ├── index.vue │ └── schemas.tsx │ ├── menu │ ├── columns.ts │ ├── index.vue │ └── schemas.tsx │ ├── profession │ ├── columns.tsx │ ├── index.vue │ └── schemas.tsx │ ├── role │ ├── AssignPermTree.vue │ ├── columns.tsx │ ├── index.vue │ └── schemas.tsx │ └── user │ ├── columns.tsx │ ├── index.vue │ └── schemas.tsx ├── tsconfig.json ├── types ├── axios.d.ts ├── components.d.ts ├── config.d.ts ├── global.d.ts ├── index.d.ts ├── module.d.ts └── vue-router.d.ts └── vite.config.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | .npmrc 6 | .cache 7 | .local 8 | 9 | # testing css 10 | *.test.scss 11 | *.test.less 12 | *.test.css 13 | 14 | # local env files 15 | .env.local 16 | .env.*.local 17 | .eslintcache 18 | 19 | # Log files 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | pnpm-debug.log* 24 | 25 | # Editor directories and files 26 | .idea 27 | # .vscode 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # dev port 2 | VITE_PORT = 9528 -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # proxy target 2 | VITE_PROXY_API_TARGET = 'http://127.0.0.1:7001' 3 | 4 | # base api 5 | VITE_APP_BASE_API = '/api/admin' -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # base api 2 | VITE_APP_BASE_API = '/api/admin' -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | *.md 4 | *.woff 5 | *.ttf 6 | .vscode 7 | .idea 8 | dist 9 | /public 10 | /docs 11 | .husky 12 | .local 13 | /bin 14 | Dockerfile -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Image 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | docker: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repo 12 | uses: actions/checkout@v2 13 | - name: Set output 14 | id: vars 15 | run: echo ::set-output name=short_ref::${GITHUB_REF#refs/*/} 16 | - name: Set up QEMU 17 | uses: docker/setup-qemu-action@v1 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v1 20 | - name: Login to DockerHub 21 | uses: docker/login-action@v1 22 | with: 23 | username: arklnk 24 | password: ${{ secrets.DOCKERHUB_TOKEN }} 25 | - name: Build and push 26 | id: docker_build 27 | uses: docker/build-push-action@v2 28 | with: 29 | push: true 30 | tags: arklnk/ark-admin:web 31 | - name: Image digest 32 | run: echo ${{ steps.docker_build.outputs.digest }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | .npmrc 6 | .cache 7 | .local 8 | 9 | # testing css 10 | *.test.scss 11 | *.test.less 12 | *.test.css 13 | 14 | # local env files 15 | .env.local 16 | .env.*.local 17 | .eslintcache 18 | 19 | # Log files 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | pnpm-debug.log* 24 | 25 | # Editor directories and files 26 | .idea 27 | # .vscode 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | -------------------------------------------------------------------------------- /.vscode/admin.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "defns": { 3 | "scope": "scss", 4 | "prefix": "defns", 5 | "body": [ 6 | "@use '/@/styles/var.scss';", 7 | "", 8 | "\\$prefixCls: #{var.\\$namespace}-$1;", 9 | "", 10 | ".#{\\$prefixCls} {", 11 | "", 12 | "}" 13 | ], 14 | "description": "namespace scss define" 15 | }, 16 | "defapi": { 17 | "scope": "typescript", 18 | "prefix": "defapi", 19 | "body": [ 20 | "import { defHttp } from '/@/utils/http/axios'", 21 | "", 22 | "export const Api = {", 23 | " list: '$1',", 24 | " add: '',", 25 | " update: '',", 26 | " delete: '',", 27 | "}" 28 | ], 29 | "description": "permission api define" 30 | }, 31 | "defreq": { 32 | "scope": "typescript", 33 | "prefix": "defreq", 34 | "body": [ 35 | "export async function $1Request(): Promise {", 36 | " return await defHttp.$2({ url: Api.$3 })", 37 | "}" 38 | ], 39 | "description": "define api request" 40 | }, 41 | "defcol": { 42 | "scope": "typescript", 43 | "prefix": "defcol", 44 | "body": [ 45 | "import type { TableColumn } from '/@/components/Table'", 46 | "", 47 | "export function createColumns(): TableColumn[] {", 48 | " return [$1]", 49 | "}" 50 | ], 51 | "description": "define table columns" 52 | }, 53 | "defscm": { 54 | "scope": "typescript", 55 | "prefix": "defscm", 56 | "body": [ 57 | "import type { FormSchema } from '/@/components/Form'", 58 | "", 59 | "export function createSchemas(): FormSchema[] {", 60 | " return [$1]", 61 | "}" 62 | ], 63 | "description": "define form schemas" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "Vue.vscode-typescript-vue-plugin", 5 | "stylelint.vscode-stylelint", 6 | "dbaeumer.vscode-eslint", 7 | "esbenp.prettier-vscode", 8 | "mrmlnc.vscode-scss", 9 | "lokalise.i18n-ally", 10 | "antfu.iconify", 11 | "voorjaar.windicss-intellisense", 12 | "mikestead.dotenv" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "npm.packageManager": "pnpm", 4 | "files.eol": "\n", 5 | "editor.tabSize": 2, 6 | "editor.defaultFormatter": "esbenp.prettier-vscode", 7 | "search.exclude": { 8 | "**/node_modules": true, 9 | "**/*.log": true, 10 | "**/*.log*": true, 11 | "**/bower_components": true, 12 | "**/dist": true, 13 | "**/elehukouben": true, 14 | "**/.git": true, 15 | "**/.gitignore": true, 16 | "**/.svn": true, 17 | "**/.DS_Store": true, 18 | "**/.idea": true, 19 | "**/.vscode": false, 20 | "**/yarn.lock": true, 21 | "**/tmp": true, 22 | "out": true, 23 | "dist": true, 24 | "node_modules": true, 25 | "CHANGELOG.md": true, 26 | "examples": true, 27 | "res": true, 28 | "screenshots": true, 29 | "yarn-error.log": true, 30 | "**/.yarn": true 31 | }, 32 | "[javascriptreact]": { 33 | "editor.defaultFormatter": "esbenp.prettier-vscode" 34 | }, 35 | "[typescript]": { 36 | "editor.defaultFormatter": "esbenp.prettier-vscode" 37 | }, 38 | "[typescriptreact]": { 39 | "editor.defaultFormatter": "esbenp.prettier-vscode" 40 | }, 41 | "[html]": { 42 | "editor.defaultFormatter": "esbenp.prettier-vscode" 43 | }, 44 | "[css]": { 45 | "editor.defaultFormatter": "esbenp.prettier-vscode" 46 | }, 47 | "[less]": { 48 | "editor.defaultFormatter": "esbenp.prettier-vscode" 49 | }, 50 | "[scss]": { 51 | "editor.defaultFormatter": "esbenp.prettier-vscode" 52 | }, 53 | "[markdown]": { 54 | "editor.defaultFormatter": "esbenp.prettier-vscode" 55 | }, 56 | "editor.codeActionsOnSave": { 57 | "source.fixAll.eslint": true 58 | }, 59 | "[vue]": { 60 | "editor.codeActionsOnSave": { 61 | "source.fixAll.eslint": true, 62 | "source.fixAll.stylelint": true 63 | } 64 | }, 65 | "i18n-ally.enabledFrameworks": ["vue"], 66 | "i18n-ally.sourceLanguage": "zh_CN", 67 | "i18n-ally.displayLanguage": "zh_CN", 68 | "i18n-ally.localesPaths": ["src/locales/lang"], 69 | "i18n-ally.keystyle": "nested", 70 | "i18n-ally.namespace": true, 71 | "i18n-ally.enabledParsers": ["ts"], 72 | "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}", 73 | "i18n-ally.sortKeys": true 74 | } 75 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine as builder 2 | WORKDIR /build 3 | 4 | # 安装pnpm 5 | RUN npm install -g pnpm 6 | 7 | # 安装开发期依赖 8 | COPY package.json pnpm-lock.yaml ./ 9 | RUN pnpm bootstrap 10 | 11 | # 构建项目 12 | COPY ./ /build 13 | RUN pnpm build 14 | 15 | FROM nginx:alpine 16 | COPY --from=builder /build/dist/ /web 17 | EXPOSE 80 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present 方舟互联 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README-cn.md: -------------------------------------------------------------------------------- 1 | ![](https://docs.arklnk.com/images/ark-admin.png) 2 | 3 | [English](README.md) | 简体中文 4 | 5 | ## 官方文档 6 | 7 | 文档:[https://docs.arklnk.com](https://docs.arklnk.com) 8 | 9 | go-zero版后端:[https://github.com/arklnk/ark-admin-zero](https://github.com/arklnk/ark-admin-zero) 10 | 11 | nestjs版后端: [https://github.com/arklnk/ark-admin-nest](https://github.com/arklnk/ark-admin-nest) 12 | 13 | vuenext纯前端模板: [https://github.com/arklnk/ark-admin-vuenext-pure](https://github.com/arklnk/ark-admin-vuenext-pure) 14 | 15 | ## 在线体验 16 | 17 | 在线体验地址: 18 | 19 | [http://arkadmin.si-yee.com (go-zero)](http://arkadmin.si-yee.com) 20 | 21 | [http://nest.arkadmin.si-yee.com (nestjs)](http://nest.arkadmin.si-yee.com) 22 | 23 | | 账号 | 密码 | 备注 | 24 | | ---- | ------ | -------- | 25 | | demo | 123456 | 演示账号 | 26 | 27 | ![login](https://docs.arklnk.com/images/zero/login.png) 28 | 29 | ![](https://docs.arklnk.com/images/zero/menu.png) 30 | 31 | # 欢迎 Star && PR 32 | 33 | 如果项目有帮助到你可以点个 Star 支持下。有更好的实现欢迎 PR。 34 | 35 | ## 浏览器支持 36 | 37 | Modern browsers and Internet Explorer 10+. 38 | 39 | | [![IE / Edge](https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png)](https://godban.github.io/browsers-support-badges/) IE / Edge | [![Firefox](https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png)](https://godban.github.io/browsers-support-badges/) Firefox | [![Chrome](https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png)](https://godban.github.io/browsers-support-badges/) Chrome | [![Safari](https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png)](https://godban.github.io/browsers-support-badges/) Safari | 40 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 41 | | IE10, IE11, Edge | last 2 versions | last 2 versions | last 2 versions | 42 | 43 | ## 致谢 44 | 45 | [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) 46 | 47 | -------------------------------------------------------------------------------- /build/utils.ts: -------------------------------------------------------------------------------- 1 | export function isDevFn(mode: string): boolean { 2 | return mode === 'development' 3 | } 4 | 5 | export function isProdFn(mode: string): boolean { 6 | return mode === 'production' 7 | } 8 | -------------------------------------------------------------------------------- /build/vite/plugin/compress.ts: -------------------------------------------------------------------------------- 1 | import type { PluginOption } from 'vite' 2 | import viteCompression from 'vite-plugin-compression' 3 | 4 | export function configCompressionPlugin( 5 | compress: 'gzip' | 'brotli' | 'none', 6 | deleteOriginFile = false 7 | ): PluginOption | PluginOption[] { 8 | const compressSetting = compress.split(',') 9 | 10 | const plugins: PluginOption[] = [] 11 | 12 | if (compressSetting.includes('gzip')) { 13 | plugins.push( 14 | viteCompression({ 15 | ext: '.gz', 16 | deleteOriginFile, 17 | algorithm: 'gzip', 18 | }) 19 | ) 20 | } 21 | 22 | if (compressSetting.includes('brotli')) { 23 | plugins.push( 24 | viteCompression({ 25 | ext: '.br', 26 | algorithm: 'brotliCompress', 27 | deleteOriginFile, 28 | }) 29 | ) 30 | } 31 | 32 | return plugins 33 | } 34 | -------------------------------------------------------------------------------- /build/vite/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { PluginOption } from 'vite' 2 | 3 | import vue from '@vitejs/plugin-vue' 4 | import vueJsx from '@vitejs/plugin-vue-jsx' 5 | 6 | import { configCompressionPlugin } from './compress' 7 | import { configUnoPlugin } from './uno' 8 | 9 | export function createVitePlugins(_env: ViteEnv, isBuild: boolean) { 10 | const vitePlugins: (PluginOption | PluginOption[])[] = [vue(), vueJsx()] 11 | 12 | // unocss 13 | vitePlugins.push(configUnoPlugin(isBuild)) 14 | 15 | // The following plugins only work in the production environment 16 | if (isBuild) { 17 | // gzip 18 | vitePlugins.push(configCompressionPlugin('gzip', false)) 19 | } 20 | 21 | return vitePlugins 22 | } 23 | -------------------------------------------------------------------------------- /build/vite/plugin/uno.ts: -------------------------------------------------------------------------------- 1 | import type { PluginOption } from 'vite' 2 | 3 | import unocss from 'unocss/vite' 4 | import { presetUno } from 'unocss' 5 | import presetIcons from '@unocss/preset-icons' 6 | import iconData from '/@/components/Icon/data/icon.data' 7 | 8 | export function configUnoPlugin(isBuild: boolean): PluginOption { 9 | const prefix = 'i-' 10 | 11 | // 白名单列表,用于动态设置菜单Icon防止未使用的icon导致不被打包 12 | function createSafeListByIcon(): string[] { 13 | return iconData.map((i) => `${prefix}${i}`) 14 | } 15 | 16 | return unocss({ 17 | envMode: isBuild ? 'build' : 'dev', 18 | safelist: [...createSafeListByIcon()], 19 | presets: [ 20 | // icon 21 | presetIcons({ 22 | prefix, 23 | extraProperties: { 24 | display: 'inline-block', 25 | overflow: 'hidden', 26 | width: '1em', 27 | height: '1em', 28 | 'vertical-align': '-0.15em', 29 | }, 30 | }), 31 | 32 | // unocss 33 | presetUno({ 34 | dark: 'class', 35 | }), 36 | ], 37 | shortcuts: { 38 | 'bg-comp': 'bg-white dark:bg-overlay', 39 | }, 40 | theme: { 41 | colors: { 42 | primary: 'var(--el-color-primary)', 43 | success: 'var(--el-color-success)', 44 | warning: 'var(--el-color-warning)', 45 | danger: 'var(--el-color-danger)', 46 | error: 'var(--el-color-error)', 47 | info: 'var(--el-color-info)', 48 | overlay: 'var(--el-bg-color-overlay)', 49 | page: 'var(--el-bg-color-page)', 50 | bd: 'var(--el-border-color)', 51 | disabled: 'var(--el-text-color-disabled)', 52 | regular: 'var(--el-text-color-regular)', 53 | placeholder: 'var(--el-text-color-placeholder)', 54 | primarytext: 'var(--el-text-color-primary)', 55 | }, 56 | }, 57 | exclude: [ 58 | 'node_modules', 59 | '.git', 60 | '.vscode', 61 | 'dist', 62 | 'public', 63 | 'build', 64 | 'mock', 65 | '.github', 66 | 'types', 67 | ], 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /build/vite/server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerOptions, PreviewOptions } from 'vite' 2 | 3 | export function createServerOptions(env: ViteEnv): ServerOptions { 4 | const { VITE_PORT, VITE_PROXY_API_TARGET } = env 5 | return { 6 | host: true, 7 | open: true, 8 | port: VITE_PORT, 9 | proxy: { 10 | '/api': { 11 | target: VITE_PROXY_API_TARGET, 12 | changeOrigin: true, 13 | rewrite: (path) => path.replace(/^\/api/, ''), 14 | }, 15 | }, 16 | } 17 | } 18 | 19 | export function createPreviewOptions(env: ViteEnv): PreviewOptions { 20 | const { VITE_PORT, VITE_PROXY_API_TARGET } = env 21 | return { 22 | host: true, 23 | open: true, 24 | port: VITE_PORT + 1, 25 | proxy: { 26 | '/api': { 27 | target: VITE_PROXY_API_TARGET, 28 | changeOrigin: true, 29 | rewrite: (path) => path.replace(/^\/api/, ''), 30 | }, 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ark-admin-vuenext", 3 | "version": "1.0.1", 4 | "scripts": { 5 | "bootstrap": "pnpm install", 6 | "serve": "npm run dev", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview:dist": "vite preview", 10 | "lint": "eslint --cache --max-warnings 0 \"src/**/*.{vue,ts,tsx}\" --fix", 11 | "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite", 12 | "clean:lib": "rimraf node_modules", 13 | "type:check": "vue-tsc --noEmit --skipLibCheck", 14 | "reinstall": "rimraf yarn.lock && rimraf package-lock.json && rimraf node_modules && npm run bootstrap" 15 | }, 16 | "dependencies": { 17 | "@unocss/reset": "^0.48.2", 18 | "@vueuse/core": "^9.5.0", 19 | "axios": "^0.24.0", 20 | "crypto-js": "^4.1.1", 21 | "dayjs": "^1.11.6", 22 | "echarts": "^5.4.0", 23 | "element-plus": "^2.2.27", 24 | "lodash-es": "^4.17.21", 25 | "mitt": "^3.0.0", 26 | "nprogress": "^0.2.0", 27 | "pinia": "^2.0.23", 28 | "qs": "^6.11.0", 29 | "vue": "^3.2.45", 30 | "vue-i18n": "^9.2.2", 31 | "vue-router": "^4.1.6" 32 | }, 33 | "devDependencies": { 34 | "@iconify/json": "^2.1.136", 35 | "@types/crypto-js": "^4.1.1", 36 | "@types/lodash-es": "^4.17.6", 37 | "@types/node": "^17.0.45", 38 | "@types/nprogress": "^0.2.0", 39 | "@types/qs": "^6.9.7", 40 | "@typescript-eslint/eslint-plugin": "^5.42.1", 41 | "@typescript-eslint/parser": "^5.42.1", 42 | "@unocss/preset-icons": "^0.48.2", 43 | "@vitejs/plugin-vue": "^3.2.0", 44 | "@vitejs/plugin-vue-jsx": "^2.1.1", 45 | "autoprefixer": "^10.4.13", 46 | "eslint": "^8.27.0", 47 | "eslint-config-prettier": "^8.5.0", 48 | "eslint-define-config": "^1.12.0", 49 | "eslint-plugin-vue": "^8.7.1", 50 | "postcss": "^8.4.19", 51 | "rimraf": "^3.0.2", 52 | "sass": "^1.56.1", 53 | "terser": "^5.15.1", 54 | "typescript": "^4.8.4", 55 | "unocss": "^0.48.2", 56 | "vite": "^3.2.3", 57 | "vite-plugin-compression": "^0.5.1", 58 | "vue-eslint-parser": "^8.3.0", 59 | "vue-tsc": "^1.0.9" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | semi: false, 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | proseWrap: 'never', 7 | htmlWhitespaceSensitivity: 'strict', 8 | endOfLine: 'auto', 9 | } 10 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arklnk/ark-admin-vuenext/f4763ddacf8f047c0a4bb9b27d5af2cf14d04896/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | -------------------------------------------------------------------------------- /src/api/config/dict.ts: -------------------------------------------------------------------------------- 1 | import type { PageRequestParams, PaginationResult } from '/#/axios' 2 | 3 | import { defHttp } from '/@/utils/http/axios' 4 | 5 | export const Api = { 6 | list: '/config/dict/list', 7 | page: '/config/dict/data/page', 8 | add: '/config/dict/add', 9 | update: '/config/dict/update', 10 | delete: '/config/dict/delete', 11 | } 12 | 13 | export interface ParamConfigResult { 14 | id: number 15 | name: string 16 | orderNum: number 17 | parentId: number 18 | remark: string 19 | status: number 20 | uniqueKey: string 21 | value: string 22 | } 23 | export async function getDictListRequest(): Promise { 24 | const { list } = await defHttp.get({ url: Api.list }) 25 | return list 26 | } 27 | 28 | export async function getDictDataPageRequest( 29 | params: PageRequestParams 30 | ): Promise> { 31 | return await defHttp.get({ url: Api.page, params }) 32 | } 33 | 34 | export async function addDictRequest(data: Omit) { 35 | return await defHttp.post({ url: Api.add, data }) 36 | } 37 | 38 | export async function updateDictRequest(data: Omit) { 39 | return await defHttp.post({ url: Api.update, data }) 40 | } 41 | 42 | export async function deleteDictRequest(data: { id: number }) { 43 | return await defHttp.post({ url: Api.delete, data }) 44 | } 45 | -------------------------------------------------------------------------------- /src/api/log/login.ts: -------------------------------------------------------------------------------- 1 | import type { PageRequestParams, PaginationResult } from '/#/axios' 2 | 3 | import { defHttp } from '/@/utils/http/axios' 4 | 5 | export const Api = { 6 | page: '/log/login/page', 7 | delete: '/log/login/delete', 8 | } 9 | 10 | export interface LoginLog { 11 | account: string 12 | id: number 13 | ip: string 14 | status: number 15 | uri: string 16 | createTime: string 17 | } 18 | 19 | export async function getLoginLogPageRequest( 20 | params: PageRequestParams 21 | ): Promise> { 22 | return await defHttp.get({ url: Api.page, params }) 23 | } 24 | -------------------------------------------------------------------------------- /src/api/system/dept.ts: -------------------------------------------------------------------------------- 1 | import { listToTree } from '/@/utils/helper/tree' 2 | import { defHttp } from '/@/utils/http/axios' 3 | 4 | export const Api = { 5 | list: '/sys/dept/list', 6 | add: '/sys/dept/add', 7 | update: '/sys/dept/update', 8 | delete: '/sys/dept/delete', 9 | } 10 | 11 | export interface DeptResult { 12 | fullName: string 13 | id: number 14 | name: string 15 | orderNum: number 16 | parentId: number 17 | remark: string 18 | status: number 19 | uniqueKey: string 20 | } 21 | 22 | export async function getDeptListRequest(): Promise<(DeptResult & { children?: DeptResult[] })[]> { 23 | const { list }: { list: DeptResult[] } = await defHttp.get({ url: Api.list }) 24 | return listToTree(list) 25 | } 26 | 27 | export async function addDeptRequest(data: Omit) { 28 | return await defHttp.post({ url: Api.add, data }) 29 | } 30 | 31 | export async function updateDeptRequest(data: DeptResult) { 32 | return await defHttp.post({ url: Api.update, data }) 33 | } 34 | 35 | export async function deleteDeptRequest(data: { id: number }) { 36 | return await defHttp.post({ url: Api.delete, data }) 37 | } 38 | -------------------------------------------------------------------------------- /src/api/system/job.ts: -------------------------------------------------------------------------------- 1 | import type { PageRequestParams, PaginationResult } from '/#/axios' 2 | 3 | import { defHttp } from '/@/utils/http/axios' 4 | 5 | export const Api = { 6 | page: '/sys/job/page', 7 | add: '/sys/job/add', 8 | update: '/sys/job/update', 9 | delete: '/sys/job/delete', 10 | } 11 | 12 | export interface JobResult { 13 | id: number 14 | name: string 15 | orderNum: number 16 | status: number 17 | } 18 | export async function getJobPageRequest( 19 | params: PageRequestParams 20 | ): Promise> { 21 | return await defHttp.get({ url: Api.page, params }) 22 | } 23 | 24 | export async function addJobRequest(data: Omit) { 25 | return await defHttp.post({ url: Api.add, data }) 26 | } 27 | 28 | export async function updateJobRequest(data: JobResult) { 29 | return await defHttp.post({ url: Api.update, data }) 30 | } 31 | 32 | export async function deleteJobRequest(data: { id: number }) { 33 | return await defHttp.post({ url: Api.delete, data }) 34 | } 35 | -------------------------------------------------------------------------------- /src/api/system/menu.ts: -------------------------------------------------------------------------------- 1 | import { listToTree } from '/@/utils/helper/tree' 2 | import { defHttp } from '/@/utils/http/axios' 3 | 4 | export const Api = { 5 | list: '/sys/perm/menu/list', 6 | add: '/sys/perm/menu/add', 7 | delete: '/sys/perm/menu/delete', 8 | update: '/sys/perm/menu/update', 9 | } 10 | 11 | export interface MenuResult { 12 | id: number 13 | parentId?: number 14 | name: string 15 | router: string 16 | perms: string[] 17 | type: number 18 | icon: string 19 | orderNum: number 20 | viewPath: string 21 | isShow: boolean 22 | activeRouter: string 23 | } 24 | 25 | export async function getMenuListRequest(): Promise<(MenuResult & { children?: MenuResult[] })[]> { 26 | const { list }: { list: MenuResult[] } = await defHttp.get({ url: Api.list }) 27 | return listToTree(list) 28 | } 29 | 30 | export async function addMenuRequest(data: Omit) { 31 | return await defHttp.post({ 32 | url: Api.add, 33 | data, 34 | }) 35 | } 36 | 37 | export async function deleteMenuRequest(data: { id: number }) { 38 | return await defHttp.post({ 39 | url: Api.delete, 40 | data, 41 | }) 42 | } 43 | 44 | export async function updateMenuRequest(data: MenuResult) { 45 | return await defHttp.post({ url: Api.update, data }) 46 | } 47 | -------------------------------------------------------------------------------- /src/api/system/profession.ts: -------------------------------------------------------------------------------- 1 | import type { PageRequestParams, PaginationResult } from '/#/axios' 2 | 3 | import { defHttp } from '/@/utils/http/axios' 4 | 5 | export const Api = { 6 | page: '/sys/profession/page', 7 | add: '/sys/profession/add', 8 | update: '/sys/profession/update', 9 | delete: '/sys/profession/delete', 10 | } 11 | 12 | export interface ProfessionResult { 13 | id: number 14 | name: string 15 | orderNum: number 16 | status: number 17 | } 18 | 19 | export async function getProfPageRequest( 20 | params: PageRequestParams 21 | ): Promise> { 22 | return await defHttp.get({ url: Api.page, params }) 23 | } 24 | 25 | export async function addProfRequest(data: Omit) { 26 | return await defHttp.post({ url: Api.add, data }) 27 | } 28 | 29 | export async function updateProfRequest(data: ProfessionResult) { 30 | return await defHttp.post({ url: Api.update, data }) 31 | } 32 | 33 | export async function deleteProfRequest(data: { id: number }) { 34 | return await defHttp.post({ url: Api.delete, data }) 35 | } 36 | -------------------------------------------------------------------------------- /src/api/system/role.ts: -------------------------------------------------------------------------------- 1 | import { listToTree } from '/@/utils/helper/tree' 2 | import { defHttp } from '/@/utils/http/axios' 3 | 4 | export const Api = { 5 | list: '/sys/role/list', 6 | add: '/sys/role/add', 7 | update: '/sys/role/update', 8 | delete: '/sys/role/delete', 9 | } 10 | 11 | export interface RoleResult { 12 | id: number 13 | name: string 14 | orderNum: number 15 | parentId: number 16 | remark: string 17 | status: number 18 | uniqueKey: string 19 | permMenuIds: number[] 20 | } 21 | 22 | export async function getRoleListRequest(): Promise<(RoleResult & { children?: RoleResult[] })[]> { 23 | const { list }: { list: RoleResult[] } = await defHttp.get({ url: Api.list }) 24 | return listToTree(list) 25 | } 26 | 27 | export async function addRoleRequest(data: Omit) { 28 | return await defHttp.post({ url: Api.add, data }) 29 | } 30 | 31 | export async function updateRoleRequest(data: RoleResult) { 32 | return await defHttp.post({ url: Api.update, data }) 33 | } 34 | 35 | export async function deleteRoleRequest(data: { id: number }) { 36 | return await defHttp.post({ url: Api.delete, data }) 37 | } 38 | -------------------------------------------------------------------------------- /src/api/system/user.ts: -------------------------------------------------------------------------------- 1 | import type { PageRequestParams, PaginationResult } from '/#/axios' 2 | 3 | import { defHttp } from '/@/utils/http/axios' 4 | 5 | export const Api = { 6 | page: '/sys/user/page', 7 | add: '/sys/user/add', 8 | update: '/sys/user/update', 9 | delete: '/sys/user/delete', 10 | rdpj: '/sys/user/rdpj/info', 11 | pwd: '/sys/user/password/update', 12 | } 13 | 14 | interface IdNameRecord { 15 | id: number 16 | name: string 17 | } 18 | export interface UserResult { 19 | account: string 20 | avatar: string 21 | birthday: string 22 | dept: IdNameRecord 23 | email: string 24 | gender: number 25 | id: number 26 | job: IdNameRecord 27 | mobile: string 28 | nickname: string 29 | orderNum: number 30 | profession: IdNameRecord 31 | remark: string 32 | roles: IdNameRecord[] 33 | status: number 34 | username: string 35 | } 36 | export async function getUserPageRequest( 37 | params: PageRequestParams & { deptId: number } 38 | ): Promise> { 39 | return await defHttp.get({ url: Api.page, params }) 40 | } 41 | 42 | export type UserRequestParams = Omit & { 43 | professionId: number 44 | roleIds: number[] 45 | jobId: number 46 | deptId: number 47 | avatar: string 48 | } 49 | 50 | export async function addUserRequest(data: UserRequestParams) { 51 | return await defHttp.post({ url: Api.add, data }) 52 | } 53 | 54 | export async function updateUserRequest(data: UserRequestParams & { id: number }) { 55 | return await defHttp.post({ url: Api.update, data }) 56 | } 57 | 58 | export async function deleteUserRequest(data: { id: number }) { 59 | return await defHttp.post({ url: Api.delete, data }) 60 | } 61 | 62 | interface RDPJInfoResult { 63 | role: IdNameRecord[] 64 | profession: IdNameRecord[] 65 | job: IdNameRecord[] 66 | dept: IdNameRecord[] 67 | } 68 | 69 | export async function getRDPJInfoRequest(params?: { userId: number }): Promise { 70 | return await defHttp.get({ url: Api.rdpj, params }) 71 | } 72 | 73 | export async function updateUserPwdRequest(data: { id: number; password: string }) { 74 | return await defHttp.post({ url: Api.pwd, data }) 75 | } 76 | -------------------------------------------------------------------------------- /src/assets/icons/config.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/documentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/log.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/system.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arklnk/ark-admin-vuenext/f4763ddacf8f047c0a4bb9b27d5af2cf14d04896/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | import AppProviderComp from './src/AppProvider.vue' 2 | import AppLogoComp from './src/AppLogo.vue' 3 | import AppDarkModeToggleComp from './src/AppDarkModeToggle.vue' 4 | import AppLocalePickerComp from './src/AppLocalePicker.vue' 5 | 6 | import { withInstall } from '/@/utils' 7 | 8 | export const AppProvider = withInstall(AppProviderComp) 9 | export const AppLogo = withInstall(AppLogoComp) 10 | export const AppDarkModeToggle = withInstall(AppDarkModeToggleComp) 11 | export const AppLocalePicker = withInstall(AppLocalePickerComp) 12 | -------------------------------------------------------------------------------- /src/components/Application/src/AppDarkModeToggle.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 40 | -------------------------------------------------------------------------------- /src/components/Application/src/AppLocalePicker.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | -------------------------------------------------------------------------------- /src/components/Application/src/AppLogo.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 40 | 41 | 76 | -------------------------------------------------------------------------------- /src/components/Application/src/AppProvider.vue: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /src/components/Application/src/useAppContext.ts: -------------------------------------------------------------------------------- 1 | import type { Ref, InjectionKey } from 'vue' 2 | import { createContext, useContext } from '/@/composables/core/useContext' 3 | 4 | export interface AppProviderContextProps { 5 | prefixCls: Ref 6 | isMobile: Ref 7 | } 8 | 9 | const key: InjectionKey = Symbol() 10 | 11 | // use like element-plus handle 12 | // this is meant to fix global methods like `ElMessage(opts)`, this way we can inject current locale 13 | // into the component as default injection value. 14 | // refer to: https://github.com/element-plus/element-plus/issues/2610#issuecomment-887965266 15 | let appGlobalConfig: AppProviderContextProps | null = null 16 | 17 | export function createAppProviderContext(context: AppProviderContextProps) { 18 | const { state } = createContext(context, key) 19 | // global init 20 | appGlobalConfig = state 21 | } 22 | 23 | export function useAppProviderContext() { 24 | return useContext(key, appGlobalConfig) 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | import PopConfirmButtonComp from './src/PopConfirmButton.vue' 2 | import { withInstall } from '/@/utils' 3 | 4 | export const PopConfirmButton = withInstall(PopConfirmButtonComp) 5 | 6 | export * from './src/typing' 7 | -------------------------------------------------------------------------------- /src/components/Button/src/PopConfirmButton.vue: -------------------------------------------------------------------------------- 1 | 57 | -------------------------------------------------------------------------------- /src/components/Button/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ButtonProps as EleButtonProps, 3 | PopconfirmProps as ElePopconfirmProps, 4 | } from 'element-plus' 5 | 6 | export type ButtonProps = Partial> 7 | 8 | export type PopconfirmProps = Partial> 9 | -------------------------------------------------------------------------------- /src/components/ContextMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { createContextMenu, destoryContextMenu } from './src/createContextMenu' 2 | 3 | export * from './src/typing' 4 | -------------------------------------------------------------------------------- /src/components/ContextMenu/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, Component } from 'vue' 2 | 3 | export interface Axis { 4 | x: number 5 | y: number 6 | } 7 | 8 | export interface ContextMenuItem { 9 | label?: string 10 | icon?: string | Component 11 | hidden?: boolean 12 | disabled?: boolean 13 | divider?: boolean 14 | handler?: Fn 15 | } 16 | 17 | export interface ContextMenuProps { 18 | width?: number 19 | offset?: number 20 | showIcon?: boolean 21 | customStyle?: CSSProperties 22 | customEvent?: MouseEvent 23 | axis?: Axis 24 | items?: ContextMenuItem[] 25 | } 26 | 27 | export interface CreateContextMenuOptions { 28 | event: MouseEvent 29 | style?: CSSProperties 30 | offset?: number 31 | items?: ContextMenuItem[] 32 | showIcon?: boolean 33 | width?: number 34 | } 35 | 36 | export interface ItemContentProps { 37 | showIcon: boolean | undefined 38 | item: ContextMenuItem 39 | } 40 | -------------------------------------------------------------------------------- /src/components/CountTo/index.ts: -------------------------------------------------------------------------------- 1 | import CountToComp from './src/CountTo.vue' 2 | import { withInstall } from '/@/utils' 3 | 4 | export const CountTo = withInstall(CountToComp) 5 | -------------------------------------------------------------------------------- /src/components/Dialog/index.ts: -------------------------------------------------------------------------------- 1 | import BasicDialogComp from './src/BasicDialog.vue' 2 | import { withInstall } from '/@/utils' 3 | 4 | export const BasicDialog = withInstall(BasicDialogComp) 5 | 6 | export { useDialog, useDialogInner } from './src/composables/useDialog' 7 | export * from './src/typing' 8 | -------------------------------------------------------------------------------- /src/components/Dialog/src/components/DialogClose.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 37 | -------------------------------------------------------------------------------- /src/components/Dialog/src/components/DialogFooter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 50 | -------------------------------------------------------------------------------- /src/components/Dialog/src/props.ts: -------------------------------------------------------------------------------- 1 | import { ButtonProps } from 'element-plus' 2 | 3 | export const basicProps = { 4 | // el-dialog props 5 | draggable: { type: Boolean, default: true }, 6 | top: { type: String, default: '15vh' }, 7 | width: { type: [String, Number] as PropType, default: '50%' }, 8 | modal: { type: Boolean, default: true }, 9 | appendToBody: { type: Boolean }, 10 | lockScroll: { type: Boolean, default: true }, 11 | openDelay: { type: Number, default: 0 }, 12 | closeDelay: { type: Number, default: 0 }, 13 | closeOnClickModal: { type: Boolean, default: true }, 14 | closeOnPressEscape: { type: Boolean, default: true }, 15 | destroyOnClose: { type: Boolean }, 16 | 17 | // extra props 18 | visible: { type: Boolean }, 19 | defaultFullscreen: { type: Boolean }, 20 | minHeight: { type: [String, Number] as PropType }, 21 | // 在全屏下该值无效,会直接占满容器剩余高度 22 | height: { type: [String, Number] as PropType }, 23 | loading: { type: Boolean }, 24 | loadingTip: { type: String }, 25 | canFullscreen: { type: Boolean, default: true }, 26 | showConfirmBtn: { type: Boolean, default: true }, 27 | showCancelBtn: { type: Boolean, default: true }, 28 | confirmText: { type: String }, 29 | cancelText: { type: String }, 30 | confirmBtnProps: { type: Object as PropType>> }, 31 | cancelBtnProps: { type: Object as PropType>> }, 32 | title: { type: String }, 33 | helpMessage: { type: String }, 34 | closeFunc: { type: Function as PropType<() => Promise> }, 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Dialog/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { ExtractPropTypes } from 'vue' 2 | import { basicProps } from './props' 3 | 4 | export type BasicDialogProps = Partial> 5 | 6 | export interface BasicDialogActionType { 7 | setProps: (props: Partial) => void 8 | } 9 | 10 | export interface ExtraBasicDialogActionType extends BasicDialogActionType { 11 | openDialog: (data?: T) => void 12 | closeDialog: () => void 13 | setLoading: (loading?: boolean) => void 14 | setConfirmLoading: (loading?: boolean) => void 15 | } 16 | 17 | export type RegisterFn = (action: BasicDialogActionType, uid: number) => void 18 | 19 | export type UseDialogReturnType = [RegisterFn, ExtraBasicDialogActionType] 20 | 21 | export type UseDialogInnerReturnType = [RegisterFn, Omit] 22 | -------------------------------------------------------------------------------- /src/components/Drawer/index.ts: -------------------------------------------------------------------------------- 1 | import BasicDrawerComp from './src/BasicDrawer.vue' 2 | import { withInstall } from '/@/utils' 3 | 4 | export const BasicDrawer = withInstall(BasicDrawerComp) 5 | 6 | export { useDrawer, useDrawerInner } from './src/composables/useDrawer' 7 | 8 | export * from './src/typing' 9 | -------------------------------------------------------------------------------- /src/components/Drawer/src/components/DrawerFooter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 51 | -------------------------------------------------------------------------------- /src/components/Drawer/src/props.ts: -------------------------------------------------------------------------------- 1 | import { drawerProps } from 'element-plus' 2 | import { omit } from 'lodash-es' 3 | import { ButtonProps } from '/@/components/Button' 4 | 5 | export const basicProps = { 6 | // el-drawer原有属性 7 | ...omit(drawerProps, ['modelValue', 'customClass', 'beforeClose']), 8 | 9 | // 扩展属性 10 | visible: { type: Boolean }, 11 | helpMessage: { type: String }, 12 | loading: { type: Boolean }, 13 | loadingTip: { type: String }, 14 | showConfirmBtn: { type: Boolean, default: true }, 15 | showCancelBtn: { type: Boolean, default: true }, 16 | confirmText: { type: String }, 17 | cancelText: { type: String }, 18 | confirmBtnProps: { type: Object as PropType>> }, 19 | cancelBtnProps: { type: Object as PropType>> }, 20 | closeFunc: { type: Function as PropType<() => Promise> }, 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Drawer/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { ExtractPropTypes } from 'vue' 2 | import { basicProps } from './props' 3 | 4 | export type BasicDrawerProps = Partial> 5 | 6 | export interface BasicDrawerActionType { 7 | setProps: (props: Partial) => void 8 | } 9 | 10 | export interface ExtraBasicDrawerActionType extends BasicDrawerActionType { 11 | openDrawer: (data?: T) => void 12 | closeDrawer: () => void 13 | setLoading: (loading?: boolean) => void 14 | setConfirmLoading: (loading?: boolean) => void 15 | } 16 | 17 | export type RegisterFn = (action: BasicDrawerActionType, uid: number) => void 18 | 19 | export type UseDrawerReturnType = [RegisterFn, ExtraBasicDrawerActionType] 20 | 21 | export type UseDrawerInnerReturnType = [RegisterFn, Omit] 22 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | import BasicFormComp from './src/BasicForm.vue' 2 | import { withInstall } from '/@/utils' 3 | 4 | export const BasicForm = withInstall(BasicFormComp) 5 | 6 | export { useForm } from './src/composables/useForm' 7 | 8 | export { createFormDialog, BasicFormDialog } from './src/functional/createFormDialog' 9 | export { createFormDrawer, BasicFormDrawer } from './src/functional/createFormDrawer' 10 | 11 | export * from './src/typing' 12 | -------------------------------------------------------------------------------- /src/components/Form/src/components/FormAction.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 51 | -------------------------------------------------------------------------------- /src/components/Form/src/composables/useFormContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from 'vue' 2 | import { createContext, useContext } from '/@/composables/core/useContext' 3 | 4 | export interface BasicFormContextProps { 5 | submitAction: () => Promise 6 | resetAction: () => Promise 7 | } 8 | 9 | const formKey: InjectionKey = Symbol() 10 | 11 | export function createFormContext(context: BasicFormContextProps) { 12 | return createContext(context, formKey) 13 | } 14 | 15 | export function useFormContext() { 16 | return useContext(formKey) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Form/src/composables/useFormValues.ts: -------------------------------------------------------------------------------- 1 | import { ComputedRef, Ref } from 'vue' 2 | import type { BasicFormProps, FormSchema } from '../typing' 3 | 4 | import { unref } from 'vue' 5 | import { cloneDeep, get, isFunction, isNil, isPlainObject, isString, set } from 'lodash-es' 6 | import { formatToDateTime, isDateObject } from '/@/utils/date' 7 | 8 | interface UseFormValuesParams { 9 | defaultValueRef: Ref 10 | getSchema: ComputedRef 11 | formModel: Recordable 12 | getProps: ComputedRef 13 | } 14 | 15 | export function useFormValues({ 16 | getSchema, 17 | formModel, 18 | defaultValueRef, 19 | getProps, 20 | }: UseFormValuesParams) { 21 | function processFormValues(values: Recordable) { 22 | if (!isPlainObject(values)) { 23 | return {} 24 | } 25 | const res: Recordable = {} 26 | for (const item of Object.entries(values)) { 27 | let [, v] = item 28 | const [k] = item 29 | if (!k || isFunction(v)) { 30 | continue 31 | } 32 | 33 | const dateFormat = unref(getProps).dateFormat 34 | // transform 35 | if (isString(v)) { 36 | // string remove spaces 37 | v = v.trim() 38 | } else if (isDateObject(v)) { 39 | // date object 40 | v = formatToDateTime(v, dateFormat) 41 | } else if (Array.isArray(v) && v.length === 2 && isDateObject(v[0]) && isDateObject(v[1])) { 42 | // date range array 43 | v = v.map((e) => formatToDateTime(e, dateFormat)) 44 | } else if (isPlainObject(v)) { 45 | // deep transform 46 | v = processFormValues(v) 47 | } 48 | 49 | // 其他值则不再处理 50 | set(res, k, v) 51 | } 52 | return res 53 | } 54 | 55 | function initDefault() { 56 | const schemas = unref(getSchema) 57 | const def: Recordable = {} 58 | 59 | schemas.forEach((item: FormSchema) => { 60 | const { defaultValue } = item 61 | 62 | if (!isNil(defaultValue)) { 63 | set(def, item.prop, defaultValue) 64 | 65 | // update form model 66 | if (get(formModel, item.prop) === undefined) { 67 | set(formModel, item.prop, defaultValue) 68 | } 69 | } 70 | }) 71 | 72 | defaultValueRef.value = cloneDeep(def) 73 | } 74 | 75 | return { 76 | processFormValues, 77 | initDefault, 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/components/Heading/index.ts: -------------------------------------------------------------------------------- 1 | import BasicHeadingComp from './src/Heading.vue' 2 | import { withInstall } from '/@/utils' 3 | 4 | export const BasicHeading = withInstall(BasicHeadingComp) 5 | -------------------------------------------------------------------------------- /src/components/Heading/src/Heading.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | 30 | 35 | -------------------------------------------------------------------------------- /src/components/Icon/data/icon.data.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * safe icon data list 3 | */ 4 | export default [ 5 | 'carbon:dashboard', 6 | 'carbon:settings', 7 | 'ic:baseline-history', 8 | 'material-symbols:library-books-outline', 9 | 'icon-park-outline:setting-config', 10 | 'material-symbols:chrome-reader-mode-outline-rounded', 11 | 'carbon:application-web', 12 | ] 13 | -------------------------------------------------------------------------------- /src/components/Icon/index.ts: -------------------------------------------------------------------------------- 1 | import IconPickerComp from './src/IconPicker.vue' 2 | import { withInstall } from '/@/utils' 3 | 4 | export const IconPicker = withInstall(IconPickerComp) 5 | -------------------------------------------------------------------------------- /src/components/Page/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils' 2 | 3 | import PageWrapperComp from './src/PageWrapper.vue' 4 | import PageHeaderComp from './src/PageHeader.vue' 5 | 6 | export const PageWrapper = withInstall(PageWrapperComp) 7 | export const PageHeader = withInstall(PageHeaderComp) 8 | 9 | export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight' 10 | -------------------------------------------------------------------------------- /src/components/Page/src/PageHeader.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 24 | 25 | 62 | -------------------------------------------------------------------------------- /src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | import BasicTableComp from './src/BasicTable.vue' 2 | import TableActionComp from './src/components/TableAction.vue' 3 | 4 | import { withInstall } from '/@/utils' 5 | 6 | export const BasicTableAction = withInstall(TableActionComp) 7 | export const BasicTable = withInstall(BasicTableComp) 8 | 9 | export { useTable } from './src/composables/useTable' 10 | 11 | export * from './src/types/column' 12 | export * from './src/types/pagination' 13 | export * from './src/types/table' 14 | export * from './src/types/action' 15 | -------------------------------------------------------------------------------- /src/components/Table/src/components/TableColumn.tsx: -------------------------------------------------------------------------------- 1 | import type { TableColumn, BasicTableColumnData } from '../types/column' 2 | 3 | import { defineComponent } from 'vue' 4 | import { isEmpty, isFunction, omit } from 'lodash-es' 5 | import { getSlot } from '/@/utils/helper/tsx' 6 | 7 | export default defineComponent({ 8 | name: 'BasicTableColumn', 9 | props: { 10 | columns: { 11 | type: Array as PropType, 12 | default: () => null, 13 | }, 14 | }, 15 | setup(props, { slots }) { 16 | function renderColumns(columns: TableColumn[]) { 17 | return columns.map((col) => { 18 | const slotsObj: Recordable = {} 19 | 20 | // render content 21 | if (col.children && col.children.length > 0) { 22 | // multiple table header 23 | const childColumns = renderColumns(col.children) 24 | slotsObj.default = () => childColumns 25 | } else if (!isEmpty(col.slot)) { 26 | // slot 渲染默认内容 27 | slotsObj.default = (scope: BasicTableColumnData) => getSlot(slots, col.slot, scope) 28 | } else if (col.render && isFunction(col.render)) { 29 | // render function渲染默认内容 30 | slotsObj.default = (scope: BasicTableColumnData) => col.render!(scope) 31 | } 32 | 33 | // render header 34 | if (!isEmpty(col.headerSlot)) { 35 | slotsObj.header = (scope: Omit) => 36 | getSlot(slots, col.headerSlot, scope) 37 | } else if (col.renderHeader && isFunction(col.renderHeader)) { 38 | slotsObj.header = (scope: Omit) => col.renderHeader!(scope) 39 | } 40 | 41 | const bindValue = omit(col, ['children', 'render', 'slot', 'renderHeader', 'headerSlot']) 42 | 43 | return 44 | }) 45 | } 46 | 47 | return () => { 48 | if (!props.columns) return null 49 | 50 | return renderColumns(props.columns) 51 | } 52 | }, 53 | }) 54 | -------------------------------------------------------------------------------- /src/components/Table/src/components/TableFooter.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 54 | 55 | 82 | -------------------------------------------------------------------------------- /src/components/Table/src/components/TableHeader.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 49 | 50 | 75 | -------------------------------------------------------------------------------- /src/components/Table/src/components/settings/FullscreenSetting.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /src/components/Table/src/components/settings/RedoSetting.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /src/components/Table/src/components/settings/SizeSetting.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 40 | -------------------------------------------------------------------------------- /src/components/Table/src/composables/useCurrentRow.ts: -------------------------------------------------------------------------------- 1 | import type { ElTable } from 'element-plus' 2 | import type { Ref } from 'vue' 3 | 4 | import { ref, unref } from 'vue' 5 | 6 | export function useCurrentRow(tableRef: Ref>>) { 7 | const currentRowRef = ref>(null) 8 | 9 | function setCurrentRow(row: Recordable) { 10 | unref(tableRef)?.setCurrentRow(row) 11 | } 12 | 13 | function getCurrentRow() { 14 | return unref(currentRowRef) 15 | } 16 | 17 | function setCurrentRowRef(row: Recordable) { 18 | currentRowRef.value = row 19 | } 20 | 21 | return { 22 | setCurrentRow, 23 | getCurrentRow, 24 | // Internal use 25 | setCurrentRowRef, 26 | currentRowRef, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Table/src/composables/useLoading.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef } from 'vue' 2 | import type { BasicTableProps } from '../types/table' 3 | 4 | import { ref, unref, watch, computed } from 'vue' 5 | 6 | export function useLoading(getProps: ComputedRef) { 7 | const loadingRef = ref(unref(getProps).loading) 8 | 9 | function setLoading(loading: boolean) { 10 | loadingRef.value = loading 11 | } 12 | 13 | watch( 14 | () => unref(getProps).loading, 15 | (loading) => { 16 | loadingRef.value = loading 17 | } 18 | ) 19 | 20 | const getLoading = computed(() => unref(loadingRef)) 21 | 22 | return { setLoading, getLoading } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Table/src/composables/usePagination.ts: -------------------------------------------------------------------------------- 1 | import type { BasicTableProps } from '../types/table' 2 | import type { ComputedRef } from 'vue' 3 | import type { PaginationProps } from '../types/pagination' 4 | 5 | import { ref, watch, unref, computed } from 'vue' 6 | import { isBoolean } from 'lodash-es' 7 | import { 8 | DEFAULT_PAGE_BG, 9 | DEFAULT_PAGE_LAYOUT, 10 | DEFAULT_PAGE_SIZE, 11 | DEFAULT_PAGE_SIZES_OPTIONS, 12 | DEFAULT_PAGE_SMALL, 13 | } from '../const' 14 | 15 | export function usePagination(getProps: ComputedRef) { 16 | const configRef = ref({}) 17 | const showRef = ref(true) 18 | 19 | watch( 20 | () => unref(getProps).pagination, 21 | (pagination) => { 22 | // check pagination props type, if is true or null will use default config 23 | // if false will disable pagination 24 | showRef.value = !(isBoolean(pagination) && !pagination) 25 | }, 26 | { 27 | immediate: true, 28 | } 29 | ) 30 | 31 | const getPaginationRef = computed((): Nullable => { 32 | if (!unref(showRef)) return null 33 | 34 | const { pagination } = unref(getProps) 35 | 36 | return { 37 | total: 0, 38 | currentPage: 1, 39 | pageSizes: DEFAULT_PAGE_SIZES_OPTIONS, 40 | pageSize: DEFAULT_PAGE_SIZE, 41 | layout: DEFAULT_PAGE_LAYOUT, 42 | background: DEFAULT_PAGE_BG, 43 | small: DEFAULT_PAGE_SMALL, 44 | ...(isBoolean(pagination) ? {} : pagination), 45 | ...unref(configRef), 46 | } 47 | }) 48 | 49 | function setPagination(info: Partial) { 50 | const prev = unref(getPaginationRef) 51 | 52 | configRef.value = { 53 | ...prev, 54 | ...info, 55 | } 56 | } 57 | 58 | function getPagination() { 59 | return unref(getPaginationRef) 60 | } 61 | 62 | function setShowPagination(show: boolean) { 63 | showRef.value = show 64 | } 65 | 66 | function getShowPagination() { 67 | return unref(showRef) 68 | } 69 | 70 | const getShowPaginationRef = computed(() => { 71 | return unref(showRef) 72 | }) 73 | 74 | return { 75 | setShowPagination, 76 | getShowPagination, 77 | getShowPaginationRef, 78 | 79 | setPagination, 80 | getPagination, 81 | getPaginationRef, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/Table/src/composables/useRowSelection.ts: -------------------------------------------------------------------------------- 1 | import type { ElTable } from 'element-plus' 2 | import type { Ref } from 'vue' 3 | 4 | import { unref } from 'vue' 5 | 6 | export function useRowSelection(tableRef: Ref>>) { 7 | function clearSelection() { 8 | unref(tableRef)?.clearSelection() 9 | } 10 | 11 | function getSelectionRows(): T[] { 12 | const table = unref(tableRef) 13 | if (!table) return [] 14 | return table.getSelectionRows() 15 | } 16 | 17 | function toggleRowSelection(row: Recordable, selected?: boolean) { 18 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 19 | // @ts-expect-error 20 | unref(tableRef)?.toggleRowSelection(row, selected) 21 | } 22 | 23 | function toggleAllSelection() { 24 | unref(tableRef)?.toggleAllSelection() 25 | } 26 | 27 | return { 28 | clearSelection, 29 | getSelectionRows, 30 | toggleRowSelection, 31 | toggleAllSelection, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Table/src/composables/useTableContext.ts: -------------------------------------------------------------------------------- 1 | import type { BasicTableActionType, BasicTableProps } from '../types/table' 2 | import type { ElTable } from 'element-plus' 3 | import type { Ref, ComputedRef } from 'vue' 4 | 5 | import { inject, provide } from 'vue' 6 | 7 | const tableKey = Symbol('basic-table') 8 | 9 | type Instance = BasicTableActionType & { 10 | wrapRef: Ref> 11 | tableRef: Ref>> 12 | getBindValues: ComputedRef 13 | } 14 | 15 | type RetInstance = Omit & { 16 | getBindValues: ComputedRef 17 | } 18 | 19 | export function createTableContext(instance: Instance) { 20 | provide(tableKey, instance) 21 | } 22 | 23 | export function useTableContext(): RetInstance { 24 | return inject(tableKey) as RetInstance 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Table/src/composables/useTableExpand.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue' 2 | import type { BasicTableProps } from '../types/table' 3 | import type { ElTable } from 'element-plus' 4 | 5 | import { computed, unref } from 'vue' 6 | 7 | export function useTableExpand( 8 | getProps: ComputedRef, 9 | tableRef: Ref>> 10 | ) { 11 | const getChildrenName = computed(() => { 12 | return unref(getProps).treeProps?.children 13 | }) 14 | 15 | function handleRowClickToggleExpand(row: any, event: Event) { 16 | // tree table and must be a cell target, a blank is in effect 17 | // highlightCurrentRow is true then do not process 18 | if ( 19 | !unref(getProps).highlightCurrentRow && 20 | Reflect.has(row, unref(getChildrenName)!) && 21 | event.target instanceof HTMLElement && 22 | event.target.className.includes('cell') 23 | ) { 24 | unref(tableRef)?.toggleRowExpansion(row) 25 | } 26 | } 27 | 28 | function toggleRowExpansion(row: Recordable, expanded?: boolean) { 29 | unref(tableRef)?.toggleRowExpansion(row, expanded) 30 | } 31 | 32 | return { 33 | handleRowClickToggleExpand, 34 | toggleRowExpansion, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Table/src/const.ts: -------------------------------------------------------------------------------- 1 | import componentSetting from '/@/settings/componentSetting' 2 | 3 | const { 4 | table: { 5 | pagination: { pageSize, pageSizes, small, layout, background }, 6 | fetchSetting, 7 | }, 8 | } = componentSetting 9 | 10 | export const DEFAULT_PAGE_SIZE = pageSize 11 | 12 | export const DEFAULT_PAGE_SIZES_OPTIONS = pageSizes 13 | 14 | export const DEFAULT_PAGE_SMALL = small 15 | 16 | export const DEFAULT_PAGE_BG = background 17 | 18 | export const DEFAULT_PAGE_LAYOUT = layout 19 | 20 | export const FETCH_SETTING = fetchSetting 21 | -------------------------------------------------------------------------------- /src/components/Table/src/types/action.ts: -------------------------------------------------------------------------------- 1 | import type { ButtonProps, PopconfirmProps } from '/@/components/Button' 2 | import { RoleEnum } from '/@/enums/roleEnum' 3 | 4 | export interface TableActionItem extends ButtonProps { 5 | onClick?: Fn 6 | // 按钮文本 7 | label?: string 8 | // 权限控制是否显示,隐藏元素并非按钮禁用 9 | permission?: RoleEnum | RoleEnum[] | string | string[] 10 | // 分割线 11 | divider?: boolean 12 | // 是否需要隐藏 13 | hidden?: boolean | ((item: TableActionItem) => boolean) 14 | // 是否需要使用PopconfirmButton,如果为dropdown是会使用MessageBox代替 15 | popconfirm?: boolean | PopconfirmProps 16 | // Vue需要用到的key,当label重复时需要使用key来区分,优先使用key后使用label 17 | key?: string 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Table/src/types/column.ts: -------------------------------------------------------------------------------- 1 | import type columnProps from 'element-plus/es/components/table/src/table-column/defaults' 2 | import type { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults' 3 | import type { ExtractPropTypes } from 'vue' 4 | 5 | export type BasicTableColumnCtx = TableColumnCtx 6 | 7 | export interface BasicTableColumnData { 8 | row: T 9 | column: BasicTableColumnCtx 10 | $index: number 11 | } 12 | 13 | /** 14 | * table-column-props 15 | */ 16 | export interface TableColumn 17 | extends Omit>, 'renderHeader'> { 18 | // 对应列的类型,重写用于ts提示 19 | type?: 'selection' | 'index' | 'expand' 20 | // 对齐方式,重写用于ts提示 21 | align?: 'left' | 'right' | 'center' 22 | headerAlign?: 'left' | 'right' | 'center' 23 | // 固定列,重写用于ts提示 24 | fixed?: boolean | 'left' | 'right' 25 | // 自定义列的内容 26 | render?: (data: BasicTableColumnData) => VueNode 27 | // 自定义表头内容 28 | renderHeader?: (data: Omit) => VueNode 29 | // 插槽形式自定义列内容 30 | slot?: string 31 | // Header插槽形式自定义列头内容 32 | headerSlot?: string 33 | // 控制是否显示 34 | hidden?: boolean | ((column: TableColumn) => boolean) 35 | // 多级表头 36 | children?: TableColumn[] 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Table/src/types/pagination.ts: -------------------------------------------------------------------------------- 1 | import { PaginationProps as ElPaginationProps } from 'element-plus' 2 | 3 | export interface PaginationProps extends Partial> { 4 | // 分页位置,默认为right 5 | position?: 'left' | 'center' | 'right' 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Table/src/types/table.ts: -------------------------------------------------------------------------------- 1 | import type { basicProps } from '../props' 2 | import type { ExtractPropTypes } from 'vue' 3 | import type { SizeType } from '/#/config' 4 | import type { TableColumn } from './column' 5 | import type { PaginationProps } from './pagination' 6 | 7 | export interface TableSetting { 8 | redo?: boolean 9 | size?: boolean 10 | fullscreen?: boolean 11 | } 12 | 13 | export interface FetchSetting { 14 | // 请求接口当前页数 15 | pageField: string 16 | // 每页显示数量 17 | sizeField: string 18 | // 请求结果列表字段 支持 a.b.c 19 | listField: string 20 | // 请求结果总数字段 支持 a.b.c 21 | totalField: string 22 | } 23 | 24 | export interface FetchParams { 25 | page?: number 26 | searchInfo?: Recordable 27 | } 28 | 29 | export type BasicTableProps = Partial> 30 | 31 | export type Key = string | number 32 | 33 | export type GetRowKey = (record: Recordable, index?: number) => Key 34 | 35 | export interface GetColumnsParams { 36 | ignoreIndex?: boolean 37 | ignoreSelection?: boolean 38 | ignoreExpand?: boolean 39 | } 40 | 41 | export interface BasicTableActionType { 42 | reload: (opt?: FetchParams) => Promise 43 | setLoading: (loading: boolean) => void 44 | setProps: (props: Partial) => void 45 | setShowPagination: (show: boolean) => void 46 | getShowPagination: () => boolean 47 | getPagination: () => Nullable 48 | getDataSource: () => T[] 49 | getSize: () => SizeType 50 | redoHeight: () => void 51 | setCurrentRow: (row: Recordable) => void 52 | getCurrentRow: () => Nullable 53 | clearSelection: () => void 54 | getSelectionRows: () => T[] 55 | toggleRowSelection: (row: Recordable, selected?: boolean) => void 56 | toggleAllSelection: () => void 57 | toggleRowExpansion: (row: Recordable, expanded?: boolean) => void 58 | setColumns: (columns: TableColumn[]) => void 59 | getColumns: (params?: GetColumnsParams) => TableColumn[] 60 | } 61 | -------------------------------------------------------------------------------- /src/components/registerGlobalComp.ts: -------------------------------------------------------------------------------- 1 | import type { App, AppContext } from 'vue' 2 | import ElementPlus from 'element-plus' 3 | 4 | export let globalAppContext: AppContext 5 | 6 | export function registerGlobalComp(app: App) { 7 | // element-plus 8 | app.use(ElementPlus) 9 | 10 | // global app context 11 | globalAppContext = app._context 12 | } 13 | -------------------------------------------------------------------------------- /src/composables/core/onMountedOrActivated.ts: -------------------------------------------------------------------------------- 1 | import { nextTick, onMounted, onActivated } from 'vue' 2 | 3 | export function onMountedOrActivated(hook: Fn) { 4 | let mounted: boolean 5 | 6 | onMounted(() => { 7 | hook() 8 | nextTick(() => { 9 | mounted = true 10 | }) 11 | }) 12 | 13 | onActivated(() => { 14 | if (mounted) { 15 | hook() 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/composables/core/useAppInject.ts: -------------------------------------------------------------------------------- 1 | import { computed, unref } from 'vue' 2 | import { useAppProviderContext } from '/@/components/Application/src/useAppContext' 3 | 4 | export function useAppInject() { 5 | const ctx = useAppProviderContext() 6 | 7 | const getIsMobile = computed(() => unref(ctx.isMobile)) 8 | 9 | return { 10 | getIsMobile, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/composables/core/useContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, UnwrapRef } from 'vue' 2 | 3 | import { inject, provide, reactive, readonly as defineReadonly } from 'vue' 4 | 5 | export interface CreateContextOptions { 6 | readonly?: boolean 7 | createProvider?: boolean 8 | native?: boolean 9 | } 10 | 11 | type ShallowUnwrap = { 12 | [P in keyof T]: UnwrapRef 13 | } 14 | 15 | export function createContext( 16 | context: any, 17 | key: InjectionKey = Symbol(), 18 | options: CreateContextOptions = {} 19 | ) { 20 | const { readonly = true, createProvider = false, native = false } = options 21 | 22 | const state = reactive(context) 23 | const provideData = readonly ? defineReadonly(state) : state 24 | !createProvider && provide(key, native ? state : provideData) 25 | 26 | return { 27 | state: provideData, 28 | } 29 | } 30 | 31 | export function useContext( 32 | key: InjectionKey = Symbol(), 33 | defaultValue?: any 34 | ): ShallowUnwrap { 35 | return inject(key, defaultValue || {}) 36 | } 37 | -------------------------------------------------------------------------------- /src/composables/core/useDesign.ts: -------------------------------------------------------------------------------- 1 | import { useAppProviderContext } from '/@/components/Application/src/useAppContext' 2 | 3 | export function useDesign(scope: string) { 4 | const values = useAppProviderContext() 5 | 6 | return { 7 | prefixCls: `${values.prefixCls}-${scope}`, 8 | prefixVar: values.prefixCls, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/composables/core/usePermission.ts: -------------------------------------------------------------------------------- 1 | import { isEmpty } from 'lodash-es' 2 | import { PermissionModeEnum } from '/@/enums/appEnum' 3 | import { RoleEnum } from '/@/enums/roleEnum' 4 | import { resetRouter, router } from '/@/router' 5 | 6 | import projectSetting from '/@/settings/projectSetting' 7 | import { usePermissionStore } from '/@/stores/modules/permission' 8 | import { useUserStore } from '/@/stores/modules/user' 9 | 10 | export function usePermission() { 11 | const permissionStore = usePermissionStore() 12 | const userStore = useUserStore() 13 | 14 | async function resume() { 15 | resetRouter() 16 | const routes = await permissionStore.buildRoutesAction() 17 | routes.forEach((route) => router.addRoute(route)) 18 | } 19 | 20 | /** 21 | * 检查是否具有权限 22 | * @param values 权限值 23 | * @param nor and / or 且或非 24 | * @returns 25 | */ 26 | function hasPermission( 27 | values?: RoleEnum | RoleEnum[] | string | string[], 28 | nor: 'and' | 'or' = 'or' 29 | ): boolean { 30 | // 空值跳过权限校验 31 | if (isEmpty(values)) return true 32 | 33 | const mode = projectSetting.permissionMode 34 | 35 | // 角色权限模式 36 | if (mode === PermissionModeEnum.ROLE) { 37 | const perms = values as RoleEnum | RoleEnum[] 38 | if (!Array.isArray(perms)) { 39 | return userStore.getRoleList.includes(perms) 40 | } 41 | 42 | if (nor === 'and') { 43 | return perms.every((item) => userStore.getRoleList.includes(item)) 44 | } else { 45 | return perms.some((item) => userStore.getRoleList.includes(item)) 46 | } 47 | } 48 | 49 | // 后端权限模式 50 | if (mode === PermissionModeEnum.BACK) { 51 | const perms = values as string | string[] 52 | if (!Array.isArray(perms)) { 53 | return permissionStore.getPermissionList.includes(perms) 54 | } 55 | 56 | if (nor === 'and') { 57 | return perms.every((item) => permissionStore.getPermissionList.includes(item)) 58 | } else { 59 | return perms.some((item) => permissionStore.getPermissionList.includes(item)) 60 | } 61 | } 62 | 63 | return false 64 | } 65 | 66 | return { 67 | hasPermission, 68 | resume, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/composables/core/useTransl.ts: -------------------------------------------------------------------------------- 1 | import type { Composer } from 'vue-i18n' 2 | 3 | import { i18n } from '/@/locales/setupI18n' 4 | 5 | /** 6 | * https://vue-i18n.intlify.dev/api/injection.html#t-key 7 | */ 8 | type I18nGlobalTranslation = Composer['t'] 9 | 10 | function getKey(namespace: string | undefined, key: string) { 11 | if (!namespace) return key 12 | if (key.startsWith(namespace)) return key 13 | 14 | return `${namespace}.${key}` 15 | } 16 | 17 | // useI18n in vue-i18n, prevent to nuptial rename to useTransl 18 | export function useTransl(namespace?: string) { 19 | const normalT: I18nGlobalTranslation = (key: string) => getKey(namespace, key) 20 | 21 | // i18n not init 22 | if (!i18n) { 23 | return { 24 | t: normalT, 25 | } 26 | } 27 | 28 | const { t } = i18n.global 29 | 30 | const tFn: I18nGlobalTranslation = (key: string, ...args: unknown[]) => { 31 | if (!key) return '' 32 | if (!key.includes('.') && !namespace) return key 33 | 34 | return (t as I18nGlobalTranslation)(getKey(namespace, key), ...(args as [string, any])) 35 | } 36 | 37 | return { 38 | t: tFn, 39 | } 40 | } 41 | 42 | // cooperate with vscode i18nn ally plugin, only using in define routes meta title 43 | export const t = (key: string) => key 44 | -------------------------------------------------------------------------------- /src/composables/event/useEventListener.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref, unref, watch } from 'vue' 2 | import { throttle, debounce } from 'lodash-es' 3 | 4 | type RemoveEventFn = () => void 5 | interface EventListenerParams { 6 | el?: Element | Ref | Window | any 7 | name: string 8 | listener: EventListener 9 | options?: boolean | AddEventListenerOptions 10 | autoRemove?: boolean 11 | isDebounce?: boolean 12 | wait?: number 13 | } 14 | 15 | export function useEventListener({ 16 | el = window, 17 | name, 18 | listener, 19 | options, 20 | autoRemove = true, 21 | isDebounce = true, 22 | wait = 100, 23 | }: EventListenerParams): RemoveEventFn { 24 | let remove: RemoveEventFn = () => {} 25 | const isAddRef = ref(false) 26 | 27 | if (el) { 28 | const element = ref(el as Element) as Ref 29 | 30 | const handler = isDebounce ? debounce(listener, wait) : throttle(listener, wait) 31 | const realHandler = wait ? handler : listener 32 | 33 | const removeEventListener = (ele: Element) => { 34 | isAddRef.value = true 35 | ele.removeEventListener(name, realHandler, options) 36 | } 37 | const addEventListener = (ele: Element) => ele.addEventListener(name, realHandler, options) 38 | 39 | const removeWatch = watch( 40 | element, 41 | (v, _ov, cleanUp) => { 42 | if (v) { 43 | !unref(isAddRef) && addEventListener(v) 44 | cleanUp(() => { 45 | autoRemove && removeEventListener(v) 46 | }) 47 | } 48 | }, 49 | { immediate: true } 50 | ) 51 | 52 | // update 53 | remove = () => { 54 | removeEventListener(element.value) 55 | removeWatch() 56 | } 57 | } 58 | 59 | return remove 60 | } 61 | -------------------------------------------------------------------------------- /src/composables/event/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import { tryOnMounted, tryOnUnmounted } from '@vueuse/core' 2 | import { debounce } from 'lodash-es' 3 | 4 | interface WindowSizeOptions { 5 | immediate?: boolean 6 | listenerOptions?: AddEventListenerOptions | boolean 7 | } 8 | 9 | export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { 10 | let handler = () => { 11 | fn() 12 | } 13 | const realHandler = debounce(handler, wait) 14 | handler = realHandler 15 | 16 | const start = () => { 17 | if (options && options.immediate) { 18 | handler() 19 | } 20 | window.addEventListener('resize', handler) 21 | } 22 | 23 | const stop = () => { 24 | window.removeEventListener('resize', handler) 25 | } 26 | 27 | tryOnMounted(() => { 28 | start() 29 | }) 30 | 31 | tryOnUnmounted(() => { 32 | stop() 33 | }) 34 | 35 | return [start, stop] 36 | } 37 | -------------------------------------------------------------------------------- /src/composables/setting/useHeaderSetting.ts: -------------------------------------------------------------------------------- 1 | import type { HeaderSetting } from '/#/config' 2 | 3 | import { computed, unref } from 'vue' 4 | import { useAppStore } from '/@/stores/modules/app' 5 | import { useMenuSetting } from './useMenuSetting' 6 | import { useRootSetting } from './useRootSetting' 7 | import { useFullContent } from '../web/useFullContent' 8 | 9 | export function useHeaderSetting() { 10 | const appStore = useAppStore() 11 | const { getFullContent } = useFullContent() 12 | const { getShowTopMenu } = useMenuSetting() 13 | const { getShowLogo } = useRootSetting() 14 | 15 | const getShowHeader = computed(() => !unref(getFullContent)) 16 | 17 | const getFixed = computed(() => appStore.getHeaderSetting.fixed) 18 | 19 | const getBgColor = computed(() => appStore.getHeaderSetting.bgColor) 20 | 21 | const getShowFullScreen = computed(() => appStore.getHeaderSetting.showFullScreen) 22 | 23 | const getHeaderTheme = computed(() => appStore.getHeaderSetting.theme) 24 | 25 | const getShowHeaderLogo = computed(() => unref(getShowLogo) && unref(getShowTopMenu)) 26 | 27 | /* set header */ 28 | function setHeaderSetting(headerSetting: Partial) { 29 | appStore.setProjectConfig({ headerSetting }) 30 | } 31 | 32 | return { 33 | setHeaderSetting, 34 | 35 | getShowHeader, 36 | getShowFullScreen, 37 | getBgColor, 38 | getFixed, 39 | getHeaderTheme, 40 | getShowHeaderLogo, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/composables/setting/useRootSetting.ts: -------------------------------------------------------------------------------- 1 | import type { ProjectConfig } from '/#/config' 2 | 3 | import { computed } from 'vue' 4 | import { useAppStore } from '/@/stores/modules/app' 5 | import { ThemeEnum } from '/@/enums/appEnum' 6 | 7 | type RootSetting = Omit< 8 | ProjectConfig, 9 | 'menuSetting' | 'elementUISetting' | 'headerSetting' | 'transitionSetting' 10 | > 11 | 12 | export function useRootSetting() { 13 | const appStore = useAppStore() 14 | 15 | const getGrayMode = computed(() => appStore.getProjectConfig.grayMode) 16 | 17 | const getShowBreadCrumb = computed(() => appStore.getProjectConfig.showBreadCrumb) 18 | 19 | const getShowFooter = computed(() => appStore.getProjectConfig.showFooter) 20 | 21 | const getThemeColor = computed(() => appStore.getProjectConfig.themeColor) 22 | 23 | const getColorWeak = computed(() => appStore.getProjectConfig.colorWeak) 24 | 25 | const getShowLogo = computed(() => appStore.getProjectConfig.showLogo) 26 | 27 | const getContentMode = computed(() => appStore.getProjectConfig.contentMode) 28 | 29 | const getShowSettingButton = computed(() => appStore.getProjectConfig.showSettingButton) 30 | 31 | const getFullContent = computed(() => appStore.getProjectConfig.fullContent) 32 | 33 | const getRemoveAllHttpPending = computed(() => appStore.getProjectConfig.removeAllHttpPending) 34 | 35 | const getTheme = computed(() => appStore.getDarkMode) 36 | 37 | const getUseOpenBackTop = computed(() => appStore.getProjectConfig.useOpenBackTop) 38 | 39 | const getOpenKeepAlive = computed(() => appStore.getProjectConfig.openKeepAlive) 40 | 41 | function setRootSetting(setting: Partial) { 42 | appStore.setProjectConfig(setting) 43 | } 44 | 45 | function setDarkMode(mode: ThemeEnum) { 46 | appStore.setDarkMode(mode) 47 | } 48 | 49 | return { 50 | setRootSetting, 51 | setDarkMode, 52 | 53 | getFullContent, 54 | getShowSettingButton, 55 | getContentMode, 56 | getRemoveAllHttpPending, 57 | getShowLogo, 58 | getColorWeak, 59 | getThemeColor, 60 | getGrayMode, 61 | getShowBreadCrumb, 62 | getShowFooter, 63 | getTheme, 64 | getUseOpenBackTop, 65 | getOpenKeepAlive, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/composables/setting/useTransitionSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { TransitionSetting } from '/#/config' 3 | import { useAppStore } from '/@/stores/modules/app' 4 | 5 | export function useTransitionSetting() { 6 | const appStore = useAppStore() 7 | 8 | const getEnableNProgress = computed(() => appStore.getTransitionSetting.enableNProgress) 9 | 10 | const getRouterTransition = computed(() => appStore.getTransitionSetting.routerTransition) 11 | 12 | const getEnableTransition = computed(() => appStore.getTransitionSetting.enable) 13 | 14 | function setTransitionSetting(transitionSetting: Partial) { 15 | appStore.setProjectConfig({ transitionSetting }) 16 | } 17 | 18 | return { 19 | setTransitionSetting, 20 | 21 | getEnableTransition, 22 | getRouterTransition, 23 | getEnableNProgress, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/composables/web/useFullContent.ts: -------------------------------------------------------------------------------- 1 | import { computed, unref } from 'vue' 2 | import { useRouter } from 'vue-router' 3 | import { useAppStore } from '/@/stores/modules/app' 4 | 5 | export function useFullContent() { 6 | const appStore = useAppStore() 7 | const router = useRouter() 8 | 9 | const { currentRoute } = router 10 | 11 | // Whether to display the content in full screen without displaying the menu 12 | const getFullContent = computed(() => { 13 | // Query parameters, the full screen is displayed when the address bar has a full parameter 14 | const route = unref(currentRoute) 15 | const query = route.query 16 | 17 | if (query && Reflect.has(query, '__full__')) { 18 | return true 19 | } 20 | 21 | // Return to the configuration in the configuration file 22 | return appStore.getProjectConfig.fullContent 23 | }) 24 | 25 | return { 26 | getFullContent, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/composables/web/useGo.ts: -------------------------------------------------------------------------------- 1 | import type { Router, RouteLocationRaw } from 'vue-router' 2 | 3 | import { useRouter } from 'vue-router' 4 | import { PageEnum } from '/@/enums/pageEnum' 5 | 6 | function handleError(e: Error) { 7 | console.error(e) 8 | } 9 | 10 | export function useGo(_router?: Router) { 11 | let router: Router | undefined 12 | if (!_router) { 13 | router = useRouter() 14 | } 15 | const { replace, push } = _router || router! 16 | 17 | function go(opt: PageEnum | string | RouteLocationRaw = PageEnum.Root, isReplace = false) { 18 | if (!opt) return 19 | 20 | isReplace ? replace(opt).catch(handleError) : push(opt).catch(handleError) 21 | } 22 | 23 | return go 24 | } 25 | -------------------------------------------------------------------------------- /src/composables/web/useMessage.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification, ElMessage, ElMessageBox } from 'element-plus' 2 | 3 | export function useMessage() { 4 | return { 5 | createNotification: ElNotification, 6 | createMessage: ElMessage, 7 | createMessageBox: ElMessageBox, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/composables/web/useRedo.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | 3 | import { unref } from 'vue' 4 | import { useRouter } from 'vue-router' 5 | import { RedirectRouteName } from '/@/enums/pageEnum' 6 | 7 | export function useRedo(_router?: Router) { 8 | const { replace, currentRoute } = _router || useRouter() 9 | const { query, fullPath, params = {}, name } = unref(currentRoute) 10 | 11 | function redo(): Promise { 12 | return new Promise((resolve) => { 13 | if (name === RedirectRouteName) { 14 | resolve(false) 15 | return 16 | } 17 | 18 | if (name && Object.keys(params).length > 0) { 19 | params['__redirect_type__'] = 'name' 20 | params['path'] = String(name) 21 | } else { 22 | params['__redirect_type__'] = 'path' 23 | params['path'] = fullPath 24 | } 25 | 26 | replace({ name: RedirectRouteName, params, query }).then(() => resolve(true)) 27 | }) 28 | } 29 | 30 | return redo 31 | } 32 | -------------------------------------------------------------------------------- /src/composables/web/useScript.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref } from 'vue' 2 | 3 | interface ScriptOptions { 4 | src: string 5 | id?: string 6 | } 7 | 8 | export function useScript(opt: ScriptOptions) { 9 | const isLoading = ref(false) 10 | const isSuccess = ref(false) 11 | 12 | let script: HTMLScriptElement 13 | 14 | const promise = new Promise((resolve, reject) => { 15 | onMounted(() => { 16 | script = document.createElement('script') 17 | script.type = 'text/javascript' 18 | script.onload = function () { 19 | isLoading.value = false 20 | isSuccess.value = true 21 | resolve('') 22 | } 23 | 24 | script.onerror = function (err) { 25 | isLoading.value = false 26 | isSuccess.value = false 27 | reject(err) 28 | } 29 | 30 | script.src = opt.src 31 | opt.id && (script.id = opt.id) 32 | 33 | isLoading.value = true 34 | document.head.appendChild(script) 35 | }) 36 | }) 37 | 38 | onUnmounted(() => { 39 | script && script.remove() 40 | }) 41 | 42 | return { 43 | isLoading, 44 | isSuccess, 45 | toPromise: () => promise, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/composables/web/useTitle.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'vue-router' 2 | import { useTitle as usePageTitle } from '@vueuse/core' 3 | import { unref, watch } from 'vue' 4 | import { useTransl } from '../core/useTransl' 5 | 6 | export function useTitle() { 7 | const { currentRoute } = useRouter() 8 | const pageTitle = usePageTitle() 9 | const { t } = useTransl() 10 | 11 | watch( 12 | () => currentRoute.value.path, 13 | () => { 14 | const route = unref(currentRoute) 15 | 16 | const name = t(route?.meta?.title || '') 17 | pageTitle.value = name ? `${name} - ${t('common.appName')}` : t('common.appName') 18 | }, 19 | { 20 | immediate: true, 21 | } 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/directives/permission.ts: -------------------------------------------------------------------------------- 1 | import type { App, Directive, DirectiveBinding } from 'vue' 2 | import { usePermission } from '/@/composables/core/usePermission' 3 | 4 | /** 5 | * @description Support .and directive modifiers 6 | * @usage 7 | * 8 | * 9 | */ 10 | const directive: Directive = { 11 | mounted: (el: Element, binding: DirectiveBinding) => { 12 | const { hasPermission } = usePermission() 13 | 14 | const { value, modifiers } = binding 15 | 16 | // and or to compare 17 | let nor: 'or' | 'and' = 'or' 18 | if (modifiers.and) { 19 | nor = 'and' 20 | } 21 | 22 | if (!hasPermission(value, nor)) { 23 | el.parentNode?.removeChild(el) 24 | } 25 | }, 26 | } 27 | 28 | export function setupPermissionDirective(app: App) { 29 | app.directive('permission', directive) 30 | } 31 | 32 | export default directive 33 | -------------------------------------------------------------------------------- /src/directives/setupGlobalDirectives.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | 3 | import { setupPermissionDirective } from '/@/directives/permission' 4 | 5 | /** 6 | * Configure and register global directives 7 | */ 8 | export function setupGlobalDirectives(app: App) { 9 | setupPermissionDirective(app) 10 | } 11 | -------------------------------------------------------------------------------- /src/enums/appEnum.ts: -------------------------------------------------------------------------------- 1 | // sidebar 菜单折叠模式时宽度值 2 | export const SIDE_BAR_COLLAPSED_WIDTH = 64 3 | 4 | // 定宽模式下时的宽度设定值 5 | export const APP_CONTENT_FIXED_WIDTH = 1200 6 | 7 | // menu theme enum 8 | export enum ThemeEnum { 9 | DARK = 'dark', 10 | LIGHT = 'light', 11 | } 12 | 13 | // 夜间模式下的mix color,如果覆写了element-plus中的值也需要一并修改这个值 14 | /** 15 | * // Background 16 | * $bg-color: () !default; 17 | * $bg-color: map.merge( 18 | * ( 19 | * 'page': #0a0a0a, 20 | * '': #141414, // 与该值关联 21 | * 'overlay': #1d1e1f, 22 | * ), 23 | * $bg-color 24 | * ); 25 | */ 26 | export const DARK_MODE_MIX_COLOR = '#141414' 27 | 28 | // Route switching animation 29 | export enum RouterTransitionEnum { 30 | FADE = 'fade', 31 | ZOOM_FADE = 'zoom-fade', 32 | ZOOM_OUT = 'zoom-out', 33 | FADE_SIDE = 'fade-slide', 34 | FADE_BOTTOM = 'fade-bottom', 35 | FADE_SCALE = 'fade-scale', 36 | } 37 | 38 | export enum ContentEnum { 39 | FULL = 'full', 40 | FIXED = 'fixed', 41 | } 42 | 43 | export enum PermissionModeEnum { 44 | /** 45 | * 角色模式 46 | * 在前端固定写死路由的权限,指定路由有哪些权限可以查看。 47 | * 只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。 48 | * 在登陆后或者其他方式获取用户角色后,通过角色去遍历路由表,获取该角色可以访问的路由表,生成路由表, 49 | * 再通过 router.addRoutes 添加到路由实例,实现权限的过滤。 50 | */ 51 | ROLE = 'ROLE', 52 | 53 | /** 54 | * 后台动态模式,配合后端 55 | * 是通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构, 56 | * 再通过 router.addRoutes 添加到路由实例,实现权限的动态生成。 57 | */ 58 | BACK = 'BACK', 59 | } 60 | -------------------------------------------------------------------------------- /src/enums/breakpointEnum.ts: -------------------------------------------------------------------------------- 1 | export enum SizeEnum { 2 | XS = 'XS', 3 | SM = 'SM', 4 | MD = 'MD', 5 | LG = 'LG', 6 | XL = 'XL', 7 | } 8 | 9 | export enum ScreenEnum { 10 | XS = 480, 11 | SM = 768, 12 | MD = 992, 13 | LG = 1200, 14 | XL = 1920, 15 | } 16 | 17 | const screenMap = new Map() 18 | 19 | screenMap.set(SizeEnum.XS, ScreenEnum.XS) 20 | screenMap.set(SizeEnum.SM, ScreenEnum.SM) 21 | screenMap.set(SizeEnum.MD, ScreenEnum.MD) 22 | screenMap.set(SizeEnum.LG, ScreenEnum.LG) 23 | screenMap.set(SizeEnum.XL, ScreenEnum.XL) 24 | 25 | export { screenMap } 26 | -------------------------------------------------------------------------------- /src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token 2 | export const KEY_TOKEN = '__TOKEN' 3 | 4 | // locale 5 | export const KEY_LOCALE = '__LOCALE' 6 | 7 | export const KEY_SETTING = '__APP_SETTING' 8 | 9 | export const KEY_APP_DARK_MODE = '__APP_DARK_MODE__' 10 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Request result set 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | TIMEOUT = 401, 7 | TYPE = 'success', 8 | /** 9 | * @description server error 10 | */ 11 | ERROR = 500, 12 | 13 | /** 14 | * @description token error related 15 | */ 16 | TOKEN_INVALID = 1026, 17 | } 18 | 19 | /** 20 | * @description: request method 21 | */ 22 | export enum RequestEnum { 23 | GET = 'GET', 24 | POST = 'POST', 25 | PUT = 'PUT', 26 | DELETE = 'DELETE', 27 | } 28 | 29 | /** 30 | * @description: contentType 31 | */ 32 | export enum ContentTypeEnum { 33 | // json 34 | JSON = 'application/json;charset=UTF-8', 35 | // form-data qs 36 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 37 | // form-data upload 38 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 39 | } 40 | -------------------------------------------------------------------------------- /src/enums/menuEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 菜单类型 3 | */ 4 | export enum MenuTypeEnum { 5 | /** 6 | * 目录 7 | */ 8 | Catalogue = 0, 9 | 10 | /** 11 | * 菜单 12 | */ 13 | Menu = 1, 14 | 15 | /** 16 | * 权限 17 | */ 18 | Permission = 2, 19 | } 20 | 21 | /** 22 | * 菜单模式 23 | */ 24 | export enum MenuModeEnum { 25 | /** 26 | * 侧边栏模式 27 | */ 28 | SIDEBAR = 'sidebar', 29 | 30 | /** 31 | * 顶部菜单模式 32 | */ 33 | TOP_MENU = 'top_menu', 34 | } 35 | 36 | /** 37 | * 折叠菜单触发器位置 38 | */ 39 | export enum MenuTriggerEnum { 40 | /** 41 | * 不显示 42 | */ 43 | NONE = 'none', 44 | 45 | /** 46 | * 底部 47 | */ 48 | BOTTOM = 'bottom', 49 | 50 | /** 51 | * 顶部 52 | */ 53 | TOP = 'top', 54 | } 55 | -------------------------------------------------------------------------------- /src/enums/pageEnum.ts: -------------------------------------------------------------------------------- 1 | export const NotFoundRouteName = Symbol('NotFound') 2 | 3 | export const RedirectRouteName = Symbol('RedirectTo') 4 | 5 | export enum PageEnum { 6 | Root = '/', 7 | Login = '/login', 8 | Dashboard = '/dashboard', 9 | NotFound = '/404', 10 | Forbidden = '/403', 11 | Account = '/account', 12 | Logout = '/logout', 13 | Error = '/error', 14 | } 15 | -------------------------------------------------------------------------------- /src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | /** 3 | * 超级管理员 4 | */ 5 | ROOT, 6 | 7 | /** 8 | * 运营 9 | */ 10 | OPERATE, 11 | } 12 | -------------------------------------------------------------------------------- /src/enums/typeEnum.ts: -------------------------------------------------------------------------------- 1 | export enum StatusTypeEnum { 2 | /** 3 | * 失败、禁用 4 | */ 5 | Failure = 0, 6 | Disable = 0, 7 | 8 | /** 9 | * 成功、启用 10 | */ 11 | Successful = 1, 12 | Enable = 1, 13 | } 14 | -------------------------------------------------------------------------------- /src/layouts/default/content/getTransitionName.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocation } from 'vue-router' 2 | 3 | export function getTransitionName({ 4 | enableTransition, 5 | def, 6 | route, 7 | }: { 8 | enableTransition: boolean 9 | def: string 10 | route: RouteLocation 11 | }): string | undefined { 12 | if (!enableTransition) { 13 | return undefined 14 | } 15 | return route.meta?.transitionName || def 16 | } 17 | -------------------------------------------------------------------------------- /src/layouts/default/content/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 49 | 50 | 60 | -------------------------------------------------------------------------------- /src/layouts/default/content/useLayoutHeight.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | const appHeaderHeightRef = ref(0) 4 | const appFooterHeightRef = ref(0) 5 | 6 | export function useLayoutHeight() { 7 | function setAppHeaderHeight(val: number) { 8 | appHeaderHeightRef.value = val 9 | } 10 | 11 | function setAppFooterHeight(val: number) { 12 | appFooterHeightRef.value = val 13 | } 14 | 15 | return { setAppHeaderHeight, setAppFooterHeight, appFooterHeightRef, appHeaderHeightRef } 16 | } 17 | -------------------------------------------------------------------------------- /src/layouts/default/feature/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 28 | 29 | 52 | -------------------------------------------------------------------------------- /src/layouts/default/footer/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | 30 | 43 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/FullScreen.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/Redo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/UserDropdown.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 56 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/index.ts: -------------------------------------------------------------------------------- 1 | import FullScreen from './FullScreen.vue' 2 | import UserDropdown from './UserDropdown.vue' 3 | import Redo from './Redo.vue' 4 | 5 | export { FullScreen, UserDropdown, Redo } 6 | -------------------------------------------------------------------------------- /src/layouts/default/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | 34 | 53 | -------------------------------------------------------------------------------- /src/layouts/default/menu/components/MenuItem.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 36 | 37 | 75 | -------------------------------------------------------------------------------- /src/layouts/default/menu/components/Trigger.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | 28 | 42 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/SelectItem.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 47 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/SettingFooter.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/SwitchItem.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 42 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/ThemeColorPicker.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 33 | -------------------------------------------------------------------------------- /src/layouts/default/sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | 35 | 47 | -------------------------------------------------------------------------------- /src/layouts/empty/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/layouts/iframe/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 36 | -------------------------------------------------------------------------------- /src/locales/helper.ts: -------------------------------------------------------------------------------- 1 | import type { LocaleType } from '/#/config' 2 | 3 | import { set } from 'lodash-es' 4 | 5 | // loaded locale pool 6 | export const loadLocalePool: LocaleType[] = [] 7 | 8 | export function setHtmlPageLang(lang: LocaleType) { 9 | document.querySelector('html')?.setAttribute('lang', lang) 10 | } 11 | 12 | export function genMessage(modules: Recordable, lang: string) { 13 | const message: Recordable = {} 14 | 15 | Object.keys(modules).forEach((key) => { 16 | const langModule = modules[key].default 17 | 18 | // use file name to namespace 19 | let filename = key.replace(`./${lang}/`, '') 20 | const lastIndex = filename.lastIndexOf('.') 21 | // remove file suffix 22 | filename = filename.substring(0, lastIndex) 23 | const keyList = filename.split('/') // '/' is not exist will empty array 24 | const moduleName = keyList.shift() 25 | const objKey = keyList.join('.') 26 | 27 | // module name is not undef 28 | if (moduleName) { 29 | if (objKey) { 30 | // set namespace 31 | set(message, moduleName, message[moduleName] || {}) 32 | // set ns module 33 | set(message[moduleName], objKey, langModule) 34 | } else { 35 | set(message, moduleName, langModule) 36 | } 37 | } 38 | }) 39 | 40 | return message 41 | } 42 | -------------------------------------------------------------------------------- /src/locales/lang/en.ts: -------------------------------------------------------------------------------- 1 | import type { LangModule } from '../typing' 2 | 3 | import { genMessage } from '../helper' 4 | import eleLocale from 'element-plus/lib/locale/lang/en' 5 | import dayjsLocale from 'dayjs/locale/en' 6 | 7 | const modules = import.meta.glob('./en/**/*.ts', { eager: true }) as Recordable> 8 | 9 | export default { 10 | message: { 11 | ...genMessage(modules, 'en'), 12 | eleLocale, 13 | dayjsLocale, 14 | }, 15 | } as LangModule 16 | -------------------------------------------------------------------------------- /src/locales/lang/en/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | appName: 'Ark Admin', 3 | 4 | // http util 5 | http: { 6 | errorTip: 'Error tip', 7 | requestFailed: 'The interface request failed, please try again later!', 8 | requestTimeout: 'The interface request timed out, please refresh the page and try again!', 9 | networkException: 'Please check if your network connection is normal! The network is abnormal', 10 | 11 | errMsg401: 'The authorization has expired!', 12 | errMsg403: 'Insufficient privileges!', 13 | errMsg404: 'Network request error, the resource was not found!', 14 | errMsg405: 'Network request error, request method not allowed!', 15 | errMsg408: 'Network request timed out!', 16 | errMsg500: 'Server error, please contact the administrator!', 17 | errMsg503: 'The service is unavailable, the server is temporarily overloaded or maintained!', 18 | }, 19 | 20 | // exception view 21 | exception: { 22 | backHome: 'Back Home', 23 | noDataTitle: 'No data', 24 | subTitle403: 'Sorry, you don\'t have access to this page.', 25 | subTitle404: 'Sorry, the page you visited does not exist.', 26 | subTitle500: 'Sorry, the server is reporting an error.', 27 | networkErrorTitle: 'Network Error', 28 | networkErrorSubTitle: 29 | 'Sorry, Your network connection has been disconnected, please check your network!', 30 | }, 31 | 32 | // login page 33 | login: { 34 | oslink: 'Github Link', 35 | signin: 'Sign in', 36 | account: 'Username', 37 | passwd: 'Password', 38 | captcha: 'Captcha', 39 | }, 40 | 41 | // common basic words 42 | basic: { 43 | warning: 'Warning', 44 | add: 'Add', 45 | update: 'Update', 46 | delete: 'Delete', 47 | query: 'Query', 48 | save: 'Save', 49 | reset: 'Reset', 50 | search: 'Search', 51 | operation: 'Operation', 52 | edit: 'Edit', 53 | confirm: 'Confirm', 54 | submit: 'Submit', 55 | sort: 'Sort', 56 | 57 | show: 'Show', 58 | hidden: 'Hidden', 59 | disabled: 'Disabled', 60 | enable: 'Enable', 61 | 62 | status: 'Status', 63 | remark: 'Remark', 64 | 65 | ok: 'Ok', 66 | cancel: 'Cancel', 67 | close: 'Close', 68 | redo: 'Refresh', 69 | back: 'Back', 70 | loading: 'Loading...', 71 | 72 | male: 'Male', 73 | female: 'Female', 74 | secrecy: 'Secrecy', 75 | }, 76 | } 77 | -------------------------------------------------------------------------------- /src/locales/lang/en/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | form: { 3 | invalid: 'invalid ', 4 | enter: 'Please enter ', 5 | choose: 'Please choose ', 6 | }, 7 | 8 | icon: { 9 | placeholder: 'Click to select icon', 10 | searchPlaceholder: 'Search icon', 11 | }, 12 | 13 | table: { 14 | toolbar: { 15 | desityCompact: 'Compact', 16 | desityDefault: 'Default', 17 | desityLoose: 'Loose', 18 | }, 19 | index: 'Index', 20 | confirmMsg: 'Sure perform operations?', 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /src/locales/lang/en/layout.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | setting: { 3 | clearCache: 'Clear cache and return to login', 4 | resetSuccess: 'Reset succeeded, the refresh must take effect', 5 | on: 'On', 6 | off: 'Off', 7 | 8 | // drawer 9 | title: 'Configuration', 10 | darkMode: 'Dark mode', 11 | navMode: 'Navigation mode', 12 | sysTheme: 'System theme', 13 | headerTheme: 'Header theme', 14 | sidebarTheme: 'Menu theme', 15 | interfaceFunction: 'Interface function', 16 | interfaceDisplay: 'Interface display', 17 | animation: 'Animation', 18 | 19 | // menu type 20 | menuTypeSidebar: 'Left menu mode', 21 | menuTypeTop: 'Top menu mode', 22 | 23 | // top menu type 24 | topMenuTypeCenter: 'Center', 25 | topMenuTypeLeft: 'Left', 26 | topMenuTypeRight: 'Right', 27 | 28 | // content mode 29 | contentModeFull: 'Full', 30 | contentModeFixed: 'Fixed', 31 | 32 | // menu trigger 33 | menuTriggerNone: 'Not Show', 34 | menuTriggerBottom: 'Bottom', 35 | menuTriggerTop: 'Top', 36 | 37 | // interface function 38 | menuCollapse: 'Collapse menu', 39 | menuAccordion: 'Sidebar accordion', 40 | fixedHeader: 'Fixed header', 41 | menuCollapseButton: 'Menu collapse button', 42 | topMenuLayout: 'Top menu layout', 43 | contentAreaWidth: 'Contect area width', 44 | 45 | // interface display 46 | logo: 'Logo', 47 | footer: 'Footer', 48 | fullContent: 'Full content', 49 | grayMode: 'Gray mode', 50 | colorWeakMode: 'Color weak mode', 51 | 52 | // animation 53 | progress: 'Progress', 54 | switchAnimation: 'Switch Animation', 55 | switchAnimationType: 'Switch Animation Type', 56 | }, 57 | 58 | header: { 59 | userDropdown: { 60 | accountSetting: 'Account setting', 61 | logout: 'Login out', 62 | }, 63 | }, 64 | } 65 | -------------------------------------------------------------------------------- /src/locales/lang/en/routes.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // basic 3 | login: 'Login', 4 | dashboard: 'Dashboard', 5 | notfound: 'Page not found', 6 | redirect: 'Redirecting...', 7 | account: 'Personal center', 8 | } 9 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN.ts: -------------------------------------------------------------------------------- 1 | import type { LangModule } from '../typing' 2 | 3 | import { genMessage } from '../helper' 4 | import eleLocale from 'element-plus/lib/locale/lang/zh-cn' 5 | import dayjsLocale from 'dayjs/locale/zh-cn' 6 | 7 | const modules = import.meta.glob('./zh_CN/**/*.ts', { eager: true }) as Recordable> 8 | 9 | export default { 10 | message: { 11 | ...genMessage(modules, 'zh_CN'), 12 | eleLocale, 13 | dayjsLocale, 14 | }, 15 | } as LangModule 16 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | appName: '方舟管理后台', 3 | 4 | // http util 5 | http: { 6 | errorTip: '错误提示', 7 | requestFailed: '请求出错,请稍候重试!', 8 | requestTimeout: '接口请求超时,请刷新页面重试!', 9 | networkException: '网络异常,请检查您的网络连接是否正常!', 10 | 11 | errMsg401: '授权已失效!', 12 | errMsg403: '权限不足!', 13 | errMsg404: '网络请求错误,未找到该资源!', 14 | errMsg405: '网络请求错误,请求方法未允许!', 15 | errMsg408: '网络请求超时!', 16 | errMsg500: '服务器错误,请联系管理员!', 17 | errMsg503: '服务不可用,服务器暂时过载或维护!', 18 | }, 19 | 20 | // exception view 21 | exception: { 22 | backHome: '返回首页', 23 | noDataTitle: '无数据', 24 | subTitle403: '抱歉,您无权访问此页面。', 25 | subTitle404: '抱歉,您访问的页面不存在。', 26 | subTitle500: '抱歉,服务器报告错误。', 27 | networkErrorTitle: '网络错误', 28 | networkErrorSubTitle: '抱歉,您的网络连接已断开,请检查您的网络!', 29 | }, 30 | 31 | // login page 32 | login: { 33 | oslink: '开源地址', 34 | signin: '登录', 35 | account: '用户名', 36 | passwd: '密码', 37 | captcha: '验证码', 38 | }, 39 | 40 | // common basic words 41 | basic: { 42 | warning: '警告', 43 | add: '新增', 44 | update: '更新', 45 | delete: '删除', 46 | query: '查询', 47 | save: '保存', 48 | reset: '重置', 49 | search: '搜索', 50 | operation: '操作', 51 | edit: '编辑', 52 | confirm: '确定', 53 | submit: '提交', 54 | sort: '排序', 55 | 56 | show: '显示', 57 | hidden: '隐藏', 58 | disabled: '禁用', 59 | enable: '启用', 60 | 61 | status: '状态', 62 | remark: '备注', 63 | 64 | ok: '好', 65 | cancel: '取消', 66 | close: '关闭', 67 | redo: '刷新', 68 | back: '返回', 69 | loading: '加载中...', 70 | 71 | male: '男', 72 | female: '女', 73 | secrecy: '保密', 74 | }, 75 | } 76 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | form: { 3 | invalid: '无效的', 4 | enter: '请输入', 5 | choose: '请选择', 6 | }, 7 | 8 | icon: { 9 | placeholder: '点击选择图标', 10 | searchPlaceholder: '搜索图标', 11 | }, 12 | 13 | table: { 14 | toolbar: { 15 | desityCompact: '紧凑', 16 | desityDefault: '默认', 17 | desityLoose: '宽松', 18 | }, 19 | index: '序号', 20 | confirmMsg: '确定执行操作?', 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN/layout.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | setting: { 3 | clearCache: '清空缓存并返回登录', 4 | resetSuccess: '重置成功,需刷新生效', 5 | on: '开', 6 | off: '关', 7 | 8 | // drawer 9 | title: '项目配置', 10 | darkMode: '夜间模式', 11 | navMode: '导航模式', 12 | sysTheme: '系统主题', 13 | headerTheme: '顶栏主题', 14 | sidebarTheme: '菜单主题', 15 | interfaceFunction: '界面功能', 16 | interfaceDisplay: '界面显示', 17 | animation: '动画', 18 | 19 | // menu type 20 | menuTypeSidebar: '左侧菜单模式', 21 | menuTypeTop: '顶部菜单模式', 22 | 23 | // top menu type 24 | topMenuTypeCenter: '居中', 25 | topMenuTypeLeft: '居左', 26 | topMenuTypeRight: '居右', 27 | 28 | // content mode 29 | contentModeFull: '流式', 30 | contentModeFixed: '定宽', 31 | 32 | // menu trigger 33 | menuTriggerNone: '不显示', 34 | menuTriggerBottom: '底部', 35 | menuTriggerTop: '顶部', 36 | 37 | // interface function config name 38 | menuCollapse: '折叠菜单', 39 | menuAccordion: '侧边菜单手风琴模式', 40 | fixedHeader: '固定顶栏', 41 | menuCollapseButton: '菜单折叠按钮', 42 | topMenuLayout: '顶部菜单布局', 43 | contentAreaWidth: '内容区域宽度', 44 | 45 | // interface display 46 | logo: 'Logo', 47 | footer: '页脚', 48 | fullContent: '全屏内容', 49 | grayMode: '灰色模式', 50 | colorWeakMode: '色弱模式', 51 | 52 | // animation 53 | progress: '顶栏进度条', 54 | switchAnimation: '切换动画', 55 | switchAnimationType: '切换动画类型', 56 | }, 57 | 58 | header: { 59 | userDropdown: { 60 | accountSetting: '账号设置', 61 | logout: '退出系统', 62 | }, 63 | }, 64 | } 65 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN/routes.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // basic 3 | login: '登录', 4 | dashboard: '工作台', 5 | notfound: '无法找到此页面', 6 | redirect: '跳转中...', 7 | account: '个人中心', 8 | } 9 | -------------------------------------------------------------------------------- /src/locales/setupI18n.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import type { I18nOptions, I18n } from 'vue-i18n' 3 | 4 | import { createI18n } from 'vue-i18n' 5 | import { localeSetting } from '../settings/localeSetting' 6 | import { useLocaleStore } from '../stores/modules/locale' 7 | import { loadLocalePool, setHtmlPageLang } from './helper' 8 | 9 | // global i18n instance, replace to useI18n 10 | export let i18n: I18n 11 | 12 | async function createI18nOptions(): Promise { 13 | const { fallback, availableLocales } = localeSetting 14 | const localeStore = useLocaleStore() 15 | 16 | const locale = localeStore.getLocale 17 | 18 | // init default lang 19 | const defaultLocale = await import(`./lang/${locale}.ts`) 20 | const message = defaultLocale.default?.message || {} 21 | setHtmlPageLang(locale) 22 | loadLocalePool.push(locale) 23 | 24 | return { 25 | legacy: false, // must be false 26 | locale, 27 | messages: { 28 | [locale]: message, 29 | }, 30 | fallbackLocale: fallback, 31 | availableLocales, 32 | sync: false, 33 | silentTranslationWarn: true, 34 | missingWarn: true, 35 | silentFallbackWarn: true, 36 | } 37 | } 38 | 39 | export async function setupI18n(app: App) { 40 | const options = await createI18nOptions() 41 | i18n = createI18n(options) 42 | app.use(i18n) 43 | } 44 | -------------------------------------------------------------------------------- /src/locales/typing.ts: -------------------------------------------------------------------------------- 1 | interface LocaleMessage extends Recordable { 2 | // element-ui locale config 3 | eleLocale: any 4 | 5 | // dayjs locale config 6 | dayjsLocale: any 7 | } 8 | 9 | export interface LangModule { 10 | message: LocaleMessage 11 | } 12 | -------------------------------------------------------------------------------- /src/locales/useLocale.ts: -------------------------------------------------------------------------------- 1 | import type { LangModule } from './typing' 2 | import type { LocaleType } from '/#/config' 3 | 4 | import { computed, unref } from 'vue' 5 | import { useLocaleStore } from '/@/stores/modules/locale' 6 | import { loadLocalePool, setHtmlPageLang } from './helper' 7 | import { i18n } from './setupI18n' 8 | 9 | export function useLocale() { 10 | const localeStore = useLocaleStore() 11 | 12 | const getLocale = computed(() => localeStore.getLocale) 13 | 14 | const getShowPicker = computed(() => localeStore.getShowPicker) 15 | 16 | const getEleLocale = computed((): any => { 17 | return i18n.global.getLocaleMessage(unref(getLocale))?.eleLocale ?? {} 18 | }) 19 | 20 | const getDayjsLocale = computed((): any => { 21 | return i18n.global.getLocaleMessage(unref(getLocale))?.dayjsLocale ?? {} 22 | }) 23 | 24 | function setI18nLang(locale: LocaleType) { 25 | // check mode is legacy or composition 26 | if (typeof i18n.global.locale === 'string') { 27 | i18n.global.locale = locale 28 | } else { 29 | i18n.global.locale.value = locale 30 | } 31 | 32 | // update store 33 | localeStore.setLocaleInfo({ locale }) 34 | 35 | // update html lang 36 | setHtmlPageLang(locale) 37 | } 38 | 39 | // switch the lang will change the locale of useI18n 40 | async function changeLocale(locale: LocaleType) { 41 | const globalI18n = i18n.global 42 | 43 | const currentLocale = unref(globalI18n.locale) 44 | 45 | // nothing change 46 | if (currentLocale === locale) { 47 | return 48 | } 49 | 50 | // is loaded 51 | if (loadLocalePool.includes(locale)) { 52 | setI18nLang(locale) 53 | return 54 | } 55 | 56 | // load module and set locale 57 | const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule 58 | if (!langModule) return 59 | 60 | const { message } = langModule 61 | globalI18n.setLocaleMessage(locale, message) 62 | loadLocalePool.push(locale) 63 | 64 | setI18nLang(locale) 65 | } 66 | 67 | return { 68 | getLocale, 69 | getShowPicker, 70 | getEleLocale, 71 | getDayjsLocale, 72 | changeLocale, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/logics/initAppConfig.ts: -------------------------------------------------------------------------------- 1 | import type { ProjectConfig } from '/#/config' 2 | 3 | import { useAppStore } from '/@/stores/modules/app' 4 | import { useUserStore } from '/@/stores/modules/user' 5 | import defaultSetting from '/@/settings/projectSetting' 6 | import { getToken } from '/@/utils/auth' 7 | import { updateTheme } from './theme/updateTheme' 8 | import { updateGrayMode } from './theme/updateGrayMode' 9 | import { updateColorWeak } from './theme/updateColorWeak' 10 | import { updateHeaderBgColor, updateSidebarBgColor } from './theme/updateBackground' 11 | import { KEY_SETTING } from '/@/enums/cacheEnum' 12 | import { merge } from 'lodash-es' 13 | import { updateDarkMode } from './theme/updateDarkMode' 14 | import { ThemeEnum } from '../enums/appEnum' 15 | import { useLocaleStore } from '../stores/modules/locale' 16 | import WebStorage from '/@/utils/cache' 17 | 18 | /** 19 | * Initial project configuration 20 | */ 21 | export function initAppConfig() { 22 | const appStore = useAppStore() 23 | const userStore = useUserStore() 24 | const localeStore = useLocaleStore() 25 | 26 | // setup global config 27 | try { 28 | const config = WebStorage.get(KEY_SETTING, {}) as ProjectConfig 29 | appStore.setProjectConfig(merge({}, defaultSetting, config)) 30 | } catch (err) { 31 | appStore.setProjectConfig(defaultSetting) 32 | } 33 | 34 | const darkMode = appStore.getDarkMode 35 | 36 | const { 37 | grayMode, 38 | colorWeak, 39 | themeColor, 40 | menuSetting: { bgColor }, 41 | headerSetting: { bgColor: headerbgColor }, 42 | } = appStore.getProjectConfig 43 | 44 | // update primary theme color 45 | updateTheme(themeColor) 46 | 47 | if (darkMode === ThemeEnum.DARK) { 48 | // 夜间模式 49 | updateDarkMode(darkMode) 50 | 51 | // 更新夜间模式下的配置,以防被手动修改了非夜间模式不支持的背景设置 52 | updateSidebarBgColor() 53 | updateHeaderBgColor() 54 | } else { 55 | // update background 56 | bgColor && updateSidebarBgColor(bgColor) 57 | headerbgColor && updateHeaderBgColor(headerbgColor) 58 | } 59 | 60 | // root class 61 | grayMode && updateGrayMode(grayMode) 62 | colorWeak && updateColorWeak(colorWeak) 63 | 64 | // init locale 65 | localeStore.initLocale() 66 | 67 | // setup user config 68 | userStore.setToken(getToken()) 69 | } 70 | -------------------------------------------------------------------------------- /src/logics/theme/updateColorWeak.ts: -------------------------------------------------------------------------------- 1 | import { toggleClass } from '/@/utils/dom' 2 | 3 | /** 4 | * Change project color weak status 5 | * @param gray 6 | */ 7 | export function updateColorWeak(weak: boolean) { 8 | toggleClass(weak, 'color-weak', document.documentElement) 9 | } 10 | -------------------------------------------------------------------------------- /src/logics/theme/updateDarkMode.ts: -------------------------------------------------------------------------------- 1 | import { ThemeEnum } from '/@/enums/appEnum' 2 | import { toggleClass } from '/@/utils/dom' 3 | 4 | /** 5 | * 设置暗色主题模式 6 | */ 7 | export function updateDarkMode(theme: ThemeEnum) { 8 | toggleClass(theme === ThemeEnum.DARK, 'dark', document.documentElement) 9 | } 10 | -------------------------------------------------------------------------------- /src/logics/theme/updateGrayMode.ts: -------------------------------------------------------------------------------- 1 | import { toggleClass } from '/@/utils/dom' 2 | 3 | /** 4 | * Change project gray mode status 5 | * @param gray 6 | */ 7 | export function updateGrayMode(gray: boolean) { 8 | toggleClass(gray, 'gray-mode', document.documentElement) 9 | } 10 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import '@unocss/reset/tailwind.css' 2 | import 'element-plus/dist/index.css' 3 | import 'element-plus/theme-chalk/dark/css-vars.css' 4 | import 'uno.css' 5 | import '/@/styles/index.scss' 6 | 7 | import { createApp } from 'vue' 8 | import { registerGlobalComp } from '/@/components/registerGlobalComp' 9 | import { setupGlobalDirectives } from '/@/directives/setupGlobalDirectives' 10 | import { initAppConfig } from '/@/logics/initAppConfig' 11 | import { router, setupRouter } from '/@/router' 12 | import { setupRouterGuard } from '/@/router/guard' 13 | import { setupStore } from '/@/stores' 14 | import { setupI18n } from '/@/locales/setupI18n' 15 | import App from './App.vue' 16 | 17 | async function bootstrap() { 18 | const app = createApp(App) 19 | 20 | // store 21 | setupStore(app) 22 | 23 | // init appconfig 24 | initAppConfig() 25 | 26 | // register global component and lib 27 | registerGlobalComp(app) 28 | 29 | // setup i18n 30 | await setupI18n(app) 31 | 32 | // setup router 33 | setupRouter(app) 34 | 35 | // setup router guard 36 | setupRouterGuard(router) 37 | 38 | // global directive 39 | setupGlobalDirectives(app) 40 | 41 | // mount 42 | app.mount('#app') 43 | } 44 | 45 | bootstrap() 46 | -------------------------------------------------------------------------------- /src/router/contants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description default layout 3 | */ 4 | export const ParentLayout = () => import('/@/layouts/default/index.vue') 5 | 6 | /** 7 | * @description iframe layout 8 | */ 9 | export const IFrameLayout = () => import('/@/layouts/iframe/index.vue') 10 | 11 | /** 12 | * @description empty layout 13 | */ 14 | export const EmptyLayout = () => import('/@/layouts/empty/index.vue') 15 | 16 | /** 17 | * @description exception comp 18 | */ 19 | export const ExceptionComponent = () => import('/@/views/basic/exception/Exception.vue') 20 | -------------------------------------------------------------------------------- /src/router/guard/httpGuard.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | 3 | import projectSetting from '/@/settings/projectSetting' 4 | import { AxiosCanceler } from '/@/utils/http/axios/axiosCanceler' 5 | 6 | export function createHttpGuard(router: Router) { 7 | const { removeAllHttpPending } = projectSetting 8 | 9 | let axiosCanceler: Nullable 10 | 11 | if (removeAllHttpPending) { 12 | axiosCanceler = new AxiosCanceler() 13 | } 14 | 15 | router.beforeEach(() => { 16 | // 切换路由将删除所有的请求 17 | axiosCanceler?.removeAllPending() 18 | return true 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/router/guard/index.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | import { createHttpGuard } from './httpGuard' 3 | import { createMessageGuard } from './messageGuard' 4 | import { createPermissionGuard } from './permissionGuard' 5 | import { createProgressGuard } from './progressGuard' 6 | import { createStateGuard } from './stateGuard' 7 | 8 | export function setupRouterGuard(router: Router) { 9 | createHttpGuard(router) 10 | createMessageGuard(router) 11 | createProgressGuard(router) 12 | createPermissionGuard(router) 13 | createStateGuard(router) 14 | } 15 | -------------------------------------------------------------------------------- /src/router/guard/messageGuard.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | import { useMessage } from '/@/composables/web/useMessage' 3 | import projectSetting from '/@/settings/projectSetting' 4 | import { warn } from '/@/utils/log' 5 | 6 | export function createMessageGuard(router: Router) { 7 | const { closeMessageOnSwitch } = projectSetting 8 | const { createMessage, createNotification } = useMessage() 9 | 10 | router.beforeEach(() => { 11 | try { 12 | if (closeMessageOnSwitch) { 13 | createMessage.closeAll() 14 | createNotification.closeAll() 15 | } 16 | } catch (err) { 17 | warn('MessageGuard Error:' + err) 18 | } 19 | return true 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /src/router/guard/progressGuard.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | 3 | import NProgress from 'nprogress' 4 | import { useTransitionSetting } from '/@/composables/setting/useTransitionSetting' 5 | import { unref } from 'vue' 6 | 7 | export function createProgressGuard(router: Router) { 8 | const { getEnableNProgress } = useTransitionSetting() 9 | NProgress.configure({ showSpinner: false }) 10 | 11 | router.beforeEach(() => { 12 | unref(getEnableNProgress) && NProgress.start() 13 | return true 14 | }) 15 | 16 | router.afterEach(() => { 17 | unref(getEnableNProgress) && NProgress.done() 18 | return true 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/router/guard/stateGuard.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | import { PageEnum } from '/@/enums/pageEnum' 3 | import { usePermissionStore } from '/@/stores/modules/permission' 4 | import { useUserStore } from '/@/stores/modules/user' 5 | 6 | export function createStateGuard(router: Router) { 7 | router.afterEach((to) => { 8 | // 进入登录页面后确保清除所有信息 9 | if (to.path === PageEnum.Login) { 10 | const userStore = useUserStore() 11 | const permissionStore = usePermissionStore() 12 | 13 | userStore.resetState() 14 | permissionStore.resetState() 15 | } 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/router/helper/defineModule.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | export function defineRoleModule( 4 | module: RouteRecordRaw | RouteRecordRaw[] 5 | ): RouteRecordRaw | RouteRecordRaw[] { 6 | return module 7 | } 8 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import { createRouter, createWebHistory } from 'vue-router' 3 | 4 | import { basicRoutes } from './routes/basic' 5 | 6 | /** 7 | * 白名单应该包含基本静态路由 8 | */ 9 | const WHITE_NAME_LIST: string[] = [] 10 | const getRouteNames = (array: any[]) => 11 | array.forEach((item) => { 12 | if (item.name) { 13 | WHITE_NAME_LIST.push(item.name) 14 | } 15 | getRouteNames(item.children || []) 16 | }) 17 | getRouteNames([...basicRoutes]) 18 | 19 | export const router = createRouter({ 20 | history: createWebHistory(), 21 | routes: basicRoutes, 22 | strict: true, 23 | scrollBehavior: () => ({ left: 0, top: 0 }), 24 | }) 25 | 26 | export function resetRouter() { 27 | router.getRoutes().forEach((route) => { 28 | const { name } = route 29 | if (name && !WHITE_NAME_LIST.includes(name as string)) { 30 | if (router.hasRoute(name)) router.removeRoute(name) 31 | } 32 | }) 33 | } 34 | 35 | export async function setupRouter(app: App) { 36 | app.use(router) 37 | } 38 | -------------------------------------------------------------------------------- /src/router/routes/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | import type { Component } from '../typing' 3 | 4 | import { warn } from '/@/utils/log' 5 | 6 | export const backModuleMap: Record = {} 7 | export const roleRoutes: RouteRecordRaw[] = [] 8 | 9 | const roleModules: Recordable = import.meta.glob('./modules/**/*.ts', { eager: true }) 10 | 11 | /** 12 | * 角色权限路由时使用 13 | */ 14 | Object.keys(roleModules).forEach((key) => { 15 | const mod = roleModules[key].default 16 | if (!mod) { 17 | warn(`导出的${key}模块为空`) 18 | return 19 | } 20 | const modList = Array.isArray(mod) ? [...mod] : [mod] 21 | roleRoutes.push(...modList) 22 | }) 23 | -------------------------------------------------------------------------------- /src/router/routes/modules/system.ts: -------------------------------------------------------------------------------- 1 | import { IFrameLayout, ParentLayout } from '../../contants' 2 | import { defineRoleModule } from '../../helper/defineModule' 3 | // import { RoleEnum } from '/@/enums/roleEnum' 4 | 5 | export default defineRoleModule([ 6 | { 7 | path: '/docs', 8 | name: 'docs', 9 | component: ParentLayout, 10 | meta: { 11 | single: true, 12 | }, 13 | children: [ 14 | { 15 | path: '/docs/typeorm', 16 | name: 'docstypeorm', 17 | component: IFrameLayout, 18 | meta: { 19 | title: '官方文档(内嵌)', 20 | icon: 'carbon:application-web', 21 | iframeSrc: 'https://docs.arklnk.com/', 22 | }, 23 | }, 24 | ], 25 | }, 26 | ]) 27 | -------------------------------------------------------------------------------- /src/router/typing.ts: -------------------------------------------------------------------------------- 1 | import type { defineComponent } from 'vue' 2 | 3 | export type Component = 4 | | ReturnType 5 | | (() => Promise) 6 | | (() => Promise) 7 | 8 | export interface Menu { 9 | id: number 10 | parentId?: number 11 | name: string 12 | router: string 13 | type: number 14 | icon: string 15 | orderNum?: number 16 | viewPath?: string 17 | isShow?: boolean 18 | activeRouter?: string 19 | children?: Menu[] 20 | } 21 | -------------------------------------------------------------------------------- /src/settings/componentSetting.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | table: { 3 | pagination: { 4 | pageSizes: [50, 80, 100, 200], 5 | pageSize: 50, 6 | layout: 'total, sizes, prev, pager, next', 7 | background: true, 8 | small: true, 9 | }, 10 | fetchSetting: { 11 | pageField: 'page', 12 | sizeField: 'limit', 13 | listField: 'list', 14 | totalField: 'pagination.total', 15 | }, 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /src/settings/designSetting.ts: -------------------------------------------------------------------------------- 1 | import { ThemeEnum } from '../enums/appEnum' 2 | 3 | export const prefixCls = 'ark' 4 | 5 | export const themeMode = ThemeEnum.LIGHT 6 | 7 | // app theme preset color 8 | export const APP_PRESET_COLOR_LIST: string[] = [ 9 | '#1c8efe', 10 | '#0960bd', 11 | '#0084f4', 12 | '#009688', 13 | '#536dfe', 14 | '#ff5c93', 15 | '#ee4f12', 16 | '#0096c7', 17 | '#9c27b0', 18 | '#ff9800', 19 | ] 20 | 21 | // header preset color 22 | export const HEADER_PRESET_BG_COLOR_LIST: string[] = [ 23 | '#ffffff', 24 | '#151515', 25 | '#009688', 26 | '#5172DC', 27 | '#018ffb', 28 | '#409eff', 29 | '#e74c3c', 30 | '#24292e', 31 | '#394664', 32 | '#001529', 33 | '#383f45', 34 | ] 35 | 36 | // sider preset color 37 | export const SIDE_BAR_BG_COLOR_LIST: string[] = [ 38 | '#212121', 39 | '#001529', 40 | '#273352', 41 | '#ffffff', 42 | '#191b24', 43 | '#191a23', 44 | '#304156', 45 | '#001628', 46 | '#28333E', 47 | '#344058', 48 | '#383f45', 49 | ] 50 | -------------------------------------------------------------------------------- /src/settings/encryptionSetting.ts: -------------------------------------------------------------------------------- 1 | // AES Cipher key 2 | export const CipherKey = '_12345678900987654321@' 3 | 4 | // AES Cipher iv 5 | export const CipherIv = '@12345678900987654321_' 6 | -------------------------------------------------------------------------------- /src/settings/localeSetting.ts: -------------------------------------------------------------------------------- 1 | import type { LocaleSetting, LocaleType } from '/#/config' 2 | 3 | export const LOCALE: { [key: string]: LocaleType } = { 4 | EN: 'en', 5 | ZH_CN: 'zh_CN', 6 | } 7 | 8 | export const localeSetting: LocaleSetting = { 9 | showPicker: false, 10 | locale: LOCALE.ZH_CN, 11 | fallback: LOCALE.ZH_CN, 12 | availableLocales: [LOCALE.EN, LOCALE.ZH_CN], 13 | } 14 | 15 | export const localeList: LabelValueOptions = [ 16 | { 17 | label: '简体中文', 18 | value: LOCALE.ZH_CN, 19 | }, 20 | { 21 | label: 'English', 22 | value: LOCALE.EN, 23 | }, 24 | ] 25 | -------------------------------------------------------------------------------- /src/settings/projectSetting.ts: -------------------------------------------------------------------------------- 1 | import type { ProjectConfig } from '/#/config' 2 | 3 | import { ContentEnum, PermissionModeEnum, RouterTransitionEnum, ThemeEnum } from '/@/enums/appEnum' 4 | import { MenuModeEnum, MenuTriggerEnum } from '/@/enums/menuEnum' 5 | import { 6 | APP_PRESET_COLOR_LIST, 7 | HEADER_PRESET_BG_COLOR_LIST, 8 | SIDE_BAR_BG_COLOR_LIST, 9 | } from './designSetting' 10 | 11 | const setting: ProjectConfig = { 12 | grayMode: false, 13 | colorWeak: false, 14 | showBreadCrumb: true, 15 | showFooter: false, 16 | showLogo: true, 17 | showSettingButton: true, 18 | fullContent: false, 19 | useOpenBackTop: true, 20 | showDarkModeToggle: true, 21 | themeColor: APP_PRESET_COLOR_LIST[0], 22 | contentMode: ContentEnum.FULL, 23 | // 权限路由配置:Back模式为后端动态生成,Role模式为纯前端 24 | permissionMode: PermissionModeEnum.BACK, 25 | removeAllHttpPending: false, 26 | closeMessageOnSwitch: false, 27 | openKeepAlive: true, 28 | menuSetting: { 29 | collapsed: false, 30 | uniqueOpened: false, 31 | menuWidth: 256, 32 | bgColor: SIDE_BAR_BG_COLOR_LIST[0], 33 | theme: ThemeEnum.DARK, 34 | menuMode: MenuModeEnum.SIDEBAR, 35 | topMenuAlign: 'flex-start', 36 | trigger: MenuTriggerEnum.TOP, 37 | }, 38 | headerSetting: { 39 | fixed: true, 40 | bgColor: HEADER_PRESET_BG_COLOR_LIST[0], 41 | theme: ThemeEnum.LIGHT, 42 | showFullScreen: true, 43 | }, 44 | elementUISetting: { 45 | size: 'default', 46 | zIndex: 2000, 47 | button: { 48 | autoInsertSpace: true, 49 | }, 50 | message: { 51 | max: 5, 52 | }, 53 | }, 54 | transitionSetting: { 55 | enable: true, 56 | enableNProgress: true, 57 | routerTransition: RouterTransitionEnum.FADE, 58 | }, 59 | } 60 | 61 | export default setting 62 | -------------------------------------------------------------------------------- /src/settings/siteSetting.ts: -------------------------------------------------------------------------------- 1 | // github repo url 2 | export const VUE_REPO_GITHUB_URL = 'https://github.com/arklnk/ark-admin-vuenext' 3 | export const ZERO_REPO_GITHUB_URL = 'https://github.com/arklnk/ark-admin-zero' 4 | export const NEST_REPO_GITHUB_URL = 'https://github.com/arklnk/ark-admin-nest' 5 | export const DOC_SITE = 'https://docs.arklnk.com' 6 | // site url 7 | export const SITE_URL = 'http://opensource.admin.si-yee.com' 8 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import { createPinia } from 'pinia' 3 | 4 | const store = createPinia() 5 | 6 | export function setupStore(app: App) { 7 | app.use(store) 8 | } 9 | 10 | export { store } 11 | -------------------------------------------------------------------------------- /src/stores/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { defineStore, _DeepPartial } from 'pinia' 2 | import type { HeaderSetting, MenuSetting, TransitionSetting, ProjectConfig } from '/#/config' 3 | import { merge } from 'lodash-es' 4 | import { KEY_SETTING, KEY_APP_DARK_MODE } from '/@/enums/cacheEnum' 5 | import { ThemeEnum } from '/@/enums/appEnum' 6 | import { themeMode } from '/@/settings/designSetting' 7 | import WebStorage from '/@/utils/cache' 8 | 9 | interface AppState { 10 | projectConfig: Nullable 11 | darkMode: Nullable 12 | } 13 | 14 | export const useAppStore = defineStore({ 15 | id: 'app', 16 | state: (): AppState => ({ 17 | projectConfig: null, 18 | darkMode: null, 19 | }), 20 | getters: { 21 | getDarkMode(): ThemeEnum | string { 22 | // index.html中需要获取该值,只允许使用localStorage获取 23 | return this.darkMode || localStorage.getItem(KEY_APP_DARK_MODE) || themeMode 24 | }, 25 | getProjectConfig(): ProjectConfig { 26 | return this.projectConfig || ({} as ProjectConfig) 27 | }, 28 | getMenuSetting(): MenuSetting { 29 | return this.getProjectConfig.menuSetting 30 | }, 31 | getHeaderSetting(): HeaderSetting { 32 | return this.getProjectConfig.headerSetting 33 | }, 34 | getTransitionSetting(): TransitionSetting { 35 | return this.getProjectConfig.transitionSetting 36 | }, 37 | }, 38 | actions: { 39 | setDarkMode(mode: ThemeEnum) { 40 | this.darkMode = mode 41 | // index.html中需要获取该值,只允许使用localStorage设置 42 | localStorage.setItem(KEY_APP_DARK_MODE, mode) 43 | }, 44 | setProjectConfig(config: DeepPartial): void { 45 | this.projectConfig = merge(this.projectConfig || {}, config) as ProjectConfig 46 | // store 47 | WebStorage.set(KEY_SETTING, this.getProjectConfig) 48 | }, 49 | }, 50 | }) 51 | -------------------------------------------------------------------------------- /src/stores/modules/locale.ts: -------------------------------------------------------------------------------- 1 | import type { LocaleSetting, LocaleType } from '/#/config' 2 | 3 | import { defineStore } from 'pinia' 4 | import { KEY_LOCALE } from '/@/enums/cacheEnum' 5 | import { localeSetting } from '/@/settings/localeSetting' 6 | import WebStorage from '/@/utils/cache' 7 | 8 | interface LocaleState { 9 | localeInfo: LocaleSetting 10 | } 11 | 12 | export const useLocaleStore = defineStore({ 13 | id: 'app-locale', 14 | state: (): LocaleState => ({ 15 | localeInfo: WebStorage.get(KEY_LOCALE, {})!, 16 | }), 17 | getters: { 18 | getShowPicker(): boolean { 19 | return !!this.localeInfo?.showPicker 20 | }, 21 | getLocale(): LocaleType { 22 | return this.localeInfo.locale ?? 'zh_CN' 23 | }, 24 | }, 25 | actions: { 26 | setLocaleInfo(info: Partial) { 27 | this.localeInfo = { ...this.localeInfo, ...info } 28 | WebStorage.set(KEY_LOCALE, this.localeInfo) 29 | }, 30 | /** 31 | * init and load existing config 32 | */ 33 | initLocale() { 34 | this.setLocaleInfo({ 35 | ...localeSetting, 36 | ...this.localeInfo, 37 | }) 38 | }, 39 | }, 40 | }) 41 | -------------------------------------------------------------------------------- /src/stores/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { merge } from 'lodash-es' 2 | import { defineStore } from 'pinia' 3 | import { usePermissionStore } from './permission' 4 | import { getUserInfo, userLogout as logoutRequest } from '/@/api/basic' 5 | import { RoleEnum } from '/@/enums/roleEnum' 6 | import { resetRouter } from '/@/router' 7 | import { setToken as setLocalToken, removeToken } from '/@/utils/auth' 8 | 9 | interface UserState { 10 | token: string 11 | userInfo: Nullable 12 | roleList: RoleEnum[] 13 | } 14 | 15 | interface UserInfo { 16 | username: string 17 | avatar: string 18 | } 19 | 20 | export const useUserStore = defineStore({ 21 | id: 'app-user', 22 | state: (): UserState => { 23 | return { 24 | token: '', 25 | userInfo: null, 26 | roleList: [], 27 | } 28 | }, 29 | getters: { 30 | getToken(): string { 31 | return this.token 32 | }, 33 | getUserInfo(): Nullable { 34 | return this.userInfo 35 | }, 36 | getRoleList(): RoleEnum[] { 37 | return this.roleList 38 | }, 39 | }, 40 | actions: { 41 | setToken(token: string) { 42 | // storage token 43 | setLocalToken(token) 44 | // store token 45 | this.token = token 46 | }, 47 | setRoleList(roleList: RoleEnum[]): void { 48 | this.roleList = roleList 49 | }, 50 | async getUserInfoAction(): Promise { 51 | const data = await getUserInfo() 52 | this.userInfo = { 53 | username: data!.username, 54 | avatar: data!.avatar, 55 | } 56 | // 角色列表(远程获取,这里Mock了假数据) 57 | this.setRoleList([RoleEnum.ROOT]) 58 | }, 59 | async logout(): Promise { 60 | // can fail 61 | await logoutRequest() 62 | 63 | const permissionStore = usePermissionStore() 64 | this.resetState() 65 | permissionStore.resetState() 66 | 67 | resetRouter() 68 | }, 69 | updateUserInfo(info: Partial) { 70 | this.userInfo = merge(this.userInfo, info) 71 | }, 72 | resetState(): void { 73 | // remove storage token 74 | removeToken() 75 | // store 76 | this.token = '' 77 | this.userInfo = null 78 | this.roleList = [] 79 | }, 80 | }, 81 | }) 82 | -------------------------------------------------------------------------------- /src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | .el-drawer { 2 | .el-drawer__header { 3 | margin-bottom: 0px; 4 | } 5 | } 6 | 7 | // ============================================== 8 | // =======移除chrome自动填充form表单的默认值======= 9 | // ============================================== 10 | .el-input__inner:-webkit-autofill, 11 | .el-input__inner:-webkit-autofill:hover, 12 | .el-input__inner:-webkit-autofill:focus, 13 | .el-input__inner:-webkit-autofill:active { 14 | box-shadow: none !important; 15 | } 16 | 17 | // ============================================== 18 | // ===============修改Tab默认样式================= 19 | // ============================================== 20 | .el-tabs { 21 | &.el-tabs--top { 22 | // 移除top/bottom模式时被移除的padding值 23 | .el-tabs__item.is-top, 24 | .el-tabs__item.is-bottom { 25 | &:nth-child(2), 26 | &:last-child { 27 | padding: 0 20px; 28 | } 29 | } 30 | } 31 | 32 | .el-tabs__nav-wrap { 33 | &::after { 34 | height: 1px; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @use './var.scss'; 2 | @use './public.scss'; 3 | @use './transition.scss'; 4 | @use './element-ui.scss'; 5 | @use './mixins.scss'; 6 | 7 | input:-webkit-autofill { 8 | box-shadow: 0 0 0 1000px white inset !important; 9 | } 10 | 11 | :-webkit-autofill { 12 | transition: background-color 5000s ease-in-out 0s !important; 13 | } 14 | 15 | html { 16 | overflow: hidden; 17 | text-size-adjust: 100%; 18 | } 19 | 20 | html, 21 | body { 22 | width: 100%; 23 | height: 100%; 24 | overflow: visible !important; 25 | overflow-x: hidden !important; 26 | 27 | &.gray-mode { 28 | filter: grayscale(100%); 29 | filter: progid:dximagetransform.microsoft.basicimage(grayscale=1); 30 | } 31 | 32 | &.color-weak { 33 | filter: invert(80%); 34 | } 35 | } 36 | 37 | a, 38 | a:active, 39 | a:focus, 40 | div:focus, 41 | button, 42 | div, 43 | svg, 44 | span { 45 | outline: none !important; 46 | text-decoration: none !important; 47 | -webkit-tap-highlight-color: transparent; 48 | } 49 | -------------------------------------------------------------------------------- /src/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | // BEM Source by https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/mixins/mixins.scss 2 | 3 | @mixin when($state) { 4 | @at-root { 5 | &.#{$state} { 6 | @content; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/styles/public.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | // ================================= 7 | // ==============scrollbar========== 8 | // ================================= 9 | 10 | ::-webkit-scrollbar { 11 | width: 7px; 12 | height: 8px; 13 | border-radius: 4px; 14 | } 15 | 16 | // ::-webkit-scrollbar-track { 17 | // background: transparent; 18 | // } 19 | 20 | ::-webkit-scrollbar-track { 21 | background-color: rgb(0 0 0 / 5%); 22 | } 23 | 24 | ::-webkit-scrollbar-thumb { 25 | // background: rgba(0, 0, 0, 0.6); 26 | background-color: rgb(144 147 153 / 30%); 27 | // background-color: rgba(144, 147, 153, 0.3); 28 | border-radius: 2px; 29 | box-shadow: inset 0 0 6px rgb(0 0 0 / 20%); 30 | } 31 | 32 | ::-webkit-scrollbar-thumb:hover { 33 | background-color: #b6b7b9; 34 | } 35 | 36 | // ================================= 37 | // ==============nprogress========== 38 | // ================================= 39 | #nprogress { 40 | pointer-events: none; 41 | 42 | .bar { 43 | position: fixed; 44 | top: 0; 45 | left: 0; 46 | z-index: 99999; 47 | width: 100%; 48 | height: 2px; 49 | background-color: var(--el-color-primary); 50 | opacity: 80%; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // fade-bottom 2 | .fade-bottom-enter-active, 3 | .fade-bottom-leave-active { 4 | transition: opacity 0.5s, transform 0.5s; 5 | } 6 | 7 | .fade-bottom-enter-from { 8 | opacity: 0%; 9 | transform: translateY(-10%); 10 | } 11 | 12 | .fade-bottom-leave-to { 13 | opacity: 0%; 14 | transform: translateY(10%); 15 | } 16 | 17 | // fade 18 | .fade-enter-active, 19 | .fade-leave-active { 20 | transition: opacity 0.2s ease-in-out; 21 | } 22 | 23 | .fade-enter-from, 24 | .fade-leave-to { 25 | opacity: 0%; 26 | } 27 | 28 | // fade-slide 29 | .fade-slide-leave-active, 30 | .fade-slide-enter-active { 31 | transition: all 0.3s; 32 | } 33 | 34 | .fade-slide-enter-from { 35 | opacity: 0%; 36 | transform: translateX(-30px); 37 | } 38 | 39 | .fade-slide-leave-to { 40 | opacity: 0%; 41 | transform: translateX(30px); 42 | } 43 | 44 | // fade-scale 45 | .fade-scale-leave-active, 46 | .fade-scale-enter-active { 47 | transition: all 0.28s; 48 | } 49 | 50 | .fade-scale-enter-from { 51 | opacity: 0%; 52 | transform: scale(1.2); 53 | } 54 | 55 | .fade-scale-leave-to { 56 | opacity: 0%; 57 | transform: scale(0.8); 58 | } 59 | 60 | // zoom-out 61 | .zoom-out-enter-active, 62 | .zoom-out-leave-active { 63 | transition: opacity 0.1 ease-in-out, transform 0.15s ease-out; 64 | } 65 | 66 | .zoom-out-enter-from, 67 | .zoom-out-leave-to { 68 | opacity: 0%; 69 | transform: scale(0); 70 | } 71 | 72 | // zoom-fade 73 | .zoom-fade-enter-active, 74 | .zoom-fade-leave-active { 75 | transition: transform 0.2s, opacity 0.3s ease-out; 76 | } 77 | 78 | .zoom-fade-enter-from { 79 | opacity: 0%; 80 | transform: scale(0.92); 81 | } 82 | 83 | .zoom-fade-leave-to { 84 | opacity: 0%; 85 | transform: scale(1.06); 86 | } 87 | -------------------------------------------------------------------------------- /src/styles/var.scss: -------------------------------------------------------------------------------- 1 | // bem 2 | $namespace: 'ark'; 3 | $element-separator: '__'; 4 | $modifier-separator: '--'; 5 | $state-prefix: 'is-'; 6 | 7 | // layout 8 | $header-height: 50px; 9 | $app-bg-color: #f0f2f5; 10 | $app-dark-bg-color: #2a2c2c; 11 | 12 | //basic color 13 | $color-white: #ffffff; 14 | $color-black: #000000; 15 | 16 | // transition 17 | $transition-duration: 0.3s; 18 | 19 | // css var 20 | :root { 21 | // sidebar bg 22 | --sidebar-bg-color: #{$color-white}; 23 | --sidebar-darken-bg-color: #{$color-white}; 24 | 25 | // header 26 | --header-bg-color: #{$color-white}; 27 | --header-hover-bg-color: #{$color-white}; 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | import WebStorage from './cache' 2 | import { KEY_TOKEN } from '/@/enums/cacheEnum' 3 | 4 | export function getToken(): string { 5 | return WebStorage.get(KEY_TOKEN, '', true) || '' 6 | } 7 | 8 | export function setToken(token: string) { 9 | WebStorage.set(KEY_TOKEN, token, null, true) 10 | } 11 | 12 | export function removeToken() { 13 | WebStorage.remove(KEY_TOKEN) 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/cache/index.ts: -------------------------------------------------------------------------------- 1 | import { createStorage } from './storage' 2 | import { prefixCls } from '/@/settings/designSetting' 3 | 4 | export const WebStorage = createStorage({ storage: localStorage, prefixKey: `${prefixCls}` }) 5 | 6 | export default WebStorage 7 | -------------------------------------------------------------------------------- /src/utils/cipher.ts: -------------------------------------------------------------------------------- 1 | import type CryptoJS from 'crypto-js' 2 | 3 | import { MD5, enc, mode, AES, pad } from 'crypto-js' 4 | 5 | export function createAESCipherOptions(iv?: string) { 6 | let wordIv: CryptoJS.lib.WordArray | undefined 7 | if (iv) { 8 | wordIv = enc.Utf8.parse(iv) 9 | } 10 | return { 11 | iv: wordIv, 12 | mode: mode.CFB, 13 | padding: pad.AnsiX923, 14 | } 15 | } 16 | 17 | export function encryptByAES(text: string, key: string, options?: Recordable) { 18 | return AES.encrypt(text, key, options).toString() 19 | } 20 | 21 | export function decryptByAES(text: string, key: string, options?: Recordable) { 22 | return AES.decrypt(text, key, options).toString(enc.Utf8) 23 | } 24 | 25 | export function encryptByMD5(text: string) { 26 | return MD5(text).toString() 27 | } 28 | 29 | export function encodeByBase64(text: string) { 30 | return enc.Utf8.parse(text).toString(enc.Base64) 31 | } 32 | 33 | export function decodeByBase64(decode: string) { 34 | return enc.Base64.parse(decode).toString() 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | import { isDate } from 'lodash-es' 3 | 4 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' 5 | const DATE_FORMAT = 'YYYY-MM-DD' 6 | 7 | export function formatToDateTime( 8 | date: string | number | Date | dayjs.Dayjs | null | undefined = undefined, 9 | format = DATE_TIME_FORMAT 10 | ): string { 11 | return dayjs(date).format(format) 12 | } 13 | 14 | export function formatToDate( 15 | date: string | number | Date | dayjs.Dayjs | null | undefined = undefined, 16 | format = DATE_FORMAT 17 | ): string { 18 | return dayjs(date).format(format) 19 | } 20 | 21 | export function isDateObject(obj: unknown): boolean { 22 | return isDate(obj) || dayjs.isDayjs(obj) 23 | } 24 | 25 | export const dateUtil = dayjs 26 | -------------------------------------------------------------------------------- /src/utils/env.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Development mode 3 | */ 4 | export const devMode = 'development' 5 | 6 | /** 7 | * @description: Production mode 8 | */ 9 | export const prodMode = 'production' 10 | 11 | /** 12 | * @description: Get environment variables 13 | * @returns: 14 | * @example: 15 | */ 16 | export function getEnv(): string { 17 | return import.meta.env.MODE 18 | } 19 | 20 | /** 21 | * @description: Is it a development mode 22 | * @returns: 23 | * @example: 24 | */ 25 | export function isDevMode(): boolean { 26 | return import.meta.env.DEV 27 | } 28 | 29 | /** 30 | * @description: Is it a production mode 31 | * @returns: 32 | * @example: 33 | */ 34 | export function isProdMode(): boolean { 35 | return import.meta.env.PROD 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/helper/tsx.ts: -------------------------------------------------------------------------------- 1 | import type { Slots } from 'vue' 2 | 3 | import { isFunction } from 'lodash-es' 4 | 5 | export function getSlot(slots: Readonly, name = 'default', data?: any) { 6 | if (!slots || !Reflect.has(slots, name)) { 7 | return null 8 | } 9 | 10 | const slotFn = slots[name] 11 | if (!slotFn) return 12 | if (!isFunction(slotFn)) return 13 | 14 | return slotFn(data) 15 | } 16 | 17 | export function extendSlots(slots: Slots, excludeKeys: string[] = []) { 18 | const slotKeys = Object.keys(slots) 19 | 20 | const ret: Recordable = {} 21 | slotKeys.forEach((key) => { 22 | if (excludeKeys.includes(key)) return 23 | 24 | ret[key] = (scope?: any) => getSlot(slots, key, scope) 25 | }) 26 | 27 | return ret 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/http/axios/axiosCanceler.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosRequestConfig, Canceler } from 'axios' 2 | import axios from 'axios' 3 | 4 | // 请求集合 5 | let pendingMap = new Map() 6 | 7 | export function getPendingUrl(config: AxiosRequestConfig): string { 8 | return [config.method, config.url].join('&') 9 | } 10 | 11 | export class AxiosCanceler { 12 | /** 13 | * add request 14 | * @param config 15 | */ 16 | addPending(config: AxiosRequestConfig) { 17 | this.removePending(config) 18 | 19 | const url = getPendingUrl(config) 20 | config.cancelToken = 21 | config.cancelToken || 22 | new axios.CancelToken((cancel) => { 23 | if (!pendingMap.has(url)) { 24 | // 如果没有挂起当前的请求则添加 25 | pendingMap.set(url, cancel) 26 | } 27 | }) 28 | } 29 | 30 | /** 31 | * remove request 32 | * @param config 33 | */ 34 | removePending(config: AxiosRequestConfig) { 35 | const url = getPendingUrl(config) 36 | 37 | if (pendingMap.has(url)) { 38 | // 如果挂起中有当前请求标识符 39 | // 取消并删除当前请求 40 | const cancel = pendingMap.get(url) 41 | 42 | cancel && cancel(url) 43 | pendingMap.delete(url) 44 | } 45 | } 46 | 47 | /** 48 | * remove all pending request 49 | */ 50 | removeAllPending() { 51 | pendingMap.forEach((cancel, url) => { 52 | cancel && cancel(url) 53 | }) 54 | 55 | pendingMap.clear() 56 | } 57 | 58 | /** 59 | * reset 60 | */ 61 | reset() { 62 | pendingMap = new Map() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/http/axios/axiosRetry.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosError, AxiosInstance } from 'axios' 2 | import type { CreateAxiosOptions } from './sAxios' 3 | 4 | export class AxiosRetry { 5 | retry(axiosInstance: AxiosInstance, error: AxiosError) { 6 | const config = error.response?.config as CreateAxiosOptions & { __retryCount: number } 7 | const { waitTime = 100, count = 3 } = config?.requestOptions?.retryRequest || {} 8 | // init 9 | config.__retryCount = config.__retryCount || 0 10 | 11 | if (config.__retryCount < count) { 12 | config.__retryCount += 1 13 | 14 | // ignore promise error 15 | return this.delay(waitTime) 16 | .then(() => axiosInstance(config)) 17 | .catch(() => {}) 18 | } 19 | } 20 | 21 | private delay(waitTime: number) { 22 | return new Promise((resove) => setTimeout(resove, waitTime)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/http/axios/axiosTransform.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' 2 | import type { RequestOptions, Result } from '/#/axios' 3 | 4 | export abstract class AxiosTransform { 5 | /** 6 | * 请求前配置 7 | */ 8 | beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig 9 | 10 | /** 11 | * 请求成功处理 12 | */ 13 | transformResponseHook?: (res: AxiosResponse, options: RequestOptions) => any 14 | 15 | /** 16 | * 请求失败处理 17 | */ 18 | requestCatchHook?: (e: Error, options: RequestOptions) => Promise 19 | 20 | /** 21 | * 请求拦截器 22 | */ 23 | requestInterceptors?: ( 24 | config: AxiosRequestConfig, 25 | options: Nullable 26 | ) => AxiosRequestConfig 27 | 28 | /** 29 | * 请求拦截器错误处理 30 | */ 31 | requestInterceptorsCatch?: (e: Error) => void 32 | 33 | /** 34 | * 响应拦截器 35 | */ 36 | responseInterceptors?: (res: AxiosResponse) => AxiosResponse 37 | 38 | /** 39 | * 响应拦截器错误处理 40 | */ 41 | responseInterceptorsCatch?: (res: AxiosInstance, e: Error) => void 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/http/axios/helper.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | import type { Dayjs } from 'dayjs' 3 | import { isObject, isString } from 'lodash-es' 4 | 5 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' 6 | 7 | export function joinTimestamp(join: boolean, restful = false): string | object { 8 | if (!join) { 9 | return restful ? {} : '' 10 | } 11 | 12 | const now = new Date().getTime() 13 | 14 | return restful ? `?_t=${now}` : { _t: now } 15 | } 16 | 17 | /** 18 | * 将时间对象转换成统一的时间格式 19 | */ 20 | export function formatRequestDate(params: Recordable) { 21 | if (Object.prototype.toString.call(params) !== '[object Object]') { 22 | return 23 | } 24 | 25 | for (const key in params) { 26 | if (params[key] && dayjs.isDayjs(params[key])) { 27 | params[key] = (params[key] as Dayjs).format(DATE_TIME_FORMAT) 28 | } 29 | 30 | if (params[key] && isString(params[key])) { 31 | const value = params[key] as string 32 | params[key] = value.trim() 33 | } 34 | 35 | //递归 36 | if (isObject(params[key])) { 37 | formatRequestDate(params[key]) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/is.ts: -------------------------------------------------------------------------------- 1 | export const isServer = typeof window === 'undefined' 2 | 3 | export function isUrl(path: string): boolean { 4 | return /^(https?:|mailto:|tel:)/.test(path) 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/lib/echarts.ts: -------------------------------------------------------------------------------- 1 | // https://echarts.apache.org/zh/api.html#echarts.use 2 | import * as echarts from 'echarts/core' 3 | 4 | /** 5 | * svg vs canvas 6 | * https://echarts.apache.org/handbook/zh/best-practices/canvas-vs-svg 7 | */ 8 | import { SVGRenderer } from 'echarts/renderers' 9 | 10 | // 组件 11 | import { 12 | TitleComponent, 13 | TooltipComponent, 14 | ToolboxComponent, 15 | LegendComponent, 16 | PolarComponent, 17 | DatasetComponent, 18 | VisualMapComponent, 19 | TransformComponent, 20 | GridComponent, 21 | CalendarComponent, 22 | DataZoomComponent, 23 | TimelineComponent, 24 | AriaComponent, 25 | AxisPointerComponent, 26 | } from 'echarts/components' 27 | 28 | // 图表 29 | import { 30 | BarChart, 31 | LineChart, 32 | PieChart, 33 | ScatterChart, 34 | RadarChart, 35 | GaugeChart, 36 | MapChart, 37 | PictorialBarChart, 38 | } from 'echarts/charts' 39 | 40 | // 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。 41 | // 注册必须的组件 42 | echarts.use([ 43 | // render 44 | SVGRenderer, 45 | 46 | // component 47 | TitleComponent, 48 | TooltipComponent, 49 | ToolboxComponent, 50 | LegendComponent, 51 | PolarComponent, 52 | DatasetComponent, 53 | VisualMapComponent, 54 | TransformComponent, 55 | GridComponent, 56 | CalendarComponent, 57 | DataZoomComponent, 58 | TimelineComponent, 59 | AriaComponent, 60 | AxisPointerComponent, 61 | 62 | // charts 63 | BarChart, 64 | LineChart, 65 | PieChart, 66 | ScatterChart, 67 | RadarChart, 68 | GaugeChart, 69 | MapChart, 70 | PictorialBarChart, 71 | ]) 72 | 73 | export default echarts 74 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | import { isProdMode } from './env' 2 | 3 | export function info(message: string) { 4 | if (isProdMode()) return 5 | console.info(`[info]: ${message}`) 6 | } 7 | 8 | export function warn(message: string) { 9 | if (isProdMode()) return 10 | console.warn(`[warn]: ${message}`) 11 | } 12 | 13 | export function error(message: string) { 14 | throw new Error(`[error]: ${message}`) 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/uuid.ts: -------------------------------------------------------------------------------- 1 | const hexList: string[] = [] 2 | for (let i = 0; i <= 15; i++) { 3 | hexList[i] = i.toString(16) 4 | } 5 | 6 | export function buildUUID(): string { 7 | let uuid = '' 8 | for (let i = 1; i <= 36; i++) { 9 | if (i === 9 || i === 14 || i === 19 || i === 24) { 10 | uuid += '-' 11 | } else if (i === 15) { 12 | uuid += 4 13 | } else if (i === 20) { 14 | uuid += hexList[(Math.random() * 4) | 8] 15 | } else { 16 | uuid += hexList[(Math.random() * 16) | 0] 17 | } 18 | } 19 | return uuid.replace(/-/g, '') 20 | } 21 | 22 | let unique = 0 23 | export function buildShortUUID(prefix = ''): string { 24 | const time = Date.now() 25 | const random = Math.floor(Math.random() * 1000000000) 26 | unique++ 27 | return prefix + '_' + random + unique + String(time) 28 | } 29 | -------------------------------------------------------------------------------- /src/views/basic/account/Account.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /src/views/basic/dashboard/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 40 | -------------------------------------------------------------------------------- /src/views/basic/login/Login.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 33 | 34 | 53 | -------------------------------------------------------------------------------- /src/views/basic/redirect/Redirect.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 32 | -------------------------------------------------------------------------------- /src/views/config/dict/DictValueType.ts: -------------------------------------------------------------------------------- 1 | // 1文本 2数字 3数组 4单选 5多选 6下拉 7日期 8时间 9单图 10多图 11单文件 12多文件 2 | export const DictValueTypes: LabelValueOptions = [ 3 | { 4 | label: '文本', 5 | value: 1, 6 | }, 7 | { 8 | label: '数字', 9 | value: 2, 10 | }, 11 | { 12 | label: '数组', 13 | value: 3, 14 | }, 15 | { 16 | label: '单选框', 17 | value: 4, 18 | }, 19 | { 20 | label: '复选框', 21 | value: 5, 22 | }, 23 | { 24 | label: '下拉框', 25 | value: 6, 26 | }, 27 | { 28 | label: '日期', 29 | value: 7, 30 | }, 31 | { 32 | label: '时间', 33 | value: 8, 34 | }, 35 | { 36 | label: '单个图片', 37 | value: 9, 38 | }, 39 | { 40 | label: '多个图片', 41 | value: 10, 42 | }, 43 | { 44 | label: '单个文件', 45 | value: 11, 46 | }, 47 | { 48 | label: '多个文件', 49 | value: 12, 50 | }, 51 | ] 52 | -------------------------------------------------------------------------------- /src/views/config/dict/columns.tsx: -------------------------------------------------------------------------------- 1 | import type { TableColumn } from '/@/components/Table' 2 | 3 | import { DictValueTypes } from './DictValueType' 4 | import { StatusTypeEnum } from '/@/enums/typeEnum' 5 | 6 | export function createDictItemColumns(): TableColumn[] { 7 | return [ 8 | { 9 | label: '字典项名称', 10 | prop: 'name', 11 | width: 220, 12 | }, 13 | { 14 | label: '字典项标识', 15 | prop: 'uniqueKey', 16 | width: 120, 17 | align: 'center', 18 | }, 19 | { 20 | label: '值类型', 21 | prop: 'type', 22 | width: 200, 23 | align: 'center', 24 | formatter: (row: Recordable) => { 25 | return DictValueTypes.find((e) => e.value === row.type)!.label 26 | }, 27 | }, 28 | { 29 | label: '字典项值', 30 | prop: 'value', 31 | width: 340, 32 | showOverflowTooltip: true, 33 | align: 'center', 34 | }, 35 | { 36 | align: 'center', 37 | label: '状态', 38 | prop: 'status', 39 | width: 120, 40 | render: ({ row }) => { 41 | return ( 42 | 43 | {row.status === StatusTypeEnum.Disable ? '禁用' : '启用'} 44 | 45 | ) 46 | }, 47 | }, 48 | { 49 | align: 'center', 50 | label: '备注', 51 | prop: 'remark', 52 | width: 300, 53 | showOverflowTooltip: true, 54 | }, 55 | { 56 | align: 'center', 57 | label: '排序', 58 | width: 100, 59 | prop: 'orderNum', 60 | }, 61 | { 62 | align: 'center', 63 | label: '操作', 64 | width: 140, 65 | fixed: 'right', 66 | slot: 'action', 67 | }, 68 | ] 69 | } 70 | 71 | export function createDictColumns(): TableColumn[] { 72 | return [ 73 | { 74 | label: '字典名称', 75 | prop: 'name', 76 | headerSlot: 'dictHeader', 77 | }, 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /src/views/log/login/columns.tsx: -------------------------------------------------------------------------------- 1 | import type { TableColumn } from '/@/components/Table' 2 | import { StatusTypeEnum } from '/@/enums/typeEnum' 3 | 4 | export function createColumns(): TableColumn[] { 5 | return [ 6 | { 7 | type: 'index', 8 | }, 9 | { 10 | label: '操作账号', 11 | prop: 'account', 12 | }, 13 | { 14 | align: 'center', 15 | label: 'IP', 16 | prop: 'ip', 17 | }, 18 | { 19 | align: 'center', 20 | label: '请求状态', 21 | prop: 'status', 22 | render: ({ row }) => { 23 | return ( 24 | 25 | {row.status === StatusTypeEnum.Successful ? '成功' : '失败'} 26 | 27 | ) 28 | }, 29 | }, 30 | { 31 | align: 'center', 32 | label: '请求路径', 33 | prop: 'uri', 34 | }, 35 | { 36 | align: 'center', 37 | label: '登录时间', 38 | prop: 'createTime', 39 | }, 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/views/log/login/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/views/system/dept/columns.tsx: -------------------------------------------------------------------------------- 1 | import type { TableColumn } from '/@/components/Table' 2 | import { StatusTypeEnum } from '/@/enums/typeEnum' 3 | 4 | export function createColumns(): TableColumn[] { 5 | return [ 6 | { 7 | width: 300, 8 | label: '部门名称', 9 | prop: 'name', 10 | }, 11 | { 12 | align: 'center', 13 | width: 140, 14 | label: '部门标识', 15 | prop: 'uniqueKey', 16 | }, 17 | { 18 | align: 'center', 19 | width: 300, 20 | label: '部门全称', 21 | prop: 'fullName', 22 | }, 23 | { 24 | align: 'center', 25 | width: 120, 26 | label: '部门类型', 27 | prop: 'type', 28 | slot: 'type', 29 | }, 30 | { 31 | align: 'center', 32 | width: 100, 33 | label: '状态', 34 | prop: 'status', 35 | render: ({ row }) => { 36 | return ( 37 | 38 | {row.status === StatusTypeEnum.Disable ? '禁用' : '启用'} 39 | 40 | ) 41 | }, 42 | }, 43 | { 44 | align: 'center', 45 | label: '备注', 46 | prop: 'remark', 47 | minWidth: 280, 48 | showOverflowTooltip: true, 49 | }, 50 | { 51 | align: 'center', 52 | width: 80, 53 | label: '排序', 54 | prop: 'orderNum', 55 | }, 56 | { 57 | width: 140, 58 | align: 'center', 59 | label: '操作', 60 | fixed: 'right', 61 | slot: 'action', 62 | }, 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/views/system/job/columns.tsx: -------------------------------------------------------------------------------- 1 | import type { TableColumn } from '/@/components/Table' 2 | import { StatusTypeEnum } from '/@/enums/typeEnum' 3 | 4 | export function createColumns(): TableColumn[] { 5 | return [ 6 | { 7 | label: '岗位名称', 8 | prop: 'name', 9 | minWidth: 300, 10 | align: 'center', 11 | }, 12 | { 13 | align: 'center', 14 | label: '状态', 15 | prop: 'status', 16 | render: ({ row }) => { 17 | return ( 18 | 19 | {row.status === StatusTypeEnum.Disable ? '禁用' : '启用'} 20 | 21 | ) 22 | }, 23 | }, 24 | { 25 | align: 'center', 26 | label: '排序', 27 | prop: 'orderNum', 28 | }, 29 | { 30 | align: 'center', 31 | label: '操作', 32 | slot: 'action', 33 | width: 140, 34 | fixed: 'right', 35 | }, 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /src/views/system/job/schemas.tsx: -------------------------------------------------------------------------------- 1 | import type { FormSchema } from '/@/components/Form' 2 | 3 | export function createSchemas(): FormSchema[] { 4 | return [ 5 | { 6 | label: '岗位名称', 7 | prop: 'name', 8 | defaultValue: '', 9 | component: 'ElInput', 10 | rules: { 11 | required: true, 12 | type: 'string', 13 | message: '请输入岗位名称', 14 | }, 15 | }, 16 | { 17 | label: '状态', 18 | defaultValue: 1, 19 | prop: 'status', 20 | render: ({ model }) => { 21 | return ( 22 | 23 | 启用 24 | 禁用 25 | 26 | ) 27 | }, 28 | colProps: { 29 | span: 12, 30 | }, 31 | }, 32 | { 33 | label: '排序', 34 | defaultValue: 0, 35 | prop: 'orderNum', 36 | component: 'ElInputNumber', 37 | componentProps: { 38 | min: 0, 39 | }, 40 | colProps: { 41 | span: 12, 42 | }, 43 | }, 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /src/views/system/menu/columns.ts: -------------------------------------------------------------------------------- 1 | import type { TableColumn } from '/@/components/Table' 2 | 3 | export function createColumns(): TableColumn[] { 4 | return [ 5 | { 6 | width: 300, 7 | label: '菜单名称', 8 | prop: 'name', 9 | slot: 'name', 10 | }, 11 | { 12 | align: 'center', 13 | width: 120, 14 | label: '类型', 15 | prop: 'type', 16 | slot: 'type', 17 | }, 18 | { 19 | width: 80, 20 | align: 'center', 21 | label: '图标', 22 | prop: 'icon', 23 | slot: 'icon', 24 | }, 25 | { 26 | align: 'center', 27 | label: '路由', 28 | prop: 'router', 29 | showOverflowTooltip: true, 30 | minWidth: 240, 31 | }, 32 | { 33 | align: 'center', 34 | label: '视图路径', 35 | prop: 'viewPath', 36 | showOverflowTooltip: true, 37 | minWidth: 240, 38 | }, 39 | { 40 | width: 340, 41 | align: 'center', 42 | label: '权限', 43 | prop: 'perms', 44 | slot: 'perms', 45 | }, 46 | { 47 | width: 80, 48 | align: 'center', 49 | label: '排序', 50 | prop: 'orderNum', 51 | }, 52 | { 53 | width: 140, 54 | align: 'center', 55 | label: '操作', 56 | slot: 'action', 57 | fixed: 'right', 58 | }, 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /src/views/system/profession/columns.tsx: -------------------------------------------------------------------------------- 1 | import type { TableColumn } from '/@/components/Table' 2 | import { StatusTypeEnum } from '/@/enums/typeEnum' 3 | 4 | export function createColumns(): TableColumn[] { 5 | return [ 6 | { 7 | label: '职称', 8 | prop: 'name', 9 | minWidth: 300, 10 | align: 'center', 11 | }, 12 | { 13 | align: 'center', 14 | label: '状态', 15 | prop: 'status', 16 | render: ({ row }) => { 17 | return ( 18 | 19 | {row.status === StatusTypeEnum.Disable ? '禁用' : '启用'} 20 | 21 | ) 22 | }, 23 | }, 24 | { 25 | align: 'center', 26 | label: '排序', 27 | prop: 'orderNum', 28 | }, 29 | { 30 | align: 'center', 31 | label: '操作', 32 | slot: 'action', 33 | width: 140, 34 | fixed: 'right', 35 | }, 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /src/views/system/profession/schemas.tsx: -------------------------------------------------------------------------------- 1 | import type { FormSchema } from '/@/components/Form' 2 | 3 | export function createSchemas(): FormSchema[] { 4 | return [ 5 | { 6 | label: '职称', 7 | prop: 'name', 8 | defaultValue: '', 9 | component: 'ElInput', 10 | rules: { 11 | required: true, 12 | type: 'string', 13 | message: '请输入职称', 14 | }, 15 | }, 16 | { 17 | label: '状态', 18 | defaultValue: 1, 19 | prop: 'status', 20 | render: ({ model }) => { 21 | return ( 22 | 23 | 启用 24 | 禁用 25 | 26 | ) 27 | }, 28 | colProps: { 29 | span: 12, 30 | }, 31 | }, 32 | { 33 | label: '排序', 34 | defaultValue: 0, 35 | prop: 'orderNum', 36 | component: 'ElInputNumber', 37 | componentProps: { 38 | min: 0, 39 | }, 40 | colProps: { 41 | span: 12, 42 | }, 43 | }, 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /src/views/system/role/AssignPermTree.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 60 | 61 | 66 | -------------------------------------------------------------------------------- /src/views/system/role/columns.tsx: -------------------------------------------------------------------------------- 1 | import type { TableColumn } from '/@/components/Table' 2 | import { StatusTypeEnum } from '/@/enums/typeEnum' 3 | 4 | export function createColumns(): TableColumn[] { 5 | return [ 6 | { 7 | label: '角色名称', 8 | prop: 'name', 9 | width: 280, 10 | }, 11 | { 12 | label: '角色标识', 13 | prop: 'uniqueKey', 14 | width: 220, 15 | align: 'center', 16 | }, 17 | { 18 | align: 'center', 19 | width: 100, 20 | label: '状态', 21 | prop: 'status', 22 | render: ({ row }) => { 23 | return ( 24 | 25 | {row.status === StatusTypeEnum.Disable ? '禁用' : '启用'} 26 | 27 | ) 28 | }, 29 | }, 30 | { 31 | align: 'center', 32 | label: '备注', 33 | prop: 'remark', 34 | showOverflowTooltip: true, 35 | }, 36 | { 37 | width: 100, 38 | align: 'center', 39 | label: '排序', 40 | prop: 'orderNum', 41 | }, 42 | { 43 | width: 140, 44 | align: 'center', 45 | label: '操作', 46 | slot: 'action', 47 | fixed: 'right', 48 | }, 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "noLib": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strictFunctionTypes": false, 11 | "jsx": "preserve", 12 | "baseUrl": ".", 13 | "allowJs": true, 14 | "sourceMap": true, 15 | "esModuleInterop": true, 16 | "resolveJsonModule": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "experimentalDecorators": true, 20 | "lib": ["dom", "esnext"], 21 | "noImplicitAny": false, 22 | "skipLibCheck": true, 23 | "removeComments": true, 24 | "types": ["vite/client", "element-plus/global"], 25 | "paths": { 26 | "/@/*": ["./src/*"], 27 | "/#/*": ["./types/*"] 28 | } 29 | }, 30 | "include": [ 31 | "src/**/*.ts", 32 | "src/**/*.d.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "types/**/*.d.ts", 36 | "types/**/*.ts", 37 | "build/**/*.ts", 38 | "build/**/*.d.ts", 39 | "vite.config.ts" 40 | ], 41 | "exclude": ["node_modules", "dist", "**/*.js"] 42 | } 43 | -------------------------------------------------------------------------------- /types/axios.d.ts: -------------------------------------------------------------------------------- 1 | export interface Result { 2 | msg: string 3 | code: number 4 | data?: T 5 | } 6 | 7 | export interface PaginationResult { 8 | list: T[] 9 | 10 | pagination: { 11 | page: number 12 | limit: number 13 | total: number 14 | } 15 | } 16 | 17 | export interface PageRequestParams { 18 | page: number 19 | limit: number 20 | } 21 | 22 | export type ErrorMessageMode = 'messageBox' | 'message' | 'none' | undefined 23 | 24 | export interface RequestOptions { 25 | /** 26 | * 格式化请求参数时间 27 | */ 28 | formatDate?: boolean 29 | 30 | /** 31 | * 是否处理请求结果 32 | */ 33 | isTransformResponse?: boolean 34 | 35 | /** 36 | * 是否返回原生响应结果 37 | * 例如:需要获取响应结果Header 38 | */ 39 | isReturnNativeResponse?: boolean 40 | 41 | /** 42 | * 接口路径 43 | */ 44 | apiUrl?: string 45 | 46 | /** 47 | * 是否加入时间戳 48 | */ 49 | joinTimestamp?: boolean 50 | 51 | /** 52 | * 消息提示类型 53 | */ 54 | errorMessageMode?: ErrorMessageMode 55 | 56 | /** 57 | * 忽略CancelToken 58 | */ 59 | ignoreCancelToken?: boolean 60 | 61 | /** 62 | * 是否需要token验证 63 | */ 64 | withToken?: boolean 65 | 66 | /** 67 | * 请求重试配置 68 | */ 69 | retryRequest?: RetryRequest 70 | } 71 | 72 | export interface RetryRequest { 73 | /** 74 | * 开启失败请求重试 75 | */ 76 | useRetry?: boolean 77 | 78 | /** 79 | * 失败重试次数 80 | */ 81 | count?: number 82 | 83 | /** 84 | * 等待时间 85 | */ 86 | waitTime?: number 87 | } 88 | 89 | // multipart/form-data: upload file 90 | export interface UploadFileParams { 91 | /** 92 | * 其他参数 93 | */ 94 | data?: Recordable 95 | 96 | /** 97 | * 文件参数接口字段名称 98 | */ 99 | name?: string 100 | 101 | /** 102 | * 上传的文件 103 | */ 104 | file: File | Blob 105 | 106 | /** 107 | * 文件名称 108 | */ 109 | filename?: string 110 | 111 | [key: string]: any 112 | } 113 | -------------------------------------------------------------------------------- /types/components.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://github.com/johnsoncodehk/volar/tree/master/extensions/vscode-vue-language-features#usage 3 | * Define Global Components 4 | */ 5 | declare module '@vue/runtime-core' { 6 | export interface GlobalComponents { 7 | RouterLink: typeof import('vue-router')['RouterLink'] 8 | RouterView: typeof import('vue-router')['RouterView'] 9 | } 10 | } 11 | 12 | export {} 13 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | import type { PropType as VuePropType, VNodeChild, ComputedRef, Ref } from 'vue' 2 | 3 | declare global { 4 | const __APP_INFO__: { 5 | pkg: { 6 | version: string 7 | dependencies: Record 8 | devDependencies: Record 9 | } 10 | lastBuildTime: string 11 | } 12 | 13 | declare interface ViteEnv { 14 | VITE_PORT: number 15 | VITE_APP_BASE_API: string 16 | VITE_PROXY_API_TARGET: string 17 | } 18 | 19 | declare type PropType = VuePropType 20 | declare type VueNode = VNodeChild | JSX.Element 21 | declare type RefableProps = { 22 | [P in keyof T]: Ref | T[P] | ComputedRef 23 | } 24 | 25 | declare type Nullable = T | null | undefined 26 | declare type NonNullable = T extends null | undefined ? never : T 27 | declare type Recordable = Record 28 | declare type ReadonlyRecordable = { 29 | readonly [key: string]: T 30 | } 31 | declare type ReadonlyRecordable = { 32 | readonly [key: string]: T 33 | } 34 | declare type Writable = { 35 | -readonly [P in keyof T]: T[P] 36 | } 37 | declare type Arrayable = T | T[] 38 | 39 | /** 40 | * Recursive `Partial`. 41 | */ 42 | declare type DeepPartial = { 43 | [P in keyof T]?: DeepPartial 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Fn { 2 | (...arg: T[]): R 3 | } 4 | 5 | declare interface PromiseFn { 6 | (...arg: T[]): Promise 7 | } 8 | 9 | declare type EmitFn = (event: any, ...args: any[]) => void 10 | 11 | declare type WindowTargetContext = '_self' | '_blank' 12 | 13 | declare type LabelValueOptions = { 14 | label: string 15 | value: any 16 | [key: string]: string | number | boolean 17 | }[] 18 | 19 | declare interface ComponentEl { 20 | $el?: T 21 | } 22 | -------------------------------------------------------------------------------- /types/module.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | 10 | interface ImportMetaEnv { 11 | readonly VITE_APP_BASE_API: string 12 | readonly VITE_PROXY_API_TARGET: string 13 | } 14 | 15 | interface ImportMeta { 16 | readonly env: ImportMetaEnv 17 | } 18 | -------------------------------------------------------------------------------- /types/vue-router.d.ts: -------------------------------------------------------------------------------- 1 | import type { RouterTransitionEnum } from '/@/enums/appEnum' 2 | import type { RoleEnum } from '/@/enums/roleEnum' 3 | 4 | /** 5 | * 扩展RouteMeta属性 6 | */ 7 | declare module 'vue-router' { 8 | interface RouteMeta extends Record { 9 | /** 10 | * @description if true, not show in sidebar menu 11 | * @default false 12 | */ 13 | hidden?: boolean 14 | 15 | /** 16 | * @description sidebar menu name 17 | */ 18 | title?: string 19 | 20 | /** 21 | * @description icon name 22 | */ 23 | icon?: string 24 | 25 | /** 26 | * @description if false, not show in breadcrumb 27 | * @default true 28 | */ 29 | breadcrumb?: boolean 30 | 31 | /** 32 | * @description affix tag view 33 | * @default false 34 | */ 35 | affix?: boolean 36 | 37 | /** 38 | * @description page transition enum 39 | */ 40 | transitionName?: RouterTransitionEnum 41 | 42 | /** 43 | * @description iframe src 44 | */ 45 | iframeSrc?: string 46 | 47 | /** 48 | * @description role info 49 | */ 50 | roles?: RoleEnum[] 51 | 52 | /** 53 | * @description current active menu 54 | */ 55 | currentActiveMenu?: string 56 | 57 | /** 58 | * @description used internally to mark single-level menus 59 | */ 60 | single?: boolean 61 | 62 | /** 63 | * @description ignore route, only build for BACK mode 64 | */ 65 | ignoreRoute?: boolean 66 | } 67 | } 68 | --------------------------------------------------------------------------------