├── .editorconfig ├── .env ├── .env.development ├── .env.production ├── .env.test ├── .eslintignore ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .idea ├── .gitignore ├── inspectionProfiles │ └── Project_Default.xml ├── jsLinters │ └── eslint.xml ├── modules.xml ├── prettier.xml └── vue3-admin-design.iml ├── .prettierignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.en-US.md ├── README.md ├── build ├── utils.ts └── vite │ ├── plugin │ ├── components.ts │ ├── index.ts │ ├── mock.ts │ └── svgIcons.ts │ └── proxy.ts ├── commitlint.config.js ├── components.d.ts ├── eslint.config.js ├── index.html ├── mock ├── _createProductionServer.ts ├── _util.ts └── data │ ├── table.ts │ └── user.ts ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── public └── favicon.ico ├── src ├── App.tsx ├── api │ └── index.ts ├── assets │ ├── icons │ │ ├── bug.svg │ │ ├── collapsed.svg │ │ ├── color-bg.svg │ │ ├── color-font.svg │ │ ├── compo.svg │ │ ├── computer.svg │ │ ├── document.svg │ │ ├── editor.svg │ │ ├── excel.svg │ │ ├── flow.svg │ │ ├── font-align.svg │ │ ├── font-bold.svg │ │ ├── font-italic.svg │ │ ├── font-shadow.svg │ │ ├── form.svg │ │ ├── github.svg │ │ ├── heart.svg │ │ ├── hints.svg │ │ ├── home.svg │ │ ├── image.svg │ │ ├── like.svg │ │ ├── linking.svg │ │ ├── locale.svg │ │ ├── location.svg │ │ ├── message.svg │ │ ├── moon.svg │ │ ├── person.svg │ │ ├── pushpin-fill.svg │ │ ├── pushpin-line.svg │ │ ├── screen-full.svg │ │ ├── screen-normal.svg │ │ ├── search.svg │ │ ├── sun.svg │ │ ├── system.svg │ │ ├── table.svg │ │ ├── tree.svg │ │ ├── unfold.svg │ │ └── video.svg │ └── images │ │ ├── avatar.jpeg │ │ ├── login-bg.png │ │ ├── logo.png │ │ ├── logo_name.png │ │ ├── name.png │ │ └── name_white.png ├── components │ ├── ApiCompo │ │ ├── index.ts │ │ └── src │ │ │ ├── components │ │ │ ├── ApiCascader.tsx │ │ │ ├── ApiCheckboxGroup.tsx │ │ │ ├── ApiRadioGroup.tsx │ │ │ ├── ApiSelect.tsx │ │ │ ├── ApiTransfer.tsx │ │ │ ├── ApiTree.tsx │ │ │ └── ApiTreeSelect.tsx │ │ │ ├── hooks │ │ │ └── useBindValue.ts │ │ │ └── types.ts │ ├── Application │ │ ├── index.ts │ │ └── src │ │ │ ├── AppDarkMode │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ │ ├── AppLocalePicker.tsx │ │ │ ├── AppLogo │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ │ └── AppSearch │ │ │ ├── Modal.tsx │ │ │ ├── index.tsx │ │ │ ├── modal.module.less │ │ │ └── useMenuSearch.ts │ ├── Container │ │ └── index.tsx │ ├── CountTo │ │ ├── index.ts │ │ └── src │ │ │ ├── index.tsx │ │ │ └── props.ts │ ├── Crud │ │ ├── index.ts │ │ └── src │ │ │ ├── _mockData.ts │ │ │ ├── index.tsx │ │ │ ├── props.ts │ │ │ └── types │ │ │ └── crud.ts │ ├── DndNode │ │ ├── index.ts │ │ └── src │ │ │ ├── DndNode.tsx │ │ │ └── types.ts │ ├── Form │ │ ├── index.ts │ │ └── src │ │ │ ├── compoMap.ts │ │ │ ├── components │ │ │ ├── FormAction.tsx │ │ │ └── FormItem.tsx │ │ │ ├── constant.ts │ │ │ ├── helper.ts │ │ │ ├── hooks │ │ │ ├── useAdvanced.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEvents.ts │ │ │ └── useFormValues.ts │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── form.ts │ │ │ └── index.ts │ ├── Iframe │ │ ├── index.ts │ │ └── src │ │ │ └── IframeWrapper.tsx │ ├── LogicFlow │ │ ├── index.ts │ │ └── src │ │ │ ├── Approve │ │ │ ├── components │ │ │ │ └── NodePanel │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ ├── data.ts │ │ │ ├── index.tsx │ │ │ ├── register.ts │ │ │ └── type.ts │ │ │ └── Bpmn │ │ │ ├── components │ │ │ └── Pattern │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ ├── Menu │ │ ├── index.ts │ │ └── src │ │ │ ├── components │ │ │ ├── MenuItemCont.tsx │ │ │ ├── MenuItems.tsx │ │ │ └── SubMenuItem.tsx │ │ │ ├── index.tsx │ │ │ ├── props.ts │ │ │ ├── types.ts │ │ │ └── useOpenKeys.ts │ ├── Page │ │ ├── index.ts │ │ └── src │ │ │ ├── PageWrapper.tsx │ │ │ └── compo.module.less │ ├── RichText │ │ ├── index.ts │ │ └── src │ │ │ ├── RichTextInput.tsx │ │ │ └── RichTextSetting.tsx │ ├── Scrollbar │ │ ├── index.ts │ │ └── src │ │ │ ├── bar.tsx │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── types.ts │ │ │ └── util.ts │ ├── SvgIcon │ │ ├── index.module.less │ │ └── index.tsx │ ├── Table │ │ ├── index.ts │ │ └── src │ │ │ ├── compoMap.ts │ │ │ ├── components │ │ │ ├── EditableCell │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ └── TableAction.tsx │ │ │ ├── constant.ts │ │ │ ├── helper.ts │ │ │ ├── hooks │ │ │ └── useTableContext.ts │ │ │ ├── index.module.less │ │ │ ├── index.tsx │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── column.ts │ │ │ ├── index.ts │ │ │ ├── pagination.ts │ │ │ ├── row.ts │ │ │ └── table.ts │ ├── Upload │ │ ├── index.ts │ │ └── src │ │ │ └── UploadImage.tsx │ ├── VueDrr │ │ ├── index.ts │ │ └── src │ │ │ ├── dom.ts │ │ │ ├── index.tsx │ │ │ └── props.ts │ └── Widgets │ │ ├── index.ts │ │ └── src │ │ ├── FullScreen.tsx │ │ ├── Refresh.tsx │ │ └── Search.tsx ├── design │ ├── antd │ │ ├── index.less │ │ └── menu.less │ ├── dark-mode.less │ ├── index.less │ ├── plugin.less │ ├── public.less │ ├── scroll-bar.less │ ├── transition │ │ ├── base.less │ │ ├── fade.less │ │ ├── index.less │ │ ├── scale.less │ │ ├── scroll.less │ │ ├── slide.less │ │ └── zoom.less │ └── variable │ │ ├── breakpoint.less │ │ ├── color.less │ │ └── index.less ├── directives │ ├── clickOutside.ts │ └── infiniteScroll.ts ├── enums │ ├── appEnum.ts │ ├── cacheEnum.ts │ ├── exceptionEnum.ts │ ├── menuEnum.ts │ └── roleEnum.ts ├── hooks │ ├── core │ │ ├── useRefs.ts │ │ └── useTimeout.ts │ ├── event │ │ ├── useEventListener.ts │ │ ├── useScrollTo.ts │ │ └── useWindowSizeFn.ts │ ├── index.ts │ ├── setting │ │ ├── useBaseSetting.ts │ │ ├── useDarkModeSetting.ts │ │ ├── useHeaderSetting.ts │ │ ├── useLocaleSetting.ts │ │ ├── useMenuSetting.ts │ │ └── useTransitionSetting.ts │ └── web │ │ ├── useCopyToClipboard.ts │ │ ├── useECharts.ts │ │ ├── useLockPage.ts │ │ ├── useMessage.tsx │ │ ├── usePage.ts │ │ ├── useTags.ts │ │ └── useTitle.ts ├── layout │ ├── content │ │ ├── components │ │ │ ├── Iframe.tsx │ │ │ └── Page.tsx │ │ ├── index.less │ │ ├── index.tsx │ │ └── usePageTransition.ts │ ├── feature │ │ ├── components │ │ │ ├── DocLink.tsx │ │ │ ├── FullScreen.tsx │ │ │ ├── GithubLink.tsx │ │ │ ├── UserDropdown.tsx │ │ │ └── index.ts │ │ ├── index.module.less │ │ └── index.tsx │ ├── footer │ │ ├── index.module.less │ │ └── index.tsx │ ├── header │ │ ├── BasicHeader.tsx │ │ ├── HybridHeader │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── components │ │ │ ├── Breadcrumb.tsx │ │ │ ├── FoldTrigger.tsx │ │ │ └── compo.module.less │ │ └── index.tsx │ ├── index.tsx │ ├── lock │ │ ├── components │ │ │ ├── LockModal │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── LockScreen │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ └── UnlockForm │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── useNowTime.ts │ ├── menu │ │ ├── index.tsx │ │ └── useLayoutMenu.ts │ ├── setting │ │ ├── components │ │ │ ├── InputNumItem.tsx │ │ │ ├── MenuThemeRadio │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── MenuTypePicker │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── SelectItem.tsx │ │ │ ├── SettingFooter.tsx │ │ │ ├── SwitchItem.tsx │ │ │ ├── ThemeColorPicker │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ └── index.ts │ │ ├── enum.ts │ │ ├── handler.ts │ │ ├── index.module.less │ │ └── index.tsx │ ├── sider │ │ ├── BasicSider.tsx │ │ ├── HybridSider │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── components │ │ │ ├── DragBar │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ └── SiderTrigger │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── useLayoutSider.ts │ ├── tags │ │ ├── components │ │ │ └── TagItem │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ ├── index.less │ │ ├── index.tsx │ │ └── useTags.ts │ └── useLayout.ts ├── locales │ ├── helper.ts │ ├── index.ts │ └── langs │ │ ├── en_US │ │ ├── components.json │ │ ├── layout.json │ │ ├── routes.json │ │ └── system.json │ │ ├── index.ts │ │ ├── zh_CN │ │ ├── components.json │ │ ├── layout.json │ │ ├── routes.json │ │ └── system.json │ │ └── zh_TW │ │ ├── components.json │ │ ├── layout.json │ │ ├── routes.json │ │ └── system.json ├── logics │ ├── initAppConfig.ts │ ├── mitt │ │ └── routeChange.ts │ └── theme │ │ ├── index.ts │ │ ├── mode.ts │ │ └── util.ts ├── main.ts ├── plugins │ ├── index.ts │ └── modules │ │ ├── form-create-designer.ts │ │ ├── form-create-ui.ts │ │ └── org-tree.ts ├── router │ ├── guard │ │ ├── index.ts │ │ └── permissionGuard.ts │ ├── helper │ │ ├── menuHelper.ts │ │ └── routeHelper.ts │ ├── index.ts │ ├── menus │ │ └── index.ts │ ├── routes │ │ ├── index.ts │ │ └── modules │ │ │ ├── compo.ts │ │ │ ├── excel.ts │ │ │ ├── exception.ts │ │ │ ├── flow-editor.ts │ │ │ ├── form.ts │ │ │ ├── home.ts │ │ │ ├── iframe.ts │ │ │ ├── image.ts │ │ │ ├── system.ts │ │ │ ├── table.ts │ │ │ ├── text-editor.ts │ │ │ ├── tree.ts │ │ │ └── video.ts │ └── types.ts ├── settings │ ├── appBaseSetting.ts │ ├── designSetting.ts │ ├── encryptionSetting.ts │ ├── localeSetting.ts │ └── websiteSetting.ts ├── stores │ ├── index.ts │ ├── modules │ │ ├── app.ts │ │ ├── lock.ts │ │ ├── permission.ts │ │ ├── tags.ts │ │ └── user.ts │ └── plugin │ │ └── persist.ts ├── types │ ├── config.ts │ ├── index.ts │ └── utils.ts ├── utils │ ├── auth │ │ └── index.ts │ ├── axios │ │ └── index.ts │ ├── cache │ │ ├── index.ts │ │ ├── memory.ts │ │ ├── persistent.ts │ │ └── storageCache.ts │ ├── cipher.ts │ ├── color.ts │ ├── dateUtil.ts │ ├── dom.ts │ ├── dom │ │ ├── position.ts │ │ ├── scroll.ts │ │ └── style.ts │ ├── download.ts │ ├── echarts.ts │ ├── env.ts │ ├── factory │ │ └── ImportAsyncComponent.tsx │ ├── helper │ │ ├── treeHelper.ts │ │ └── tsxHelper.ts │ ├── image.ts │ ├── index.ts │ ├── is.ts │ ├── mitt.ts │ ├── propTypes.ts │ ├── resizeEvent.ts │ └── rich-text.ts └── views │ ├── compo │ ├── count-to.tsx │ ├── drag │ │ ├── drag-list.tsx │ │ └── drag-resize.tsx │ ├── image-upload.tsx │ └── transfer │ │ ├── data.ts │ │ └── index.tsx │ ├── editor │ ├── code-mirror │ │ ├── components │ │ │ ├── CodeInfo.tsx │ │ │ └── Toolbar.tsx │ │ └── index.tsx │ ├── markdown.tsx │ └── rich-text.tsx │ ├── excel │ ├── export-excel │ │ ├── data.ts │ │ └── index.tsx │ ├── import-excel.tsx │ ├── types.ts │ └── useExcel.ts │ ├── exception │ └── index.tsx │ ├── flow │ ├── flow-approve.tsx │ └── flow-bpmn.tsx │ ├── form │ ├── basic-form │ │ ├── data.ts │ │ └── index.tsx │ └── form-designer.tsx │ ├── home │ ├── components │ │ ├── ChartsCard.tsx │ │ └── CountToCard.tsx │ ├── data.ts │ └── index.tsx │ ├── image │ ├── image-composition │ │ ├── data.ts │ │ ├── index.tsx │ │ └── types.ts │ ├── image-compress.tsx │ └── image-cropper.tsx │ ├── login │ ├── index.module.less │ └── index.tsx │ ├── redirect.tsx │ ├── system │ ├── account.vue │ ├── dept.vue │ ├── dict.vue │ ├── job.vue │ ├── menu.vue │ ├── role.vue │ └── user.vue │ ├── table │ ├── table-basic.tsx │ └── table-edit-row.tsx │ ├── tree │ ├── antd-tree │ │ ├── data.ts │ │ └── index.tsx │ └── org-tree │ │ ├── data.ts │ │ └── index.tsx │ └── video │ ├── video-player.tsx │ └── video-watermark │ ├── data.ts │ ├── index.tsx │ └── types.ts ├── tsconfig.json ├── typings ├── global.d.ts ├── index.d.ts ├── module.d.ts └── vue-router.d.ts └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # @see: http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = off 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # name 2 | VITE_APP_NAME = Admin Design 3 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # Port 2 | VITE_PORT = 8888 3 | 4 | # Cross-domain proxy, you can configure multiple 5 | # Please note that no line breaks 6 | VITE_PROXY=[["/api","http://localhost:8888"]] 7 | 8 | # Delete console 9 | VITE_DROP_CONSOLE = false -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # Port 2 | VITE_PORT = 8888 3 | 4 | VITE_APP_BASE_API = / 5 | 6 | # Delete console 7 | VITE_DROP_CONSOLE = true 8 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/.env.test -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | .husky 4 | .local 5 | 6 | /public 7 | /docs 8 | /src/assets 9 | dist 10 | node_modules 11 | pnpm-lock.yaml 12 | Dockerfile 13 | 14 | eslint.config.js 15 | postcss.config.js 16 | prettier.config.js 17 | commitlint.config.js 18 | 19 | *.md 20 | *.woff 21 | *.ttf 22 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # 将静态内容部署到 GitHub Pages 的工作流程 2 | name: deploy 3 | 4 | on: 5 | # 仅在推送到默认分支时运行 6 | push: 7 | branches: ['main'] 8 | 9 | # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages。 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # 允许一个并发的部署 16 | concurrency: 17 | group: 'pages' 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | # 单次部署的工作描述 22 | push-to-gh-pages: 23 | environment: 24 | name: github-pages 25 | url: ${{ steps.deployment.outputs.page_url }} 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | with: 31 | persist-credentials: false 32 | 33 | - name: Set up Node 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: 18 37 | 38 | - name: Install pnpm 39 | uses: pnpm/action-setup@v2 40 | with: 41 | version: 8 42 | run_install: false 43 | 44 | - name: Build 45 | env: 46 | NODE_OPTIONS: '--max_old_space_size=4096' 47 | run: | 48 | pnpm install 49 | pnpm run build 50 | 51 | - name: Setup Pages 52 | uses: actions/configure-pages@v3 53 | 54 | - name: Upload artifact 55 | uses: actions/upload-pages-artifact@v2 56 | with: 57 | # Upload dist repository 58 | path: './dist' 59 | 60 | - name: Deploy to GitHub Pages 61 | id: deployment 62 | uses: actions/deploy-pages@v2 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | package-lock.json 10 | yarn.lock 11 | 12 | node_modules 13 | dist 14 | dist-ssr 15 | *.local 16 | .eslintcache 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | !.vscode/settings.json 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm run lint:lint-staged 5 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | # GitHub Copilot persisted chat sessions 7 | /copilot/chatSessions 8 | 9 | /vcs.xml 10 | /watcherTasks.xml 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.idea/jsLinters/eslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/vue3-admin-design.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /public/* 3 | /node_modules/** 4 | 5 | .local 6 | **/*.svg 7 | 8 | eslint.config.js 9 | postcss.config.js 10 | prettier.config.js 11 | commitlint.config.js 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "csstools.postcss", 7 | "vscode-icons-team.vscode-icons", 8 | "vue.volar" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit" 5 | }, 6 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], 7 | "eslint.useFlatConfig": true, 8 | "prettier.vueIndentScriptAndStyle": true, 9 | "[vue]": { 10 | "editor.defaultFormatter": "esbenp.prettier-vscode" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 baimingxuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/utils.ts: -------------------------------------------------------------------------------- 1 | declare type Recordable = Record 2 | 3 | interface ViteEnv { 4 | VITE_PORT: number 5 | VITE_PROXY: [string, string][] 6 | VITE_DROP_CONSOLE: boolean 7 | } 8 | 9 | // read all environment variable configuration files to process.env 10 | export function wrapperEnv(envConf: Recordable): ViteEnv { 11 | const result: any = {} 12 | 13 | for (const envName of Object.keys(envConf)) { 14 | let realName = envConf[envName].replace(/\\n/g, '\n') 15 | realName = realName === 'true' ? true : realName === 'false' ? false : realName 16 | 17 | if (envName === 'VITE_PORT') { 18 | realName = Number(realName) 19 | } 20 | 21 | if (envName === 'VITE_PROXY' && realName) { 22 | try { 23 | realName = JSON.parse(realName.replace(/'/g, '"')) 24 | } catch (error) { 25 | realName = '' 26 | } 27 | } 28 | 29 | result[envName] = realName 30 | 31 | if (typeof realName === 'string') { 32 | process.env[envName] = realName 33 | } else if (typeof realName === 'object') { 34 | process.env[envName] = JSON.stringify(realName) 35 | } 36 | } 37 | 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /build/vite/plugin/components.ts: -------------------------------------------------------------------------------- 1 | import Components from 'unplugin-vue-components/vite' 2 | import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' 3 | 4 | export function configAntdComponentsPlugin() { 5 | return Components({ 6 | resolvers: [ 7 | AntDesignVueResolver({ 8 | importStyle: false 9 | }) 10 | ] 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /build/vite/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | // import { codeInspectorPlugin } from 'code-inspector-plugin' 5 | import { configMockPlugin } from './mock' 6 | import { configSvgIconsPlugin } from './svgIcons' 7 | import { configAntdComponentsPlugin } from './components' 8 | 9 | export function createVitePlugins(isBuild: boolean) { 10 | const vitePlugins: (Plugin | Plugin[])[] = [ 11 | // required plugin 12 | vue(), 13 | vueJsx() 14 | ] 15 | 16 | // unplugin-vue-components 17 | vitePlugins.push(configAntdComponentsPlugin()) 18 | 19 | // vite-plugin-svg-icons 20 | vitePlugins.push(configSvgIconsPlugin(isBuild)) 21 | 22 | // vite-plugin-mock 23 | vitePlugins.push(configMockPlugin(isBuild)) 24 | 25 | // code-inspector-plugin 26 | // vitePlugins.push( 27 | // codeInspectorPlugin({ 28 | // bundler: 'vite' 29 | // }) 30 | // ) 31 | 32 | return vitePlugins 33 | } 34 | -------------------------------------------------------------------------------- /build/vite/plugin/mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock plugin for development and production. 3 | * https://github.com/vbenjs/vite-plugin-mock 4 | */ 5 | import { viteMockServe } from 'vite-plugin-mock' 6 | 7 | export function configMockPlugin(isBuild: boolean) { 8 | return viteMockServe({ 9 | mockPath: 'mock', 10 | ignore: /^\_/, 11 | localEnabled: !isBuild, 12 | prodEnabled: isBuild, 13 | injectCode: ` 14 | import { setupProdMockServer } from '../mock/_createProductionServer' 15 | 16 | setupProdMockServer() 17 | ` 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /build/vite/plugin/svgIcons.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * vite plugin for importing svg-icons 3 | * https://github.com/vbenjs/vite-plugin-svg-icons 4 | */ 5 | 6 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 7 | import { resolve } from 'path' 8 | 9 | export function configSvgIconsPlugin(isBuild: boolean) { 10 | return createSvgIconsPlugin({ 11 | iconDirs: [resolve(process.cwd(), 'src/assets/icons')], 12 | svgoOptions: isBuild, 13 | symbolId: 'icon-[dir]-[name]' 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /build/vite/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to parse the .env.development proxy configuration 3 | */ 4 | import type { ProxyOptions } from 'vite' 5 | 6 | type ProxyItem = [string, string] 7 | 8 | type ProxyList = ProxyItem[] 9 | 10 | type ProxyTargetList = Record 11 | 12 | const httpsRE = /^https:\/\// 13 | 14 | /** 15 | * Generate proxy 16 | * @param list 17 | */ 18 | export function createProxy(list: ProxyList = []) { 19 | const ret: ProxyTargetList = {} 20 | for (const [prefix, target] of list) { 21 | const isHttps = httpsRE.test(target) 22 | 23 | // https://github.com/http-party/node-http-proxy#options 24 | ret[prefix] = { 25 | target: target, 26 | changeOrigin: true, 27 | ws: true, 28 | rewrite: path => path.replace(new RegExp(`^${prefix}`), ''), 29 | // https is require secure=false 30 | ...(isHttps ? { secure: false } : {}) 31 | } 32 | } 33 | return ret 34 | } 35 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | // Generated by unplugin-vue-components 4 | // Read more: https://github.com/vuejs/core/pull/3399 5 | export {} 6 | 7 | /* prettier-ignore */ 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | RouterLink: typeof import('vue-router')['RouterLink'] 11 | RouterView: typeof import('vue-router')['RouterView'] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %VITE_APP_NAME% 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /mock/_createProductionServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' 2 | 3 | const modules = import.meta.glob('./**/*.ts', { eager: true }) as Object 4 | 5 | const mockModules: any[] = [] 6 | Object.keys(modules).forEach(key => { 7 | if (key.includes('/_')) return 8 | 9 | const module = modules[key].default || {} 10 | const moduleList = Array.isArray(module) ? [...module] : [module] 11 | mockModules.push(...moduleList) 12 | }) 13 | 14 | /** 15 | * Used in a production environment, need to manually import all modules. 16 | */ 17 | export function setupProdMockServer() { 18 | createProdMockServer(mockModules) 19 | } 20 | -------------------------------------------------------------------------------- /mock/_util.ts: -------------------------------------------------------------------------------- 1 | // Interface data format used to return a unified format 2 | 3 | export interface requestParams { 4 | headers?: { authorization?: string } 5 | method: string 6 | body: any 7 | query: any 8 | } 9 | 10 | export function resultSuccess(data: T, { message = '成功' } = {}) { 11 | return { 12 | type: 'success', 13 | code: 0, 14 | data, 15 | message 16 | } 17 | } 18 | 19 | export function resultError(message = '失败', { code = -1, data = null } = {}) { 20 | return { 21 | type: 'error', 22 | code, 23 | data, 24 | message 25 | } 26 | } 27 | 28 | // This function is used to get a token from the request data 29 | export function getRequestToken({ headers }: requestParams): string | undefined { 30 | return headers?.authorization 31 | } 32 | 33 | export function pagination(currentPage: number, pageSize: number, array: T[]): T[] { 34 | const offset = (currentPage - 1) * Number(pageSize) 35 | return offset + Number(pageSize) >= array.length 36 | ? array.slice(offset, array.length) 37 | : array.slice(offset, offset + Number(pageSize)) 38 | } 39 | 40 | export function resultPageSuccess(currentPage: number, pageSize: number, list: T[], { message = 'ok' } = {}) { 41 | const pageData = pagination(currentPage, pageSize, list) 42 | 43 | return { 44 | ...resultSuccess({ 45 | list: pageData, 46 | total: list.length 47 | }), 48 | message 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | export default { 3 | plugins: { 4 | autoprefixer: {}, 5 | ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // 配置文档: https://prettier.nodejs.cn/ 2 | 3 | /** @type {import('prettier').Config} */ 4 | export default { 5 | // 每行最大宽度,超过换行 6 | printWidth: 120, 7 | // 缩进级别的空格数 8 | tabWidth: 2, 9 | // 用制表符而不是空格缩进行 10 | useTabs: false, 11 | // 语句末尾用分号 12 | semi: false, 13 | // 使用单引号而不是双引号 14 | singleQuote: true, 15 | // 在 JSX 中使用单引号而不是双引号 16 | jsxSingleQuote: true, 17 | // 尾随逗号 18 | trailingComma: 'none', 19 | // 对象字面量中括号之间有空格 { foo: bar } 20 | bracketSpacing: true, 21 | // 将多行 HTML(HTML、JSX)元素的 > 放在最后一行的末尾,而不是单独放在下一行 22 | bracketSameLine: false, 23 | // 在唯一的箭头函数参数周围包含括号(avoid:省略括号, always:不省略括号) 24 | arrowParens: 'avoid', 25 | // 换行符使用 lf 结尾 可选值 auto|lf|crlf|cr 26 | endOfLine: 'lf', 27 | // 启用 Vue 文件中 script 和 style 标签的缩进 28 | vueIndentScriptAndStyle: true 29 | } 30 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/public/favicon.ico -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, unref, watch } from 'vue' 2 | import { RouterView } from 'vue-router' 3 | import { ConfigProvider } from 'ant-design-vue' 4 | import { useBaseSetting } from '@/hooks/setting/useBaseSetting' 5 | import { useDarkModeSetting } from '@/hooks/setting/useDarkModeSetting' 6 | import { useTitle } from '@/hooks/web/useTitle' 7 | import { setThemColor } from '@/logics/theme' 8 | 9 | export default defineComponent({ 10 | name: 'App', 11 | 12 | setup() { 13 | const { getThemeColor } = useBaseSetting() 14 | const { getModeAlgorithm } = useDarkModeSetting() 15 | 16 | watch(getThemeColor, () => { 17 | const htmlRoot = document.getElementById('htmlRoot') 18 | if (!htmlRoot) return 19 | 20 | setThemColor(htmlRoot) 21 | }) 22 | 23 | useTitle() 24 | 25 | return () => ( 26 | 35 | 36 | 37 | ) 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import type { LoginFormState } from '@/types' 2 | import { service } from '@/utils/axios' 3 | 4 | // User login api 5 | export function loginApi(data: LoginFormState): Promise { 6 | return service({ 7 | url: '/login', 8 | method: 'post', 9 | data 10 | }) 11 | } 12 | 13 | // Get User info 14 | export function getUserInfo(): Promise { 15 | return service({ 16 | url: '/getUserInfo', 17 | method: 'get' 18 | }) 19 | } 20 | 21 | // User logout api 22 | export function logoutApi() { 23 | return service({ 24 | url: '/logout', 25 | method: 'get' 26 | }) 27 | } 28 | 29 | // Table list 30 | export function getTableList(params) { 31 | return service({ 32 | url: '/table/getTableList', 33 | method: 'get', 34 | params 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/assets/icons/collapsed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/color-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/color-font.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/compo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/computer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/editor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/font-align.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/font-italic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/font-shadow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/like.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/linking.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/locale.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 11 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/icons/person.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/pushpin-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/pushpin-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/screen-full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/screen-normal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/unfold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/assets/images/avatar.jpeg -------------------------------------------------------------------------------- /src/assets/images/login-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/assets/images/login-bg.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/logo_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/assets/images/logo_name.png -------------------------------------------------------------------------------- /src/assets/images/name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/assets/images/name.png -------------------------------------------------------------------------------- /src/assets/images/name_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/assets/images/name_white.png -------------------------------------------------------------------------------- /src/components/ApiCompo/index.ts: -------------------------------------------------------------------------------- 1 | import ApiCascader from './src/components/ApiCascader' 2 | import ApiCheckboxGroup from './src/components/ApiCheckboxGroup' 3 | import ApiRadioGroup from './src/components/ApiRadioGroup' 4 | import ApiSelect from './src/components/ApiSelect' 5 | import ApiTransfer from './src/components/ApiTransfer' 6 | import ApiTree from './src/components/ApiTree' 7 | import ApiTreeSelect from './src/components/ApiTreeSelect' 8 | 9 | export { ApiCascader, ApiCheckboxGroup, ApiRadioGroup, ApiSelect, ApiTransfer, ApiTree, ApiTreeSelect } 10 | -------------------------------------------------------------------------------- /src/components/ApiCompo/src/hooks/useBindValue.ts: -------------------------------------------------------------------------------- 1 | import type { UnwrapRef, Ref, WritableComputedRef, DeepReadonly } from 'vue' 2 | import { reactive, readonly, computed, getCurrentInstance, watchEffect, unref, toRaw, nextTick } from 'vue' 3 | import { isEqual } from 'lodash-es' 4 | 5 | export function useBindValue>( 6 | props: T, 7 | key?: K, 8 | changeEvent?: string, 9 | emitData?: Ref 10 | ): [WritableComputedRef, (val: V) => void, DeepReadonly] 11 | 12 | export function useBindValue( 13 | props: T, 14 | key: keyof T = 'value', 15 | changeEvent = 'change', 16 | emitData?: Ref 17 | ) { 18 | const instance = getCurrentInstance() 19 | const emit = instance?.emit 20 | 21 | const innerState = reactive({ 22 | value: props[key] 23 | }) 24 | const defaultState = readonly(innerState) 25 | 26 | const setState = (val: UnwrapRef): void => { 27 | innerState.value = val as T[keyof T] 28 | } 29 | 30 | watchEffect(() => { 31 | innerState.value = props[key] 32 | }) 33 | 34 | const state: any = computed({ 35 | get() { 36 | return innerState.value 37 | }, 38 | set(value) { 39 | if (isEqual(value, defaultState.value)) return 40 | 41 | innerState.value = value as T[keyof T] 42 | nextTick(() => { 43 | emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || [])) 44 | }) 45 | } 46 | }) 47 | 48 | return [state, setState, defaultState] 49 | } 50 | -------------------------------------------------------------------------------- /src/components/ApiCompo/src/types.ts: -------------------------------------------------------------------------------- 1 | export type SelectOptionsType = { 2 | label?: string 3 | value?: string 4 | disabled?: boolean 5 | [name: string]: any 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | import AppLogo from './src/AppLogo' 2 | import AppDarkMode from './src/AppDarkMode' 3 | import AppLocalePicker from './src/AppLocalePicker' 4 | import AppSearch from './src/AppSearch' 5 | 6 | export { AppLogo, AppDarkMode, AppSearch, AppLocalePicker } 7 | -------------------------------------------------------------------------------- /src/components/Application/src/AppDarkMode/index.module.less: -------------------------------------------------------------------------------- 1 | .compo_app-dark-mode { 2 | position: relative; 3 | display: flex; 4 | align-items: center; 5 | justify-content: space-between; 6 | width: 50px; 7 | height: 26px; 8 | padding: 0 6px; 9 | margin: 0 auto; 10 | border-radius: 30px; 11 | box-sizing: border-box; 12 | cursor: pointer; 13 | 14 | .mode__inner { 15 | position: absolute; 16 | width: 18px; 17 | height: 18px; 18 | background-color: #fff; 19 | border-radius: 50%; 20 | transition: transform 0.5s, background-color 0.5s; 21 | will-change: transform; 22 | } 23 | 24 | &--dark { 25 | .mode__inner { 26 | transform: translateX(calc(100% + 2px)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Application/src/AppDarkMode/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, computed, unref } from 'vue' 2 | import { AppModeEnum } from '@/enums/appEnum' 3 | import { useBaseSetting } from '@/hooks/setting/useBaseSetting' 4 | import { updateDarkTheme } from '@/logics/theme/mode' 5 | import SvgIcon from '@/components/SvgIcon' 6 | import compoStyle from './index.module.less' 7 | 8 | export default defineComponent({ 9 | name: 'AppDarkMode', 10 | setup() { 11 | const prefixCls = 'compo_app-dark-mode' 12 | const { setAppMode, getAppMode, getThemeColor } = useBaseSetting() 13 | 14 | const isDarkMode = computed(() => getAppMode.value === AppModeEnum.DARK) 15 | 16 | function switchAppMode() { 17 | const mode = getAppMode.value === AppModeEnum.DARK ? AppModeEnum.LIGHT : AppModeEnum.DARK 18 | setAppMode(mode) 19 | updateDarkTheme(mode) 20 | } 21 | 22 | return () => ( 23 |
28 |
29 | 30 | 31 |
32 | ) 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/Application/src/AppLogo/index.module.less: -------------------------------------------------------------------------------- 1 | .app-logo { 2 | display: flex; 3 | align-items: center; 4 | height: 48px; 5 | padding-left: 14px; 6 | cursor: pointer; 7 | transition: all 0.3s ease; 8 | 9 | &.collapsed { 10 | padding-left: 8px; 11 | } 12 | 13 | .logo-img { 14 | display: block; 15 | width: 32px; 16 | height: 32px; 17 | } 18 | 19 | .logo-name { 20 | display: block; 21 | width: 120px; 22 | height: 15px; 23 | 24 | &.hidden { 25 | display: none; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Application/src/AppSearch/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref, unref } from 'vue' 2 | import { Tooltip } from 'ant-design-vue' 3 | import { useI18n } from 'vue-i18n' 4 | import SvgIcon from '@/components/SvgIcon' 5 | import SearchModal from './Modal' 6 | 7 | export default defineComponent({ 8 | name: 'AppSearch', 9 | setup() { 10 | const { t } = useI18n() 11 | const showModal = ref(false) 12 | 13 | function changeModal(show: boolean) { 14 | showModal.value = show 15 | } 16 | 17 | return () => ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ) 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /src/components/CountTo/index.ts: -------------------------------------------------------------------------------- 1 | import CountTo from './src' 2 | 3 | export { CountTo } 4 | -------------------------------------------------------------------------------- /src/components/CountTo/src/props.ts: -------------------------------------------------------------------------------- 1 | export const props = { 2 | startVal: { 3 | type: Number, 4 | default: 0 5 | }, 6 | endVal: { 7 | type: Number, 8 | default: 2020 9 | }, 10 | duration: { 11 | type: Number, 12 | default: 1500 13 | }, 14 | autoplay: { 15 | type: Boolean, 16 | default: true 17 | }, 18 | decimals: { 19 | type: Number, 20 | default: 0, 21 | validator(value: number) { 22 | return value >= 0 23 | } 24 | }, 25 | prefix: { 26 | type: String, 27 | default: '' 28 | }, 29 | suffix: { 30 | type: String, 31 | default: '' 32 | }, 33 | separator: { 34 | type: String, 35 | default: ',' 36 | }, 37 | decimal: { 38 | type: String, 39 | default: '.' 40 | }, 41 | // Font size 42 | size: { 43 | type: Number, 44 | default: 32 45 | }, 46 | // Font color 47 | color: { 48 | type: String, 49 | default: '#e65d6e' 50 | }, 51 | // Turn on digital animation 52 | useEasing: { 53 | type: Boolean, 54 | default: true 55 | }, 56 | // Digital animation 57 | transition: { 58 | type: String, 59 | default: 'linear' 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Crud/index.ts: -------------------------------------------------------------------------------- 1 | import Crud from './src' 2 | 3 | export { Crud } 4 | -------------------------------------------------------------------------------- /src/components/Crud/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { Card } from 'ant-design-vue' 3 | import { BasicForm } from '@/components/Form' 4 | import { formSchemas } from './_mockData' 5 | 6 | export default defineComponent({ 7 | name: 'Crud', 8 | setup() { 9 | return () => ( 10 |
11 | 12 | 13 | 14 |
15 | ) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /src/components/Crud/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue' 2 | import type { TableConfigType } from './types/crud' 3 | 4 | export const basicProps = { 5 | // Whether to show the aside tree 6 | showTree: { 7 | type: Boolean as PropType, 8 | default: false 9 | }, 10 | // Whether to show the search form 11 | showForm: { 12 | type: Boolean as PropType, 13 | default: true 14 | }, 15 | // Section table configuration 16 | tableConfig: { 17 | type: Object as PropType, 18 | default: () => ({}) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Crud/src/types/crud.ts: -------------------------------------------------------------------------------- 1 | export interface TableConfigType { 2 | // Whether to show the index column 3 | showIndex?: boolean 4 | // Whether to show the selection column 5 | showSelection?: boolean 6 | // Whether to show the pagination 7 | showPagination?: boolean 8 | } 9 | -------------------------------------------------------------------------------- /src/components/DndNode/index.ts: -------------------------------------------------------------------------------- 1 | import DndNode from './src/DndNode' 2 | 3 | export { DndNode } 4 | -------------------------------------------------------------------------------- /src/components/DndNode/src/types.ts: -------------------------------------------------------------------------------- 1 | export enum handlerEnum { 2 | n = 'tm', 3 | e = 'mr', 4 | s = 'bm', 5 | w = 'ml', 6 | nw = 'tl', 7 | ne = 'tr', 8 | se = 'br', 9 | sw = 'bl' 10 | } 11 | 12 | export type handlerType = 'n' | 'e' | 's' | 'w' | 'nw' | 'ne' | 'se' | 'sw' 13 | 14 | export interface ElementState { 15 | x: number 16 | y: number 17 | z?: number | 'auto' 18 | w: number 19 | h: number 20 | active: boolean 21 | type: 'text' | 'image' 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | import BasicForm from './src/index' 2 | 3 | export { BasicForm } 4 | -------------------------------------------------------------------------------- /src/components/Form/src/compoMap.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'vue' 2 | import { 3 | AutoComplete, 4 | Cascader, 5 | Checkbox, 6 | DatePicker, 7 | Input, 8 | InputNumber, 9 | Radio, 10 | Rate, 11 | Select, 12 | Slider, 13 | Switch, 14 | TimePicker, 15 | Transfer, 16 | Tree, 17 | TreeSelect 18 | } from 'ant-design-vue' 19 | import { 20 | ApiCascader, 21 | ApiCheckboxGroup, 22 | ApiRadioGroup, 23 | ApiSelect, 24 | ApiTransfer, 25 | ApiTree, 26 | ApiTreeSelect 27 | } from '../../ApiCompo' 28 | 29 | export const compoMap = new Map([ 30 | ['AutoComplete', AutoComplete], 31 | ['Cascader', Cascader], 32 | ['ApiCascader', ApiCascader], 33 | ['Checkbox', Checkbox], 34 | ['CheckboxGroup', Checkbox.Group], 35 | ['ApiCheckboxGroup', ApiCheckboxGroup], 36 | ['DatePicker', DatePicker], 37 | ['RangePicker', DatePicker.RangePicker], 38 | ['Input', Input], 39 | ['InputPassword', Input.Password], 40 | ['InputTextArea', Input.TextArea], 41 | ['InputNumber', InputNumber], 42 | ['RadioGroup', Radio.Group], 43 | ['ApiRadioGroup', ApiRadioGroup], 44 | ['Rate', Rate], 45 | ['Select', Select], 46 | ['ApiSelect', ApiSelect], 47 | ['Slider', Slider], 48 | ['Switch', Switch], 49 | ['TimePicker', TimePicker], 50 | ['TimeRangePicker', TimePicker.TimeRangePicker], 51 | ['Transfer', Transfer], 52 | ['ApiTransfer', ApiTransfer], 53 | ['Tree', Tree], 54 | ['ApiTree', ApiTree], 55 | ['TreeSelect', TreeSelect], 56 | ['ApiTreeSelect', ApiTreeSelect] 57 | ]) 58 | -------------------------------------------------------------------------------- /src/components/Form/src/constant.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentType } from './types' 2 | 3 | export const BASIC_COL_LEN = 24 4 | export const MINI_ACTION_COL_LEN = 4 5 | 6 | export const DEFAULT_VALUE_COMPONENTS = ['Input', 'InputPassword', 'InputNumber', 'InputSearch', 'InputTextArea'] 7 | 8 | export const DATE_COMPONENTS = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'] 9 | 10 | export const INPUT_COMPONENTS = ['Input', 'InputPassword', 'InputNumber', 'InputSearch', 'InputTextArea'] 11 | 12 | export const NO_AUTO_LINK_COMPONENTS: ComponentType[] = [ 13 | // 'Upload', 14 | // 'ApiTransfer', 15 | // 'ApiTree', 16 | // 'ApiTreeSelect', 17 | // 'ApiRadioGroup', 18 | // 'ApiCascader', 19 | // 'AutoComplete', 20 | // 'RadioButtonGroup', 21 | // 'ImageUpload', 22 | // 'ApiSelect' 23 | ] 24 | -------------------------------------------------------------------------------- /src/components/Form/src/index.less: -------------------------------------------------------------------------------- 1 | .basic-form-wrapper { 2 | 3 | .ant-form-item { 4 | margin-bottom: 22px; 5 | 6 | .suffix-item { 7 | .ant-form-item-children { 8 | display: flex; 9 | } 10 | 11 | .suffix-cont { 12 | display: inline-flex; 13 | align-items: center; 14 | margin-top: 1px; 15 | padding-left: 6px; 16 | line-height: 1; 17 | } 18 | } 19 | 20 | .form-advanced { 21 | margin-inline-start: 4px !important; 22 | transform: rotate(0deg); 23 | transform-origin: center center; 24 | transition: all 0.1s ease 0.1s; 25 | 26 | &.active { 27 | transform: rotate(-180deg); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Iframe/index.ts: -------------------------------------------------------------------------------- 1 | import IframeWrapper from './src/IframeWrapper' 2 | 3 | export { IframeWrapper } 4 | -------------------------------------------------------------------------------- /src/components/LogicFlow/index.ts: -------------------------------------------------------------------------------- 1 | import Approve from './src/Approve' 2 | import Bpmn from './src/Bpmn' 3 | 4 | export { Approve, Bpmn } 5 | -------------------------------------------------------------------------------- /src/components/LogicFlow/src/Approve/components/NodePanel/index.module.less: -------------------------------------------------------------------------------- 1 | .node-panel { 2 | z-index: 99; 3 | position: absolute; 4 | top: 10px; 5 | left: 10px; 6 | width: 70px; 7 | padding: 20px 10px; 8 | background-color: white; 9 | box-shadow: 0 0 10px 1px rgb(228, 224, 219); 10 | border-radius: 6px; 11 | text-align: center; 12 | 13 | .node-item { 14 | display: inline-block; 15 | margin-bottom: 20px; 16 | } 17 | 18 | .name { 19 | text-align: center; 20 | font-size: 12px; 21 | margin-top: 5px; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/LogicFlow/src/Approve/components/NodePanel/index.tsx: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue' 2 | import type LogicFlow from '@logicflow/core' 3 | import type { HtmlNodeConfig } from '../../type' 4 | import { defineComponent } from 'vue' 5 | import { approveNodes } from '../../data' 6 | import styles from './index.module.less' 7 | 8 | export default defineComponent({ 9 | name: 'NodePanel', 10 | props: { 11 | lf: { 12 | type: Object as PropType, 13 | default: () => ({}) 14 | } 15 | }, 16 | setup(props) { 17 | return () => ( 18 |
19 | {approveNodes.map((node: HtmlNodeConfig) => { 20 | return ( 21 |
22 |
{ 25 | props.lf.value.dnd.startDrag({ 26 | type: node.type, 27 | text: node.label 28 | }) 29 | }} 30 | /> 31 |
{node.label}
32 |
33 | ) 34 | })} 35 |
36 | ) 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /src/components/LogicFlow/src/Approve/type.ts: -------------------------------------------------------------------------------- 1 | export type HtmlNodeConfig = { 2 | type: string 3 | label: string 4 | style: { 5 | width: string 6 | height: string 7 | borderRadius?: string 8 | border: string 9 | transform?: string 10 | } 11 | } 12 | export type IApproveUser = { 13 | label: string 14 | value: string 15 | } 16 | export type nodeProperty = { 17 | labelColor: string 18 | approveTypeLabel: string 19 | approveType: string 20 | } 21 | -------------------------------------------------------------------------------- /src/components/LogicFlow/src/Bpmn/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onMounted, ref } from 'vue' 2 | import LogicFlow from '@logicflow/core' 3 | import { BpmnElement, Control, Menu, SelectionSelect } from '@logicflow/extension' 4 | import BpmnPattern from './components/Pattern' 5 | import '@logicflow/extension/lib/style/index.css' 6 | import '@logicflow/core/dist/style/index.css' 7 | 8 | const config = { 9 | stopScrollGraph: true, 10 | stopZoomGraph: true, 11 | metaKeyMultipleSelected: true, 12 | grid: { 13 | size: 10, 14 | type: 'dot' 15 | }, 16 | keyboard: { 17 | enabled: true 18 | }, 19 | snapline: true 20 | } 21 | 22 | export default defineComponent({ 23 | name: 'Bpmn', 24 | setup() { 25 | const lf = ref(null) as unknown as LogicFlow 26 | 27 | onMounted(() => { 28 | LogicFlow.use(BpmnElement) 29 | LogicFlow.use(Control) 30 | LogicFlow.use(Menu) 31 | LogicFlow.use(SelectionSelect) 32 | const lfInstance = new LogicFlow({ 33 | ...config, 34 | container: document.querySelector('#graphBpmn') as HTMLElement 35 | }) 36 | lfInstance.render() 37 | lf.value = lfInstance 38 | }) 39 | 40 | return () => ( 41 |
42 |
43 |
{lf.value && }
44 |
45 | ) 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /src/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | import Menu from './src' 2 | 3 | export { Menu } 4 | -------------------------------------------------------------------------------- /src/components/Menu/src/components/MenuItemCont.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties } from 'vue' 2 | import { defineComponent, computed, unref } from 'vue' 3 | import { useI18n } from 'vue-i18n' 4 | import { menuItemContentProps } from '../props' 5 | import SvgIcon from '@/components/SvgIcon' 6 | 7 | export default defineComponent({ 8 | name: 'MenuItemCont', 9 | components: { SvgIcon }, 10 | props: menuItemContentProps, 11 | setup(props) { 12 | const { t } = useI18n() 13 | 14 | const getIcon = computed(() => props.item?.icon as string) 15 | const getName = computed(() => props.item?.name) 16 | const getHideName = computed(() => props.collapsed && !props.showTitle) 17 | const getContStyle = computed( 18 | (): CSSProperties => ({ 19 | marginLeft: '8px', 20 | transition: 'all 0.3s ease' 21 | }) 22 | ) 23 | 24 | return () => ( 25 | 26 | {unref(getIcon) ? : <>} 27 | 28 | {t(unref(getName))} 29 | 30 | 31 | ) 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /src/components/Menu/src/components/MenuItems.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { MenuItem } from 'ant-design-vue' 3 | 4 | import { menuItemProps } from '../props' 5 | import MenuItemCont from './MenuItemCont' 6 | 7 | export default defineComponent({ 8 | name: 'MenuItems', 9 | props: menuItemProps, 10 | render() { 11 | const { item, collapsed, showTitle } = this 12 | return ( 13 | 14 | 15 | 16 | ) 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /src/components/Menu/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface MenuState { 2 | // array with the keys of currently opened sub menus 3 | openKeys: string[] 4 | 5 | // array with the keys of currently selected menu items 6 | selectedKeys: string[] 7 | 8 | // array with the keys of currently opened sub menus with collapsed status 9 | collapsedOpenKeys: string[] 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Page/index.ts: -------------------------------------------------------------------------------- 1 | import PageWrapper from './src/PageWrapper' 2 | 3 | export { PageWrapper } 4 | -------------------------------------------------------------------------------- /src/components/Page/src/compo.module.less: -------------------------------------------------------------------------------- 1 | .compo_page-wrapper { 2 | 3 | .page-header { 4 | width: 100%; 5 | min-height: 48px; 6 | padding: 16px 24px; 7 | margin-bottom: 12px; 8 | box-sizing: border-box; 9 | 10 | &-name { 11 | margin-bottom: 4px; 12 | font-size: 16px; 13 | font-weight: 600; 14 | 15 | svg { 16 | margin-right: 6px; 17 | } 18 | } 19 | 20 | } 21 | 22 | .page-content { 23 | min-height: 420px 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/RichText/index.ts: -------------------------------------------------------------------------------- 1 | import RichTextInput from './src/RichTextInput' 2 | import RichTextSetting from './src/RichTextSetting' 3 | 4 | export { RichTextInput, RichTextSetting } 5 | -------------------------------------------------------------------------------- /src/components/Scrollbar/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description referenced from element-ui/scrollbar 3 | * @link see: https://github.com/ElemeFE/element/tree/dev/packages/scrollbar 4 | */ 5 | 6 | import Scrollbar from './src' 7 | 8 | export default Scrollbar 9 | -------------------------------------------------------------------------------- /src/components/Scrollbar/src/index.less: -------------------------------------------------------------------------------- 1 | .scrollbar { 2 | position: relative; 3 | height: 100%; 4 | overflow: hidden; 5 | 6 | &__wrap { 7 | height: 100%; 8 | overflow: auto; 9 | 10 | &--hidden-default { 11 | scrollbar-width: none; 12 | 13 | &::-webkit-scrollbar { 14 | display: none; 15 | width: 0; 16 | height: 0; 17 | opacity: 0%; 18 | } 19 | } 20 | } 21 | 22 | &__thumb { 23 | position: relative; 24 | display: block; 25 | width: 0; 26 | height: 0; 27 | cursor: pointer; 28 | background-color: rgb(144 147 153 / 30%); 29 | border-radius: inherit; 30 | transition: 0.3s background-color; 31 | 32 | &:hover { 33 | background-color: rgb(144 147 153 / 50%); 34 | } 35 | } 36 | 37 | &__bar { 38 | position: absolute; 39 | right: 2px; 40 | bottom: 2px; 41 | z-index: 1; 42 | border-radius: 4px; 43 | opacity: 0%; 44 | transition: opacity 80ms ease; 45 | 46 | &.is-vertical { 47 | top: 2px; 48 | width: 6px; 49 | 50 | & > div { 51 | width: 100%; 52 | } 53 | } 54 | 55 | &.is-horizontal { 56 | left: 2px; 57 | height: 6px; 58 | 59 | & > div { 60 | height: 100%; 61 | } 62 | } 63 | } 64 | 65 | &:active, 66 | &:focus, 67 | &:hover { 68 | > .scrollbar__bar { 69 | opacity: 100%; 70 | transition: opacity 340ms ease-out; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/Scrollbar/src/types.ts: -------------------------------------------------------------------------------- 1 | interface BarMapItem { 2 | offset: string 3 | scroll: string 4 | scrollSize: string 5 | size: string 6 | key: string 7 | axis: string 8 | client: string 9 | direction: string 10 | } 11 | 12 | export interface BarMap { 13 | vertical: BarMapItem 14 | horizontal: BarMapItem 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Scrollbar/src/util.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { BarMap } from './types' 3 | 4 | export const BAR_MAP: BarMap = { 5 | vertical: { 6 | offset: 'offsetHeight', 7 | scroll: 'scrollTop', 8 | scrollSize: 'scrollHeight', 9 | size: 'height', 10 | key: 'vertical', 11 | axis: 'Y', 12 | client: 'clientY', 13 | direction: 'top' 14 | }, 15 | horizontal: { 16 | offset: 'offsetWidth', 17 | scroll: 'scrollLeft', 18 | scrollSize: 'scrollWidth', 19 | size: 'width', 20 | key: 'horizontal', 21 | axis: 'X', 22 | client: 'clientX', 23 | direction: 'left' 24 | } 25 | } 26 | 27 | // @ts-ignore 28 | export function renderThumbStyle({ move, size, bar }) { 29 | const style = {} as any 30 | const translate = `translate${bar.axis}(${move}%)` 31 | 32 | style[bar.size] = size 33 | style.transform = translate 34 | style.msTransform = translate 35 | style.webkitTransform = translate 36 | 37 | return style 38 | } 39 | 40 | function extend(to: T, from: K): T & K { 41 | return Object.assign(to, from) 42 | } 43 | 44 | export function toObject(arr: Array): Recordable { 45 | const res = {} 46 | for (let i = 0; i < arr.length; i++) { 47 | if (arr[i]) { 48 | extend(res, arr[i]) 49 | } 50 | } 51 | return res 52 | } 53 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.module.less: -------------------------------------------------------------------------------- 1 | .svg-icon { 2 | display: inline-block; 3 | vertical-align: -0.15em; 4 | fill: currentColor; 5 | overflow: hidden; 6 | } -------------------------------------------------------------------------------- /src/components/SvgIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties } from 'vue' 2 | import { defineComponent, computed, unref } from 'vue' 3 | import styles from './index.module.less' 4 | 5 | export default defineComponent({ 6 | name: 'SvgIcon', 7 | props: { 8 | prefix: { 9 | type: String, 10 | default: 'icon' 11 | }, 12 | name: { 13 | type: String, 14 | required: true 15 | }, 16 | size: { 17 | type: [Number, String], 18 | default: 16 19 | } 20 | }, 21 | setup(props) { 22 | const symbolId = computed(() => `#${props.prefix}-${props.name}`) 23 | const iconStyle = computed((): CSSProperties => { 24 | let size = `${props.size}` 25 | size = `${size.replace('px', '')}px` 26 | return { 27 | width: size, 28 | height: size 29 | } 30 | }) 31 | 32 | return () => ( 33 | 36 | ) 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /src/components/Table/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/components/Table/index.ts -------------------------------------------------------------------------------- /src/components/Table/src/compoMap.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'vue' 2 | import { 3 | AutoComplete, 4 | Checkbox, 5 | DatePicker, 6 | Input, 7 | InputNumber, 8 | Radio, 9 | Select, 10 | Switch, 11 | TimePicker 12 | } from 'ant-design-vue' 13 | import { ApiCheckboxGroup, ApiRadioGroup, ApiSelect, ApiTreeSelect } from '../../ApiCompo' 14 | 15 | export const compoMap = new Map([ 16 | ['AutoComplete', AutoComplete], 17 | ['Checkbox', Checkbox], 18 | ['CheckboxGroup', Checkbox.Group], 19 | ['ApiCheckboxGroup', ApiCheckboxGroup], 20 | ['DatePicker', DatePicker], 21 | ['RangePicker', DatePicker.RangePicker], 22 | ['Input', Input], 23 | ['InputPassword', Input.Password], 24 | ['InputTextArea', Input.TextArea], 25 | ['InputNumber', InputNumber], 26 | ['RadioGroup', Radio.Group], 27 | ['ApiRadioGroup', ApiRadioGroup], 28 | ['Select', Select], 29 | ['ApiSelect', ApiSelect], 30 | ['Switch', Switch], 31 | ['TimePicker', TimePicker], 32 | ['TimeRangePicker', TimePicker.TimeRangePicker], 33 | ['ApiTreeSelect', ApiTreeSelect] 34 | ]) 35 | -------------------------------------------------------------------------------- /src/components/Table/src/components/EditableCell/index.module.less: -------------------------------------------------------------------------------- 1 | .editable-cell { 2 | position: relative; 3 | 4 | &__wrapper { 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | 9 | > .ant-select { 10 | min-width: calc(100% - 50px); 11 | } 12 | } 13 | 14 | &__action { 15 | &-icon { 16 | &:hover { 17 | transform: scale(1.2); 18 | 19 | svg { 20 | color: @primary-color; 21 | } 22 | } 23 | } 24 | } 25 | 26 | .ellipsis-cell { 27 | .cell-content { 28 | overflow-wrap: break-word; 29 | word-break: break-word; 30 | overflow: hidden; 31 | white-space: nowrap; 32 | text-overflow: ellipsis; 33 | } 34 | } 35 | 36 | &__normal { 37 | &-icon { 38 | position: absolute; 39 | top: 4px; 40 | right: 0; 41 | display: none; 42 | width: 20px; 43 | cursor: pointer; 44 | } 45 | } 46 | 47 | &:hover { 48 | .@{prefix-cls}__normal-icon { 49 | display: inline-block; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Table/src/components/TableAction.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/components/Table/src/components/TableAction.tsx -------------------------------------------------------------------------------- /src/components/Table/src/constant.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/components/Table/src/constant.ts -------------------------------------------------------------------------------- /src/components/Table/src/helper.ts: -------------------------------------------------------------------------------- 1 | export function generatePlaceholder(component: any) { 2 | if ( 3 | component.includes('Select') || 4 | component.includes('Picker') || 5 | component.includes('Cascader') || 6 | component.includes('Checkbox') || 7 | component.includes('Radio') || 8 | component.includes('Switch') 9 | ) { 10 | return '请选择' 11 | } else if (component.includes('Input')) { 12 | return '请输入' 13 | } else { 14 | return '' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useTableContext.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | import type { BasicTableProps, TableActionType } from '../types/table' 3 | import { provide, inject, type ComputedRef } from 'vue' 4 | 5 | const key = Symbol('basic-table') 6 | 7 | type Instance = TableActionType & { 8 | wrapRef: Ref> 9 | getBindValues: ComputedRef 10 | } 11 | 12 | type RetInstance = Omit & { 13 | getBindValues: ComputedRef 14 | } 15 | 16 | export function createTableContext(instance: Instance) { 17 | provide(key, instance) 18 | } 19 | 20 | export function useTableContext(): RetInstance { 21 | return inject(key) as RetInstance 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Table/src/index.module.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/components/Table/src/index.module.less -------------------------------------------------------------------------------- /src/components/Table/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Table } from 'ant-design-vue' 2 | -------------------------------------------------------------------------------- /src/components/Table/src/props.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/components/Table/src/props.ts -------------------------------------------------------------------------------- /src/components/Table/src/types/column.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from 'ant-design-vue/lib/form/interface' 2 | import type { ComponentProps, ComponentType } from './' 3 | 4 | export interface EditColumnType { 5 | editComponent?: ComponentType 6 | editRule?: Rule[] 7 | editComponentProps?: ComponentProps 8 | editRow?: boolean 9 | editable?: boolean 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Table/src/types/row.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | 3 | export type EditRecordRow = Partial< 4 | { 5 | onEdit: (editable: boolean, submit?: boolean) => Promise 6 | onValid: () => Promise 7 | editable: boolean 8 | onCancel: Fn 9 | onSubmit: Fn 10 | submitCbs: Fn[] 11 | cancelCbs: Fn[] 12 | validCbs: Fn[] 13 | editValueRefs: Recordable 14 | } & T 15 | > 16 | -------------------------------------------------------------------------------- /src/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | import UploadImage from './src/UploadImage' 2 | 3 | export { UploadImage } 4 | -------------------------------------------------------------------------------- /src/components/VueDrr/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/components/VueDrr/index.ts -------------------------------------------------------------------------------- /src/components/VueDrr/src/dom.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '@/utils/is' 2 | 3 | export function matchesSelectorToParentElements(el, selector, baseNode) { 4 | let node = el 5 | 6 | const matchesSelectorFunc = [ 7 | 'matches', 8 | 'webkitMatchesSelector', 9 | 'mozMatchesSelector', 10 | 'msMatchesSelector', 11 | 'oMatchesSelector' 12 | ].find(func => isFunction(node[func])) 13 | 14 | if (!isFunction(node[matchesSelectorFunc!])) return false 15 | 16 | do { 17 | if (node[matchesSelectorFunc!](selector)) return true 18 | if (node === baseNode) return false 19 | node = node.parentNode 20 | } while (node) 21 | 22 | return false 23 | } 24 | -------------------------------------------------------------------------------- /src/components/VueDrr/src/index.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties } from 'vue' 2 | import { defineComponent, reactive, unref, computed } from 'vue' 3 | import { props } from './props' 4 | 5 | export default defineComponent({ 6 | name: 'VueDrr', 7 | props, 8 | setup(props) { 9 | const config = reactive({ 10 | top: props.y, 11 | left: props.x, 12 | width: props.w, 13 | height: props.h, 14 | rotateAngle: props.angle, 15 | enabled: props.active, 16 | zIndex: props.z, 17 | resizing: false, 18 | dragging: false, 19 | rotating: false, 20 | handle: null, 21 | parentW: 9999, 22 | parentH: 9999, 23 | mouseX: 0, 24 | mouseY: 0, 25 | lastMouseX: 0, 26 | lastMouseY: 0, 27 | mouseOffX: 0, 28 | mouseOffY: 0, 29 | elmX: 0, 30 | elmY: 0, 31 | elmW: 0, 32 | elmH: 0 33 | }) 34 | 35 | const getWrapStyle = computed( 36 | (): CSSProperties => ({ 37 | top: config.top + 'px', 38 | left: config.left + 'px', 39 | width: config.width + 'px', 40 | height: config.height + 'px', 41 | transform: 'rotate(' + config.rotateAngle + 'deg)', 42 | zIndex: config.zIndex as CSSProperties['zIndex'], 43 | overflowY: props.overflowY 44 | }) 45 | ) 46 | 47 | return () =>
48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /src/components/Widgets/index.ts: -------------------------------------------------------------------------------- 1 | import Search from './src/Search' 2 | import Refresh from './src/Refresh' 3 | import FullScreen from './src/FullScreen' 4 | 5 | export { Search, Refresh, FullScreen } 6 | -------------------------------------------------------------------------------- /src/components/Widgets/src/FullScreen.tsx: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue' 2 | import type { MaybeElementRef } from '@vueuse/core' 3 | import { defineComponent, unref } from 'vue' 4 | import { Tooltip } from 'ant-design-vue' 5 | import { ExpandOutlined, CompressOutlined } from '@ant-design/icons-vue' 6 | import { useFullscreen } from '@vueuse/core' 7 | import { useI18n } from 'vue-i18n' 8 | 9 | export default defineComponent({ 10 | name: 'FullScreen', 11 | props: { 12 | targetElement: { 13 | type: Object as PropType, 14 | default: document.body 15 | } 16 | }, 17 | setup(props) { 18 | const { t } = useI18n() 19 | const { isFullscreen, toggle } = useFullscreen(props.targetElement) 20 | 21 | return () => ( 22 | 23 | 30 | {!unref(isFullscreen) ? : } 31 | 32 | 33 | ) 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /src/components/Widgets/src/Refresh.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { Tooltip } from 'ant-design-vue' 3 | import { SyncOutlined } from '@ant-design/icons-vue' 4 | import { useI18n } from 'vue-i18n' 5 | 6 | export default defineComponent({ 7 | name: 'Refresh', 8 | emits: ['refresh'], 9 | setup(_, { emit }) { 10 | const { t } = useI18n() 11 | 12 | return () => ( 13 | emit('refresh')}> 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/Widgets/src/Search.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { Tooltip } from 'ant-design-vue' 3 | import { SearchOutlined } from '@ant-design/icons-vue' 4 | import { useI18n } from 'vue-i18n' 5 | 6 | export default defineComponent({ 7 | name: 'Search', 8 | emits: ['toggle'], 9 | setup(_, { emit }) { 10 | const { t } = useI18n() 11 | 12 | return () => ( 13 | emit('toggle')}> 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/design/antd/index.less: -------------------------------------------------------------------------------- 1 | @import './menu.less'; 2 | 3 | .ant-drawer { 4 | &-body { 5 | padding: 0 !important; 6 | } 7 | } 8 | 9 | .ant-layout-sider{ 10 | 11 | &.ant-layout-sider-dark { 12 | background: @primary-dark-bg; 13 | } 14 | } 15 | 16 | .ant-btn-link { 17 | padding: 4px 6px; 18 | 19 | &:hover, 20 | &:focus { 21 | border: solid 1px transparent !important; 22 | } 23 | } 24 | 25 | .ant-table-thead { 26 | 27 | .sub-title { 28 | font-size: 12px; 29 | color: rgba(0, 0, 0, .4); 30 | } 31 | } 32 | 33 | .ant-layout-header { 34 | padding: 0 !important; 35 | } 36 | 37 | .ant-upload { 38 | width: 100% !important; 39 | } 40 | 41 | .ant-tag { 42 | 43 | &.ant-tag-checkable-checked { 44 | border: none; 45 | color: @white !important; 46 | 47 | & .tag-dot { 48 | background: @white; 49 | } 50 | 51 | & .anticon-close { 52 | color: @white; 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/design/antd/menu.less: -------------------------------------------------------------------------------- 1 | .ant-menu { 2 | &.ant-menu-horizontal { 3 | line-height: 46px !important; 4 | border-bottom: 0 !important; 5 | } 6 | 7 | &.ant-menu-dark { 8 | background: @primary-dark-bg !important; 9 | 10 | .ant-menu-sub { 11 | background: @submenu-dark-bg !important; 12 | } 13 | } 14 | 15 | .submenu-collapsed { 16 | padding-bottom: 0 !important; 17 | } 18 | 19 | .menu-item-collapsed { 20 | height: auto !important; 21 | padding-bottom: 0 !important; 22 | padding-inline: 0 !important; 23 | } 24 | 25 | .submenu-collapsed .ant-menu-submenu-title, 26 | .menu-item-collapsed .ant-menu-title-content { 27 | display: block; 28 | height: auto !important; 29 | line-height: initial !important; 30 | padding: 12px 0 !important; 31 | margin: 0; 32 | text-align: center !important; 33 | transition: all 0.2s ease; 34 | } 35 | 36 | .submenu-collapsed, 37 | .menu-item-collapsed { 38 | 39 | .menu-item-cont { 40 | display: block; 41 | 42 | .svg-icon { 43 | vertical-align: 0; 44 | margin-bottom: 6px; 45 | transition: all 0.2s; 46 | } 47 | 48 | &__name { 49 | display: block; 50 | height: 22px; 51 | line-height: 22px; 52 | margin-left: 0 !important; 53 | transition: all 0.2s !important; 54 | } 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/design/plugin.less: -------------------------------------------------------------------------------- 1 | // 视频播放器 2 | .video-js { 3 | .vjs-big-play-button { 4 | top: 45% !important; 5 | left: 45% !important; 6 | font-size: 2em !important; 7 | } 8 | } 9 | 10 | // 富文本编辑器 11 | .rich-text-wrapper { 12 | border: 1px solid #ccc; 13 | 14 | .toolbar-box { 15 | border-bottom: 1px solid #ccc; 16 | } 17 | } 18 | 19 | // 拖拽组件 20 | .vdr { 21 | .vdr-stick { 22 | border-radius: 4px; 23 | } 24 | } 25 | 26 | // 组织树 27 | .zm-tree-org { 28 | height: calc(100% - 32px) !important; 29 | padding: 14px 0 0 !important; 30 | } 31 | 32 | // nprogress 33 | #nprogress { 34 | pointer-events: none; 35 | 36 | .bar { 37 | position: fixed; 38 | z-index: 99999; 39 | top: 0; 40 | left: 0; 41 | width: 100%; 42 | height: 2px; 43 | opacity: 0.75; 44 | background-color: @primary-color; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/design/public.less: -------------------------------------------------------------------------------- 1 | span.icon-btn { 2 | display: flex; 3 | align-items: center; 4 | cursor: pointer; 5 | margin-left: 10px; 6 | } 7 | 8 | .batch-upload { 9 | height: auto!important; 10 | } 11 | 12 | .list-upload { 13 | .ant-upload { 14 | width: 100%; 15 | } 16 | 17 | .ant-btn { 18 | display: block; 19 | margin: 0 auto 8px; 20 | } 21 | } 22 | 23 | .feature-divider { 24 | height: 50%; 25 | margin: 0; 26 | border-color: rgba(0, 0, 0, .2); 27 | } 28 | 29 | .ant-layout-sider-light { 30 | border-right: solid 1px @border-color; 31 | 32 | .ant-menu-inline { 33 | border-right: none!important; 34 | } 35 | } 36 | 37 | // ScrollContainer组件样式 38 | .scroll-container { 39 | width: 100%; 40 | height: 100%; 41 | 42 | .scrollbar__wrap { 43 | margin-bottom: 18px !important; 44 | overflow-x: hidden; 45 | } 46 | 47 | .scrollbar__view { 48 | box-sizing: border-box; 49 | } 50 | } 51 | 52 | // Iframe container 样式 53 | .iframe-wrapper { 54 | background: @white; 55 | border-radius: 4px; 56 | box-sizing: border-box; 57 | 58 | .ant-spin-nested-loading { 59 | 60 | & >div>.ant-spin { 61 | max-height: 100% 62 | } 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/design/scroll-bar.less: -------------------------------------------------------------------------------- 1 | // Customize the scroll-bar style 2 | &::-webkit-scrollbar-track-piece { 3 | background-color: rgba(0, 0, 0, 0); 4 | border-left: 1px solid rgba(0, 0, 0, 0); 5 | } 6 | 7 | &::-webkit-scrollbar { 8 | width: 6px; 9 | height: 6px; 10 | -webkit-border-radius: 3px; 11 | -moz-border-radius: 3px; 12 | border-radius: 3px; 13 | } 14 | 15 | &::-webkit-scrollbar-thumb { 16 | background-color: rgba(0, 0, 0, 0.2); 17 | background-clip: padding-box; 18 | -webkit-border-radius: 3px; 19 | -moz-border-radius: 3px; 20 | border-radius: 3px; 21 | min-height: 28px; 22 | } 23 | 24 | &::-webkit-scrollbar-thumb:hover { 25 | background-color: rgba(0, 0, 0, 0.3); 26 | -webkit-border-radius: 3px; 27 | -moz-border-radius: 3px; 28 | border-radius: 3px; 29 | } 30 | -------------------------------------------------------------------------------- /src/design/transition/base.less: -------------------------------------------------------------------------------- 1 | .transition-default() { 2 | &-enter-active, 3 | &-leave-active { 4 | transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; 5 | } 6 | 7 | &-move { 8 | transition: transform 0.4s; 9 | } 10 | } 11 | 12 | .expand-transition { 13 | .transition-default(); 14 | } 15 | 16 | .expand-x-transition { 17 | .transition-default(); 18 | } 19 | -------------------------------------------------------------------------------- /src/design/transition/index.less: -------------------------------------------------------------------------------- 1 | @import './base.less'; 2 | @import './fade.less'; 3 | @import './scale.less'; 4 | @import './scroll.less'; 5 | @import './slide.less'; 6 | @import './zoom.less'; 7 | 8 | .collapse-transition { 9 | transition: 10 | 0.2s height ease-in-out, 11 | 0.2s padding-top ease-in-out, 12 | 0.2s padding-bottom ease-in-out; 13 | } 14 | -------------------------------------------------------------------------------- /src/design/transition/scale.less: -------------------------------------------------------------------------------- 1 | .scale-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave, 6 | &-leave-to { 7 | transform: scale(0); 8 | opacity: 0; 9 | } 10 | } 11 | 12 | .scale-rotate-transition { 13 | .transition-default(); 14 | 15 | &-enter-from, 16 | &-leave, 17 | &-leave-to { 18 | transform: scale(0) rotate(-45deg); 19 | opacity: 0; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/design/transition/scroll.less: -------------------------------------------------------------------------------- 1 | .scroll-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | } 8 | 9 | &-enter-from { 10 | transform: translateY(-15px); 11 | } 12 | 13 | &-leave-to { 14 | transform: translateY(15px); 15 | } 16 | } 17 | 18 | .scroll-y-reverse-transition { 19 | .transition-default(); 20 | 21 | &-enter-from, 22 | &-leave-to { 23 | opacity: 0; 24 | } 25 | 26 | &-enter-from { 27 | transform: translateY(15px); 28 | } 29 | 30 | &-leave-to { 31 | transform: translateY(-15px); 32 | } 33 | } 34 | 35 | .scroll-x-transition { 36 | .transition-default(); 37 | 38 | &-enter-from, 39 | &-leave-to { 40 | opacity: 0; 41 | } 42 | 43 | &-enter-from { 44 | transform: translateX(-15px); 45 | } 46 | 47 | &-leave-to { 48 | transform: translateX(15px); 49 | } 50 | } 51 | 52 | .scroll-x-reverse-transition { 53 | .transition-default(); 54 | 55 | &-enter-from, 56 | &-leave-to { 57 | opacity: 0; 58 | } 59 | 60 | &-enter-from { 61 | transform: translateX(15px); 62 | } 63 | 64 | &-leave-to { 65 | transform: translateX(-15px); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/design/transition/slide.less: -------------------------------------------------------------------------------- 1 | .slide-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | transform: translateY(-15px); 7 | opacity: 0; 8 | } 9 | } 10 | 11 | .slide-y-reverse-transition { 12 | .transition-default(); 13 | 14 | &-enter-from, 15 | &-leave-to { 16 | transform: translateY(15px); 17 | opacity: 0; 18 | } 19 | } 20 | 21 | .slide-x-transition { 22 | .transition-default(); 23 | 24 | &-enter-from, 25 | &-leave-to { 26 | transform: translateX(-15px); 27 | opacity: 0; 28 | } 29 | } 30 | 31 | .slide-x-reverse-transition { 32 | .transition-default(); 33 | 34 | &-enter-from, 35 | &-leave-to { 36 | transform: translateX(15px); 37 | opacity: 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/design/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: opacity 0.1 ease-in-out, 5 | transform 0.15s ease-out; 6 | } 7 | 8 | .zoom-out-enter-from, 9 | .zoom-out-leave-to { 10 | transform: scale(0); 11 | opacity: 0; 12 | } 13 | 14 | // zoom-fade 15 | .zoom-fade-enter-active, 16 | .zoom-fade-leave-active { 17 | transition: 18 | transform 0.2s, 19 | opacity 0.3s ease-out; 20 | } 21 | 22 | .zoom-fade-enter-from { 23 | transform: scale(0.92); 24 | opacity: 0; 25 | } 26 | 27 | .zoom-fade-leave-to { 28 | transform: scale(1.06); 29 | opacity: 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/design/variable/breakpoint.less: -------------------------------------------------------------------------------- 1 | // Small screen / tablet 2 | @screen-sm: 576px; 3 | @screen-sm-min: @screen-sm; 4 | 5 | // Medium screen / desktop 6 | @screen-md: 768px; 7 | @screen-md-min: @screen-md; 8 | 9 | // Large screen / wide desktop 10 | @screen-lg: 992px; 11 | @screen-lg-min: @screen-lg; 12 | 13 | // Extra large screen / full hd 14 | @screen-xl: 1200px; 15 | @screen-xl-min: @screen-xl; 16 | 17 | // Extra extra large screen / large desktop 18 | @screen-2xl: 1600px; 19 | @screen-2xl-min: @screen-2xl; 20 | 21 | @screen-xs-max: (@screen-sm-min - 1px); 22 | @screen-sm-max: (@screen-md-min - 1px); 23 | @screen-md-max: (@screen-lg-min - 1px); 24 | @screen-lg-max: (@screen-xl-min - 1px); 25 | @screen-xl-max: (@screen-2xl-min - 1px); -------------------------------------------------------------------------------- /src/design/variable/color.less: -------------------------------------------------------------------------------- 1 | @white: #fff; 2 | @primary-color: #1890ff; 3 | @border-color: rgba(5, 5, 5, .06); 4 | 5 | @primary-dark-bg: #263238; 6 | @submenu-dark-bg: #202b30; 7 | 8 | @dark-mode-bg: #141414; 9 | @dark-mode-border-color: #424242; 10 | @dark-mode-border: 1px solid @dark-mode-border-color; 11 | @dark-mode-color: rgba(255, 255, 255, 0.85); 12 | @dark-mode-hover-color: rgba(255, 255, 255, 0.65); 13 | -------------------------------------------------------------------------------- /src/design/variable/index.less: -------------------------------------------------------------------------------- 1 | @import './color.less'; 2 | @import './breakpoint.less'; 3 | 4 | @namespace: v-desg; 5 | 6 | @layout-hybrid-sider-fixed-z-index: 550; 7 | 8 | -------------------------------------------------------------------------------- /src/enums/appEnum.ts: -------------------------------------------------------------------------------- 1 | export const SIDE_BAR_MIN_WIDTH = 48 2 | export const SIDE_BAR_SHOW_TITLE_MIN_WIDTH = 80 3 | 4 | // App mode enum 5 | export enum AppModeEnum { 6 | DARK = 'dark', 7 | LIGHT = 'light' 8 | } 9 | 10 | // Menu theme enum 11 | export enum ThemeEnum { 12 | DARK = 'dark', 13 | LIGHT = 'light' 14 | } 15 | 16 | // App locale enum 17 | export enum LocaleEnum { 18 | ZH_CN = 'zh_CN', 19 | Zh_TW = 'zh_TW', 20 | EN_US = 'en_US' 21 | } 22 | 23 | // Page switching animation 24 | export enum PageTransitionEnum { 25 | FADE = 'fade', 26 | FADE_SLIDE = 'fade-slide', 27 | FADE_SCALE = 'fade-scale', 28 | FADE_BOTTOM = 'fade-bottom', 29 | ZOOM_FADE = 'zoom-fade', 30 | ZOOM_OUT = 'zoom-out' 31 | } 32 | 33 | // Permission mode 34 | export enum PermissionModeEnum { 35 | // Route mapping 36 | MAPPING = 'MAPPING', 37 | // The back-end response 38 | BACKEND = 'BACKEND' 39 | } 40 | -------------------------------------------------------------------------------- /src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | export const TOKEN_KEY = 'TOKEN_KEY' 2 | 3 | export const LOCALE_KEY = 'LOCALE_KEY' 4 | 5 | export const LOCK_INFO_KEY = 'LOCK_INFO_KEY' 6 | 7 | export const USER_INFO_KEY = 'USER_INFO_KEY' 8 | 9 | export const APP_CONFIG_KEY = 'APP_CONFIG_KEY' 10 | 11 | export const APP_MODE_KEY = 'APP_MODE_KEY' 12 | 13 | export const APP_TAGS_KEY = 'APP_TAGS_KEY' 14 | 15 | export const APP_LOCAL_CACHE_KEY = 'APP_LOCAL_CACHE_KEY' 16 | 17 | export const APP_SESSION_CACHE_KEY = 'APP_SESSION_CACHE_KEY' 18 | 19 | export enum CacheTypeEnum { 20 | SESSION, 21 | LOCAL 22 | } 23 | -------------------------------------------------------------------------------- /src/enums/exceptionEnum.ts: -------------------------------------------------------------------------------- 1 | export enum ExceptionEnum { 2 | // page not access 3 | PAGE_NOT_ACCESS = 403, 4 | 5 | // page not found 6 | PAGE_NOT_FOUND = 404, 7 | 8 | // server error 9 | SERVER_ERROR = 500 10 | } 11 | -------------------------------------------------------------------------------- /src/enums/menuEnum.ts: -------------------------------------------------------------------------------- 1 | // Menu typings 2 | export enum MenuTypeEnum { 3 | SIDER_MENU = 'sider-menu', 4 | 5 | HEADER_MENU = 'header-menu', 6 | 7 | HYBRID_MENU = 'hybrid-menu' 8 | } 9 | 10 | // Menu modes 11 | export enum MenuModeEnum { 12 | VERTICAL = 'vertical', 13 | 14 | HORIZONTAL = 'horizontal', 15 | 16 | INLINE = 'inline' 17 | } 18 | 19 | // Menu folding button location 20 | export enum MenuFoldBtnEnum { 21 | NONE = 'none', 22 | 23 | HEADER = 'header', 24 | 25 | SIDER = 'sider' 26 | } 27 | -------------------------------------------------------------------------------- /src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // super admin 3 | SUPER = 'super', 4 | 5 | // tester 6 | TEST = 'test' 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/core/useRefs.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | import { ref, onBeforeUpdate } from 'vue' 3 | 4 | export function useRefs(): [Ref, (index: number) => (el: HTMLElement) => void] { 5 | const refs = ref([]) as Ref 6 | 7 | onBeforeUpdate(() => { 8 | refs.value = [] 9 | }) 10 | 11 | const setRefs = (index: number) => (el: HTMLElement) => { 12 | refs.value[index] = el 13 | } 14 | 15 | return [refs, setRefs] 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/core/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch } from 'vue' 2 | import { tryOnUnmounted } from '@vueuse/core' 3 | import { isFunction } from '@/utils/is' 4 | 5 | export function useTimeoutFn(handle: Fn, wait: number, native = false) { 6 | if (!isFunction(handle)) { 7 | throw new Error('handle is not Function!') 8 | } 9 | 10 | const { readyRef, stop, start } = useTimeoutRef(wait) 11 | 12 | if (native) { 13 | handle() 14 | } else { 15 | watch( 16 | readyRef, 17 | maturity => { 18 | maturity && handle() 19 | }, 20 | { immediate: false } 21 | ) 22 | } 23 | 24 | return { readyRef, stop, start } 25 | } 26 | 27 | export function useTimeoutRef(wait: number) { 28 | const readyRef = ref(false) 29 | 30 | let timer: TimeoutHandle 31 | 32 | function stop(): void { 33 | readyRef.value = false 34 | timer && window.clearTimeout(timer) 35 | } 36 | 37 | function start(): void { 38 | stop() 39 | timer = setTimeout(() => { 40 | readyRef.value = true 41 | }, wait) 42 | } 43 | 44 | start() 45 | 46 | tryOnUnmounted(stop) 47 | 48 | return { readyRef, stop, start } 49 | } 50 | -------------------------------------------------------------------------------- /src/hooks/event/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import type { AnyFunction } from '@/types/utils' 2 | import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core' 3 | 4 | interface UseWindowSizeOptions { 5 | wait?: number 6 | once?: boolean 7 | immediate?: boolean 8 | listenerOptions?: AddEventListenerOptions | boolean 9 | } 10 | 11 | function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) { 12 | const { wait = 150, immediate } = options 13 | let handler = () => { 14 | fn() 15 | } 16 | const handleSize = useDebounceFn(handler, wait) 17 | handler = handleSize 18 | 19 | const start = () => { 20 | if (immediate) { 21 | handler() 22 | } 23 | window.addEventListener('resize', handler) 24 | } 25 | 26 | const stop = () => { 27 | window.removeEventListener('resize', handler) 28 | } 29 | 30 | tryOnMounted(() => { 31 | start() 32 | }) 33 | 34 | tryOnUnmounted(() => { 35 | stop() 36 | }) 37 | return { start, stop } 38 | } 39 | 40 | export { useWindowSizeFn, type UseWindowSizeOptions } 41 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baimingxuan/vue3-admin-design/d368ff2f1680414ae9b9fed93ced7cfba8b76b7f/src/hooks/index.ts -------------------------------------------------------------------------------- /src/hooks/setting/useBaseSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { useAppStore } from '@/stores/modules/app' 3 | import type { AppModeEnum } from '@/enums/appEnum' 4 | 5 | export function useBaseSetting() { 6 | const appStore = useAppStore() 7 | 8 | const getAppMode = computed(() => appStore.getAppMode) 9 | 10 | const getThemeColor = computed(() => appStore.getAppConfig.themeColor) 11 | 12 | const getTagsCached = computed(() => appStore.getAppConfig.tagsCached) 13 | 14 | const getLockScreenTime = computed(() => appStore.getAppConfig.lockScreenTime) 15 | 16 | const getShowFooter = computed(() => appStore.getAppConfig.showFooter) 17 | 18 | const getColorWeak = computed(() => appStore.getAppConfig.colorWeak) 19 | 20 | const getGrayMode = computed(() => appStore.getAppConfig.grayMode) 21 | 22 | function setAppMode(mode: AppModeEnum) { 23 | appStore.setAppMode(mode) 24 | } 25 | 26 | return { 27 | setAppMode, 28 | getAppMode, 29 | getThemeColor, 30 | getTagsCached, 31 | getLockScreenTime, 32 | getShowFooter, 33 | getColorWeak, 34 | getGrayMode 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/setting/useDarkModeSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed, unref } from 'vue' 2 | import { theme } from 'ant-design-vue/lib' 3 | import { ThemeEnum } from '@/enums/appEnum' 4 | import { useBaseSetting } from './useBaseSetting' 5 | 6 | export function useDarkModeSetting() { 7 | const { getAppMode } = useBaseSetting() 8 | const { defaultAlgorithm, darkAlgorithm } = theme 9 | 10 | const isDarkMode = computed(() => unref(getAppMode) === ThemeEnum.DARK) 11 | 12 | const getModeAlgorithm = computed(() => { 13 | return unref(isDarkMode) ? darkAlgorithm : defaultAlgorithm 14 | }) 15 | 16 | return { getModeAlgorithm, isDarkMode, getAppMode } 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/setting/useHeaderSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { useAppStore } from '@/stores/modules/app' 3 | 4 | export function useHeaderSetting() { 5 | const appStore = useAppStore() 6 | 7 | const getShowBreadCrumb = computed(() => appStore.getHeaderSetting.showBreadCrumb) 8 | 9 | const getShowTags = computed(() => appStore.getHeaderSetting.showTags) 10 | 11 | const getShowSearch = computed(() => appStore.getHeaderSetting.showSearch) 12 | 13 | const getShowLocale = computed(() => appStore.getHeaderSetting.showLocale) 14 | 15 | const getShowFullScreen = computed(() => appStore.getHeaderSetting.showFullScreen) 16 | 17 | const getShowDoc = computed(() => appStore.getHeaderSetting.showDoc) 18 | 19 | const getShowGithub = computed(() => appStore.getHeaderSetting.showGithub) 20 | 21 | return { 22 | getShowBreadCrumb, 23 | getShowTags, 24 | getShowSearch, 25 | getShowFullScreen, 26 | getShowLocale, 27 | getShowDoc, 28 | getShowGithub 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/hooks/setting/useLocaleSetting.ts: -------------------------------------------------------------------------------- 1 | import type { LocaleType } from '@/types' 2 | import { unref, computed } from 'vue' 3 | import { useI18n } from 'vue-i18n' 4 | import { setHtmlPageLang } from '@/locales/helper' 5 | import { genLangs } from '@/locales/langs' 6 | import { LOCALE_KEY } from '@/enums/cacheEnum' 7 | import { createLocalStorage } from '@/utils/cache' 8 | import { getBrowserLang } from '@/locales/helper' 9 | 10 | const ls = createLocalStorage() 11 | 12 | export function useLocaleSetting() { 13 | const { locale: currentLocale, setLocaleMessage } = useI18n() 14 | const getLocale = computed(() => ls.get(LOCALE_KEY) || getBrowserLang()) 15 | 16 | async function changeLocale(locale: LocaleType) { 17 | if (unref(currentLocale) === locale) { 18 | return locale 19 | } 20 | 21 | const langModule = genLangs(locale) 22 | if (!langModule) return 23 | 24 | setLocaleMessage(locale, langModule.message) 25 | 26 | ls.set(LOCALE_KEY, locale) 27 | setHtmlPageLang(locale) 28 | 29 | return locale 30 | } 31 | 32 | return { 33 | getLocale, 34 | changeLocale 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/setting/useTransitionSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { useAppStoreWithOut } from '@/stores/modules/app' 3 | 4 | export function useTransitionSetting() { 5 | const appStore = useAppStoreWithOut() 6 | 7 | const getOpenNProgress = computed(() => appStore.getTransitionSetting.openNProgress) 8 | 9 | const getOpenTransition = computed(() => appStore.getTransitionSetting.openTransition) 10 | 11 | const getBasicTransition = computed(() => appStore.getTransitionSetting.basicTransition) 12 | 13 | return { 14 | getOpenNProgress, 15 | getOpenTransition, 16 | getBasicTransition 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/web/useTitle.ts: -------------------------------------------------------------------------------- 1 | import { watch, unref } from 'vue' 2 | import { useTitle as usePageTitle } from '@vueuse/core' 3 | import { useRouter } from 'vue-router' 4 | import { useI18n } from 'vue-i18n' 5 | 6 | /** 7 | * Listening to page changes and dynamically changing site titles 8 | */ 9 | export function useTitle() { 10 | const { t } = useI18n() 11 | const { currentRoute } = useRouter() 12 | 13 | const pageTitle = usePageTitle() 14 | const appName = import.meta.env.VITE_APP_NAME 15 | 16 | watch( 17 | () => currentRoute.value.path, 18 | () => { 19 | const route = unref(currentRoute) 20 | 21 | if (route.name === 'RedirectTo') return 22 | 23 | const title = route.meta.title ? t(route.meta.title) : '' 24 | pageTitle.value = title ? `${title} | ${appName}` : appName 25 | }, 26 | { immediate: true } 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/layout/content/index.less: -------------------------------------------------------------------------------- 1 | .layout_content { 2 | position: relative; 3 | flex: 1 1 auto; 4 | min-height: 0; 5 | margin: 12px; 6 | // background: @white; 7 | 8 | &-loading { 9 | z-index: 9000; 10 | position: absolute; 11 | top: 200px; 12 | } 13 | } -------------------------------------------------------------------------------- /src/layout/content/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import Page from './components/Page' 3 | import Iframe from './components/Iframe' 4 | import './index.less' 5 | 6 | export default defineComponent({ 7 | name: 'LayoutContent', 8 | setup() { 9 | return () => ( 10 |
11 | 12 |