├── .babelrc ├── .browserslistrc ├── .cz-config.js ├── .editorconfig ├── .env ├── .env.development ├── .env.production ├── .eslintrc-auto-import.json ├── .eslintrc.js ├── .github └── workflows │ ├── dev.yml │ └── pro.yml ├── .gitignore ├── .husky ├── commit-msg ├── lintstagedrc.js └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── .stylelintignore ├── .vscode ├── file-notes.json └── settings.json ├── CHANGELOG.zh_CN.md ├── LICENSE ├── README.md ├── README.zh-CN.md ├── build ├── utils.ts └── vite │ ├── define │ └── index.ts │ ├── plugin │ ├── autoComponents.ts │ ├── autoImport.ts │ ├── buildInfo.ts │ ├── buildVisualizer.ts │ ├── generateNaiveDts.ts │ ├── html.ts │ ├── icons.ts │ ├── importImgs.ts │ ├── index.ts │ ├── jsx.ts │ ├── mock.ts │ ├── restart.ts │ └── setupName.ts │ ├── proxy.ts │ └── resolve │ ├── alias.ts │ └── index.ts ├── commitlint.config.js ├── index.html ├── mock ├── _createProductionServer.ts ├── config │ ├── data │ │ ├── backData.ts │ │ └── moveData.ts │ ├── icon.ts │ └── index.ts └── modules │ ├── permissions.ts │ └── test │ ├── err.ts │ └── method.ts ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public └── favicon.svg ├── src ├── App.vue ├── api │ ├── Api.ts │ ├── index.ts │ └── test.ts ├── assets │ ├── icon.json │ ├── images │ │ ├── login2Img.png │ │ ├── login_bg2.png │ │ ├── login_logo.png │ │ ├── public.png │ │ ├── title.png │ │ ├── user.png │ │ └── user1.png │ └── svg │ │ ├── books.svg │ │ ├── helpEmpty.svg │ │ ├── login-bg-dark.svg │ │ ├── login-bg.svg │ │ ├── login-box-bg.svg │ │ ├── login_fast.svg │ │ ├── logo-icon.svg │ │ ├── logo-sub-heading.svg │ │ ├── logo.svg │ │ ├── me.svg │ │ └── trash.svg ├── bootstrap │ └── index.ts ├── components │ ├── AsyncComp │ │ ├── index.ts │ │ └── src │ │ │ ├── errorComponent.tsx │ │ │ └── loadingComponent.tsx │ ├── Basic │ │ ├── index.ts │ │ └── src │ │ │ └── BasicTitle.vue │ ├── Content │ │ └── index.vue │ ├── Descriptions │ │ ├── index.ts │ │ └── src │ │ │ ├── index.vue │ │ │ ├── type.ts │ │ │ └── useDescription.ts │ ├── ErrorComponent │ │ └── index.tsx │ ├── Form │ │ ├── index.ts │ │ └── src │ │ │ ├── config │ │ │ └── type.ts │ │ │ ├── index.vue │ │ │ └── userForm.ts │ ├── Icon │ │ └── index.vue │ ├── JsonView │ │ └── index.vue │ ├── LoadingComponent │ │ └── index.tsx │ ├── Modal │ │ ├── index.ts │ │ └── src │ │ │ ├── index.vue │ │ │ ├── type.ts │ │ │ └── useModal.ts │ ├── SvgIcon │ │ └── index.vue │ ├── Table │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicTable.vue │ │ │ ├── components │ │ │ └── TableHeader.vue │ │ │ ├── hooks │ │ │ ├── default.config.ts │ │ │ ├── useFetch.ts │ │ │ └── userTable.ts │ │ │ └── type │ │ │ └── table.ts │ ├── codeEditor │ │ └── index.vue │ └── windowUtils │ │ └── index.vue ├── config │ ├── index.ts │ └── settings.ts ├── directive │ ├── index.ts │ └── modules │ │ └── test.ts ├── enum │ ├── axios.ts │ ├── locale.ts │ └── route.ts ├── hooks │ ├── router │ │ └── index.ts │ ├── useConfig.ts │ ├── useEnv.ts │ ├── useHttp.ts │ ├── useI18n.ts │ └── useLogin.ts ├── http │ ├── README.md │ ├── Tips.ts │ ├── addCancel.ts │ ├── axios.ts │ ├── cancel.ts │ └── index.ts ├── layouts │ ├── default │ │ ├── backTop │ │ │ └── index.vue │ │ ├── components │ │ │ ├── right │ │ │ │ ├── index.ts │ │ │ │ └── src │ │ │ │ │ ├── config.ts │ │ │ │ │ ├── fixedSettings.vue │ │ │ │ │ ├── language.vue │ │ │ │ │ ├── page │ │ │ │ │ ├── PageAnimation.vue │ │ │ │ │ ├── PageFun.vue │ │ │ │ │ ├── PageShow.vue │ │ │ │ │ ├── PageTheme.vue │ │ │ │ │ ├── copyOptions.ts │ │ │ │ │ └── openDrawer.vue │ │ │ │ │ ├── screen.vue │ │ │ │ │ ├── settings.vue │ │ │ │ │ └── user.vue │ │ │ └── settings │ │ │ │ ├── index.ts │ │ │ │ └── src │ │ │ │ ├── ColorSelect.vue │ │ │ │ ├── FormItem.tsx │ │ │ │ ├── PageInputNum.vue │ │ │ │ ├── PageLayout.vue │ │ │ │ ├── PageSelect.vue │ │ │ │ ├── PageSwitch.vue │ │ │ │ ├── props │ │ │ │ └── props.ts │ │ │ │ └── type │ │ │ │ └── type.ts │ │ ├── content │ │ │ └── index.vue │ │ ├── footer │ │ │ └── index.vue │ │ ├── header │ │ │ ├── fold.vue │ │ │ ├── index.vue │ │ │ └── right.vue │ │ ├── logo │ │ │ └── index.vue │ │ ├── menu │ │ │ └── index.vue │ │ ├── page │ │ │ ├── hock.tsx │ │ │ └── index.tsx │ │ ├── sider │ │ │ ├── index.vue │ │ │ └── trigger.vue │ │ ├── tabs │ │ │ ├── index.vue │ │ │ └── src │ │ │ │ ├── config │ │ │ │ └── index.tsx │ │ │ │ ├── suffix.tsx │ │ │ │ └── utils │ │ │ │ └── index.ts │ │ └── view.vue │ ├── error │ │ ├── 403.vue │ │ ├── 404.vue │ │ ├── 500.vue │ │ └── router404.vue │ ├── iframe │ │ └── index.vue │ ├── index.ts │ ├── refresh │ │ └── index.vue │ └── routerView │ │ └── index.vue ├── locales │ ├── index.ts │ ├── language │ │ ├── en │ │ │ └── layout.ts │ │ ├── ru │ │ │ └── layout.ts │ │ ├── zh_CN │ │ │ ├── layout.ts │ │ │ └── route.ts │ │ ├── zh_HK │ │ │ └── layout.ts │ │ └── zh_TW │ │ │ └── layout.ts │ └── useLocal.ts ├── main.ts ├── naive │ ├── config │ │ ├── Tabs.ts │ │ └── Theme.ts │ ├── index.ts │ └── style │ │ ├── dark.less │ │ ├── divider.less │ │ ├── index.less │ │ ├── init.css │ │ ├── layout.less │ │ ├── light.less │ │ ├── menu.less │ │ ├── table.less │ │ └── tabs.less ├── pinia │ ├── README.md │ ├── demo.ts │ ├── index.ts │ ├── modules │ │ ├── config.ts │ │ ├── ref.ts │ │ ├── routeStore.ts │ │ ├── theme.ts │ │ └── user.ts │ └── type │ │ └── user.ts ├── router │ ├── backRoutes │ │ └── index.ts │ ├── index.ts │ ├── moveRoutes │ │ ├── index.ts │ │ └── modules │ │ │ ├── errPage.ts │ │ │ └── system.ts │ ├── roleRoutes │ │ ├── home.ts │ │ ├── index.ts │ │ └── modules │ │ │ └── system.ts │ ├── routes │ │ └── basic.ts │ └── utils │ │ ├── afterEach.ts │ │ ├── beforeEach.ts │ │ ├── createRoutes.ts │ │ ├── index.ts │ │ ├── mountRouter.ts │ │ └── remove.ts ├── type │ ├── component.d.ts │ ├── config.d.ts │ ├── form.d.ts │ ├── global.d.ts │ ├── http.d.ts │ ├── index.ts │ ├── route.d.ts │ ├── use-naive-hooks.d.ts │ └── vite.defined.ts ├── utils │ ├── NProgress │ │ └── index.ts │ ├── browser.ts │ ├── createAsyncComponent │ │ └── index.tsx │ ├── dataType.ts │ ├── fullScreen │ │ └── index.ts │ ├── index.ts │ ├── log │ │ └── index.ts │ ├── message │ │ └── index.ts │ ├── regExp.ts │ ├── type │ │ └── index.ts │ └── utils.ts └── views │ ├── errPage │ ├── 403 │ │ └── index.vue │ ├── 404 │ │ └── index.vue │ └── 500 │ │ └── index.vue │ ├── function │ ├── copy │ │ └── index.vue │ ├── fullScreen │ │ └── index.vue │ ├── http │ │ └── index.vue │ ├── msg │ │ └── index.vue │ └── sessionTimeout │ │ └── index.vue │ ├── home.vue │ ├── level │ ├── menu1 │ │ └── menu1 │ │ │ └── index.vue │ └── menu2.vue │ ├── login │ ├── index.vue │ ├── src │ │ ├── components │ │ │ ├── Theme.vue │ │ │ ├── passWord.vue │ │ │ ├── pictureCode.vue │ │ │ ├── userName.vue │ │ │ └── userNameLogin.vue │ │ ├── config.ts │ │ ├── hooks │ │ │ └── loginFn.ts │ │ ├── type.ts │ │ └── useLogin.ts │ └── template │ │ ├── index.vue │ │ ├── register.vue │ │ └── user.vue │ ├── permission │ ├── back │ │ └── index.vue │ └── web │ │ └── index.vue │ └── system │ ├── about │ ├── index.vue │ └── src │ │ └── rednder.tsx │ ├── changePassword │ └── index.vue │ ├── dept │ └── index.vue │ ├── menu │ ├── index.vue │ ├── src │ │ └── AddModal.vue │ └── type │ │ └── index.ts │ ├── role │ ├── index.vue │ └── src │ │ └── AddModal.vue │ └── user │ ├── index.vue │ ├── src │ └── addModal.vue │ └── type │ └── index.ts ├── stylelint.config.js ├── stylelintrc.json ├── tsconfig.json ├── vite.config.ts └── windi.config.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@vue/babel-plugin-jsx"] 3 | } -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | not dead 3 | Android >= 4.0 4 | iOS >= 8 5 | last 2 versions -------------------------------------------------------------------------------- /.cz-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // type 类型(定义之后,可通过上下键选择) 3 | types: [ 4 | { value: 'feat', name: 'feat: 新增功能' }, 5 | { value: 'fix', name: 'fix: 修复 bug' }, 6 | { value: 'refactor', name: 'refactor: 代码重构(不包括 bug 修复、功能新增)' }, 7 | { value: 'docs', name: 'docs: 文档变更' }, 8 | { value: 'resou', name: 'resou: 资源变更(新增或删除src/assets资源)' }, 9 | { value: 'style', name: 'style: 代码格式(不影响功能,例如空格、分号等格式修正)' }, 10 | { value: 'perf', name: 'perf: 性能优化' }, 11 | { value: 'test', name: 'test: 添加、修改测试用例' }, 12 | { value: 'build', name: 'build: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)' }, 13 | { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, 14 | { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' }, 15 | { value: 'revert', name: 'revert: 回滚 commit' }, 16 | ], 17 | 18 | // scope 类型(定义之后,可通过上下键选择) 19 | scopes: [ 20 | ['components', '组件相关'], 21 | ['hooks', 'hook 相关'], 22 | ['utils', 'utils 相关'], 23 | ['naive-ui', '对 naive-ui 的调整'], 24 | ['styles', '样式相关'], 25 | ['deps', '项目依赖'], 26 | ['auth', '对 auth 修改'], 27 | ['assets', '对 assets 修改'], 28 | ['other', '其他修改'], 29 | // 如果选择 custom,后面会让你再输入一个自定义的 scope。也可以不设置此项,把后面的 allowCustomScopes 设置为 true 30 | ['custom', '以上都不是?我要自定义'], 31 | ].map(([value, description]) => { 32 | return { 33 | value, 34 | name: `${value.padEnd(30)} (${description})`, 35 | } 36 | }), 37 | 38 | // 是否允许自定义填写 scope,在 scope 选择的时候,会有 empty 和 custom 可以选择。 39 | // allowCustomScopes: true, 40 | 41 | // allowTicketNumber: false, 42 | // isTicketNumberRequired: false, 43 | // ticketNumberPrefix: 'TICKET-', 44 | // ticketNumberRegExp: '\\d{1,5}', 45 | 46 | // 针对每一个 type 去定义对应的 scopes,例如 fix 47 | /* 48 | scopeOverrides: { 49 | fix: [ 50 | { name: 'merge' }, 51 | { name: 'style' }, 52 | { name: 'e2eTest' }, 53 | { name: 'unitTest' } 54 | ] 55 | }, 56 | */ 57 | 58 | // 交互提示信息 59 | messages: { 60 | type: '确保本次提交遵循提交规范!\n选择你要提交的类型:', 61 | scope: '\n选择一个 scope(可选):', 62 | // 选择 scope: custom 时会出下面的提示 63 | customScope: '请输入自定义的 scope:', 64 | subject: '填写简短精炼的变更描述:\n', 65 | body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n', 66 | breaking: '列举非兼容性重大的变更(可选):\n', 67 | footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n', 68 | confirmCommit: '确认提交?', 69 | }, 70 | 71 | // 设置只有 type 选择了 feat 或 fix,才询问 breaking message 72 | allowBreakingChanges: ['feat', 'fix'], 73 | 74 | // 跳过要询问的步骤 75 | // skipQuestions: ['body', 'footer'], 76 | 77 | // subject 限制长度 78 | subjectLimit: 100, 79 | breaklineChar: '|', // 支持 body 和 footer 80 | // footerPrefix : 'ISSUES CLOSED:' 81 | // askForBreakingChangeFirst : true, 82 | } 83 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # PORT 2 | VITE_APP_PORT = 8010 3 | # build 4 | VITE_APP_BUILD = dist 5 | # path 6 | VITE_APP_PATH = / 7 | # close console.log 8 | VITE_APP_LOG = true 9 | # sourceMap 10 | VITE_APP_SOURCEMAP = false 11 | # https 12 | VITE_APP_HTTPS = false 13 | # open 14 | VITE_APP_OPEN = false 15 | # mock 16 | VITE_APP_MOCK = false 17 | # 生产mock 18 | VITE_APP_PROD_MOCK = true 19 | # title 20 | VITE_APP_TITLE = VnaiveAdmin 21 | # layout prefixCls 22 | VITE_APP_PREFIXCLS = Vnaive 23 | # rollup-plugin-visualizer 24 | VITE_APP_ANALYSIS = true 25 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # dev 2 | VITE_APP_PROXY = [["/api","https://dev.mmxxn.cn/admin"],["/tapi","http://localhost:3666"],["/douban","https://movie.douban.com/"]] 3 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # pro 2 | VITE_APP_PROXY = [["/api","http://localhost:3666"],["/tapi","http://localhost:3666"]] 3 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | # main.yml 2 | name: devEvn to server 3 | on: 4 | push: 5 | branches: [develop] 6 | pull_request: 7 | branches: [develop] 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # 切换分支 13 | - name: 切换分支 Checkout 14 | uses: actions/checkout@v3 15 | 16 | # pnpm 17 | - name: 安装 pnpm 18 | uses: pnpm/action-setup@v2 19 | with: 20 | version: 7 21 | run_install: true 22 | 23 | # 使用 node:16.14.0 24 | - name: 切换node use Node.js 16.14.0 25 | uses: actions/setup-node@v3.0.0 26 | with: 27 | node-version: 16.14.0 28 | #缓存依赖 29 | - name: Cache nodeModules 30 | uses: actions/cache@v1 31 | env: 32 | cache-name: cache-node-modules 33 | with: 34 | # 需要缓存的文件的路径 35 | path: ./node_modules 36 | # 对缓存的文件指定的唯一标识 37 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./package.json') }} 38 | # 用于没有再找目标key的缓存的backup选项 39 | restore-keys: | 40 | ${{ runner.os }}-build-${{ env.cache-name }}- 41 | ${{ runner.os }}-build- 42 | {{ runner.os }}- 43 | # 安装依赖 44 | - name: 安装依赖 pnpm install 45 | run: pnpm run bootstrap # 安装依赖 46 | 47 | # npm build 48 | - name: 执行打包 npm build 49 | env: 50 | NODE_OPTIONS: --max_old_space_size=4096 51 | run: pnpm run build # 执行打包 | 52 | 53 | # npm 压缩 54 | - name: 压缩dist 55 | run: tar -zcvf release.tgz dist 56 | # 连接到服务器 57 | # name: rsync-deployments 是通过ssh通过rsync部署代码的GitHub操作 58 | - name: 发布dev环境 Deploy 🚀 59 | uses: cross-the-world/scp-pipeline@master 60 | env: 61 | WELCOME: 'ssh scp ssh pipelines' 62 | LASTSSH: 'Doing something after copying' 63 | with: 64 | host: ${{ secrets.SERVER_HOST }} 65 | user: ${{ secrets.SERVER_USER }} 66 | pass: ${{ secrets.SERVER_PASS }} 67 | remote: ${{ secrets.SERVER_DEV_DIR }} # 放到服务器上指定文件夹 68 | local: ./release.tgz # 构建完成后静态目录的地址 69 | connect_timeout: 15s 70 | -------------------------------------------------------------------------------- /.github/workflows/pro.yml: -------------------------------------------------------------------------------- 1 | # main.yml 2 | name: prod to server 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # 切换分支 13 | - name: 切换分支 Checkout 14 | uses: actions/checkout@v3 15 | 16 | # pnpm 17 | - name: 安装 pnpm 18 | uses: pnpm/action-setup@v2 19 | with: 20 | version: 7 21 | run_install: true 22 | 23 | # 使用 node:16.14.0 24 | - name: 切换node use Node.js 16.14.0 25 | uses: actions/setup-node@v3.0.0 26 | with: 27 | node-version: 16.14.0 28 | #缓存依赖 29 | - name: Cache nodeModules 30 | uses: actions/cache@v1 31 | env: 32 | cache-name: cache-node-modules 33 | with: 34 | # 需要缓存的文件的路径 35 | path: ./node_modules 36 | # 对缓存的文件指定的唯一标识 37 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./package.json') }} 38 | # 用于没有再找目标key的缓存的backup选项 39 | restore-keys: | 40 | ${{ runner.os }}-build-${{ env.cache-name }}- 41 | ${{ runner.os }}-build- 42 | {{ runner.os }}- 43 | # 安装依赖 44 | - name: 安装依赖 pnpm install 45 | run: pnpm run bootstrap # 安装依赖 46 | 47 | # npm build 48 | - name: 执行打包 npm build 49 | env: 50 | NODE_OPTIONS: --max_old_space_size=4096 51 | run: pnpm run build # 执行打包 | 52 | 53 | # npm 压缩 54 | - name: 压缩dist 55 | run: tar -zcvf release.tgz dist 56 | # 连接到服务器 57 | # name: rsync-deployments 是通过ssh通过rsync部署代码的GitHub操作 58 | - name: 发布dev环境 Deploy 🚀 59 | uses: cross-the-world/scp-pipeline@master 60 | env: 61 | WELCOME: 'ssh scp ssh pipelines' 62 | LASTSSH: 'Doing something after copying' 63 | with: 64 | host: ${{ secrets.SERVER_HOST }} 65 | user: ${{ secrets.SERVER_USER }} 66 | pass: ${{ secrets.SERVER_PASS }} 67 | remote: ${{ secrets.SERVER_DIR }} # 放到服务器上指定文件夹 68 | local: ./release.tgz # 构建完成后静态目录的地址 69 | connect_timeout: 15s 70 | -------------------------------------------------------------------------------- /.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 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | !.vscode/extensions.json 17 | .idea 18 | .DS_Store 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | .eslintignore 25 | # file 26 | auto-import.d.ts 27 | components.d.ts 28 | naive.d.ts 29 | stats.html 30 | .eslintcache -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'src/**/{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], 3 | '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'], 4 | 'package.json': ['prettier --write'], 5 | '*.vue': ['eslint --fix', 'prettier --write'], 6 | '*.{scss,less,style,html}': ['prettier --write'], 7 | '*.md': ['prettier --write'], 8 | }; 9 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # npx lint-staged 5 | npm run lint:lint-staged 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | /node_modules/** 5 | 6 | **/*.svg 7 | **/*.sh 8 | 9 | /public/* 10 | /src/assets/icons 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "trailingComma": "all", 6 | "arrowParens": "avoid", 7 | "endOfLine": "lf", 8 | "tabWidth": 2, 9 | "bracketSpacing": true 10 | } 11 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /public/* 3 | public/* 4 | /src/assets/icons -------------------------------------------------------------------------------- /.vscode/file-notes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/src/utils/NProgress": "", 3 | "/src/utils/NProgress/index.ts": "", 4 | "/src/utils/browser.ts": "" 5 | } -------------------------------------------------------------------------------- /CHANGELOG.zh_CN.md: -------------------------------------------------------------------------------- 1 | ## 1.2.0(2022-12.30) 2 | 3 | ### 升级说明 4 | 5 | - 升级墨菲安全扫描 6 | 7 | ### ✨ Features 8 | 9 | - 10 | 11 | ### 🐛 Bug Fixes 12 | 13 | - 修复 tab 标签页与菜单绑定不一致问题 14 | 15 | ## 1.2.0-beta.1(2022-12.24) 16 | 17 | ### 升级说明 18 | 19 | - 移除 moment 时间插件 使用 dayjs 20 | - 删除第二套登录模板 21 | - 登录页右上角不在显示版本信息 22 | 23 | ### ✨ Features 24 | 25 | - 更换 logo、README(logo) 26 | 27 | ### 🐛 Bug Fixes 28 | 29 | - 使用动态路由时获取不到组件控制台给出 warn 提示 30 | 31 | ## 1.2.0-beta.0(2022-12.19) 32 | 33 | ### 升级说明 34 | 35 | - 去掉 mock 数据使用接口数据 36 | - 系统管理(用户/角色/菜单)增删改查权限控制 37 | - 阿里字体图标资源不再放入项目使用 CDN 方式 38 | 39 | ### ✨ Features 40 | 41 | - 全局组件 Modal/table/Descriptions 开发 42 | - 用户头像更换 43 | 44 | ### 🐛 Bug Fixes 45 | 46 | - 异步动态路由不再使用 import(`${componentName}`)方式改为 import.meta.glob 的方式加载 47 | - pinia 数据调整 48 | - github action dist 改为压缩包上传,防止传输过程丢包 49 | - lodash 使用方式更改 50 | 51 | ## 1.1.1(2022-10.8) 52 | 53 | ### 升级说明 54 | 55 | 小问题不用在意 56 | 57 | ### ✨ Features 58 | 59 | - 新增加载 loading 60 | - 引入 init.css 61 | 62 | ### 🐛 Bug Fixes 63 | 64 | ## 1.1.0(2022-10.8) 65 | 66 | ### 升级说明 67 | 68 | - Vite2 升级 Vite3 69 | - src/config/settings.ts 配置项联动 pinia 数据 70 | - 封装 Descriptions useDescription 71 | - about Components 72 | - 修复左侧菜单样式(折叠 / 展开 / 白色 / 深色) 73 | 74 | ### ✨ Features 75 | 76 | ### 🐛 Bug Fixes 77 | 78 | - 修复 getEnv 方法返回数据类型 79 | - 修复主题样式 80 | 81 | ## 1.0.1(2022-9.27) 82 | 83 | ### 升级说明 84 | 85 | - .eslintrc.js 新增规则 86 | - .gitignore 忽略 eslintcache 文件 87 | - README.md 更新浏览器信息 88 | - **APP_INFO** eslint-disable-next-line no-underscore-dangle 89 | - 更新墨菲安全检测信息 90 | 91 | ## v 1.0.0 (2022-9.19) 92 | 93 | ### 升级说明 94 | 95 | - v1.0.0 版本发布正式版 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 yc6 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 | import { Record } from '../src/type' 2 | const booleanStr = ['true', 'false'] 3 | const transBoolean = (value: string): Boolean => value === booleanStr[0] 4 | const transNum = (value: string): Number => Number(value) 5 | export const wrapperEnv = (envConf: Record): ViteEnv => { 6 | const env: any = {} 7 | for (const envName of Object.keys(envConf)) { 8 | if (envName === 'VITE_APP_PORT') { 9 | env[envName] = transNum(envConf[envName]) 10 | } else if (envName === 'VITE_APP_SOURCEMAP') { 11 | if (booleanStr.includes(envConf[envName])) { 12 | env[envName] = transBoolean(envConf[envName]) 13 | } else { 14 | env[envName] = envConf[envName] 15 | } 16 | } else if ( 17 | [ 18 | 'VITE_APP_HTTPS', 19 | 'VITE_APP_LOG', 20 | 'VITE_APP_OPEN', 21 | 'VITE_APP_MOCK', 22 | 'VITE_APP_ANALYSIS', 23 | 'VITE_APP_PROD_MOCK', 24 | ].includes(envName) 25 | ) { 26 | env[envName] = transBoolean(envConf[envName]) 27 | } else if (envName === 'VITE_APP_PROXY') { 28 | env[envName] = JSON.parse(envConf[envName].replace(/"/g, '"')) 29 | } else { 30 | env[envName] = envConf[envName] 31 | } 32 | } 33 | return env 34 | } 35 | -------------------------------------------------------------------------------- /build/vite/define/index.ts: -------------------------------------------------------------------------------- 1 | import pkg from '../../../package.json' 2 | const { dependencies, devDependencies, name, version } = pkg 3 | export const APP_INFO = { 4 | pkg: { 5 | dependencies, 6 | devDependencies, 7 | name, 8 | version, 9 | }, 10 | lastBuildTime: Number(new Date()), 11 | } 12 | -------------------------------------------------------------------------------- /build/vite/plugin/autoComponents.ts: -------------------------------------------------------------------------------- 1 | import Components from 'unplugin-vue-components/vite' 2 | import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' 3 | import { autoPath } from '.' 4 | export const componentTS = () => { 5 | return Components({ 6 | resolvers: [NaiveUiResolver()], 7 | directoryAsNamespace: true, 8 | extensions: ['vue'], 9 | dts: autoPath + '/components.d.ts', 10 | // dts: false, 11 | dirs: ['src/components'], 12 | deep: true, 13 | globalNamespaces: [], 14 | directives: true, 15 | include: [ 16 | /\.vue$/, 17 | /\.vue\?vue/, 18 | /\.[tj]s[x]?$/, // .ts, .tsx, .js, .jsx 19 | ], 20 | exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/], 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /build/vite/plugin/autoImport.ts: -------------------------------------------------------------------------------- 1 | import AutoImport from 'unplugin-auto-import/vite' 2 | import { autoPath } from '.' 3 | export const autoImport = () => { 4 | return AutoImport({ 5 | include: [ 6 | /\.vue$/, 7 | /\.vue\?vue/, 8 | /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx 9 | ], 10 | imports: [ 11 | 'vue', 12 | 'vue-router', 13 | 'vue-i18n', 14 | '@vueuse/head', 15 | '@vueuse/core', 16 | 'pinia', 17 | ], 18 | dts: autoPath + '/auto-import.d.ts', 19 | eslintrc: { 20 | enabled: false, // 默认false, true启用。生成一次就可以,避免每次工程启动都生成 21 | filepath: './.eslintrc-auto-import.json', // 生成json文件 22 | globalsPropValue: true, 23 | }, 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /build/vite/plugin/buildInfo.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | import dayjs, { Dayjs } from 'dayjs' 3 | import duration from 'dayjs/plugin/duration' 4 | import { green, blue, bold } from 'picocolors' 5 | import { getPackageSize } from '@pureadmin/utils' 6 | dayjs.extend(duration) 7 | 8 | export function viteBuildInfo(): Plugin { 9 | let config: { command: string } 10 | let startTime: Dayjs 11 | let endTime: Dayjs 12 | let outDir: string 13 | return { 14 | name: 'vite:buildInfo', 15 | configResolved(resolvedConfig) { 16 | config = resolvedConfig 17 | outDir = resolvedConfig.build?.outDir ?? 'dist' 18 | }, 19 | buildStart() { 20 | console.log( 21 | bold( 22 | green( 23 | `👏欢迎使用${blue( 24 | '[v-naive-admin]', 25 | )},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/vzane0904/v-naive-admin`, 26 | ), 27 | ), 28 | ) 29 | if (config.command === 'build') { 30 | startTime = dayjs(new Date()) 31 | } 32 | }, 33 | closeBundle() { 34 | if (config.command === 'build') { 35 | endTime = dayjs(new Date()) 36 | getPackageSize({ 37 | folder: outDir, 38 | callback: (size: string) => { 39 | console.log( 40 | bold( 41 | green( 42 | `🎉恭喜打包完成(总用时${dayjs 43 | .duration(endTime.diff(startTime)) 44 | .format('mm分ss秒')},打包后的大小为${size})`, 45 | ), 46 | ), 47 | ) 48 | }, 49 | }) 50 | } 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /build/vite/plugin/buildVisualizer.ts: -------------------------------------------------------------------------------- 1 | import visualizer from 'rollup-plugin-visualizer' 2 | 3 | export const buildVisualizer: any = () => 4 | visualizer({ 5 | open: true, 6 | gzipSize: true, 7 | brotliSize: true, 8 | }) 9 | -------------------------------------------------------------------------------- /build/vite/plugin/html.ts: -------------------------------------------------------------------------------- 1 | import { createHtmlPlugin } from 'vite-plugin-html' 2 | 3 | export const html = (title: string) => { 4 | return createHtmlPlugin({ 5 | minify: false, 6 | /** 7 | * 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除 8 | * @default src/main.ts 9 | */ 10 | entry: '/src/main.ts', 11 | /** 12 | * 如果你想将 `index.html`存放在指定文件夹,可以修改它,否则不需要配置 13 | * @default index.html 14 | */ 15 | template: 'index.html', 16 | /** 17 | * 需要注入 index.html ejs 模版的数据 18 | */ 19 | inject: { 20 | data: { 21 | title: title, 22 | injectScript: ``, 23 | }, 24 | tags: [], 25 | }, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /build/vite/plugin/icons.ts: -------------------------------------------------------------------------------- 1 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 2 | import path from 'path' 3 | export const icons = () => { 4 | return createSvgIconsPlugin({ 5 | iconDirs: [path.resolve(process.cwd(), 'src/assets/svg')], 6 | symbolId: 'icon-[dir]-[name]', 7 | /** 8 | * 自定义插入位置 9 | * @default: body-last 10 | */ 11 | inject: 'body-last', 12 | // | 'body-first' 13 | 14 | /** 15 | * custom dom id 16 | * @default: __svg__icons__dom__ 17 | */ 18 | customDomId: '__svg__icons__dom__', 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /build/vite/plugin/importImgs.ts: -------------------------------------------------------------------------------- 1 | import ViteImages from 'vite-plugin-vue-images' 2 | export const importImgs = () => { 3 | return ViteImages({ 4 | dirs: ['src/assets/images'], // 指明图片存放目录 5 | extensions: ['jpg', 'jpeg', 'png', 'svg', 'webp'], 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /build/vite/plugin/jsx.ts: -------------------------------------------------------------------------------- 1 | import vueJsx from '@vitejs/plugin-vue-jsx' 2 | export const jsx = () => { 3 | return vueJsx({}) 4 | } 5 | -------------------------------------------------------------------------------- /build/vite/plugin/mock.ts: -------------------------------------------------------------------------------- 1 | import { viteMockServe } from 'vite-plugin-mock' 2 | export const mock = ( 3 | isOPen: boolean, //开发环境开启mock 4 | prodMock: boolean, //生产环境开启mock 5 | isBuild: boolean, //当前运行环境 true => build FALSE => serve 6 | _command: String, 7 | ) => { 8 | return viteMockServe({ 9 | ignore: /^_/, 10 | mockPath: 'mock', 11 | prodEnabled: isBuild && prodMock, // 生产打包开关 12 | localEnabled: isOPen && !isBuild, 13 | logger: true, //是否在控制台显示请求日志 14 | supportTs: true, //打开后,可以读取 ts 文件模块。 打开后将无法监视.js 文件 15 | injectCode: ` 16 | import { setupProdMockServer } from '/mock/_createProductionServer'; 17 | 18 | setupProdMockServer(); 19 | `, // 这段不走 vite 浏览器阶段编译,是纯 esbuild 编译 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /build/vite/plugin/restart.ts: -------------------------------------------------------------------------------- 1 | import ViteRestart from 'vite-plugin-restart' 2 | 3 | export const restart = () => { 4 | return ViteRestart({ 5 | restart: ['.env.[jt]s'], 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /build/vite/plugin/setupName.ts: -------------------------------------------------------------------------------- 1 | import vueSetupExtend from 'vite-plugin-vue-setup-extend' 2 | export const setupName = () => { 3 | // support name 4 | return vueSetupExtend() 5 | } 6 | -------------------------------------------------------------------------------- /build/vite/proxy.ts: -------------------------------------------------------------------------------- 1 | import type { ProxyOptions } from 'vite' 2 | type ProxyItem = [string, string] 3 | type ProxyList = ProxyItem[] 4 | type ProxyTargetItem = Record 5 | const isHttps = (target: string) => /^https:\/\//.test(target) 6 | 7 | export const createProxy = (list: ProxyList) => { 8 | const proxyList: ProxyTargetItem = {} 9 | 10 | for (const [prefix, target] of list) { 11 | proxyList[prefix] = { 12 | target: target, 13 | changeOrigin: true, 14 | secure: isHttps(target), 15 | rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), 16 | } 17 | } 18 | return proxyList 19 | } 20 | -------------------------------------------------------------------------------- /build/vite/resolve/alias.ts: -------------------------------------------------------------------------------- 1 | export const aliasList = [ 2 | ['@', '/src'], 3 | ['@assets', '/src/assets'], 4 | ['@styles', '/src/styles'], 5 | ['@components', '/src/components'], 6 | ['@views', '/src/views'], 7 | ['@pinia', '/src/pinia'], 8 | ['@api', '/src/api'], 9 | ['@utils', '/src/utils'], 10 | ['@type', '/src/type'], 11 | ['@public', '/public'], 12 | ] 13 | -------------------------------------------------------------------------------- /build/vite/resolve/index.ts: -------------------------------------------------------------------------------- 1 | import { AliasOptions } from 'vite' 2 | import { aliasList } from './alias' 3 | export const createAlias = (path: string) => { 4 | const alias: Array = [] 5 | for (const item of aliasList) { 6 | alias.push({ 7 | find: item[0], 8 | replacement: path + item[1], 9 | }) 10 | } 11 | return alias 12 | } 13 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | // 定义规则类型 4 | rules: { 5 | // type 类型定义,表示 git 提交的 type 必须在以下类型范围内 6 | 'type-enum': [ 7 | 2, 8 | 'always', 9 | [ 10 | 'feat', // 新功能 11 | 'fix', // 修复 12 | 'docs', // 文档变更 13 | 'resou', //资源变更 14 | 'style', // 代码格式(不影响代码运行的变动) 15 | 'refactor', // 重构(既不是增加feature),也不是修复bug 16 | 'perf', //性能优化 17 | 'test', // 增加测试 18 | 'build', // 打包 19 | 'ci', //修改 CI 配置、脚本 20 | 'chore', // 构建过程或辅助工具的变动 21 | 'revert', // 回退 22 | 'deps', //项目依赖 23 | ], 24 | ], 25 | // subject 大小写不做校验 26 | 'subject-case': [0], 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 56 | <%- title %> 57 | <%- injectScript %> 58 | 59 | 60 |
61 |
62 |
63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /mock/_createProductionServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' 2 | const modules = import.meta.globEager('./modules/**/*.ts') 3 | 4 | const mockModules: any[] = [] 5 | Object.keys(modules).forEach((key) => { 6 | if (key.includes('/_')) { 7 | return 8 | } 9 | mockModules.push(...modules[key].default) 10 | }) 11 | 12 | export function setupProdMockServer() { 13 | createProdMockServer(mockModules) 14 | } 15 | -------------------------------------------------------------------------------- /mock/config/data/backData.ts: -------------------------------------------------------------------------------- 1 | export const backData = [] 2 | export const backAllAuth = [] 3 | -------------------------------------------------------------------------------- /mock/config/data/moveData.ts: -------------------------------------------------------------------------------- 1 | export const moveData = [] 2 | export const moveAllAuth = [] 3 | -------------------------------------------------------------------------------- /mock/config/icon.ts: -------------------------------------------------------------------------------- 1 | export enum MenuIcon { 2 | home = 'zhuye3', 3 | permission = 'setting', 4 | permissionWeb = 'html5', 5 | permissionBack = 'houtaiguanli', 6 | function = 'chuangjian1', 7 | functionHttp = 'http', 8 | functionSessionTimeout = 'tuichudenglu', 9 | functionCopy = 'copy-full', 10 | functionMsg = 'tixing', 11 | functionFullScreen = 'quanping_o', 12 | iframe = 'waibulianjie', 13 | iframeDoc = 'wendang', 14 | iframeNaive = 'wendang', 15 | iframeDocMmxxn = 'wendang', 16 | level = 'jichugongneng', 17 | levelMenu1 = 'wendang1', 18 | levelMenu2 = 'wendang1', 19 | system = 'settings', 20 | systemAccount = 'zizhanghaoguanli', 21 | systemRole = 'he_86zhanghaoguanli', 22 | systemMenu = '_caidanguanli', 23 | systemDept = 'bumenguanli1', 24 | systemChangePassword = '', 25 | errPage = 'cuowuyemian', 26 | errPage403 = 'icon-test1', 27 | errPage404 = 'icon-test2', 28 | errPage500 = 'icon-test3', 29 | about = 'wode-woshituanchang', 30 | } 31 | -------------------------------------------------------------------------------- /mock/config/index.ts: -------------------------------------------------------------------------------- 1 | export const baseUrl = '/api' 2 | export function resultData( 3 | data: T, 4 | code = 200, 5 | success = true, 6 | msg = '', 7 | ) { 8 | return { 9 | code, 10 | msg, 11 | data, 12 | success, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /mock/modules/permissions.ts: -------------------------------------------------------------------------------- 1 | export default [] 2 | -------------------------------------------------------------------------------- /mock/modules/test/err.ts: -------------------------------------------------------------------------------- 1 | import { baseUrl, resultData } from '../../config/index' 2 | const err = 'err' 3 | export default [ 4 | { 5 | url: `${baseUrl}/${err}/200`, 6 | method: 'get', 7 | statusCode: 200, 8 | timeout: 1000, 9 | response: () => 10 | resultData({ 11 | result: 'get 200', 12 | }), 13 | }, 14 | { 15 | url: `${baseUrl}/${err}/206`, 16 | method: 'get', 17 | statusCode: 206, 18 | timeout: 1000, 19 | response: () => 20 | resultData({ 21 | result: 'get 206', 22 | }), 23 | }, 24 | { 25 | url: `${baseUrl}/${err}/401`, 26 | method: 'get', 27 | statusCode: 401, 28 | timeout: 1000, 29 | response: () => 30 | resultData({ 31 | result: 'get 401', 32 | }), 33 | }, 34 | { 35 | url: `${baseUrl}/${err}/402`, 36 | method: 'get', 37 | statusCode: 402, 38 | timeout: 1000, 39 | response: () => 40 | resultData({ 41 | result: 'get 402', 42 | }), 43 | }, 44 | { 45 | url: `${baseUrl}/${err}/403`, 46 | method: 'get', 47 | statusCode: 403, 48 | timeout: 1000, 49 | response: () => 50 | resultData({ 51 | result: 'get 403', 52 | }), 53 | }, 54 | { 55 | url: `${baseUrl}/${err}/404`, 56 | method: 'get', 57 | statusCode: 404, 58 | timeout: 1000, 59 | response: () => 60 | resultData({ 61 | result: 'get 404', 62 | }), 63 | }, 64 | { 65 | url: `${baseUrl}/${err}/500`, 66 | method: 'get', 67 | statusCode: 500, 68 | timeout: 1000, 69 | response: () => 70 | resultData({ 71 | result: 'get 500', 72 | }), 73 | }, 74 | { 75 | url: `${baseUrl}/${err}/501`, 76 | method: 'get', 77 | statusCode: 501, 78 | timeout: 1000, 79 | response: () => 80 | resultData({ 81 | result: 'get 501', 82 | }), 83 | }, 84 | { 85 | url: `${baseUrl}/${err}/503`, 86 | method: 'get', 87 | statusCode: 503, 88 | timeout: 1000, 89 | response: () => 90 | resultData({ 91 | result: 'get 503', 92 | }), 93 | }, 94 | ] 95 | -------------------------------------------------------------------------------- /mock/modules/test/method.ts: -------------------------------------------------------------------------------- 1 | import { baseUrl, resultData } from '../../config/index' 2 | export default [ 3 | { 4 | url: `${baseUrl}/test/get`, 5 | method: 'get', 6 | timeout: 1000, 7 | response: () => 8 | resultData({ 9 | result: 'get请求', 10 | }), 11 | }, 12 | { 13 | url: `${baseUrl}/test/post`, 14 | method: 'post', 15 | timeout: 1000, 16 | response: () => 17 | resultData({ 18 | result: 'post请求', 19 | }), 20 | }, 21 | { 22 | url: `${baseUrl}/test/options`, 23 | method: 'options', 24 | timeout: 1000, 25 | response: () => 26 | resultData({ 27 | result: 'options请求', 28 | }), 29 | }, 30 | { 31 | url: `${baseUrl}/test/delete`, 32 | method: 'delete', 33 | timeout: 1000, 34 | response: () => 35 | resultData({ 36 | result: 'delete请求', 37 | }), 38 | }, 39 | { 40 | url: `${baseUrl}/test/put`, 41 | method: 'put', 42 | timeout: 1000, 43 | response: () => 44 | resultData({ 45 | result: 'put请求', 46 | }), 47 | }, 48 | { 49 | url: `${baseUrl}/test/patch`, 50 | method: 'patch', 51 | timeout: 1000, 52 | response: () => 53 | resultData({ 54 | result: 'patch请求', 55 | }), 56 | }, 57 | ] 58 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // module.exports = { 2 | // plugins: [require('autoprefixer')], 3 | // } 4 | module.exports = { 5 | plugins: { 6 | // 兼容浏览器,添加前缀 7 | autoprefixer: { 8 | overrideBrowserslist: [ 9 | 'Android 4.1', 10 | 'iOS 7.1', 11 | 'Chrome > 31', 12 | 'Edge > 16', 13 | 'Firefox > 56', 14 | 'Ios_saf > 11', 15 | 'Opera > 48', 16 | 'Safari > 11', 17 | 'Samsung > 5', 18 | 'ff > 31', 19 | 'ie >= 8', 20 | 'last 10 versions', // 所有主流浏览器最近10版本用 21 | ], 22 | grid: true, 23 | }, 24 | // 'postcss-pxtorem': { 25 | // rootValue: 16, //结果为:设计稿元素尺寸/16,比如元素宽320px,最终页面会换算成 20rem 26 | // propList: ['*'], //是一个存储哪些将被转换的属性列表,这里设置为['*']全部,假设需要仅对边框进行设置,可以写['*', '!border*'] 27 | // unitPrecision: 5, //保留rem小数点多少位 28 | // //selectorBlackList: ['.radius'], //则是一个对css选择器进行过滤的数组,比如你设置为['fs'],那例如fs-xl类名,里面有关px的样式将不被转换,这里也支持正则写法。 29 | // replace: true, //这个真不知到干嘛用的。有知道的告诉我一下 30 | // mediaQuery: false, //媒体查询( @media screen 之类的)中不生效 31 | // minPixelValue: 12, //px小于12的不会被转换 32 | // }, 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 35 | 36 | 37 | 38 | 40 | 42 | 44 | 45 | 46 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 23 | 69 | 76 | -------------------------------------------------------------------------------- /src/api/Api.ts: -------------------------------------------------------------------------------- 1 | const prefix = '/api' 2 | const system = `${prefix}/system` //系统设置相关的前缀 3 | const userPrefix = `${system}/user` // 用户相关前缀 4 | const rolePrefix = `${system}/role` // 角色相关前缀 5 | const menuPrefix = `${system}/menu` // 角色相关前缀 6 | export enum Api { 7 | userNameLogin = `${prefix}/login/userName`, //用户名登录 8 | // 用户相关 9 | getUserList = `${userPrefix}/list`, //获取用户列表 10 | addUser = `${userPrefix}/add`, // 新增用户 11 | deleteUser = `${userPrefix}/del/`, // 删除当前用户 /后边 拼接ID 12 | updateUser = `${userPrefix}/update`, // 更新用户信息 13 | // 角色相关 14 | getRoleList = `${rolePrefix}/list`, // 获取角色列表 15 | addRole = `${rolePrefix}/add`, // 获取角色列表 16 | deleteRole = `${rolePrefix}/delete/`, // 删除角色 拼接ID 17 | ApiUpdateRole = `${rolePrefix}/update`, // 更新角色 18 | // 菜单相关 19 | APiMenuList = `${menuPrefix}/list`, // 获取菜单列表 20 | ApiAddMenu = `${menuPrefix}/add`, // 新增菜单 21 | ApiUpdateMenu = `${menuPrefix}/update`, // 修改菜单 22 | ApiDelMenu = `${menuPrefix}/delete/`, // 删除菜单 拼接ID 23 | // ------------------------------------------------------------------------------------------------------------------------------------------ 24 | useRegister = '/not/sys/register', //用户名注册 25 | permissions = '/users/back/permissions', 26 | movePermissions = '/users/move/permissions', 27 | smsCode = '/sys/sms', 28 | userPhoneLogin = '/sys/login/phone', 29 | useRetrievePass = '/sys/retrievePass', 30 | permissions401 = '/err/401', 31 | // 权限相关 32 | getBackMenu = `${prefix}/user/permissions`, 33 | } 34 | export enum BaseApi { 35 | picCode = `${prefix}/base/picCode`, //获取图片验证码 36 | } 37 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/http' 2 | import { IUserNameLogin } from '@/views/login/src/type' 3 | import { Api } from './Api' 4 | export const baseUrl = '/api' 5 | // export const permissions = () => http.post(`${baseUrl}${Api.permissions}`) 6 | export const movePermissions = () => 7 | http.post(`${baseUrl}${Api.movePermissions}`) 8 | // 获取手机号验证码 9 | export const getSms = () => http.get(`${baseUrl}${Api.smsCode}`) 10 | // 用户名登录 11 | export const userNameLogin = (params: IUserNameLogin) => 12 | http.post(`${baseUrl}${Api.userNameLogin}`, params) 13 | // 手机号登录 14 | export const userPhoneLogin = (params: any) => 15 | http.post(`${baseUrl}${Api.userPhoneLogin}`, params) 16 | // 用户名注册 17 | export const register = (params: any) => 18 | http.post(`${baseUrl}${Api.useRegister}`, params) 19 | // 找回密码 20 | export const retrievePass = (params: any) => 21 | http.post(`${baseUrl}${Api.useRetrievePass}`, params) 22 | // 角色列表 23 | export const getRoleList = (params: any) => http.get(Api.getRoleList, params) 24 | // 用户列表 25 | export const getUserList = (params: Record | undefined) => 26 | http.get(Api.getUserList, params) 27 | // 菜单列表 28 | export const getMenuList = (params: Record | undefined) => 29 | http.get(Api.APiMenuList, params) 30 | // 获取菜单权限 31 | export const getPermissions = () => http.get(Api.getBackMenu) 32 | -------------------------------------------------------------------------------- /src/api/test.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/http' 2 | import { baseUrl } from '.' 3 | import { Api } from './Api' 4 | export const get401 = () => http.get(`${baseUrl}${Api.permissions401}`) 5 | export const getErr = (status: 200 | 206 | 402 | 403 | 404 | 500 | 501 | 503) => 6 | http.get(`${baseUrl}/err/${status}`) 7 | -------------------------------------------------------------------------------- /src/assets/images/login2Img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/assets/images/login2Img.png -------------------------------------------------------------------------------- /src/assets/images/login_bg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/assets/images/login_bg2.png -------------------------------------------------------------------------------- /src/assets/images/login_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/assets/images/login_logo.png -------------------------------------------------------------------------------- /src/assets/images/public.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/assets/images/public.png -------------------------------------------------------------------------------- /src/assets/images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/assets/images/title.png -------------------------------------------------------------------------------- /src/assets/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/assets/images/user.png -------------------------------------------------------------------------------- /src/assets/images/user1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/assets/images/user1.png -------------------------------------------------------------------------------- /src/assets/svg/books.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/helpEmpty.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/assets/svg/login-bg-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/login-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/svg/logo-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 35 | 36 | 37 | 38 | 40 | 41 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/assets/svg/me.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/bootstrap/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import { setupPinia } from '@/pinia' 3 | import { setRoute } from '@/router' 4 | import { getFullScreen } from '@/utils/fullScreen' 5 | import { setupI18n } from '@/locales' 6 | import '@/naive/index' 7 | import installAllDirective from '@/directive' 8 | export const bootstrap = (app: App) => { 9 | setupPinia(app) 10 | setRoute(app) 11 | getFullScreen() 12 | setupI18n(app) 13 | installAllDirective(app) 14 | app.mount('#app') 15 | } 16 | -------------------------------------------------------------------------------- /src/components/AsyncComp/index.ts: -------------------------------------------------------------------------------- 1 | import { errorComponent } from './src/errorComponent' 2 | import { loadingComponent } from './src/loadingComponent' 3 | 4 | export const LoadingComponent = loadingComponent 5 | export const ErrorComponent = errorComponent 6 | -------------------------------------------------------------------------------- /src/components/AsyncComp/src/errorComponent.tsx: -------------------------------------------------------------------------------- 1 | export const errorComponent = defineComponent({ 2 | name: 'ErrorComponent', 3 | setup() { 4 | return () => <>Error组件 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /src/components/AsyncComp/src/loadingComponent.tsx: -------------------------------------------------------------------------------- 1 | export const loadingComponent = defineComponent({ 2 | name: 'LoadingComponent', 3 | setup() { 4 | return () => <>loading组件 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /src/components/Basic/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTitle } from './src/BasicTitle.vue' 2 | -------------------------------------------------------------------------------- /src/components/Basic/src/BasicTitle.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/Content/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/Descriptions/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Description } from './src/index.vue' 2 | export { useDescription } from './src/useDescription' 3 | -------------------------------------------------------------------------------- /src/components/Descriptions/src/index.vue: -------------------------------------------------------------------------------- 1 | 73 | 80 | -------------------------------------------------------------------------------- /src/components/Descriptions/src/type.ts: -------------------------------------------------------------------------------- 1 | import { DescriptionsProps } from 'naive-ui' 2 | import { VNode } from 'vue' 3 | // const NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem'] 4 | export interface IInfoSchema extends DescriptionsProps { 5 | label: string 6 | field: string 7 | render?: ( 8 | val: any, 9 | data: Record, 10 | ) => VNode | undefined | JSX.Element | Element | string | number 11 | show?: (data: Record) => boolean 12 | } 13 | interface IData { 14 | id: any 15 | [x: string]: any 16 | } 17 | export interface IDescriptionProps extends DescriptionsProps { 18 | title?: string 19 | column?: number 20 | schema?: IInfoSchema[] 21 | data?: IData 22 | } 23 | export interface IDescExample { 24 | setDescProps(descProps: IDescriptionProps): void 25 | } 26 | 27 | export type UseDescReturnType = [IRregister, IDescExample] 28 | -------------------------------------------------------------------------------- /src/components/Descriptions/src/useDescription.ts: -------------------------------------------------------------------------------- 1 | import { IDescExample, IDescriptionProps, UseDescReturnType } from './type' 2 | 3 | export const useDescription = (props: IDescriptionProps): UseDescReturnType => { 4 | if (!getCurrentInstance()) { 5 | throw new Error( 6 | 'useDescription() can only be used inside setup() or functional components!', 7 | ) 8 | } 9 | const desc = ref(null) 10 | function register(methodsProps: IDescExample) { 11 | desc.value = methodsProps 12 | methodsProps.setDescProps(props) 13 | } 14 | const methods: IDescExample = { 15 | setDescProps: (props: IDescriptionProps) => { 16 | unref(desc)?.setDescProps(props) 17 | }, 18 | } 19 | return [register, methods] 20 | } 21 | -------------------------------------------------------------------------------- /src/components/ErrorComponent/index.tsx: -------------------------------------------------------------------------------- 1 | export const ErrorComponent = defineComponent({ 2 | name: 'ErrorComponent', 3 | setup() { 4 | return () => <>Error组件 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Form } from './src/index.vue' 2 | export { useForm } from './src/userForm' 3 | -------------------------------------------------------------------------------- /src/components/Form/src/config/type.ts: -------------------------------------------------------------------------------- 1 | import { FormProps } from 'naive-ui' 2 | 3 | export type IFormProps = FormProps 4 | -------------------------------------------------------------------------------- /src/components/Form/src/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /src/components/Form/src/userForm.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from 'vue' 2 | import { IFormProps } from './config/type' 3 | 4 | export const useForm = function (_props: IFormProps) { 5 | if (!getCurrentInstance()) { 6 | throw new Error( 7 | 'useForm() can only be used inside setup() or functional components!', 8 | ) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Icon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | -------------------------------------------------------------------------------- /src/components/JsonView/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/components/LoadingComponent/index.tsx: -------------------------------------------------------------------------------- 1 | export const LoadingComponent = defineComponent({ 2 | name: 'LoadingComponent', 3 | setup() { 4 | return () => <>loading组件 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Modal } from './src/index.vue' 2 | export { useModal } from './src/useModal' 3 | -------------------------------------------------------------------------------- /src/components/Modal/src/index.vue: -------------------------------------------------------------------------------- 1 | 63 | -------------------------------------------------------------------------------- /src/components/Modal/src/type.ts: -------------------------------------------------------------------------------- 1 | import { ModalProps } from 'naive-ui' 2 | 3 | export interface IModalProps { 4 | title: string 5 | props?: Omit 6 | show: boolean 7 | } 8 | export interface IModalExample { 9 | setModalProps(descProps: Partial): void 10 | } 11 | export type UseModalReturnType = [IRregister, IModalExample] 12 | 13 | export interface IAttrs { 14 | onNegativeClick?: () => void 15 | onClose?: () => void 16 | positiveClick?: () => void 17 | onOk?: () => void 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Modal/src/useModal.ts: -------------------------------------------------------------------------------- 1 | import { IModalProps, IModalExample, UseModalReturnType } from './type' 2 | export const useModal = (props: IModalProps): UseModalReturnType => { 3 | if (!getCurrentInstance()) { 4 | throw new Error( 5 | 'useModal() can only be used inside setup() or functional components!', 6 | ) 7 | } 8 | const desc = ref(null) 9 | function register(methodsProps: IModalExample) { 10 | desc.value = methodsProps 11 | methodsProps.setModalProps(props) 12 | } 13 | const methods: IModalExample = { 14 | setModalProps: (props: Partial) => { 15 | unref(desc)?.setModalProps(props) 16 | }, 17 | } 18 | return [register, methods] 19 | } 20 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | 46 | -------------------------------------------------------------------------------- /src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTable } from './src/BasicTable.vue' 2 | export { default as useTable } from './src/hooks/userTable' 3 | -------------------------------------------------------------------------------- /src/components/Table/src/components/TableHeader.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 61 | 62 | 83 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/default.config.ts: -------------------------------------------------------------------------------- 1 | import { BasicTableProps } from '../type/table' 2 | 3 | const config: Partial = { 4 | immediate: true, 5 | data: [], 6 | columns: [], 7 | tableSettings: { 8 | showHeader: true, 9 | }, 10 | dataTableProps: { 11 | flexHeight: true, 12 | size: 'medium', 13 | remote: true, 14 | bordered: true, 15 | bottomBordered: true, 16 | }, 17 | pageSetting: { 18 | listField: 'list', 19 | pageSizeField: 'pageSize', 20 | pageIndexField: 'pageIndex', 21 | pageTotalField: 'count', 22 | }, 23 | pagination: { 24 | defaultPage: 1, 25 | defaultPageSize: 10, 26 | pageSizes: [10, 20, 50, 100], 27 | showSizePicker: true, 28 | showQuickJumper: true, 29 | }, 30 | } 31 | export default config 32 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useFetch.ts: -------------------------------------------------------------------------------- 1 | import { BasicTableProps } from '../type/table' 2 | import { cloneDeep } from 'lodash-es' 3 | import { toType } from '@/utils/dataType' 4 | export const beforeFetch = function () {} 5 | export const useAfterFetch = async function ( 6 | config: BasicTableProps, 7 | data: Record[], 8 | ) { 9 | const clone = cloneDeep(data) 10 | const res = await config.afterFetch!(clone) 11 | if (toType(res) === 'array') { 12 | return res 13 | } 14 | console.warn(`Table data type must be an array`) 15 | return [] 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/userTable.ts: -------------------------------------------------------------------------------- 1 | import { BasicTableProps, ITableExample } from '../type/table' 2 | import { cloneDeep, merge } from 'lodash-es' 3 | import config from './default.config' 4 | const useTable = function (props: BasicTableProps) { 5 | const baseConfig = merge(cloneDeep(config), props) 6 | if (!getCurrentInstance()) { 7 | throw new Error( 8 | 'useForm() can only be used inside setup() or functional components!', 9 | ) 10 | } 11 | // 事件处理 12 | const desc = ref() 13 | // 响应示数据 14 | const propsValue = ref() 15 | const register = function (methodsProps: ITableExample) { 16 | desc.value = methodsProps 17 | propsValue.value = methodsProps.setTableProps(baseConfig) 18 | } 19 | const setLoading = (value: boolean) => 20 | (unref(propsValue)!.dataTableProps!.loading = value) 21 | // 设置所有配置 22 | const setTableProps = (config: BasicTableProps) => 23 | unref(desc)!.setTableProps(config) 24 | const reload = () => desc.value?.reload() 25 | const getDataSource = () => unref(propsValue)!.data as [] 26 | const methods: ITableExample = { 27 | setTableProps, 28 | setLoading, 29 | reload, 30 | getDataSource, 31 | } 32 | return { register, methods } 33 | } 34 | export default useTable 35 | -------------------------------------------------------------------------------- /src/components/Table/src/type/table.ts: -------------------------------------------------------------------------------- 1 | import { DataTableColumns, DataTableProps, PaginationProps } from 'naive-ui' 2 | import { Recordable } from 'vite-plugin-mock' 3 | export interface TableSettings { 4 | showHeader?: boolean //header区域是否显示 5 | showRefresh?: boolean //刷新按钮 6 | size?: boolean //显示调整表格大小按钮 7 | } 8 | export interface IPageSetting { 9 | listField?: string //请求结果数据对应参数名 10 | pageSizeField?: string //一页多少条对应字段 11 | pageIndexField?: string //当前第几页对应字段 12 | pageTotalField?: string //总数据对应字段 13 | showPagination?: boolean //是否分页 14 | } 15 | export interface BasicTableProps { 16 | // title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string) 17 | title?: string | ((data?: Recordable) => string) 18 | dataTableProps?: Omit 19 | data?: Record[] 20 | api?: (...arg: any) => Promise 21 | tableSettings?: TableSettings 22 | beforeFetch?: (value: any) => Promise //请求之前对参数进行处理 23 | afterFetch?: (value: any) => Promise //请求之后对返回值进行处理 24 | immediate?: boolean //组件加载完成是否立即请求数据,api必须存在 25 | additionalParams?: Record //额外参数 26 | columns: DataTableColumns 27 | showIndexColum?: boolean // 是否显示序号 28 | showIndexColumText?: string // 序号文案 29 | pageSetting?: IPageSetting 30 | pagination?: PaginationProps 31 | } 32 | export interface ITableExample { 33 | setTableProps(desc: Partial): BasicTableProps //设置表格参数 34 | setLoading(value: Boolean): void 35 | reload(): void // 刷新表格 36 | getDataSource: >() => T[] // 获取数据 37 | } 38 | -------------------------------------------------------------------------------- /src/components/codeEditor/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/components/windowUtils/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /src/config/settings.ts: -------------------------------------------------------------------------------- 1 | import { ProjectConfig } from '@/type/config' 2 | const settings: ProjectConfig = { 3 | layout: 1, 4 | themeColor: '#409eff', 5 | progressBar: true, 6 | header: { 7 | color: '#ffffff', 8 | height: 48, 9 | language: 'zh_CN', 10 | isFullScreenBtn: true, 11 | showLanGuaGe: true, 12 | showTopSet: true, 13 | foldBtnPosition: 'top', 14 | showLogo: true, 15 | }, 16 | sider: { 17 | color: '#001529', 18 | fold: false, 19 | width: 210, 20 | showSider: true, 21 | isAccordion: true, 22 | }, 23 | footer: { 24 | height: 48, 25 | showFooter: true, 26 | }, 27 | tabs: { 28 | showTabs: true, 29 | showRefresh: true, 30 | }, 31 | } 32 | export default settings 33 | -------------------------------------------------------------------------------- /src/directive/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | const modules = import.meta.globEager('./modules/**/*.ts') 3 | const installAllDirective = function (app: App) { 4 | Object.keys(modules).forEach(async (item) => { 5 | const default1 = 6 | (modules[item] as any).default || 7 | (modules[item] as any)[Object.keys(modules[item] as any)[0]] 8 | 9 | if (default1) { 10 | app.use(default1) 11 | } else { 12 | console.error('eeee') 13 | } 14 | }) 15 | } 16 | export default installAllDirective 17 | -------------------------------------------------------------------------------- /src/directive/modules/test.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | 3 | const hasPermission = { 4 | install(Vue: App) { 5 | //自定义指令v-has: 6 | Vue.directive('has', { 7 | // mounted(el, binding, vnode) { 8 | // if (!checkPermission(binding.value)) { 9 | // const tooltipNode = vnode.children.find( 10 | // (childrenCmpt) => childrenCmpt.component?.type.name == 'ElTooltip', 11 | // ) 12 | // tooltipNode.component.props.disabled = false 13 | // el.querySelector('button').setAttribute('disabled', true) 14 | // } 15 | // }, 16 | // 在绑定元素的 attribute 前 17 | // 或事件监听器应用前调用 18 | created(_el, _binding, _vnode, _prevVnode) { 19 | // console.log('created') 20 | // 下面会介绍各个参数的细节 21 | }, 22 | // 在元素被插入到 DOM 前调用 23 | beforeMount(_el, _binding, _vnode, _prevVnode) { 24 | // console.log('beforeMount') 25 | }, 26 | // 在绑定元素的父组件 27 | // 及他自己的所有子节点都挂载完成后调用 28 | mounted(_el, _binding, _vnode, _prevVnode) { 29 | // console.log('mounted') 30 | }, 31 | // 绑定元素的父组件更新前调用 32 | beforeUpdate(_el, _binding, _vnode, _prevVnode) { 33 | // console.log('beforeUpdate') 34 | }, 35 | // 在绑定元素的父组件 36 | // 及他自己的所有子节点都更新后调用 37 | updated(_el, _binding, _vnode, _prevVnode) { 38 | // console.log('updated') 39 | }, 40 | // 绑定元素的父组件卸载前调用 41 | beforeUnmount(_el, _binding, _vnode, _prevVnode) { 42 | // console.log('beforeUnmount') 43 | }, 44 | // 绑定元素的父组件卸载后调用 45 | unmounted(_el, _binding, _vnode, _prevVnode) { 46 | // console.log('unmounted') 47 | }, 48 | }) 49 | //权限检查方法 50 | // function checkPermission(value: string | []): boolean { 51 | // // let isExist = false 52 | // // const userlogin = JSON.parse(sessionStorage.getItem('userlogin') || '[]') 53 | // // const buttonArr = userlogin.haspermissions 54 | // // //判断是否按钮有权限 55 | // // if (buttonArr.includes(value)) { 56 | // // isExist = true 57 | // // } 58 | // // return isExist 59 | // console.log(value) 60 | // return true 61 | // } 62 | }, 63 | } 64 | export default hasPermission 65 | -------------------------------------------------------------------------------- /src/enum/axios.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @server: Request result set 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | ERROR = 1, 7 | TIMEOUT = 401, 8 | TYPE = 'success', 9 | } 10 | /** 11 | * @serverFun: request method 12 | */ 13 | export enum RequestEnum { 14 | GET = 'get', 15 | POST = 'post', 16 | PUT = 'put', 17 | DELETE = 'delete', 18 | HEAD = 'head', 19 | PATCH = 'patch', 20 | OPTIONS = 'options', 21 | } 22 | export const methodData: Array = [ 23 | RequestEnum.GET, 24 | RequestEnum.DELETE, 25 | RequestEnum.HEAD, 26 | RequestEnum.PATCH, 27 | RequestEnum.POST, 28 | RequestEnum.PUT, 29 | ] 30 | /** 31 | * @header: contentTyp 32 | */ 33 | export enum ContentTypeEnum { 34 | // json 35 | JSON = 'application/json;charset=UTF-8', 36 | // form-data qs 37 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 38 | // form-data upload 39 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 40 | } 41 | export enum AxiosErrorTip { 42 | MESSAGE = 'Message', 43 | MODAL = 'Modal', 44 | Notification = 'Notification', 45 | NONE = 'None', 46 | } 47 | /** 48 | * 如果有重复提示信息 49 | * **/ 50 | export const ignoreTip = '请求重复,请稍后重试' 51 | // 接口异常 状态码提示信息 52 | export const responseErrInfo: { 53 | [key: number]: string 54 | } = { 55 | 301: '请求失败,资源地址已改动', 56 | 302: '请求失败,资源地址临时移动', 57 | 303: '请求失败,资源错误', 58 | // 304:'后端资源未变,前端使用缓存,一般不做提示', 59 | 305: '请求失败,请使用代理访问', 60 | 307: '请求失败,临时重定向', 61 | 400: '请求错误,请检查参数', 62 | 401: '身份验证失败,请重新登录', 63 | 403: '权限不足,禁止访问当前资源', 64 | 404: '资源不存在', 65 | 405: '禁用此请求方式', 66 | 500: '服务器内部错误', 67 | 501: '不支持此请求', 68 | 502: '无效请求', 69 | 503: '暂时无法处理此请求', 70 | 505: '不支持此版本协议', 71 | } 72 | -------------------------------------------------------------------------------- /src/enum/locale.ts: -------------------------------------------------------------------------------- 1 | export type LocaleType = 'zh_CN' | 'zh_TW' | 'ru' | 'en' 2 | 3 | export const Locale = { 4 | ZH_CN: 'zh_CN', 5 | ZH_TW: 'zh_TW', 6 | RU: 'ru', 7 | EN: 'en', 8 | } 9 | -------------------------------------------------------------------------------- /src/enum/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 1.MOVE模式 -> 3 | - router/backRoutes/* 通过变量定义路由名称 后端返回动态菜单时不需要返回页面路径,返回变量名称就可以,配合path 拿到变量名生成路由 4 | - 优点: 如果前端页面路径有改动不需要在去通过菜单配置修改组件页面地址 5 | - 缺点: 对于查找某个路由页面比较麻烦,需要先看接口返回的路由变量名称,通过名称在去查找 6 | 2.ROLE模式 router/roleRoutes/* 自定义路由,通过角色去判断路由是否能访问 7 | 2.BACK模式 后端完全返回,前端做动态处理 8 | */ 9 | export enum RoleEnum { 10 | BACK = 'BACK', 11 | ROLE = 'ROLE', 12 | MOVE = 'MOVE', 13 | } 14 | export type roleModel = `${RoleEnum}` 15 | -------------------------------------------------------------------------------- /src/hooks/router/index.ts: -------------------------------------------------------------------------------- 1 | import { logError, logWarn } from '@/utils/log' 2 | import { Router } from 'vue-router' 3 | import { router as rootRouter } from '@/router' 4 | const handleError = (e: Error) => logError(e) 5 | /** 6 | * @useGo 路由跳转 7 | * @push String 8 | * @replace String 9 | * @returns ()=>Function 10 | * **/ 11 | export const useGo = (_router?: Router) => { 12 | let router = _router 13 | if (!router) { 14 | router = rootRouter 15 | } 16 | const { push, replace } = router 17 | const go = (path: string, query: any = {}, isReplace = false) => { 18 | return new Promise((resolve, reject) => { 19 | if (!path) { 20 | logWarn('Path不可为空') 21 | reject(false) 22 | } 23 | if (isReplace) { 24 | return replace(path) 25 | .catch(handleError) 26 | .then( 27 | () => resolve(true), 28 | () => reject(false), 29 | ) 30 | } 31 | if (path === '/login' && !query.redirectPath) { 32 | Reflect.set(query, 'redirectPath', router?.currentRoute.value.fullPath) 33 | } 34 | push({ path, query }) 35 | .catch(handleError) 36 | .then( 37 | () => resolve(true), 38 | () => reject(false), 39 | ) 40 | }) 41 | } 42 | return go 43 | } 44 | /** 45 | * @useRefresh 刷新 46 | * @refresh String 47 | * @returns ()=>Function 48 | * **/ 49 | export const useRefresh = (_router: Router) => { 50 | let router = _router 51 | if (!router) { 52 | router = useRouter() 53 | } 54 | const { replace, currentRoute } = router 55 | const { path } = unref(currentRoute.value) 56 | const refresh = () => { 57 | replace(path) 58 | } 59 | return refresh 60 | } 61 | /** 62 | * @useRouter 获取路由实例 63 | * @returns Router 64 | * **/ 65 | export const useCustomizeRouter = () => rootRouter 66 | -------------------------------------------------------------------------------- /src/hooks/useConfig.ts: -------------------------------------------------------------------------------- 1 | import { themeStore } from '@/pinia/modules/theme' 2 | const { theme } = storeToRefs(themeStore()) 3 | export const useConfig = () => { 4 | const getConfig = (key: 'bg') => { 5 | const bg = ref() 6 | if (key === 'bg') { 7 | watchEffect(() => { 8 | bg.value = { 'background-color': theme.value ? '#3a3b3c4d' : '#fff' } 9 | }) 10 | } 11 | return { bg } 12 | // return undefined 13 | } 14 | return { 15 | getConfig, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/useEnv.ts: -------------------------------------------------------------------------------- 1 | import { wrapperEnv } from '../../build/utils' 2 | 3 | export const getEnv = (envName: keyof ViteEnv) => { 4 | // 解决 [rollup-plugin-dynamic-import-variables] Unexpected token 5 | return wrapperEnv(import.meta.env)[envName] 6 | } 7 | -------------------------------------------------------------------------------- /src/hooks/useHttp.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { http } from '@/http' 4 | import { RequestCustom, RequestOptions } from '@/type/http' 5 | import { Method } from 'axios' 6 | import { merge } from 'lodash-es' 7 | type Headers = Pick 8 | interface ISetHeader { 9 | [key: string]: any 10 | } 11 | // eslint-disable-next-line no-unused-vars 12 | interface IUseHttp extends Headers { 13 | readonly Api: string 14 | data?: Record 15 | params?: Record 16 | readonly methods: Method 17 | props?: RequestCustom 18 | } 19 | 20 | export const useHttp = function (configProps: IUseHttp) { 21 | const loading = ref(false) 22 | const err = ref() 23 | const data = ref() 24 | // 发起请求 25 | const run = () => { 26 | return new Promise(async (resolve) => { 27 | loading.value = true 28 | try { 29 | const result = await http.require({ 30 | url: configProps.Api, 31 | method: configProps.methods, 32 | data: configProps.data, 33 | params: configProps.params, 34 | headers: configProps.headers, 35 | requestOptions: { 36 | ...configProps.props, 37 | }, 38 | }) 39 | // 如果获取原生请求头直接成功 40 | if (configProps.props?.isReturnNativeResponse) { 41 | data.value = result 42 | err.value = undefined 43 | resolve(true) 44 | } 45 | // 拿到data结果 46 | if (!configProps.props?.isConversionRequestResult) { 47 | data.value = result 48 | err.value = undefined 49 | resolve(true) 50 | } else if (result && result.success && result.code !== 0) { 51 | //获取原生接口结果 52 | data.value = result 53 | err.value = undefined 54 | } else { 55 | data.value = undefined 56 | err.value = result 57 | } 58 | resolve(true) 59 | } catch (error: any) { 60 | data.value = undefined 61 | err.value = error 62 | resolve(true) 63 | } 64 | loading.value = false 65 | }) 66 | } 67 | const setProps = (configParams: RequestCustom) => { 68 | configProps.props = merge(configProps.props, configParams) 69 | } 70 | const setHeaders = (configParams: ISetHeader) => 71 | (configProps.headers = merge(configProps.headers, configParams)) 72 | return { run, loading, err, data, setHeaders, setProps } 73 | } 74 | -------------------------------------------------------------------------------- /src/hooks/useI18n.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/hooks/useI18n.ts -------------------------------------------------------------------------------- /src/hooks/useLogin.ts: -------------------------------------------------------------------------------- 1 | import { routeStore } from '@/pinia/modules/routeStore' 2 | import { useProfileStore } from '@/pinia/modules/user' 3 | import { useGo } from './router' 4 | /** 5 | * @name logOut 退出 6 | * @returns Promise 7 | * **/ 8 | export const useLogOut = () => { 9 | return new Promise(async (resolve, reject) => { 10 | try { 11 | const { token } = storeToRefs(useProfileStore()) 12 | token.value = '' 13 | const go = useGo() 14 | await go('/login') 15 | const routerS = routeStore() 16 | await routerS.reset() 17 | resolve(true) 18 | } catch (error) { 19 | reject(error) 20 | } 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /src/http/Tips.ts: -------------------------------------------------------------------------------- 1 | import { AxiosErrorTip, responseErrInfo } from '@/enum/axios' 2 | import { RequestOptions } from '@/type/http' 3 | import { createModal, createMsg, createNotification } from '@/utils/message' 4 | import type { AxiosError } from 'axios' 5 | export const TipMsg = (response: AxiosError) => { 6 | const strMsg: string = responseErrInfo[response.response!.status] 7 | const config: RequestOptions = response.config as RequestOptions 8 | const msg = 9 | `${response.response!.status} ${strMsg}` || 10 | `${response.response!.status}请联系管理员` 11 | switch (config.requestOptions.errorMessageModal) { 12 | case AxiosErrorTip.MESSAGE: //message提示 13 | createMsg(msg, { type: 'error' }) 14 | break 15 | case AxiosErrorTip.MODAL: //弹框 16 | createModal({ 17 | title: '系统错误', 18 | content: msg, 19 | type: 'error', 20 | }) 21 | break 22 | case AxiosErrorTip.Notification: //通知 23 | createNotification({ 24 | title: '系统错误', 25 | content: msg, 26 | type: 'error', 27 | }) 28 | break 29 | default: 30 | // 不提示兜底 31 | break 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/http/addCancel.ts: -------------------------------------------------------------------------------- 1 | import { PendingType, RequestOptions } from '@/type/http' 2 | import axios from 'axios' 3 | import { addPending } from './cancel' 4 | export const addCancel = (request: RequestOptions) => { 5 | const cancelToken = axios.CancelToken 6 | let pendingObj: PendingType 7 | request.cancelToken = new cancelToken((c) => { 8 | pendingObj = { ...request, cancel: c } 9 | }) 10 | addPending(pendingObj!) 11 | } 12 | -------------------------------------------------------------------------------- /src/http/cancel.ts: -------------------------------------------------------------------------------- 1 | import { PendingType, RequestOptions } from '@/type/http' 2 | /** 3 | @name axiosRequestMap 4 | @Fun 所有请求暂存 5 | **/ 6 | export const axiosRequestMap: Map = new Map() 7 | /** 8 | @name cancelRequestMap 9 | @Fun 所有请求的取消方法暂存 10 | **/ 11 | export const cancelRequestMap: Map void> = new Map() 12 | /** 13 | @name handleName 14 | @Fun 处理暂存到 axiosRequestMap 方法中时对应的KEY 15 | **/ 16 | export const handleName = (config: RequestOptions) => { 17 | let url: string 18 | if (!config.requestOptions.ignoreRequest) { 19 | url = `${config.url}_${config.requestOptions.id}_${config.method}` 20 | } else { 21 | url = `${config.url}_${config.method}` 22 | } 23 | return url 24 | } 25 | 26 | /** 27 | @name addPending 28 | @Fun 添加一条到 axiosRequestMap 29 | **/ 30 | export const addPending = (request: PendingType) => { 31 | axiosRequestMap.set(handleName(request), request) 32 | cancelRequestMap.set(request.requestOptions.id!, request.cancel) 33 | } 34 | /** 35 | @name deletePending 36 | @Fun map中删除一条 37 | **/ 38 | export const deletePending = (request: RequestOptions) => { 39 | axiosRequestMap.delete(handleName(request)) 40 | if (axiosRequestMap.size === 0) { 41 | cancelRequestMap.clear() 42 | } else { 43 | cancelRequestMap.delete(request.requestOptions.id!) 44 | } 45 | } 46 | /** 47 | @name cancelPending 48 | @Fun 调用取消方法取消请求,并从map中删除 49 | **/ 50 | export const cancelPending = (request: RequestOptions) => { 51 | if (axiosRequestMap.has(handleName(request))) { 52 | axiosRequestMap 53 | .get(handleName(request)) 54 | ?.cancel(request.requestOptions.ignoreMsg) 55 | deletePending(request) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/http/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | carryToken, 3 | RETRY_COUNT, 4 | RETRY_INTERVAL, 5 | RETRY_OPENRETRY, 6 | } from '@/config' 7 | import { AxiosErrorTip, ContentTypeEnum, ignoreTip } from '@/enum/axios' 8 | import { RequestOptions } from '@/type/http' 9 | import { merge } from 'lodash-es' 10 | import { VAxios } from './axios' 11 | export const data: RequestOptions = { 12 | timeout: 1000 * 10, // 10ms 超时 13 | baseUrl: '', 14 | headers: { 'Content-Type': ContentTypeEnum.JSON }, 15 | // 如果是form-data格式 16 | // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, 17 | // 配置项,下面的选项都可以在独立的接口请求中覆盖 18 | requestOptions: { 19 | id: undefined, 20 | // 是否返回原生响应头 比如:需要获取响应头时使用该属性 21 | isReturnNativeResponse: false, 22 | // 消息提示类型 23 | errorMessageModal: AxiosErrorTip.MODAL, 24 | // 重复信息提示 25 | ignoreMsg: ignoreTip, 26 | // 是否加入时间戳 27 | joinTime: true, 28 | // 忽略重复请求 29 | ignoreRequest: true, 30 | // 是否携带token 31 | withToken: carryToken, 32 | // 重试次数 33 | count: RETRY_COUNT, 34 | // 重试间隔 35 | interval: RETRY_INTERVAL, 36 | // 默认不开启错误重试 37 | openRetry: RETRY_OPENRETRY, 38 | // 当前重试次数 39 | retryCount: 0, 40 | // 是否转换请求结果直接拿到data 41 | isConversionRequestResult: true, 42 | }, 43 | } 44 | const createHttp = (request?: RequestOptions) => { 45 | return new VAxios(merge(data, request)) 46 | } 47 | export const http = createHttp() 48 | -------------------------------------------------------------------------------- /src/layouts/default/backTop/index.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Language } from './src/language.vue' 2 | export { default as Screen } from './src/screen.vue' 3 | export { default as Settings } from './src/settings.vue' 4 | export { default as User } from './src/user.vue' 5 | export { default as FixedSettings } from './src/fixedSettings.vue' 6 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/config.ts: -------------------------------------------------------------------------------- 1 | import { useLogOut } from '@/hooks/useLogin' 2 | import { logError } from '@/utils/log' 3 | import { createModal } from '@/utils/message' 4 | import { DropdownOption } from 'naive-ui' 5 | 6 | export const userOptions = [ 7 | { 8 | label: '修改密码', 9 | key: '修改密码', 10 | fn: () => { 11 | console.log('修改密码') 12 | }, 13 | }, 14 | { 15 | label: '个人中心', 16 | key: '个人中心', 17 | }, 18 | { 19 | label: '退出登录', 20 | key: '退出登录', 21 | fn: (_key: string | number, _option: DropdownOption) => { 22 | const example = createModal({ 23 | title: '温馨提示', 24 | type: 'warning', 25 | content: '是否确认退出系统?', 26 | positiveText: '确定', 27 | negativeText: '取消', 28 | maskClosable: false, 29 | onPositiveClick: async () => { 30 | return new Promise(async (resolve) => { 31 | example.loading = true 32 | try { 33 | await useLogOut() 34 | resolve(true) 35 | } catch (error) { 36 | logError(error as Error) 37 | } 38 | }) 39 | }, 40 | }) 41 | }, 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/fixedSettings.vue: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/language.vue: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/page/PageAnimation.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/page/PageFun.vue: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/page/PageShow.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/page/PageTheme.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/page/copyOptions.ts: -------------------------------------------------------------------------------- 1 | import { themeStore } from '@/pinia/modules/theme' 2 | import { ProjectConfig } from '@/type/config' 3 | 4 | export const useOptions = () => { 5 | const { 6 | layout, 7 | themeColor, 8 | progressBar, 9 | headerColor, 10 | headerHeight, 11 | language, 12 | isFullScreen, 13 | showLanGuaGe, 14 | showTopSet, 15 | foldBtnPosition, 16 | showLogo, 17 | siderColor, 18 | siderFold, 19 | siderWidth, 20 | showSider, 21 | isAccordion, 22 | footerHeight, 23 | showFooter, 24 | showTabs, 25 | showRefresh, 26 | } = storeToRefs(themeStore()) 27 | const obj: ProjectConfig = { 28 | layout: layout.value, 29 | themeColor: themeColor.value, 30 | progressBar: progressBar.value, 31 | header: { 32 | color: headerColor.value, 33 | height: headerHeight.value, 34 | language: language.value, 35 | isFullScreenBtn: isFullScreen.value, 36 | showLanGuaGe: showLanGuaGe.value, 37 | showTopSet: showTopSet.value, 38 | foldBtnPosition: foldBtnPosition.value, 39 | showLogo: showLogo.value, 40 | }, 41 | sider: { 42 | color: siderColor.value, 43 | fold: siderFold.value, 44 | width: siderWidth.value, 45 | showSider: showSider.value, 46 | isAccordion: isAccordion.value, 47 | }, 48 | footer: { 49 | height: footerHeight.value, 50 | showFooter: showFooter.value, 51 | }, 52 | tabs: { 53 | showTabs: showTabs.value, 54 | showRefresh: showRefresh.value, 55 | }, 56 | } 57 | 58 | const options = ref('') 59 | options.value = JSON.stringify(obj, null, 2) 60 | return options 61 | } 62 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/screen.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/settings.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /src/layouts/default/components/right/src/user.vue: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /src/layouts/default/components/settings/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PageSwitch } from './src/PageSwitch.vue' 2 | export { default as PageSelect } from './src/PageSelect.vue' 3 | export { default as PageInputNum } from './src/PageInputNum.vue' 4 | export { default as ColorSelect } from './src/ColorSelect.vue' 5 | export { default as PageLayout } from './src/PageLayout.vue' 6 | -------------------------------------------------------------------------------- /src/layouts/default/components/settings/src/ColorSelect.vue: -------------------------------------------------------------------------------- 1 | 37 | 42 | -------------------------------------------------------------------------------- /src/layouts/default/components/settings/src/FormItem.tsx: -------------------------------------------------------------------------------- 1 | import { FormItemProps } from './props/props' 2 | 3 | export const FormItemComp = defineComponent({ 4 | name: 'FormItemComp', 5 | props: FormItemProps, 6 | setup(props, { slots }) { 7 | return () => ( 8 | 13 |
14 | {slots.default && slots.default()} 15 |
16 |
17 | ) 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /src/layouts/default/components/settings/src/PageInputNum.vue: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/layouts/default/components/settings/src/PageSelect.vue: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /src/layouts/default/components/settings/src/PageSwitch.vue: -------------------------------------------------------------------------------- 1 | 30 | 36 | -------------------------------------------------------------------------------- /src/layouts/default/components/settings/src/props/props.ts: -------------------------------------------------------------------------------- 1 | import { SelectMixedOption } from 'naive-ui/lib/select/src/interface' 2 | import { PropType } from 'vue' 3 | const basicProps = { 4 | name: { 5 | type: String, 6 | default: 'xx', 7 | }, 8 | model: { 9 | type: [String, Number, Boolean], 10 | }, 11 | disabled: { 12 | type: Boolean, 13 | default: false, 14 | }, 15 | } 16 | export const FormItemProps = { 17 | name: { 18 | type: String, 19 | required: true, 20 | }, 21 | } 22 | export const SelectProps = { 23 | list: { 24 | type: Array as PropType, 25 | required: true, 26 | }, 27 | ...basicProps, 28 | clearable: { 29 | type: Boolean, 30 | default: false, 31 | }, 32 | } 33 | export const SwitchProps = { 34 | ...basicProps, 35 | } 36 | export const InputNumProps = { 37 | ...basicProps, 38 | model: { 39 | type: Number, 40 | }, 41 | suffix: { 42 | type: String, 43 | default: '', 44 | }, 45 | max: { 46 | type: Number, 47 | }, 48 | min: { 49 | type: Number, 50 | }, 51 | } 52 | export const ColorProps = { 53 | colorList: { 54 | type: Array as PropType, 55 | require: true, 56 | default: [], 57 | }, 58 | model: { 59 | type: [String, Number, Boolean], 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /src/layouts/default/components/settings/src/type/type.ts: -------------------------------------------------------------------------------- 1 | export interface SelectType { 2 | label: string | number 3 | value: string | number 4 | } 5 | -------------------------------------------------------------------------------- /src/layouts/default/content/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 22 | -------------------------------------------------------------------------------- /src/layouts/default/footer/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 54 | -------------------------------------------------------------------------------- /src/layouts/default/header/fold.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /src/layouts/default/header/index.vue: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /src/layouts/default/header/right.vue: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /src/layouts/default/logo/index.vue: -------------------------------------------------------------------------------- 1 | 46 | -------------------------------------------------------------------------------- /src/layouts/default/page/hock.tsx: -------------------------------------------------------------------------------- 1 | import LayoutContent from '../content/index.vue' 2 | import LayoutHeader from '../header/index.vue' 3 | import Layoutsider from '../sider/index.vue' 4 | export const LayoutOne = defineComponent({ 5 | name: 'LayoutOne', 6 | setup() { 7 | return () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | }, 17 | }) 18 | export const LayoutTwo = defineComponent({ 19 | name: 'LayoutOne', 20 | setup() { 21 | return () => ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | }, 31 | }) 32 | export const LayoutThr = defineComponent({ 33 | name: 'Layouthree', 34 | setup() { 35 | return () => ( 36 | 37 | 38 | 39 | 40 | ) 41 | }, 42 | }) 43 | -------------------------------------------------------------------------------- /src/layouts/default/page/index.tsx: -------------------------------------------------------------------------------- 1 | import { themeStore } from '@/pinia/modules/theme' 2 | import { LayoutOne, LayoutThr, LayoutTwo } from './hock' 3 | export default defineComponent({ 4 | name: 'ViewComponent', 5 | setup() { 6 | const theme = themeStore() 7 | return () => ( 8 | <> 9 | 10 | {theme.layout === 1 ? ( 11 | 12 | ) : theme.layout === 2 ? ( 13 | 14 | ) : ( 15 | 16 | )} 17 | 18 | 19 | ) 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /src/layouts/default/sider/index.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /src/layouts/default/sider/trigger.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/src/config/index.tsx: -------------------------------------------------------------------------------- 1 | import { h } from 'vue' 2 | import Icon from '@/components/Icon/index.vue' 3 | import { NIcon } from 'naive-ui' 4 | export const renderIcon = (name: string) => { 5 | return () => { 6 | return h(NIcon, null, { 7 | default: () => , 8 | }) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/src/suffix.tsx: -------------------------------------------------------------------------------- 1 | import Icon from '@/components/Icon/index.vue' 2 | import { DropdownOption } from 'naive-ui' 3 | import useTabs from './utils' 4 | import { configStore } from '@/pinia/modules/config' 5 | import { themeStore } from '@/pinia/modules/theme' 6 | export const Suffix = defineComponent({ 7 | name: 'Suffix', 8 | setup() { 9 | const { rightFun, options, refreshRoute } = useTabs() 10 | const { isRefresh, prefix } = storeToRefs(configStore()) 11 | const { showRefresh } = storeToRefs(themeStore()) 12 | const className = ref( 13 | `w-36px h-1/1 flex justify-center items-center cursor-pointer ${prefix.value}-tabs-extra-fn`, 14 | ) 15 | return () => ( 16 |
17 | {showRefresh.value ? ( 18 |
!isRefresh.value && refreshRoute()} 21 | > 22 | 28 | {isRefresh.value ? null : } 29 | 30 |
31 | ) : null} 32 | 33 |
34 | { 38 | rightFun(key, option) 39 | }} 40 | > 41 | 50 | 51 |
52 |
53 | ) 54 | }, 55 | }) 56 | -------------------------------------------------------------------------------- /src/layouts/default/view.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/layouts/error/403.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/layouts/error/404.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/layouts/error/500.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/layouts/error/router404.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/layouts/iframe/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/layouts/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/layouts/index.ts -------------------------------------------------------------------------------- /src/layouts/refresh/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 24 | -------------------------------------------------------------------------------- /src/layouts/routerView/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import { createI18n } from 'vue-i18n' 3 | import type { I18n, I18nOptions } from 'vue-i18n' 4 | import { set } from 'lodash-es' 5 | import { Locale } from '@/enum/locale' 6 | export let i18n: ReturnType 7 | export const lacalName = 'locale' 8 | const locale = localStorage.getItem(lacalName) ?? Locale.ZH_CN 9 | export function getMsg( 10 | langs: Record>, 11 | prefix = 'language', 12 | ) { 13 | const obj: any = {} 14 | Object.keys(langs).forEach((key) => { 15 | const data = langs[key].default 16 | let fileName = key.replace(`./${prefix}/`, '').replace(/^\.\//, '') 17 | const lastIndex = fileName.lastIndexOf('.') 18 | fileName = fileName.substring(0, lastIndex) 19 | const keyList = fileName.split('/') 20 | const moduleName = keyList.shift() 21 | const objKey = keyList.join('.') 22 | if (moduleName) { 23 | if (objKey) { 24 | set(obj, moduleName, obj[moduleName] || {}) 25 | set(obj[moduleName], objKey, data) 26 | } else { 27 | set(obj, moduleName, data || {}) 28 | } 29 | } 30 | }) 31 | return obj 32 | } 33 | const createI18nOptions = async () => { 34 | return { 35 | locale, 36 | silentFallbackWarn: true, 37 | silentTranslationWarn: true, 38 | legacy: false, // you must specify 'legacy: false' option 指定false时切换语言不会生效 需要刷新页面 如果为true切换语言会生效 但是控制台会有警告 39 | fallbackLocale: Locale.ZH_CN, //没有英文的时候默认中文语言 40 | globalInjection: true, // 全局模式,可以直接使用 $t 41 | messages: getMsg(import.meta.globEager('./language/**/*.ts')), 42 | } 43 | } 44 | export const setupI18n = async (app: App) => { 45 | const options: I18nOptions = await createI18nOptions() 46 | i18n = createI18n(options) as I18n 47 | app.use(i18n) 48 | } 49 | -------------------------------------------------------------------------------- /src/locales/language/en/layout.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | message: 'eng', 3 | } 4 | -------------------------------------------------------------------------------- /src/locales/language/ru/layout.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | message: '乌拉', 3 | } 4 | -------------------------------------------------------------------------------- /src/locales/language/zh_CN/layout.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | message: '中文', 3 | } 4 | -------------------------------------------------------------------------------- /src/locales/language/zh_CN/route.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | home: '首页11 ', 3 | } 4 | -------------------------------------------------------------------------------- /src/locales/language/zh_HK/layout.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | message: '中国香港', 3 | } 4 | -------------------------------------------------------------------------------- /src/locales/language/zh_TW/layout.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | message: '台湾', 3 | } 4 | -------------------------------------------------------------------------------- /src/locales/useLocal.ts: -------------------------------------------------------------------------------- 1 | import { LocaleType } from '@/enum/locale' 2 | import { themeStore } from '@/pinia/modules/theme' 3 | import { computed } from 'vue' 4 | import { i18n } from '.' 5 | // type MyPick = { 6 | // [P in K] : T[P] 7 | // } 8 | export function useI18n() { 9 | const { t, ...methods } = i18n.global 10 | function setLocal(key: LocaleType) { 11 | const store = themeStore() 12 | localStorage.setItem('locale', key) 13 | i18n.global.locale = key 14 | store.language = key 15 | location.reload() 16 | } 17 | const getLocal = computed(() => i18n.global.locale) 18 | const tFn = (key: string) => t(key) 19 | return { 20 | getLocal, 21 | setLocal, 22 | t: tFn, 23 | ...methods, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { bootstrap } from '@/bootstrap' 4 | const app = createApp(App) 5 | bootstrap(app) 6 | -------------------------------------------------------------------------------- /src/naive/config/Tabs.ts: -------------------------------------------------------------------------------- 1 | import { themeStore } from '@/pinia/modules/theme' 2 | import { TabsThemeVars } from 'naive-ui/lib/tabs/styles' 3 | 4 | const TabsThemeConfig = (): Partial => { 5 | const store = themeStore() 6 | const { theme } = storeToRefs(store) 7 | return { 8 | tabColor: unref(theme) ? '#151515' : '#fff', 9 | tabBorderColor: unref(theme) ? '#303030' : '#d9d9d9', //边框颜色 10 | tabTextColorActiveBar: 'red', 11 | tabTextColorActiveCard: '#fff', //字体颜色 12 | tabTextColorActiveLine: 'blue', 13 | tabFontWeightActive: '700', //字体粗细 14 | tabTextColorCard: theme.value ? '#c9d1d9' : '#666', //正常字体颜色 15 | closeColorHover: '#fff', 16 | tabGapMediumCard: '4px', //每个tabs之间的间距 17 | tabPaddingMediumCard: '2px 21px', //大小 18 | tabTextColorHoverCard: 'red', 19 | } 20 | } 21 | export default TabsThemeConfig 22 | -------------------------------------------------------------------------------- /src/naive/index.ts: -------------------------------------------------------------------------------- 1 | import './style/index.less' 2 | import 'virtual:svg-icons-register' 3 | import 'virtual:windi.css' 4 | // 通用字体 5 | import 'vfonts/Lato.css' 6 | // 等宽字体 7 | // import 'vfonts/FiraCode.css' 8 | import 'virtual:windi-devtools' 9 | // import '@/assets/icons/iconfont.js' 10 | // import '@/assets/icons/iconfont.css' 11 | export { default as TabsThemeConfig } from './config/Tabs' 12 | export { default as themeOverrides } from './config/Theme' 13 | -------------------------------------------------------------------------------- /src/naive/style/dark.less: -------------------------------------------------------------------------------- 1 | // 黑暗主题 2 | @prefixCls: ~'@{prefix}'; 3 | 4 | [data-theme='dark'] { 5 | .@{prefixCls}-tabs-extra-content { 6 | .@{prefixCls}-tabs-extra-fn { 7 | border-left: 1px solid #303030; 8 | } 9 | } 10 | 11 | .@{prefixCls}-dropdown-select, 12 | .@{prefixCls}-button-Refresh { 13 | color: #8b949e; 14 | transition: all 0.3s; 15 | 16 | &:active, 17 | &:focus { 18 | color: #8b949e; 19 | } 20 | } 21 | 22 | .hover:hover { 23 | color: #fff; 24 | } 25 | 26 | svg { 27 | outline: none; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/naive/style/divider.less: -------------------------------------------------------------------------------- 1 | .@{prefixCls}-divider:not(.@{prefixCls}-divider--vertical) { 2 | // margin: 0 0 5px; 3 | } 4 | -------------------------------------------------------------------------------- /src/naive/style/index.less: -------------------------------------------------------------------------------- 1 | @import url('animate.css'); 2 | @import './light.less'; 3 | @import './dark.less'; 4 | @import './layout.less'; 5 | @import './tabs.less'; 6 | @import './menu.less'; 7 | @import './divider.less'; 8 | @import './table.less'; 9 | @import './init.css'; 10 | @prefixCls: ~'@{prefix}'; 11 | 12 | #app { 13 | width: 100%; 14 | height: 100%; 15 | display: flex; 16 | // border: 1px solid rosybrown; 17 | } 18 | 19 | .icon { 20 | width: 1em; 21 | height: 1em; 22 | vertical-align: -0.15em; 23 | /* stylelint-disable-next-line value-keyword-case */ 24 | fill: currentColor; 25 | overflow: hidden; 26 | } 27 | 28 | .none-hover:hover { 29 | color: rgba(#ddd, #ddd, #ddd, 0%); 30 | } 31 | -------------------------------------------------------------------------------- /src/naive/style/init.css: -------------------------------------------------------------------------------- 1 | body, 2 | h1, 3 | h2, 4 | h3, 5 | h4, 6 | h5, 7 | h6, 8 | hr, 9 | p, 10 | blockquote, 11 | dl, 12 | dt, 13 | dd, 14 | ul, 15 | ol, 16 | li, 17 | pre, 18 | form, 19 | fieldset, 20 | legend, 21 | button, 22 | input, 23 | textarea, 24 | th, 25 | td { 26 | margin: 0; 27 | padding: 0; 28 | } 29 | 30 | body, 31 | button, 32 | input, 33 | select, 34 | textarea { 35 | font: 12px/1.5, arial, '\5b8b\4f53'; 36 | } 37 | 38 | h1, 39 | h2, 40 | h3, 41 | h4, 42 | h5, 43 | h6 { 44 | font-size: 100%; 45 | } 46 | 47 | address, 48 | cite, 49 | dfn, 50 | em, 51 | var { 52 | font-style: normal; 53 | } 54 | 55 | html, 56 | code, 57 | kbd, 58 | pre, 59 | samp { 60 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 61 | 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 62 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 63 | 'Noto Color Emoji' couriernew, courier, monospace; 64 | } 65 | 66 | small { 67 | font-size: 12px; 68 | } 69 | 70 | ul, 71 | ol { 72 | list-style: none; 73 | } 74 | 75 | a { 76 | text-decoration: none; 77 | } 78 | 79 | sup { 80 | vertical-align: text-top; 81 | } 82 | 83 | sub { 84 | vertical-align: text-bottom; 85 | } 86 | 87 | legend { 88 | color: #000; 89 | } 90 | 91 | fieldset, 92 | img { 93 | border: 0; 94 | } 95 | 96 | button, 97 | input, 98 | select, 99 | textarea { 100 | font-size: 100%; 101 | } 102 | 103 | *::-webkit-scrollbar { 104 | /* 滚动条整体样式 */ 105 | width: 6px; /* 高宽分别对应横竖滚动条的尺寸 */ 106 | height: 1px; 107 | transition: opacity 80ms ease; 108 | } 109 | 110 | *::-webkit-scrollbar-thumb { 111 | /* 滚动条里面小方块 */ 112 | border-radius: 10px; 113 | background-color: transparent; 114 | cursor: pointer; 115 | } 116 | 117 | *::-webkit-scrollbar-thumb:hover { 118 | /* 滚动条里面小方块 */ 119 | background-color: #9093994d; 120 | } 121 | 122 | *::-webkit-scrollbar-track { 123 | /* 滚动条里面轨道 */ 124 | 125 | /* box-shadow: inset 0 0 5px rgb(0 0 0 / 20%); */ 126 | 127 | /* background: #ededed; */ 128 | border-radius: 10px; 129 | } 130 | 131 | .inp-out { 132 | outline: none; 133 | } 134 | 135 | html, 136 | body { 137 | width: 100vw; 138 | height: 100%; 139 | } 140 | 141 | #app { 142 | height: 100%; 143 | display: flex; 144 | justify-content: center; 145 | align-items: center; 146 | } 147 | -------------------------------------------------------------------------------- /src/naive/style/layout.less: -------------------------------------------------------------------------------- 1 | @prefixCls: ~'@{prefix}'; 2 | .@{prefixCls}-layout { 3 | // background-color: transparent; 4 | 5 | &-scroll-container { 6 | width: 100%; 7 | display: flex; 8 | flex-direction: column; 9 | flex: 1; 10 | } 11 | 12 | &-sider-scroll-container { 13 | min-width: 0 !important; 14 | display: flex; 15 | flex-direction: column; 16 | overflow: hidden !important; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/naive/style/light.less: -------------------------------------------------------------------------------- 1 | @prefixCls: ~'@{prefix}'; 2 | // 亮色主题 3 | [data-theme='light'] { 4 | .@{prefixCls}-layout-scroll-container { 5 | &-box { 6 | background-color: #f0f2f5; 7 | } 8 | } 9 | 10 | .@{prefixCls}-tabs-extra-content { 11 | .@{prefixCls}-tabs-extra-fn { 12 | border-left: 1px solid #d9d9d9; 13 | } 14 | } 15 | 16 | .@{prefixCls}-dropdown-select, 17 | .@{prefixCls}-button-Refresh { 18 | color: #00000073; 19 | transition: all 0.3s; 20 | 21 | &:active, 22 | &:focus { 23 | color: #00000073; 24 | } 25 | } 26 | 27 | .hover:hover { 28 | color: #000000d9; 29 | } 30 | 31 | svg { 32 | outline: none; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/naive/style/menu.less: -------------------------------------------------------------------------------- 1 | .@{prefixCls}-menu { 2 | &-item:first-child { 3 | margin-top: 0 !important; 4 | } 5 | .@{prefixCls}-menu-item-content::before { 6 | left: 0; 7 | right: 0; 8 | } 9 | 10 | &--horizontal { 11 | width: 100%; 12 | flex: 1; 13 | } 14 | 15 | &--collapsed { 16 | .@{prefixCls}-menu-item-content--selected::after, 17 | .@{prefixCls}-menu-item-content--child-active::after { 18 | position: absolute; 19 | top: 0; 20 | right: 0; 21 | width: 3px; 22 | height: 100%; 23 | content: ''; 24 | background-color: #0960bd; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/naive/style/table.less: -------------------------------------------------------------------------------- 1 | .@{prefixCls}-table-wrapper { 2 | height: 100%; 3 | } 4 | 5 | .@{prefixCls}-data-table { 6 | height: calc(100% - 40px); 7 | } 8 | 9 | .@{prefixCls}-data-table--bordered { 10 | .@{prefixCls}-data-table-tbody { 11 | .@{prefixCls}-data-table-tr:last-child { 12 | td { 13 | border-bottom: 1px solid var(--n-merged-border-color); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/naive/style/tabs.less: -------------------------------------------------------------------------------- 1 | .@{prefixCls}-tabs { 2 | overflow: hidden; 3 | height: 100%; 4 | 5 | &-tab { 6 | padding: 0; 7 | // border: none !important; 8 | // width: 100%; 9 | // position: absolute; 10 | 11 | &__label { 12 | div { 13 | display: flex !important; 14 | justify-content: center !important; 15 | align-items: center; 16 | position: relative; 17 | top: 0; 18 | 19 | svg { 20 | position: absolute; 21 | right: -17px; 22 | top: 2px; 23 | opacity: 0; 24 | } 25 | } 26 | } 27 | 28 | &:hover { 29 | color: #0960bd !important; 30 | 31 | svg { 32 | opacity: 1; 33 | } 34 | } 35 | } 36 | 37 | /* stylelint-disable-next-line no-duplicate-selectors */ 38 | &-tab--active { 39 | &:hover { 40 | color: #fff !important; 41 | } 42 | } 43 | } 44 | .@{prefixCls}-tabs-extra-content { 45 | .@{prefixCls}-tabs-extra-fn { 46 | border-left: 1px solid #303030; 47 | } 48 | .@{prefixCls}-tabs-tab--active { 49 | background-color: #0960bd !important; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/pinia/README.md: -------------------------------------------------------------------------------- 1 | ### pinia 2 | 3 | # 推荐使用,比 vuex 舒服操作更方便, 4 | 5 | # 官方文档 => https://pinia.vuejs.org/ 6 | 7 | # 数据持久化文档 => https://seb-l.github.io/pinia-plugin-persist/ 8 | -------------------------------------------------------------------------------- /src/pinia/demo.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | export const demoStore = defineStore({ 3 | id: 'demoStore', //唯一 4 | state: () => ({ 5 | count: 0, 6 | }), //存放数据 7 | getters: {}, //获取state 8 | actions: {}, //异步提交 9 | persist: { 10 | // https://seb-l.github.io/pinia-plugin-persist/advanced/strategies.html 11 | enabled: true, 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /src/pinia/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import { createPinia } from 'pinia' 3 | import piniaPersist from 'pinia-plugin-persist' 4 | 5 | export const setupPinia = (app: App) => { 6 | app.use(createPinia().use(piniaPersist)) 7 | console.log( 8 | '%c[Pinia] devtools support enabled %c\nread more at https://pinia.vuejs.org/', 9 | 'background:#0ea5e9; color:white; padding: 1px 4px; border-radius: 3px;font-size:12px', 10 | '', 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/pinia/modules/config.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | export const configStore = defineStore({ 3 | id: 'configStore', 4 | state: () => ({ 5 | prefix: import.meta.env.VITE_APP_PREFIXCLS as string, //前缀 6 | isRefresh: false, 7 | openSettingDrawer: false, 8 | }), 9 | }) 10 | -------------------------------------------------------------------------------- /src/pinia/modules/ref.ts: -------------------------------------------------------------------------------- 1 | import { MenuInst } from 'naive-ui' 2 | import { defineStore } from 'pinia' 3 | export const refStore = defineStore({ 4 | id: 'refStore', 5 | state: () => ({ 6 | menuRef: null as MenuInst | null, 7 | }), 8 | actions: { 9 | /** 10 | @param setMenuSelect 设置左侧菜单选中 11 | * **/ 12 | setMenuSelect(val: string) { 13 | this.menuRef?.showOption(val) 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /src/pinia/modules/routeStore.ts: -------------------------------------------------------------------------------- 1 | import { baseHome } from '@/config' 2 | import { removeRoute } from '@/router/utils/remove' 3 | import { RouteType } from '@/type/route' 4 | import { defineStore } from 'pinia' 5 | import { TabsType } from '../type/user' 6 | import { useProfileStore } from './user' 7 | 8 | export const routeStore = defineStore({ 9 | id: 'routeStore', 10 | state: () => ({ 11 | routesName: [] as Array, //原数据 12 | routesList: [] as Array, // back路由模式接口数据 13 | auth: [], //权限 14 | selectMenu: baseHome, //首页路径 15 | tabs: >[ 16 | { 17 | name: baseHome, 18 | path: baseHome, 19 | isClose: false, 20 | title: '首页', 21 | }, 22 | ], 23 | tabsActive: baseHome, 24 | role: ['admin', 'int'], 25 | }), 26 | actions: { 27 | reset(callBack: () => void = () => {}) { 28 | return new Promise(async (resolve) => { 29 | const user = useProfileStore() 30 | removeRoute() 31 | user.$reset() 32 | await callBack() 33 | setTimeout(() => { 34 | resolve(true) 35 | this.$reset() 36 | }, 100) 37 | }) 38 | }, 39 | }, 40 | persist: { 41 | enabled: true, 42 | strategies: [ 43 | { 44 | storage: localStorage, 45 | paths: [ 46 | 'auth', 47 | // 'tabs', 48 | 'role', 49 | 'routesList', 50 | ], 51 | }, 52 | ], 53 | }, 54 | }) 55 | -------------------------------------------------------------------------------- /src/pinia/modules/theme.ts: -------------------------------------------------------------------------------- 1 | import { LocaleType } from '@/enum/locale' 2 | import { headerColor, siderColor, themeColor } from '@/config' 3 | import { Layout, Position } from '@/type/config' 4 | import { defineStore } from 'pinia' 5 | import settings from '@/config/settings' 6 | 7 | export const themeStore = defineStore({ 8 | id: 'themeStore', 9 | state: () => ({ 10 | theme: false, //主题 darkTheme | light 11 | layout: settings.layout as Layout, //页面布局模式 12 | themeColor: settings.themeColor as typeof themeColor[number], //主题颜色 13 | headerColor: settings.header.color as typeof headerColor[number], //顶部颜色 14 | siderColor: settings.sider.color as typeof siderColor[number], //左侧菜单颜色 15 | siderFold: settings.sider.fold, //是否折叠 16 | foldBtnPosition: settings.header.foldBtnPosition as Position, //折叠菜单按钮位置 17 | isAccordion: settings.sider.isAccordion, //手风琴模式 18 | siderWidth: settings.sider.width, //正常宽度 19 | headerHeight: settings.header.height, //顶部高度 20 | footerHeight: settings.footer.height, //底部高度 21 | showTabs: settings.tabs.showTabs, //显示标签页 22 | showRefresh: settings.tabs.showRefresh, //显示刷新按钮 23 | showSider: settings.sider.showSider, //显示左侧菜单 24 | showLogo: settings.header.showLogo, //显示logo 25 | isFullScreenBtn: settings.header.isFullScreenBtn, //是否显示全屏按钮 26 | showFooter: settings.footer.showFooter, //显示footer 27 | showLanGuaGe: settings.header.showLanGuaGe, //显示多语言切换 28 | showTopSet: settings.header.showTopSet, //顶部设置按钮 29 | language: settings.header.language as LocaleType, 30 | progressBar: settings.progressBar, //进度条 31 | isFullScreen: false, //是否全屏 32 | collapsedWidth: 48, //左侧菜单折叠宽度 33 | switchLoading: true, //切换显示loading 34 | toggleAnimation: true, //切换页面显示动画 35 | animationType: '动画1', //动画类型 36 | }), 37 | getters: {}, 38 | actions: {}, 39 | persist: { 40 | enabled: true, 41 | strategies: [ 42 | { 43 | storage: localStorage, 44 | }, 45 | ], 46 | }, 47 | }) 48 | -------------------------------------------------------------------------------- /src/pinia/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | export const useProfileStore = defineStore({ 3 | id: 'useProfileStore', //唯一 4 | state: () => ({ 5 | token: '', 6 | userName: '', 7 | }), 8 | getters: {}, 9 | actions: {}, 10 | persist: { 11 | enabled: true, 12 | strategies: [ 13 | { 14 | storage: localStorage, 15 | }, 16 | ], 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /src/pinia/type/user.ts: -------------------------------------------------------------------------------- 1 | export interface TabsType { 2 | name: string 3 | path: string 4 | isClose?: boolean 5 | title: string 6 | query: Object 7 | params: Object 8 | } 9 | -------------------------------------------------------------------------------- /src/router/backRoutes/index.ts: -------------------------------------------------------------------------------- 1 | import { getPermissions } from '@/api' 2 | import { routeStore } from '@/pinia/modules/routeStore' 3 | import { createRoutes } from '../utils/createRoutes' 4 | import { removeRoute } from '../utils/remove' 5 | export const getBackRoutes = async function () { 6 | try { 7 | const store = routeStore() 8 | // const { allAuth, route } = await permissions() 9 | const { result, perms } = await getPermissions() 10 | await removeRoute() 11 | store.$patch({ 12 | auth: perms, 13 | routesList: result, 14 | }) 15 | createRoutes() 16 | } catch (error) { 17 | console.error('error', error) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { useProfileStore } from '@/pinia/modules/user' 2 | import { App } from 'vue' 3 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 4 | import { staticRoutes } from './routes/basic' 5 | import { afterEach } from './utils/afterEach' 6 | import { beforeEach } from './utils/beforeEach' 7 | import { mountNewData, mountRouter } from './utils/mountRouter' 8 | export const router = createRouter({ 9 | history: createWebHistory(), 10 | routes: staticRoutes as RouteRecordRaw[], 11 | scrollBehavior: () => ({ left: 0, top: 0 }), 12 | strict: true, 13 | }) 14 | export async function setRoute(app: App) { 15 | const { token } = storeToRefs(useProfileStore()) 16 | if (unref(token)) { 17 | mountRouter() 18 | } 19 | app.use(router) 20 | if (unref(token)) { 21 | mountNewData() 22 | } 23 | } 24 | router.beforeEach(async (to, from, next) => beforeEach(to, from, next)) 25 | // 切换完成 26 | router.afterEach(() => afterEach()) 27 | -------------------------------------------------------------------------------- /src/router/moveRoutes/index.ts: -------------------------------------------------------------------------------- 1 | import { movePermissions } from '@/api' 2 | import { routeStore } from '@/pinia/modules/routeStore' 3 | import { createRoutes } from '../utils/createRoutes' 4 | import { removeRoute } from '../utils/remove' 5 | const modules = import.meta.globEager('./**/*.ts') 6 | const router404Components = () => import('@/layouts/error/router404.vue') 7 | export const errSymbol = Symbol('router404Components') 8 | export const moveRoutersMap = new Map() 9 | moveRoutersMap.set(errSymbol, router404Components) 10 | export const getMoveRoutes = async function () { 11 | try { 12 | const { route, allAuth } = await movePermissions() 13 | removeRoute() 14 | const routeStoreValue = routeStore() 15 | routeStoreValue.$patch({ 16 | routesList: route, 17 | auth: allAuth, 18 | }) 19 | createRoutes() 20 | } catch (error) { 21 | console.error(`Move模式获取权限失败:`, error) 22 | } 23 | } 24 | export const moveStaticRoutes = async function () { 25 | Object.keys(modules).forEach((item: string) => { 26 | Object.keys(modules[item].default).forEach((comKey: string) => { 27 | moveRoutersMap.set(comKey, modules[item].default[comKey]) 28 | }) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/router/moveRoutes/modules/errPage.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/router/moveRoutes/modules/errPage.ts -------------------------------------------------------------------------------- /src/router/moveRoutes/modules/system.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vzane0904/v-naive-admin/eaf8d1714df9cf4edfd4a649c91c0526f755d02b/src/router/moveRoutes/modules/system.ts -------------------------------------------------------------------------------- /src/router/roleRoutes/home.ts: -------------------------------------------------------------------------------- 1 | import { RouteType } from '@/type/route' 2 | import { MenuIcon } from '../../../mock/config/icon' 3 | 4 | const route: Array = [ 5 | { 6 | path: '/home', 7 | name: 'home', 8 | component: () => import('@/views/home.vue'), 9 | meta: { 10 | icon: MenuIcon.home, 11 | title: '首页', 12 | roles: ['admin'], 13 | }, 14 | }, 15 | ] 16 | export default route 17 | -------------------------------------------------------------------------------- /src/router/roleRoutes/index.ts: -------------------------------------------------------------------------------- 1 | import { RouteType } from '@/type/route' 2 | const modules = import.meta.globEager('./**/*.ts') 3 | export let roleRoutesMap: Array = [] 4 | export const roleStaticRoutes = async function (): Promise { 5 | roleRoutesMap = [] 6 | Object.keys(modules).forEach((item: string) => { 7 | roleRoutesMap = roleRoutesMap.concat(...modules[item].default) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/router/routes/basic.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router' 2 | const layoutPage = () => import(`@/layouts/default/view.vue`) 3 | const error404Page = () => import(`@/layouts/error/404.vue`) 4 | const login: Array = [ 5 | { 6 | path: '/login', 7 | name: 'login', 8 | component: () => import('@/views/login/index.vue'), 9 | meta: { 10 | icon: 'ion:grid-outline', 11 | title: 'login', 12 | }, 13 | }, 14 | ] 15 | const redirect = { 16 | path: '/redirect', 17 | name: 'redirectPath', 18 | component: layoutPage, 19 | meta: { 20 | icon: 'ion:grid-outline', 21 | title: 'redirect', 22 | }, 23 | children: [ 24 | { 25 | path: '/redirect/:path(.*)', 26 | name: 'redirect', 27 | component: () => import('@/layouts/refresh/index.vue'), 28 | meta: { 29 | title: '刷新中', 30 | }, 31 | }, 32 | ], 33 | } 34 | const error = [ 35 | { 36 | path: '/error', 37 | name: 'error', 38 | redirect: '/404', 39 | component: layoutPage, 40 | meta: { 41 | icon: 'ion:grid-outline', 42 | title: 'error', 43 | }, 44 | children: [ 45 | { 46 | path: '/404', 47 | name: '404', 48 | component: error404Page, 49 | meta: { 50 | icon: 'ion:grid-outline', 51 | title: '404', 52 | }, 53 | }, 54 | ], 55 | }, 56 | // 404页面 57 | { 58 | path: '/:pathMatch(.*)*', 59 | component: error404Page, 60 | redirect: '/error', 61 | name: 'Match', 62 | }, 63 | ] 64 | 65 | export const staticRoutes = [...login, redirect, ...error] 66 | -------------------------------------------------------------------------------- /src/router/utils/afterEach.ts: -------------------------------------------------------------------------------- 1 | import { NProgressDone } from '@/utils/NProgress' 2 | 3 | export const afterEach = () => { 4 | NProgressDone() 5 | } 6 | -------------------------------------------------------------------------------- /src/router/utils/beforeEach.ts: -------------------------------------------------------------------------------- 1 | import { baseHome, noAddTabs } from '@/config' 2 | import { themeStore } from '@/pinia/modules/theme' 3 | import { useProfileStore } from '@/pinia/modules/user' 4 | import { NProgressStart } from '@/utils/NProgress' 5 | import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router' 6 | import { addTabs } from '.' 7 | 8 | export const beforeEach = ( 9 | to: RouteLocationNormalized, 10 | from: RouteLocationNormalized, 11 | next: NavigationGuardNext, 12 | ) => { 13 | const useStore = useProfileStore() 14 | if (to.path !== '/login' && !useStore.token) { 15 | const query = { redirectPath: to.path } 16 | next({ 17 | path: '/login', 18 | query: to.path === '/404' || !to.path ? {} : query, 19 | }) 20 | } 21 | if (!noAddTabs.includes(to.name as string) && !to.meta.hideTab) { 22 | addTabs(to) 23 | } 24 | nextTick(() => { 25 | const title = useTitle() 26 | title.value = 27 | (to.meta.title || 'App') + ' - ' + import.meta.env.VITE_APP_TITLE 28 | }) 29 | const store = themeStore() 30 | if (store.progressBar) { 31 | NProgressStart() 32 | } 33 | // 登录后禁止手动跳转到login 必须要点击退出按钮 清空token 34 | if (to.path === '/login' && useStore.token) { 35 | next(baseHome) 36 | } else { 37 | next() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/router/utils/createRoutes.ts: -------------------------------------------------------------------------------- 1 | import { permissionMode } from '@/config' 2 | import { RoleEnum } from '@/enum/route' 3 | import { routeStore } from '@/pinia/modules/routeStore' 4 | import { RouteType } from '@/type/route' 5 | import { RenderComponent, transformRoute } from '.' 6 | import { router } from '..' 7 | import { roleRoutesMap } from '../roleRoutes' 8 | // 初始化路由防止路由刷新后页面404 这里是获取的旧路由信息进行挂载 9 | export const createRoutes = () => { 10 | let roInfo: Array = [] 11 | const store = routeStore() 12 | // @ts-ignore 13 | if (permissionMode === RoleEnum.MOVE || permissionMode === RoleEnum.BACK) { 14 | roInfo = transformRoute(store.routesList) 15 | } else { 16 | //角色 17 | const item = transformRoute(roleRoutesMap) 18 | if (item) { 19 | roInfo = store.routesList = item 20 | } 21 | } 22 | 23 | roInfo.forEach((item: RouteType) => { 24 | let routeName = item.name 25 | if (item.children?.length) { 26 | router.addRoute(item) 27 | store.routesName.push(item.name) 28 | } else { 29 | routeName = item.path + 'Parent' 30 | const view = { 31 | name: routeName, 32 | path: '', 33 | redirect: item.path, //重定向 34 | component: RenderComponent('view'), 35 | children: [item], 36 | } 37 | router.addRoute(view) 38 | store.routesName.push(view.name) 39 | } 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /src/router/utils/mountRouter.ts: -------------------------------------------------------------------------------- 1 | import { onLoadGetPermission, permissionMode } from '@/config' 2 | import { RoleEnum } from '@/enum/route' 3 | import { getBackRoutes } from '../backRoutes' 4 | import { getMoveRoutes, moveStaticRoutes } from '../moveRoutes' 5 | import { roleStaticRoutes } from '../roleRoutes' 6 | import { createRoutes } from './createRoutes' 7 | /** 8 | @mountRouter 获取旧数据进行路由挂载 9 | @returns undefined 10 | **/ 11 | export const mountRouter = () => { 12 | // @ts-ignore 13 | if (permissionMode === RoleEnum.ROLE) { 14 | roleStaticRoutes() 15 | // @ts-ignore 16 | } else if (permissionMode === RoleEnum.MOVE) { 17 | moveStaticRoutes() 18 | } 19 | createRoutes() 20 | } 21 | /** 22 | @mountNewData 获取新数据进行路由挂载 23 | @returns undefined 24 | **/ 25 | export const mountNewData = async () => { 26 | return new Promise(async (resolve) => { 27 | // @ts-ignore 28 | if (onLoadGetPermission && permissionMode === RoleEnum.MOVE) { 29 | await moveStaticRoutes() 30 | await getMoveRoutes() 31 | resolve(true) 32 | // @ts-ignore 33 | } else if (permissionMode === RoleEnum.BACK) { 34 | await getBackRoutes() 35 | resolve(true) 36 | // @ts-ignore 37 | } else if (permissionMode === RoleEnum.ROLE) { 38 | await roleStaticRoutes() 39 | await createRoutes() 40 | resolve(true) 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /src/router/utils/remove.ts: -------------------------------------------------------------------------------- 1 | import { routeStore } from '@/pinia/modules/routeStore' 2 | import { router } from '..' 3 | export const removeRoute = () => { 4 | const store = routeStore() 5 | store.routesName.forEach((item) => router.removeRoute(item)) 6 | store.$patch({ 7 | routesName: [], 8 | routesList: [], 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/type/component.d.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | declare type Component = 3 | | ReturnType 4 | | (() => Promise) 5 | | (() => Promise) 6 | -------------------------------------------------------------------------------- /src/type/config.d.ts: -------------------------------------------------------------------------------- 1 | import { LocaleType } from '@/enum/locale' 2 | import { themeColor, headerColor, siderColor } from '@/config' 3 | export type Position = 'top' | 'none' | 'bottom' 4 | export type Layout = 1 | 2 | 3 5 | export interface ProjectConfig { 6 | layout: Layout 7 | themeColor: typeof themeColor[number] 8 | progressBar: boolean 9 | header: { 10 | color: typeof headerColor[number] 11 | height: number 12 | language: LocaleType 13 | isFullScreenBtn: boolean 14 | showLanGuaGe: boolean 15 | showTopSet: boolean 16 | foldBtnPosition: Position 17 | showLogo: boolean 18 | } 19 | sider: { 20 | color: typeof siderColor[number] 21 | fold: boolean 22 | width: number 23 | showSider: boolean 24 | isAccordion: boolean 25 | } 26 | footer: { 27 | height: number 28 | showFooter: boolean 29 | } 30 | tabs: { 31 | showTabs: boolean 32 | showRefresh: boolean 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/type/form.d.ts: -------------------------------------------------------------------------------- 1 | import { FormInst } from 'naive-ui' 2 | 3 | declare type ComponentType = 4 | | 'Input' 5 | | 'InputGroup' 6 | | 'InputPassword' 7 | | 'InputSearch' 8 | | 'InputTextArea' 9 | | 'InputNumber' 10 | | 'InputCountDown' 11 | | 'Select' 12 | | 'ApiSelect' 13 | | 'TreeSelect' 14 | | 'ApiTree' 15 | | 'ApiTreeSelect' 16 | | 'ApiRadioGroup' 17 | | 'RadioButtonGroup' 18 | | 'RadioGroup' 19 | | 'Checkbox' 20 | | 'CheckboxGroup' 21 | | 'AutoComplete' 22 | | 'ApiCascader' 23 | | 'Cascader' 24 | | 'DatePicker' 25 | | 'MonthPicker' 26 | | 'RangePicker' 27 | | 'WeekPicker' 28 | | 'TimePicker' 29 | | 'Switch' 30 | | 'StrengthMeter' 31 | | 'Upload' 32 | | 'IconPicker' 33 | | 'Render' 34 | | 'Slider' 35 | | 'Rate' 36 | | 'Divider' 37 | | 'ApiTransfer' 38 | interface FormRef extends Element, FormInst { 39 | resetFields: () => void 40 | } 41 | -------------------------------------------------------------------------------- /src/type/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, {}, any> 6 | export default component 7 | } 8 | declare interface Window { 9 | $message: any 10 | $useDialog: any 11 | $useNotification: any 12 | } 13 | declare interface ViteEnv { 14 | readonly VITE_APP_TITLE: string 15 | readonly VITE_APP_PORT: number 16 | VITE_APP_BUILD: string 17 | VITE_APP_HTTPS: boolean 18 | VITE_APP_LOG: boolean 19 | VITE_APP_OPEN: boolean 20 | VITE_APP_PATH: string 21 | VITE_APP_PROXY: Array<[string, string]> 22 | VITE_APP_SOURCEMAP: boolean 23 | VITE_APP_PREFIXCLS: string 24 | VITE_APP_MOCK: boolean 25 | VITE_APP_ANALYSIS: boolean 26 | VITE_APP_PROD_MOCK: boolean 27 | } 28 | -------------------------------------------------------------------------------- /src/type/http.d.ts: -------------------------------------------------------------------------------- 1 | import { axiosTokenName } from '@/config' 2 | import { AxiosRequestConfig } from 'axios' 3 | import { AxiosErrorTip, ContentTypeEnum } from '@/enum/axios' 4 | import { Expand } from '.' 5 | export interface RequestCustom { 6 | // 是否返回原生响应头 比如:需要获取响应头时使用该属性 7 | isReturnNativeResponse?: boolean 8 | // 消息提示类型 9 | errorMessageModal?: `${AxiosErrorTip}` 10 | // 是否加入时间戳 11 | joinTime?: boolean 12 | // 忽略重复请求 13 | ignoreRequest?: boolean 14 | // 有请求重复是cancel提示信息 15 | readonly ignoreMsg?: string 16 | // 是否携带token 17 | withToken?: boolean 18 | //查找某条请求时根据id查找 19 | id?: string 20 | // 是否重试 21 | readonly openRetry?: boolean 22 | // 重试次数 23 | readonly count?: number 24 | // 重试间隔 25 | readonly interval?: number 26 | // 重试第几次 27 | retryCount?: number 28 | // 是否转换请求结果直接拿到data 29 | isConversionRequestResult?: boolean 30 | } 31 | /** 32 | * 基础配置项+自定义配置项 requestOptions 33 | **/ 34 | export interface RequestOptions extends AxiosRequestConfig { 35 | timeout?: number 36 | baseUrl?: string 37 | headers?: { 38 | 'Content-Type'?: 39 | | ContentTypeEnum.JSON 40 | | ContentTypeEnum.FORM_URLENCODED 41 | | ContentTypeEnum.FORM_DATA 42 | [axiosTokenName]?: string 43 | [key: string]: any 44 | } 45 | // 配置项,下面的选项都可以在独立的接口请求中覆盖 46 | requestOptions: RequestCustom 47 | } 48 | /** 49 | * 记录当前请求约束字段 50 | **/ 51 | export interface PendingType extends RequestOptions { 52 | cancel: (msg?: string) => void 53 | } 54 | /** 55 | * 请求成功返回结果 56 | **/ 57 | export interface resultType { 58 | code: number 59 | data: T 60 | msg: string 61 | success: boolean 62 | timestamp?: number 63 | } 64 | 65 | /** 66 | * 请求失败返回结果 67 | **/ 68 | export interface ErrorInfo { 69 | status: number 70 | statusText: string 71 | success: boolean 72 | response?: Object 73 | } 74 | /** 75 | * 处理转换data数据类型 76 | **/ 77 | export type Type = Expand 78 | export type TConversion = b extends false 79 | ? Expand> 80 | : b extends true 81 | ? Expand 82 | : never 83 | -------------------------------------------------------------------------------- /src/type/index.ts: -------------------------------------------------------------------------------- 1 | export interface Record { 2 | [x: string]: T 3 | } 4 | export type Expand = T extends infer o ? { [K in keyof o]: o[K] } : never 5 | type Simplify = { 6 | [P in keyof T]: T[P] 7 | } 8 | export type SetOptional = Simplify< 9 | // Partial in 可有可无键值的那部分 10 | // Pick 必须有的键值的那部分 11 | Partial> & Pick> 12 | > 13 | -------------------------------------------------------------------------------- /src/type/route.d.ts: -------------------------------------------------------------------------------- 1 | import { RouteMeta } from 'vue-router' 2 | import { Expand } from '.' 3 | import { Component } from './component' 4 | 5 | interface MenuType { 6 | name: string 7 | path: string 8 | redirect?: string 9 | paramPath?: string 10 | disabled?: boolean 11 | children?: MenuType[] 12 | meta: RouteMeta 13 | component: Component | string 14 | components?: Component 15 | } 16 | type RouteType = Expand 17 | 18 | declare module 'vue-router' { 19 | interface RouteMeta { 20 | icon?: string 21 | title: string 22 | keepAlive?: boolean 23 | hideMenu?: boolean 24 | roles?: Array 25 | currentActiveMenu?: string // 当前激活的菜单。用于配置详情页时左侧激活的菜单路径 26 | hideTab?: boolean // 当前路由不再标签页显示 27 | transitionName?: string // 指定该路由切换的动画名 28 | iframeSrc?: string 29 | requiresAuth?: boolean 30 | orderNo?: number 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/type/use-naive-hooks.d.ts: -------------------------------------------------------------------------------- 1 | declare type IRregister = (descExample: T) => void 2 | -------------------------------------------------------------------------------- /src/type/vite.defined.ts: -------------------------------------------------------------------------------- 1 | import { Recordable } from 'vite-plugin-mock' 2 | 3 | export {} 4 | declare global { 5 | // eslint-disable-next-line no-underscore-dangle 6 | const __APP_INFO__: { 7 | pkg: { 8 | name: string 9 | version: string 10 | dependencies: Recordable 11 | devDependencies: Recordable 12 | } 13 | lastBuildTime: string 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/NProgress/index.ts: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress' 2 | import 'nprogress/nprogress.css' 3 | // 开始 4 | export const NProgressStart = () => NProgress.start() 5 | // 设置进度 6 | export const NProgressSet = (num: number) => NProgress.set(num) 7 | // 自定义进度 不会为100% 8 | export const NProgressInc = () => NProgress.inc() 9 | // 结束 10 | export const NProgressDone = () => NProgress.done() 11 | NProgress.configure({ showSpinner: false }) //关闭进度条右侧刷新icon 12 | -------------------------------------------------------------------------------- /src/utils/browser.ts: -------------------------------------------------------------------------------- 1 | // //判断是否IE<11浏览器 2 | // export function isIE() { 3 | // return navigator.userAgent.indexOf('compatible') > -1 && navigator.userAgent.indexOf('MSIE') > -1 4 | 5 | // } 6 | 7 | // export function isIE11() { 8 | // return navigator.userAgent.indexOf('Trident') > -1 && navigator.userAgent.indexOf('rv:11.0') > -1 9 | // } 10 | 11 | // //判断是否IE的Edge浏览器 12 | // export function isEdge() { 13 | // return navigator.userAgent.indexOf('Edge') > -1 && !isIE() 14 | // } 15 | 16 | // export function getIEVersion() { 17 | // let userAgent = navigator.userAgent //取得浏览器的userAgent字符串 18 | // let isIE = isIE() 19 | // let isIE11 = isIE11() 20 | // let isEdge = isEdge() 21 | 22 | // if (isIE) { 23 | // let reIE = new RegExp('MSIE (\\d+\\.\\d+);') 24 | // reIE.test(userAgent) 25 | // let fIEVersion = parseFloat(RegExp['$1']) 26 | // if (fIEVersion === 7 || fIEVersion === 8 || fIEVersion === 9 || fIEVersion === 10) { 27 | // return fIEVersion 28 | // } else { 29 | // return 6//IE版本<7 30 | // } 31 | // } else if (isEdge) { 32 | // return 'edge' 33 | // } else if (isIE11) { 34 | // return 11 35 | // } else { 36 | // return -1 37 | // } 38 | // } 39 | -------------------------------------------------------------------------------- /src/utils/createAsyncComponent/index.tsx: -------------------------------------------------------------------------------- 1 | // import { ErrorComponent } from '@/components/ErrorComponent' 2 | // import { LoadingComponent } from '@/components/LoadingComponent' 3 | // import { Fn } from '@vueuse/core' 4 | // import { defineAsyncComponent } from 'vue' 5 | 6 | // const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) 7 | // interface Options { 8 | // delay?: number 9 | // timeout?: number 10 | // loading?: boolean 11 | // error?: boolean 12 | // delayCerateTime?: number //延迟创建时间 13 | // } 14 | // export const createAsyncComponent = (loaderCom: Fn, options: Options = {}) => { 15 | // let { 16 | // delay = 200, 17 | // timeout = 10000, 18 | // loading = true, 19 | // error = true, 20 | // delayCerateTime = 200, 21 | // } = options 22 | // return defineAsyncComponent({ 23 | // loader: async () => { 24 | // await sleep(delayCerateTime) 25 | // return loaderCom 26 | // }, 27 | // loadingComponent: loading ? LoadingComponent : undefined, 28 | // errorComponent: error ? ErrorComponent : undefined, 29 | // delay, 30 | // timeout, 31 | // onError: error 32 | // ? (error, retry, fail, attempts) => { 33 | // if (error.message.match(/fetch/) && attempts <= 3) { 34 | // // 请求发生错误时重试,最多可尝试 3 次 35 | // retry() 36 | // } else { 37 | // // 注意,retry/fail 就像 promise 的 resolve/reject 一样: 38 | // // 必须调用其中一个才能继续错误处理。 39 | // fail() 40 | // } 41 | // } 42 | // : () => {}, 43 | // }) 44 | // } 45 | -------------------------------------------------------------------------------- /src/utils/dataType.ts: -------------------------------------------------------------------------------- 1 | // 检测是否空数组 isEmptyObje对象 2 | // 是否window对象 isWindow 3 | // 检测全类型 toType 4 | // 根据传入的类型名称返回 5 | // 是否纯粹对象标准普通对象直接指向Object的原型 isPlanObject 6 | // 是否类数组 isArrayLike 7 | // isNumeric检测是否为数字 8 | const result = {}, 9 | toString = result.toString 10 | /** 11 | * @name 检测空对象 12 | **/ 13 | export const isEmptyObjet = function isEmptyObjet(target: object) { 14 | if (typeof Reflect !== 'undefined') { 15 | return Reflect.ownKeys(target).length > 0 16 | } 17 | if (typeof Symbol !== 'undefined') { 18 | return ( 19 | [ 20 | ...Object.getOwnPropertyNames(target), 21 | ...Object.getOwnPropertySymbols(target), 22 | ].length > 0 23 | ) 24 | } 25 | } 26 | /** 27 | * @name 是否是window对象 28 | **/ 29 | export const isWindow = function isWindow(target: { window: any } | null) { 30 | return target !== null && target.window === target 31 | } 32 | /** 33 | * @name 是否函数 34 | * @1 排除某些浏览器对于 typeof document.createElement('object') === 'Function' 存在的问题 35 | * @2 排除某些浏览器对于 typeof document.querySelectorAll('div') === 'function' 存在的问题 36 | **/ 37 | export const isFunction = function isFunction(target: any): Boolean { 38 | return ( 39 | typeof target === 'function' && 40 | typeof target.nodeType !== 'number' && // @1 41 | typeof target.item !== 'function' // @1 42 | ) 43 | } 44 | /** 45 | * @name 判断数据类型 46 | **/ 47 | export const toType = function toType(target: any) { 48 | const reg = /^\[object ([\W\w]+)\]$/ 49 | if (target === null) { 50 | return String(target) 51 | } 52 | const type = typeof target 53 | return /^(object|function)$/.test(type) 54 | ? (reg.exec(toString.call(target)) as unknown as string)[1].toLowerCase() 55 | : type 56 | } 57 | /** 58 | * @name 是否是类数组 59 | * 判断的不算是很严谨,如有需要可扩展 60 | **/ 61 | export const isArrayLike = function isArrayLike(target: any) { 62 | const length = target && 'target' in target, 63 | type = toType(target) 64 | return ( 65 | length === 0 || 66 | type === 'array' || 67 | (typeof length === 'number' && length > 0 && length - 1 in target) 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /src/utils/fullScreen/index.ts: -------------------------------------------------------------------------------- 1 | // import { themeStore } from '@/pinia/theme' 2 | import { themeStore } from '@/pinia/modules/theme' 3 | import screenfull from 'screenfull' 4 | 5 | // 获取是否全屏状态 6 | export const getFullScreen = () => { 7 | const theme = themeStore() 8 | theme.isFullScreen = screenfull.isFullscreen 9 | } 10 | /** 11 | * 全屏 12 | **/ 13 | export const openFullScreen = () => { 14 | if (!screenfull.isEnabled) { 15 | // createErrorMsg({ title: "错误", content: "浏览器不支持全屏" }); 16 | } else { 17 | screenfull.toggle() 18 | screenfull.on('change', () => { 19 | getFullScreen() 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | // 判断是否是https 2 | export const isHttps = (target: string) => /^https:\/\//.test(target) 3 | /** 4 | *@params isProtocol is https www ? 5 | **/ 6 | export const isProtocol = (target: string) => 7 | /^(https:\/\/|http:\/\/|www.)/.test(target) 8 | /** 9 | *@params DeepCopy 递归深拷贝 10 | **/ 11 | export const DeepCopy = (source: any) => { 12 | let target: any = {} 13 | if (typeof source === 'object') { 14 | target = Array.isArray(source) ? [] : {} 15 | for (const key in source) { 16 | // eslint-disable-next-line no-prototype-builtins 17 | if (source.hasOwnProperty(key)) { 18 | if (typeof source[key] !== 'object') { 19 | target[key] = source[key] 20 | } else { 21 | target[key] = DeepCopy(source[key]) 22 | } 23 | } 24 | } 25 | } else { 26 | target = source 27 | } 28 | return target 29 | } 30 | /** 31 | * 平铺节点转换树结构 32 | * @parentId 父节点id 33 | * @id 自己的唯一id 34 | **/ 35 | export const flatTree = (treeArr: Array) => { 36 | const map: Record = [] 37 | const arr = [] 38 | for (const key of treeArr) { 39 | map[key.id] = key 40 | } 41 | for (const key of treeArr) { 42 | if (key.parentId === 0) { 43 | arr.push(key) 44 | } else if (!map[key.parentId]?.children) { 45 | map[key.parentId].children = [key] 46 | } else { 47 | map[key.parentId].children.push(key) 48 | } 49 | } 50 | return arr 51 | } 52 | 53 | // 路径转数组 54 | export function deepPaths(paths: string[], splitOr?: string) { 55 | const list: any[] = [] 56 | paths.forEach((e) => { 57 | const arr: string[] = e.split(splitOr || '/').filter(Boolean) 58 | let c = list 59 | arr.forEach((a, i) => { 60 | let d = c.find((e) => e.label === a) 61 | if (!d) { 62 | d = { 63 | label: a, 64 | // value: a, 65 | value: arr.slice(0, i + 1).join('/'), 66 | children: arr[i + 1] ? [] : null, 67 | } 68 | c.push(d) 69 | } 70 | if (d.children) { 71 | c = d.children 72 | } 73 | }) 74 | }) 75 | return list 76 | } 77 | -------------------------------------------------------------------------------- /src/utils/log/index.ts: -------------------------------------------------------------------------------- 1 | export const logError = (e: Error) => { 2 | console.error(e) 3 | } 4 | export const logWarn = (e: string) => console.warn(e) 5 | -------------------------------------------------------------------------------- /src/utils/message/index.ts: -------------------------------------------------------------------------------- 1 | import { MessageOptions, DialogOptions, NotificationOptions } from 'naive-ui' 2 | import { DialogApiInjection } from 'naive-ui/es/dialog/src/DialogProvider' 3 | import { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider' 4 | import { NotificationApiInjection } from 'naive-ui/es/notification/src/NotificationProvider' 5 | export const useMessage = (): MessageApiInjection => window.$message 6 | export const useDialog = (): DialogApiInjection => window.$useDialog 7 | export const useNotification = (): NotificationApiInjection => 8 | window.$useNotification 9 | /** 10 | * @content 内容 11 | * @options MessageOptions 12 | * @returns useMessage 13 | **/ 14 | export const createMsg = (content: string, options?: MessageOptions) => { 15 | const customOptions: MessageOptions = { 16 | type: 'success', 17 | } 18 | const merge: MessageOptions = Object.assign(customOptions, options) 19 | return useMessage().create(content, merge) 20 | } 21 | 22 | /** 23 | * @createModal 创建 Modal 24 | * @options DialogOptions 25 | * @returns useDialog 26 | **/ 27 | export const createModal = (options: DialogOptions) => { 28 | const customOptions: DialogOptions = { 29 | autoFocus: false, 30 | positiveText: '确定', 31 | } 32 | const merge = Object.assign(customOptions, options) 33 | return useDialog().create(merge) 34 | } 35 | /** 36 | * @createModal 创建通知 37 | * @options DialogOptions 38 | * @returns useNotification 39 | **/ 40 | export const createNotification = (options: NotificationOptions) => { 41 | const customOptions: NotificationOptions = { 42 | type: 'success', 43 | title: '标题', 44 | content: '暂无内容', 45 | description: '', 46 | duration: 3000, 47 | keepAliveOnHover: true, 48 | } 49 | const merge = Object.assign(customOptions, options) 50 | return useNotification().create(merge) 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/regExp.ts: -------------------------------------------------------------------------------- 1 | //去除空格 2 | export const replaceTrim = (text: string) => text.replace(/\s+/g, '') 3 | //去除换行 4 | export const ClearBr = (text: string) => 5 | replaceTrim(text.replace(/<\/?.+?>/g, '').replace(/[\r\n]/g, '')) 6 | //去除左侧空格 7 | export const ClearLeftTrim = (text: string) => text.replace(/^\s*/g, '') 8 | //去右空格 9 | export const ClearRightTrim = (text: string) => text.replace(/\s*$/g, '') 10 | //去掉字符串两端的空格 11 | export const ClearTrim = (text: string) => text.replace(/(^\s*)|(\s*$)/g, '') 12 | //去除字符串中间空格 13 | export const ClearCenterTrim = (text: string) => text.replace(/\s/g, '') 14 | //是否为由数字组成的字符串 15 | export const IsNumberStr = (text: string) => /^[0-9]*$/.test(text) 16 | // 邮箱 17 | // export const emailReg = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/ 18 | export const emailReg = 19 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 20 | // 手机号 21 | export const phoneReg = 22 | /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/ 23 | // 车牌号 24 | export const licensePlateReg = 25 | /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/ 26 | // 密码8位以上 27 | // export const passWordReg = 28 | // /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*])[\da-zA-Z~!@#$%^&*.]{8,}$/ 29 | export const grade = { 30 | //大小写字母0-9随意组合 31 | one: /^(?=.*[a-zA-Z\d])[a-zA-Z\d]{5,12}$/, 32 | // ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$ 33 | //至少一个字母(不区分大小写)和一个数字 34 | two: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{4,}$/, 35 | //一个字母(不区分大小写)一个数字和一个特殊字符 36 | three: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@!%*#&.])[A-Za-z\d$@!%*#&.]{5,12}$/, 37 | //一个大写字母,一个小写字母,一个数字和一个特殊字符 38 | rour: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@!%*#&.])[A-Za-z\d$@!%*#&.]{5,12}$/, 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/type/index.ts: -------------------------------------------------------------------------------- 1 | type LocaleType = 2 | | 'string' 3 | | 'number' 4 | | 'boolean' 5 | | 'undefined' 6 | | 'null' 7 | | 'symbol' 8 | | 'bigint' 9 | | 'function' 10 | | 'object' 11 | | 'array' 12 | | 'date' 13 | | 'regexp' 14 | | 'math' 15 | /** 16 | * @toTye 判断数据类型 17 | * @result [string、number、boolean、undefined、null、symbol、bigint、function、object、array、date、regexp、math ... ] 18 | **/ 19 | const toTye = function toTye(target: any) { 20 | const reg = /^\[object ([\w\W]+)\]$/, 21 | objOrFun = /^(object|function)$/, 22 | type = typeof target 23 | return objOrFun.test(type) 24 | ? (reg.exec(toString.call(target)) as Array)[1].toLowerCase() 25 | : type 26 | } 27 | const isType = function isType(target: any, result: LocaleType) { 28 | return toTye(target) === result 29 | } 30 | export default { 31 | toTye, 32 | isType, 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | // 数据类型 2 | const is = (val: any, type: string) => { 3 | return toString.call(val) === `[object ${type}]` 4 | } 5 | // 是否对象 6 | export const isObj = (val: any) => { 7 | return is(val, 'Object') 8 | } 9 | // 指定长度和基数 10 | export const uuid = (len?: number, radix?: number) => { 11 | const chars: Array = 12 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('') 13 | const uuid: Array = [] 14 | let i = 0 15 | radix = radix || chars.length 16 | if (len) { 17 | for (i = 0; i < len; i++) { 18 | uuid[i] = chars[0 | (Math.random() * radix)] 19 | } 20 | } else { 21 | // rfc4122, version 4 form 22 | let r 23 | // rfc4122 requires these characters 24 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-' 25 | uuid[14] = '4' 26 | for (i = 0; i < 36; i++) { 27 | if (!uuid[i]) { 28 | r = 0 | (Math.random() * 16) 29 | uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r] 30 | } 31 | } 32 | } 33 | return uuid.join('') 34 | } 35 | /** 36 | * 平铺节点转换树结构 37 | * @parentId 父节点id 38 | * @id 自己的weiyiid 39 | **/ 40 | export const flatTree = (treeArr: Array) => { 41 | const map: Record = [] 42 | const arr = [] 43 | for (const key of treeArr) { 44 | map[key.id] = key 45 | } 46 | for (const key of treeArr) { 47 | if (key.parentId === 0) { 48 | arr.push(key) 49 | } else if (!map[key.parentId]?.children) { 50 | map[key.parentId].children = [key] 51 | } else { 52 | map[key.parentId].children.push(key) 53 | } 54 | } 55 | return arr 56 | } 57 | // 对象转字符串 58 | export const jsonStr = (json: object) => { 59 | return JSON.stringify(json) 60 | } 61 | // 字符串转对象 62 | export const jsonObj = (json: string) => { 63 | return JSON.parse(json) 64 | } 65 | // 判断是否是https 66 | export const isHttps = (target: string) => /^https:\/\//.test(target) 67 | // 转16 rgba 颜色 68 | export const colorToRGB = (color: string | any[], opt = '1') => { 69 | let color1, color2, color3 70 | color = String(color) 71 | if (typeof color !== 'string') { 72 | return 73 | } 74 | if (color.charAt(0) === '#') { 75 | color = color.substring(1) 76 | } 77 | if (color.length === 3) { 78 | color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2] 79 | } 80 | if (/^[0-9a-fA-F]{6}$/.test(color)) { 81 | color1 = parseInt(color.slice(0, 2), 16) 82 | color2 = parseInt(color.slice(2, 4), 16) 83 | color3 = parseInt(color.slice(4, 6), 16) 84 | return 'rgba(' + color1 + ',' + color2 + ',' + color3 + ',' + opt + ')' 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/views/errPage/403/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/views/errPage/404/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/views/errPage/500/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/views/function/copy/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/function/fullScreen/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/function/http/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 26 | 27 | -------------------------------------------------------------------------------- /src/views/function/msg/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/function/sessionTimeout/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | -------------------------------------------------------------------------------- /src/views/home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/views/level/menu1/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/level/menu2.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /src/views/login/src/components/Theme.vue: -------------------------------------------------------------------------------- 1 | 18 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /src/views/login/src/components/passWord.vue: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /src/views/login/src/components/pictureCode.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/views/login/src/components/userName.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /src/views/login/src/components/userNameLogin.vue: -------------------------------------------------------------------------------- 1 | 16 | 45 | -------------------------------------------------------------------------------- /src/views/login/src/config.ts: -------------------------------------------------------------------------------- 1 | import { emailReg, grade } from '@/utils/regExp' 2 | import { FormItemRule, FormRules } from 'naive-ui' 3 | export const UserNameRules: FormRules = { 4 | userName: [ 5 | { 6 | required: true, 7 | validator(rule: FormItemRule, value: string) { 8 | if (!value) { 9 | return new Error('请输入用户名') 10 | } else if (!grade.one.test(value)) { 11 | return new Error('用户名长度5-12位(字母/数字)') 12 | } 13 | return true 14 | }, 15 | trigger: ['input', 'blur'], 16 | }, 17 | ], 18 | } 19 | export const PasswordRules: FormRules = { 20 | password: [ 21 | { 22 | required: true, 23 | validator(rule: FormItemRule, value: string) { 24 | if (!value) { 25 | return new Error('请输入密码') 26 | } else if (!grade.two.test(value)) { 27 | return new Error('密码必须包含:大小写字母,数字,字符($@!%*#&.)') 28 | } 29 | return true 30 | }, 31 | trigger: ['input', 'blur'], 32 | }, 33 | ], 34 | } 35 | export const EmailRules: FormRules = { 36 | email: [ 37 | { 38 | required: true, 39 | validator(rule: FormItemRule, value: string) { 40 | if (!value) { 41 | return new Error('请输入邮箱') 42 | } else if (!emailReg.test(value)) { 43 | return new Error('邮箱不符合规则') 44 | } 45 | return true 46 | }, 47 | trigger: ['input', 'blur'], 48 | }, 49 | ], 50 | } 51 | const PictureRules: FormRules = { 52 | picCode: [ 53 | { 54 | required: true, 55 | validator(rule: FormItemRule, value: string) { 56 | if (!value) { 57 | return new Error('请输入图片验证码') 58 | } 59 | return true 60 | }, 61 | trigger: ['input', 'blur'], 62 | }, 63 | ], 64 | } 65 | // 用户名登录 66 | export const userRules: FormRules = { 67 | ...UserNameRules, 68 | ...PasswordRules, 69 | ...PictureRules, 70 | } 71 | -------------------------------------------------------------------------------- /src/views/login/src/hooks/loginFn.ts: -------------------------------------------------------------------------------- 1 | // import { userNameLogin } from '@/api' 2 | import { baseHome } from '@/config' 3 | import { useProfileStore } from '@/pinia/modules/user' 4 | import { mountNewData } from '@/router/utils/mountRouter' 5 | import { createNotification } from '@/utils/message' 6 | import { Fn } from '@vueuse/core' 7 | import { FormInst } from 'naive-ui' 8 | import { IUserNameLogin } from '../type' 9 | import { useHttp } from '@/hooks/useHttp' 10 | import { Api } from '@/api/Api' 11 | import dayjs from 'dayjs' 12 | export const userNameLogin = function (formValue: IUserNameLogin) { 13 | const route = useRoute() 14 | const router = useRouter() 15 | const ElRef = ref(null) 16 | const loading = ref(false) 17 | const loginGO = async () => { 18 | try { 19 | loading.value = true 20 | const { 21 | run, 22 | data, 23 | loading: httpLoading, 24 | } = useHttp({ 25 | Api: Api.userNameLogin, 26 | methods: 'POST', 27 | data: formValue, 28 | }) 29 | await run() 30 | if (data.value) { 31 | const useStore = useProfileStore() 32 | useStore.$patch({ 33 | token: data.value.token, 34 | userName: data.value.userName, 35 | }) 36 | await mountNewData() 37 | const next = () => 38 | createNotification({ 39 | title: '登录成功', 40 | description: dayjs().format('YYYY-MM-DD HH:mm:ss'), 41 | content: `欢迎回来: ${data.value.userName}`, 42 | type: 'success', 43 | }) 44 | if (route.query.redirectPath && route.query.redirectPath !== '/404') { 45 | router.push(route.query.redirectPath as string).finally(() => next()) 46 | } else { 47 | router 48 | .push(baseHome) 49 | .catch(() => {}) 50 | .finally(() => next()) 51 | } 52 | loading.value = unref(httpLoading) 53 | return data 54 | } 55 | loading.value = unref(httpLoading) 56 | return false 57 | } catch (error) { 58 | loading.value = false 59 | return false 60 | } 61 | } 62 | const loginValidate = (callBack?: Fn | null, error?: Fn | null) => { 63 | ElRef.value?.validate(async (errors: any) => { 64 | if (!errors) { 65 | if (callBack) { 66 | await callBack() 67 | loginGO() 68 | } 69 | } else if (error) { 70 | await error() 71 | } 72 | }) 73 | } 74 | 75 | return { 76 | ElRef, 77 | loginValidate, 78 | loginGO, 79 | loading, 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/views/login/src/type.ts: -------------------------------------------------------------------------------- 1 | export type TargetContext = 'user' | 'register' 2 | export interface Params { 3 | userName: string | number 4 | password: string | number 5 | } 6 | export interface IUserNameLogin { 7 | userName: string 8 | password: string 9 | picCode: string 10 | picId: string 11 | } 12 | -------------------------------------------------------------------------------- /src/views/login/src/useLogin.ts: -------------------------------------------------------------------------------- 1 | // import { getSms } from '@/api' 2 | import { ref } from 'vue' 3 | 4 | export const useGetCode = function (count = 60) { 5 | const isGetPhone = ref(false) //loading 6 | const codeTest = ref('获取验证码') //倒计时文字 7 | const current = ref(count) //几秒后停止 8 | const getPhoneCode = async function () { 9 | isGetPhone.value = true 10 | codeTest.value = `${current.value}秒后重新获取` 11 | // getSms() 12 | const inter = setInterval(() => { 13 | codeTest.value = --current.value + '秒后重新获取' 14 | if (current.value === 0) { 15 | codeTest.value = '获取验证码' 16 | isGetPhone.value = false 17 | current.value = count 18 | clearInterval(inter) 19 | } 20 | }, 1000) 21 | } 22 | return { 23 | isGetPhone, 24 | codeTest, 25 | getPhoneCode, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/views/login/template/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 40 | 58 | -------------------------------------------------------------------------------- /src/views/permission/back/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/permission/web/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/system/about/src/rednder.tsx: -------------------------------------------------------------------------------- 1 | import { TagProps } from 'naive-ui' 2 | 3 | export const renderLink = (text: string, href: any) => ( 4 | 5 | {text} 6 | 7 | ) 8 | export const renderTag = ( 9 | text: string, 10 | type: Pick['type'], 11 | ) => {text} 12 | -------------------------------------------------------------------------------- /src/views/system/changePassword/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/system/dept/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/system/menu/type/index.ts: -------------------------------------------------------------------------------- 1 | import { RouteMeta } from 'vue-router' 2 | 3 | export interface IMenuList { 4 | parentId: string | number 5 | id?: number 6 | name: string 7 | orderNo: number 8 | state: number 9 | icon: string 10 | path: string 11 | redirect: string 12 | component: string | null 13 | iframeSrc: string 14 | title: string 15 | keepAlive: number 16 | hideMenu: boolean 17 | hideTab: boolean 18 | type: number 19 | meta?: RouteMeta 20 | } 21 | -------------------------------------------------------------------------------- /src/views/system/user/type/index.ts: -------------------------------------------------------------------------------- 1 | export interface IUserList { 2 | state: number 3 | createDate: string 4 | updateDate: string 5 | deleteDate: string 6 | id: number 7 | menuIdList: string 8 | label: string 9 | name: string 10 | remark: string 11 | roleName: Array 12 | } 13 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | plugins: ['stylelint-order'], 4 | customSyntax: 'postcss-less', 5 | extends: ['stylelint-config-standard', 'stylelint-config-prettier'], 6 | rules: { 7 | 'selector-class-pattern': null, 8 | 'selector-pseudo-class-no-unknown': [ 9 | true, 10 | { 11 | ignorePseudoClasses: ['global'], 12 | }, 13 | ], 14 | 'selector-pseudo-element-no-unknown': [ 15 | true, 16 | { 17 | ignorePseudoElements: ['v-deep'], 18 | }, 19 | ], 20 | 'at-rule-no-unknown': [ 21 | true, 22 | { 23 | ignoreAtRules: [ 24 | 'tailwind', 25 | 'apply', 26 | 'variants', 27 | 'responsive', 28 | 'screen', 29 | 'function', 30 | 'if', 31 | 'each', 32 | 'include', 33 | 'mixin', 34 | ], 35 | }, 36 | ], 37 | 'no-empty-source': null, 38 | 'named-grid-areas-no-invalid': null, 39 | 'unicode-bom': 'never', 40 | 'no-descending-specificity': null, 41 | 'font-family-no-missing-generic-family-keyword': null, 42 | 'declaration-colon-space-after': 'always-single-line', 43 | 'declaration-colon-space-before': 'never', 44 | // 'declaration-block-trailing-semicolon': 'always', 45 | 'rule-empty-line-before': [ 46 | 'always', 47 | { 48 | ignore: ['after-comment', 'first-nested'], 49 | }, 50 | ], 51 | 'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }], 52 | 'order/order': [ 53 | [ 54 | 'dollar-variables', 55 | 'custom-properties', 56 | 'at-rules', 57 | 'declarations', 58 | { 59 | type: 'at-rule', 60 | name: 'supports', 61 | }, 62 | { 63 | type: 'at-rule', 64 | name: 'media', 65 | }, 66 | 'rules', 67 | ], 68 | { severity: 'warning' }, 69 | ], 70 | }, 71 | ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], 72 | // overrides: [ 73 | // { 74 | // files: ['*.vue', '**/*.vue'], 75 | // extends: ['stylelint-config-recommended', 'stylelint-config-html'], 76 | // rules: { 77 | // 'selector-pseudo-class-no-unknown': [ 78 | // true, 79 | // { 80 | // ignorePseudoClasses: ['deep', 'global'], 81 | // }, 82 | // ], 83 | // 'selector-pseudo-element-no-unknown': [ 84 | // true, 85 | // { 86 | // ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'], 87 | // }, 88 | // ], 89 | // }, 90 | // }, 91 | // ], 92 | } 93 | -------------------------------------------------------------------------------- /stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"], 3 | "overrides": [ 4 | { 5 | "files": ["**/*.less"], 6 | "customSyntax": "sugarss", 7 | "rules": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "declaration": true, 6 | "skipLibCheck": false, 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "allowJs": true, 10 | "jsx": "preserve", 11 | "jsxFactory": "React.createElement", 12 | "sourceMap": true, 13 | "esModuleInterop": true, 14 | "lib": ["ES2018.Promise", "dom", "esnext"], 15 | "types": ["vite/client", "naive-ui/volar", "node"], 16 | "typeRoots": ["./node_modules/@types"], 17 | "outDir": "./dist", 18 | "resolveJsonModule": true, 19 | "baseUrl": ".", 20 | "paths": { 21 | "@/*": ["src/*"], 22 | "@assets/*": ["./src/assets/*"], 23 | "@styles/*": ["./src/assets/*"], 24 | "@components/*": ["./src/components/*"], 25 | "@views/*": ["./src/views/*"], 26 | "@pinia/*": ["./src/pinia/*"], 27 | "@api/*": ["./src/api/*"], 28 | "@utils/*": ["./src/utils/*"], 29 | "@type/*": ["./src/type/*"], 30 | "@public/*": ["./public/*"] 31 | } 32 | }, 33 | "include": [ 34 | "src/**/*.js", 35 | "src/**/*.ts", 36 | "src/**/*.d.ts", 37 | "src/**/*.tsx", 38 | "src/**/*.d.tsx", 39 | "src/**/*.vue", 40 | "build/**/*.ts", 41 | "build/**/*.d.ts", 42 | "mock/**/*.ts", 43 | "vite.config.ts" 44 | ], 45 | "exclude": ["node_modules", "dist", "axios"] 46 | } 47 | -------------------------------------------------------------------------------- /windi.config.ts: -------------------------------------------------------------------------------- 1 | // windi.config.ts 2 | import { defineConfig } from 'vite-plugin-windicss' 3 | const range = (size: number, startAt = 1) => 4 | Array.from(Array(size).keys()).map((i) => i + startAt) 5 | export default defineConfig({ 6 | attributify: { 7 | prefix: 'wd:', 8 | }, 9 | preflight: false, 10 | alias: { 11 | hstack: 'flex items-center', 12 | vstack: 'flex flex-col', 13 | icon: 'w-6 h-6 fill-current', 14 | app: 'text-red', 15 | 'app-border': 'border-gray-200 dark:border-dark-300', 16 | }, 17 | extract: { 18 | include: ['src/**/*.{vue,html,jsx,tsx}'], 19 | exclude: ['node_modules', '.git'], 20 | }, 21 | safelist: [ 22 | range(50).map((i) => `text-${i}px`), 23 | range(50).map((i) => `m-${i}px`), 24 | range(50).map((i) => `p-${i}px`), 25 | ], 26 | }) 27 | --------------------------------------------------------------------------------