9个文件已删除
17个文件已添加
56个文件已修改
2 文件已重命名
New file |
| | |
| | | name: deploy |
| | | |
| | | on: |
| | | push: |
| | | branches: |
| | | - main |
| | | |
| | | jobs: |
| | | build-deploy: |
| | | runs-on: ubuntu-latest |
| | | |
| | | steps: |
| | | - uses: actions/checkout@v1 |
| | | - run: npm install |
| | | - run: npm run build |
| | | |
| | | - name: Deploy |
| | | uses: peaceiris/actions-gh-pages@v2.5.0 |
| | | env: |
| | | ACTIONS_DEPLOY_KEY: ${{secrets.ACTIONS_DEPLOY_KEY}} |
| | | PUBLISH_BRANCH: gh-pages |
| | | PUBLISH_DIR: dist |
| | |
| | | { |
| | | "version": "0.2.0", |
| | | "configurations": [ |
| | | // node环境调试当前激活编辑器ts/js代码 |
| | | { |
| | | "type": "node", |
| | | "type": "chrome", |
| | | "request": "launch", |
| | | "name": "file", |
| | | "cwd": "${workspaceFolder}", |
| | | "program": "${file}", |
| | | // .vscode 目录又不认识了??? |
| | | "preLaunchTask": "tsc: 监视 - build/tsconfig.json", // cn |
| | | // "preLaunchTask": "tsc: watch - build/tsconfig.json", // en |
| | | "outFiles": ["${workspaceFolder}/compile/**/*.js"] |
| | | // "args": ["--experimental-modules", "--loader", "./loader.mjs"] |
| | | "name": "Launch Chrome", |
| | | "url": "http://localhost:3100", |
| | | "webRoot": "${workspaceFolder}/src", |
| | | "sourceMaps": true |
| | | }, |
| | | // 调试开发环境脚本 |
| | | { |
| | | "type": "node", |
| | | "request": "launch", |
| | | "name": "dev", |
| | | // "stopOnEntry": true, |
| | | "cwd": "${workspaceFolder}", |
| | | "program": "${workspaceFolder}/node_modules/@vue/cli-service/bin/vue-cli-service.js", |
| | | "args": ["serve", "--open"] |
| | | }, |
| | | // 调试生产环境脚本 |
| | | { |
| | | "type": "node", |
| | | "request": "launch", |
| | | "name": "build", |
| | | // "stopOnEntry": true, |
| | | "cwd": "${workspaceFolder}", |
| | | "program": "${workspaceFolder}/node_modules/@vue/cli-service/bin/vue-cli-service.js", |
| | | "args": ["build"] |
| | | }, |
| | | // 调试单元测试脚本 |
| | | { |
| | | "type": "node", |
| | | "request": "launch", |
| | | "name": "test:unit", |
| | | // "stopOnEntry": true, |
| | | "cwd": "${workspaceFolder}", |
| | | "program": "${workspaceFolder}/node_modules/@vue/cli-service/bin/vue-cli-service.js", |
| | | "args": ["test:unit", "--detectOpenHandles"] |
| | | } |
| | | ] |
| | | } |
| | |
| | | "[typescriptreact]": { |
| | | "editor.defaultFormatter": "esbenp.prettier-vscode" |
| | | }, |
| | | "[json]": { |
| | | "editor.defaultFormatter": "vscode.json-language-features" |
| | | }, |
| | | "[jsonc]": { |
| | | "editor.defaultFormatter": "vscode.json-language-features" |
| | | }, |
| | | "[html]": { |
| | | "editor.defaultFormatter": "esbenp.prettier-vscode" |
| | | }, |
| | |
| | | "ts" |
| | | ], |
| | | "i18n-ally.sourceLanguage": "zh", |
| | | "i18n-ally.enabledFrameworks":["vue","react"] |
| | | "i18n-ally.enabledFrameworks": [ |
| | | "vue", |
| | | "react" |
| | | ] |
| | | } |
| | |
| | | ## Wip |
| | | |
| | | ## (破坏性更新) Breaking changes |
| | | |
| | | - 路由重构, 不再支持以前的格式。改为支持 vue-router 最初的默认结构,具体格式可以参考示例更改。实现多级路由缓存,不再将路由转化为 2 级。 |
| | | - 重构面包屑,使用 antd 的面包屑组件。之前的组件已删除 |
| | | |
| | | ### ✨ Features |
| | | |
| | | - 还原 antdv 默认 loading,重构 `Loading` 组件,增加`useLoading`和`v-loading`指令。并增加示例 |
| | | - i18n 支持 vscode `i18n-ally`插件 |
| | | - 新增多级路由缓存示例 |
| | | |
| | | ### 🎫 Chores |
| | | |
| | | - 首屏 loading 修改 |
| | | |
| | | ### 🐛 Bug Fixes |
| | | |
| | | -修复表格 i18n 错误 |
| | | |
| | | ## 2.0.0-rc.12 (2020-11-30) |
| | | |
| | | ## (破坏性更新) Breaking changes |
| | |
| | | test({ path }) { |
| | | // Only convert the file |
| | | return ( |
| | | path.includes('/src/utils/helper/dynamicImport.ts') || |
| | | path.includes(`\\src\\utils\\helper\\dynamicImport.ts`) |
| | | path.includes('/src/router/helper/dynamicImport.ts') || |
| | | path.includes(`\\src\\router\\helper\\dynamicImport.ts`) |
| | | ); |
| | | }, |
| | | transform({ code }) { |
| | |
| | | import { resultSuccess } from '../_util'; |
| | | import { MockMethod } from 'vite-plugin-mock'; |
| | | |
| | | // single |
| | | const dashboardRoute = { |
| | | path: '/dashboard', |
| | | name: 'Dashboard', |
| | | component: 'PAGE_LAYOUT', |
| | | redirect: '/dashboard/welcome', |
| | | path: '/home', |
| | | name: 'Home', |
| | | component: '/dashboard/welcome/index', |
| | | meta: { |
| | | title: 'routes.dashboard.welcome', |
| | | affix: true, |
| | | icon: 'ant-design:home-outlined', |
| | | title: 'Dashboard', |
| | | }, |
| | | children: [ |
| | | { |
| | | path: '/welcome', |
| | | name: 'Welcome', |
| | | component: '/dashboard/welcome/index', |
| | | meta: { |
| | | title: '欢迎页', |
| | | affix: true, |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | const frontRoute = { |
| | | path: '/front', |
| | | path: 'front', |
| | | name: 'PermissionFrontDemo', |
| | | meta: { |
| | | title: '基于前端权限', |
| | | title: 'routes.demo.permission.front', |
| | | }, |
| | | children: [ |
| | | { |
| | |
| | | name: 'FrontPageAuth', |
| | | component: '/demo/permission/front/index', |
| | | meta: { |
| | | title: '页面权限', |
| | | title: 'routes.demo.permission.frontPage', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | name: 'FrontBtnAuth', |
| | | component: '/demo/permission/front/Btn', |
| | | meta: { |
| | | title: '按钮权限', |
| | | title: 'routes.demo.permission.frontBtn', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | name: 'FrontAuthPageA', |
| | | component: '/demo/permission/front/AuthPageA', |
| | | meta: { |
| | | title: '权限测试页A', |
| | | title: 'routes.demo.permission.frontTestA', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | name: 'FrontAuthPageB', |
| | | component: '/demo/permission/front/AuthPageB', |
| | | meta: { |
| | | title: '权限测试页B', |
| | | title: 'routes.demo.permission.frontTestB', |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | const backRoute = { |
| | | path: '/back', |
| | | path: 'back', |
| | | name: 'PermissionBackDemo', |
| | | meta: { |
| | | title: '基于后台权限', |
| | | title: 'routes.demo.permission.back', |
| | | }, |
| | | |
| | | children: [ |
| | | { |
| | | path: 'page', |
| | | name: 'BackAuthPage', |
| | | component: '/demo/permission/back/index', |
| | | meta: { |
| | | title: '页面权限', |
| | | title: 'routes.demo.permission.backPage', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | name: 'BackAuthBtn', |
| | | component: '/demo/permission/back/Btn', |
| | | meta: { |
| | | title: '按钮权限', |
| | | title: 'routes.demo.permission.backBtn', |
| | | }, |
| | | }, |
| | | ], |
| | |
| | | const authRoute = { |
| | | path: '/permission', |
| | | name: 'Permission', |
| | | component: 'PAGE_LAYOUT', |
| | | component: 'LAYOUT', |
| | | redirect: '/permission/front/page', |
| | | meta: { |
| | | icon: 'ant-design:home-outlined', |
| | | title: '权限管理', |
| | | icon: 'carbon:user-role', |
| | | title: 'routes.demo.permission.permission', |
| | | }, |
| | | children: [frontRoute, backRoute], |
| | | }; |
| | |
| | | const authRoute1 = { |
| | | path: '/permission', |
| | | name: 'Permission', |
| | | component: 'PAGE_LAYOUT', |
| | | component: 'LAYOUT', |
| | | redirect: '/permission/front/page', |
| | | meta: { |
| | | icon: 'ant-design:home-outlined', |
| | | title: '权限管理', |
| | | icon: 'carbon:user-role', |
| | | title: 'routes.demo.permission.permission', |
| | | }, |
| | | children: [backRoute], |
| | | }; |
| | | |
| | | const levelRoute = { |
| | | path: '/level', |
| | | name: 'Level', |
| | | component: 'LAYOUT', |
| | | redirect: '/level/menu1/menu1-1', |
| | | meta: { |
| | | icon: 'carbon:user-role', |
| | | title: 'routes.demo.level.level', |
| | | }, |
| | | |
| | | children: [ |
| | | { |
| | | path: 'menu1', |
| | | name: 'Menu1Demo', |
| | | meta: { |
| | | title: 'Menu1', |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'menu1-1', |
| | | name: 'Menu11Demo', |
| | | meta: { |
| | | title: 'Menu1-1', |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'menu1-1-1', |
| | | name: 'Menu111Demo', |
| | | component: '/demo/level/Menu111', |
| | | meta: { |
| | | title: 'Menu111', |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: 'menu1-2', |
| | | name: 'Menu12Demo', |
| | | component: '/demo/level/Menu12', |
| | | meta: { |
| | | title: 'Menu1-2', |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: 'menu2', |
| | | name: 'Menu2Demo', |
| | | component: '/demo/level/Menu2', |
| | | meta: { |
| | | title: 'Menu2', |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | export default [ |
| | | { |
| | |
| | | response: ({ query }) => { |
| | | const { id } = query; |
| | | if (!id || id === '1') { |
| | | return resultSuccess([dashboardRoute, authRoute]); |
| | | return resultSuccess([dashboardRoute, authRoute, levelRoute]); |
| | | } |
| | | if (id === '2') { |
| | | return resultSuccess([dashboardRoute, authRoute1]); |
| | | return resultSuccess([dashboardRoute, authRoute1, levelRoute]); |
| | | } |
| | | }, |
| | | }, |
| | |
| | | "qrcode": "^1.4.4", |
| | | "sortablejs": "^1.12.0", |
| | | "vditor": "^3.7.0", |
| | | "vue": "^3.0.3", |
| | | "vue": "^3.0.4", |
| | | "vue-i18n": "^9.0.0-beta.8", |
| | | "vue-router": "^4.0.0-rc.6", |
| | | "vue-types": "^3.0.1", |
| | |
| | | "devDependencies": { |
| | | "@commitlint/cli": "^11.0.0", |
| | | "@commitlint/config-conventional": "^11.0.0", |
| | | "@iconify/json": "^1.1.266", |
| | | "@iconify/json": "^1.1.267", |
| | | "@ls-lint/ls-lint": "^1.9.2", |
| | | "@purge-icons/generated": "^0.4.1", |
| | | "@types/echarts": "^4.9.2", |
| | |
| | | "@types/qrcode": "^1.3.5", |
| | | "@types/rollup-plugin-visualizer": "^2.6.0", |
| | | "@types/sortablejs": "^1.10.6", |
| | | "@types/yargs": "^15.0.10", |
| | | "@types/yargs": "^15.0.11", |
| | | "@types/zxcvbn": "^4.4.0", |
| | | "@typescript-eslint/eslint-plugin": "^4.9.0", |
| | | "@typescript-eslint/parser": "^4.9.0", |
| | | "@vue/compiler-sfc": "^3.0.3", |
| | | "@vue/compiler-sfc": "^3.0.4", |
| | | "@vuedx/typecheck": "^0.2.4-0", |
| | | "@vuedx/typescript-plugin-vue": "^0.2.4-0", |
| | | "autoprefixer": "^9.8.6", |
| | | "commitizen": "^4.2.2", |
| | | "conventional-changelog-cli": "^2.1.1", |
| | | "conventional-changelog-custom-config": "^0.3.1", |
| | | "cross-env": "^7.0.2", |
| | | "cross-env": "^7.0.3", |
| | | "dot-prop": "^6.0.1", |
| | | "dotenv": "^8.2.0", |
| | | "eslint": "^7.14.0", |
| | | "eslint-config-prettier": "^6.15.0", |
| | | "eslint-plugin-prettier": "^3.1.4", |
| | | "eslint-plugin-vue": "^7.1.0", |
| | | "esno": "^0.2.4", |
| | | "esno": "^0.3.0", |
| | | "fs-extra": "^9.0.1", |
| | | "globrex": "^0.1.2", |
| | | "husky": "^4.3.0", |
| | |
| | | |
| | | import { basicProps } from './props'; |
| | | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
| | | import { REDIRECT_NAME } from '/@/router/constant'; |
| | | export default defineComponent({ |
| | | name: 'BasicMenu', |
| | | props: basicProps, |
| | |
| | | watch( |
| | | () => currentRoute.value.name, |
| | | (name: string) => { |
| | | if (name === 'Redirect') return; |
| | | if (name === REDIRECT_NAME) return; |
| | | handleMenuChange(); |
| | | props.isHorizontal && appStore.getProjectConfig.menuSetting.split && getParentPath(); |
| | | } |
| | |
| | | import type { Ref } from 'vue'; |
| | | |
| | | import { unref } from 'vue'; |
| | | import { getAllParentPath } from '/@/utils/helper/menuHelper'; |
| | | import { getAllParentPath } from '/@/router/helper/menuHelper'; |
| | | import { es6Unique } from '/@/utils'; |
| | | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
| | | |
| | |
| | | import { isString } from '/@/utils/is'; |
| | | import { unref } from 'vue'; |
| | | import { es6Unique } from '/@/utils'; |
| | | import { getAllParentPath } from '/@/utils/helper/menuHelper'; |
| | | import { getAllParentPath } from '/@/router/helper/menuHelper'; |
| | | |
| | | interface UseSearchInputOptions { |
| | | menuState: MenuState; |
| | |
| | | |
| | | <Tooltip placement="top" v-if="getSetting.setting"> |
| | | <template #title> |
| | | <span>{{ t('settingColumn') }}</span> |
| | | <span>{{ t('component.table.settingColumn') }}</span> |
| | | </template> |
| | | <Popover |
| | | placement="bottomLeft" |
| | |
| | | v-model:checked="checkAll" |
| | | @change="onCheckAllChange" |
| | | > |
| | | {{ t('settingColumnShow') }} |
| | | {{ t('component.table.settingColumnShow') }} |
| | | </Checkbox> |
| | | <a-button size="small" type="link" @click="reset"> {{ t('settingReset') }}</a-button> |
| | | <a-button size="small" type="link" @click="reset"> |
| | | {{ t('component.table.settingReset') }}</a-button |
| | | > |
| | | </div> |
| | | </template> |
| | | <SettingOutlined /> |
| | |
| | | |
| | | <Tooltip placement="top" v-if="getSetting.fullScreen"> |
| | | <template #title> |
| | | <span>{{ t('settingFullScreen') }}</span> |
| | | <span>{{ t('component.table.settingFullScreen') }}</span> |
| | | </template> |
| | | <FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" /> |
| | | <FullscreenExitOutlined @click="handleFullScreen" v-else /> |
| | |
| | | Empty, |
| | | Avatar, |
| | | Menu, |
| | | Breadcrumb, |
| | | } from 'ant-design-vue'; |
| | | import { getApp } from '/@/setup/App'; |
| | | |
| | |
| | | getApp() |
| | | .use(Select) |
| | | .use(Alert) |
| | | .use(Breadcrumb) |
| | | .use(Checkbox) |
| | | .use(DatePicker) |
| | | .use(Radio) |
| | |
| | | @import './slide.less'; |
| | | @import './scroll.less'; |
| | | @import './zoom.less'; |
| | | @import './breadcrumb.less'; |
| | |
| | | // basic login path |
| | | BASE_LOGIN = '/login', |
| | | // basic home path |
| | | BASE_HOME = '/dashboard', |
| | | BASE_HOME = '/home', |
| | | // error page path |
| | | ERROR_PAGE = '/exception', |
| | | // error log page path |
| | |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import type { RouteLocationRaw } from 'vue-router'; |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | import { PageEnum } from '/@/enums/pageEnum'; |
| | | import { isString } from '/@/utils/is'; |
| | | import { unref } from 'vue'; |
| | | |
| | | import router from '/@/router'; |
| | | |
| | | export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & { path: PageEnum }; |
| | | |
| | |
| | | |
| | | // page switch |
| | | export function useGo() { |
| | | const { push, replace } = useRouter(); |
| | | const { push, replace } = router; |
| | | function go(opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME, isReplace = false) { |
| | | if (!opt) return; |
| | | if (isString(opt)) { |
| | |
| | | * @description: redo current page |
| | | */ |
| | | export const useRedo = () => { |
| | | const { push, currentRoute } = useRouter(); |
| | | const { push, currentRoute } = router; |
| | | const { query, params } = currentRoute.value; |
| | | function redo() { |
| | | push({ |
| | |
| | | import { useTabs } from './useTabs'; |
| | | |
| | | import router, { resetRouter } from '/@/router'; |
| | | import { RootRoute } from '/@/router/routes'; |
| | | // import { RootRoute } from '/@/router/routes'; |
| | | |
| | | import { PermissionModeEnum } from '/@/enums/appEnum'; |
| | | import { RoleEnum } from '/@/enums/roleEnum'; |
| | | |
| | | import { intersection } from 'lodash-es'; |
| | | import { isArray } from '/@/utils/is'; |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | |
| | | // User permissions related operations |
| | | export function usePermission() { |
| | |
| | | ? PermissionModeEnum.ROLE |
| | | : PermissionModeEnum.BACK, |
| | | }); |
| | | resume(); |
| | | // location.reload(); |
| | | location.reload(); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param id |
| | | */ |
| | | async function resume(id?: string | number) { |
| | | tabStore.commitClearCache(); |
| | | resetRouter(); |
| | | const routes = await permissionStore.buildRoutesAction(id); |
| | | routes.forEach((route) => { |
| | | router.addRoute(RootRoute.name!, route as RouteRecordRaw); |
| | | router.addRoute(route as RouteRecordRaw); |
| | | }); |
| | | permissionStore.commitLastBuildMenuTimeState(); |
| | | const { |
| | | // closeAll, |
| | | closeOther, |
| | | } = useTabs(); |
| | | // closeAll(); |
| | | closeOther(); |
| | | const { closeAll } = useTabs(); |
| | | closeAll(); |
| | | } |
| | | |
| | | /** |
| | |
| | | import { TabItem, tabStore } from '/@/store/modules/tab'; |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | import { appStore } from '/@/store/modules/app'; |
| | | |
| | | type RouteFn = (tabItem: TabItem) => void; |
| | | |
| | | interface TabFn { |
| | | refreshPageFn: RouteFn; |
| | | closeAllFn: Fn; |
| | | closeLeftFn: RouteFn; |
| | | closeRightFn: RouteFn; |
| | | closeOtherFn: RouteFn; |
| | | closeCurrentFn: RouteFn; |
| | | } |
| | | |
| | | let refreshPage: RouteFn; |
| | | let closeAll: Fn; |
| | | let closeLeft: RouteFn; |
| | | let closeRight: RouteFn; |
| | | let closeOther: RouteFn; |
| | | let closeCurrent: RouteFn; |
| | | |
| | | export let isInitUseTab = false; |
| | | |
| | | export function useTabs() { |
| | | function initTabFn({ |
| | | refreshPageFn, |
| | | closeAllFn, |
| | | closeLeftFn, |
| | | closeRightFn, |
| | | closeOtherFn, |
| | | closeCurrentFn, |
| | | }: TabFn) { |
| | | if (isInitUseTab) return; |
| | | |
| | | refreshPageFn && (refreshPage = refreshPageFn); |
| | | closeAllFn && (closeAll = closeAllFn); |
| | | closeLeftFn && (closeLeft = closeLeftFn); |
| | | closeRightFn && (closeRight = closeRightFn); |
| | | closeOtherFn && (closeOther = closeOtherFn); |
| | | closeCurrentFn && (closeCurrent = closeCurrentFn); |
| | | isInitUseTab = true; |
| | | } |
| | | |
| | | function resetCache() { |
| | | const def = undefined as any; |
| | | refreshPage = def; |
| | | closeAll = def; |
| | | closeLeft = def; |
| | | closeRight = def; |
| | | closeOther = def; |
| | | closeCurrent = def; |
| | | } |
| | | |
| | | function canIUseFn(): boolean { |
| | | const { multiTabsSetting: { show } = {} } = appStore.getProjectConfig; |
| | | if (!show) { |
| | | throw new Error('当前未开启多标签页,请在设置中打开!'); |
| | | throw new Error('The multi-tab page is currently not open, please open it in the settings!'); |
| | | } |
| | | return !!show; |
| | | } |
| | | |
| | | return { |
| | | initTabFn, |
| | | refreshPage: () => canIUseFn() && refreshPage(tabStore.getCurrentTab), |
| | | closeAll: () => canIUseFn() && closeAll(), |
| | | closeLeft: () => canIUseFn() && closeLeft(tabStore.getCurrentTab), |
| | | closeRight: () => canIUseFn() && closeRight(tabStore.getCurrentTab), |
| | | closeOther: () => canIUseFn() && closeOther(tabStore.getCurrentTab), |
| | | closeCurrent: () => canIUseFn() && closeCurrent(tabStore.getCurrentTab), |
| | | resetCache: () => canIUseFn() && resetCache(), |
| | | refreshPage: () => canIUseFn() && tabStore.commitRedoPage(), |
| | | closeAll: () => canIUseFn() && tabStore.closeAllTabAction(), |
| | | closeLeft: () => canIUseFn() && tabStore.closeLeftTabAction(tabStore.getCurrentTab), |
| | | closeRight: () => canIUseFn() && tabStore.closeRightTabAction(tabStore.getCurrentTab), |
| | | closeOther: () => canIUseFn() && tabStore.closeOtherTabAction(tabStore.getCurrentTab), |
| | | closeCurrent: () => canIUseFn() && tabStore.closeTabAction(tabStore.getCurrentTab), |
| | | }; |
| | | } |
| | |
| | | import { defineComponent, unref } from 'vue'; |
| | | import { Loading } from '/@/components/Loading'; |
| | | |
| | | import { RouterView } from 'vue-router'; |
| | | |
| | | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
| | | import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; |
| | | |
| | | import PageLayout from '/@/layouts/page/index.vue'; |
| | | export default defineComponent({ |
| | | name: 'LayoutContent', |
| | | setup() { |
| | |
| | | {unref(getOpenPageLoading) && ( |
| | | <Loading loading={unref(getPageLoading)} absolute class="layout-content__loading" /> |
| | | )} |
| | | <RouterView /> |
| | | <PageLayout /> |
| | | </div> |
| | | ); |
| | | }; |
New file |
| | |
| | | <template> |
| | | <div class="layout-breadcrumb"> |
| | | <a-breadcrumb :routes="routes"> |
| | | <template #itemRender="{ route, routes }"> |
| | | <Icon :icon="route.meta.icon" v-if="showIcon && route.meta.icon" /> |
| | | <span v-if="routes.indexOf(route) === routes.length - 1"> |
| | | {{ t(route.meta.title) }} |
| | | </span> |
| | | <router-link v-else :to="route.path"> |
| | | {{ t(route.meta.title) }} |
| | | </router-link> |
| | | </template> |
| | | </a-breadcrumb> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { PropType } from 'vue'; |
| | | import { defineComponent, ref, toRaw, watchEffect } from 'vue'; |
| | | import { useI18n } from 'vue-i18n'; |
| | | |
| | | import type { RouteLocationMatched } from 'vue-router'; |
| | | import { useRouter } from 'vue-router'; |
| | | import { filter } from '/@/utils/helper/treeHelper'; |
| | | import { REDIRECT_NAME } from '/@/router/constant'; |
| | | import Icon from '/@/components/Icon'; |
| | | |
| | | import { HomeOutlined } from '@ant-design/icons-vue'; |
| | | import { PageEnum } from '/@/enums/pageEnum'; |
| | | export default defineComponent({ |
| | | name: 'LayoutBreadcrumb', |
| | | components: { HomeOutlined, Icon }, |
| | | props: { |
| | | showIcon: { |
| | | type: Boolean as PropType<boolean>, |
| | | default: false, |
| | | }, |
| | | }, |
| | | setup() { |
| | | const routes = ref<RouteLocationMatched[]>([]); |
| | | const { currentRoute } = useRouter(); |
| | | |
| | | const { t } = useI18n(); |
| | | watchEffect(() => { |
| | | if (currentRoute.value.name === REDIRECT_NAME) { |
| | | return; |
| | | } |
| | | const matched = currentRoute.value.matched; |
| | | if (!matched || matched.length === 0) return; |
| | | |
| | | let breadcrumbList = filter(toRaw(matched), (item) => { |
| | | if (!item.meta) { |
| | | return false; |
| | | } |
| | | const { title, hideBreadcrumb } = item.meta; |
| | | if (!title || hideBreadcrumb) { |
| | | return false; |
| | | } |
| | | return true; |
| | | }); |
| | | |
| | | const filterBreadcrumbList = breadcrumbList.filter( |
| | | (item) => item.path !== PageEnum.BASE_HOME |
| | | ); |
| | | |
| | | if (filterBreadcrumbList.length === breadcrumbList.length) { |
| | | filterBreadcrumbList.unshift({ |
| | | path: PageEnum.BASE_HOME, |
| | | meta: { |
| | | title: t('layout.header.home'), |
| | | }, |
| | | }); |
| | | } |
| | | routes.value = filterBreadcrumbList; |
| | | }); |
| | | |
| | | return { routes, t }; |
| | | }, |
| | | }); |
| | | </script> |
| | |
| | | import { AppLogo } from '/@/components/Application'; |
| | | import UserDropdown from './UserDropdown'; |
| | | import LayoutMenu from '../menu'; |
| | | import LayoutBreadcrumb from './LayoutBreadcrumb'; |
| | | import LayoutBreadcrumb from './LayoutBreadcrumb.vue'; |
| | | import LockAction from '../lock/LockAction'; |
| | | import LayoutTrigger from '../LayoutTrigger'; |
| | | import NoticeAction from './notice/NoticeActionItem.vue'; |
| | |
| | | .multiple-tab-header { |
| | | flex: 0 0 auto; |
| | | margin-left: -1px; |
| | | |
| | | &.fixed { |
| | | position: fixed; |
| | |
| | | |
| | | &__left { |
| | | display: flex; |
| | | height: 100%; |
| | | align-items: center; |
| | | |
| | | .layout-trigger { |
| | | display: flex; |
| | | height: 100%; |
| | | padding: 1px 10px 0 16px; |
| | | cursor: pointer; |
| | | align-items: center; |
| | | |
| | | .anticon { |
| | | font-size: 17px; |
| | |
| | | } |
| | | |
| | | .layout-breadcrumb { |
| | | display: flex; |
| | | padding: 0 8px; |
| | | align-items: center; |
| | | |
| | | .ant-breadcrumb-link { |
| | | .anticon { |
| | | margin-right: 4px; |
| | | margin-bottom: 2px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | &__content { |
| | | display: flex; |
| | | height: 100%; |
| | | flex-grow: 1; |
| | | align-items: center; |
| | | } |
| | |
| | | .ant-menu-submenu { |
| | | height: @header-height; |
| | | line-height: @header-height; |
| | | } |
| | | } |
| | | |
| | | .layout-breadcrumb { |
| | | .ant-breadcrumb-link { |
| | | color: @breadcrumb-item-normal-color; |
| | | |
| | | a { |
| | | color: @text-color-base; |
| | | |
| | | &:hover { |
| | | color: @primary-color; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .ant-breadcrumb-separator { |
| | | color: @breadcrumb-item-normal-color; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | .breadcrumb { |
| | | &__item:last-child .breadcrumb__inner, |
| | | &__item:last-child &__inner a, |
| | | &__item:last-child &__inner a:hover, |
| | | &__item:last-child &__inner:hover { |
| | | font-weight: 400; |
| | | .layout-breadcrumb { |
| | | .ant-breadcrumb-link { |
| | | color: rgba(255, 255, 255, 0.6); |
| | | cursor: text; |
| | | |
| | | a { |
| | | color: rgba(255, 255, 255, 0.8); |
| | | |
| | | &:hover { |
| | | color: @white; |
| | | } |
| | | } |
| | | } |
| | | |
| | | &__inner, |
| | | &__inner.is-link, |
| | | &__separator { |
| | | color: @white; |
| | | .ant-breadcrumb-separator, |
| | | .anticon { |
| | | color: rgba(255, 255, 255, 0.8); |
| | | } |
| | | } |
| | | } |
| | |
| | | import type { PropType } from 'vue'; |
| | | |
| | | import { defineComponent, unref, computed, FunctionalComponent } from 'vue'; |
| | | |
| | | import { TabItem, tabStore } from '/@/store/modules/tab'; |
| | | import { getScaleAction, TabContentProps } from './data'; |
| | | |
| | | import { Dropdown } from '/@/components/Dropdown/index'; |
| | | |
| | | import { defineComponent, unref, FunctionalComponent } from 'vue'; |
| | | |
| | | import { TabContentProps } from './types'; |
| | | |
| | | import { RightOutlined } from '@ant-design/icons-vue'; |
| | | |
| | | import { TabContentEnum } from './data'; |
| | | import { TabContentEnum } from './types'; |
| | | |
| | | import { useTabDropdown } from './useTabDropdown'; |
| | | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
| | | import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
| | | import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | |
| | | import { RouteLocationNormalized } from 'vue-router'; |
| | | |
| | | const { t: titleT } = useI18n(); |
| | | |
| | |
| | | ); |
| | | }; |
| | | |
| | | const TabContent: FunctionalComponent<{ tabItem: TabItem }> = (props) => { |
| | | const TabContent: FunctionalComponent<{ tabItem: RouteLocationNormalized; handler: Fn }> = ( |
| | | props |
| | | ) => { |
| | | const { tabItem: { meta } = {} } = props; |
| | | |
| | | function handleContextMenu(e: Event) { |
| | | if (!props.tabItem) return; |
| | | const tableItem = props.tabItem; |
| | | e?.preventDefault(); |
| | | const index = unref(tabStore.getTabsState).findIndex((tab) => tab.path === tableItem.path); |
| | | |
| | | tabStore.commitCurrentContextMenuIndexState(index); |
| | | tabStore.commitCurrentContextMenuState(props.tabItem); |
| | | } |
| | | |
| | | return ( |
| | | <div class={`multiple-tabs-content__content `} onContextmenu={handleContextMenu}> |
| | | <div class={`multiple-tabs-content__content `} onContextmenu={props.handler(props.tabItem)}> |
| | | <span class="ml-1">{meta && titleT(meta.title)}</span> |
| | | </div> |
| | | ); |
| | |
| | | name: 'TabContent', |
| | | props: { |
| | | tabItem: { |
| | | type: Object as PropType<TabItem>, |
| | | type: Object as PropType<RouteLocationNormalized>, |
| | | default: null, |
| | | }, |
| | | |
| | |
| | | }, |
| | | }, |
| | | setup(props) { |
| | | const { t } = useI18n(); |
| | | const { getShowMenu } = useMenuSetting(); |
| | | const { getShowHeader } = useHeaderSetting(); |
| | | const { getShowQuick } = useMultipleTabSetting(); |
| | | |
| | | const getIsScale = computed(() => { |
| | | return !unref(getShowMenu) && !unref(getShowHeader); |
| | | }); |
| | | |
| | | const getIsTab = computed(() => { |
| | | return !unref(getShowQuick) ? true : props.type === TabContentEnum.TAB_TYPE; |
| | | }); |
| | | |
| | | const { getDropMenuList, handleMenuEvent } = useTabDropdown(props as TabContentProps); |
| | | const { |
| | | getDropMenuList, |
| | | handleMenuEvent, |
| | | handleContextMenu, |
| | | getTrigger, |
| | | isTabs, |
| | | } = useTabDropdown(props as TabContentProps); |
| | | |
| | | return () => { |
| | | const scaleAction = getScaleAction( |
| | | unref(getIsScale) ? t('layout.multipleTab.putAway') : t('layout.multipleTab.unfold'), |
| | | unref(getIsScale) |
| | | ); |
| | | const dropMenuList = unref(getDropMenuList) || []; |
| | | |
| | | const isTab = unref(getIsTab); |
| | | return ( |
| | | <Dropdown |
| | | dropMenuList={!isTab ? [scaleAction, ...dropMenuList] : dropMenuList} |
| | | trigger={isTab ? ['contextmenu'] : ['click']} |
| | | dropMenuList={unref(getDropMenuList)} |
| | | trigger={unref(getTrigger)} |
| | | onMenuEvent={handleMenuEvent} |
| | | > |
| | | {() => (isTab ? <TabContent tabItem={props.tabItem} /> : <ExtraContent />)} |
| | | {() => { |
| | | if (!unref(isTabs)) { |
| | | return <ExtraContent />; |
| | | } |
| | | return <TabContent handler={handleContextMenu} tabItem={props.tabItem} />; |
| | | }} |
| | | </Dropdown> |
| | | ); |
| | | }; |
| | |
| | | import './index.less'; |
| | | |
| | | import type { TabContentProps } from './data'; |
| | | import type { TabItem } from '/@/store/modules/tab'; |
| | | import type { AppRouteRecordRaw } from '/@/router/types'; |
| | | import type { TabContentProps } from './types'; |
| | | |
| | | import { defineComponent, watch, computed, unref, ref, onMounted, nextTick } from 'vue'; |
| | | import Sortable from 'sortablejs'; |
| | | |
| | | import { defineComponent, watch, computed, unref, ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Tabs } from 'ant-design-vue'; |
| | |
| | | |
| | | import { useGo } from '/@/hooks/web/usePage'; |
| | | |
| | | import { TabContentEnum } from './data'; |
| | | import { TabContentEnum } from './types'; |
| | | |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | import { userStore } from '/@/store/modules/user'; |
| | | |
| | | import { closeTab } from './useTabDropdown'; |
| | | import { initAffixTabs } from './useMultipleTabs'; |
| | | import { isNullAndUnDef } from '/@/utils/is'; |
| | | import { useProjectSetting } from '/@/hooks/setting'; |
| | | import { initAffixTabs, useTabsDrag } from './useMultipleTabs'; |
| | | |
| | | export default defineComponent({ |
| | | name: 'MultipleTabs', |
| | |
| | | |
| | | const affixTextList = initAffixTabs(); |
| | | |
| | | const go = useGo(); |
| | | useTabsDrag(affixTextList); |
| | | |
| | | const { multiTabsSetting } = useProjectSetting(); |
| | | const go = useGo(); |
| | | |
| | | const { currentRoute } = useRouter(); |
| | | |
| | | const getTabsState = computed(() => tabStore.getTabsState); |
| | | |
| | | // If you monitor routing changes, tab switching will be stuck. So setting this method |
| | | watch( |
| | | () => tabStore.getLastChangeRouteState, |
| | | () => tabStore.getLastChangeRouteState?.path, |
| | | () => { |
| | | const lastChangeRoute = unref(tabStore.getLastChangeRouteState); |
| | | |
| | | if (!lastChangeRoute || !userStore.getTokenState) return; |
| | | |
| | | const { path, fullPath } = lastChangeRoute as AppRouteRecordRaw; |
| | | const { path, fullPath } = lastChangeRoute; |
| | | const p = fullPath || path; |
| | | if (activeKeyRef.value !== p) { |
| | | activeKeyRef.value = p; |
| | | } |
| | | tabStore.commitAddTab(lastChangeRoute); |
| | | tabStore.addTabAction(lastChangeRoute); |
| | | }, |
| | | { |
| | | immediate: true, |
| | |
| | | // Close the current tab |
| | | function handleEdit(targetKey: string) { |
| | | // Added operation to hide, currently only use delete operation |
| | | const index = unref(getTabsState).findIndex( |
| | | (item) => (item.fullPath || item.path) === targetKey |
| | | ); |
| | | index !== -1 && closeTab(unref(getTabsState)[index]); |
| | | tabStore.closeTabByKeyAction(targetKey); |
| | | } |
| | | |
| | | function renderQuick() { |
| | | const tabContentProps: TabContentProps = { |
| | | tabItem: (currentRoute as unknown) as AppRouteRecordRaw, |
| | | tabItem: currentRoute.value, |
| | | type: TabContentEnum.EXTRA_TYPE, |
| | | }; |
| | | return <TabContent {...(tabContentProps as any)} />; |
| | | return <TabContent {...tabContentProps} />; |
| | | } |
| | | |
| | | function renderTabs() { |
| | | return unref(getTabsState).map((item: TabItem) => { |
| | | return unref(getTabsState).map((item) => { |
| | | const key = item.query ? item.fullPath : item.path; |
| | | const closable = !(item && item.meta && item.meta.affix); |
| | | |
| | |
| | | ); |
| | | }); |
| | | } |
| | | |
| | | function initSortableTabs() { |
| | | if (!multiTabsSetting.canDrag) return; |
| | | nextTick(() => { |
| | | const el = document.querySelectorAll( |
| | | '.multiple-tabs .ant-tabs-nav > div' |
| | | )?.[0] as HTMLElement; |
| | | |
| | | if (!el) return; |
| | | Sortable.create(el, { |
| | | animation: 500, |
| | | delay: 400, |
| | | delayOnTouchOnly: true, |
| | | filter: (e: ChangeEvent) => { |
| | | const text = e?.target?.innerText; |
| | | if (!text) return false; |
| | | return affixTextList.includes(text); |
| | | }, |
| | | onEnd: (evt) => { |
| | | const { oldIndex, newIndex } = evt; |
| | | |
| | | if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) { |
| | | return; |
| | | } |
| | | |
| | | tabStore.commitSortTabs({ oldIndex, newIndex }); |
| | | }, |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | initSortableTabs(); |
| | | }); |
| | | |
| | | return () => { |
| | | const slots = { |
New file |
| | |
| | | import type { DropMenu } from '/@/components/Dropdown/index'; |
| | | import type { RouteLocationNormalized } from 'vue-router'; |
| | | |
| | | export enum TabContentEnum { |
| | | TAB_TYPE, |
| | | EXTRA_TYPE, |
| | | } |
| | | |
| | | export type { DropMenu }; |
| | | |
| | | export interface TabContentProps { |
| | | tabItem: RouteLocationNormalized; |
| | | type?: TabContentEnum; |
| | | trigger?: ('click' | 'hover' | 'contextmenu')[]; |
| | | } |
| | | |
| | | /** |
| | | * @description: 右键:下拉菜单文字 |
| | | */ |
| | | export enum MenuEventEnum { |
| | | // 刷新 |
| | | REFRESH_PAGE, |
| | | // 关闭当前 |
| | | CLOSE_CURRENT, |
| | | // 关闭左侧 |
| | | CLOSE_LEFT, |
| | | // 关闭右侧 |
| | | CLOSE_RIGHT, |
| | | // 关闭其他 |
| | | CLOSE_OTHER, |
| | | // 关闭所有 |
| | | CLOSE_ALL, |
| | | // 放大 |
| | | SCALE, |
| | | } |
| | |
| | | import { toRaw, ref } from 'vue'; |
| | | import Sortable from 'sortablejs'; |
| | | import { toRaw, ref, nextTick, onMounted } from 'vue'; |
| | | import { RouteLocationNormalized } from 'vue-router'; |
| | | import { useProjectSetting } from '/@/hooks/setting'; |
| | | import router from '/@/router'; |
| | | import { AppRouteRecordRaw } from '/@/router/types'; |
| | | import { TabItem, tabStore } from '/@/store/modules/tab'; |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | import { isNullAndUnDef } from '/@/utils/is'; |
| | | |
| | | export function initAffixTabs() { |
| | | const affixList = ref<TabItem[]>([]); |
| | | export function initAffixTabs(): string[] { |
| | | const affixList = ref<RouteLocationNormalized[]>([]); |
| | | /** |
| | | * @description: Filter all fixed routes |
| | | */ |
| | | function filterAffixTabs(routes: AppRouteRecordRaw[]) { |
| | | const tabs: TabItem[] = []; |
| | | function filterAffixTabs(routes: RouteLocationNormalized[]) { |
| | | const tabs: RouteLocationNormalized[] = []; |
| | | routes && |
| | | routes.forEach((route) => { |
| | | if (route.meta && route.meta.affix) { |
| | | tabs.push(toRaw(route) as TabItem); |
| | | tabs.push(toRaw(route)); |
| | | } |
| | | }); |
| | | return tabs; |
| | |
| | | * @description: Set fixed tabs |
| | | */ |
| | | function addAffixTabs(): void { |
| | | const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as AppRouteRecordRaw[]); |
| | | const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as RouteLocationNormalized[]); |
| | | affixList.value = affixTabs; |
| | | for (const tab of affixTabs) { |
| | | tabStore.commitAddTab(tab); |
| | | tabStore.addTabAction(({ |
| | | meta: tab.meta, |
| | | name: tab.name, |
| | | path: tab.path, |
| | | } as unknown) as RouteLocationNormalized); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | return affixList.value.map((item) => item.meta?.title).filter(Boolean); |
| | | } |
| | | |
| | | export function useTabsDrag(affixTextList: string[]) { |
| | | const { multiTabsSetting } = useProjectSetting(); |
| | | |
| | | function initSortableTabs() { |
| | | if (!multiTabsSetting.canDrag) return; |
| | | nextTick(() => { |
| | | const el = document.querySelectorAll( |
| | | '.multiple-tabs .ant-tabs-nav > div' |
| | | )?.[0] as HTMLElement; |
| | | |
| | | if (!el) return; |
| | | Sortable.create(el, { |
| | | animation: 500, |
| | | delay: 400, |
| | | delayOnTouchOnly: true, |
| | | filter: (e: ChangeEvent) => { |
| | | const text = e?.target?.innerText; |
| | | if (!text) return false; |
| | | return affixTextList.includes(text); |
| | | }, |
| | | onEnd: (evt) => { |
| | | const { oldIndex, newIndex } = evt; |
| | | |
| | | if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) { |
| | | return; |
| | | } |
| | | |
| | | tabStore.commitSortTabs({ oldIndex, newIndex }); |
| | | }, |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | initSortableTabs(); |
| | | }); |
| | | } |
| | |
| | | import type { AppRouteRecordRaw } from '/@/router/types'; |
| | | import type { TabContentProps } from './data'; |
| | | import type { Ref } from 'vue'; |
| | | import type { TabItem } from '/@/store/modules/tab'; |
| | | import type { TabContentProps } from './types'; |
| | | import type { DropMenu } from '/@/components/Dropdown'; |
| | | |
| | | import { computed, unref } from 'vue'; |
| | | import { TabContentEnum, MenuEventEnum, getActions } from './data'; |
| | | import { computed, unref, reactive } from 'vue'; |
| | | import { TabContentEnum, MenuEventEnum } from './types'; |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import { PageEnum } from '/@/enums/pageEnum'; |
| | | import { useGo, useRedo } from '/@/hooks/web/usePage'; |
| | | import router from '/@/router'; |
| | | import { useTabs, isInitUseTab } from '/@/hooks/web/useTabs'; |
| | | import { RouteLocationRaw } from 'vue-router'; |
| | | import { RouteLocationNormalized } from 'vue-router'; |
| | | import { useTabs } from '/@/hooks/web/useTabs'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
| | | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
| | | import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; |
| | | |
| | | const { initTabFn } = useTabs(); |
| | | const { t } = useI18n(); |
| | | |
| | | export function useTabDropdown(tabContentProps: TabContentProps) { |
| | | const { currentRoute } = router; |
| | | const redo = useRedo(); |
| | | const go = useGo(); |
| | | |
| | | const isTabsRef = computed(() => tabContentProps.type === TabContentEnum.TAB_TYPE); |
| | | const getCurrentTab: Ref<TabItem | AppRouteRecordRaw> = computed(() => { |
| | | return unref(isTabsRef) |
| | | ? tabContentProps.tabItem |
| | | : ((unref(currentRoute) as any) as AppRouteRecordRaw); |
| | | const state = reactive({ |
| | | current: null as Nullable<RouteLocationNormalized>, |
| | | currentIndex: 0, |
| | | }); |
| | | |
| | | // Current tab list |
| | | const getTabsState = computed(() => tabStore.getTabsState); |
| | | const { currentRoute } = router; |
| | | |
| | | const { getShowMenu, setMenuSetting } = useMenuSetting(); |
| | | const { getShowHeader, setHeaderSetting } = useHeaderSetting(); |
| | | const { getShowQuick } = useMultipleTabSetting(); |
| | | |
| | | const isTabs = computed(() => |
| | | !unref(getShowQuick) ? true : tabContentProps.type === TabContentEnum.TAB_TYPE |
| | | ); |
| | | |
| | | const getCurrentTab = computed( |
| | | (): RouteLocationNormalized => { |
| | | return unref(isTabs) ? tabContentProps.tabItem : unref(currentRoute); |
| | | } |
| | | ); |
| | | |
| | | const getIsScale = computed(() => { |
| | | return !unref(getShowMenu) && !unref(getShowHeader); |
| | | }); |
| | | |
| | | /** |
| | | * @description: drop-down list |
| | | */ |
| | | const getDropMenuList = computed(() => { |
| | | const dropMenuList = getActions(); |
| | | // Reset to initial state |
| | | for (const item of dropMenuList) { |
| | | item.disabled = false; |
| | | } |
| | | |
| | | // No tab |
| | | if (!unref(getTabsState) || unref(getTabsState).length <= 0) { |
| | | return dropMenuList; |
| | | } else if (unref(getTabsState).length === 1) { |
| | | // Only one tab |
| | | for (const item of dropMenuList) { |
| | | if (item.event !== MenuEventEnum.REFRESH_PAGE) { |
| | | item.disabled = true; |
| | | } |
| | | } |
| | | return dropMenuList; |
| | | } |
| | | if (!unref(getCurrentTab)) return; |
| | | const { meta, path } = unref(getCurrentTab); |
| | | const { meta } = unref(getCurrentTab); |
| | | const { path } = unref(currentRoute); |
| | | |
| | | // Refresh button |
| | | const curItem = tabStore.getCurrentContextMenuState; |
| | | const index = tabStore.getCurrentContextMenuIndexState; |
| | | const curItem = state.current; |
| | | const index = state.currentIndex; |
| | | const refreshDisabled = curItem ? curItem.path !== path : true; |
| | | // Close left |
| | | const closeLeftDisabled = index === 0; |
| | | |
| | | const disabled = tabStore.getTabsState.length === 1; |
| | | |
| | | // Close right |
| | | const closeRightDisabled = index === unref(getTabsState).length - 1; |
| | | // Currently fixed tab |
| | | // TODO PERf |
| | | dropMenuList[0].disabled = unref(isTabsRef) ? refreshDisabled : false; |
| | | if (meta && meta.affix) { |
| | | dropMenuList[1].disabled = true; |
| | | const closeRightDisabled = |
| | | index === tabStore.getTabsState.length - 1 && tabStore.getLastDragEndIndexState >= 0; |
| | | const dropMenuList: DropMenu[] = [ |
| | | { |
| | | icon: 'ant-design:reload-outlined', |
| | | event: MenuEventEnum.REFRESH_PAGE, |
| | | text: t('layout.multipleTab.redo'), |
| | | disabled: refreshDisabled, |
| | | }, |
| | | { |
| | | icon: 'ant-design:close-outlined', |
| | | event: MenuEventEnum.CLOSE_CURRENT, |
| | | text: t('layout.multipleTab.close'), |
| | | disabled: meta?.affix || disabled, |
| | | divider: true, |
| | | }, |
| | | { |
| | | icon: 'ant-design:pic-left-outlined', |
| | | event: MenuEventEnum.CLOSE_LEFT, |
| | | text: t('layout.multipleTab.closeLeft'), |
| | | disabled: closeLeftDisabled, |
| | | divider: false, |
| | | }, |
| | | { |
| | | icon: 'ant-design:pic-right-outlined', |
| | | event: MenuEventEnum.CLOSE_RIGHT, |
| | | text: t('layout.multipleTab.closeRight'), |
| | | disabled: closeRightDisabled, |
| | | divider: true, |
| | | }, |
| | | { |
| | | icon: 'ant-design:pic-center-outlined', |
| | | event: MenuEventEnum.CLOSE_OTHER, |
| | | text: t('layout.multipleTab.closeOther'), |
| | | disabled: disabled, |
| | | }, |
| | | { |
| | | icon: 'ant-design:line-outlined', |
| | | event: MenuEventEnum.CLOSE_ALL, |
| | | text: t('layout.multipleTab.closeAll'), |
| | | disabled: disabled, |
| | | }, |
| | | ]; |
| | | |
| | | if (!unref(isTabs)) { |
| | | const isScale = unref(getIsScale); |
| | | dropMenuList.unshift({ |
| | | icon: isScale ? 'codicon:screen-normal' : 'codicon:screen-full', |
| | | event: MenuEventEnum.SCALE, |
| | | text: isScale ? t('layout.multipleTab.putAway') : t('layout.multipleTab.unfold'), |
| | | disabled: false, |
| | | }); |
| | | } |
| | | dropMenuList[2].disabled = closeLeftDisabled; |
| | | dropMenuList[3].disabled = closeRightDisabled; |
| | | |
| | | return dropMenuList; |
| | | }); |
| | | |
| | | /** |
| | | * @description: Jump to page when closing all pages |
| | | */ |
| | | function gotoPage() { |
| | | const len = unref(getTabsState).length; |
| | | const { path } = unref(currentRoute); |
| | | const getTrigger = computed(() => { |
| | | return unref(isTabs) ? ['contextmenu'] : ['click']; |
| | | }); |
| | | |
| | | let toPath: PageEnum | string = PageEnum.BASE_HOME; |
| | | |
| | | if (len > 0) { |
| | | const page = unref(getTabsState)[len - 1]; |
| | | const p = page.fullPath || page.path; |
| | | if (p) { |
| | | toPath = p; |
| | | } |
| | | } |
| | | // Jump to the current page and report an error |
| | | path !== toPath && go(toPath as PageEnum, true); |
| | | } |
| | | |
| | | function isGotoPage(currentTab?: TabItem) { |
| | | const { path } = unref(currentRoute); |
| | | const currentPath = (currentTab || unref(getCurrentTab)).path; |
| | | // Not the current tab, when you close the left/right side, you need to jump to the page |
| | | if (path !== currentPath) { |
| | | go(currentPath as PageEnum, true); |
| | | } |
| | | } |
| | | function refreshPage(tabItem?: TabItem) { |
| | | try { |
| | | tabStore.commitCloseTabKeepAlive(tabItem || unref(getCurrentTab)); |
| | | } catch (error) {} |
| | | redo(); |
| | | } |
| | | |
| | | function closeAll() { |
| | | tabStore.commitCloseAllTab(); |
| | | gotoPage(); |
| | | } |
| | | |
| | | function closeLeft(tabItem?: TabItem) { |
| | | tabStore.closeLeftTabAction(tabItem || unref(getCurrentTab)); |
| | | isGotoPage(tabItem); |
| | | } |
| | | |
| | | function closeRight(tabItem?: TabItem) { |
| | | tabStore.closeRightTabAction(tabItem || unref(getCurrentTab)); |
| | | isGotoPage(tabItem); |
| | | } |
| | | |
| | | function closeOther(tabItem?: TabItem) { |
| | | tabStore.closeOtherTabAction(tabItem || unref(getCurrentTab)); |
| | | isGotoPage(tabItem); |
| | | } |
| | | |
| | | function closeCurrent(tabItem?: TabItem) { |
| | | closeTab(unref(tabItem || unref(getCurrentTab))); |
| | | function handleContextMenu(tabItem: RouteLocationNormalized) { |
| | | return (e: Event) => { |
| | | if (!tabItem) return; |
| | | e?.preventDefault(); |
| | | const index = tabStore.getTabsState.findIndex((tab) => tab.path === tabItem.path); |
| | | state.current = tabItem; |
| | | state.currentIndex = index; |
| | | }; |
| | | } |
| | | |
| | | function scaleScreen() { |
| | | const { |
| | | headerSetting: { show: showHeader }, |
| | | menuSetting: { show: showMenu }, |
| | | } = appStore.getProjectConfig; |
| | | const isScale = !showHeader && !showMenu; |
| | | appStore.commitProjectConfigState({ |
| | | headerSetting: { show: isScale }, |
| | | menuSetting: { show: isScale }, |
| | | const isScale = !unref(getShowMenu) && !unref(getShowHeader); |
| | | setMenuSetting({ |
| | | show: isScale, |
| | | }); |
| | | } |
| | | |
| | | if (!isInitUseTab) { |
| | | initTabFn({ |
| | | refreshPageFn: refreshPage, |
| | | closeAllFn: closeAll, |
| | | closeCurrentFn: closeCurrent, |
| | | closeLeftFn: closeLeft, |
| | | closeOtherFn: closeOther, |
| | | closeRightFn: closeRight, |
| | | setHeaderSetting({ |
| | | show: isScale, |
| | | }); |
| | | } |
| | | |
| | | // Handle right click event |
| | | function handleMenuEvent(menu: DropMenu): void { |
| | | const { refreshPage, closeAll, closeCurrent, closeLeft, closeOther, closeRight } = useTabs(); |
| | | const { event } = menu; |
| | | |
| | | switch (event) { |
| | | case MenuEventEnum.SCALE: |
| | | scaleScreen(); |
| | |
| | | break; |
| | | } |
| | | } |
| | | return { getDropMenuList, handleMenuEvent }; |
| | | } |
| | | |
| | | export function getObj(tabItem: TabItem) { |
| | | const { params, path, query } = tabItem; |
| | | return { |
| | | params: params || {}, |
| | | path, |
| | | query: query || {}, |
| | | }; |
| | | } |
| | | |
| | | export function closeTab(closedTab: TabItem | AppRouteRecordRaw) { |
| | | const { currentRoute, replace } = router; |
| | | // Current tab list |
| | | const getTabsState = computed(() => tabStore.getTabsState); |
| | | |
| | | const { path } = unref(currentRoute); |
| | | if (path !== closedTab.path) { |
| | | // Closed is not the activation tab |
| | | tabStore.commitCloseTab(closedTab); |
| | | return; |
| | | } |
| | | |
| | | // Closed is activated atb |
| | | let toObj: RouteLocationRaw = {}; |
| | | |
| | | const index = unref(getTabsState).findIndex((item) => item.path === path); |
| | | |
| | | // If the current is the leftmost tab |
| | | if (index === 0) { |
| | | // There is only one tab, then jump to the homepage, otherwise jump to the right tab |
| | | if (unref(getTabsState).length === 1) { |
| | | toObj = PageEnum.BASE_HOME; |
| | | } else { |
| | | // Jump to the right tab |
| | | const page = unref(getTabsState)[index + 1]; |
| | | toObj = getObj(page); |
| | | } |
| | | } else { |
| | | // Close the current tab |
| | | const page = unref(getTabsState)[index - 1]; |
| | | toObj = getObj(page); |
| | | } |
| | | const route = (unref(currentRoute) as unknown) as AppRouteRecordRaw; |
| | | tabStore.commitCloseTab(route); |
| | | replace(toObj); |
| | | return { getDropMenuList, handleMenuEvent, handleContextMenu, getTrigger, isTabs }; |
| | | } |
| | |
| | | <template> |
| | | <template v-for="frame in getFramePages" :key="frame.path"> |
| | | <FramePage |
| | | v-if="frame.meta.frameSrc && hasRenderFrame(frame.path)" |
| | | v-if="frame.meta.frameSrc && hasRenderFrame(frame.name)" |
| | | v-show="showIframe(frame)" |
| | | :frameSrc="frame.meta.frameSrc" |
| | | /> |
| | |
| | | const getOpenTabList = computed((): string[] => { |
| | | return tabStore.getTabsState.reduce((prev: string[], next) => { |
| | | if (next.meta && Reflect.has(next.meta, 'frameSrc')) { |
| | | prev.push(next.path!); |
| | | prev.push(next.name as string); |
| | | } |
| | | return prev; |
| | | }, []); |
| | |
| | | } |
| | | |
| | | function showIframe(item: AppRouteRecordRaw) { |
| | | return item.path === unref(currentRoute).path; |
| | | return item.name === unref(currentRoute).name; |
| | | } |
| | | |
| | | function hasRenderFrame(path: string) { |
| | | return unref(getShowMultipleTab) ? unref(getOpenTabList).includes(path) : true; |
| | | function hasRenderFrame(name: string) { |
| | | if (!unref(getShowMultipleTab)) { |
| | | return true; |
| | | } |
| | | return unref(getOpenTabList).includes(name); |
| | | } |
| | | return { hasRenderFrame, getFramePages, showIframe, getAllFramePages }; |
| | | } |
New file |
| | |
| | | <template> |
| | | <ParentLayout :isPage="true" /> |
| | | <FrameLayout v-if="getCanEmbedIFramePage" /> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | |
| | | import FrameLayout from '/@/layouts/iframe/index.vue'; |
| | | |
| | | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
| | | |
| | | import ParentLayout from '/@/layouts/parent/index.vue'; |
| | | export default defineComponent({ |
| | | components: { ParentLayout, FrameLayout }, |
| | | setup() { |
| | | const { getCanEmbedIFramePage } = useRootSetting(); |
| | | |
| | | return { getCanEmbedIFramePage }; |
| | | }, |
| | | }); |
| | | </script> |
New file |
| | |
| | | <!-- |
| | | * @Description: The reason is that tsx will report warnings under multi-level nesting. |
| | | --> |
| | | <template> |
| | | <div> |
| | | <router-view> |
| | | <template #default="{ Component, route }"> |
| | | <transition v-bind="transitionEvent" :name="getName(route)" mode="out-in" appear> |
| | | <keep-alive v-if="openCache" :include="getCaches"> |
| | | <component :max="getMax" :is="Component" :key="route.fullPath" /> |
| | | </keep-alive> |
| | | <component v-else :max="getMax" :is="Component" :key="route.fullPath" /> |
| | | </transition> |
| | | </template> |
| | | </router-view> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { computed, defineComponent, unref } from 'vue'; |
| | | import { RouteLocationNormalized } from 'vue-router'; |
| | | |
| | | import { useTransition } from './useTransition'; |
| | | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
| | | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
| | | import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; |
| | | |
| | | import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; |
| | | import { useCache } from './useCache'; |
| | | |
| | | export default defineComponent({ |
| | | props: { |
| | | isPage: { |
| | | type: Boolean, |
| | | }, |
| | | }, |
| | | setup(props) { |
| | | const { getCaches } = useCache(props.isPage); |
| | | |
| | | const { getShowMenu } = useMenuSetting(); |
| | | |
| | | const { getOpenKeepAlive } = useRootSetting(); |
| | | |
| | | const { getBasicTransition, getEnableTransition } = useTransitionSetting(); |
| | | |
| | | const { getMax } = useMultipleTabSetting(); |
| | | |
| | | const transitionEvent = useTransition(); |
| | | |
| | | const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMenu)); |
| | | |
| | | function getName(route: RouteLocationNormalized) { |
| | | if (!unref(getEnableTransition)) { |
| | | return null; |
| | | } |
| | | const cacheTabs = unref(getCaches); |
| | | const isInCache = cacheTabs.includes(route.name as string); |
| | | const name = isInCache && route.meta.inTab ? 'fade-slide' : null; |
| | | |
| | | return name || route.meta.transitionName || unref(getBasicTransition); |
| | | } |
| | | |
| | | return { |
| | | getCaches, |
| | | getMax, |
| | | transitionEvent, |
| | | getBasicTransition, |
| | | getName, |
| | | openCache, |
| | | getEnableTransition, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
New file |
| | |
| | | import { computed, ref, unref } from 'vue'; |
| | | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
| | | import { tryTsxEmit } from '/@/utils/helper/vueHelper'; |
| | | import { tabStore, PAGE_LAYOUT_KEY } from '/@/store/modules/tab'; |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | const ParentLayoutName = 'ParentLayout'; |
| | | export function useCache(isPage: boolean) { |
| | | const name = ref(''); |
| | | const { currentRoute } = useRouter(); |
| | | |
| | | tryTsxEmit((instance: any) => { |
| | | const routeName = instance.ctx.$options.name; |
| | | |
| | | if (routeName && ![ParentLayoutName].includes(routeName)) { |
| | | name.value = routeName; |
| | | } else { |
| | | const matched = currentRoute.value.matched; |
| | | const len = matched.length; |
| | | if (len < 2) return; |
| | | name.value = matched[len - 2].name as string; |
| | | } |
| | | }); |
| | | const { getOpenKeepAlive } = useRootSetting(); |
| | | |
| | | const getCaches = computed((): string[] => { |
| | | if (!unref(getOpenKeepAlive)) { |
| | | return []; |
| | | } |
| | | const cached = tabStore.getCachedMapState; |
| | | |
| | | if (isPage) { |
| | | // page Layout |
| | | // not parent layout |
| | | return cached.get(PAGE_LAYOUT_KEY) || []; |
| | | } |
| | | |
| | | const cacheSet = new Set<string>(); |
| | | cacheSet.add(unref(name)); |
| | | |
| | | const list = cached.get(unref(name)); |
| | | if (!list) { |
| | | return Array.from(cacheSet); |
| | | } |
| | | list.forEach((item) => { |
| | | cacheSet.add(item); |
| | | }); |
| | | return Array.from(cacheSet); |
| | | }); |
| | | return { getCaches }; |
| | | } |
| | |
| | | putAway: 'Put away', |
| | | unfold: 'Unfold', |
| | | |
| | | input: 'Please Input', |
| | | choose: 'Please Choose', |
| | | input: 'Please Input ', |
| | | choose: 'Please Choose ', |
| | | |
| | | maxTip: 'The number of characters should be less than {0}', |
| | | }; |
| | |
| | | lockScreen: 'Lock screen', |
| | | lockScreenBtn: 'Locking', |
| | | notLockScreenPassword: 'No password lock screen', |
| | | |
| | | home: 'Home', |
| | | }; |
New file |
| | |
| | | export default { |
| | | level: 'Multi menu cache', |
| | | }; |
| | |
| | | lockScreen: '锁定屏幕', |
| | | lockScreenBtn: '锁定', |
| | | notLockScreenPassword: '不设置密码锁屏', |
| | | |
| | | home: '首页', |
| | | }; |
| | |
| | | export default { |
| | | feat: '页面功能', |
| | | feat: '功能', |
| | | icon: '图标', |
| | | tabs: '标签页操作', |
| | | contextMenu: '右键菜单', |
New file |
| | |
| | | export default { |
| | | level: '多级菜单缓存', |
| | | }; |
| | |
| | | import type { AppRouteRecordRaw } from '/@/router/types'; |
| | | import ParentLayout from '/@/layouts/parent/index.vue'; |
| | | |
| | | const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception'); |
| | | |
| | | /** |
| | | * @description: default layout |
| | | */ |
| | | export const DEFAULT_LAYOUT_COMPONENT = () => import('/@/layouts/default/index'); |
| | | export const LAYOUT = () => import('/@/layouts/default/index'); |
| | | |
| | | /** |
| | | * @description: page-layout |
| | | */ |
| | | export const PAGE_LAYOUT_COMPONENT = () => import('/@/layouts/page/index'); |
| | | export const PAGE_LAYOUT_COMPONENT = () => import('/@/layouts/page/index.vue'); |
| | | |
| | | /** |
| | | * @description: page-layout |
| | | */ |
| | | export const getParentLayout = (name: string) => { |
| | | return () => |
| | | new Promise((resolve) => { |
| | | resolve({ |
| | | ...ParentLayout, |
| | | name, |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 404 on a page |
| | | export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { |
| | |
| | | }, |
| | | }; |
| | | |
| | | export const REDIRECT_NAME = 'Redirect'; |
| | | |
| | | export const REDIRECT_ROUTE: AppRouteRecordRaw = { |
| | | path: '/redirect/:path(.*)*', |
| | | name: 'Redirect', |
| | | component: () => import('/@/views/sys/redirect/index.vue'), |
| | | path: '/redirect', |
| | | name: REDIRECT_NAME, |
| | | component: LAYOUT, |
| | | meta: { |
| | | title: 'Redirect', |
| | | title: REDIRECT_NAME, |
| | | hideBreadcrumb: true, |
| | | }, |
| | | children: [ |
| | | { |
| | | path: '/redirect/:path(.*)', |
| | | name: REDIRECT_NAME, |
| | | component: () => import('/@/views/sys/redirect/index.vue'), |
| | | meta: { |
| | | title: REDIRECT_NAME, |
| | | hideBreadcrumb: true, |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | |
| | | |
| | | import { useGlobSetting, useProjectSetting } from '/@/hooks/setting'; |
| | | |
| | | import { getIsOpenTab, setCurrentTo } from '/@/utils/helper/routeHelper'; |
| | | import { getIsOpenTab, getRoute } from '/@/router/helper/routeHelper'; |
| | | import { setTitle } from '/@/utils/browser'; |
| | | import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel'; |
| | | |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { REDIRECT_NAME } from '/@/router/constant'; |
| | | |
| | | const { closeMessageOnSwitch, removeAllHttpPending } = useProjectSetting(); |
| | | const globSetting = useGlobSetting(); |
| | | |
| | | export function createGuard(router: Router) { |
| | | let axiosCanceler: AxiosCanceler | null; |
| | | let axiosCanceler: Nullable<AxiosCanceler>; |
| | | if (removeAllHttpPending) { |
| | | axiosCanceler = new AxiosCanceler(); |
| | | } |
| | |
| | | to.meta.inTab = isOpen; |
| | | |
| | | // Notify routing changes |
| | | const { fullPath, path, query, params, name, meta } = to; |
| | | tabStore.commitLastChangeRouteState({ |
| | | fullPath, |
| | | path, |
| | | query, |
| | | params, |
| | | name, |
| | | meta, |
| | | } as any); |
| | | tabStore.commitLastChangeRouteState(getRoute(to)); |
| | | |
| | | try { |
| | | if (closeMessageOnSwitch) { |
| | |
| | | } catch (error) { |
| | | console.warn('basic guard error:' + error); |
| | | } |
| | | setCurrentTo(to); |
| | | return true; |
| | | }); |
| | | |
| | | router.afterEach((to) => { |
| | | const { t } = useI18n(); |
| | | // change html title |
| | | to.name !== 'Redirect' && setTitle(t(to.meta.title), globSetting.title); |
| | | to.name !== REDIRECT_NAME && setTitle(t(to.meta.title), globSetting.title); |
| | | }); |
| | | createProgressGuard(router); |
| | | createPermissionGuard(router); |
| | |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import { userStore } from '/@/store/modules/user'; |
| | | import { getParams } from '/@/utils/helper/routeHelper'; |
| | | import { getParams } from '/@/router/helper/routeHelper'; |
| | | import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; |
| | | import { unref } from 'vue'; |
| | | |
| | |
| | | if (!userStore.getTokenState) { |
| | | return true; |
| | | } |
| | | |
| | | if (!unref(getEnableTransition) && unref(getOpenPageLoading)) { |
| | | appStore.commitPageLoadingState(true); |
| | | return true; |
| | |
| | | import { getToken } from '/@/utils/auth'; |
| | | |
| | | import { PAGE_NOT_FOUND_ROUTE } from '/@/router/constant'; |
| | | import { RootRoute } from '../routes/index'; |
| | | // import { RootRoute } from '../routes/index'; |
| | | |
| | | const LOGIN_PATH = PageEnum.BASE_LOGIN; |
| | | |
| | |
| | | } |
| | | const routes = await permissionStore.buildRoutesAction(); |
| | | routes.forEach((route) => { |
| | | router.addRoute(RootRoute.name!, route as RouteRecordRaw); |
| | | // router.addRoute(RootRoute.name!, route as RouteRecordRaw); |
| | | router.addRoute(route as RouteRecordRaw); |
| | | }); |
| | | |
| | | const redirectPath = (from.query.redirect || to.path) as string; |
| | |
| | | const { getOpenNProgress } = useTransitionSetting(); |
| | | |
| | | export function createProgressGuard(router: Router) { |
| | | // NProgress.inc(0.1); |
| | | // NProgress.configure({ easing: 'ease', speed: 200, showSpinner: false }); |
| | | |
| | | router.beforeEach(async (to) => { |
| | | !to.meta.inTab && unref(getOpenNProgress) && NProgress.start(); |
| | | return true; |
New file |
| | |
| | | // The content here is just for type approval. The actual file content is overwritten by transform |
| | | // For specific coverage, see build/vite/plugin/transform/dynamic-import/index.ts |
| | | export default function (name: string) { |
| | | return name as any; |
| | | } |
File was renamed from src/utils/helper/menuHelper.ts |
| | |
| | | import { AppRouteModule, RouteModule } from '/@/router/types.d'; |
| | | import { AppRouteModule } from '/@/router/types.d'; |
| | | import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; |
| | | |
| | | import { findPath, forEach, treeMap, treeToList } from './treeHelper'; |
| | | import { findPath, forEach, treeMap, treeToList } from '/@/utils/helper/treeHelper'; |
| | | import { cloneDeep } from 'lodash-es'; |
| | | |
| | | export function getAllParentPath(treeData: any[], path: string) { |
| | |
| | | const cloneRouteModList = cloneDeep(routeModList); |
| | | const routeList: AppRouteRecordRaw[] = []; |
| | | cloneRouteModList.forEach((item) => { |
| | | const { layout, routes, children } = item as RouteModule; |
| | | if (layout) { |
| | | layout.children = routes || children; |
| | | routeList.push(layout); |
| | | if (item.meta?.single) { |
| | | const realItem = item?.children?.[0]; |
| | | realItem && routeList.push(realItem); |
| | | } else { |
| | | routes && routeList.push(...routes); |
| | | routeList.push(item); |
| | | } |
| | | }); |
| | | return treeMap(routeList, { |
New file |
| | |
| | | import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'; |
| | | import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; |
| | | |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | import { getParentLayout, LAYOUT } from '/@/router/constant'; |
| | | import dynamicImport from './dynamicImport'; |
| | | import { cloneDeep } from 'lodash-es'; |
| | | |
| | | // 动态引入 |
| | | function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { |
| | | if (!routes) return; |
| | | routes.forEach((item) => { |
| | | const { component, name } = item; |
| | | const { children } = item; |
| | | if (component) { |
| | | item.component = dynamicImport(component); |
| | | } else if (name) { |
| | | item.component = getParentLayout(name); |
| | | } |
| | | children && asyncImportRoute(children); |
| | | }); |
| | | } |
| | | |
| | | function getLayoutComp(comp: string) { |
| | | return comp === 'LAYOUT' ? LAYOUT : ''; |
| | | } |
| | | |
| | | // Turn background objects into routing objects |
| | | export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] { |
| | | routeList.forEach((route) => { |
| | | if (route.component) { |
| | | if ((route.component as string).toUpperCase() === 'LAYOUT') { |
| | | route.component = getLayoutComp(route.component); |
| | | } else { |
| | | route.children = [cloneDeep(route)]; |
| | | route.component = LAYOUT; |
| | | route.name = `${route.name}Parent`; |
| | | route.path = ''; |
| | | const meta = route.meta || {}; |
| | | meta.single = true; |
| | | meta.affix = false; |
| | | route.meta = meta; |
| | | } |
| | | } |
| | | route.children && asyncImportRoute(route.children); |
| | | }); |
| | | return (routeList as unknown) as T[]; |
| | | } |
| | | |
| | | /** |
| | | * Determine whether the tab has been opened |
| | | * @param toPath |
| | | */ |
| | | export function getIsOpenTab(toPath: string) { |
| | | const { openKeepAlive, multiTabsSetting: { show } = {} } = appStore.getProjectConfig; |
| | | |
| | | if (show && openKeepAlive) { |
| | | const tabList = tabStore.getTabsState; |
| | | return tabList.some((tab) => tab.path === toPath); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | export function getParams(data: any = {}) { |
| | | const { params = {} } = data; |
| | | let ret = ''; |
| | | Object.keys(params).forEach((key) => { |
| | | const p = params[key]; |
| | | ret += `/${p}`; |
| | | }); |
| | | return ret; |
| | | } |
| | | |
| | | // Return to the new routing structure, not affected by the original example |
| | | export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized { |
| | | if (!route) return route; |
| | | const { matched, ...opt } = route; |
| | | return { |
| | | ...opt, |
| | | matched: (matched |
| | | ? matched.map((item) => ({ |
| | | meta: item.meta, |
| | | name: item.name, |
| | | path: item.path, |
| | | })) |
| | | : undefined) as RouteRecordNormalized[], |
| | | }; |
| | | } |
| | |
| | | import type { RouteRecordNormalized } from 'vue-router'; |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import { permissionStore } from '/@/store/modules/permission'; |
| | | import { transformMenuModule, flatMenus, getAllParentPath } from '/@/utils/helper/menuHelper'; |
| | | import { transformMenuModule, flatMenus, getAllParentPath } from '/@/router/helper/menuHelper'; |
| | | import { filter } from '/@/utils/helper/treeHelper'; |
| | | import router from '/@/router'; |
| | | import { PermissionModeEnum } from '/@/enums/appEnum'; |
| | |
| | | import type { MenuModule } from '/@/router/types.d'; |
| | | |
| | | const menu: MenuModule[] = [ |
| | | { |
| | | orderNo: 0, |
| | | menu: { |
| | | path: '/dashboard/welcome', |
| | | name: 'routes.dashboard.welcome', |
| | | }, |
| | | const menu: MenuModule = { |
| | | orderNo: 10, |
| | | menu: { |
| | | name: 'routes.dashboard.dashboard', |
| | | path: '/dashboard', |
| | | children: [ |
| | | { |
| | | path: '/workbench', |
| | | name: 'routes.dashboard.workbench', |
| | | }, |
| | | { |
| | | path: '/analysis', |
| | | name: 'routes.dashboard.analysis', |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | orderNo: 10, |
| | | menu: { |
| | | name: 'routes.dashboard.dashboard', |
| | | path: '/dashboard', |
| | | children: [ |
| | | { |
| | | path: '/workbench', |
| | | name: 'routes.dashboard.workbench', |
| | | }, |
| | | { |
| | | path: '/analysis', |
| | | name: 'routes.dashboard.analysis', |
| | | }, |
| | | // { |
| | | // path: '/welcome', |
| | | // name: 'routes.dashboard.welcome', |
| | | // }, |
| | | ], |
| | | }, |
| | | }, |
| | | ]; |
| | | }; |
| | | export default menu; |
New file |
| | |
| | | import type { MenuModule } from '/@/router/types.d'; |
| | | |
| | | const menu: MenuModule = { |
| | | orderNo: 2000, |
| | | menu: { |
| | | name: 'routes.demo.level.level', |
| | | path: '/level', |
| | | tag: { |
| | | dot: true, |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'menu1', |
| | | name: 'Menu1', |
| | | children: [ |
| | | { |
| | | path: 'menu1-1', |
| | | name: 'Menu1-1', |
| | | children: [ |
| | | { |
| | | path: 'menu1-1-1', |
| | | name: 'Menu1-1-1', |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: 'menu1-2', |
| | | name: 'Menu1-2', |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: 'menu2', |
| | | name: 'Menu2', |
| | | }, |
| | | ], |
| | | }, |
| | | }; |
| | | export default menu; |
New file |
| | |
| | | import type { MenuModule } from '/@/router/types.d'; |
| | | |
| | | const menu: MenuModule = { |
| | | orderNo: 0, |
| | | menu: { |
| | | path: '/home/welcome', |
| | | name: 'routes.dashboard.welcome', |
| | | }, |
| | | }; |
| | | export default menu; |
| | |
| | | import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { DEFAULT_LAYOUT_COMPONENT, PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '../constant'; |
| | | import { genRouteModule } from '/@/utils/helper/routeHelper'; |
| | | import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE, LAYOUT } from '../constant'; |
| | | import { PageEnum } from '/@/enums/pageEnum'; |
| | | |
| | | import modules from 'globby!/@/router/routes/modules/**/*.@(ts)'; |
| | | |
| | | const routeModuleList: AppRouteModule[] = []; |
| | | |
| | | Object.keys(modules).forEach((key) => { |
| | | routeModuleList.push(modules[key]); |
| | | const mod = Array.isArray(modules[key]) ? [...modules[key]] : [modules[key]]; |
| | | routeModuleList.push(...mod); |
| | | }); |
| | | |
| | | export const asyncRoutes = [ |
| | | REDIRECT_ROUTE, |
| | | PAGE_NOT_FOUND_ROUTE, |
| | | ...genRouteModule(routeModuleList), |
| | | ]; |
| | | export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]; |
| | | |
| | | // 主框架根路由 |
| | | export const RootRoute: AppRouteRecordRaw = { |
| | | const MainRoute: AppRouteModule = { |
| | | path: '/', |
| | | name: 'Root', |
| | | component: DEFAULT_LAYOUT_COMPONENT, |
| | | redirect: '/dashboard', |
| | | name: 'MainRoute', |
| | | component: LAYOUT, |
| | | redirect: PageEnum.BASE_HOME, |
| | | meta: { |
| | | title: 'Root', |
| | | icon: 'ant-design:home-outlined', |
| | | title: 'routes.dashboard.dashboard', |
| | | }, |
| | | children: [], |
| | | }; |
| | | |
| | | export const LoginRoute: AppRouteRecordRaw = { |
| | |
| | | }; |
| | | |
| | | // 基础路由 不用权限 |
| | | export const basicRoutes = [LoginRoute, RootRoute]; |
| | | export const basicRoutes = [LoginRoute, MainRoute, REDIRECT_ROUTE]; |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | |
| | | const dashboard: AppRouteModule = { |
| | | layout: { |
| | | path: '/dashboard', |
| | | name: 'Dashboard', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/dashboard/welcome', |
| | | meta: { |
| | | icon: 'ant-design:home-outlined', |
| | | title: 'routes.dashboard.dashboard', |
| | | }, |
| | | path: '/dashboard', |
| | | name: 'Dashboard', |
| | | component: LAYOUT, |
| | | redirect: '/dashboard/welcome', |
| | | meta: { |
| | | icon: 'ant-design:home-outlined', |
| | | title: 'routes.dashboard.dashboard', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/welcome', |
| | | name: 'Welcome', |
| | | component: () => import('/@/views/dashboard/welcome/index.vue'), |
| | | meta: { |
| | | title: 'routes.dashboard.welcome', |
| | | affix: true, |
| | | icon: 'ant-design:home-outlined', |
| | | }, |
| | | }, |
| | | { |
| | | path: '/workbench', |
| | | path: 'workbench', |
| | | name: 'Workbench', |
| | | component: () => import('/@/views/dashboard/workbench/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/analysis', |
| | | path: 'analysis', |
| | | name: 'Analysis', |
| | | component: () => import('/@/views/dashboard/analysis/index.vue'), |
| | | meta: { |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { getParentLayout, LAYOUT } from '/@/router/constant'; |
| | | |
| | | const charts: AppRouteModule = { |
| | | layout: { |
| | | path: '/charts', |
| | | name: 'Charts', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/charts/apexChart', |
| | | meta: { |
| | | icon: 'ant-design:area-chart-outlined', |
| | | title: 'routes.demo.charts.charts', |
| | | }, |
| | | path: '/charts', |
| | | name: 'Charts', |
| | | component: LAYOUT, |
| | | redirect: '/charts/apexChart', |
| | | meta: { |
| | | icon: 'ant-design:area-chart-outlined', |
| | | title: 'routes.demo.charts.charts', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/echarts', |
| | | path: 'echarts', |
| | | name: 'Echarts', |
| | | component: getParentLayout('Echarts'), |
| | | meta: { |
| | | title: 'Echarts', |
| | | }, |
| | |
| | | ], |
| | | }, |
| | | { |
| | | path: '/apexChart', |
| | | path: 'apexChart', |
| | | name: 'ApexChart', |
| | | meta: { |
| | | title: 'routes.demo.charts.apexChart', |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { getParentLayout, LAYOUT } from '/@/router/constant'; |
| | | |
| | | const comp: AppRouteModule = { |
| | | layout: { |
| | | path: '/comp', |
| | | name: 'Comp', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/comp/basic', |
| | | meta: { |
| | | icon: 'ant-design:table-outlined', |
| | | title: 'routes.demo.comp.comp', |
| | | }, |
| | | path: '/comp', |
| | | name: 'Comp', |
| | | component: LAYOUT, |
| | | redirect: '/comp/basic', |
| | | meta: { |
| | | icon: 'ant-design:table-outlined', |
| | | title: 'routes.demo.comp.comp', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/basic', |
| | | path: 'basic', |
| | | name: 'BasicDemo', |
| | | component: () => import('/@/views/demo/comp/button/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/transition', |
| | | path: 'transition', |
| | | name: 'transitionDemo', |
| | | component: () => import('/@/views/demo/comp/transition/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/countTo', |
| | | path: 'countTo', |
| | | name: 'CountTo', |
| | | component: () => import('/@/views/demo/comp/count-to/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | |
| | | { |
| | | path: '/scroll', |
| | | path: 'scroll', |
| | | name: 'ScrollDemo', |
| | | redirect: '/comp/scroll/basic', |
| | | component: getParentLayout('ScrollDemo'), |
| | | meta: { |
| | | title: 'routes.demo.comp.scroll', |
| | | }, |
| | |
| | | }, |
| | | |
| | | { |
| | | path: '/modal', |
| | | path: 'modal', |
| | | name: 'ModalDemo', |
| | | component: () => import('/@/views/demo/comp/modal/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/drawer', |
| | | path: 'drawer', |
| | | name: 'DrawerDemo', |
| | | component: () => import('/@/views/demo/comp/drawer/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/desc', |
| | | path: 'desc', |
| | | name: 'DescDemo', |
| | | component: () => import('/@/views/demo/comp/desc/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | |
| | | { |
| | | path: '/lazy', |
| | | name: 'lazyDemo', |
| | | path: 'lazy', |
| | | name: 'LazyDemo', |
| | | component: getParentLayout('LazyDemo'), |
| | | redirect: '/comp/lazy/basic', |
| | | meta: { |
| | | title: 'routes.demo.comp.lazy', |
| | |
| | | ], |
| | | }, |
| | | { |
| | | path: '/verify', |
| | | path: 'verify', |
| | | name: 'VerifyDemo', |
| | | component: getParentLayout('VerifyDemo'), |
| | | redirect: '/comp/verify/drag', |
| | | meta: { |
| | | title: 'routes.demo.comp.verify', |
| | |
| | | // |
| | | |
| | | { |
| | | path: '/qrcode', |
| | | path: 'qrcode', |
| | | name: 'QrCodeDemo', |
| | | component: () => import('/@/views/demo/comp/qrcode/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/strength-meter', |
| | | path: 'strength-meter', |
| | | name: 'StrengthMeterDemo', |
| | | component: () => import('/@/views/demo/comp/strength-meter/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/upload', |
| | | path: 'upload', |
| | | name: 'UploadDemo', |
| | | component: () => import('/@/views/demo/comp/upload/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/loading', |
| | | path: 'loading', |
| | | name: 'LoadingDemo', |
| | | component: () => import('/@/views/demo/comp/loading/index.vue'), |
| | | meta: { |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { getParentLayout, LAYOUT } from '/@/router/constant'; |
| | | |
| | | const editor: AppRouteModule = { |
| | | layout: { |
| | | path: '/editor', |
| | | name: 'Editor', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/editor/markdown', |
| | | meta: { |
| | | icon: 'ant-design:table-outlined', |
| | | title: 'routes.demo.editor.editor', |
| | | }, |
| | | path: '/editor', |
| | | name: 'Editor', |
| | | component: LAYOUT, |
| | | redirect: '/editor/markdown', |
| | | meta: { |
| | | icon: 'ant-design:table-outlined', |
| | | title: 'routes.demo.editor.editor', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/markdown', |
| | | path: 'markdown', |
| | | name: 'MarkdownDemo', |
| | | component: () => import('/@/views/demo/editor/Markdown.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/tinymce', |
| | | path: 'tinymce', |
| | | component: getParentLayout('TinymceDemo'), |
| | | name: 'TinymceDemo', |
| | | meta: { |
| | | title: 'routes.demo.editor.tinymce', |
| | |
| | | title: 'routes.demo.editor.tinymceBasic', |
| | | }, |
| | | }, |
| | | // TODO |
| | | { |
| | | path: 'editor', |
| | | name: 'TinymceFormDemo', |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | |
| | | const excel: AppRouteModule = { |
| | | layout: { |
| | | path: '/excel', |
| | | name: 'Excel', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/excel/customExport', |
| | | meta: { |
| | | icon: 'mdi:microsoft-excel', |
| | | title: 'routes.demo.excel.excel', |
| | | }, |
| | | path: '/excel', |
| | | name: 'Excel', |
| | | component: LAYOUT, |
| | | redirect: '/excel/customExport', |
| | | meta: { |
| | | icon: 'mdi:microsoft-excel', |
| | | title: 'routes.demo.excel.excel', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/customExport', |
| | | path: 'customExport', |
| | | name: 'CustomExport', |
| | | component: () => import('/@/views/demo/excel/CustomExport.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/jsonExport', |
| | | path: 'jsonExport', |
| | | name: 'JsonExport', |
| | | component: () => import('/@/views/demo/excel/JsonExport.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/arrayExport', |
| | | path: 'arrayExport', |
| | | name: 'ArrayExport', |
| | | component: () => import('/@/views/demo/excel/ArrayExport.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/importExcel', |
| | | path: 'importExcel', |
| | | name: 'ImportExcel', |
| | | component: () => import('/@/views/demo/excel/ImportExcel.vue'), |
| | | meta: { |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | |
| | | const feat: AppRouteModule = { |
| | | layout: { |
| | | path: '/feat', |
| | | name: 'FeatDemo', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/feat/icon', |
| | | meta: { |
| | | icon: 'ic:outline-featured-play-list', |
| | | title: 'routes.demo.feat.feat', |
| | | }, |
| | | path: '/feat', |
| | | name: 'FeatDemo', |
| | | component: LAYOUT, |
| | | redirect: '/feat/icon', |
| | | meta: { |
| | | icon: 'ic:outline-featured-play-list', |
| | | title: 'routes.demo.feat.feat', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/icon', |
| | | path: 'icon', |
| | | name: 'IconDemo', |
| | | component: () => import('/@/views/demo/feat/icon/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/tabs', |
| | | path: 'tabs', |
| | | name: 'TabsDemo', |
| | | component: () => import('/@/views/demo/feat/tabs/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | |
| | | { |
| | | path: '/context-menu', |
| | | path: 'context-menu', |
| | | name: 'ContextMenuDemo', |
| | | component: () => import('/@/views/demo/feat/context-menu/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/download', |
| | | path: 'download', |
| | | name: 'DownLoadDemo', |
| | | component: () => import('/@/views/demo/feat/download/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/click-out-side', |
| | | path: 'click-out-side', |
| | | name: 'ClickOutSideDemo', |
| | | component: () => import('/@/views/demo/feat/click-out-side/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/img-preview', |
| | | path: 'img-preview', |
| | | name: 'ImgPreview', |
| | | component: () => import('/@/views/demo/feat/img-preview/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/copy', |
| | | path: 'copy', |
| | | name: 'CopyDemo', |
| | | component: () => import('/@/views/demo/feat/copy/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/msg', |
| | | path: 'msg', |
| | | name: 'MsgDemo', |
| | | component: () => import('/@/views/demo/feat/msg/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/watermark', |
| | | path: 'watermark', |
| | | name: 'WatermarkDemo', |
| | | component: () => import('/@/views/demo/feat/watermark/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/full-screen', |
| | | path: 'full-screen', |
| | | name: 'FullScreenDemo', |
| | | component: () => import('/@/views/demo/feat/full-screen/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/error-log', |
| | | path: 'error-log', |
| | | name: 'ErrorLog', |
| | | component: () => import('/@/views/sys/error-log/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/testTab/:id', |
| | | path: 'testTab/:id', |
| | | name: 'TestTab', |
| | | component: () => import('/@/views/demo/feat/tab-params/index.vue'), |
| | | meta: { |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | |
| | | const form: AppRouteModule = { |
| | | layout: { |
| | | path: '/form', |
| | | name: 'FormDemo', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/form/basic', |
| | | meta: { |
| | | icon: 'ant-design:table-outlined', |
| | | title: 'routes.demo.form.form', |
| | | }, |
| | | path: '/form', |
| | | name: 'FormDemo', |
| | | component: LAYOUT, |
| | | redirect: '/form/basic', |
| | | meta: { |
| | | icon: 'ant-design:table-outlined', |
| | | title: 'routes.demo.form.form', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/basic', |
| | | path: 'basic', |
| | | name: 'FormBasicDemo', |
| | | component: () => import('/@/views/demo/form/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/useForm', |
| | | path: 'useForm', |
| | | name: 'UseFormDemo', |
| | | component: () => import('/@/views/demo/form/UseForm.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/refForm', |
| | | path: 'refForm', |
| | | name: 'RefFormDemo', |
| | | component: () => import('/@/views/demo/form/RefForm.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/advancedForm', |
| | | path: 'advancedForm', |
| | | name: 'AdvancedFormDemo', |
| | | component: () => import('/@/views/demo/form/AdvancedForm.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/ruleForm', |
| | | path: 'ruleForm', |
| | | name: 'RuleFormDemo', |
| | | component: () => import('/@/views/demo/form/RuleForm.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/dynamicForm', |
| | | path: 'dynamicForm', |
| | | name: 'DynamicFormDemo', |
| | | component: () => import('/@/views/demo/form/DynamicForm.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/customerForm', |
| | | path: 'customerForm', |
| | | name: 'CustomerFormDemo', |
| | | component: () => import('/@/views/demo/form/CustomerForm.vue'), |
| | | meta: { |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | const IFrame = () => import('/@/views/sys/iframe/FrameBlank.vue'); |
| | | |
| | | const iframe: AppRouteModule = { |
| | | layout: { |
| | | path: '/frame', |
| | | name: 'Frame', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/frame/antv', |
| | | meta: { |
| | | icon: 'mdi:page-next-outline', |
| | | title: 'routes.demo.iframe.frame', |
| | | }, |
| | | path: '/frame', |
| | | name: 'Frame', |
| | | component: LAYOUT, |
| | | redirect: '/frame/antv', |
| | | meta: { |
| | | icon: 'mdi:page-next-outline', |
| | | title: 'routes.demo.iframe.frame', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/antv', |
| | | path: 'antv', |
| | | name: 'Antv', |
| | | component: IFrame, |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/doc', |
| | | path: 'doc', |
| | | name: 'Doc', |
| | | component: IFrame, |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/docExternal', |
| | | path: 'docExternal', |
| | | name: 'DocExternal', |
| | | component: IFrame, |
| | | meta: { |
New file |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { getParentLayout, LAYOUT } from '/@/router/constant'; |
| | | |
| | | const permission: AppRouteModule = { |
| | | path: '/level', |
| | | name: 'Level', |
| | | component: LAYOUT, |
| | | redirect: '/level/menu1/menu1-1', |
| | | meta: { |
| | | icon: 'carbon:user-role', |
| | | title: 'routes.demo.level.level', |
| | | }, |
| | | |
| | | children: [ |
| | | { |
| | | path: 'menu1', |
| | | name: 'Menu1Demo', |
| | | component: getParentLayout('Menu1Demo'), |
| | | meta: { |
| | | title: 'Menu1', |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'menu1-1', |
| | | name: 'Menu11Demo', |
| | | component: getParentLayout('Menu11Demo'), |
| | | meta: { |
| | | title: 'Menu1-1', |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'menu1-1-1', |
| | | name: 'Menu111Demo', |
| | | component: () => import('/@/views/demo/level/Menu111.vue'), |
| | | meta: { |
| | | title: 'Menu111', |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: 'menu1-2', |
| | | name: 'Menu12Demo', |
| | | component: () => import('/@/views/demo/level/Menu12.vue'), |
| | | meta: { |
| | | title: 'Menu1-2', |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: 'menu2', |
| | | name: 'Menu2Demo', |
| | | component: () => import('/@/views/demo/level/Menu2.vue'), |
| | | meta: { |
| | | title: 'Menu2', |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | export default permission; |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { getParentLayout, LAYOUT } from '/@/router/constant'; |
| | | import { ExceptionEnum } from '/@/enums/exceptionEnum'; |
| | | |
| | | const ExceptionPage = () => import('/@/views/sys/exception/Exception'); |
| | |
| | | const page: AppRouteModule = { |
| | | path: '/page-demo', |
| | | name: 'PageDemo', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | component: LAYOUT, |
| | | redirect: '/page-demo/exception', |
| | | meta: { |
| | | icon: 'mdi:page-next-outline', |
| | |
| | | children: [ |
| | | // =============================form start============================= |
| | | { |
| | | path: '/form', |
| | | path: 'form', |
| | | name: 'FormPage', |
| | | redirect: '/page-demo/form/basic', |
| | | component: getParentLayout('FormPage'), |
| | | meta: { |
| | | title: 'routes.demo.page.form', |
| | | }, |
| | |
| | | // =============================form end============================= |
| | | // =============================desc start============================= |
| | | { |
| | | path: '/desc', |
| | | path: 'desc', |
| | | name: 'DescPage', |
| | | component: getParentLayout('DescPage'), |
| | | redirect: '/page-demo/desc/basic', |
| | | meta: { |
| | | title: 'routes.demo.page.desc', |
| | |
| | | |
| | | // =============================result start============================= |
| | | { |
| | | path: '/result', |
| | | path: 'result', |
| | | name: 'ResultPage', |
| | | redirect: '/page-demo/result/success', |
| | | component: getParentLayout('ResultPage'), |
| | | |
| | | meta: { |
| | | title: 'routes.demo.page.result', |
| | | }, |
| | |
| | | |
| | | // =============================account start============================= |
| | | { |
| | | path: '/account', |
| | | path: 'account', |
| | | name: 'AccountPage', |
| | | component: getParentLayout('AccountPage'), |
| | | redirect: '/page-demo/account/setting', |
| | | meta: { |
| | | title: 'routes.demo.page.account', |
| | |
| | | // =============================account end============================= |
| | | // =============================exception start============================= |
| | | { |
| | | path: '/exception', |
| | | path: 'exception', |
| | | name: 'ExceptionPage', |
| | | component: getParentLayout('ExceptionPage'), |
| | | redirect: '/page-demo/exception/404', |
| | | meta: { |
| | | title: 'routes.demo.page.exception', |
| | |
| | | // =============================exception end============================= |
| | | // =============================list start============================= |
| | | { |
| | | path: '/list', |
| | | path: 'list', |
| | | name: 'ListPage', |
| | | component: getParentLayout('ListPage'), |
| | | redirect: '/page-demo/list/card', |
| | | meta: { |
| | | title: 'routes.demo.page.list', |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { getParentLayout, LAYOUT } from '/@/router/constant'; |
| | | import { RoleEnum } from '/@/enums/roleEnum'; |
| | | |
| | | const permission: AppRouteModule = { |
| | | layout: { |
| | | path: '/permission', |
| | | name: 'Permission', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/permission/front/page', |
| | | meta: { |
| | | icon: 'carbon:user-role', |
| | | title: 'routes.demo.permission.permission', |
| | | }, |
| | | path: '/permission', |
| | | name: 'Permission', |
| | | component: LAYOUT, |
| | | redirect: '/permission/front/page', |
| | | meta: { |
| | | icon: 'carbon:user-role', |
| | | title: 'routes.demo.permission.permission', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/front', |
| | | path: 'front', |
| | | name: 'PermissionFrontDemo', |
| | | component: getParentLayout('PermissionFrontDemo'), |
| | | meta: { |
| | | title: 'routes.demo.permission.front', |
| | | }, |
| | |
| | | ], |
| | | }, |
| | | { |
| | | path: '/back', |
| | | path: 'back', |
| | | name: 'PermissionBackDemo', |
| | | component: getParentLayout('PermissionBackDemo'), |
| | | meta: { |
| | | title: 'routes.demo.permission.back', |
| | | }, |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | |
| | | const table: AppRouteModule = { |
| | | layout: { |
| | | path: '/table', |
| | | name: 'TableDemo', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/table/basic', |
| | | meta: { |
| | | icon: 'ant-design:table-outlined', |
| | | title: 'routes.demo.table.table', |
| | | }, |
| | | path: '/table', |
| | | name: 'TableDemo', |
| | | component: LAYOUT, |
| | | redirect: '/table/basic', |
| | | meta: { |
| | | icon: 'ant-design:table-outlined', |
| | | title: 'routes.demo.table.table', |
| | | }, |
| | | |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/basic', |
| | | path: 'basic', |
| | | name: 'TableBasicDemo', |
| | | component: () => import('/@/views/demo/table/Basic.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/treeTable', |
| | | path: 'treeTable', |
| | | name: 'TreeTableDemo', |
| | | component: () => import('/@/views/demo/table/TreeTable.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/fetchTable', |
| | | path: 'fetchTable', |
| | | name: 'FetchTableDemo', |
| | | component: () => import('/@/views/demo/table/FetchTable.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/fixedColumn', |
| | | path: 'fixedColumn', |
| | | name: 'FixedColumnDemo', |
| | | component: () => import('/@/views/demo/table/FixedColumn.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/customerCell', |
| | | path: 'customerCell', |
| | | name: 'CustomerCellDemo', |
| | | component: () => import('/@/views/demo/table/CustomerCell.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/formTable', |
| | | path: 'formTable', |
| | | name: 'FormTableDemo', |
| | | component: () => import('/@/views/demo/table/FormTable.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/useTable', |
| | | path: 'useTable', |
| | | name: 'UseTableDemo', |
| | | component: () => import('/@/views/demo/table/UseTable.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/refTable', |
| | | path: 'refTable', |
| | | name: 'RefTableDemo', |
| | | component: () => import('/@/views/demo/table/RefTable.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/multipleHeader', |
| | | path: 'multipleHeader', |
| | | name: 'MultipleHeaderDemo', |
| | | component: () => import('/@/views/demo/table/MultipleHeader.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/mergeHeader', |
| | | path: 'mergeHeader', |
| | | name: 'MergeHeaderDemo', |
| | | component: () => import('/@/views/demo/table/MergeHeader.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/expandTable', |
| | | path: 'expandTable', |
| | | name: 'ExpandTableDemo', |
| | | component: () => import('/@/views/demo/table/ExpandTable.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/fixedHeight', |
| | | path: 'fixedHeight', |
| | | name: 'FixedHeightDemo', |
| | | component: () => import('/@/views/demo/table/FixedHeight.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/footerTable', |
| | | path: 'footerTable', |
| | | name: 'FooterTableDemo', |
| | | component: () => import('/@/views/demo/table/FooterTable.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/editCellTable', |
| | | path: 'editCellTable', |
| | | name: 'EditCellTableDemo', |
| | | component: () => import('/@/views/demo/table/EditCellTable.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/editRowTable', |
| | | path: 'editRowTable', |
| | | name: 'EditRowTableDemo', |
| | | component: () => import('/@/views/demo/table/EditRowTable.vue'), |
| | | meta: { |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { PAGE_LAYOUT_COMPONENT } from '/@/router/constant'; |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | |
| | | const tree: AppRouteModule = { |
| | | layout: { |
| | | path: '/tree', |
| | | name: 'TreeDemo', |
| | | component: PAGE_LAYOUT_COMPONENT, |
| | | redirect: '/tree/basic', |
| | | meta: { |
| | | icon: 'clarity:tree-view-line', |
| | | title: 'routes.demo.tree.tree', |
| | | }, |
| | | path: '/tree', |
| | | name: 'TreeDemo', |
| | | component: LAYOUT, |
| | | redirect: '/tree/basic', |
| | | meta: { |
| | | icon: 'clarity:tree-view-line', |
| | | title: 'routes.demo.tree.tree', |
| | | }, |
| | | routes: [ |
| | | children: [ |
| | | { |
| | | path: '/basic', |
| | | path: 'basic', |
| | | name: 'BasicTreeDemo', |
| | | component: () => import('/@/views/demo/tree/index.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/editTree', |
| | | path: 'editTree', |
| | | name: 'EditTreeDemo', |
| | | component: () => import('/@/views/demo/tree/EditTree.vue'), |
| | | meta: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: '/actionTree', |
| | | path: 'actionTree', |
| | | name: 'ActionTreeDemo', |
| | | component: () => import('/@/views/demo/tree/ActionTree.vue'), |
| | | meta: { |
New file |
| | |
| | | import type { AppRouteModule } from '/@/router/types'; |
| | | |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | |
| | | const dashboard: AppRouteModule = { |
| | | path: '/home', |
| | | name: 'Home', |
| | | component: LAYOUT, |
| | | redirect: '/home/welcome', |
| | | meta: { |
| | | icon: 'ant-design:home-outlined', |
| | | title: 'routes.dashboard.welcome', |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'welcome', |
| | | name: 'Welcome', |
| | | component: () => import('/@/views/dashboard/welcome/index.vue'), |
| | | meta: { |
| | | title: 'routes.dashboard.welcome', |
| | | affix: true, |
| | | icon: 'ant-design:home-outlined', |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | export default dashboard; |
| | |
| | | import type { RouteRecordRaw } from 'vue-router'; |
| | | import { RoleEnum } from '/@/enums/roleEnum'; |
| | | import Component from '/@/components/types'; |
| | | export interface RouteMeta { |
| | | // title |
| | | title: string; |
| | |
| | | // Whether the route has been dynamically added |
| | | hideBreadcrumb?: boolean; |
| | | |
| | | // disabled redirect |
| | | disabledRedirect?: boolean; |
| | | |
| | | // close loading |
| | | afterCloseLoading?: boolean; |
| | | // Is it in the tab |
| | | inTab?: boolean; |
| | | // Carrying parameters |
| | | carryParam?: boolean; |
| | | |
| | | single?: boolean; |
| | | } |
| | | |
| | | export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> { |
| | | name: string; |
| | | meta: RouteMeta; |
| | | component?: any; |
| | | components?: any; |
| | | component?: Component; |
| | | components?: Component; |
| | | children?: AppRouteRecordRaw[]; |
| | | props?: any; |
| | | props?: Record<string, any>; |
| | | fullPath?: string; |
| | | } |
| | | export interface MenuTag { |
| | |
| | | menu: Menu; |
| | | } |
| | | |
| | | interface RouteModule { |
| | | layout: AppRouteRecordRaw; |
| | | routes: AppRouteRecordRaw[]; |
| | | children?: AppRouteRecordRaw[]; |
| | | component?: any; |
| | | } |
| | | // interface RouteModule { |
| | | // layout: AppRouteRecordRaw; |
| | | // routes: AppRouteRecordRaw[]; |
| | | // children?: AppRouteRecordRaw[]; |
| | | // component?: Component; |
| | | // } |
| | | |
| | | export type AppRouteModule = RouteModule | AppRouteRecordRaw; |
| | | // export type AppRouteModule = RouteModule | AppRouteRecordRaw; |
| | | export type AppRouteModule = AppRouteRecordRaw; |
| | |
| | | import { REDIRECT_ROUTE } from '/@/router/constant'; |
| | | import type { AppRouteRecordRaw, Menu } from '/@/router/types'; |
| | | import store from '/@/store/index'; |
| | | import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; |
| | |
| | | import { toRaw } from 'vue'; |
| | | import { getMenuListById } from '/@/api/sys/menu'; |
| | | |
| | | import { genRouteModule, transformObjToRoute } from '/@/utils/helper/routeHelper'; |
| | | import { transformRouteToMenu } from '/@/utils/helper/menuHelper'; |
| | | import { transformObjToRoute } from '/@/router/helper/routeHelper'; |
| | | import { transformRouteToMenu } from '/@/router/helper/menuHelper'; |
| | | |
| | | import { useMessage } from '/@/hooks/web/useMessage'; |
| | | // import { warn } from '/@/utils/log'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | |
| | | const { t } = useI18n(); |
| | | |
| | | const { createMessage } = useMessage(); |
| | | const NAME = 'permission'; |
| | |
| | | |
| | | @Action |
| | | async buildRoutesAction(id?: number | string): Promise<AppRouteRecordRaw[]> { |
| | | const { t } = useI18n(); |
| | | let routes: AppRouteRecordRaw[] = []; |
| | | const roleList = toRaw(userStore.getRoleListState); |
| | | |
| | |
| | | // role permissions |
| | | if (permissionMode === PermissionModeEnum.ROLE) { |
| | | routes = filter(asyncRoutes, (route) => { |
| | | const { meta } = route; |
| | | const { roles } = meta!; |
| | | const { meta } = route as AppRouteRecordRaw; |
| | | const { roles } = meta || {}; |
| | | if (!roles) return true; |
| | | return roleList.some((role) => roles.includes(role)); |
| | | }); |
| | | // 如果确定不需要做后台动态权限,请将下面整个判断注释 |
| | | } else if (permissionMode === PermissionModeEnum.BACK) { |
| | | const messageKey = 'loadMenu'; |
| | | createMessage.loading({ |
| | | content: t('sys.app.menuLoading'), |
| | | key: messageKey, |
| | | duration: 1, |
| | | }); |
| | | // 这里获取后台路由菜单逻辑自行修改 |
| | |
| | | routeList = transformObjToRoute(routeList); |
| | | // 后台路由转菜单结构 |
| | | const backMenuList = transformRouteToMenu(routeList); |
| | | |
| | | this.commitBackMenuListState(backMenuList); |
| | | // 生成路由 |
| | | routes = genRouteModule(routeList) as AppRouteRecordRaw[]; |
| | | routes.push(REDIRECT_ROUTE); |
| | | |
| | | routes = routeList; |
| | | } |
| | | return routes; |
| | | } |
| | |
| | | import { computed, toRaw } from 'vue'; |
| | | import type { AppRouteRecordRaw, RouteMeta } from '/@/router/types.d'; |
| | | import { toRaw } from 'vue'; |
| | | |
| | | import { unref } from 'vue'; |
| | | import { Action, Module, Mutation, VuexModule, getModule } from 'vuex-module-decorators'; |
| | | import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; |
| | | |
| | | import { PageEnum } from '/@/enums/pageEnum'; |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import { userStore } from './user'; |
| | | |
| | | import store from '/@/store'; |
| | | import router from '/@/router'; |
| | | import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/constant'; |
| | | import { getCurrentTo } from '/@/utils/helper/routeHelper'; |
| | | import { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'; |
| | | import { getRoute } from '/@/router/helper/routeHelper'; |
| | | import { useGo, useRedo } from '/@/hooks/web/usePage'; |
| | | |
| | | type CacheName = string | symbol | null | undefined; |
| | | |
| | | /** |
| | | * @description: vuex Tab模块 |
| | | */ |
| | | // declare namespace TabsStore { |
| | | export interface TabItem { |
| | | fullPath: string; |
| | | path?: string; |
| | | params?: any; |
| | | query?: any; |
| | | name?: CacheName; |
| | | meta?: RouteMeta; |
| | | } |
| | | |
| | | const NAME = 'tab'; |
| | | |
| | | hotModuleUnregisterModule(NAME); |
| | | |
| | | const getOpenKeepAliveRef = computed(() => appStore.getProjectConfig.openKeepAlive); |
| | | export const PAGE_LAYOUT_KEY = '__PAGE_LAYOUT__'; |
| | | |
| | | function isGotoPage() { |
| | | const go = useGo(); |
| | | go(unref(router.currentRoute).path, true); |
| | | } |
| | | |
| | | @Module({ namespaced: true, name: NAME, dynamic: true, store }) |
| | | class Tab extends VuexModule { |
| | | cachedMapState = new Map<string, string[]>(); |
| | | |
| | | // tab list |
| | | tabsState: TabItem[] = []; |
| | | // tab cache list |
| | | keepAliveTabsState: CacheName[] = []; |
| | | |
| | | currentContextMenuIndexState = -1; |
| | | |
| | | currentContextMenuState: TabItem | null = null; |
| | | tabsState: RouteLocationNormalized[] = []; |
| | | |
| | | // Last route change |
| | | lastChangeRouteState: AppRouteRecordRaw | null = null; |
| | | lastChangeRouteState: RouteLocationNormalized | null = null; |
| | | |
| | | lastDragEndIndexState = 0; |
| | | |
| | | get getTabsState() { |
| | | return this.tabsState; |
| | |
| | | return this.lastChangeRouteState; |
| | | } |
| | | |
| | | get getCurrentContextMenuIndexState() { |
| | | return this.currentContextMenuIndexState; |
| | | } |
| | | |
| | | get getCurrentContextMenuState() { |
| | | return this.currentContextMenuState; |
| | | } |
| | | |
| | | get getKeepAliveTabsState() { |
| | | return this.keepAliveTabsState; |
| | | } |
| | | |
| | | get getCurrentTab(): TabItem { |
| | | get getCurrentTab(): RouteLocationNormalized { |
| | | const route = unref(router.currentRoute); |
| | | return this.tabsState.find((item) => item.path === route.path)!; |
| | | } |
| | | |
| | | get getCachedMapState(): Map<string, string[]> { |
| | | return this.cachedMapState; |
| | | } |
| | | |
| | | get getLastDragEndIndexState(): number { |
| | | return this.lastDragEndIndexState; |
| | | } |
| | | |
| | | @Mutation |
| | | commitLastChangeRouteState(route: AppRouteRecordRaw): void { |
| | | commitLastChangeRouteState(route: RouteLocationNormalized): void { |
| | | if (!userStore.getTokenState) return; |
| | | this.lastChangeRouteState = route; |
| | | } |
| | | |
| | | @Mutation |
| | | commitClearCache(): void { |
| | | this.keepAliveTabsState = []; |
| | | this.cachedMapState = new Map(); |
| | | } |
| | | |
| | | @Mutation |
| | | commitCurrentContextMenuIndexState(index: number): void { |
| | | this.currentContextMenuIndexState = index; |
| | | } |
| | | goToPage() { |
| | | const go = useGo(); |
| | | const len = this.tabsState.length; |
| | | const { path } = unref(router.currentRoute); |
| | | |
| | | @Mutation |
| | | commitCurrentContextMenuState(item: TabItem): void { |
| | | this.currentContextMenuState = item; |
| | | } |
| | | let toPath: PageEnum | string = PageEnum.BASE_HOME; |
| | | |
| | | /** |
| | | * @description: add tab |
| | | */ |
| | | @Mutation |
| | | commitAddTab(route: AppRouteRecordRaw | TabItem): void { |
| | | const { path, name, meta, fullPath, params, query } = route as TabItem; |
| | | // 404 页面不需要添加tab |
| | | if (path === PageEnum.ERROR_PAGE || !name) { |
| | | return; |
| | | } else if ([REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)) { |
| | | return; |
| | | if (len > 0) { |
| | | const page = this.tabsState[len - 1]; |
| | | const p = page.fullPath || page.path; |
| | | if (p) { |
| | | toPath = p; |
| | | } |
| | | } |
| | | // Jump to the current page and report an error |
| | | path !== toPath && go(toPath as PageEnum, true); |
| | | } |
| | | |
| | | @Mutation |
| | | commitCachedMapState(): void { |
| | | const cacheMap = new Map<string, string[]>(); |
| | | |
| | | const pageCacheSet = new Set<string>(); |
| | | this.tabsState.forEach((tab) => { |
| | | const item = getRoute(tab); |
| | | const needAuth = !item.meta.ignoreAuth; |
| | | if (item.meta.affix) { |
| | | const name = item.name as string; |
| | | pageCacheSet.add(name); |
| | | } else if (item.matched && needAuth) { |
| | | const matched = item.matched; |
| | | const len = matched.length; |
| | | |
| | | if (len < 2) return; |
| | | |
| | | for (let i = 0; i < matched.length; i++) { |
| | | const key = matched[i].name as string; |
| | | |
| | | if (i < 2) { |
| | | pageCacheSet.add(key); |
| | | } |
| | | if (i < len - 1) { |
| | | const { meta, name } = matched[i + 1]; |
| | | if (meta && (meta.affix || needAuth)) { |
| | | const mapList = cacheMap.get(key) || []; |
| | | if (!mapList.includes(name as string)) { |
| | | mapList.push(name as string); |
| | | } |
| | | cacheMap.set(key, mapList); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | |
| | | cacheMap.set(PAGE_LAYOUT_KEY, Array.from(pageCacheSet)); |
| | | this.cachedMapState = cacheMap; |
| | | } |
| | | |
| | | @Mutation |
| | | commitTabRoutesState(route: RouteLocationNormalized) { |
| | | const { path, fullPath, params, query } = route; |
| | | |
| | | let updateIndex = -1; |
| | | // 已经存在的页面,不重复添加tab |
| | |
| | | this.tabsState.splice(updateIndex, 1, curTab); |
| | | return; |
| | | } |
| | | this.tabsState.push({ path, fullPath, name, meta, params, query }); |
| | | if (unref(getOpenKeepAliveRef) && name) { |
| | | const noKeepAlive = meta && meta.ignoreKeepAlive; |
| | | const hasName = this.keepAliveTabsState.includes(name); |
| | | !noKeepAlive && !hasName && this.keepAliveTabsState.push(name); |
| | | } |
| | | this.tabsState.push(route); |
| | | } |
| | | |
| | | /** |
| | | * @description: close tab |
| | | */ |
| | | @Mutation |
| | | commitCloseTab(route: AppRouteRecordRaw | TabItem): void { |
| | | try { |
| | | const { fullPath, name, meta: { affix } = {} } = route; |
| | | if (affix) return; |
| | | const index = this.tabsState.findIndex((item) => item.fullPath === fullPath); |
| | | index !== -1 && this.tabsState.splice(index, 1); |
| | | |
| | | if (unref(getOpenKeepAliveRef) && name) { |
| | | const i = this.keepAliveTabsState.findIndex((item) => item === name); |
| | | i !== -1 && this.keepAliveTabsState.splice(i, 1); |
| | | } |
| | | } catch (error) {} |
| | | } |
| | | |
| | | @Mutation |
| | | commitCloseTabKeepAlive(route: AppRouteRecordRaw | TabItem): void { |
| | | const { name } = route; |
| | | if (unref(getOpenKeepAliveRef) && name) { |
| | | const i = this.keepAliveTabsState.findIndex((item) => item === name); |
| | | i !== -1 && toRaw(this.keepAliveTabsState).splice(i, 1); |
| | | } |
| | | commitCloseTab(route: RouteLocationNormalized): void { |
| | | const { fullPath, meta: { affix } = {} } = route; |
| | | if (affix) return; |
| | | const index = this.tabsState.findIndex((item) => item.fullPath === fullPath); |
| | | index !== -1 && this.tabsState.splice(index, 1); |
| | | } |
| | | |
| | | @Mutation |
| | |
| | | this.tabsState = this.tabsState.filter((item) => { |
| | | return item.meta && item.meta.affix; |
| | | }); |
| | | const names = this.tabsState.map((item) => item.name); |
| | | this.keepAliveTabsState = names as string[]; |
| | | } |
| | | |
| | | @Mutation |
| | | commitResetState(): void { |
| | | this.tabsState = []; |
| | | this.currentContextMenuState = null; |
| | | this.currentContextMenuIndexState = -1; |
| | | this.keepAliveTabsState = []; |
| | | this.cachedMapState = new Map(); |
| | | } |
| | | |
| | | @Mutation |
| | |
| | | |
| | | this.tabsState.splice(oldIndex, 1); |
| | | this.tabsState.splice(newIndex, 0, currentTab); |
| | | this.lastDragEndIndexState = this.lastDragEndIndexState + 1; |
| | | } |
| | | |
| | | @Mutation |
| | | closeMultipleTab({ pathList, nameList }: { pathList: string[]; nameList: string[] }): void { |
| | | closeMultipleTab({ pathList }: { pathList: string[] }): void { |
| | | this.tabsState = toRaw(this.tabsState).filter((item) => !pathList.includes(item.fullPath)); |
| | | if (unref(getOpenKeepAliveRef) && nameList) { |
| | | this.keepAliveTabsState = toRaw(this.keepAliveTabsState).filter( |
| | | (item) => !nameList.includes(item as string) |
| | | ); |
| | | } |
| | | } |
| | | |
| | | @Action |
| | | closeLeftTabAction(route: AppRouteRecordRaw | TabItem): void { |
| | | addTabAction(route: RouteLocationNormalized) { |
| | | const { path, name } = route; |
| | | // 404 页面不需要添加tab |
| | | if ( |
| | | path === PageEnum.ERROR_PAGE || |
| | | !name || |
| | | [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string) |
| | | ) { |
| | | return; |
| | | } |
| | | this.commitTabRoutesState(getRoute(route)); |
| | | |
| | | this.commitCachedMapState(); |
| | | } |
| | | |
| | | @Mutation |
| | | commitRedoPage() { |
| | | const route = router.currentRoute.value; |
| | | |
| | | for (const [key, value] of this.cachedMapState) { |
| | | const index = value.findIndex((item) => item === (route.name as string)); |
| | | if (index === -1) { |
| | | continue; |
| | | } |
| | | if (value.length === 1) { |
| | | this.cachedMapState.delete(key); |
| | | continue; |
| | | } |
| | | value.splice(index, 1); |
| | | this.cachedMapState.set(key, value); |
| | | } |
| | | const redo = useRedo(); |
| | | redo(); |
| | | } |
| | | |
| | | @Action |
| | | closeAllTabAction() { |
| | | this.commitCloseAllTab(); |
| | | this.commitClearCache(); |
| | | this.goToPage(); |
| | | } |
| | | |
| | | @Action |
| | | closeTabAction(tab: RouteLocationNormalized) { |
| | | function getObj(tabItem: RouteLocationNormalized) { |
| | | const { params, path, query } = tabItem; |
| | | return { |
| | | params: params || {}, |
| | | path, |
| | | query: query || {}, |
| | | }; |
| | | } |
| | | const { currentRoute, replace } = router; |
| | | |
| | | const { path } = unref(currentRoute); |
| | | if (path !== tab.path) { |
| | | // Closed is not the activation tab |
| | | this.commitCloseTab(tab); |
| | | return; |
| | | } |
| | | |
| | | // Closed is activated atb |
| | | let toObj: RouteLocationRaw = {}; |
| | | |
| | | const index = this.getTabsState.findIndex((item) => item.path === path); |
| | | |
| | | // If the current is the leftmost tab |
| | | if (index === 0) { |
| | | // There is only one tab, then jump to the homepage, otherwise jump to the right tab |
| | | if (this.getTabsState.length === 1) { |
| | | toObj = PageEnum.BASE_HOME; |
| | | } else { |
| | | // Jump to the right tab |
| | | const page = this.getTabsState[index + 1]; |
| | | toObj = getObj(page); |
| | | } |
| | | } else { |
| | | // Close the current tab |
| | | const page = this.getTabsState[index - 1]; |
| | | toObj = getObj(page); |
| | | } |
| | | this.commitCloseTab(currentRoute.value); |
| | | replace(toObj); |
| | | } |
| | | |
| | | @Action |
| | | closeTabByKeyAction(key: string) { |
| | | const index = this.tabsState.findIndex((item) => (item.fullPath || item.path) === key); |
| | | index !== -1 && this.closeTabAction(this.tabsState[index]); |
| | | } |
| | | |
| | | @Action |
| | | closeLeftTabAction(route: RouteLocationNormalized): void { |
| | | const index = this.tabsState.findIndex((item) => item.path === route.path); |
| | | |
| | | if (index > 0) { |
| | | const leftTabs = this.tabsState.slice(0, index); |
| | | const pathList: string[] = []; |
| | | const nameList: string[] = []; |
| | | for (const item of leftTabs) { |
| | | const affix = item.meta ? item.meta.affix : false; |
| | | if (!affix) { |
| | | pathList.push(item.fullPath); |
| | | nameList.push(item.name as string); |
| | | } |
| | | } |
| | | this.closeMultipleTab({ pathList, nameList }); |
| | | this.closeMultipleTab({ pathList }); |
| | | } |
| | | this.commitCachedMapState(); |
| | | isGotoPage(); |
| | | } |
| | | |
| | | @Action |
| | | addTabByPathAction(): void { |
| | | const toRoute = getCurrentTo(); |
| | | if (!toRoute) return; |
| | | const { meta } = toRoute; |
| | | if (meta && meta.affix) { |
| | | return; |
| | | } |
| | | this.commitAddTab((toRoute as unknown) as AppRouteRecordRaw); |
| | | } |
| | | |
| | | @Action |
| | | closeRightTabAction(route: AppRouteRecordRaw | TabItem): void { |
| | | closeRightTabAction(route: RouteLocationNormalized): void { |
| | | const index = this.tabsState.findIndex((item) => item.fullPath === route.fullPath); |
| | | |
| | | if (index >= 0 && index < this.tabsState.length - 1) { |
| | | const rightTabs = this.tabsState.slice(index + 1, this.tabsState.length); |
| | | |
| | | const pathList: string[] = []; |
| | | const nameList: string[] = []; |
| | | for (const item of rightTabs) { |
| | | const affix = item.meta ? item.meta.affix : false; |
| | | if (!affix) { |
| | | pathList.push(item.fullPath); |
| | | nameList.push(item.name as string); |
| | | } |
| | | } |
| | | this.closeMultipleTab({ pathList, nameList }); |
| | | this.closeMultipleTab({ pathList }); |
| | | } |
| | | this.commitCachedMapState(); |
| | | isGotoPage(); |
| | | } |
| | | |
| | | @Action |
| | | closeOtherTabAction(route: AppRouteRecordRaw | TabItem): void { |
| | | closeOtherTabAction(route: RouteLocationNormalized): void { |
| | | const closePathList = this.tabsState.map((item) => item.fullPath); |
| | | const pathList: string[] = []; |
| | | const nameList: string[] = []; |
| | | closePathList.forEach((path) => { |
| | | if (path !== route.fullPath) { |
| | | const closeItem = this.tabsState.find((item) => item.path === path); |
| | |
| | | const affix = closeItem.meta ? closeItem.meta.affix : false; |
| | | if (!affix) { |
| | | pathList.push(closeItem.fullPath); |
| | | nameList.push(closeItem.name as string); |
| | | } |
| | | } |
| | | }); |
| | | this.closeMultipleTab({ pathList, nameList }); |
| | | this.closeMultipleTab({ pathList }); |
| | | this.commitCachedMapState(); |
| | | isGotoPage(); |
| | | } |
| | | } |
| | | export const tabStore = getModule<Tab>(Tab); |
| | |
| | | import { useMessage } from '/@/hooks/web/useMessage'; |
| | | |
| | | export default defineComponent({ |
| | | name: 'Copy', |
| | | components: { CollapseContainer }, |
| | | setup() { |
| | | const valueRef = ref(''); |
New file |
| | |
| | | <template> |
| | | <div class="p-5"> |
| | | 多层级缓存-页面1-1-1 |
| | | <br /> |
| | | <input /> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | export default defineComponent({ name: 'Menu111Demo' }); |
| | | </script> |
New file |
| | |
| | | <template> |
| | | <div class="p-5"> |
| | | 多层级缓存-页面1-2 |
| | | <br /> |
| | | <input /> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | export default defineComponent({ name: 'Menu12Demo' }); |
| | | </script> |
New file |
| | |
| | | <template> |
| | | <div class="p-5"> |
| | | 多层级缓存-页面2 |
| | | <br /> |
| | | <input /> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | export default defineComponent({ |
| | | name: 'Menu2Demo', |
| | | }); |
| | | </script> |
| | |
| | | <template> |
| | | <div /> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, unref } from 'vue'; |
| | | |
| | |
| | | path: '/' + _path, |
| | | query, |
| | | }); |
| | | // close loading |
| | | if (unref(getEnableTransition) && unref(getOpenPageLoading)) { |
| | | setTimeout(() => { |
| | | appStore.setPageLoadingAction(false); |
| | | }, 0); |
| | | } |
| | | return () => null; |
| | | return {}; |
| | | }, |
| | | }); |
| | | </script> |
| | |
| | | resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.2.tgz#c4a95ddc06ca9b9496df03604e66fdefb39f4c4b" |
| | | integrity sha512-BybEHU5/I9EQ0CcwKAqmreZ2bMnAXrqLCTptAc6vPetHMbrXdZfejP5mt57e/8PNSt/qE7BHniU5PCYA+PGIHw== |
| | | |
| | | "@iconify/json@^1.1.266": |
| | | version "1.1.266" |
| | | resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.266.tgz#3537de808399652b3ca2c89a561216324121b785" |
| | | integrity sha512-I8S9lChQATaRroMGccdOQkFbBtMt4C2V/PQGiSjDq9yzdyqDCrPNN9X1qM4FoQt84zfW/+JMHIgShi42E+SXeA== |
| | | "@iconify/json@^1.1.267": |
| | | version "1.1.267" |
| | | resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.267.tgz#52ab5390fcaf95e0d68260523a3a3fbc575dfe01" |
| | | integrity sha512-VKNvyALvbuwsXO7r2XvdoqdctmvJzp1/XYOXRfhJ4w+sjtWYp8T3oRGDJ0AZTafzGiBBUaMwCZVP+j87rqgD3w== |
| | | |
| | | "@koa/cors@^3.1.0": |
| | | version "3.1.0" |
| | |
| | | resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" |
| | | integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== |
| | | |
| | | "@types/yargs@^15.0.10": |
| | | version "15.0.10" |
| | | resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.10.tgz#0fe3c8173a0d5c3e780b389050140c3f5ea6ea74" |
| | | integrity sha512-z8PNtlhrj7eJNLmrAivM7rjBESG6JwC5xP3RVk12i/8HVP7Xnx/sEmERnRImyEuUaJfO942X0qMOYsoupaJbZQ== |
| | | "@types/yargs@^15.0.11": |
| | | version "15.0.11" |
| | | resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.11.tgz#361d7579ecdac1527687bcebf9946621c12ab78c" |
| | | integrity sha512-jfcNBxHFYJ4nPIacsi3woz1+kvUO6s1CyeEhtnDHBjHUMNj5UlW2GynmnSgiJJEdNg9yW5C8lfoNRZrHGv5EqA== |
| | | dependencies: |
| | | "@types/yargs-parser" "*" |
| | | |
| | |
| | | estree-walker "^2.0.1" |
| | | source-map "^0.6.1" |
| | | |
| | | "@vue/compiler-core@3.0.4": |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.4.tgz#0122aca6eada4cb28b39ed930af917444755e330" |
| | | integrity sha512-snpMICsbWTZqBFnPB03qr4DtiSxVYfDF3DvbDSkN9Z9NTM8Chl8E/lYhKBSsvauq91DAWAh8PU3lr9vrLyQsug== |
| | | dependencies: |
| | | "@babel/parser" "^7.12.0" |
| | | "@babel/types" "^7.12.0" |
| | | "@vue/shared" "3.0.4" |
| | | estree-walker "^2.0.1" |
| | | source-map "^0.6.1" |
| | | |
| | | "@vue/compiler-dom@3.0.2": |
| | | version "3.0.2" |
| | | resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.2.tgz#1d40de04bcdf9aabb79fb6a802dd70a2f3c2992a" |
| | |
| | | dependencies: |
| | | "@vue/compiler-core" "3.0.3" |
| | | "@vue/shared" "3.0.3" |
| | | |
| | | "@vue/compiler-dom@3.0.4": |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.4.tgz#834fd4b15c5698cf9f4505c2bfbccca058a843eb" |
| | | integrity sha512-FOxbHBIkkGjYQeTz1DlXQjS1Ms8EPXQWsdTdTPeohoS0KzCz6RiOjiAG+jLtMi6Nr5GX2h0TlCvcnI8mcsicFQ== |
| | | dependencies: |
| | | "@vue/compiler-core" "3.0.4" |
| | | "@vue/shared" "3.0.4" |
| | | |
| | | "@vue/compiler-sfc@*", "@vue/compiler-sfc@^3.0.0-rc.5": |
| | | version "3.0.2" |
| | |
| | | postcss-selector-parser "^6.0.4" |
| | | source-map "^0.6.1" |
| | | |
| | | "@vue/compiler-sfc@^3.0.4": |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.0.4.tgz#2119fe1e68d2c268aafa20461c82c139a9adf8e0" |
| | | integrity sha512-brDn6HTuK6R3oBCjtMPPsIpyJEZFinlnxjtBXww/goFJOJBAU9CrsdegwyZItNnixCFUIg4CLv4Nj1Eg/eKlfg== |
| | | dependencies: |
| | | "@babel/parser" "^7.12.0" |
| | | "@babel/types" "^7.12.0" |
| | | "@vue/compiler-core" "3.0.4" |
| | | "@vue/compiler-dom" "3.0.4" |
| | | "@vue/compiler-ssr" "3.0.4" |
| | | "@vue/shared" "3.0.4" |
| | | consolidate "^0.16.0" |
| | | estree-walker "^2.0.1" |
| | | hash-sum "^2.0.0" |
| | | lru-cache "^5.1.1" |
| | | magic-string "^0.25.7" |
| | | merge-source-map "^1.1.0" |
| | | postcss "^7.0.32" |
| | | postcss-modules "^3.2.2" |
| | | postcss-selector-parser "^6.0.4" |
| | | source-map "^0.6.1" |
| | | |
| | | "@vue/compiler-ssr@3.0.2": |
| | | version "3.0.2" |
| | | resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.0.2.tgz#73af4d274a79bfcc72a996a9b45f1072e7deaa26" |
| | |
| | | "@vue/compiler-dom" "3.0.3" |
| | | "@vue/shared" "3.0.3" |
| | | |
| | | "@vue/compiler-ssr@3.0.4": |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.0.4.tgz#ccbd1f55734d51d1402fad825ac102002a7a07c7" |
| | | integrity sha512-4aYWQEL4+LS4+D44K9Z7xMOWMEjBsz4Li9nMcj2rxRQ35ewK6uFPodvs6ORP60iBDSkwUFZoldFlNemQlu1BFw== |
| | | dependencies: |
| | | "@vue/compiler-dom" "3.0.4" |
| | | "@vue/shared" "3.0.4" |
| | | |
| | | "@vue/reactivity@3.0.2": |
| | | version "3.0.2" |
| | | resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.2.tgz#42ed5af6025b494a5e69b05169fcddf04eebfe77" |
| | |
| | | integrity sha512-t39Qmc42MX7wJtf8L6tHlu17eP9Rc5w4aRnxpLHNWoaRxddv/7FBhWqusJ2Bwkk8ixFHOQeejcLMt5G469WYJw== |
| | | dependencies: |
| | | "@vue/shared" "3.0.3" |
| | | |
| | | "@vue/reactivity@3.0.4": |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.4.tgz#b6599dd8271a745960a03f05744ccf7991ba5d8d" |
| | | integrity sha512-AFTABrLhUYZY2on3ea9FxeXal7w3f6qIp9gT+/oG93H7dFTL5LvVnxygCopv7tvkIl/GSGQb/yK1D1gmXx1Pww== |
| | | dependencies: |
| | | "@vue/shared" "3.0.4" |
| | | |
| | | "@vue/runtime-core@3.0.2": |
| | | version "3.0.2" |
| | |
| | | "@vue/reactivity" "3.0.3" |
| | | "@vue/shared" "3.0.3" |
| | | |
| | | "@vue/runtime-core@3.0.4": |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.0.4.tgz#a5b9a001560b1fd8c01a43f68b764c555de7836c" |
| | | integrity sha512-qH9e4kqU7b3u1JewvLmGmoAGY+mnuBqz7aEKb2mhpEgwa1yFv496BRuUfMXXMCix3+TndUVMJ8jt41FSdNppwg== |
| | | dependencies: |
| | | "@vue/reactivity" "3.0.4" |
| | | "@vue/shared" "3.0.4" |
| | | |
| | | "@vue/runtime-dom@3.0.3": |
| | | version "3.0.3" |
| | | resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.0.3.tgz#5e3e5e5418b9defcac988d2be0cf65596fa2cc03" |
| | |
| | | dependencies: |
| | | "@vue/runtime-core" "3.0.3" |
| | | "@vue/shared" "3.0.3" |
| | | csstype "^2.6.8" |
| | | |
| | | "@vue/runtime-dom@3.0.4": |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.0.4.tgz#6f81aec545f24511d2c28a315aa3391420b69c68" |
| | | integrity sha512-BGIoiTSESzWUhN0Ofi2X/q+HN8f6IUFmUEyyBGKbmx7DTAJNZhFfjqsepfXQrM5IGeTfJLB1ZEVyroDQJNXq3g== |
| | | dependencies: |
| | | "@vue/runtime-core" "3.0.4" |
| | | "@vue/shared" "3.0.4" |
| | | csstype "^2.6.8" |
| | | |
| | | "@vue/runtime-dom@^3.0.0": |
| | |
| | | version "3.0.3" |
| | | resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.0.3.tgz#ef12ebff93a446df281e8a0fd765b5aea8e7745b" |
| | | integrity sha512-yGgkF7u4W0Dmwri9XdeY50kOowN4UIX7aBQ///jbxx37itpzVjK7QzvD3ltQtPfWaJDGBfssGL0wpAgwX9OJpQ== |
| | | |
| | | "@vue/shared@3.0.4": |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.0.4.tgz#6dc50f593bdfdeaa6183d1dbc15e2d45e7c6b8b3" |
| | | integrity sha512-Swfbz31AaMX48CpFl+YmIrqOH9MgJMTrltG9e26A4ZxYx9LjGuMV+41WnxFzS3Bc9nbrc6sDPM37G6nIT8NJSg== |
| | | |
| | | "@vuedx/analyze@0.2.4-0": |
| | | version "0.2.4-0" |
| | |
| | | exit-on-epipe "~1.0.1" |
| | | printj "~1.1.0" |
| | | |
| | | cross-env@^7.0.2: |
| | | version "7.0.2" |
| | | resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9" |
| | | integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== |
| | | cross-env@^7.0.3: |
| | | version "7.0.3" |
| | | resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" |
| | | integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== |
| | | dependencies: |
| | | cross-spawn "^7.0.1" |
| | | |
| | |
| | | resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.3.26.tgz#7b507044e97d5b03b01d4392c74ffeb9c177a83b" |
| | | integrity sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA== |
| | | |
| | | esbuild-register@^1.1.0: |
| | | version "1.1.0" |
| | | resolved "https://registry.npmjs.org/esbuild-register/-/esbuild-register-1.1.0.tgz#8ec1fbf6b84f0d7654b87eec04029a383dcb539d" |
| | | integrity sha512-A+KGHDc7me/ATyNqnVQKsHxt2A/ORVvV2gmukx5ZtVcy5HVf19QBbHdfdP5QHFA8rF/WHmcnDxaxewu+VUvUhQ== |
| | | esbuild-register@^1.1.1: |
| | | version "1.1.1" |
| | | resolved "https://registry.npmjs.org/esbuild-register/-/esbuild-register-1.1.1.tgz#7d50e87ac0b9000085d9e6d9a78e4c2223fcce83" |
| | | integrity sha512-hAPWuaUkPDLXCENc/AigJZaaDCvCkpmghRw8XPyT+rk08JHcIgUrmw1uabbUTfa6B6J9Wo2bFufb01JjbmzcfQ== |
| | | dependencies: |
| | | joycon "^2.2.5" |
| | | pirates "^4.0.1" |
| | | source-map-support "^0.5.19" |
| | | strip-json-comments "^3.1.1" |
| | | |
| | | esbuild@^0.7.17, esbuild@^0.7.19: |
| | | esbuild@^0.7.19: |
| | | version "0.7.22" |
| | | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.7.22.tgz#9149b903f8128b7c45a754046c24199d76bbe08e" |
| | | integrity sha512-B43SYg8LGWYTCv9Gs0RnuLNwjzpuWOoCaZHTWEDEf5AfrnuDMerPVMdCEu7xOdhFvQ+UqfP2MGU9lxEy0JzccA== |
| | |
| | | version "0.8.15" |
| | | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.15.tgz#cbc4d82a7fc4571d455233456e6fba83fd0364f1" |
| | | integrity sha512-mSaLo9t/oYtQE6FRUEdO47Pr8PisSPzHtgr+LcihIcjBEhbYwjT6WLCQ7noDoTBfIatBCw229rtmIwl9u9UQwg== |
| | | |
| | | esbuild@^0.8.17: |
| | | version "0.8.17" |
| | | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.17.tgz#1c16c6d5988dcfdcf27a7e1612b7fd05e1477c54" |
| | | integrity sha512-ReHap+Iyn5BQF0B8F3xrLwu+j57ri5uDUw2ej9XTPAuFDebYiWwRzBY4jhF610bklveXLbCGim/8/2wQKQlu1w== |
| | | |
| | | escalade@^3.1.1: |
| | | version "3.1.1" |
| | |
| | | resolved "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" |
| | | integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== |
| | | |
| | | esno@^0.2.4: |
| | | version "0.2.4" |
| | | resolved "https://registry.npmjs.org/esno/-/esno-0.2.4.tgz#b04a368181bc03e5d11d5147106bf0d0ac4f3a48" |
| | | integrity sha512-XlgsQe2va257kc1xsZg/X22fRyLVRNkCKEXjONoltA7HeXtmhrQ3n19all0eK0X6YRNE8X9qiVyWV0vMLZvY3w== |
| | | esno@^0.3.0: |
| | | version "0.3.0" |
| | | resolved "https://registry.npmjs.org/esno/-/esno-0.3.0.tgz#c818996bdaaf2deaf81413d6f45538ffa6e41b42" |
| | | integrity sha512-4sF/j8jruQv9jScU8tNkgoDFLjyGxTTB8bmjRmWHyNNygra3WS3X0U1Cc7GuOvfSEjn3NDS57P0LRnzgiupKJg== |
| | | dependencies: |
| | | esbuild "^0.7.17" |
| | | esbuild-register "^1.1.0" |
| | | esbuild "^0.8.17" |
| | | esbuild-register "^1.1.1" |
| | | esm "^3.2.25" |
| | | |
| | | espree@^6.2.1: |
| | |
| | | "@vue/runtime-dom" "3.0.3" |
| | | "@vue/shared" "3.0.3" |
| | | |
| | | vue@^3.0.4: |
| | | version "3.0.4" |
| | | resolved "https://registry.npmjs.org/vue/-/vue-3.0.4.tgz#872c65c143f5717bd5387c61613d9f55f4cc0f43" |
| | | integrity sha512-2o+AiQF8sAupyhbyl3oxVCl3WCwC/n5NI7VMM+gVQ231qvSB8eI7sCBloloqDJK6yA367EEtmRSeSCf4sxCC+A== |
| | | dependencies: |
| | | "@vue/compiler-dom" "3.0.4" |
| | | "@vue/runtime-dom" "3.0.4" |
| | | "@vue/shared" "3.0.4" |
| | | |
| | | vuex-module-decorators@^1.0.1: |
| | | version "1.0.1" |
| | | resolved "https://registry.npmjs.org/vuex-module-decorators/-/vuex-module-decorators-1.0.1.tgz#d34dafb5428a3636f1c26d3d014c15fc9659ccd0" |