├── .stylelintignore ├── src ├── components │ ├── Tree │ │ ├── style │ │ │ ├── index.ts │ │ │ └── index.less │ │ ├── index.ts │ │ └── src │ │ │ └── TreeIcon.ts │ ├── Menu │ │ ├── index.ts │ │ └── src │ │ │ ├── types.ts │ │ │ └── components │ │ │ ├── BasicMenuItem.vue │ │ │ └── MenuItemContent.vue │ ├── CodeEditor │ │ ├── src │ │ │ ├── typing.ts │ │ │ ├── json-preview │ │ │ │ └── JsonPreview.vue │ │ │ └── codemirror │ │ │ │ └── codeMirror.ts │ │ └── index.ts │ ├── Markdown │ │ ├── src │ │ │ ├── typing.ts │ │ │ └── getTheme.ts │ │ └── index.ts │ ├── Preview │ │ ├── index.ts │ │ └── src │ │ │ └── functional.ts │ ├── ContextMenu │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ ├── SimpleMenu │ │ ├── src │ │ │ ├── types.ts │ │ │ └── components │ │ │ │ ├── useSimpleMenuContext.ts │ │ │ │ └── types.ts │ │ └── index.ts │ ├── Time │ │ └── index.ts │ ├── CountTo │ │ └── index.ts │ ├── Qrcode │ │ ├── src │ │ │ ├── qrcodePlus.ts │ │ │ ├── toCanvas.ts │ │ │ └── typing.ts │ │ └── index.ts │ ├── CardList │ │ ├── index.ts │ │ └── src │ │ │ └── data.ts │ ├── Tinymce │ │ ├── index.ts │ │ └── src │ │ │ └── tinymce.ts │ ├── Authority │ │ └── index.ts │ ├── FlowChart │ │ ├── index.ts │ │ └── src │ │ │ ├── enum.ts │ │ │ ├── types.ts │ │ │ └── useFlowContext.ts │ ├── Form │ │ ├── src │ │ │ ├── types │ │ │ │ └── hooks.ts │ │ │ └── hooks │ │ │ │ ├── useComponentRegister.ts │ │ │ │ └── useFormContext.ts │ │ └── index.ts │ ├── Upload │ │ ├── index.ts │ │ └── src │ │ │ ├── ThumbUrl.vue │ │ │ └── helper.ts │ ├── VirtualScroll │ │ └── index.ts │ ├── ClickOutSide │ │ ├── index.ts │ │ └── src │ │ │ └── ClickOutSide.vue │ ├── StrengthMeter │ │ └── index.ts │ ├── Dropdown │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ ├── Loading │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ ├── Cropper │ │ ├── src │ │ │ └── typing.ts │ │ └── index.ts │ ├── Scrollbar │ │ ├── index.ts │ │ └── src │ │ │ └── types.d.ts │ ├── Icon │ │ └── index.ts │ ├── registerGlobComp.ts │ ├── Description │ │ ├── index.ts │ │ └── src │ │ │ └── useDescription.ts │ ├── Drawer │ │ └── index.ts │ ├── Table │ │ ├── src │ │ │ ├── types │ │ │ │ ├── componentType.ts │ │ │ │ └── tableAction.ts │ │ │ ├── components │ │ │ │ ├── EditTableHeaderIcon.vue │ │ │ │ ├── editable │ │ │ │ │ └── helper.ts │ │ │ │ └── settings │ │ │ │ │ └── RedoSetting.vue │ │ │ ├── hooks │ │ │ │ ├── useLoading.ts │ │ │ │ ├── useTableContext.ts │ │ │ │ └── useTableStyle.ts │ │ │ ├── const.ts │ │ │ └── componentMap.ts │ │ └── index.ts │ ├── CountDown │ │ ├── index.ts │ │ └── src │ │ │ └── useCountdown.ts │ ├── Application │ │ ├── src │ │ │ ├── search │ │ │ │ ├── AppSearchKeyItem.vue │ │ │ │ └── AppSearch.vue │ │ │ └── useAppContext.ts │ │ └── index.ts │ ├── Verify │ │ ├── src │ │ │ └── typing.ts │ │ └── index.ts │ ├── Modal │ │ ├── index.ts │ │ └── src │ │ │ ├── hooks │ │ │ └── useModalContext.ts │ │ │ └── components │ │ │ ├── ModalHeader.vue │ │ │ ├── Modal.tsx │ │ │ └── ModalFooter.vue │ ├── Page │ │ └── index.ts │ ├── Basic │ │ └── index.ts │ ├── Excel │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ ├── Button │ │ ├── index.ts │ │ └── src │ │ │ └── props.ts │ └── Container │ │ ├── index.ts │ │ └── src │ │ ├── typing.ts │ │ └── collapse │ │ └── CollapseHeader.vue ├── views │ ├── sys │ │ ├── exception │ │ │ └── index.ts │ │ ├── iframe │ │ │ └── FrameBlank.vue │ │ ├── lock │ │ │ └── index.vue │ │ ├── redirect │ │ │ └── index.vue │ │ ├── error-log │ │ │ └── DetailModal.vue │ │ └── login │ │ │ ├── LoginFormTitle.vue │ │ │ └── QrCodeForm.vue │ ├── demo │ │ ├── feat │ │ │ ├── breadcrumb │ │ │ │ ├── FlatListDetail.vue │ │ │ │ ├── ChildrenListDetail.vue │ │ │ │ ├── FlatList.vue │ │ │ │ └── ChildrenList.vue │ │ │ ├── request-demo │ │ │ │ └── index.vue │ │ │ ├── tabs │ │ │ │ └── TabDetail.vue │ │ │ ├── tab-params │ │ │ │ └── index.vue │ │ │ ├── ripple │ │ │ │ └── index.vue │ │ │ ├── watermark │ │ │ │ └── index.vue │ │ │ └── img-preview │ │ │ │ └── index.vue │ │ ├── main-out │ │ │ └── index.vue │ │ ├── level │ │ │ ├── Menu12.vue │ │ │ ├── Menu111.vue │ │ │ └── Menu2.vue │ │ ├── page │ │ │ └── list │ │ │ │ ├── card │ │ │ │ └── data.tsx │ │ │ │ ├── basic │ │ │ │ └── data.tsx │ │ │ │ └── search │ │ │ │ └── data.tsx │ │ ├── comp │ │ │ ├── drawer │ │ │ │ ├── Drawer1.vue │ │ │ │ ├── Drawer5.vue │ │ │ │ ├── Drawer2.vue │ │ │ │ └── Drawer3.vue │ │ │ ├── modal │ │ │ │ ├── Modal3.vue │ │ │ │ └── Modal2.vue │ │ │ ├── flow-chart │ │ │ │ └── index.vue │ │ │ ├── lazy │ │ │ │ └── TargetContent.vue │ │ │ ├── verify │ │ │ │ └── Rotate.vue │ │ │ ├── strength-meter │ │ │ │ └── index.vue │ │ │ ├── scroll │ │ │ │ └── index.vue │ │ │ └── card-list │ │ │ │ └── index.vue │ │ ├── permission │ │ │ ├── front │ │ │ │ ├── AuthPageA.vue │ │ │ │ └── AuthPageB.vue │ │ │ └── CurrentPermissionMode.vue │ │ ├── editor │ │ │ └── tinymce │ │ │ │ └── index.vue │ │ ├── table │ │ │ ├── MergeHeader.vue │ │ │ └── MultipleHeader.vue │ │ ├── excel │ │ │ └── ArrayExport.vue │ │ └── system │ │ │ ├── password │ │ │ └── pwd.data.ts │ │ │ └── account │ │ │ └── DeptTree.vue │ └── dashboard │ │ ├── analysis │ │ ├── components │ │ │ ├── props.ts │ │ │ └── SiteAnalysis.vue │ │ ├── data.ts │ │ └── index.vue │ │ └── workbench │ │ └── components │ │ ├── QuickNav.vue │ │ ├── DynamicInfo.vue │ │ └── ProjectCard.vue ├── design │ ├── config.less │ ├── transition │ │ ├── index.less │ │ ├── base.less │ │ ├── scale.less │ │ ├── zoom.less │ │ ├── slide.less │ │ └── scroll.less │ ├── ant │ │ └── input.less │ ├── var │ │ ├── index.less │ │ ├── easing.less │ │ └── breakpoint.less │ ├── index.less │ └── public.less ├── assets │ ├── images │ │ ├── demo.png │ │ ├── logo.png │ │ └── header.jpg │ └── svg │ │ └── preview │ │ ├── resume.svg │ │ └── unscale.svg ├── locales │ └── lang │ │ ├── en │ │ ├── routes │ │ │ ├── basic.ts │ │ │ └── dashboard.ts │ │ └── common.ts │ │ ├── zh-CN │ │ ├── routes │ │ │ ├── basic.ts │ │ │ └── dashboard.ts │ │ ├── antdLocale │ │ │ └── DatePicker.ts │ │ └── common.ts │ │ ├── zh_CN.ts │ │ └── en.ts ├── api │ ├── sys │ │ ├── model │ │ │ ├── uploadModel.ts │ │ │ ├── menuModel.ts │ │ │ └── userModel.ts │ │ ├── menu.ts │ │ └── upload.ts │ ├── demo │ │ ├── model │ │ │ ├── accountModel.ts │ │ │ ├── areaModel.ts │ │ │ ├── optionsModel.ts │ │ │ └── tableModel.ts │ │ ├── error.ts │ │ ├── cascader.ts │ │ ├── tree.ts │ │ ├── select.ts │ │ ├── table.ts │ │ └── account.ts │ └── model │ │ └── baseModel.ts ├── enums │ ├── roleEnum.ts │ ├── pageEnum.ts │ ├── sizeEnum.ts │ ├── exceptionEnum.ts │ ├── breakpointEnum.ts │ ├── httpEnum.ts │ ├── cacheEnum.ts │ ├── menuEnum.ts │ └── appEnum.ts ├── store │ └── index.ts ├── logics │ ├── theme │ │ ├── updateGrayMode.ts │ │ ├── updateColorWeak.ts │ │ ├── util.ts │ │ ├── index.ts │ │ └── dark.ts │ └── mitt │ │ └── routeChange.ts ├── settings │ ├── siteSetting.ts │ ├── encryptionSetting.ts │ ├── localeSetting.ts │ └── designSetting.ts ├── utils │ ├── log.ts │ ├── dateUtil.ts │ ├── http │ │ └── axios │ │ │ └── axiosRetry.ts │ ├── uuid.ts │ ├── propTypes.ts │ ├── auth │ │ └── index.ts │ ├── helper │ │ └── tsxHelper.tsx │ ├── lib │ │ └── echarts.ts │ └── cache │ │ └── index.ts ├── hooks │ ├── web │ │ ├── useAppInject.ts │ │ ├── useContextMenu.ts │ │ ├── useSortable.ts │ │ ├── useDesign.ts │ │ ├── useFullContent.ts │ │ ├── usePagination.ts │ │ ├── useTitle.ts │ │ └── useScript.ts │ ├── core │ │ ├── onMountedOrActivated.ts │ │ ├── useRefs.ts │ │ ├── useLockFn.ts │ │ ├── useContext.ts │ │ └── useTimeout.ts │ ├── component │ │ └── usePageContext.ts │ ├── event │ │ └── useWindowSizeFn.ts │ └── setting │ │ ├── useMultipleTabSetting.ts │ │ ├── index.ts │ │ └── useTransitionSetting.ts ├── directives │ ├── index.ts │ ├── ripple │ │ └── index.less │ ├── permission.ts │ └── repeatClick.ts ├── layouts │ ├── default │ │ ├── header │ │ │ └── components │ │ │ │ ├── index.ts │ │ │ │ ├── user-dropdown │ │ │ │ └── DropMenuItem.vue │ │ │ │ └── FullScreen.vue │ │ ├── tabs │ │ │ ├── types.ts │ │ │ └── components │ │ │ │ └── TabRedo.vue │ │ ├── setting │ │ │ ├── components │ │ │ │ └── index.ts │ │ │ └── index.vue │ │ ├── content │ │ │ └── useContentContext.ts │ │ └── trigger │ │ │ ├── SiderTrigger.vue │ │ │ ├── index.vue │ │ │ └── HeaderTrigger.vue │ ├── iframe │ │ └── index.vue │ └── page │ │ └── transition.ts ├── router │ ├── routes │ │ ├── mainOut.ts │ │ └── modules │ │ │ ├── demo │ │ │ ├── flow.ts │ │ │ └── setup.ts │ │ │ ├── about.ts │ │ │ └── dashboard.ts │ ├── constant.ts │ └── guard │ │ └── stateGuard.ts └── App.vue ├── public ├── favicon.ico └── resource │ ├── img │ ├── logo.png │ ├── pwa-192x192.png │ └── pwa-512x512.png │ └── tinymce │ └── skins │ └── ui │ ├── oxide │ ├── fonts │ │ └── tinymce-mobile.woff │ └── content.mobile.min.css │ └── oxide-dark │ └── content.mobile.min.css ├── postcss.config.js ├── .prettierignore ├── .gitpod.yml ├── .husky ├── commit-msg ├── pre-commit └── common.sh ├── .env ├── types ├── utils.d.ts ├── module.d.ts ├── index.d.ts └── store.d.ts ├── .eslintignore ├── tests └── server │ ├── nodemon.json │ ├── utils.ts │ ├── README.md │ ├── tsconfig.json │ ├── controller │ ├── UserController.ts │ └── FileController.ts │ ├── ecosystem.config.js │ ├── routes.ts │ ├── service │ └── UserService.ts │ └── package.json ├── prettier.config.js ├── .vscode ├── launch.json └── extensions.json ├── .github ├── contributing.md ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 3-bug-cn.md │ └── 2-feature.md └── workflows │ ├── issue-close-require.yml │ ├── release.yml │ └── issue-labeled.yml ├── .editorconfig ├── .gitignore ├── mock ├── _createProductionServer.ts └── demo │ ├── select-demo.ts │ └── tree-demo.ts ├── .env.development ├── .env.production ├── .env.test ├── LICENSE └── tsconfig.json /.stylelintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /public/* 3 | public/* 4 | -------------------------------------------------------------------------------- /src/components/Tree/style/index.ts: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | -------------------------------------------------------------------------------- /src/views/sys/exception/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Exception } from './Exception.vue'; 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijinglei/vue-vben-admin/main/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/design/config.less: -------------------------------------------------------------------------------- 1 | @import (reference) 'color.less'; 2 | @import (reference) 'var/index.less'; 3 | -------------------------------------------------------------------------------- /src/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | import BasicMenu from './src/BasicMenu.vue'; 2 | 3 | export { BasicMenu }; 4 | -------------------------------------------------------------------------------- /src/assets/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijinglei/vue-vben-admin/main/src/assets/images/demo.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijinglei/vue-vben-admin/main/src/assets/images/logo.png -------------------------------------------------------------------------------- /public/resource/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijinglei/vue-vben-admin/main/public/resource/img/logo.png -------------------------------------------------------------------------------- /src/assets/images/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijinglei/vue-vben-admin/main/src/assets/images/header.jpg -------------------------------------------------------------------------------- /src/locales/lang/en/routes/basic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: 'Login', 3 | errorLogList: 'Error Log', 4 | }; 5 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/routes/basic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: '登录', 3 | errorLogList: '错误日志列表', 4 | }; 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | /node_modules/** 5 | 6 | **/*.svg 7 | **/*.sh 8 | 9 | /public/* 10 | -------------------------------------------------------------------------------- /public/resource/img/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijinglei/vue-vben-admin/main/public/resource/img/pwa-192x192.png -------------------------------------------------------------------------------- /public/resource/img/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijinglei/vue-vben-admin/main/public/resource/img/pwa-512x512.png -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | ports: 2 | - port: 3344 3 | onOpen: open-preview 4 | tasks: 5 | - init: pnpm install 6 | command: pnpm run dev 7 | -------------------------------------------------------------------------------- /src/api/sys/model/uploadModel.ts: -------------------------------------------------------------------------------- 1 | export interface UploadApiResult { 2 | message: string; 3 | code: number; 4 | url: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // super admin 3 | SUPER = 'super', 4 | 5 | // tester 6 | TEST = 'test', 7 | } 8 | -------------------------------------------------------------------------------- /src/components/CodeEditor/src/typing.ts: -------------------------------------------------------------------------------- 1 | export enum MODE { 2 | JSON = 'application/json', 3 | HTML = 'htmlmixed', 4 | JS = 'javascript', 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Markdown/src/typing.ts: -------------------------------------------------------------------------------- 1 | import Vditor from 'vditor'; 2 | export interface MarkDownActionType { 3 | getVditor: () => Vditor; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/Preview/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImagePreview } from './src/Preview.vue'; 2 | export { createImgPreview } from './src/functional'; 3 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=./_/husky.sh 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | npx --no-install commitlint --edit "$1" 7 | -------------------------------------------------------------------------------- /src/components/ContextMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { createContextMenu, destroyContextMenu } from './src/createContextMenu'; 2 | 3 | export * from './src/typing'; 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # port 2 | VITE_PORT = 3100 3 | 4 | # spa-title 5 | VITE_GLOB_APP_TITLE = Vben Admin 6 | 7 | # spa shortname 8 | VITE_GLOB_APP_SHORT_NAME = vue_vben_admin 9 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface MenuState { 2 | activeName: string; 3 | openNames: string[]; 4 | activeSubMenuNames: string[]; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Time/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils/index'; 2 | import time from './src/Time.vue'; 3 | 4 | export const Time = withInstall(time); 5 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/routes/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dashboard: 'Dashboard', 3 | about: '关于', 4 | workbench: '工作台', 5 | analysis: '分析页', 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SimpleMenu } from './src/SimpleMenu.vue'; 2 | export { default as SimpleMenuTag } from './src/SimpleMenuTag.vue'; 3 | -------------------------------------------------------------------------------- /types/utils.d.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue'; 2 | 3 | export type DynamicProps = { 4 | [P in keyof T]: Ref | T[P] | ComputedRef; 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/CountTo/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import countTo from './src/CountTo.vue'; 3 | 4 | export const CountTo = withInstall(countTo); 5 | -------------------------------------------------------------------------------- /src/components/Qrcode/src/qrcodePlus.ts: -------------------------------------------------------------------------------- 1 | // 参考 qr-code-with-logo 进行ts版本修改 2 | import { toCanvas } from './toCanvas'; 3 | export * from './typing'; 4 | export { toCanvas }; 5 | -------------------------------------------------------------------------------- /src/locales/lang/en/routes/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dashboard: 'Dashboard', 3 | about: 'About', 4 | workbench: 'Workbench', 5 | analysis: 'Analysis', 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/CardList/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import cardList from './src/CardList.vue'; 3 | 4 | export const CardList = withInstall(cardList); 5 | -------------------------------------------------------------------------------- /src/components/Tinymce/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils/index'; 2 | import tinymce from './src/Editor.vue'; 3 | 4 | export const Tinymce = withInstall(tinymce); 5 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijinglei/vue-vben-admin/main/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /src/components/Authority/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import authority from './src/Authority.vue'; 3 | 4 | export const Authority = withInstall(authority); 5 | -------------------------------------------------------------------------------- /src/components/FlowChart/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import flowChart from './src/FlowChart.vue'; 3 | 4 | export const FlowChart = withInstall(flowChart); 5 | -------------------------------------------------------------------------------- /src/components/Form/src/types/hooks.ts: -------------------------------------------------------------------------------- 1 | export interface AdvanceState { 2 | isAdvanced: boolean; 3 | hideAdvanceBtn: boolean; 4 | isLoad: boolean; 5 | actionSpan: number; 6 | } 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | *.sh 3 | node_modules 4 | *.md 5 | *.woff 6 | *.ttf 7 | .vscode 8 | .idea 9 | dist 10 | /public 11 | /docs 12 | .husky 13 | .local 14 | /bin 15 | Dockerfile 16 | -------------------------------------------------------------------------------- /src/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import basicUpload from './src/BasicUpload.vue'; 3 | 4 | export const BasicUpload = withInstall(basicUpload); 5 | -------------------------------------------------------------------------------- /src/components/VirtualScroll/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils/index'; 2 | import vScroll from './src/VirtualScroll.vue'; 3 | 4 | export const VScroll = withInstall(vScroll); 5 | -------------------------------------------------------------------------------- /src/api/demo/model/accountModel.ts: -------------------------------------------------------------------------------- 1 | export interface GetAccountInfoModel { 2 | email: string; 3 | name: string; 4 | introduction: string; 5 | phone: string; 6 | address: string; 7 | } 8 | -------------------------------------------------------------------------------- /tests/server/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "exec": "ts-node -r tsconfig-paths/register index.ts", 5 | "events": { 6 | "restart": "clear" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/ClickOutSide/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import clickOutSide from './src/ClickOutSide.vue'; 3 | 4 | export const ClickOutSide = withInstall(clickOutSide); 5 | -------------------------------------------------------------------------------- /src/components/Qrcode/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import qrCode from './src/Qrcode.vue'; 3 | 4 | export const QrCode = withInstall(qrCode); 5 | export * from './src/typing'; 6 | -------------------------------------------------------------------------------- /tests/server/utils.ts: -------------------------------------------------------------------------------- 1 | export class Result { 2 | static success(data: any) { 3 | return { 4 | code: 0, 5 | success: true, 6 | result: data, 7 | }; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/StrengthMeter/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import strengthMeter from './src/StrengthMeter.vue'; 3 | 4 | export const StrengthMeter = withInstall(strengthMeter); 5 | -------------------------------------------------------------------------------- /src/components/Dropdown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import dropdown from './src/Dropdown.vue'; 3 | 4 | export * from './src/typing'; 5 | export const Dropdown = withInstall(dropdown); 6 | -------------------------------------------------------------------------------- /src/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | import Loading from './src/Loading.vue'; 2 | 3 | export { Loading }; 4 | export { useLoading } from './src/useLoading'; 5 | export { createLoading } from './src/createLoading'; 6 | -------------------------------------------------------------------------------- /src/api/model/baseModel.ts: -------------------------------------------------------------------------------- 1 | export interface BasicPageParams { 2 | page: number; 3 | pageSize: number; 4 | } 5 | 6 | export interface BasicFetchResult { 7 | items: T[]; 8 | total: number; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Cropper/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type Cropper from 'cropperjs'; 2 | 3 | export interface CropendResult { 4 | imgBase64: string; 5 | imgInfo: Cropper.Data; 6 | } 7 | 8 | export type { Cropper }; 9 | -------------------------------------------------------------------------------- /src/components/Scrollbar/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * copy from element-ui 3 | */ 4 | 5 | import Scrollbar from './src/Scrollbar.vue'; 6 | 7 | export { Scrollbar }; 8 | export type { ScrollbarType } from './src/types'; 9 | -------------------------------------------------------------------------------- /src/views/demo/feat/breadcrumb/FlatListDetail.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | [ -n "$CI" ] && exit 0 6 | 7 | # Format and submit code according to lintstagedrc.js configuration 8 | npm run lint:lint-staged 9 | -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command_exists () { 3 | command -v "$1" >/dev/null 2>&1 4 | } 5 | 6 | # Workaround for Windows 10, Git Bash and Yarn 7 | if command_exists winpty && test -t 1; then 8 | exec < /dev/tty 9 | fi 10 | -------------------------------------------------------------------------------- /src/components/Icon/index.ts: -------------------------------------------------------------------------------- 1 | import Icon from './src/Icon.vue'; 2 | import SvgIcon from './src/SvgIcon.vue'; 3 | import IconPicker from './src/IconPicker.vue'; 4 | 5 | export { Icon, IconPicker, SvgIcon }; 6 | 7 | export default Icon; 8 | -------------------------------------------------------------------------------- /src/components/Tree/index.ts: -------------------------------------------------------------------------------- 1 | import BasicTree from './src/BasicTree.vue'; 2 | import './style'; 3 | 4 | export { BasicTree }; 5 | export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; 6 | export * from './src/types/tree'; 7 | -------------------------------------------------------------------------------- /src/components/Dropdown/src/typing.ts: -------------------------------------------------------------------------------- 1 | export interface DropMenu { 2 | onClick?: Fn; 3 | to?: string; 4 | icon?: string; 5 | event: string | number; 6 | text: string; 7 | disabled?: boolean; 8 | divider?: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /src/views/sys/iframe/FrameBlank.vue: -------------------------------------------------------------------------------- 1 | 4 | 10 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /src/components/registerGlobComp.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { Button } from './Button'; 3 | import { Input, Layout } from 'ant-design-vue'; 4 | 5 | export function registerGlobComp(app: App) { 6 | app.use(Input).use(Button).use(Layout); 7 | } 8 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | semi: true, 4 | vueIndentScriptAndStyle: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | proseWrap: 'never', 8 | htmlWhitespaceSensitivity: 'strict', 9 | endOfLine: 'auto', 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/Description/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import description from './src/Description.vue'; 3 | 4 | export * from './src/typing'; 5 | export { useDescription } from './src/useDescription'; 6 | export const Description = withInstall(description); 7 | -------------------------------------------------------------------------------- /src/components/Drawer/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import basicDrawer from './src/BasicDrawer.vue'; 3 | 4 | export const BasicDrawer = withInstall(basicDrawer); 5 | export * from './src/typing'; 6 | export { useDrawer, useDrawerInner } from './src/useDrawer'; 7 | -------------------------------------------------------------------------------- /src/components/FlowChart/src/enum.ts: -------------------------------------------------------------------------------- 1 | export enum ToolbarTypeEnum { 2 | ZOOM_IN = 'zoomIn', 3 | ZOOM_OUT = 'zoomOut', 4 | RESET_ZOOM = 'resetZoom', 5 | 6 | UNDO = 'undo', 7 | REDO = 'redo', 8 | 9 | SNAPSHOT = 'snapshot', 10 | VIEW_DATA = 'viewData', 11 | } 12 | -------------------------------------------------------------------------------- /src/logics/theme/updateGrayMode.ts: -------------------------------------------------------------------------------- 1 | import { toggleClass } from './util'; 2 | 3 | /** 4 | * Change project gray mode status 5 | * @param gray 6 | */ 7 | export function updateGrayMode(gray: boolean) { 8 | toggleClass(gray, 'gray-mode', document.documentElement); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Loading/src/typing.ts: -------------------------------------------------------------------------------- 1 | import { SizeEnum } from '/@/enums/sizeEnum'; 2 | 3 | export interface LoadingProps { 4 | tip: string; 5 | size: SizeEnum; 6 | absolute: boolean; 7 | loading: boolean; 8 | background: string; 9 | theme: 'dark' | 'light'; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Table/src/types/componentType.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'Input' 3 | | 'InputNumber' 4 | | 'Select' 5 | | 'ApiSelect' 6 | | 'AutoComplete' 7 | | 'ApiTreeSelect' 8 | | 'Checkbox' 9 | | 'Switch' 10 | | 'DatePicker' 11 | | 'TimePicker'; 12 | -------------------------------------------------------------------------------- /src/settings/siteSetting.ts: -------------------------------------------------------------------------------- 1 | // github repo url 2 | export const GITHUB_URL = 'https://github.com/anncwb/vue-vben-admin'; 3 | 4 | // vue-vben-admin-next-doc 5 | export const DOC_URL = 'https://vvbin.cn/doc-next/'; 6 | 7 | // site url 8 | export const SITE_URL = 'https://vvbin.cn/next/'; 9 | -------------------------------------------------------------------------------- /src/views/demo/main-out/index.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/api/demo/model/areaModel.ts: -------------------------------------------------------------------------------- 1 | export interface AreaModel { 2 | id: string; 3 | code: string; 4 | parentCode: string; 5 | name: string; 6 | levelType: number; 7 | [key: string]: string | number; 8 | } 9 | 10 | export interface AreaParams { 11 | parentCode: string; 12 | } 13 | -------------------------------------------------------------------------------- /tests/server/README.md: -------------------------------------------------------------------------------- 1 | # Test Server 2 | 3 | It is used to start the test interface service, which can test the upload, websocket, login and other interfaces. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | 9 | cd ./test/server 10 | 11 | pnpm install 12 | 13 | pnpm run start 14 | 15 | ``` 16 | -------------------------------------------------------------------------------- /src/api/demo/error.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | 3 | enum Api { 4 | // The address does not exist 5 | Error = '/error', 6 | } 7 | 8 | /** 9 | * @description: Trigger ajax error 10 | */ 11 | 12 | export const fireErrorApi = () => defHttp.get({ url: Api.Error }); 13 | -------------------------------------------------------------------------------- /src/enums/pageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum PageEnum { 2 | // basic login path 3 | BASE_LOGIN = '/login', 4 | // basic home path 5 | BASE_HOME = '/dashboard', 6 | // error page path 7 | ERROR_PAGE = '/exception', 8 | // error log page path 9 | ERROR_LOG_PAGE = '/error-log/list', 10 | } 11 | -------------------------------------------------------------------------------- /src/components/CountDown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import countButton from './src/CountButton.vue'; 3 | import countdownInput from './src/CountdownInput.vue'; 4 | 5 | export const CountdownInput = withInstall(countdownInput); 6 | export const CountButton = withInstall(countButton); 7 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const projectName = import.meta.env.VITE_GLOB_APP_TITLE; 2 | 3 | export function warn(message: string) { 4 | console.warn(`[${projectName} warn]:${message}`); 5 | } 6 | 7 | export function error(message: string) { 8 | throw new Error(`[${projectName} error]:${message}`); 9 | } 10 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN.ts: -------------------------------------------------------------------------------- 1 | import { genMessage } from '../helper'; 2 | import antdLocale from 'ant-design-vue/es/locale/zh_CN'; 3 | 4 | const modules = import.meta.globEager('./zh-CN/**/*.ts'); 5 | export default { 6 | message: { 7 | ...genMessage(modules, 'zh-CN'), 8 | antdLocale, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/Application/src/search/AppSearchKeyItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /src/logics/theme/updateColorWeak.ts: -------------------------------------------------------------------------------- 1 | import { toggleClass } from './util'; 2 | 3 | /** 4 | * Change the status of the project's color weakness mode 5 | * @param colorWeak 6 | */ 7 | export function updateColorWeak(colorWeak: boolean) { 8 | toggleClass(colorWeak, 'color-weak', document.documentElement); 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Launch Chrome", 8 | "url": "http://localhost:3100", 9 | "webRoot": "${workspaceFolder}/src", 10 | "sourceMaps": true 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Markdown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import markDown from './src/Markdown.vue'; 3 | import markDownViewer from './src/MarkdownViewer.vue'; 4 | 5 | export const MarkDown = withInstall(markDown); 6 | export const MarkdownViewer = withInstall(markDownViewer); 7 | export * from './src/typing'; 8 | -------------------------------------------------------------------------------- /src/hooks/web/useAppInject.ts: -------------------------------------------------------------------------------- 1 | import { useAppProviderContext } from '/@/components/Application'; 2 | import { computed, unref } from 'vue'; 3 | 4 | export function useAppInject() { 5 | const values = useAppProviderContext(); 6 | 7 | return { 8 | getIsMobile: computed(() => unref(values.isMobile)), 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | 1. Make sure you put things in the right category! 4 | 2. Always add your items to the end of a list. To be fair, the order is first-come-first-serve. 5 | 3. If you think something belongs in the wrong category, or think there needs to be a new category, feel free to edit things too. 6 | -------------------------------------------------------------------------------- /src/components/Cropper/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import cropperImage from './src/Cropper.vue'; 3 | import avatarCropper from './src/CropperAvatar.vue'; 4 | 5 | export * from './src/typing'; 6 | export const CropperImage = withInstall(cropperImage); 7 | export const CropperAvatar = withInstall(avatarCropper); 8 | -------------------------------------------------------------------------------- /src/components/Verify/src/typing.ts: -------------------------------------------------------------------------------- 1 | export interface DragVerifyActionType { 2 | resume: () => void; 3 | } 4 | 5 | export interface PassingData { 6 | isPassing: boolean; 7 | time: number; 8 | } 9 | 10 | export interface MoveData { 11 | event: MouseEvent | TouchEvent; 12 | moveDistance: number; 13 | moveX: number; 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "vue.volar", 4 | "dbaeumer.vscode-eslint", 5 | "stylelint.vscode-stylelint", 6 | "esbenp.prettier-vscode", 7 | "mrmlnc.vscode-less", 8 | "lokalise.i18n-ally", 9 | "antfu.iconify", 10 | "mikestead.dotenv", 11 | "heybourn.headwind" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/api/demo/cascader.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel'; 3 | 4 | enum Api { 5 | AREA_RECORD = '/cascader/getAreaRecord', 6 | } 7 | 8 | export const areaRecord = (data: AreaParams) => 9 | defHttp.post({ url: Api.AREA_RECORD, data }); 10 | -------------------------------------------------------------------------------- /src/components/CodeEditor/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import codeEditor from './src/CodeEditor.vue'; 3 | import jsonPreview from './src/json-preview/JsonPreview.vue'; 4 | 5 | export const CodeEditor = withInstall(codeEditor); 6 | export const JsonPreview = withInstall(jsonPreview); 7 | 8 | export * from './src/typing'; 9 | -------------------------------------------------------------------------------- /src/design/transition/index.less: -------------------------------------------------------------------------------- 1 | @import './base.less'; 2 | @import './fade.less'; 3 | @import './scale.less'; 4 | @import './slide.less'; 5 | @import './scroll.less'; 6 | @import './zoom.less'; 7 | 8 | .collapse-transition { 9 | transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/demo/tree.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | 3 | enum Api { 4 | TREE_OPTIONS_LIST = '/tree/getDemoOptions', 5 | } 6 | 7 | /** 8 | * @description: Get sample options value 9 | */ 10 | export const treeOptionsListApi = (params?: Recordable) => 11 | defHttp.get({ url: Api.TREE_OPTIONS_LIST, params }); 12 | -------------------------------------------------------------------------------- /src/components/Verify/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils/index'; 2 | import basicDragVerify from './src/DragVerify.vue'; 3 | import rotateDragVerify from './src/ImgRotate.vue'; 4 | 5 | export const BasicDragVerify = withInstall(basicDragVerify); 6 | export const RotateDragVerify = withInstall(rotateDragVerify); 7 | export * from './src/typing'; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset=utf-8 5 | end_of_line=lf 6 | insert_final_newline=true 7 | indent_style=space 8 | indent_size=2 9 | max_line_length = 100 10 | 11 | [*.{yml,yaml,json}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [Makefile] 19 | indent_style = tab 20 | -------------------------------------------------------------------------------- /src/locales/lang/en.ts: -------------------------------------------------------------------------------- 1 | import { genMessage } from '../helper'; 2 | import antdLocale from 'ant-design-vue/es/locale/en_US'; 3 | 4 | const modules = import.meta.globEager('./en/**/*.ts'); 5 | export default { 6 | message: { 7 | ...genMessage(modules, 'en'), 8 | antdLocale, 9 | }, 10 | dateLocale: null, 11 | dateLocaleName: 'en', 12 | }; 13 | -------------------------------------------------------------------------------- /src/views/demo/level/Menu12.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | -------------------------------------------------------------------------------- /src/views/demo/level/Menu111.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | -------------------------------------------------------------------------------- /src/components/CodeEditor/src/json-preview/JsonPreview.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import './src/index.less'; 3 | import basicModal from './src/BasicModal.vue'; 4 | 5 | export const BasicModal = withInstall(basicModal); 6 | export { useModalContext } from './src/hooks/useModalContext'; 7 | export { useModal, useModalInner } from './src/hooks/useModal'; 8 | export * from './src/typing'; 9 | -------------------------------------------------------------------------------- /src/components/Page/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | 3 | import pageFooter from './src/PageFooter.vue'; 4 | import pageWrapper from './src/PageWrapper.vue'; 5 | 6 | export const PageFooter = withInstall(pageFooter); 7 | export const PageWrapper = withInstall(pageWrapper); 8 | 9 | export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight'; 10 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure and register global directives 3 | */ 4 | import type { App } from 'vue'; 5 | import { setupPermissionDirective } from './permission'; 6 | import { setupLoadingDirective } from './loading'; 7 | 8 | export function setupGlobDirectives(app: App) { 9 | setupPermissionDirective(app); 10 | setupLoadingDirective(app); 11 | } 12 | -------------------------------------------------------------------------------- /src/views/demo/feat/breadcrumb/ChildrenListDetail.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /src/components/Basic/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import basicArrow from './src/BasicArrow.vue'; 3 | import basicTitle from './src/BasicTitle.vue'; 4 | import basicHelp from './src/BasicHelp.vue'; 5 | 6 | export const BasicArrow = withInstall(basicArrow); 7 | export const BasicTitle = withInstall(basicTitle); 8 | export const BasicHelp = withInstall(basicHelp); 9 | -------------------------------------------------------------------------------- /src/components/Qrcode/src/toCanvas.ts: -------------------------------------------------------------------------------- 1 | import { renderQrCode } from './drawCanvas'; 2 | import { drawLogo } from './drawLogo'; 3 | import { RenderQrCodeParams } from './typing'; 4 | export const toCanvas = (options: RenderQrCodeParams) => { 5 | return renderQrCode(options) 6 | .then(() => { 7 | return options; 8 | }) 9 | .then(drawLogo) as Promise; 10 | }; 11 | -------------------------------------------------------------------------------- /src/design/transition/base.less: -------------------------------------------------------------------------------- 1 | .transition-default() { 2 | &-enter-active, 3 | &-leave-active { 4 | transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; 5 | } 6 | 7 | &-move { 8 | transition: transform 0.4s; 9 | } 10 | } 11 | 12 | .expand-transition { 13 | .transition-default(); 14 | } 15 | 16 | .expand-x-transition { 17 | .transition-default(); 18 | } 19 | -------------------------------------------------------------------------------- /src/views/demo/level/Menu2.vue: -------------------------------------------------------------------------------- 1 | 8 | 16 | -------------------------------------------------------------------------------- /src/api/sys/menu.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { getMenuListResultModel } from './model/menuModel'; 3 | 4 | enum Api { 5 | GetMenuList = '/getMenuList', 6 | } 7 | 8 | /** 9 | * @description: Get user menu based on id 10 | */ 11 | 12 | export const getMenuList = () => { 13 | return defHttp.get({ url: Api.GetMenuList }); 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/Excel/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import impExcel from './src/ImportExcel.vue'; 3 | import expExcelModal from './src/ExportExcelModal.vue'; 4 | 5 | export const ImpExcel = withInstall(impExcel); 6 | export const ExpExcelModal = withInstall(expExcelModal); 7 | export * from './src/typing'; 8 | export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel'; 9 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/antdLocale/DatePicker.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | lang: { 3 | shortWeekDays: ['一', '二', '三', '四', '五', '六', '日'], 4 | shortMonths: [ 5 | '1月', 6 | '2月', 7 | '3月', 8 | '4月', 9 | '5月', 10 | '6月', 11 | '7月', 12 | '8月', 13 | '9月', 14 | '10月', 15 | '11月', 16 | '12月', 17 | ], 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/views/demo/page/list/card/data.tsx: -------------------------------------------------------------------------------- 1 | export const cardList = (() => { 2 | const result: any[] = []; 3 | for (let i = 0; i < 12; i++) { 4 | result.push({ 5 | title: 'Vben Admin', 6 | icon: 'logos:vue', 7 | color: '#1890ff', 8 | active: '100', 9 | new: '1,799', 10 | download: 'bx:bx-download', 11 | }); 12 | } 13 | return result; 14 | })(); 15 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/props.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from 'vue'; 2 | 3 | export interface BasicProps { 4 | width: string; 5 | height: string; 6 | } 7 | export const basicProps = { 8 | width: { 9 | type: String as PropType, 10 | default: '100%', 11 | }, 12 | height: { 13 | type: String as PropType, 14 | default: '280px', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/hooks/core/onMountedOrActivated.ts: -------------------------------------------------------------------------------- 1 | import { nextTick, onMounted, onActivated } from 'vue'; 2 | 3 | export function onMountedOrActivated(hook: Fn) { 4 | let mounted: boolean; 5 | 6 | onMounted(() => { 7 | hook(); 8 | nextTick(() => { 9 | mounted = true; 10 | }); 11 | }); 12 | 13 | onActivated(() => { 14 | if (mounted) { 15 | hook(); 16 | } 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord Chat 4 | url: https://discord.gg/8GuAdwDhj6 5 | about: Ask questions and discuss with other Vben users in real time. 6 | - name: Questions & Discussions 7 | url: https://github.com/anncwb/vue-vben-admin/discussions 8 | about: Use GitHub discussions for message-board style questions and discussions. 9 | -------------------------------------------------------------------------------- /src/api/demo/model/optionsModel.ts: -------------------------------------------------------------------------------- 1 | import { BasicFetchResult } from '/@/api/model/baseModel'; 2 | 3 | export interface DemoOptionsItem { 4 | label: string; 5 | value: string; 6 | } 7 | 8 | export interface selectParams { 9 | id: number | string; 10 | } 11 | 12 | /** 13 | * @description: Request list return value 14 | */ 15 | export type DemoOptionsGetResultModel = BasicFetchResult; 16 | -------------------------------------------------------------------------------- /src/api/demo/select.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { DemoOptionsItem, selectParams } from './model/optionsModel'; 3 | enum Api { 4 | OPTIONS_LIST = '/select/getDemoOptions', 5 | } 6 | 7 | /** 8 | * @description: Get sample options value 9 | */ 10 | export const optionsListApi = (params?: selectParams) => 11 | defHttp.get({ url: Api.OPTIONS_LIST, params }); 12 | -------------------------------------------------------------------------------- /src/components/FlowChart/src/types.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfig } from '@logicflow/core'; 2 | import { ToolbarTypeEnum } from './enum'; 3 | 4 | export interface NodeItem extends NodeConfig { 5 | icon: string; 6 | } 7 | 8 | export interface ToolbarConfig { 9 | type?: string | ToolbarTypeEnum; 10 | tooltip?: string | boolean; 11 | icon?: string; 12 | disabled?: boolean; 13 | separate?: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /src/design/transition/scale.less: -------------------------------------------------------------------------------- 1 | .scale-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave, 6 | &-leave-to { 7 | opacity: 0; 8 | transform: scale(0); 9 | } 10 | } 11 | 12 | .scale-rotate-transition { 13 | .transition-default(); 14 | 15 | &-enter-from, 16 | &-leave, 17 | &-leave-to { 18 | opacity: 0; 19 | transform: scale(0) rotate(-45deg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | okText: '确认', 3 | closeText: '关闭', 4 | cancelText: '取消', 5 | loadingText: '加载中...', 6 | saveText: '保存', 7 | delText: '删除', 8 | resetText: '重置', 9 | searchText: '搜索', 10 | queryText: '查询', 11 | 12 | inputText: '请输入', 13 | chooseText: '请选择', 14 | 15 | redo: '刷新', 16 | back: '返回', 17 | 18 | light: '亮色主题', 19 | dark: '黑暗主题', 20 | }; 21 | -------------------------------------------------------------------------------- /tests/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": false, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es6", 9 | "sourceMap": false, 10 | "esModuleInterop": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./" 13 | }, 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Scrollbar/src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface BarMapItem { 2 | offset: string; 3 | scroll: string; 4 | scrollSize: string; 5 | size: string; 6 | key: string; 7 | axis: string; 8 | client: string; 9 | direction: string; 10 | } 11 | export interface BarMap { 12 | vertical: BarMapItem; 13 | horizontal: BarMapItem; 14 | } 15 | 16 | export interface ScrollbarType { 17 | wrap: ElRef; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Form/src/hooks/useComponentRegister.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentType } from '../types/index'; 2 | import { tryOnUnmounted } from '@vueuse/core'; 3 | import { add, del } from '../componentMap'; 4 | import type { Component } from 'vue'; 5 | 6 | export function useComponentRegister(compName: ComponentType, comp: Component) { 7 | add(compName, comp); 8 | tryOnUnmounted(() => { 9 | del(compName); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /src/settings/encryptionSetting.ts: -------------------------------------------------------------------------------- 1 | import { isDevMode } from '/@/utils/env'; 2 | 3 | // System default cache time, in seconds 4 | export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; 5 | 6 | // aes encryption key 7 | export const cacheCipher = { 8 | key: '_11111000001111@', 9 | iv: '@11111000001111_', 10 | }; 11 | 12 | // Whether the system cache is encrypted using aes 13 | export const enableStorageEncryption = !isDevMode(); 14 | -------------------------------------------------------------------------------- /src/api/sys/model/menuModel.ts: -------------------------------------------------------------------------------- 1 | import type { RouteMeta } from 'vue-router'; 2 | export interface RouteItem { 3 | path: string; 4 | component: any; 5 | meta: RouteMeta; 6 | name?: string; 7 | alias?: string | string[]; 8 | redirect?: string; 9 | caseSensitive?: boolean; 10 | children?: RouteItem[]; 11 | } 12 | 13 | /** 14 | * @description: Get menu return value 15 | */ 16 | export type getMenuListResultModel = RouteItem[]; 17 | -------------------------------------------------------------------------------- /src/views/demo/comp/drawer/Drawer1.vue: -------------------------------------------------------------------------------- 1 | 4 | 14 | -------------------------------------------------------------------------------- /tests/server/controller/UserController.ts: -------------------------------------------------------------------------------- 1 | import UserService from '../service/UserService'; 2 | 3 | class UserController { 4 | private service: UserService = new UserService(); 5 | 6 | login = async (ctx) => { 7 | ctx.body = await this.service.login(); 8 | }; 9 | 10 | getUserInfoById = async (ctx) => { 11 | ctx.body = await this.service.getUserInfoById(); 12 | }; 13 | } 14 | 15 | export default new UserController(); 16 | -------------------------------------------------------------------------------- /src/components/Tree/src/TreeIcon.ts: -------------------------------------------------------------------------------- 1 | import type { VNode, FunctionalComponent } from 'vue'; 2 | 3 | import { h } from 'vue'; 4 | import { isString } from '@vue/shared'; 5 | import { Icon } from '/@/components/Icon'; 6 | 7 | export const TreeIcon: FunctionalComponent = ({ icon }: { icon: VNode | string }) => { 8 | if (!icon) return null; 9 | if (isString(icon)) { 10 | return h(Icon, { icon, class: 'mr-1' }); 11 | } 12 | return Icon; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/FlowChart/src/useFlowContext.ts: -------------------------------------------------------------------------------- 1 | import type LogicFlow from '@logicflow/core'; 2 | 3 | import { provide, inject } from 'vue'; 4 | 5 | const key = Symbol('flow-chart'); 6 | 7 | type Instance = { 8 | logicFlow: LogicFlow; 9 | }; 10 | 11 | export function createFlowChartContext(instance: Instance) { 12 | provide(key, instance); 13 | } 14 | 15 | export function useFlowChartContext(): Instance { 16 | return inject(key) as Instance; 17 | } 18 | -------------------------------------------------------------------------------- /src/views/demo/feat/breadcrumb/FlatList.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | -------------------------------------------------------------------------------- /src/locales/lang/en/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | okText: 'OK', 3 | closeText: 'Close', 4 | cancelText: 'Cancel', 5 | loadingText: 'Loading...', 6 | saveText: 'Save', 7 | delText: 'Delete', 8 | resetText: 'Reset', 9 | searchText: 'Search', 10 | queryText: 'Search', 11 | 12 | inputText: 'Please enter', 13 | chooseText: 'Please choose', 14 | 15 | redo: 'Refresh', 16 | back: 'Back', 17 | 18 | light: 'Light', 19 | dark: 'Dark', 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/issue-close-require.yml: -------------------------------------------------------------------------------- 1 | name: Issue Close Require 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: need reproduction 12 | uses: actions-cool/issues-helper@v2.1.1 13 | with: 14 | actions: 'close-issues' 15 | token: ${{ secrets.OPER_TOKEN }} 16 | labels: 'need reproduction' 17 | inactive-day: 3 18 | -------------------------------------------------------------------------------- /tests/server/ecosystem.config.js: -------------------------------------------------------------------------------- 1 | const { name } = require('./package.json'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | apps: [ 6 | { 7 | name, 8 | script: path.resolve(__dirname, './dist/index.js'), 9 | instances: require('os').cpus().length, 10 | autorestart: true, 11 | watch: true, 12 | env_production: { 13 | NODE_ENV: 'production', 14 | PORT: 8080, 15 | }, 16 | }, 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import type { ExtractPropTypes } from 'vue'; 3 | import button from './src/BasicButton.vue'; 4 | import popConfirmButton from './src/PopConfirmButton.vue'; 5 | import { buttonProps } from './src/props'; 6 | 7 | export const Button = withInstall(button); 8 | export const PopConfirmButton = withInstall(popConfirmButton); 9 | export declare type ButtonProps = Partial>; 10 | -------------------------------------------------------------------------------- /src/directives/ripple/index.less: -------------------------------------------------------------------------------- 1 | .ripple-container { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 0; 6 | height: 0; 7 | overflow: hidden; 8 | pointer-events: none; 9 | } 10 | 11 | .ripple-effect { 12 | position: relative; 13 | z-index: 9999; 14 | width: 1px; 15 | height: 1px; 16 | margin-top: 0; 17 | margin-left: 0; 18 | pointer-events: none; 19 | border-radius: 50%; 20 | transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); 21 | } 22 | -------------------------------------------------------------------------------- /src/views/demo/feat/breadcrumb/ChildrenList.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | -------------------------------------------------------------------------------- /src/views/sys/lock/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | -------------------------------------------------------------------------------- /src/hooks/core/useRefs.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import { ref, onBeforeUpdate } from 'vue'; 3 | 4 | export function useRefs(): [Ref, (index: number) => (el: HTMLElement) => void] { 5 | const refs = ref([]) as Ref; 6 | 7 | onBeforeUpdate(() => { 8 | refs.value = []; 9 | }); 10 | 11 | const setRefs = (index: number) => (el: HTMLElement) => { 12 | refs.value[index] = el; 13 | }; 14 | 15 | return [refs, setRefs]; 16 | } 17 | -------------------------------------------------------------------------------- /src/views/demo/comp/drawer/Drawer5.vue: -------------------------------------------------------------------------------- 1 | 7 | 14 | -------------------------------------------------------------------------------- /src/logics/theme/util.ts: -------------------------------------------------------------------------------- 1 | const docEle = document.documentElement; 2 | export function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) { 3 | const targetEl = target || document.body; 4 | let { className } = targetEl; 5 | className = className.replace(clsName, ''); 6 | targetEl.className = flag ? `${className} ${clsName} ` : className; 7 | } 8 | 9 | export function setCssVar(prop: string, val: any, dom = docEle) { 10 | dom.style.setProperty(prop, val); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Container/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import collapseContainer from './src/collapse/CollapseContainer.vue'; 3 | import scrollContainer from './src/ScrollContainer.vue'; 4 | import lazyContainer from './src/LazyContainer.vue'; 5 | 6 | export const CollapseContainer = withInstall(collapseContainer); 7 | export const ScrollContainer = withInstall(scrollContainer); 8 | export const LazyContainer = withInstall(lazyContainer); 9 | 10 | export * from './src/typing'; 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | .npmrc 5 | .cache 6 | 7 | tests/server/static 8 | tests/server/static/upload 9 | 10 | .local 11 | # local env files 12 | .env.local 13 | .env.*.local 14 | .eslintcache 15 | 16 | # Log files 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | pnpm-debug.log* 21 | 22 | # Editor directories and files 23 | .idea 24 | # .vscode 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | 31 | package-lock.json 32 | pnpm-lock.yaml 33 | 34 | -------------------------------------------------------------------------------- /src/components/Table/src/components/EditTableHeaderIcon.vue: -------------------------------------------------------------------------------- 1 | 8 | 17 | -------------------------------------------------------------------------------- /src/hooks/core/useLockFn.ts: -------------------------------------------------------------------------------- 1 | import { ref, unref } from 'vue'; 2 | 3 | export function useLockFn

(fn: (...args: P) => Promise) { 4 | const lockRef = ref(false); 5 | return async function (...args: P) { 6 | if (unref(lockRef)) return; 7 | lockRef.value = true; 8 | try { 9 | const ret = await fn(...args); 10 | lockRef.value = false; 11 | return ret; 12 | } catch (e) { 13 | lockRef.value = false; 14 | throw e; 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /types/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue'; 3 | const Component: DefineComponent<{}, {}, any>; 4 | export default Component; 5 | } 6 | 7 | declare module 'ant-design-vue/es/locale/*' { 8 | import { Locale } from 'ant-design-vue/types/locale-provider'; 9 | const locale: Locale & ReadonlyRecordable; 10 | export default locale as Locale & ReadonlyRecordable; 11 | } 12 | 13 | declare module 'virtual:*' { 14 | const result: any; 15 | export default result; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/ClickOutSide/src/ClickOutSide.vue: -------------------------------------------------------------------------------- 1 | 6 | 20 | -------------------------------------------------------------------------------- /src/components/Container/src/typing.ts: -------------------------------------------------------------------------------- 1 | export type ScrollType = 'default' | 'main'; 2 | 3 | export interface CollapseContainerOptions { 4 | canExpand?: boolean; 5 | title?: string; 6 | helpMessage?: Array | string; 7 | } 8 | export interface ScrollContainerOptions { 9 | enableScroll?: boolean; 10 | type?: ScrollType; 11 | } 12 | 13 | export type ScrollActionType = RefType<{ 14 | scrollBottom: () => void; 15 | getScrollWrap: () => Nullable; 16 | scrollTo: (top: number) => void; 17 | }>; 18 | -------------------------------------------------------------------------------- /src/hooks/web/useContextMenu.ts: -------------------------------------------------------------------------------- 1 | import { onUnmounted, getCurrentInstance } from 'vue'; 2 | import { createContextMenu, destroyContextMenu } from '/@/components/ContextMenu'; 3 | import type { ContextMenuItem } from '/@/components/ContextMenu'; 4 | export type { ContextMenuItem }; 5 | export function useContextMenu(authRemove = true) { 6 | if (getCurrentInstance() && authRemove) { 7 | onUnmounted(() => { 8 | destroyContextMenu(); 9 | }); 10 | } 11 | return [createContextMenu, destroyContextMenu]; 12 | } 13 | -------------------------------------------------------------------------------- /src/api/demo/table.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { DemoParams, DemoListGetResultModel } from './model/tableModel'; 3 | 4 | enum Api { 5 | DEMO_LIST = '/table/getDemoList', 6 | } 7 | 8 | /** 9 | * @description: Get sample list value 10 | */ 11 | 12 | export const demoListApi = (params: DemoParams) => 13 | defHttp.get({ 14 | url: Api.DEMO_LIST, 15 | params, 16 | headers: { 17 | // @ts-ignore 18 | ignoreCancelToken: true, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/views/demo/comp/modal/Modal3.vue: -------------------------------------------------------------------------------- 1 | 6 | 16 | -------------------------------------------------------------------------------- /src/views/demo/page/list/basic/data.tsx: -------------------------------------------------------------------------------- 1 | export const cardList = (() => { 2 | const result: any[] = []; 3 | for (let i = 0; i < 6; i++) { 4 | result.push({ 5 | id: i, 6 | title: 'Vben Admin', 7 | description: '基于Vue Next, TypeScript, Ant Design Vue实现的一套完整的企业级后台管理系统', 8 | datetime: '2020-11-26 17:39', 9 | extra: '编辑', 10 | icon: 'logos:vue', 11 | color: '#1890ff', 12 | author: 'Vben', 13 | percent: 20 * (i + 1), 14 | }); 15 | } 16 | return result; 17 | })(); 18 | -------------------------------------------------------------------------------- /tests/server/controller/FileController.ts: -------------------------------------------------------------------------------- 1 | import FileService from '../service/FileService'; 2 | 3 | class FileController { 4 | private service: FileService = new FileService(); 5 | 6 | upload = async (ctx) => { 7 | const files = ctx.request.files.file; 8 | console.log(files); 9 | 10 | if (files.length === undefined) { 11 | this.service.upload(ctx, files, false); 12 | } else { 13 | this.service.upload(ctx, files, true); 14 | } 15 | }; 16 | } 17 | 18 | export default new FileController(); 19 | -------------------------------------------------------------------------------- /src/views/demo/permission/front/AuthPageA.vue: -------------------------------------------------------------------------------- 1 | 4 | 8 | 20 | -------------------------------------------------------------------------------- /src/views/demo/permission/front/AuthPageB.vue: -------------------------------------------------------------------------------- 1 | 4 | 8 | 20 | -------------------------------------------------------------------------------- /src/enums/sizeEnum.ts: -------------------------------------------------------------------------------- 1 | export enum SizeEnum { 2 | DEFAULT = 'default', 3 | SMALL = 'small', 4 | LARGE = 'large', 5 | } 6 | 7 | export enum SizeNumberEnum { 8 | DEFAULT = 48, 9 | SMALL = 16, 10 | LARGE = 64, 11 | } 12 | 13 | export const sizeMap: Map = (() => { 14 | const map = new Map(); 15 | map.set(SizeEnum.DEFAULT, SizeNumberEnum.DEFAULT); 16 | map.set(SizeEnum.SMALL, SizeNumberEnum.SMALL); 17 | map.set(SizeEnum.LARGE, SizeNumberEnum.LARGE); 18 | return map; 19 | })(); 20 | -------------------------------------------------------------------------------- /tests/server/routes.ts: -------------------------------------------------------------------------------- 1 | import UserController from './controller/UserController'; 2 | import FileController from './controller/FileController'; 3 | 4 | export default [ 5 | // user 6 | { 7 | path: '/login', 8 | method: 'post', 9 | action: UserController.login, 10 | }, 11 | { 12 | path: '/getUserInfoById', 13 | method: 'get', 14 | action: UserController.getUserInfoById, 15 | }, 16 | 17 | // file 18 | { 19 | path: '/upload', 20 | method: 'post', 21 | action: FileController.upload, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /src/components/Modal/src/hooks/useModalContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface ModalContextProps { 5 | redoModalHeight: () => void; 6 | } 7 | 8 | const key: InjectionKey = Symbol(); 9 | 10 | export function createModalContext(context: ModalContextProps) { 11 | return createContext(context, key); 12 | } 13 | 14 | export function useModalContext() { 15 | return useContext(key); 16 | } 17 | -------------------------------------------------------------------------------- /mock/_createProductionServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | 3 | const modules = import.meta.globEager('./**/*.ts'); 4 | 5 | const mockModules: any[] = []; 6 | Object.keys(modules).forEach((key) => { 7 | if (key.includes('/_')) { 8 | return; 9 | } 10 | mockModules.push(...modules[key].default); 11 | }); 12 | 13 | /** 14 | * Used in a production environment. Need to manually import all modules 15 | */ 16 | export function setupProdMockServer() { 17 | createProdMockServer(mockModules); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/CardList/src/data.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | // 每行个数 3 | export const grid = ref(12); 4 | // slider属性 5 | export const useSlider = (min = 6, max = 12) => { 6 | // 每行显示个数滑动条 7 | const getMarks = () => { 8 | const l = {}; 9 | for (let i = min; i < max + 1; i++) { 10 | l[i] = { 11 | style: { 12 | color: '#fff', 13 | }, 14 | label: i, 15 | }; 16 | } 17 | return l; 18 | }; 19 | return { 20 | min, 21 | max, 22 | marks: getMarks(), 23 | step: 1, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/api/demo/model/tableModel.ts: -------------------------------------------------------------------------------- 1 | import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'; 2 | /** 3 | * @description: Request list interface parameters 4 | */ 5 | export type DemoParams = BasicPageParams; 6 | 7 | export interface DemoListItem { 8 | id: string; 9 | beginTime: string; 10 | endTime: string; 11 | address: string; 12 | name: string; 13 | no: number; 14 | status: number; 15 | } 16 | 17 | /** 18 | * @description: Request list return value 19 | */ 20 | export type DemoListGetResultModel = BasicFetchResult; 21 | -------------------------------------------------------------------------------- /src/design/ant/input.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../color.less'; 2 | 3 | // input 4 | .ant-input { 5 | &-number { 6 | min-width: 110px; 7 | } 8 | } 9 | 10 | .ant-input-affix-wrapper .ant-input-suffix { 11 | right: 9px; 12 | } 13 | 14 | .ant-input-clear-icon { 15 | margin-right: 5px; 16 | } 17 | 18 | .ant-input-affix-wrapper-textarea-with-clear-btn { 19 | padding: 0 !important; 20 | 21 | textarea.ant-input { 22 | padding: 4px; 23 | } 24 | } 25 | 26 | .ant-input-number { 27 | width: 100% !important; 28 | max-width: 100%; 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/views/demo/feat/request-demo/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/logics/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { getThemeColors, generateColors } from '../../../build/config/themeConfig'; 2 | 3 | import { replaceStyleVariables } from 'vite-plugin-theme/es/client'; 4 | import { mixLighten, mixDarken, tinycolor } from 'vite-plugin-theme/es/colorUtils'; 5 | 6 | export async function changeTheme(color: string) { 7 | const colors = generateColors({ 8 | mixDarken, 9 | mixLighten, 10 | tinycolor, 11 | color, 12 | }); 13 | 14 | return await replaceStyleVariables({ 15 | colorVariables: [...getThemeColors(color), ...colors], 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /tests/server/service/UserService.ts: -------------------------------------------------------------------------------- 1 | import { Result } from '../utils'; 2 | 3 | const fakeUserInfo = { 4 | userId: '1', 5 | username: 'vben', 6 | realName: 'Vben Admin', 7 | desc: 'manager', 8 | password: '123456', 9 | token: 'fakeToken1', 10 | roles: [ 11 | { 12 | roleName: 'Super Admin', 13 | value: 'super', 14 | }, 15 | ], 16 | }; 17 | export default class UserService { 18 | async login() { 19 | return Result.success(fakeUserInfo); 20 | } 21 | 22 | async getUserInfoById() { 23 | return Result.success(fakeUserInfo); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Form/src/hooks/useFormContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface FormContextProps { 5 | resetAction: () => Promise; 6 | submitAction: () => Promise; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createFormContext(context: FormContextProps) { 12 | return createContext(context, key); 13 | } 14 | 15 | export function useFormContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Markdown/src/getTheme.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取主题类型 深色浅色模式 对应的值 3 | * @param darkModeVal 深色模式值 4 | * @param themeMode 主题类型——外观(默认), 内容, 代码块 5 | */ 6 | export const getTheme = ( 7 | darkModeVal: 'light' | 'dark' | string, 8 | themeMode: 'default' | 'content' | 'code' = 'default', 9 | ) => { 10 | const isDark = darkModeVal === 'dark'; 11 | switch (themeMode) { 12 | case 'default': 13 | return isDark ? 'dark' : 'classic'; 14 | case 'content': 15 | return isDark ? 'dark' : 'light'; 16 | case 'code': 17 | return isDark ? 'dracula' : 'github'; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Button/src/props.ts: -------------------------------------------------------------------------------- 1 | export const buttonProps = { 2 | color: { type: String, validator: (v) => ['error', 'warning', 'success', ''].includes(v) }, 3 | loading: { type: Boolean }, 4 | disabled: { type: Boolean }, 5 | /** 6 | * Text before icon. 7 | */ 8 | preIcon: { type: String }, 9 | /** 10 | * Text after icon. 11 | */ 12 | postIcon: { type: String }, 13 | /** 14 | * preIcon and postIcon icon size. 15 | * @default: 14 16 | */ 17 | iconSize: { type: Number, default: 14 }, 18 | onClick: { type: Function as PropType<(...args) => any>, default: null }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Menu/src/types.ts: -------------------------------------------------------------------------------- 1 | // import { ComputedRef } from 'vue'; 2 | // import { ThemeEnum } from '/@/enums/appEnum'; 3 | // import { MenuModeEnum } from '/@/enums/menuEnum'; 4 | export interface MenuState { 5 | // 默认选中的列表 6 | defaultSelectedKeys: string[]; 7 | 8 | // 模式 9 | // mode: MenuModeEnum; 10 | 11 | // // 主题 12 | // theme: ComputedRef | ThemeEnum; 13 | 14 | // 缩进 15 | inlineIndent?: number; 16 | 17 | // 展开数组 18 | openKeys: string[]; 19 | 20 | // 当前选中的菜单项 key 数组 21 | selectedKeys: string[]; 22 | 23 | // 收缩状态下展开的数组 24 | collapsedOpenKeys: string[]; 25 | } 26 | -------------------------------------------------------------------------------- /src/design/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: opacity 0.1 ease-in-out, transform 0.15s ease-out; 5 | } 6 | 7 | .zoom-out-enter-from, 8 | .zoom-out-leave-to { 9 | opacity: 0; 10 | transform: scale(0); 11 | } 12 | 13 | // zoom-fade 14 | .zoom-fade-enter-active, 15 | .zoom-fade-leave-active { 16 | transition: transform 0.2s, opacity 0.3s ease-out; 17 | } 18 | 19 | .zoom-fade-enter-from { 20 | opacity: 0; 21 | transform: scale(0.92); 22 | } 23 | 24 | .zoom-fade-leave-to { 25 | opacity: 0; 26 | transform: scale(1.06); 27 | } 28 | -------------------------------------------------------------------------------- /src/enums/exceptionEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Exception related enumeration 3 | */ 4 | export enum ExceptionEnum { 5 | // page not access 6 | PAGE_NOT_ACCESS = 403, 7 | 8 | // page not found 9 | PAGE_NOT_FOUND = 404, 10 | 11 | // error 12 | ERROR = 500, 13 | 14 | // net work error 15 | NET_WORK_ERROR = 10000, 16 | 17 | // No data on the page. In fact, it is not an exception page 18 | PAGE_NOT_DATA = 10100, 19 | } 20 | 21 | export enum ErrorTypeEnum { 22 | VUE = 'vue', 23 | SCRIPT = 'script', 24 | RESOURCE = 'resource', 25 | AJAX = 'ajax', 26 | PROMISE = 'promise', 27 | } 28 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/index.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; 2 | import FullScreen from './FullScreen.vue'; 3 | 4 | export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { 5 | loading: true, 6 | }); 7 | 8 | export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue')); 9 | 10 | export const Notify = createAsyncComponent(() => import('./notify/index.vue')); 11 | 12 | export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue')); 13 | 14 | export { FullScreen }; 15 | -------------------------------------------------------------------------------- /src/views/demo/comp/flow-chart/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 24 | -------------------------------------------------------------------------------- /src/views/demo/comp/lazy/TargetContent.vue: -------------------------------------------------------------------------------- 1 | 9 | 20 | -------------------------------------------------------------------------------- /src/components/Application/src/useAppContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface AppProviderContextProps { 5 | prefixCls: Ref; 6 | isMobile: Ref; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createAppProviderContext(context: AppProviderContextProps) { 12 | return createContext(context, key); 13 | } 14 | 15 | export function useAppProviderContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Cross-domain proxy, you can configure multiple 8 | # Please note that no line breaks 9 | VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]] 10 | # VITE_PROXY=[["/api","https://vvbin.cn/test"]] 11 | 12 | # Delete console 13 | VITE_DROP_CONSOLE = false 14 | 15 | # Basic interface address SPA 16 | VITE_GLOB_API_URL=/basic-api 17 | 18 | # File upload address, optional 19 | VITE_GLOB_UPLOAD_URL=/upload 20 | 21 | # Interface prefix 22 | VITE_GLOB_API_URL_PREFIX= 23 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/types.ts: -------------------------------------------------------------------------------- 1 | import type { DropMenu } from '/@/components/Dropdown/index'; 2 | import type { RouteLocationNormalized } from 'vue-router'; 3 | 4 | export enum TabContentEnum { 5 | TAB_TYPE, 6 | EXTRA_TYPE, 7 | } 8 | 9 | export type { DropMenu }; 10 | 11 | export interface TabContentProps { 12 | tabItem: RouteLocationNormalized; 13 | type?: TabContentEnum; 14 | trigger?: ('click' | 'hover' | 'contextmenu')[]; 15 | } 16 | 17 | export enum MenuEventEnum { 18 | REFRESH_PAGE, 19 | CLOSE_CURRENT, 20 | CLOSE_LEFT, 21 | CLOSE_RIGHT, 22 | CLOSE_OTHER, 23 | CLOSE_ALL, 24 | SCALE, 25 | } 26 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/QuickNav.vue: -------------------------------------------------------------------------------- 1 | 11 | 18 | -------------------------------------------------------------------------------- /src/hooks/web/useSortable.ts: -------------------------------------------------------------------------------- 1 | import { nextTick, unref } from 'vue'; 2 | import type { Ref } from 'vue'; 3 | import type { Options } from 'sortablejs'; 4 | 5 | export function useSortable(el: HTMLElement | Ref, options?: Options) { 6 | function initSortable() { 7 | nextTick(async () => { 8 | if (!el) return; 9 | 10 | const Sortable = (await import('sortablejs')).default; 11 | Sortable.create(unref(el), { 12 | animation: 500, 13 | delay: 400, 14 | delayOnTouchOnly: true, 15 | ...options, 16 | }); 17 | }); 18 | } 19 | 20 | return { initSortable }; 21 | } 22 | -------------------------------------------------------------------------------- /src/api/sys/upload.ts: -------------------------------------------------------------------------------- 1 | import { UploadApiResult } from './model/uploadModel'; 2 | import { defHttp } from '/@/utils/http/axios'; 3 | import { UploadFileParams } from '/#/axios'; 4 | import { useGlobSetting } from '/@/hooks/setting'; 5 | 6 | const { uploadUrl = '' } = useGlobSetting(); 7 | 8 | /** 9 | * @description: Upload interface 10 | */ 11 | export function uploadApi( 12 | params: UploadFileParams, 13 | onUploadProgress: (progressEvent: ProgressEvent) => void, 14 | ) { 15 | return defHttp.uploadFile( 16 | { 17 | url: uploadUrl, 18 | onUploadProgress, 19 | }, 20 | params, 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/api/demo/account.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { GetAccountInfoModel } from './model/accountModel'; 3 | 4 | enum Api { 5 | ACCOUNT_INFO = '/account/getAccountInfo', 6 | SESSION_TIMEOUT = '/user/sessionTimeout', 7 | TOKEN_EXPIRED = '/user/tokenExpired', 8 | } 9 | 10 | // Get personal center-basic settings 11 | 12 | export const accountInfoApi = () => defHttp.get({ url: Api.ACCOUNT_INFO }); 13 | 14 | export const sessionTimeoutApi = () => defHttp.post({ url: Api.SESSION_TIMEOUT }); 15 | 16 | export const tokenExpiredApi = () => defHttp.post({ url: Api.TOKEN_EXPIRED }); 17 | -------------------------------------------------------------------------------- /src/components/Menu/src/components/BasicMenuItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 21 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { ref, ComputedRef, unref, computed, watch } from 'vue'; 2 | import type { BasicTableProps } from '../types/table'; 3 | 4 | export function useLoading(props: ComputedRef) { 5 | const loadingRef = ref(unref(props).loading); 6 | 7 | watch( 8 | () => unref(props).loading, 9 | (loading) => { 10 | loadingRef.value = loading; 11 | }, 12 | ); 13 | 14 | const getLoading = computed(() => unref(loadingRef)); 15 | 16 | function setLoading(loading: boolean) { 17 | loadingRef.value = loading; 18 | } 19 | 20 | return { getLoading, setLoading }; 21 | } 22 | -------------------------------------------------------------------------------- /src/layouts/default/setting/components/index.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; 2 | 3 | export const TypePicker = createAsyncComponent(() => import('./TypePicker.vue')); 4 | export const ThemeColorPicker = createAsyncComponent(() => import('./ThemeColorPicker.vue')); 5 | export const SettingFooter = createAsyncComponent(() => import('./SettingFooter.vue')); 6 | export const SwitchItem = createAsyncComponent(() => import('./SwitchItem.vue')); 7 | export const SelectItem = createAsyncComponent(() => import('./SelectItem.vue')); 8 | export const InputNumberItem = createAsyncComponent(() => import('./InputNumberItem.vue')); 9 | -------------------------------------------------------------------------------- /src/utils/dateUtil.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Independent time operation tool to facilitate subsequent switch to dayjs 3 | */ 4 | import dayjs from 'dayjs'; 5 | 6 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; 7 | const DATE_FORMAT = 'YYYY-MM-DD'; 8 | 9 | export function formatToDateTime( 10 | date: dayjs.Dayjs | undefined = undefined, 11 | format = DATE_TIME_FORMAT, 12 | ): string { 13 | return dayjs(date).format(format); 14 | } 15 | 16 | export function formatToDate( 17 | date: dayjs.Dayjs | undefined = undefined, 18 | format = DATE_FORMAT, 19 | ): string { 20 | return dayjs(date).format(format); 21 | } 22 | 23 | export const dateUtil = dayjs; 24 | -------------------------------------------------------------------------------- /src/views/demo/comp/drawer/Drawer2.vue: -------------------------------------------------------------------------------- 1 | 7 | 18 | -------------------------------------------------------------------------------- /src/layouts/default/content/useContentContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, ComputedRef } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface ContentContextProps { 5 | contentHeight: ComputedRef; 6 | setPageHeight: (height: number) => Promise; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createContentContext(context: ContentContextProps) { 12 | return createContext(context, key, { native: true }); 13 | } 14 | 15 | export function useContentContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /src/router/routes/mainOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | The routing of this file will not show the layout. 3 | It is an independent new page. 4 | the contents of the file still need to log in to access 5 | */ 6 | import type { AppRouteModule } from '/@/router/types'; 7 | 8 | // test 9 | // http:ip:port/main-out 10 | export const mainOutRoutes: AppRouteModule[] = [ 11 | { 12 | path: '/main-out', 13 | name: 'MainOut', 14 | component: () => import('/@/views/demo/main-out/index.vue'), 15 | meta: { 16 | title: 'MainOut', 17 | ignoreAuth: true, 18 | }, 19 | }, 20 | ]; 21 | 22 | export const mainOutRouteNames = mainOutRoutes.map((item) => item.name); 23 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/ModalHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 23 | -------------------------------------------------------------------------------- /src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTable } from './src/BasicTable.vue'; 2 | export { default as TableAction } from './src/components/TableAction.vue'; 3 | export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'; 4 | export { default as TableImg } from './src/components/TableImg.vue'; 5 | 6 | export * from './src/types/table'; 7 | export * from './src/types/pagination'; 8 | export * from './src/types/tableAction'; 9 | export { useTable } from './src/hooks/useTable'; 10 | export type { FormSchema, FormProps } from '/@/components/Form/src/types/form'; 11 | export type { EditRecordRow } from './src/components/editable'; 12 | -------------------------------------------------------------------------------- /src/hooks/component/usePageContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, ComputedRef, Ref } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface PageContextProps { 5 | contentHeight: ComputedRef; 6 | pageHeight: Ref; 7 | setPageHeight: (height: number) => Promise; 8 | } 9 | 10 | const key: InjectionKey = Symbol(); 11 | 12 | export function createPageContext(context: PageContextProps) { 13 | return createContext(context, key, { native: true }); 14 | } 15 | 16 | export function usePageContext() { 17 | return useContext(key); 18 | } 19 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /src/router/constant.ts: -------------------------------------------------------------------------------- 1 | export const REDIRECT_NAME = 'Redirect'; 2 | 3 | export const PARENT_LAYOUT_NAME = 'ParentLayout'; 4 | 5 | export const PAGE_NOT_FOUND_NAME = 'PageNotFound'; 6 | 7 | export const EXCEPTION_COMPONENT = () => import('/@/views/sys/exception/Exception.vue'); 8 | 9 | /** 10 | * @description: default layout 11 | */ 12 | export const LAYOUT = () => import('/@/layouts/default/index.vue'); 13 | 14 | /** 15 | * @description: parent-layout 16 | */ 17 | export const getParentLayout = (_name?: string) => { 18 | return () => 19 | new Promise((resolve) => { 20 | resolve({ 21 | name: PARENT_LAYOUT_NAME, 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-bug-cn.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug 报告 3 | about: 向我们报告一个Bug以帮助我们改进 4 | title: '' 5 | labels: 'bug: pending triage' 6 | assignees: '' 7 | --- 8 | 9 | **⚠️ 重要 ⚠️ 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭** 10 | 11 | - [ ] 已阅读 [文档](https://anncwb.github.io/vue-vben-admin-doc/). 12 | - [ ] 确保您的代码已是最新或者所报告的 Bug 在最新版本中可以重现. (部分 Bug 可能已经在最近的代码中修复) 13 | - [ ] 已在 Issues 中搜索了相关的关键词 14 | - [ ] 不是 ant design vue 组件库的 Bug 15 | 16 | ### 描述 Bug 17 | 18 | 请清晰地描述此 Bug 的具体表现。 19 | 20 | ### 复现 Bug 21 | 22 | 请描述在演示页面中复现 Bug 的详细步骤,以确保我们可以理解并定位问题。部分 Bug 如果未在 Demo 中涉及,请务必提供关键代码 23 | 24 | ## 系统信息 25 | 26 | - 操作系统: 27 | - Node 版本: 28 | - 包管理器 (npm/yarn/pnpm) 及其版本: 29 | -------------------------------------------------------------------------------- /src/enums/breakpointEnum.ts: -------------------------------------------------------------------------------- 1 | export enum sizeEnum { 2 | XS = 'XS', 3 | SM = 'SM', 4 | MD = 'MD', 5 | LG = 'LG', 6 | XL = 'XL', 7 | XXL = 'XXL', 8 | } 9 | 10 | export enum screenEnum { 11 | XS = 480, 12 | SM = 576, 13 | MD = 768, 14 | LG = 992, 15 | XL = 1200, 16 | XXL = 1600, 17 | } 18 | 19 | const screenMap = new Map(); 20 | 21 | screenMap.set(sizeEnum.XS, screenEnum.XS); 22 | screenMap.set(sizeEnum.SM, screenEnum.SM); 23 | screenMap.set(sizeEnum.MD, screenEnum.MD); 24 | screenMap.set(sizeEnum.LG, screenEnum.LG); 25 | screenMap.set(sizeEnum.XL, screenEnum.XL); 26 | screenMap.set(sizeEnum.XXL, screenEnum.XXL); 27 | 28 | export { screenMap }; 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | 16 | - name: Create Release for Tag 17 | id: release_tag 18 | uses: yyx990803/release-tag@master 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.OPER_TOKEN }} 21 | with: 22 | tag_name: ${{ github.ref }} 23 | body: | 24 | Please refer to [CHANGELOG.md](https://github.com/anncwb/vue-vben-admin/blob/main/CHANGELOG.md) for details. 25 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position: absolute;display: inline-block;background-color: green;opacity: .5;} 8 | 9 | body{-webkit-text-size-adjust: none;} 10 | 11 | body img{max-width: 96vw;} 12 | 13 | body table img{max-width: 95%;} 14 | 15 | body{font-family: sans-serif;} 16 | 17 | table{border-collapse: collapse;} 18 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide-dark/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position: absolute;display: inline-block;background-color: green;opacity: .5;} 8 | 9 | body{-webkit-text-size-adjust: none;} 10 | 11 | body img{max-width: 96vw;} 12 | 13 | body table img{max-width: 95%;} 14 | 15 | body{font-family: sans-serif;} 16 | 17 | table{border-collapse: collapse;} 18 | -------------------------------------------------------------------------------- /src/components/Excel/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { JSON2SheetOpts, WritingOptions, BookType } from 'xlsx'; 2 | 3 | export interface ExcelData { 4 | header: string[]; 5 | results: T[]; 6 | meta: { sheetName: string }; 7 | } 8 | 9 | export interface JsonToSheet { 10 | data: T[]; 11 | header?: T; 12 | filename?: string; 13 | json2sheetOpts?: JSON2SheetOpts; 14 | write2excelOpts?: WritingOptions; 15 | } 16 | 17 | export interface AoAToSheet { 18 | data: T[][]; 19 | header?: T[]; 20 | filename?: string; 21 | write2excelOpts?: WritingOptions; 22 | } 23 | 24 | export interface ExportModalResult { 25 | filename: string; 26 | bookType: BookType; 27 | } 28 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Request result set 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 0, 6 | ERROR = -1, 7 | TIMEOUT = 401, 8 | TYPE = 'success', 9 | } 10 | 11 | /** 12 | * @description: request method 13 | */ 14 | export enum RequestEnum { 15 | GET = 'GET', 16 | POST = 'POST', 17 | PUT = 'PUT', 18 | DELETE = 'DELETE', 19 | } 20 | 21 | /** 22 | * @description: contentType 23 | */ 24 | export enum ContentTypeEnum { 25 | // json 26 | JSON = 'application/json;charset=UTF-8', 27 | // form-data qs 28 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 29 | // form-data upload 30 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 31 | } 32 | -------------------------------------------------------------------------------- /src/views/demo/editor/tinymce/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 22 | -------------------------------------------------------------------------------- /src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | 3 | import appLogo from './src/AppLogo.vue'; 4 | import appProvider from './src/AppProvider.vue'; 5 | import appSearch from './src/search/AppSearch.vue'; 6 | import appLocalePicker from './src/AppLocalePicker.vue'; 7 | import appDarkModeToggle from './src/AppDarkModeToggle.vue'; 8 | 9 | export { useAppProviderContext } from './src/useAppContext'; 10 | 11 | export const AppLogo = withInstall(appLogo); 12 | export const AppProvider = withInstall(appProvider); 13 | export const AppSearch = withInstall(appSearch); 14 | export const AppLocalePicker = withInstall(appLocalePicker); 15 | export const AppDarkModeToggle = withInstall(appDarkModeToggle); 16 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useTableContext.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import type { BasicTableProps, TableActionType } from '../types/table'; 3 | import { provide, inject, ComputedRef } from 'vue'; 4 | 5 | const key = Symbol('basic-table'); 6 | 7 | type Instance = TableActionType & { 8 | wrapRef: Ref>; 9 | getBindValues: ComputedRef; 10 | }; 11 | 12 | type RetInstance = Omit & { 13 | getBindValues: ComputedRef; 14 | }; 15 | 16 | export function createTableContext(instance: Instance) { 17 | provide(key, instance); 18 | } 19 | 20 | export function useTableContext(): RetInstance { 21 | return inject(key) as RetInstance; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Preview/src/functional.ts: -------------------------------------------------------------------------------- 1 | import type { Options, Props } from './typing'; 2 | import ImgPreview from './Functional.vue'; 3 | import { isClient } from '/@/utils/is'; 4 | import { createVNode, render } from 'vue'; 5 | 6 | let instance: ReturnType | null = null; 7 | export function createImgPreview(options: Options) { 8 | if (!isClient) return; 9 | const propsData: Partial = {}; 10 | const container = document.createElement('div'); 11 | Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options); 12 | 13 | instance = createVNode(ImgPreview, propsData); 14 | render(instance, container); 15 | document.body.appendChild(container); 16 | return instance.component?.exposed; 17 | } 18 | -------------------------------------------------------------------------------- /src/design/var/index.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../color.less'; 2 | @import 'easing'; 3 | @import 'breakpoint'; 4 | 5 | @namespace: vben; 6 | 7 | // tabs 8 | @multiple-height: 30px; 9 | 10 | // headers 11 | @header-height: 48px; 12 | 13 | // logo width 14 | @logo-width: 32px; 15 | 16 | // 17 | @side-drag-z-index: 200; 18 | 19 | @page-loading-z-index: 10000; 20 | 21 | @lock-page-z-index: 3000; 22 | 23 | @layout-header-fixed-z-index: 500; 24 | 25 | @multiple-tab-fixed-z-index: 505; 26 | 27 | @layout-sider-fixed-z-index: 510; 28 | 29 | @layout-mix-sider-fixed-z-index: 550; 30 | 31 | @preview-comp-z-index: 1000; 32 | 33 | @page-footer-z-index: 99; 34 | 35 | .bem(@n; @content) { 36 | @{namespace}-@{n} { 37 | @content(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/layouts/default/setting/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 27 | -------------------------------------------------------------------------------- /src/settings/localeSetting.ts: -------------------------------------------------------------------------------- 1 | import type { DropMenu } from '../components/Dropdown'; 2 | import type { LocaleSetting, LocaleType } from '/#/config'; 3 | 4 | export const LOCALE: { [key: string]: LocaleType } = { 5 | ZH_CN: 'zh_CN', 6 | EN_US: 'en', 7 | }; 8 | 9 | export const localeSetting: LocaleSetting = { 10 | showPicker: true, 11 | // Locale 12 | locale: LOCALE.ZH_CN, 13 | // Default locale 14 | fallback: LOCALE.ZH_CN, 15 | // available Locales 16 | availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US], 17 | }; 18 | 19 | // locale list 20 | export const localeList: DropMenu[] = [ 21 | { 22 | text: '简体中文', 23 | event: LOCALE.ZH_CN, 24 | }, 25 | { 26 | text: 'English', 27 | event: LOCALE.EN_US, 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/src/components/useSimpleMenuContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, Ref } from 'vue'; 2 | import type { Emitter } from '/@/utils/mitt'; 3 | import { createContext, useContext } from '/@/hooks/core/useContext'; 4 | 5 | export interface SimpleRootMenuContextProps { 6 | rootMenuEmitter: Emitter; 7 | activeName: Ref; 8 | } 9 | 10 | const key: InjectionKey = Symbol(); 11 | 12 | export function createSimpleRootMenuContext(context: SimpleRootMenuContextProps) { 13 | return createContext(context, key, { readonly: false, native: true }); 14 | } 15 | 16 | export function useSimpleRootMenuContext() { 17 | return useContext(key); 18 | } 19 | -------------------------------------------------------------------------------- /src/design/transition/slide.less: -------------------------------------------------------------------------------- 1 | .slide-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | transform: translateY(-15px); 8 | } 9 | } 10 | 11 | .slide-y-reverse-transition { 12 | .transition-default(); 13 | 14 | &-enter-from, 15 | &-leave-to { 16 | opacity: 0; 17 | transform: translateY(15px); 18 | } 19 | } 20 | 21 | .slide-x-transition { 22 | .transition-default(); 23 | 24 | &-enter-from, 25 | &-leave-to { 26 | opacity: 0; 27 | transform: translateX(-15px); 28 | } 29 | } 30 | 31 | .slide-x-reverse-transition { 32 | .transition-default(); 33 | 34 | &-enter-from, 35 | &-leave-to { 36 | opacity: 0; 37 | transform: translateX(15px); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/SiderTrigger.vue: -------------------------------------------------------------------------------- 1 | 7 | 22 | -------------------------------------------------------------------------------- /src/views/demo/table/MergeHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | -------------------------------------------------------------------------------- /src/views/demo/table/MultipleHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 27 | -------------------------------------------------------------------------------- /mock/demo/select-demo.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock'; 2 | import { resultSuccess } from '../_util'; 3 | 4 | const demoList = (keyword, count = 20) => { 5 | const result = { 6 | list: [] as any[], 7 | }; 8 | for (let index = 0; index < count; index++) { 9 | result.list.push({ 10 | name: `${keyword ?? ''}选项${index}`, 11 | id: `${index}`, 12 | }); 13 | } 14 | return result; 15 | }; 16 | 17 | export default [ 18 | { 19 | url: '/basic-api/select/getDemoOptions', 20 | timeout: 1000, 21 | method: 'get', 22 | response: ({ query }) => { 23 | const { keyword, count } = query; 24 | console.log(keyword); 25 | return resultSuccess(demoList(keyword, count)); 26 | }, 27 | }, 28 | ] as MockMethod[]; 29 | -------------------------------------------------------------------------------- /src/router/routes/modules/demo/flow.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '/@/router/types'; 2 | 3 | import { LAYOUT } from '/@/router/constant'; 4 | import { t } from '/@/hooks/web/useI18n'; 5 | 6 | const charts: AppRouteModule = { 7 | path: '/flow', 8 | name: 'FlowDemo', 9 | component: LAYOUT, 10 | redirect: '/flow/flowChart', 11 | meta: { 12 | orderNo: 5000, 13 | icon: 'tabler:chart-dots', 14 | title: t('routes.demo.flow.name'), 15 | }, 16 | children: [ 17 | { 18 | path: 'flowChart', 19 | name: 'flowChartDemo', 20 | component: () => import('/@/views/demo/comp/flow-chart/index.vue'), 21 | meta: { 22 | title: t('routes.demo.flow.flowChart'), 23 | }, 24 | }, 25 | ], 26 | }; 27 | 28 | export default charts; 29 | -------------------------------------------------------------------------------- /src/components/Upload/src/ThumbUrl.vue: -------------------------------------------------------------------------------- 1 | 6 | 19 | 30 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 23 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Fn { 2 | (...arg: T[]): R; 3 | } 4 | 5 | declare interface PromiseFn { 6 | (...arg: T[]): Promise; 7 | } 8 | 9 | declare type RefType = T | null; 10 | 11 | declare type LabelValueOptions = { 12 | label: string; 13 | value: any; 14 | [key: string]: string | number | boolean; 15 | }[]; 16 | 17 | declare type EmitType = (event: string, ...args: any[]) => void; 18 | 19 | declare type TargetContext = '_self' | '_blank'; 20 | 21 | declare interface ComponentElRef { 22 | $el: T; 23 | } 24 | 25 | declare type ComponentRef = ComponentElRef | null; 26 | 27 | declare type ElRef = Nullable; 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 17 | 18 | ### Subject of the feature 19 | 20 | Describe your issue here. 21 | 22 | ### Problem 23 | 24 | If the feature requests relates to a problem, please describe the problem you are trying to solve here. 25 | 26 | ### Expected behaviour 27 | 28 | What should happen? Please describe the desired behaviour. 29 | 30 | ### Alternatives 31 | 32 | What are the alternative solutions? Please describe what else you have considered? 33 | -------------------------------------------------------------------------------- /src/views/demo/feat/tabs/TabDetail.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | -------------------------------------------------------------------------------- /src/api/sys/model/userModel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Login interface parameters 3 | */ 4 | export interface LoginParams { 5 | username: string; 6 | password: string; 7 | } 8 | 9 | export interface RoleInfo { 10 | roleName: string; 11 | value: string; 12 | } 13 | 14 | /** 15 | * @description: Login interface return value 16 | */ 17 | export interface LoginResultModel { 18 | userId: string | number; 19 | token: string; 20 | role: RoleInfo; 21 | } 22 | 23 | /** 24 | * @description: Get user information return value 25 | */ 26 | export interface GetUserInfoModel { 27 | roles: RoleInfo[]; 28 | // 用户id 29 | userId: string | number; 30 | // 用户名 31 | username: string; 32 | // 真实名字 33 | realName: string; 34 | // 头像 35 | avatar: string; 36 | // 介绍 37 | desc?: string; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/src/components/types.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from 'vue'; 2 | 3 | export interface Props { 4 | theme: string; 5 | activeName?: string | number | undefined; 6 | openNames: string[]; 7 | accordion: boolean; 8 | width: string; 9 | collapsedWidth: string; 10 | indentSize: number; 11 | collapse: boolean; 12 | activeSubMenuNames: (string | number)[]; 13 | } 14 | 15 | export interface SubMenuProvider { 16 | addSubMenu: (name: string | number, update?: boolean) => void; 17 | removeSubMenu: (name: string | number, update?: boolean) => void; 18 | removeAll: () => void; 19 | sliceIndex: (index: number) => void; 20 | isRemoveAllPopup: Ref; 21 | getOpenNames: () => (string | number)[]; 22 | handleMouseleave?: Fn; 23 | level: number; 24 | props: Props; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/http/axios/axiosRetry.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError, AxiosInstance } from 'axios'; 2 | /** 3 | * 请求重试机制 4 | */ 5 | 6 | export class AxiosRetry { 7 | /** 8 | * 重试 9 | */ 10 | retry(AxiosInstance: AxiosInstance, error: AxiosError) { 11 | // @ts-ignore 12 | const { config } = error.response; 13 | const { waitTime, count } = config?.requestOptions?.retryRequest; 14 | config.__retryCount = config.__retryCount || 0; 15 | if (config.__retryCount >= count) { 16 | return Promise.reject(error); 17 | } 18 | config.__retryCount += 1; 19 | return this.delay(waitTime).then(() => AxiosInstance(config)); 20 | } 21 | 22 | /** 23 | * 延迟 24 | */ 25 | private delay(waitTime: number) { 26 | return new Promise((resolve) => setTimeout(resolve, waitTime)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/views/demo/feat/tab-params/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 28 | -------------------------------------------------------------------------------- /src/views/sys/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 31 | -------------------------------------------------------------------------------- /src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token key 2 | export const TOKEN_KEY = 'TOKEN__'; 3 | 4 | export const LOCALE_KEY = 'LOCALE__'; 5 | 6 | // user info key 7 | export const USER_INFO_KEY = 'USER__INFO__'; 8 | 9 | // role info key 10 | export const ROLES_KEY = 'ROLES__KEY__'; 11 | 12 | // project config key 13 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; 14 | 15 | // lock info 16 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; 17 | 18 | export const MULTIPLE_TABS_KEY = 'MULTIPLE_TABS__KEY__'; 19 | 20 | export const APP_DARK_MODE_KEY_ = '__APP__DARK__MODE__'; 21 | 22 | // base global local key 23 | export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__'; 24 | 25 | // base global session key 26 | export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__'; 27 | 28 | export enum CacheTypeEnum { 29 | SESSION, 30 | LOCAL, 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/uuid.ts: -------------------------------------------------------------------------------- 1 | const hexList: string[] = []; 2 | for (let i = 0; i <= 15; i++) { 3 | hexList[i] = i.toString(16); 4 | } 5 | 6 | export function buildUUID(): string { 7 | let uuid = ''; 8 | for (let i = 1; i <= 36; i++) { 9 | if (i === 9 || i === 14 || i === 19 || i === 24) { 10 | uuid += '-'; 11 | } else if (i === 15) { 12 | uuid += 4; 13 | } else if (i === 20) { 14 | uuid += hexList[(Math.random() * 4) | 8]; 15 | } else { 16 | uuid += hexList[(Math.random() * 16) | 0]; 17 | } 18 | } 19 | return uuid.replace(/-/g, ''); 20 | } 21 | 22 | let unique = 0; 23 | export function buildShortUUID(prefix = ''): string { 24 | const time = Date.now(); 25 | const random = Math.floor(Math.random() * 1000000000); 26 | unique++; 27 | return prefix + '_' + random + unique + String(time); 28 | } 29 | -------------------------------------------------------------------------------- /src/logics/theme/dark.ts: -------------------------------------------------------------------------------- 1 | import { darkCssIsReady, loadDarkThemeCss } from 'vite-plugin-theme/es/client'; 2 | import { addClass, hasClass, removeClass } from '/@/utils/domUtils'; 3 | 4 | export async function updateDarkTheme(mode: string | null = 'light') { 5 | const htmlRoot = document.getElementById('htmlRoot'); 6 | if (!htmlRoot) { 7 | return; 8 | } 9 | const hasDarkClass = hasClass(htmlRoot, 'dark'); 10 | if (mode === 'dark') { 11 | if (import.meta.env.PROD && !darkCssIsReady) { 12 | await loadDarkThemeCss(); 13 | } 14 | htmlRoot.setAttribute('data-theme', 'dark'); 15 | if (!hasDarkClass) { 16 | addClass(htmlRoot, 'dark'); 17 | } 18 | } else { 19 | htmlRoot.setAttribute('data-theme', 'light'); 20 | if (hasDarkClass) { 21 | removeClass(htmlRoot, 'dark'); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/ContextMenu/src/typing.ts: -------------------------------------------------------------------------------- 1 | export interface Axis { 2 | x: number; 3 | y: number; 4 | } 5 | 6 | export interface ContextMenuItem { 7 | label: string; 8 | icon?: string; 9 | hidden?: boolean; 10 | disabled?: boolean; 11 | handler?: Fn; 12 | divider?: boolean; 13 | children?: ContextMenuItem[]; 14 | } 15 | export interface CreateContextOptions { 16 | event: MouseEvent; 17 | icon?: string; 18 | styles?: any; 19 | items?: ContextMenuItem[]; 20 | } 21 | 22 | export interface ContextMenuProps { 23 | event?: MouseEvent; 24 | styles?: any; 25 | items: ContextMenuItem[]; 26 | customEvent?: MouseEvent; 27 | axis?: Axis; 28 | width?: number; 29 | showIcon?: boolean; 30 | } 31 | 32 | export interface ItemContentProps { 33 | showIcon: boolean | undefined; 34 | item: ContextMenuItem; 35 | handler: Fn; 36 | } 37 | -------------------------------------------------------------------------------- /src/views/demo/feat/ripple/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /src/router/routes/modules/demo/setup.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '/@/router/types'; 2 | 3 | import { LAYOUT } from '/@/router/constant'; 4 | import { t } from '/@/hooks/web/useI18n'; 5 | 6 | const setup: AppRouteModule = { 7 | path: '/setup', 8 | name: 'SetupDemo', 9 | component: LAYOUT, 10 | redirect: '/setup/index', 11 | meta: { 12 | orderNo: 90000, 13 | hideChildrenInMenu: true, 14 | icon: 'whh:paintroll', 15 | title: t('routes.demo.setup.page'), 16 | }, 17 | children: [ 18 | { 19 | path: 'index', 20 | name: 'SetupDemoPage', 21 | component: () => import('/@/views/demo/setup/index.vue'), 22 | meta: { 23 | title: t('routes.demo.setup.page'), 24 | icon: 'whh:paintroll', 25 | hideMenu: true, 26 | }, 27 | }, 28 | ], 29 | }; 30 | 31 | export default setup; 32 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useTableStyle.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef } from 'vue'; 2 | import type { BasicTableProps, TableCustomRecord } from '../types/table'; 3 | import { unref } from 'vue'; 4 | import { isFunction } from '/@/utils/is'; 5 | 6 | export function useTableStyle(propsRef: ComputedRef, prefixCls: string) { 7 | function getRowClassName(record: TableCustomRecord, index: number) { 8 | const { striped, rowClassName } = unref(propsRef); 9 | const classNames: string[] = []; 10 | if (striped) { 11 | classNames.push((index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : ''); 12 | } 13 | if (rowClassName && isFunction(rowClassName)) { 14 | classNames.push(rowClassName(record, index)); 15 | } 16 | return classNames.filter((cls) => !!cls).join(' '); 17 | } 18 | 19 | return { getRowClassName }; 20 | } 21 | -------------------------------------------------------------------------------- /src/router/routes/modules/about.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '/@/router/types'; 2 | 3 | import { LAYOUT } from '/@/router/constant'; 4 | import { t } from '/@/hooks/web/useI18n'; 5 | 6 | const about: AppRouteModule = { 7 | path: '/about', 8 | name: 'About', 9 | component: LAYOUT, 10 | redirect: '/about/index', 11 | meta: { 12 | hideChildrenInMenu: true, 13 | icon: 'simple-icons:about-dot-me', 14 | title: t('routes.dashboard.about'), 15 | orderNo: 100000, 16 | }, 17 | children: [ 18 | { 19 | path: 'index', 20 | name: 'AboutPage', 21 | component: () => import('/@/views/sys/about/index.vue'), 22 | meta: { 23 | title: t('routes.dashboard.about'), 24 | icon: 'simple-icons:about-dot-me', 25 | hideMenu: true, 26 | }, 27 | }, 28 | ], 29 | }; 30 | 31 | export default about; 32 | -------------------------------------------------------------------------------- /src/hooks/web/useDesign.ts: -------------------------------------------------------------------------------- 1 | import { useAppProviderContext } from '/@/components/Application'; 2 | // import { computed } from 'vue'; 3 | // import { lowerFirst } from 'lodash-es'; 4 | export function useDesign(scope: string) { 5 | const values = useAppProviderContext(); 6 | // const $style = cssModule ? useCssModule() : {}; 7 | 8 | // const style: Record = {}; 9 | // if (cssModule) { 10 | // Object.keys($style).forEach((key) => { 11 | // // const moduleCls = $style[key]; 12 | // const k = key.replace(new RegExp(`^${values.prefixCls}-?`, 'ig'), ''); 13 | // style[lowerFirst(k)] = $style[key]; 14 | // }); 15 | // } 16 | return { 17 | // prefixCls: computed(() => `${values.prefixCls}-${scope}`), 18 | prefixCls: `${values.prefixCls}-${scope}`, 19 | prefixVar: values.prefixCls, 20 | // style, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | import BasicForm from './src/BasicForm.vue'; 2 | 3 | export * from './src/types/form'; 4 | export * from './src/types/formItem'; 5 | 6 | export { useComponentRegister } from './src/hooks/useComponentRegister'; 7 | export { useForm } from './src/hooks/useForm'; 8 | 9 | export { default as ApiSelect } from './src/components/ApiSelect.vue'; 10 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; 11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; 12 | export { default as ApiTree } from './src/components/ApiTree.vue'; 13 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; 14 | export { default as ApiCascader } from './src/components/ApiCascader.vue'; 15 | export { default as ApiTransfer } from './src/components/ApiTransfer.vue'; 16 | 17 | export { BasicForm }; 18 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from '../../types/componentType'; 2 | import { useI18n } from '/@/hooks/web/useI18n'; 3 | 4 | const { t } = useI18n(); 5 | 6 | /** 7 | * @description: 生成placeholder 8 | */ 9 | export function createPlaceholderMessage(component: ComponentType) { 10 | if (component.includes('Input') || component.includes('AutoComplete')) { 11 | return t('common.inputText'); 12 | } 13 | if (component.includes('Picker')) { 14 | return t('common.chooseText'); 15 | } 16 | 17 | if ( 18 | component.includes('Select') || 19 | component.includes('Checkbox') || 20 | component.includes('Radio') || 21 | component.includes('Switch') || 22 | component.includes('DatePicker') || 23 | component.includes('TimePicker') 24 | ) { 25 | return t('common.chooseText'); 26 | } 27 | return ''; 28 | } 29 | -------------------------------------------------------------------------------- /src/design/index.less: -------------------------------------------------------------------------------- 1 | @import 'transition/index.less'; 2 | @import 'var/index.less'; 3 | @import 'public.less'; 4 | @import 'ant/index.less'; 5 | @import './theme.less'; 6 | 7 | input:-webkit-autofill { 8 | box-shadow: 0 0 0 1000px white inset !important; 9 | } 10 | 11 | :-webkit-autofill { 12 | transition: background-color 5000s ease-in-out 0s !important; 13 | } 14 | 15 | html { 16 | overflow: hidden; 17 | text-size-adjust: 100%; 18 | } 19 | 20 | html, 21 | body { 22 | width: 100%; 23 | height: 100%; 24 | overflow: visible; 25 | overflow-x: hidden; 26 | 27 | &.color-weak { 28 | filter: invert(80%); 29 | } 30 | 31 | &.gray-mode { 32 | filter: grayscale(100%); 33 | filter: progid:dximagetransform.microsoft.basicimage(grayscale=1); 34 | } 35 | } 36 | 37 | a:focus, 38 | a:active, 39 | button, 40 | div, 41 | svg, 42 | span { 43 | outline: none; 44 | } 45 | -------------------------------------------------------------------------------- /src/directives/permission.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Global authority directive 3 | * Used for fine-grained control of component permissions 4 | * @Example v-auth="RoleEnum.TEST" 5 | */ 6 | import type { App, Directive, DirectiveBinding } from 'vue'; 7 | 8 | import { usePermission } from '/@/hooks/web/usePermission'; 9 | 10 | function isAuth(el: Element, binding: any) { 11 | const { hasPermission } = usePermission(); 12 | 13 | const value = binding.value; 14 | if (!value) return; 15 | if (!hasPermission(value)) { 16 | el.parentNode?.removeChild(el); 17 | } 18 | } 19 | 20 | const mounted = (el: Element, binding: DirectiveBinding) => { 21 | isAuth(el, binding); 22 | }; 23 | 24 | const authDirective: Directive = { 25 | mounted, 26 | }; 27 | 28 | export function setupPermissionDirective(app: App) { 29 | app.directive('auth', authDirective); 30 | } 31 | 32 | export default authDirective; 33 | -------------------------------------------------------------------------------- /src/views/sys/error-log/DetailModal.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | -------------------------------------------------------------------------------- /src/components/CodeEditor/src/codemirror/codeMirror.ts: -------------------------------------------------------------------------------- 1 | import CodeMirror from 'codemirror'; 2 | import './codemirror.css'; 3 | import 'codemirror/theme/idea.css'; 4 | import 'codemirror/theme/material-palenight.css'; 5 | // import 'codemirror/addon/lint/lint.css'; 6 | 7 | // modes 8 | import 'codemirror/mode/javascript/javascript'; 9 | import 'codemirror/mode/css/css'; 10 | import 'codemirror/mode/htmlmixed/htmlmixed'; 11 | // addons 12 | // import 'codemirror/addon/edit/closebrackets'; 13 | // import 'codemirror/addon/edit/closetag'; 14 | // import 'codemirror/addon/comment/comment'; 15 | // import 'codemirror/addon/fold/foldcode'; 16 | // import 'codemirror/addon/fold/foldgutter'; 17 | // import 'codemirror/addon/fold/brace-fold'; 18 | // import 'codemirror/addon/fold/indent-fold'; 19 | // import 'codemirror/addon/lint/json-lint'; 20 | // import 'codemirror/addon/fold/comment-fold'; 21 | export { CodeMirror }; 22 | -------------------------------------------------------------------------------- /src/components/Upload/src/helper.ts: -------------------------------------------------------------------------------- 1 | export function checkFileType(file: File, accepts: string[]) { 2 | const newTypes = accepts.join('|'); 3 | // const reg = /\.(jpg|jpeg|png|gif|txt|doc|docx|xls|xlsx|xml)$/i; 4 | const reg = new RegExp('\\.(' + newTypes + ')$', 'i'); 5 | 6 | return reg.test(file.name); 7 | } 8 | 9 | export function checkImgType(file: File) { 10 | return isImgTypeByName(file.name); 11 | } 12 | 13 | export function isImgTypeByName(name: string) { 14 | return /\.(jpg|jpeg|png|gif)$/i.test(name); 15 | } 16 | 17 | export function getBase64WithFile(file: File) { 18 | return new Promise<{ 19 | result: string; 20 | file: File; 21 | }>((resolve, reject) => { 22 | const reader = new FileReader(); 23 | reader.readAsDataURL(file); 24 | reader.onload = () => resolve({ result: reader.result as string, file }); 25 | reader.onerror = (error) => reject(error); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/design/var/easing.less: -------------------------------------------------------------------------------- 1 | // ================================= 2 | // ==============动画函数-=========== 3 | // ================================= 4 | 5 | @ease-base-out: cubic-bezier(0.7, 0.3, 0.1, 1); 6 | @ease-base-in: cubic-bezier(0.9, 0, 0.3, 0.7); 7 | @ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); 8 | @ease-in: cubic-bezier(0.55, 0.055, 0.675, 0.19); 9 | @ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); 10 | @ease-out-back: cubic-bezier(0.12, 0.4, 0.29, 1.46); 11 | @ease-in-back: cubic-bezier(0.71, -0.46, 0.88, 0.6); 12 | @ease-in-out-back: cubic-bezier(0.71, -0.46, 0.29, 1.46); 13 | @ease-out-circ: cubic-bezier(0.08, 0.82, 0.17, 1); 14 | @ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.34); 15 | @ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); 16 | @ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1); 17 | @ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06); 18 | @ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1); 19 | -------------------------------------------------------------------------------- /src/hooks/event/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'; 2 | 3 | interface WindowSizeOptions { 4 | once?: boolean; 5 | immediate?: boolean; 6 | listenerOptions?: AddEventListenerOptions | boolean; 7 | } 8 | 9 | export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { 10 | let handler = () => { 11 | fn(); 12 | }; 13 | const handleSize = useDebounceFn(handler, wait); 14 | handler = handleSize; 15 | 16 | const start = () => { 17 | if (options && options.immediate) { 18 | handler(); 19 | } 20 | window.addEventListener('resize', handler); 21 | }; 22 | 23 | const stop = () => { 24 | window.removeEventListener('resize', handler); 25 | }; 26 | 27 | tryOnMounted(() => { 28 | start(); 29 | }); 30 | 31 | tryOnUnmounted(() => { 32 | stop(); 33 | }); 34 | return [start, stop]; 35 | } 36 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/data.ts: -------------------------------------------------------------------------------- 1 | export interface GrowCardItem { 2 | icon: string; 3 | title: string; 4 | value: number; 5 | total: number; 6 | color: string; 7 | action: string; 8 | } 9 | 10 | export const growCardList: GrowCardItem[] = [ 11 | { 12 | title: '访问数', 13 | icon: 'visit-count|svg', 14 | value: 2000, 15 | total: 120000, 16 | color: 'green', 17 | action: '月', 18 | }, 19 | { 20 | title: '成交额', 21 | icon: 'total-sales|svg', 22 | value: 20000, 23 | total: 500000, 24 | color: 'blue', 25 | action: '月', 26 | }, 27 | { 28 | title: '下载数', 29 | icon: 'download-count|svg', 30 | value: 8000, 31 | total: 120000, 32 | color: 'orange', 33 | action: '周', 34 | }, 35 | { 36 | title: '成交数', 37 | icon: 'transaction|svg', 38 | value: 5000, 39 | total: 50000, 40 | color: 'purple', 41 | action: '年', 42 | }, 43 | ]; 44 | -------------------------------------------------------------------------------- /src/utils/propTypes.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, VNodeChild } from 'vue'; 2 | import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; 3 | 4 | export type VueNode = VNodeChild | JSX.Element; 5 | 6 | type PropTypes = VueTypesInterface & { 7 | readonly style: VueTypeValidableDef; 8 | readonly VNodeChild: VueTypeValidableDef; 9 | // readonly trueBool: VueTypeValidableDef; 10 | }; 11 | 12 | const propTypes = createTypes({ 13 | func: undefined, 14 | bool: undefined, 15 | string: undefined, 16 | number: undefined, 17 | object: undefined, 18 | integer: undefined, 19 | }) as PropTypes; 20 | 21 | propTypes.extend([ 22 | { 23 | name: 'style', 24 | getter: true, 25 | type: [String, Object], 26 | default: undefined, 27 | }, 28 | { 29 | name: 'VNodeChild', 30 | getter: true, 31 | type: undefined, 32 | }, 33 | ]); 34 | export { propTypes }; 35 | -------------------------------------------------------------------------------- /src/hooks/setting/useMultipleTabSetting.ts: -------------------------------------------------------------------------------- 1 | import type { MultiTabsSetting } from '/#/config'; 2 | 3 | import { computed } from 'vue'; 4 | 5 | import { useAppStore } from '/@/store/modules/app'; 6 | 7 | export function useMultipleTabSetting() { 8 | const appStore = useAppStore(); 9 | 10 | const getShowMultipleTab = computed(() => appStore.getMultiTabsSetting.show); 11 | 12 | const getShowQuick = computed(() => appStore.getMultiTabsSetting.showQuick); 13 | 14 | const getShowRedo = computed(() => appStore.getMultiTabsSetting.showRedo); 15 | 16 | const getShowFold = computed(() => appStore.getMultiTabsSetting.showFold); 17 | 18 | function setMultipleTabSetting(multiTabsSetting: Partial) { 19 | appStore.setProjectConfig({ multiTabsSetting }); 20 | } 21 | return { 22 | setMultipleTabSetting, 23 | getShowMultipleTab, 24 | getShowQuick, 25 | getShowRedo, 26 | getShowFold, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/views/demo/comp/modal/Modal2.vue: -------------------------------------------------------------------------------- 1 | 12 | 29 | -------------------------------------------------------------------------------- /src/design/var/breakpoint.less: -------------------------------------------------------------------------------- 1 | // ================================= 2 | // ==============屏幕断点============ 3 | // ================================= 4 | 5 | // Extra small screen / phone 6 | @screen-xs: 480px; 7 | @screen-xs-min: @screen-xs; 8 | 9 | // Small screen / tablet 10 | @screen-sm: 576px; 11 | @screen-sm-min: @screen-sm; 12 | 13 | // Medium screen / desktop 14 | @screen-md: 768px; 15 | @screen-md-min: @screen-md; 16 | 17 | // Large screen / wide desktop 18 | @screen-lg: 992px; 19 | @screen-lg-min: @screen-lg; 20 | 21 | // Extra large screen / full hd 22 | @screen-xl: 1200px; 23 | @screen-xl-min: @screen-xl; 24 | 25 | // Extra extra large screen / large desktop 26 | @screen-2xl: 1600px; 27 | @screen-2xl-min: @screen-2xl; 28 | 29 | @screen-xs-max: (@screen-sm-min - 1px); 30 | @screen-sm-max: (@screen-md-min - 1px); 31 | @screen-md-max: (@screen-lg-min - 1px); 32 | @screen-lg-max: (@screen-xl-min - 1px); 33 | @screen-xl-max: (@screen-2xl-min - 1px); 34 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/SiteAnalysis.vue: -------------------------------------------------------------------------------- 1 | 16 | 39 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Delete console 8 | VITE_DROP_CONSOLE = true 9 | 10 | # Whether to enable gzip or brotli compression 11 | # Optional: gzip | brotli | none 12 | # If you need multiple forms, you can use `,` to separate 13 | VITE_BUILD_COMPRESS = 'none' 14 | 15 | # Whether to delete origin files when using compress, default false 16 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false 17 | 18 | # Basic interface address SPA 19 | VITE_GLOB_API_URL=/basic-api 20 | 21 | # File upload address, optional 22 | # It can be forwarded by nginx or write the actual address directly 23 | VITE_GLOB_UPLOAD_URL=/upload 24 | 25 | # Interface prefix 26 | VITE_GLOB_API_URL_PREFIX= 27 | 28 | # Whether to enable image compression 29 | VITE_USE_IMAGEMIN= true 30 | 31 | # use pwa 32 | VITE_USE_PWA = false 33 | 34 | # Is it compatible with older browsers 35 | VITE_LEGACY = false 36 | -------------------------------------------------------------------------------- /src/components/Table/src/components/settings/RedoSetting.vue: -------------------------------------------------------------------------------- 1 | 9 | 34 | -------------------------------------------------------------------------------- /src/views/demo/comp/verify/Rotate.vue: -------------------------------------------------------------------------------- 1 | 8 | 29 | 34 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | # Whether to open mock 3 | VITE_USE_MOCK = true 4 | 5 | # public path 6 | VITE_PUBLIC_PATH = / 7 | 8 | # Delete console 9 | VITE_DROP_CONSOLE = true 10 | 11 | # Whether to enable gzip or brotli compression 12 | # Optional: gzip | brotli | none 13 | # If you need multiple forms, you can use `,` to separate 14 | VITE_BUILD_COMPRESS = 'none' 15 | 16 | # Whether to delete origin files when using compress, default false 17 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false 18 | 19 | # Basic interface address SPA 20 | VITE_GLOB_API_URL=/basic-api 21 | 22 | # File upload address, optional 23 | # It can be forwarded by nginx or write the actual address directly 24 | VITE_GLOB_UPLOAD_URL=/upload 25 | 26 | # Interface prefix 27 | VITE_GLOB_API_URL_PREFIX= 28 | 29 | # Whether to enable image compression 30 | VITE_USE_IMAGEMIN= true 31 | 32 | # use pwa 33 | VITE_USE_PWA = false 34 | 35 | # Is it compatible with older browsers 36 | VITE_LEGACY = false 37 | -------------------------------------------------------------------------------- /src/utils/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { Persistent, BasicKeys } from '/@/utils/cache/persistent'; 2 | import { CacheTypeEnum } from '/@/enums/cacheEnum'; 3 | import projectSetting from '/@/settings/projectSetting'; 4 | import { TOKEN_KEY } from '/@/enums/cacheEnum'; 5 | 6 | const { permissionCacheType } = projectSetting; 7 | const isLocal = permissionCacheType === CacheTypeEnum.LOCAL; 8 | 9 | export function getToken() { 10 | return getAuthCache(TOKEN_KEY); 11 | } 12 | 13 | export function getAuthCache(key: BasicKeys) { 14 | const fn = isLocal ? Persistent.getLocal : Persistent.getSession; 15 | return fn(key) as T; 16 | } 17 | 18 | export function setAuthCache(key: BasicKeys, value) { 19 | const fn = isLocal ? Persistent.setLocal : Persistent.setSession; 20 | return fn(key, value, true); 21 | } 22 | 23 | export function clearAuthCache(immediate = true) { 24 | const fn = isLocal ? Persistent.clearLocal : Persistent.clearSession; 25 | return fn(immediate); 26 | } 27 | -------------------------------------------------------------------------------- /src/views/demo/comp/strength-meter/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 26 | 33 | -------------------------------------------------------------------------------- /src/hooks/web/useFullContent.ts: -------------------------------------------------------------------------------- 1 | import { computed, unref } from 'vue'; 2 | 3 | import { useAppStore } from '/@/store/modules/app'; 4 | 5 | import { useRouter } from 'vue-router'; 6 | 7 | /** 8 | * @description: Full screen display content 9 | */ 10 | export const useFullContent = () => { 11 | const appStore = useAppStore(); 12 | const router = useRouter(); 13 | const { currentRoute } = router; 14 | 15 | // Whether to display the content in full screen without displaying the menu 16 | const getFullContent = computed(() => { 17 | // Query parameters, the full screen is displayed when the address bar has a full parameter 18 | const route = unref(currentRoute); 19 | const query = route.query; 20 | if (query && Reflect.has(query, '__full__')) { 21 | return true; 22 | } 23 | // Return to the configuration in the configuration file 24 | return appStore.getProjectConfig.fullContent; 25 | }); 26 | 27 | return { getFullContent }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/views/sys/login/LoginFormTitle.vue: -------------------------------------------------------------------------------- 1 | 6 | 26 | -------------------------------------------------------------------------------- /src/layouts/iframe/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 30 | -------------------------------------------------------------------------------- /src/layouts/page/transition.ts: -------------------------------------------------------------------------------- 1 | import type { FunctionalComponent } from 'vue'; 2 | import type { RouteLocation } from 'vue-router'; 3 | 4 | export interface DefaultContext { 5 | Component: FunctionalComponent & { type: Recordable }; 6 | route: RouteLocation; 7 | } 8 | 9 | export function getTransitionName({ 10 | route, 11 | openCache, 12 | cacheTabs, 13 | enableTransition, 14 | def, 15 | }: Pick & { 16 | enableTransition: boolean; 17 | openCache: boolean; 18 | def: string; 19 | cacheTabs: string[]; 20 | }): string | undefined { 21 | if (!enableTransition) { 22 | return undefined; 23 | } 24 | 25 | const isInCache = cacheTabs.includes(route.name as string); 26 | const transitionName = 'fade-slide'; 27 | let name: string | undefined = transitionName; 28 | 29 | if (openCache) { 30 | name = isInCache && route.meta.loaded ? transitionName : undefined; 31 | } 32 | return name || (route.meta.transitionName as string) || def; 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/helper/tsxHelper.tsx: -------------------------------------------------------------------------------- 1 | import { Slots } from 'vue'; 2 | import { isFunction } from '/@/utils/is'; 3 | 4 | /** 5 | * @description: Get slot to prevent empty error 6 | */ 7 | export function getSlot(slots: Slots, slot = 'default', data?: any) { 8 | if (!slots || !Reflect.has(slots, slot)) { 9 | return null; 10 | } 11 | if (!isFunction(slots[slot])) { 12 | console.error(`${slot} is not a function!`); 13 | return null; 14 | } 15 | const slotFn = slots[slot]; 16 | if (!slotFn) return null; 17 | return slotFn(data); 18 | } 19 | 20 | /** 21 | * extends slots 22 | * @param slots 23 | * @param excludeKeys 24 | */ 25 | export function extendSlots(slots: Slots, excludeKeys: string[] = []) { 26 | const slotKeys = Object.keys(slots); 27 | const ret: any = {}; 28 | slotKeys.map((key) => { 29 | if (excludeKeys.includes(key)) { 30 | return null; 31 | } 32 | ret[key] = (data?: any) => getSlot(slots, key, data); 33 | }); 34 | return ret; 35 | } 36 | -------------------------------------------------------------------------------- /src/assets/svg/preview/resume.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/settings/designSetting.ts: -------------------------------------------------------------------------------- 1 | import { ThemeEnum } from '../enums/appEnum'; 2 | 3 | export const prefixCls = 'vben'; 4 | 5 | export const darkMode = ThemeEnum.LIGHT; 6 | 7 | // app theme preset color 8 | export const APP_PRESET_COLOR_LIST: string[] = [ 9 | '#0960bd', 10 | '#0084f4', 11 | '#009688', 12 | '#536dfe', 13 | '#ff5c93', 14 | '#ee4f12', 15 | '#0096c7', 16 | '#9c27b0', 17 | '#ff9800', 18 | ]; 19 | 20 | // header preset color 21 | export const HEADER_PRESET_BG_COLOR_LIST: string[] = [ 22 | '#ffffff', 23 | '#151515', 24 | '#009688', 25 | '#5172DC', 26 | '#018ffb', 27 | '#409eff', 28 | '#e74c3c', 29 | '#24292e', 30 | '#394664', 31 | '#001529', 32 | '#383f45', 33 | ]; 34 | 35 | // sider preset color 36 | export const SIDE_BAR_BG_COLOR_LIST: string[] = [ 37 | '#001529', 38 | '#212121', 39 | '#273352', 40 | '#ffffff', 41 | '#191b24', 42 | '#191a23', 43 | '#304156', 44 | '#001628', 45 | '#28333E', 46 | '#344058', 47 | '#383f45', 48 | ]; 49 | -------------------------------------------------------------------------------- /src/views/demo/comp/scroll/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 25 | 32 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 26 | -------------------------------------------------------------------------------- /src/components/Tinymce/src/tinymce.ts: -------------------------------------------------------------------------------- 1 | // Any plugins you want to setting has to be imported 2 | // Detail plugins list see https://www.tinymce.com/docs/plugins/ 3 | // Custom builds see https://www.tinymce.com/download/custom-builds/ 4 | // colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration 5 | 6 | export const plugins = [ 7 | 'advlist anchor autolink autosave code codesample directionality fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus template textpattern visualblocks visualchars wordcount', 8 | ]; 9 | 10 | export const toolbar = [ 11 | 'fontsizeselect lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 12 | 'hr bullist numlist link preview anchor pagebreak insertdatetime media forecolor backcolor fullscreen', 13 | ]; 14 | -------------------------------------------------------------------------------- /src/enums/menuEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: menu type 3 | */ 4 | export enum MenuTypeEnum { 5 | // left menu 6 | SIDEBAR = 'sidebar', 7 | 8 | MIX_SIDEBAR = 'mix-sidebar', 9 | // mixin menu 10 | MIX = 'mix', 11 | // top menu 12 | TOP_MENU = 'top-menu', 13 | } 14 | 15 | // 折叠触发器位置 16 | export enum TriggerEnum { 17 | // 不显示 18 | NONE = 'NONE', 19 | // 菜单底部 20 | FOOTER = 'FOOTER', 21 | // 头部 22 | HEADER = 'HEADER', 23 | } 24 | 25 | export type Mode = 'vertical' | 'vertical-right' | 'horizontal' | 'inline'; 26 | 27 | // menu mode 28 | export enum MenuModeEnum { 29 | VERTICAL = 'vertical', 30 | HORIZONTAL = 'horizontal', 31 | VERTICAL_RIGHT = 'vertical-right', 32 | INLINE = 'inline', 33 | } 34 | 35 | export enum MenuSplitTyeEnum { 36 | NONE, 37 | TOP, 38 | LEFT, 39 | } 40 | 41 | export enum TopMenuAlignEnum { 42 | CENTER = 'center', 43 | START = 'start', 44 | END = 'end', 45 | } 46 | 47 | export enum MixSidebarTriggerEnum { 48 | HOVER = 'hover', 49 | CLICK = 'click', 50 | } 51 | -------------------------------------------------------------------------------- /src/hooks/setting/index.ts: -------------------------------------------------------------------------------- 1 | import type { GlobConfig } from '/#/config'; 2 | 3 | import { warn } from '/@/utils/log'; 4 | import { getAppEnvConfig } from '/@/utils/env'; 5 | 6 | export const useGlobSetting = (): Readonly => { 7 | const { 8 | VITE_GLOB_APP_TITLE, 9 | VITE_GLOB_API_URL, 10 | VITE_GLOB_APP_SHORT_NAME, 11 | VITE_GLOB_API_URL_PREFIX, 12 | VITE_GLOB_UPLOAD_URL, 13 | } = getAppEnvConfig(); 14 | 15 | if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { 16 | warn( 17 | `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`, 18 | ); 19 | } 20 | 21 | // Take global configuration 22 | const glob: Readonly = { 23 | title: VITE_GLOB_APP_TITLE, 24 | apiUrl: VITE_GLOB_API_URL, 25 | shortName: VITE_GLOB_APP_SHORT_NAME, 26 | urlPrefix: VITE_GLOB_API_URL_PREFIX, 27 | uploadUrl: VITE_GLOB_UPLOAD_URL, 28 | }; 29 | return glob as Readonly; 30 | }; 31 | -------------------------------------------------------------------------------- /src/directives/repeatClick.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prevent repeated clicks 3 | * @Example v-repeat-click="()=>{}" 4 | */ 5 | import { on, once } from '/@/utils/domUtils'; 6 | import type { Directive, DirectiveBinding } from 'vue'; 7 | 8 | const repeatDirective: Directive = { 9 | beforeMount(el: Element, binding: DirectiveBinding) { 10 | let interval: Nullable = null; 11 | let startTime = 0; 12 | const handler = (): void => binding?.value(); 13 | const clear = (): void => { 14 | if (Date.now() - startTime < 100) { 15 | handler(); 16 | } 17 | interval && clearInterval(interval); 18 | interval = null; 19 | }; 20 | 21 | on(el, 'mousedown', (e: MouseEvent): void => { 22 | if ((e as any).button !== 0) return; 23 | startTime = Date.now(); 24 | once(document as any, 'mouseup', clear); 25 | interval && clearInterval(interval); 26 | interval = setInterval(handler, 100); 27 | }); 28 | }, 29 | }; 30 | 31 | export default repeatDirective; 32 | -------------------------------------------------------------------------------- /src/logics/mitt/routeChange.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to monitor routing changes to change the status of menus and tabs. There is no need to monitor the route, because the route status change is affected by the page rendering time, which will be slow 3 | */ 4 | 5 | import mitt from '/@/utils/mitt'; 6 | import type { RouteLocationNormalized } from 'vue-router'; 7 | import { getRawRoute } from '/@/utils'; 8 | 9 | const emitter = mitt(); 10 | 11 | const key = Symbol(); 12 | 13 | let lastChangeTab: RouteLocationNormalized; 14 | 15 | export function setRouteChange(lastChangeRoute: RouteLocationNormalized) { 16 | const r = getRawRoute(lastChangeRoute); 17 | emitter.emit(key, r); 18 | lastChangeTab = r; 19 | } 20 | 21 | export function listenerRouteChange( 22 | callback: (route: RouteLocationNormalized) => void, 23 | immediate = true, 24 | ) { 25 | emitter.on(key, callback); 26 | immediate && lastChangeTab && callback(lastChangeTab); 27 | } 28 | 29 | export function removeTabChangeListener() { 30 | emitter.clear(); 31 | } 32 | -------------------------------------------------------------------------------- /src/assets/svg/preview/unscale.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/Description/src/useDescription.ts: -------------------------------------------------------------------------------- 1 | import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing'; 2 | import { ref, getCurrentInstance, unref } from 'vue'; 3 | import { isProdMode } from '/@/utils/env'; 4 | 5 | export function useDescription(props?: Partial): UseDescReturnType { 6 | if (!getCurrentInstance()) { 7 | throw new Error('useDescription() can only be used inside setup() or functional components!'); 8 | } 9 | const desc = ref>(null); 10 | const loaded = ref(false); 11 | 12 | function register(instance: DescInstance) { 13 | if (unref(loaded) && isProdMode()) { 14 | return; 15 | } 16 | desc.value = instance; 17 | props && instance.setDescProps(props); 18 | loaded.value = true; 19 | } 20 | 21 | const methods: DescInstance = { 22 | setDescProps: (descProps: Partial): void => { 23 | unref(desc)?.setDescProps(descProps); 24 | }, 25 | }; 26 | 27 | return [register, methods]; 28 | } 29 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/HeaderTrigger.vue: -------------------------------------------------------------------------------- 1 | 6 | 26 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { Modal } from 'ant-design-vue'; 2 | import { defineComponent, toRefs, unref } from 'vue'; 3 | import { basicProps } from '../props'; 4 | import { useModalDragMove } from '../hooks/useModalDrag'; 5 | import { useAttrs } from '/@/hooks/core/useAttrs'; 6 | import { extendSlots } from '/@/utils/helper/tsxHelper'; 7 | 8 | export default defineComponent({ 9 | name: 'Modal', 10 | inheritAttrs: false, 11 | props: basicProps, 12 | emits: ['cancel'], 13 | setup(props, { slots, emit }) { 14 | const { visible, draggable, destroyOnClose } = toRefs(props); 15 | const attrs = useAttrs(); 16 | useModalDragMove({ 17 | visible, 18 | destroyOnClose, 19 | draggable, 20 | }); 21 | 22 | const onCancel = (e: Event) => { 23 | emit('cancel', e); 24 | }; 25 | 26 | return () => { 27 | const propsData = { ...unref(attrs), ...props, onCancel } as Recordable; 28 | return {extendSlots(slots)}; 29 | }; 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /src/components/Table/src/const.ts: -------------------------------------------------------------------------------- 1 | import componentSetting from '/@/settings/componentSetting'; 2 | 3 | const { table } = componentSetting; 4 | 5 | const { 6 | pageSizeOptions, 7 | defaultPageSize, 8 | fetchSetting, 9 | defaultSize, 10 | defaultSortFn, 11 | defaultFilterFn, 12 | } = table; 13 | 14 | export const ROW_KEY = 'key'; 15 | 16 | // Optional display number per page; 17 | export const PAGE_SIZE_OPTIONS = pageSizeOptions; 18 | 19 | // Number of items displayed per page 20 | export const PAGE_SIZE = defaultPageSize; 21 | 22 | // Common interface field settings 23 | export const FETCH_SETTING = fetchSetting; 24 | 25 | // Default Size 26 | export const DEFAULT_SIZE = defaultSize; 27 | 28 | // Configure general sort function 29 | export const DEFAULT_SORT_FN = defaultSortFn; 30 | 31 | export const DEFAULT_FILTER_FN = defaultFilterFn; 32 | 33 | // Default layout of table cells 34 | export const DEFAULT_ALIGN = 'center'; 35 | 36 | export const INDEX_COLUMN_FLAG = 'INDEX'; 37 | 38 | export const ACTION_COLUMN_FLAG = 'ACTION'; 39 | -------------------------------------------------------------------------------- /src/components/Qrcode/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { QRCodeSegment, QRCodeRenderersOptions } from 'qrcode'; 2 | 3 | export type ContentType = string | QRCodeSegment[]; 4 | 5 | export type { QRCodeRenderersOptions }; 6 | 7 | export type LogoType = { 8 | src: string; 9 | logoSize: number; 10 | borderColor: string; 11 | bgColor: string; 12 | borderSize: number; 13 | crossOrigin: string; 14 | borderRadius: number; 15 | logoRadius: number; 16 | }; 17 | 18 | export interface RenderQrCodeParams { 19 | canvas: any; 20 | content: ContentType; 21 | width?: number; 22 | options?: QRCodeRenderersOptions; 23 | logo?: LogoType | string; 24 | image?: HTMLImageElement; 25 | downloadName?: string; 26 | download?: boolean | Fn; 27 | } 28 | 29 | export type ToCanvasFn = (options: RenderQrCodeParams) => Promise; 30 | 31 | export interface QrCodeActionType { 32 | download: (fileName?: string) => void; 33 | } 34 | 35 | export interface QrcodeDoneEventParams { 36 | url: string; 37 | ctx?: CanvasRenderingContext2D | null; 38 | } 39 | -------------------------------------------------------------------------------- /src/hooks/setting/useTransitionSetting.ts: -------------------------------------------------------------------------------- 1 | import type { TransitionSetting } from '/#/config'; 2 | 3 | import { computed } from 'vue'; 4 | 5 | import { useAppStore } from '/@/store/modules/app'; 6 | 7 | export function useTransitionSetting() { 8 | const appStore = useAppStore(); 9 | 10 | const getEnableTransition = computed(() => appStore.getTransitionSetting?.enable); 11 | 12 | const getOpenNProgress = computed(() => appStore.getTransitionSetting?.openNProgress); 13 | 14 | const getOpenPageLoading = computed((): boolean => { 15 | return !!appStore.getTransitionSetting?.openPageLoading; 16 | }); 17 | 18 | const getBasicTransition = computed(() => appStore.getTransitionSetting?.basicTransition); 19 | 20 | function setTransitionSetting(transitionSetting: Partial) { 21 | appStore.setProjectConfig({ transitionSetting }); 22 | } 23 | return { 24 | setTransitionSetting, 25 | 26 | getEnableTransition, 27 | getOpenNProgress, 28 | getOpenPageLoading, 29 | getBasicTransition, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/user-dropdown/DropMenuItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 33 | -------------------------------------------------------------------------------- /src/router/routes/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '/@/router/types'; 2 | 3 | import { LAYOUT } from '/@/router/constant'; 4 | import { t } from '/@/hooks/web/useI18n'; 5 | 6 | const dashboard: AppRouteModule = { 7 | path: '/dashboard', 8 | name: 'Dashboard', 9 | component: LAYOUT, 10 | redirect: '/dashboard/analysis', 11 | meta: { 12 | orderNo: 10, 13 | icon: 'ion:grid-outline', 14 | title: t('routes.dashboard.dashboard'), 15 | }, 16 | children: [ 17 | { 18 | path: 'analysis', 19 | name: 'Analysis', 20 | component: () => import('/@/views/dashboard/analysis/index.vue'), 21 | meta: { 22 | // affix: true, 23 | title: t('routes.dashboard.analysis'), 24 | }, 25 | }, 26 | { 27 | path: 'workbench', 28 | name: 'Workbench', 29 | component: () => import('/@/views/dashboard/workbench/index.vue'), 30 | meta: { 31 | title: t('routes.dashboard.workbench'), 32 | }, 33 | }, 34 | ], 35 | }; 36 | 37 | export default dashboard; 38 | -------------------------------------------------------------------------------- /src/router/guard/stateGuard.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router'; 2 | import { useAppStore } from '/@/store/modules/app'; 3 | import { useMultipleTabStore } from '/@/store/modules/multipleTab'; 4 | import { useUserStore } from '/@/store/modules/user'; 5 | import { usePermissionStore } from '/@/store/modules/permission'; 6 | import { PageEnum } from '/@/enums/pageEnum'; 7 | import { removeTabChangeListener } from '/@/logics/mitt/routeChange'; 8 | 9 | export function createStateGuard(router: Router) { 10 | router.afterEach((to) => { 11 | // Just enter the login page and clear the authentication information 12 | if (to.path === PageEnum.BASE_LOGIN) { 13 | const tabStore = useMultipleTabStore(); 14 | const userStore = useUserStore(); 15 | const appStore = useAppStore(); 16 | const permissionStore = usePermissionStore(); 17 | appStore.resetAllState(); 18 | permissionStore.resetState(); 19 | tabStore.resetState(); 20 | userStore.resetState(); 21 | removeTabChangeListener(); 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/views/demo/page/list/search/data.tsx: -------------------------------------------------------------------------------- 1 | import { FormSchema } from '/@/components/Form/index'; 2 | 3 | export const searchList = (() => { 4 | const result: any[] = []; 5 | for (let i = 0; i < 6; i++) { 6 | result.push({ 7 | id: i, 8 | title: 'Vben Admin', 9 | description: ['Vben', '设计语言', 'Typescript'], 10 | content: '基于Vue Next, TypeScript, Ant Design实现的一套完整的企业级后台管理系统。', 11 | time: '2020-11-14 11:20', 12 | }); 13 | } 14 | return result; 15 | })(); 16 | 17 | export const actions: any[] = [ 18 | { icon: 'clarity:star-line', text: '156', color: '#018ffb' }, 19 | { icon: 'bx:bxs-like', text: '156', color: '#459ae8' }, 20 | { icon: 'bx:bxs-message-dots', text: '2', color: '#42d27d' }, 21 | ]; 22 | 23 | export const schemas: FormSchema[] = [ 24 | { 25 | field: 'field1', 26 | component: 'InputSearch', 27 | label: '项目名', 28 | colProps: { 29 | span: 8, 30 | }, 31 | componentProps: { 32 | onChange: (e: any) => { 33 | console.log(e); 34 | }, 35 | }, 36 | }, 37 | ]; 38 | -------------------------------------------------------------------------------- /mock/demo/tree-demo.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock'; 2 | import { resultSuccess } from '../_util'; 3 | 4 | const demoTreeList = (keyword) => { 5 | const result = { 6 | list: [] as Recordable[], 7 | }; 8 | for (let index = 0; index < 5; index++) { 9 | const children: Recordable[] = []; 10 | for (let j = 0; j < 3; j++) { 11 | children.push({ 12 | title: `${keyword ?? ''}选项${index}-${j}`, 13 | value: `${index}-${j}`, 14 | key: `${index}-${j}`, 15 | }); 16 | } 17 | result.list.push({ 18 | title: `${keyword ?? ''}选项${index}`, 19 | value: `${index}`, 20 | key: `${index}`, 21 | children, 22 | }); 23 | } 24 | return result; 25 | }; 26 | 27 | export default [ 28 | { 29 | url: '/basic-api/tree/getDemoOptions', 30 | timeout: 1000, 31 | method: 'get', 32 | response: ({ query }) => { 33 | const { keyword } = query; 34 | console.log(keyword); 35 | return resultSuccess(demoTreeList(keyword)); 36 | }, 37 | }, 38 | ] as MockMethod[]; 39 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/components/TabRedo.vue: -------------------------------------------------------------------------------- 1 | 6 | 34 | -------------------------------------------------------------------------------- /src/components/Tree/style/index.less: -------------------------------------------------------------------------------- 1 | @tree-prefix-cls: ~'@{namespace}-tree'; 2 | 3 | .@{tree-prefix-cls} { 4 | background-color: @component-background; 5 | 6 | .ant-tree-node-content-wrapper { 7 | position: relative; 8 | 9 | .ant-tree-title { 10 | position: absolute; 11 | left: 0; 12 | width: 100%; 13 | overflow: hidden; 14 | text-overflow: ellipsis; 15 | white-space: nowrap; 16 | } 17 | } 18 | 19 | &__title { 20 | position: relative; 21 | display: flex; 22 | align-items: center; 23 | width: 100%; 24 | padding-right: 10px; 25 | 26 | &:hover { 27 | .@{tree-prefix-cls}__action { 28 | visibility: visible; 29 | } 30 | } 31 | } 32 | 33 | &__content { 34 | overflow: hidden; 35 | } 36 | 37 | &__actions { 38 | position: absolute; 39 | //top: 2px; 40 | right: 3px; 41 | display: flex; 42 | } 43 | 44 | &__action { 45 | margin-left: 4px; 46 | visibility: hidden; 47 | } 48 | 49 | &-header { 50 | border-bottom: 1px solid @border-color-base; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/views/demo/comp/drawer/Drawer3.vue: -------------------------------------------------------------------------------- 1 | 20 | 36 | -------------------------------------------------------------------------------- /src/enums/appEnum.ts: -------------------------------------------------------------------------------- 1 | export const SIDE_BAR_MINI_WIDTH = 48; 2 | export const SIDE_BAR_SHOW_TIT_MINI_WIDTH = 80; 3 | 4 | export enum ContentEnum { 5 | // auto width 6 | FULL = 'full', 7 | // fixed width 8 | FIXED = 'fixed', 9 | } 10 | 11 | // menu theme enum 12 | export enum ThemeEnum { 13 | DARK = 'dark', 14 | LIGHT = 'light', 15 | } 16 | 17 | export enum SettingButtonPositionEnum { 18 | AUTO = 'auto', 19 | HEADER = 'header', 20 | FIXED = 'fixed', 21 | } 22 | 23 | export enum SessionTimeoutProcessingEnum { 24 | ROUTE_JUMP, 25 | PAGE_COVERAGE, 26 | } 27 | 28 | /** 29 | * 权限模式 30 | */ 31 | export enum PermissionModeEnum { 32 | // role 33 | // 角色权限 34 | ROLE = 'ROLE', 35 | // black 36 | // 后端 37 | BACK = 'BACK', 38 | // route mapping 39 | // 路由映射 40 | ROUTE_MAPPING = 'ROUTE_MAPPING', 41 | } 42 | 43 | // Route switching animation 44 | // 路由切换动画 45 | export enum RouterTransitionEnum { 46 | ZOOM_FADE = 'zoom-fade', 47 | ZOOM_OUT = 'zoom-out', 48 | FADE_SIDE = 'fade-slide', 49 | FADE = 'fade', 50 | FADE_BOTTOM = 'fade-bottom', 51 | FADE_SCALE = 'fade-scale', 52 | } 53 | -------------------------------------------------------------------------------- /src/hooks/web/usePagination.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import { ref, unref, computed } from 'vue'; 3 | 4 | function pagination(list: T[], pageNo: number, pageSize: number): T[] { 5 | const offset = (pageNo - 1) * Number(pageSize); 6 | const ret = 7 | offset + Number(pageSize) >= list.length 8 | ? list.slice(offset, list.length) 9 | : list.slice(offset, offset + Number(pageSize)); 10 | return ret; 11 | } 12 | 13 | export function usePagination(list: Ref, pageSize: number) { 14 | const currentPage = ref(1); 15 | const pageSizeRef = ref(pageSize); 16 | 17 | const getPaginationList = computed(() => { 18 | return pagination(unref(list), unref(currentPage), unref(pageSizeRef)); 19 | }); 20 | 21 | const getTotal = computed(() => { 22 | return unref(list).length; 23 | }); 24 | 25 | function setCurrentPage(page: number) { 26 | currentPage.value = page; 27 | } 28 | 29 | function setPageSize(pageSize: number) { 30 | pageSizeRef.value = pageSize; 31 | } 32 | 33 | return { setCurrentPage, getTotal, setPageSize, getPaginationList }; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Menu/src/components/MenuItemContent.vue: -------------------------------------------------------------------------------- 1 | 7 | 35 | -------------------------------------------------------------------------------- /src/components/Table/src/types/tableAction.ts: -------------------------------------------------------------------------------- 1 | import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; 2 | import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip'; 3 | import { RoleEnum } from '/@/enums/roleEnum'; 4 | export interface ActionItem extends ButtonProps { 5 | onClick?: Fn; 6 | label?: string; 7 | color?: 'success' | 'error' | 'warning'; 8 | icon?: string; 9 | popConfirm?: PopConfirm; 10 | disabled?: boolean; 11 | divider?: boolean; 12 | // 权限编码控制是否显示 13 | auth?: RoleEnum | RoleEnum[] | string | string[]; 14 | // 业务控制是否显示 15 | ifShow?: boolean | ((action: ActionItem) => boolean); 16 | tooltip?: string | TooltipProps; 17 | } 18 | 19 | export interface PopConfirm { 20 | title: string; 21 | okText?: string; 22 | cancelText?: string; 23 | confirm: Fn; 24 | cancel?: Fn; 25 | icon?: string; 26 | placement?: 27 | | 'top' 28 | | 'left' 29 | | 'right' 30 | | 'bottom' 31 | | 'topLeft' 32 | | 'topRight' 33 | | 'leftTop' 34 | | 'leftBottom' 35 | | 'rightTop' 36 | | 'rightBottom' 37 | | 'bottomLeft' 38 | | 'bottomRight'; 39 | } 40 | -------------------------------------------------------------------------------- /src/views/demo/comp/card-list/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 33 | -------------------------------------------------------------------------------- /src/components/Application/src/search/AppSearch.vue: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/ModalFooter.vue: -------------------------------------------------------------------------------- 1 | 20 | 41 | -------------------------------------------------------------------------------- /src/views/demo/excel/ArrayExport.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present, Vben 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/DynamicInfo.vue: -------------------------------------------------------------------------------- 1 | 24 | 32 | -------------------------------------------------------------------------------- /tests/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "nodemon", 7 | "build": "rimraf ./dist && tsup ./index.ts --dts --format cjs,esm ", 8 | "prod": "npx pm2 start ecosystem.config.js --env production", 9 | "restart": "pm2 restart ecosystem.config.js --env production", 10 | "stop": "npx pm2 stop ecosystem.config.js" 11 | }, 12 | "dependencies": { 13 | "fs-extra": "^10.0.1", 14 | "koa": "^2.13.4", 15 | "koa-body": "^4.2.0", 16 | "koa-bodyparser": "^4.3.0", 17 | "koa-route": "^3.2.0", 18 | "koa-router": "^10.1.1", 19 | "koa-static": "^5.0.0", 20 | "koa-websocket": "^6.0.0", 21 | "koa2-cors": "^2.0.6" 22 | }, 23 | "devDependencies": { 24 | "@types/koa": "^2.13.4", 25 | "@types/koa-bodyparser": "^5.0.2", 26 | "@types/koa-router": "^7.4.4", 27 | "@types/node": "^17.0.21", 28 | "nodemon": "^2.0.15", 29 | "pm2": "^5.2.0", 30 | "rimraf": "^3.0.2", 31 | "ts-node": "^10.7.0", 32 | "tsconfig-paths": "^3.14.0", 33 | "tsup": "^5.12.1", 34 | "typescript": "^4.6.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/web/useTitle.ts: -------------------------------------------------------------------------------- 1 | import { watch, unref } from 'vue'; 2 | import { useI18n } from '/@/hooks/web/useI18n'; 3 | import { useTitle as usePageTitle } from '@vueuse/core'; 4 | import { useGlobSetting } from '/@/hooks/setting'; 5 | import { useRouter } from 'vue-router'; 6 | import { useLocaleStore } from '/@/store/modules/locale'; 7 | 8 | import { REDIRECT_NAME } from '/@/router/constant'; 9 | 10 | /** 11 | * Listening to page changes and dynamically changing site titles 12 | */ 13 | export function useTitle() { 14 | const { title } = useGlobSetting(); 15 | const { t } = useI18n(); 16 | const { currentRoute } = useRouter(); 17 | const localeStore = useLocaleStore(); 18 | 19 | const pageTitle = usePageTitle(); 20 | 21 | watch( 22 | [() => currentRoute.value.path, () => localeStore.getLocale], 23 | () => { 24 | const route = unref(currentRoute); 25 | 26 | if (route.name === REDIRECT_NAME) { 27 | return; 28 | } 29 | 30 | const tTitle = t(route?.meta?.title as string); 31 | pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`; 32 | }, 33 | { immediate: true }, 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/core/useContext.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InjectionKey, 3 | provide, 4 | inject, 5 | reactive, 6 | readonly as defineReadonly, 7 | // defineComponent, 8 | UnwrapRef, 9 | } from 'vue'; 10 | 11 | export interface CreateContextOptions { 12 | readonly?: boolean; 13 | createProvider?: boolean; 14 | native?: boolean; 15 | } 16 | 17 | type ShallowUnwrap = { 18 | [P in keyof T]: UnwrapRef; 19 | }; 20 | 21 | export function createContext( 22 | context: any, 23 | key: InjectionKey = Symbol(), 24 | options: CreateContextOptions = {}, 25 | ) { 26 | const { readonly = true, createProvider = false, native = false } = options; 27 | 28 | const state = reactive(context); 29 | const provideData = readonly ? defineReadonly(state) : state; 30 | !createProvider && provide(key, native ? context : provideData); 31 | 32 | return { 33 | state, 34 | }; 35 | } 36 | 37 | export function useContext(key: InjectionKey, native?: boolean): T; 38 | 39 | export function useContext( 40 | key: InjectionKey = Symbol(), 41 | defaultValue?: any, 42 | ): ShallowUnwrap { 43 | return inject(key, defaultValue || {}); 44 | } 45 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/ProjectCard.vue: -------------------------------------------------------------------------------- 1 | 20 | 33 | -------------------------------------------------------------------------------- /src/views/demo/system/password/pwd.data.ts: -------------------------------------------------------------------------------- 1 | import { FormSchema } from '/@/components/Form'; 2 | 3 | export const formSchema: FormSchema[] = [ 4 | { 5 | field: 'passwordOld', 6 | label: '当前密码', 7 | component: 'InputPassword', 8 | required: true, 9 | }, 10 | { 11 | field: 'passwordNew', 12 | label: '新密码', 13 | component: 'StrengthMeter', 14 | componentProps: { 15 | placeholder: '新密码', 16 | }, 17 | rules: [ 18 | { 19 | required: true, 20 | message: '请输入新密码', 21 | }, 22 | ], 23 | }, 24 | { 25 | field: 'confirmPassword', 26 | label: '确认密码', 27 | component: 'InputPassword', 28 | 29 | dynamicRules: ({ values }) => { 30 | return [ 31 | { 32 | required: true, 33 | validator: (_, value) => { 34 | if (!value) { 35 | return Promise.reject('密码不能为空'); 36 | } 37 | if (value !== values.passwordNew) { 38 | return Promise.reject('两次输入的密码不一致!'); 39 | } 40 | return Promise.resolve(); 41 | }, 42 | }, 43 | ]; 44 | }, 45 | }, 46 | ]; 47 | -------------------------------------------------------------------------------- /src/hooks/core/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch } from 'vue'; 2 | import { tryOnUnmounted } from '@vueuse/core'; 3 | import { isFunction } from '/@/utils/is'; 4 | 5 | export function useTimeoutFn(handle: Fn, wait: number, native = false) { 6 | if (!isFunction(handle)) { 7 | throw new Error('handle is not Function!'); 8 | } 9 | 10 | const { readyRef, stop, start } = useTimeoutRef(wait); 11 | if (native) { 12 | handle(); 13 | } else { 14 | watch( 15 | readyRef, 16 | (maturity) => { 17 | maturity && handle(); 18 | }, 19 | { immediate: false }, 20 | ); 21 | } 22 | return { readyRef, stop, start }; 23 | } 24 | 25 | export function useTimeoutRef(wait: number) { 26 | const readyRef = ref(false); 27 | 28 | let timer: TimeoutHandle; 29 | function stop(): void { 30 | readyRef.value = false; 31 | timer && window.clearTimeout(timer); 32 | } 33 | function start(): void { 34 | stop(); 35 | timer = setTimeout(() => { 36 | readyRef.value = true; 37 | }, wait); 38 | } 39 | 40 | start(); 41 | 42 | tryOnUnmounted(stop); 43 | 44 | return { readyRef, stop, start }; 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/issue-labeled.yml: -------------------------------------------------------------------------------- 1 | name: Issue Labeled 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | reply-labeled: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: remove pending 12 | if: github.event.label.name == 'enhancement' || github.event.label.name == 'bug' 13 | uses: actions-cool/issues-helper@v2.1.1 14 | with: 15 | actions: 'remove-labels' 16 | token: ${{ secrets.OPER_TOKEN }} 17 | issue-number: ${{ github.event.issue.number }} 18 | labels: 'bug: pending triage' 19 | 20 | - name: need reproduction 21 | if: github.event.label.name == 'need reproduction' 22 | uses: actions-cool/issues-helper@v2.1.1 23 | with: 24 | actions: 'create-comment, remove-labels' 25 | token: ${{ secrets.OPER_TOKEN }} 26 | issue-number: ${{ github.event.issue.number }} 27 | body: | 28 | Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `need reproduction` will be closed if no activities in 3 days. 29 | labels: 'bug: pending triage' 30 | -------------------------------------------------------------------------------- /src/components/Table/src/componentMap.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'vue'; 2 | import { 3 | Input, 4 | Select, 5 | Checkbox, 6 | InputNumber, 7 | Switch, 8 | DatePicker, 9 | TimePicker, 10 | AutoComplete, 11 | } from 'ant-design-vue'; 12 | import type { ComponentType } from './types/componentType'; 13 | import { ApiSelect, ApiTreeSelect } from '/@/components/Form'; 14 | 15 | const componentMap = new Map(); 16 | 17 | componentMap.set('Input', Input); 18 | componentMap.set('InputNumber', InputNumber); 19 | componentMap.set('Select', Select); 20 | componentMap.set('ApiSelect', ApiSelect); 21 | componentMap.set('AutoComplete', AutoComplete); 22 | componentMap.set('ApiTreeSelect', ApiTreeSelect); 23 | componentMap.set('Switch', Switch); 24 | componentMap.set('Checkbox', Checkbox); 25 | componentMap.set('DatePicker', DatePicker); 26 | componentMap.set('TimePicker', TimePicker); 27 | 28 | export function add(compName: ComponentType, component: Component) { 29 | componentMap.set(compName, component); 30 | } 31 | 32 | export function del(compName: ComponentType) { 33 | componentMap.delete(compName); 34 | } 35 | 36 | export { componentMap }; 37 | -------------------------------------------------------------------------------- /src/views/demo/permission/CurrentPermissionMode.vue: -------------------------------------------------------------------------------- 1 | 11 | 33 | -------------------------------------------------------------------------------- /src/views/demo/system/account/DeptTree.vue: -------------------------------------------------------------------------------- 1 | 14 | 43 | -------------------------------------------------------------------------------- /src/design/transition/scroll.less: -------------------------------------------------------------------------------- 1 | .scroll-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | } 8 | 9 | &-enter-from { 10 | transform: translateY(-15px); 11 | } 12 | 13 | &-leave-to { 14 | transform: translateY(15px); 15 | } 16 | } 17 | 18 | .scroll-y-reverse-transition { 19 | .transition-default(); 20 | 21 | &-enter-from, 22 | &-leave-to { 23 | opacity: 0; 24 | } 25 | 26 | &-enter-from { 27 | transform: translateY(15px); 28 | } 29 | 30 | &-leave-to { 31 | transform: translateY(-15px); 32 | } 33 | } 34 | 35 | .scroll-x-transition { 36 | .transition-default(); 37 | 38 | &-enter-from, 39 | &-leave-to { 40 | opacity: 0; 41 | } 42 | 43 | &-enter-from { 44 | transform: translateX(-15px); 45 | } 46 | 47 | &-leave-to { 48 | transform: translateX(15px); 49 | } 50 | } 51 | 52 | .scroll-x-reverse-transition { 53 | .transition-default(); 54 | 55 | &-enter-from, 56 | &-leave-to { 57 | opacity: 0; 58 | } 59 | 60 | &-enter-from { 61 | transform: translateX(15px); 62 | } 63 | 64 | &-leave-to { 65 | transform: translateX(-15px); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /types/store.d.ts: -------------------------------------------------------------------------------- 1 | import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; 2 | import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; 3 | import { RoleInfo } from '/@/api/sys/model/userModel'; 4 | 5 | // Lock screen information 6 | export interface LockInfo { 7 | // Password required 8 | pwd?: string | undefined; 9 | // Is it locked? 10 | isLock?: boolean; 11 | } 12 | 13 | // Error-log information 14 | export interface ErrorLogInfo { 15 | // Type of error 16 | type: ErrorTypeEnum; 17 | // Error file 18 | file: string; 19 | // Error name 20 | name?: string; 21 | // Error message 22 | message: string; 23 | // Error stack 24 | stack?: string; 25 | // Error detail 26 | detail: string; 27 | // Error url 28 | url: string; 29 | // Error time 30 | time?: string; 31 | } 32 | 33 | export interface UserInfo { 34 | userId: string | number; 35 | username: string; 36 | realName: string; 37 | avatar: string; 38 | desc?: string; 39 | homePath?: string; 40 | roles: RoleInfo[]; 41 | } 42 | 43 | export interface BeforeMiniState { 44 | menuCollapsed?: boolean; 45 | menuSplit?: boolean; 46 | menuMode?: MenuModeEnum; 47 | menuType?: MenuTypeEnum; 48 | } 49 | -------------------------------------------------------------------------------- /src/hooks/web/useScript.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref } from 'vue'; 2 | 3 | interface ScriptOptions { 4 | src: string; 5 | } 6 | 7 | export function useScript(opts: ScriptOptions) { 8 | const isLoading = ref(false); 9 | const error = ref(false); 10 | const success = ref(false); 11 | let script: HTMLScriptElement; 12 | 13 | const promise = new Promise((resolve, reject) => { 14 | onMounted(() => { 15 | script = document.createElement('script'); 16 | script.type = 'text/javascript'; 17 | script.onload = function () { 18 | isLoading.value = false; 19 | success.value = true; 20 | error.value = false; 21 | resolve(''); 22 | }; 23 | 24 | script.onerror = function (err) { 25 | isLoading.value = false; 26 | success.value = false; 27 | error.value = true; 28 | reject(err); 29 | }; 30 | 31 | script.src = opts.src; 32 | document.head.appendChild(script); 33 | }); 34 | }); 35 | 36 | onUnmounted(() => { 37 | script && script.remove(); 38 | }); 39 | 40 | return { 41 | isLoading, 42 | error, 43 | success, 44 | toPromise: () => promise, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/design/public.less: -------------------------------------------------------------------------------- 1 | #app { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | // ================================= 7 | // ==============scrollbar========== 8 | // ================================= 9 | 10 | ::-webkit-scrollbar { 11 | width: 7px; 12 | height: 8px; 13 | } 14 | 15 | // ::-webkit-scrollbar-track { 16 | // background: transparent; 17 | // } 18 | 19 | ::-webkit-scrollbar-track { 20 | background-color: rgb(0 0 0 / 5%); 21 | } 22 | 23 | ::-webkit-scrollbar-thumb { 24 | // background: rgba(0, 0, 0, 0.6); 25 | background-color: rgb(144 147 153 / 30%); 26 | // background-color: rgba(144, 147, 153, 0.3); 27 | border-radius: 2px; 28 | box-shadow: inset 0 0 6px rgb(0 0 0 / 20%); 29 | } 30 | 31 | ::-webkit-scrollbar-thumb:hover { 32 | background-color: @border-color-dark; 33 | } 34 | 35 | // ================================= 36 | // ==============nprogress========== 37 | // ================================= 38 | #nprogress { 39 | pointer-events: none; 40 | 41 | .bar { 42 | position: fixed; 43 | top: 0; 44 | left: 0; 45 | z-index: 99999; 46 | width: 100%; 47 | height: 2px; 48 | background-color: @primary-color; 49 | opacity: 0.75; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Container/src/collapse/CollapseHeader.vue: -------------------------------------------------------------------------------- 1 | 17 | 39 | -------------------------------------------------------------------------------- /src/components/CountDown/src/useCountdown.ts: -------------------------------------------------------------------------------- 1 | import { ref, unref } from 'vue'; 2 | import { tryOnUnmounted } from '@vueuse/core'; 3 | 4 | export function useCountdown(count: number) { 5 | const currentCount = ref(count); 6 | 7 | const isStart = ref(false); 8 | 9 | let timerId: ReturnType | null; 10 | 11 | function clear() { 12 | timerId && window.clearInterval(timerId); 13 | } 14 | 15 | function stop() { 16 | isStart.value = false; 17 | clear(); 18 | timerId = null; 19 | } 20 | 21 | function start() { 22 | if (unref(isStart) || !!timerId) { 23 | return; 24 | } 25 | isStart.value = true; 26 | timerId = setInterval(() => { 27 | if (unref(currentCount) === 1) { 28 | stop(); 29 | currentCount.value = count; 30 | } else { 31 | currentCount.value -= 1; 32 | } 33 | }, 1000); 34 | } 35 | 36 | function reset() { 37 | currentCount.value = count; 38 | stop(); 39 | } 40 | 41 | function restart() { 42 | reset(); 43 | start(); 44 | } 45 | 46 | tryOnUnmounted(() => { 47 | reset(); 48 | }); 49 | 50 | return { start, reset, restart, clear, stop, currentCount, isStart }; 51 | } 52 | -------------------------------------------------------------------------------- /src/views/demo/feat/watermark/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 33 | -------------------------------------------------------------------------------- /src/views/sys/login/QrCodeForm.vue: -------------------------------------------------------------------------------- 1 | 17 | 32 | -------------------------------------------------------------------------------- /src/utils/lib/echarts.ts: -------------------------------------------------------------------------------- 1 | import * as echarts from 'echarts/core'; 2 | 3 | import { 4 | BarChart, 5 | LineChart, 6 | PieChart, 7 | MapChart, 8 | PictorialBarChart, 9 | RadarChart, 10 | ScatterChart, 11 | } from 'echarts/charts'; 12 | 13 | import { 14 | TitleComponent, 15 | TooltipComponent, 16 | GridComponent, 17 | PolarComponent, 18 | AriaComponent, 19 | ParallelComponent, 20 | LegendComponent, 21 | RadarComponent, 22 | ToolboxComponent, 23 | DataZoomComponent, 24 | VisualMapComponent, 25 | TimelineComponent, 26 | CalendarComponent, 27 | GraphicComponent, 28 | } from 'echarts/components'; 29 | 30 | import { SVGRenderer } from 'echarts/renderers'; 31 | 32 | echarts.use([ 33 | LegendComponent, 34 | TitleComponent, 35 | TooltipComponent, 36 | GridComponent, 37 | PolarComponent, 38 | AriaComponent, 39 | ParallelComponent, 40 | BarChart, 41 | LineChart, 42 | PieChart, 43 | MapChart, 44 | RadarChart, 45 | SVGRenderer, 46 | PictorialBarChart, 47 | RadarComponent, 48 | ToolboxComponent, 49 | DataZoomComponent, 50 | VisualMapComponent, 51 | TimelineComponent, 52 | CalendarComponent, 53 | GraphicComponent, 54 | ScatterChart, 55 | ]); 56 | 57 | export default echarts; 58 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "noLib": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strictFunctionTypes": false, 11 | "jsx": "preserve", 12 | "baseUrl": ".", 13 | "allowJs": true, 14 | "sourceMap": true, 15 | "esModuleInterop": true, 16 | "resolveJsonModule": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "experimentalDecorators": true, 20 | "lib": ["dom", "esnext"], 21 | "noImplicitAny": false, 22 | "skipLibCheck": true, 23 | "types": ["vite/client"], 24 | "removeComments": true, 25 | "paths": { 26 | "/@/*": ["src/*"], 27 | "/#/*": ["types/*"] 28 | } 29 | }, 30 | "include": [ 31 | "tests/**/*.ts", 32 | "src/**/*.ts", 33 | "src/**/*.d.ts", 34 | "src/**/*.tsx", 35 | "src/**/*.vue", 36 | "types/**/*.d.ts", 37 | "types/**/*.ts", 38 | "build/**/*.ts", 39 | "build/**/*.d.ts", 40 | "mock/**/*.ts", 41 | "vite.config.ts" 42 | ], 43 | "exclude": ["node_modules", "tests/server/**/*.ts", "dist", "**/*.js"] 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/cache/index.ts: -------------------------------------------------------------------------------- 1 | import { getStorageShortName } from '/@/utils/env'; 2 | import { createStorage as create, CreateStorageParams } from './storageCache'; 3 | import { enableStorageEncryption } from '/@/settings/encryptionSetting'; 4 | import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting'; 5 | 6 | export type Options = Partial; 7 | 8 | const createOptions = (storage: Storage, options: Options = {}): Options => { 9 | return { 10 | // No encryption in debug mode 11 | hasEncrypt: enableStorageEncryption, 12 | storage, 13 | prefixKey: getStorageShortName(), 14 | ...options, 15 | }; 16 | }; 17 | 18 | export const WebStorage = create(createOptions(sessionStorage)); 19 | 20 | export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => { 21 | return create(createOptions(storage, options)); 22 | }; 23 | 24 | export const createSessionStorage = (options: Options = {}) => { 25 | return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); 26 | }; 27 | 28 | export const createLocalStorage = (options: Options = {}) => { 29 | return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); 30 | }; 31 | 32 | export default WebStorage; 33 | -------------------------------------------------------------------------------- /src/views/demo/feat/img-preview/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 32 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/FullScreen.vue: -------------------------------------------------------------------------------- 1 | 9 | 38 | --------------------------------------------------------------------------------