├── CNAME ├── .stylelintignore ├── .dockerignore ├── .env ├── packages ├── types │ ├── src │ │ └── index.ts │ ├── .eslintrc.js │ ├── tsconfig.json │ └── build.config.ts └── hooks │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── src │ ├── index.ts │ ├── useRefs.ts │ ├── onMountedOrActivated.ts │ └── useWindowSizeFn.ts │ ├── build.config.ts │ └── package.json ├── src ├── components │ ├── Tree │ │ ├── style │ │ │ ├── index.ts │ │ │ └── index.less │ │ ├── index.ts │ │ └── src │ │ │ └── TreeIcon.ts │ ├── Menu │ │ ├── index.ts │ │ └── src │ │ │ ├── components │ │ │ ├── BasicMenuItem.vue │ │ │ └── MenuItemContent.vue │ │ │ └── types.ts │ ├── CodeEditor │ │ ├── src │ │ │ ├── typing.ts │ │ │ ├── json-preview │ │ │ │ └── JsonPreview.vue │ │ │ └── codemirror │ │ │ │ └── codeMirror.ts │ │ └── index.ts │ ├── Preview │ │ ├── index.ts │ │ └── src │ │ │ └── functional.ts │ ├── ContextMenu │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ ├── Markdown │ │ ├── src │ │ │ ├── typing.ts │ │ │ └── getTheme.ts │ │ └── index.ts │ ├── Icon │ │ └── index.ts │ ├── SimpleMenu │ │ ├── src │ │ │ ├── types.ts │ │ │ └── components │ │ │ │ ├── useSimpleMenuContext.ts │ │ │ │ └── types.ts │ │ └── index.ts │ ├── Time │ │ └── index.ts │ ├── CountTo │ │ └── 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 │ ├── Loading │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ ├── Cropper │ │ ├── src │ │ │ └── typing.ts │ │ └── index.ts │ ├── Dropdown │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ ├── Scrollbar │ │ ├── index.ts │ │ └── src │ │ │ └── types.d.ts │ ├── registerGlobComp.ts │ ├── Description │ │ ├── index.ts │ │ └── src │ │ │ └── useDescription.ts │ ├── Drawer │ │ └── index.ts │ ├── Page │ │ └── index.ts │ ├── CountDown │ │ └── index.ts │ ├── Verify │ │ ├── src │ │ │ └── typing.ts │ │ └── index.ts │ ├── Table │ │ ├── src │ │ │ ├── types │ │ │ │ ├── componentType.ts │ │ │ │ └── tableAction.ts │ │ │ ├── components │ │ │ │ ├── EditTableHeaderIcon.vue │ │ │ │ ├── settings │ │ │ │ │ ├── RedoSetting.vue │ │ │ │ │ └── FullScreenSetting.vue │ │ │ │ └── editable │ │ │ │ │ └── helper.ts │ │ │ ├── hooks │ │ │ │ ├── useLoading.ts │ │ │ │ ├── useTableContext.ts │ │ │ │ └── useTableStyle.ts │ │ │ └── const.ts │ │ └── index.ts │ ├── Application │ │ ├── src │ │ │ ├── search │ │ │ │ ├── AppSearchKeyItem.vue │ │ │ │ └── AppSearch.vue │ │ │ └── useAppContext.ts │ │ └── index.ts │ ├── Basic │ │ └── index.ts │ ├── Container │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ ├── Modal │ │ ├── index.ts │ │ └── src │ │ │ ├── hooks │ │ │ └── useModalContext.ts │ │ │ └── components │ │ │ ├── ModalHeader.vue │ │ │ ├── ModalFooter.vue │ │ │ └── Modal.tsx │ ├── Excel │ │ ├── index.ts │ │ └── src │ │ │ └── typing.ts │ └── Button │ │ ├── index.ts │ │ └── src │ │ └── props.ts ├── logics │ ├── theme │ │ ├── index.ts │ │ ├── updateGrayMode.ts │ │ ├── updateColorWeak.ts │ │ ├── util.ts │ │ └── dark.ts │ └── mitt │ │ └── routeChange.ts ├── 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 │ │ ├── main-out │ │ │ └── index.vue │ │ ├── level │ │ │ ├── Menu111.vue │ │ │ ├── Menu12.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 │ │ │ ├── verify │ │ │ │ └── Rotate.vue │ │ │ ├── strength-meter │ │ │ │ └── index.vue │ │ │ ├── scroll │ │ │ │ └── index.vue │ │ │ └── card-list │ │ │ │ └── index.vue │ │ ├── permission │ │ │ └── front │ │ │ │ ├── AuthPageA.vue │ │ │ │ └── AuthPageB.vue │ │ ├── editor │ │ │ └── tinymce │ │ │ │ └── index.vue │ │ └── table │ │ │ ├── MultipleHeader.vue │ │ │ └── MergeHeader.vue │ ├── form-design │ │ ├── typings │ │ │ └── base-type.ts │ │ ├── index.vue │ │ ├── components │ │ │ └── VFormDesign │ │ │ │ └── styles │ │ │ │ └── variable.less │ │ ├── utils │ │ │ └── message.ts │ │ ├── hooks │ │ │ └── useFormDesignState.ts │ │ └── examples │ │ │ └── baseForm.vue │ └── dashboard │ │ ├── analysis │ │ ├── components │ │ │ ├── props.ts │ │ │ └── SiteAnalysis.vue │ │ ├── data.ts │ │ └── index.vue │ │ └── workbench │ │ └── components │ │ ├── QuickNav.vue │ │ └── ProjectCard.vue ├── design │ ├── config.less │ ├── transition │ │ ├── index.less │ │ ├── base.less │ │ ├── scale.less │ │ ├── zoom.less │ │ └── slide.less │ ├── ant │ │ └── input.less │ ├── var │ │ ├── index.less │ │ ├── easing.less │ │ └── breakpoint.less │ └── index.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 ├── enums │ ├── sizeEnum.ts │ ├── roleEnum.ts │ ├── pageEnum.ts │ ├── exceptionEnum.ts │ ├── breakpointEnum.ts │ ├── httpEnum.ts │ ├── cacheEnum.ts │ ├── menuEnum.ts │ └── appEnum.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 ├── store │ └── index.ts ├── settings │ ├── siteSetting.ts │ ├── encryptionSetting.ts │ ├── localeSetting.ts │ └── designSetting.ts ├── utils │ ├── log.ts │ ├── dateUtil.ts │ ├── uuid.ts │ ├── http │ │ └── axios │ │ │ └── axiosRetry.ts │ ├── auth │ │ └── index.ts │ └── helper │ │ └── tsxHelper.tsx ├── hooks │ ├── web │ │ ├── useAppInject.ts │ │ ├── useContextMenu.ts │ │ ├── useSortable.ts │ │ ├── useDesign.ts │ │ ├── useFullContent.ts │ │ ├── usePagination.ts │ │ └── useTitle.ts │ ├── component │ │ └── usePageContext.ts │ ├── setting │ │ ├── index.ts │ │ ├── useMultipleTabSetting.ts │ │ └── useTransitionSetting.ts │ └── core │ │ └── useContext.ts ├── directives │ ├── index.ts │ ├── ripple │ │ └── index.less │ ├── permission.ts │ └── repeatClick.ts ├── layouts │ ├── default │ │ ├── trigger │ │ │ ├── index.vue │ │ │ ├── SiderTrigger.vue │ │ │ └── HeaderTrigger.vue │ │ ├── setting │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── index.ts │ │ ├── header │ │ │ └── components │ │ │ │ ├── index.ts │ │ │ │ ├── user-dropdown │ │ │ │ └── DropMenuItem.vue │ │ │ │ └── FullScreen.vue │ │ ├── tabs │ │ │ ├── types.ts │ │ │ └── components │ │ │ │ └── TabRedo.vue │ │ └── content │ │ │ └── useContentContext.ts │ ├── iframe │ │ └── index.vue │ └── page │ │ └── transition.ts ├── router │ ├── routes │ │ ├── mainOut.ts │ │ └── modules │ │ │ ├── demo │ │ │ └── flow.ts │ │ │ ├── about.ts │ │ │ ├── form-design │ │ │ └── main.ts │ │ │ └── dashboard.ts │ ├── constant.ts │ └── guard │ │ └── stateGuard.ts └── App.vue ├── .browserslistrc ├── .eslintrc.js ├── .prettierrc.js ├── public ├── logo.png ├── favicon.ico └── resource │ └── tinymce │ └── skins │ └── ui │ ├── oxide │ ├── fonts │ │ └── tinymce-mobile.woff │ ├── content.mobile.min.css │ └── skin.shadowdom.min.css │ └── oxide-dark │ ├── content.mobile.min.css │ └── skin.shadowdom.min.css ├── internal ├── eslint-config │ ├── .eslintrc.js │ ├── tsconfig.json │ └── build.config.ts ├── vite-config │ ├── .eslintrc.js │ ├── src │ │ ├── index.ts │ │ ├── utils │ │ │ └── hash.ts │ │ ├── plugins │ │ │ ├── visualizer.ts │ │ │ ├── html.ts │ │ │ ├── mock.ts │ │ │ ├── svgSprite.ts │ │ │ └── compress.ts │ │ └── config │ │ │ └── common.ts │ ├── tsconfig.json │ └── build.config.ts ├── prettier-config │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── build.config.ts │ ├── src │ │ └── index.ts │ └── package.json ├── stylelint-config │ ├── .eslintrc.js │ ├── tsconfig.json │ └── build.config.ts └── ts-config │ ├── node.json │ ├── web.json │ ├── node-server.json │ ├── package.json │ └── base.json ├── pnpm-workspace.yaml ├── .stylelintrc.js ├── .gitpod.yml ├── .prettierignore ├── types ├── utils.d.ts ├── module.d.ts └── index.d.ts ├── .husky ├── commit-msg ├── common.sh └── pre-commit ├── apps └── test-server │ ├── nodemon.json │ ├── utils.ts │ ├── tsconfig.json │ ├── README.md │ ├── controller │ ├── UserController.ts │ └── FileController.ts │ ├── ecosystem.config.js │ ├── routes.ts │ ├── service │ └── UserService.ts │ └── package.json ├── .eslintignore ├── .npmrc ├── .env.development ├── .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 ├── .editorconfig ├── turbo.json ├── .gitattributes ├── .gitignore ├── .env.production ├── .env.test ├── .env.analyze ├── tsconfig.json ├── .env.docker ├── mock ├── demo │ ├── select-demo.ts │ └── tree-demo.ts └── _createProductionServer.ts ├── monorepo.code-workspace ├── vite.config.ts └── LICENSE /CNAME: -------------------------------------------------------------------------------- 1 | vben.vvbin.cn 2 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | dist 2 | public 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # spa-title 2 | VITE_GLOB_APP_TITLE = Vben Admin 3 | -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tools'; 2 | -------------------------------------------------------------------------------- /src/components/Tree/style/index.ts: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not ie 11 5 | -------------------------------------------------------------------------------- /packages/hooks/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@vben'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/types/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@vben'], 3 | }; 4 | -------------------------------------------------------------------------------- /src/logics/theme/index.ts: -------------------------------------------------------------------------------- 1 | export async function changeTheme(_color: string) {} 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben'], 4 | }; 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('@vben/prettier-config'), 3 | }; 4 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingyuv/vue-vben-admin/HEAD/public/logo.png -------------------------------------------------------------------------------- /internal/eslint-config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@vben'], 3 | }; 4 | -------------------------------------------------------------------------------- /internal/vite-config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@vben'], 3 | }; 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'internal/*' 3 | - 'packages/*' 4 | - 'apps/*' 5 | -------------------------------------------------------------------------------- /src/views/sys/exception/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Exception } from './Exception.vue'; 2 | -------------------------------------------------------------------------------- /internal/prettier-config/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@vben'], 3 | }; 4 | -------------------------------------------------------------------------------- /internal/stylelint-config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@vben'], 3 | }; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingyuv/vue-vben-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/design/config.less: -------------------------------------------------------------------------------- 1 | @import (reference) 'color.less'; 2 | @import (reference) 'var/index.less'; 3 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/stylelint-config'], 4 | }; 5 | -------------------------------------------------------------------------------- /src/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | import BasicMenu from './src/BasicMenu.vue'; 2 | 3 | export { BasicMenu }; 4 | -------------------------------------------------------------------------------- /internal/vite-config/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config/application'; 2 | export * from './config/package'; 3 | -------------------------------------------------------------------------------- /src/assets/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingyuv/vue-vben-admin/HEAD/src/assets/images/demo.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingyuv/vue-vben-admin/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingyuv/vue-vben-admin/HEAD/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 | -------------------------------------------------------------------------------- /src/enums/sizeEnum.ts: -------------------------------------------------------------------------------- 1 | export enum SizeEnum { 2 | DEFAULT = 'default', 3 | SMALL = 'small', 4 | LARGE = 'large', 5 | } 6 | -------------------------------------------------------------------------------- /.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/Preview/index.ts: -------------------------------------------------------------------------------- 1 | export { createImgPreview } from './src/functional'; 2 | export { default as ImagePreview } from './src/Preview.vue'; 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .local 3 | .output.js 4 | node_modules 5 | 6 | **/*.svg 7 | **/*.sh 8 | 9 | public 10 | .npmrc 11 | 12 | *-lock.yaml 13 | -------------------------------------------------------------------------------- /src/components/ContextMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { createContextMenu, destroyContextMenu } from './src/createContextMenu'; 2 | export * from './src/typing'; 3 | -------------------------------------------------------------------------------- /src/components/Markdown/src/typing.ts: -------------------------------------------------------------------------------- 1 | import Vditor from 'vditor'; 2 | 3 | export interface MarkDownActionType { 4 | getVditor: () => Vditor; 5 | } 6 | -------------------------------------------------------------------------------- /packages/hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/web.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/web.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Icon/index.ts: -------------------------------------------------------------------------------- 1 | import IconPicker from './src/IconPicker.vue'; 2 | import SvgIcon from './src/SvgIcon.vue'; 3 | 4 | export { IconPicker, SvgIcon }; 5 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface MenuState { 2 | activeName: string; 3 | openNames: string[]; 4 | activeSubMenuNames: string[]; 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Time/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils/index'; 2 | 3 | import time from './src/Time.vue'; 4 | 5 | export const Time = withInstall(time); 6 | -------------------------------------------------------------------------------- /src/views/sys/iframe/FrameBlank.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/eslint-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /internal/vite-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /src/components/CountTo/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import countTo from './src/CountTo.vue'; 4 | 5 | export const CountTo = withInstall(countTo); 6 | -------------------------------------------------------------------------------- /src/locales/lang/en/routes/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dashboard: 'Dashboard', 3 | about: 'About', 4 | workbench: 'Workbench', 5 | analysis: 'Analysis', 6 | }; 7 | -------------------------------------------------------------------------------- /internal/prettier-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /internal/stylelint-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /src/components/CardList/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import cardList from './src/CardList.vue'; 4 | 5 | export const CardList = withInstall(cardList); 6 | -------------------------------------------------------------------------------- /src/components/Tinymce/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils/index'; 2 | 3 | import tinymce from './src/Editor.vue'; 4 | 5 | export const Tinymce = withInstall(tinymce); 6 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingyuv/vue-vben-admin/HEAD/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /src/components/Authority/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import authority from './src/Authority.vue'; 4 | 5 | export const Authority = withInstall(authority); 6 | -------------------------------------------------------------------------------- /src/components/FlowChart/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import flowChart from './src/FlowChart.vue'; 4 | 5 | export const FlowChart = withInstall(flowChart); 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import basicUpload from './src/BasicUpload.vue'; 4 | 5 | export const BasicUpload = withInstall(basicUpload); 6 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=./_/husky.sh 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | PATH="/usr/local/bin:$PATH" 7 | 8 | npx --no-install commitlint --edit "$1" 9 | -------------------------------------------------------------------------------- /apps/test-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/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 | -------------------------------------------------------------------------------- /src/components/VirtualScroll/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils/index'; 2 | 3 | import vScroll from './src/VirtualScroll.vue'; 4 | 5 | export const VScroll = withInstall(vScroll); 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | *.md 4 | *.woff 5 | *.ttf 6 | .vscode 7 | .idea 8 | dist 9 | /public 10 | /docs 11 | .husky 12 | .local 13 | /bin 14 | Dockerfile 15 | **/dist/** 16 | -------------------------------------------------------------------------------- /src/components/ClickOutSide/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import clickOutSide from './src/ClickOutSide.vue'; 4 | 5 | export const ClickOutSide = withInstall(clickOutSide); 6 | -------------------------------------------------------------------------------- /apps/test-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 | 3 | import strengthMeter from './src/StrengthMeter.vue'; 4 | 5 | export const StrengthMeter = withInstall(strengthMeter); 6 | -------------------------------------------------------------------------------- /src/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | import Loading from './src/Loading.vue'; 2 | 3 | export { Loading }; 4 | export { createLoading } from './src/createLoading'; 5 | export { useLoading } from './src/useLoading'; 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/Dropdown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import dropdown from './src/Dropdown.vue'; 4 | 5 | export * from './src/typing'; 6 | export const Dropdown = withInstall(dropdown); 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /apps/test-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node-server.json", 4 | "compilerOptions": { 5 | "noImplicitAny": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/views/demo/feat/breadcrumb/FlatListDetail.vue: -------------------------------------------------------------------------------- 1 | 2 | 平级详情页 3 | 4 | 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/views/form-design/typings/base-type.ts: -------------------------------------------------------------------------------- 1 | export interface IAnyObject { 2 | [key: string]: T; 3 | } 4 | 5 | export interface IInputEvent { 6 | target: { 7 | value: any; 8 | checked: boolean; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /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/components/Tree/index.ts: -------------------------------------------------------------------------------- 1 | import './style'; 2 | 3 | import BasicTree from './src/BasicTree.vue'; 4 | 5 | export { BasicTree }; 6 | export * from './src/types/tree'; 7 | export type { ContextMenuItem } from '@/hooks/web/useContextMenu'; 8 | -------------------------------------------------------------------------------- /packages/hooks/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './onMountedOrActivated'; 2 | export * from './useAttrs'; 3 | export * from './useRefs'; 4 | export * from './useScrollTo'; 5 | export * from './useWindowSizeFn'; 6 | export { useTimeoutFn } from '@vueuse/core'; 7 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | import type { App } from 'vue'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /packages/hooks/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/types/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /internal/eslint-config/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /internal/vite-config/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/registerGlobComp.ts: -------------------------------------------------------------------------------- 1 | import { Input, Layout } from 'ant-design-vue'; 2 | import type { App } from 'vue'; 3 | 4 | import { Button } from './Button'; 5 | 6 | export function registerGlobComp(app: App) { 7 | app.use(Input).use(Button).use(Layout); 8 | } 9 | -------------------------------------------------------------------------------- /internal/prettier-config/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /internal/stylelint-config/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | [ -n "$CI" ] && exit 0 6 | 7 | PATH="/usr/local/bin:$PATH" 8 | 9 | # Format and submit code according to lintstagedrc.js configuration 10 | pnpm exec lint-staged 11 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=husky 2 | public-hoist-pattern[]=*eslint* 3 | public-hoist-pattern[]=*prettier* 4 | public-hoist-pattern[]=lint-staged 5 | public-hoist-pattern[]=*stylelint* 6 | public-hoist-pattern[]=@commitlint/cli 7 | public-hoist-pattern[]=@vben/eslint-config 8 | -------------------------------------------------------------------------------- /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/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://doc.vvbin.cn/'; 6 | 7 | // site url 8 | export const SITE_URL = 'https://vben.vvbin.cn/'; 9 | -------------------------------------------------------------------------------- /src/components/Description/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import description from './src/Description.vue'; 4 | 5 | export * from './src/typing'; 6 | export { useDescription } from './src/useDescription'; 7 | export const Description = withInstall(description); 8 | -------------------------------------------------------------------------------- /src/components/Drawer/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import basicDrawer from './src/BasicDrawer.vue'; 4 | 5 | export const BasicDrawer = withInstall(basicDrawer); 6 | export * from './src/typing'; 7 | export { useDrawer, useDrawerInner } from './src/useDrawer'; 8 | -------------------------------------------------------------------------------- /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/views/demo/main-out/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 位于主框架外的页面 4 | Back 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /apps/test-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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/vite-config/src/utils/hash.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from 'node:crypto'; 2 | 3 | function createContentHash(content: string, hashLSize = 12) { 4 | const hash = createHash('sha256').update(content); 5 | return hash.digest('hex').slice(0, hashLSize); 6 | } 7 | 8 | export { createContentHash }; 9 | -------------------------------------------------------------------------------- /src/components/CountDown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import countButton from './src/CountButton.vue'; 4 | import countdownInput from './src/CountdownInput.vue'; 5 | 6 | export const CountdownInput = withInstall(countdownInput); 7 | export const CountButton = withInstall(countButton); 8 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Basic interface address SPA 8 | VITE_GLOB_API_URL=/basic-api 9 | 10 | # File upload address, optional 11 | VITE_GLOB_UPLOAD_URL=/upload 12 | 13 | # Interface prefix 14 | VITE_GLOB_API_URL_PREFIX= 15 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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/Markdown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import markDown from './src/Markdown.vue'; 4 | import markDownViewer from './src/MarkdownViewer.vue'; 5 | 6 | export const MarkDown = withInstall(markDown); 7 | export const MarkdownViewer = withInstall(markDownViewer); 8 | export * from './src/typing'; 9 | -------------------------------------------------------------------------------- /src/hooks/web/useAppInject.ts: -------------------------------------------------------------------------------- 1 | import { computed, unref } from 'vue'; 2 | 3 | import { useAppProviderContext } from '@/components/Application'; 4 | 5 | export function useAppInject() { 6 | const values = useAppProviderContext(); 7 | 8 | return { 9 | getIsMobile: computed(() => unref(values.isMobile)), 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Cropper/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import cropperImage from './src/Cropper.vue'; 4 | import avatarCropper from './src/CropperAvatar.vue'; 5 | 6 | export * from './src/typing'; 7 | export const CropperImage = withInstall(cropperImage); 8 | export const CropperAvatar = withInstall(avatarCropper); 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/api/demo/cascader.ts: -------------------------------------------------------------------------------- 1 | import { AreaModel, AreaParams } from '@/api/demo/model/areaModel'; 2 | import { defHttp } from '@/utils/http/axios'; 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/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 | -------------------------------------------------------------------------------- /.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/components/CodeEditor/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import codeEditor from './src/CodeEditor.vue'; 4 | import jsonPreview from './src/json-preview/JsonPreview.vue'; 5 | 6 | export const CodeEditor = withInstall(codeEditor); 7 | export const JsonPreview = withInstall(jsonPreview); 8 | 9 | export * from './src/typing'; 10 | -------------------------------------------------------------------------------- /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 | | 'RadioGroup' 13 | | 'RadioButtonGroup' 14 | | 'ApiRadioGroup'; 15 | -------------------------------------------------------------------------------- /src/components/Verify/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils/index'; 2 | 3 | import basicDragVerify from './src/DragVerify.vue'; 4 | import rotateDragVerify from './src/ImgRotate.vue'; 5 | 6 | export const BasicDragVerify = withInstall(basicDragVerify); 7 | export const RotateDragVerify = withInstall(rotateDragVerify); 8 | export * from './src/typing'; 9 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN.ts: -------------------------------------------------------------------------------- 1 | import antdLocale from 'ant-design-vue/es/locale/zh_CN'; 2 | 3 | import { genMessage } from '../helper'; 4 | 5 | const modules = import.meta.glob('./zh-CN/**/*.ts', { eager: true }); 6 | export default { 7 | message: { 8 | ...genMessage(modules as Recordable, 'zh-CN'), 9 | antdLocale, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /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 | export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight'; 12 | -------------------------------------------------------------------------------- /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: 10 | 0.2s height ease-in-out, 11 | 0.2s padding-top ease-in-out, 12 | 0.2s padding-bottom ease-in-out; 13 | } 14 | -------------------------------------------------------------------------------- /src/views/demo/level/Menu111.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 多层级缓存-页面1-1-1 4 | 5 | 6 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /src/views/demo/level/Menu12.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 多层级缓存-页面1-2 4 | 5 | 6 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "Vue.vscode-typescript-vue-plugin", 5 | "dbaeumer.vscode-eslint", 6 | "stylelint.vscode-stylelint", 7 | "esbenp.prettier-vscode", 8 | "mrmlnc.vscode-less", 9 | "lokalise.i18n-ally", 10 | "antfu.iconify", 11 | "mikestead.dotenv", 12 | "heybourn.headwind" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /internal/ts-config/node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node Config", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["ESNext"], 7 | "noImplicitAny": true, 8 | "sourceMap": true, 9 | "baseUrl": "./", 10 | "composite": false, 11 | "allowSyntheticDefaultImports": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Application/src/search/AppSearchKeyItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /src/views/form-design/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**"] 7 | }, 8 | "stub": {}, 9 | "lint": {}, 10 | "clean": { 11 | "cache": false 12 | }, 13 | "dev": { 14 | "cache": false, 15 | "persistent": true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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/feat/breadcrumb/ChildrenListDetail.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 子级详情页内容在此 4 | 5 | 6 | 13 | -------------------------------------------------------------------------------- /src/components/Basic/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import basicArrow from './src/BasicArrow.vue'; 4 | import basicHelp from './src/BasicHelp.vue'; 5 | import basicTitle from './src/BasicTitle.vue'; 6 | 7 | export const BasicArrow = withInstall(basicArrow); 8 | export const BasicTitle = withInstall(basicTitle); 9 | export const BasicHelp = withInstall(basicHelp); 10 | -------------------------------------------------------------------------------- /src/components/Container/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import collapseContainer from './src/collapse/CollapseContainer.vue'; 4 | import scrollContainer from './src/ScrollContainer.vue'; 5 | 6 | export const CollapseContainer = withInstall(collapseContainer); 7 | export const ScrollContainer = withInstall(scrollContainer); 8 | 9 | export * from './src/typing'; 10 | -------------------------------------------------------------------------------- /src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | import './src/index.less'; 2 | 3 | import { withInstall } from '@/utils'; 4 | 5 | import basicModal from './src/BasicModal.vue'; 6 | 7 | export const BasicModal = withInstall(basicModal); 8 | export { useModal, useModalInner } from './src/hooks/useModal'; 9 | export { useModalContext } from './src/hooks/useModalContext'; 10 | export * from './src/typing'; 11 | -------------------------------------------------------------------------------- /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/level/Menu2.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 多层级缓存-页面2 4 | 5 | 6 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /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/api/sys/menu.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '@/utils/http/axios'; 2 | 3 | import { getMenuListResultModel } from './model/menuModel'; 4 | 5 | enum Api { 6 | GetMenuList = '/getMenuList', 7 | } 8 | 9 | /** 10 | * @description: Get user menu based on id 11 | */ 12 | 13 | export const getMenuList = () => { 14 | return defHttp.get({ url: Api.GetMenuList }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/locales/lang/en.ts: -------------------------------------------------------------------------------- 1 | import antdLocale from 'ant-design-vue/es/locale/en_US'; 2 | 3 | import { genMessage } from '../helper'; 4 | 5 | const modules = import.meta.glob('./en/**/*.ts', { eager: true }); 6 | export default { 7 | message: { 8 | ...genMessage(modules as Recordable, 'en'), 9 | antdLocale, 10 | }, 11 | dateLocale: null, 12 | dateLocaleName: 'en', 13 | }; 14 | -------------------------------------------------------------------------------- /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/api/demo/model/optionsModel.ts: -------------------------------------------------------------------------------- 1 | import { BasicFetchResult } from '@/api/model/baseModel'; 2 | 3 | export interface DemoOptionsItem { 4 | name: string; 5 | id: 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/components/Excel/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import expExcelModal from './src/ExportExcelModal.vue'; 4 | import impExcel from './src/ImportExcel.vue'; 5 | 6 | export const ImpExcel = withInstall(impExcel); 7 | export const ExpExcelModal = withInstall(expExcelModal); 8 | export { aoaToSheetXlsx, jsonToSheetXlsx } from './src/Export2Excel'; 9 | export * from './src/typing'; 10 | -------------------------------------------------------------------------------- /.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/components/FlowChart/src/types.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfig } from '@logicflow/core'; 2 | 3 | import { ToolbarTypeEnum } from './enum'; 4 | 5 | export interface NodeItem extends NodeConfig { 6 | icon: string; 7 | } 8 | 9 | export interface ToolbarConfig { 10 | type?: string | ToolbarTypeEnum; 11 | tooltip?: string | boolean; 12 | icon?: string; 13 | disabled?: boolean; 14 | separate?: boolean; 15 | } 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/ts-config/web.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Web Application", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "useDefineForClassFields": true, 7 | "jsx": "preserve", 8 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 9 | "noImplicitAny": false, 10 | "moduleResolution": "bundler", 11 | "types": ["vite/client"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/api/demo/select.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '@/utils/http/axios'; 2 | 3 | import { DemoOptionsItem, selectParams } from './model/optionsModel'; 4 | 5 | enum Api { 6 | OPTIONS_LIST = '/select/getDemoOptions', 7 | } 8 | 9 | /** 10 | * @description: Get sample options value 11 | */ 12 | export const optionsListApi = (params?: selectParams) => 13 | defHttp.get({ url: Api.OPTIONS_LIST, params }); 14 | -------------------------------------------------------------------------------- /src/components/Table/src/components/EditTableHeaderIcon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | 6 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /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/components/Form/src/hooks/useComponentRegister.ts: -------------------------------------------------------------------------------- 1 | import { tryOnUnmounted } from '@vueuse/core'; 2 | import type { Component } from 'vue'; 3 | 4 | import { add, del } from '../componentMap'; 5 | import type { ComponentType } from '../types/index'; 6 | 7 | export function useComponentRegister(compName: ComponentType, comp: Component) { 8 | add(compName, comp); 9 | tryOnUnmounted(() => { 10 | del(compName); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /apps/test-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 | -------------------------------------------------------------------------------- /internal/vite-config/src/plugins/visualizer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Package file volume analysis 3 | */ 4 | import visualizer from 'rollup-plugin-visualizer'; 5 | import { type PluginOption } from 'vite'; 6 | 7 | export function configVisualizerConfig() { 8 | return visualizer({ 9 | filename: './node_modules/.cache/visualizer/stats.html', 10 | open: true, 11 | gzipSize: true, 12 | brotliSize: true, 13 | }) as PluginOption; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/CodeEditor/src/json-preview/JsonPreview.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /src/api/sys/model/menuModel.ts: -------------------------------------------------------------------------------- 1 | import type { RouteMeta } from 'vue-router'; 2 | 3 | export interface RouteItem { 4 | path: string; 5 | component: any; 6 | meta: RouteMeta; 7 | name?: string; 8 | alias?: string | string[]; 9 | redirect?: string; 10 | caseSensitive?: boolean; 11 | children?: RouteItem[]; 12 | } 13 | 14 | /** 15 | * @description: Get menu return value 16 | */ 17 | export type getMenuListResultModel = RouteItem[]; 18 | -------------------------------------------------------------------------------- /src/views/demo/comp/drawer/Drawer1.vue: -------------------------------------------------------------------------------- 1 | 2 | Drawer Info. 3 | 4 | 16 | -------------------------------------------------------------------------------- /src/components/FlowChart/src/useFlowContext.ts: -------------------------------------------------------------------------------- 1 | import type LogicFlow from '@logicflow/core'; 2 | import { inject, provide } from 'vue'; 3 | 4 | const key = Symbol('flow-chart'); 5 | 6 | interface Instance { 7 | logicFlow: LogicFlow; 8 | } 9 | 10 | export function createFlowChartContext(instance: Instance) { 11 | provide(key, instance); 12 | } 13 | 14 | export function useFlowChartContext(): Instance { 15 | return inject(key) as Instance; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Tree/src/TreeIcon.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'lodash-es'; 2 | import type { FunctionalComponent, VNode } from 'vue'; 3 | import { h } from 'vue'; 4 | 5 | import Icon from '@/components/Icon/Icon.vue'; 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/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 | -------------------------------------------------------------------------------- /src/views/demo/feat/breadcrumb/FlatList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 进入平级详情页 4 | 5 | 6 | 15 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure and register global directives 3 | */ 4 | import type { App } from 'vue'; 5 | 6 | import { setupEllipsisDirective } from './ellipsis'; 7 | import { setupLoadingDirective } from './loading'; 8 | import { setupPermissionDirective } from './permission'; 9 | 10 | export function setupGlobDirectives(app: App) { 11 | setupPermissionDirective(app); 12 | setupLoadingDirective(app); 13 | setupEllipsisDirective(app); 14 | } 15 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /apps/test-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/Menu/src/components/BasicMenuItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/vite-config/src/plugins/html.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Plugin to minimize and use ejs template syntax in index.html. 3 | * https://github.com/anncwb/vite-plugin-html 4 | */ 5 | import type { PluginOption } from 'vite'; 6 | import { createHtmlPlugin } from 'vite-vue-plugin-html'; 7 | 8 | export function configHtmlPlugin({ isBuild }: { isBuild: boolean }) { 9 | const htmlPlugin: PluginOption[] = createHtmlPlugin({ 10 | minify: isBuild, 11 | }); 12 | return htmlPlugin; 13 | } 14 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /src/views/demo/feat/breadcrumb/ChildrenList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 进入子级详情页 4 | 5 | 6 | 15 | -------------------------------------------------------------------------------- /src/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | import type { ExtractPropTypes } from 'vue'; 2 | 3 | import { withInstall } from '@/utils'; 4 | 5 | import button from './src/BasicButton.vue'; 6 | import popConfirmButton from './src/PopConfirmButton.vue'; 7 | import { buttonProps } from './src/props'; 8 | 9 | export const Button = withInstall(button); 10 | export const PopConfirmButton = withInstall(popConfirmButton); 11 | export declare type ButtonProps = Partial>; 12 | -------------------------------------------------------------------------------- /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/views/sys/lock/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/SiderTrigger.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/views/demo/comp/drawer/Drawer5.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Content Message 4 | toolbar 5 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings 2 | 3 | # Automatically normalize line endings (to LF) for all text-based files. 4 | * text=auto eol=lf 5 | 6 | # Declare files that will always have CRLF line endings on checkout. 7 | *.{cmd,[cC][mM][dD]} text eol=crlf 8 | *.{bat,[bB][aA][tT]} text eol=crlf 9 | 10 | # Denote all files that are truly binary and should not be modified. 11 | *.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary -------------------------------------------------------------------------------- /src/api/demo/table.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '@/utils/http/axios'; 2 | 3 | import { DemoListGetResultModel, DemoParams } from './model/tableModel'; 4 | 5 | enum Api { 6 | DEMO_LIST = '/table/getDemoList', 7 | } 8 | 9 | /** 10 | * @description: Get sample list value 11 | */ 12 | 13 | export const demoListApi = (params: DemoParams) => 14 | defHttp.get({ 15 | url: Api.DEMO_LIST, 16 | params, 17 | headers: { 18 | ignoreCancelToken: true, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/components/Menu/src/types.ts: -------------------------------------------------------------------------------- 1 | import { Key } from 'ant-design-vue/es/_util/type'; 2 | 3 | export interface MenuState { 4 | // 默认选中的列表 5 | defaultSelectedKeys: Key[]; 6 | 7 | // 模式 8 | // mode: MenuModeEnum; 9 | 10 | // // 主题 11 | // theme: ComputedRef | ThemeEnum; 12 | 13 | // 缩进 14 | inlineIndent?: number; 15 | 16 | // 展开数组 17 | openKeys: Key[]; 18 | 19 | // 当前选中的菜单项 key 数组 20 | selectedKeys: Key[]; 21 | 22 | // 收缩状态下展开的数组 23 | collapsedOpenKeys: Key[]; 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | .cache 5 | .turbo 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 | .history 35 | -------------------------------------------------------------------------------- /src/components/ClickOutSide/src/ClickOutSide.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 21 | -------------------------------------------------------------------------------- /src/views/form-design/components/VFormDesign/styles/variable.less: -------------------------------------------------------------------------------- 1 | // 表单设计器样式 2 | @primary-color: #13c2c2; 3 | @layout-color: #9867f7; 4 | 5 | @primary-background-color: fade(@primary-color, 6%); 6 | @primary-hover-bg-color: fade(@primary-color, 20%); 7 | @layout-background-color: fade(@layout-color, 12%); 8 | @layout-hover-bg-color: fade(@layout-color, 24%); 9 | 10 | @title-text-color: #fff; 11 | @border-color: #ccc; 12 | 13 | @left-right-width: 280px; 14 | @header-height: 56px; 15 | @operating-area-height: 45px; 16 | -------------------------------------------------------------------------------- /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/layouts/default/setting/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/views/form-design/utils/message.ts: -------------------------------------------------------------------------------- 1 | import { useMessage } from '@/hooks/web/useMessage'; 2 | 3 | const { createMessage } = useMessage(); 4 | const message = Object.assign({ 5 | success: (msg: string) => { 6 | createMessage.success(msg); 7 | }, 8 | error: (msg: string) => { 9 | createMessage.error(msg); 10 | }, 11 | warning: (msg: string) => { 12 | createMessage.warning(msg); 13 | }, 14 | info: (msg: string) => { 15 | createMessage.info(msg); 16 | }, 17 | }); 18 | 19 | export default message; 20 | -------------------------------------------------------------------------------- /apps/test-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/hooks/web/useContextMenu.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, onUnmounted } from 'vue'; 2 | 3 | import type { ContextMenuItem } from '@/components/ContextMenu'; 4 | import { createContextMenu, destroyContextMenu } from '@/components/ContextMenu'; 5 | 6 | export type { ContextMenuItem }; 7 | export function useContextMenu(authRemove = true) { 8 | if (getCurrentInstance() && authRemove) { 9 | onUnmounted(() => { 10 | destroyContextMenu(); 11 | }); 12 | } 13 | return [createContextMenu, destroyContextMenu]; 14 | } 15 | -------------------------------------------------------------------------------- /src/views/demo/comp/modal/Modal3.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 根据屏幕高度自适应 4 | 5 | 6 | 18 | -------------------------------------------------------------------------------- /types/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue'; 3 | 4 | const Component: DefineComponent; 5 | export default Component; 6 | } 7 | 8 | declare module 'ant-design-vue/es/locale/*' { 9 | import { Locale } from 'ant-design-vue/types/locale-provider'; 10 | 11 | const locale: Locale & ReadonlyRecordable; 12 | export default locale as Locale & ReadonlyRecordable; 13 | } 14 | 15 | declare module 'virtual:*' { 16 | const result: any; 17 | export default result; 18 | } 19 | -------------------------------------------------------------------------------- /src/views/demo/permission/front/AuthPageA.vue: -------------------------------------------------------------------------------- 1 | 2 | Super 角色可见 3 | 4 | 9 | 21 | -------------------------------------------------------------------------------- /src/views/demo/permission/front/AuthPageB.vue: -------------------------------------------------------------------------------- 1 | 2 | Test 角色可见 3 | 4 | 9 | 21 | -------------------------------------------------------------------------------- /apps/test-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/design/ant/input.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../color.less'; 2 | 3 | // input 4 | .ant-input { 5 | &-number, 6 | &-number-group-wrapper { 7 | width: 100% !important; 8 | min-width: 110px; 9 | max-width: 100%; 10 | } 11 | } 12 | 13 | .ant-input-affix-wrapper .ant-input-suffix { 14 | right: 9px; 15 | } 16 | 17 | .ant-input-clear-icon { 18 | margin-right: 5px; 19 | } 20 | 21 | .ant-input-affix-wrapper-textarea-with-clear-btn { 22 | padding: 0 !important; 23 | 24 | textarea.ant-input { 25 | padding: 4px; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/ts-config/node-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node Server Config", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "module": "commonjs", 7 | "declaration": false, 8 | "removeComments": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": false, 13 | "esModuleInterop": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./" 16 | }, 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Modal/src/hooks/useModalContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey } from 'vue'; 2 | 3 | import { createContext, useContext } from '@/hooks/core/useContext'; 4 | 5 | export interface ModalContextProps { 6 | redoModalHeight: () => void; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createModalContext(context: ModalContextProps) { 12 | return createContext(context, key); 13 | } 14 | 15 | export function useModalContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /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 { BasicFetchResult, BasicPageParams } 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/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(date?: dayjs.ConfigType, format = DATE_TIME_FORMAT): string { 10 | return dayjs(date).format(format); 11 | } 12 | 13 | export function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): string { 14 | return dayjs(date).format(format); 15 | } 16 | 17 | export const dateUtil = dayjs; 18 | -------------------------------------------------------------------------------- /src/views/demo/feat/request-demo/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 点击会重新发起请求5次 4 | 打开浏览器的network面板,可以看到发出了六次请求 5 | 6 | 7 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Whether to enable gzip or brotli compression 8 | # Optional: gzip | brotli | none 9 | # If you need multiple forms, you can use `,` to separate 10 | VITE_BUILD_COMPRESS = 'none' 11 | 12 | 13 | # Basic interface address SPA 14 | VITE_GLOB_API_URL=/basic-api 15 | 16 | # File upload address, optional 17 | # It can be forwarded by nginx or write the actual address directly 18 | VITE_GLOB_UPLOAD_URL=/upload 19 | 20 | # Interface prefix 21 | VITE_GLOB_API_URL_PREFIX= 22 | -------------------------------------------------------------------------------- /apps/test-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 | -------------------------------------------------------------------------------- /packages/hooks/src/useRefs.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import { onBeforeUpdate, shallowRef } from 'vue'; 3 | 4 | function useRefs(): { 5 | refs: Ref; 6 | setRefs: (index: number) => (el: HTMLElement) => void; 7 | } { 8 | const refs = shallowRef([]) as Ref; 9 | 10 | onBeforeUpdate(() => { 11 | refs.value = []; 12 | }); 13 | 14 | const setRefs = (index: number) => (el: HTMLElement) => { 15 | refs.value[index] = el; 16 | }; 17 | 18 | return { 19 | refs, 20 | setRefs, 21 | }; 22 | } 23 | 24 | export { useRefs }; 25 | -------------------------------------------------------------------------------- /internal/prettier-config/src/index.ts: -------------------------------------------------------------------------------- 1 | export default { 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 | plugins: ['prettier-plugin-packagejson'], 11 | overrides: [ 12 | { 13 | files: '.*rc', 14 | options: { 15 | parser: 'json', 16 | }, 17 | }, 18 | { 19 | files: '*.html', 20 | options: { 21 | parser: 'html', 22 | }, 23 | }, 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /packages/hooks/src/onMountedOrActivated.ts: -------------------------------------------------------------------------------- 1 | import { type AnyFunction } from '@vben/types'; 2 | import { nextTick, onActivated, onMounted } from 'vue'; 3 | 4 | /** 5 | * 在 OnMounted 或者 OnActivated 时触发 6 | * @param hook 任何函数(包括异步函数) 7 | */ 8 | function onMountedOrActivated(hook: AnyFunction) { 9 | let mounted: boolean; 10 | 11 | onMounted(() => { 12 | hook(); 13 | nextTick(() => { 14 | mounted = true; 15 | }); 16 | }); 17 | 18 | onActivated(() => { 19 | if (mounted) { 20 | hook(); 21 | } 22 | }); 23 | } 24 | 25 | export { onMountedOrActivated }; 26 | -------------------------------------------------------------------------------- /.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 | # Whether to enable gzip or brotli compression 9 | # Optional: gzip | brotli | none 10 | # If you need multiple forms, you can use `,` to separate 11 | VITE_BUILD_COMPRESS = 'none' 12 | 13 | # Basic interface address SPA 14 | VITE_GLOB_API_URL=/basic-api 15 | 16 | # File upload address, optional 17 | # It can be forwarded by nginx or write the actual address directly 18 | VITE_GLOB_UPLOAD_URL=/upload 19 | 20 | # Interface prefix 21 | VITE_GLOB_API_URL_PREFIX= 22 | -------------------------------------------------------------------------------- /internal/vite-config/src/plugins/mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock plugin for development and production. 3 | * https://github.com/anncwb/vite-plugin-mock 4 | */ 5 | import { viteMockServe } from 'vite-plugin-mock'; 6 | 7 | export function configMockPlugin({ isBuild }: { isBuild: boolean }) { 8 | return viteMockServe({ 9 | ignore: /^_/, 10 | mockPath: 'mock', 11 | localEnabled: !isBuild, 12 | prodEnabled: isBuild, 13 | injectCode: ` 14 | import { setupProdMockServer } from '../mock/_createProductionServer'; 15 | 16 | setupProdMockServer(); 17 | `, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Form/src/hooks/useFormContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from 'vue'; 2 | 3 | import { createContext, useContext } from '@/hooks/core/useContext'; 4 | 5 | export interface FormContextProps { 6 | resetAction: () => Promise; 7 | submitAction: () => Promise; 8 | } 9 | 10 | const key: InjectionKey = Symbol(); 11 | 12 | export function createFormContext(context: FormContextProps) { 13 | return createContext(context, key); 14 | } 15 | 16 | export function useFormContext() { 17 | return useContext(key); 18 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/vite-config/src/plugins/svgSprite.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vite Plugin for fast creating SVG sprites. 3 | * https://github.com/anncwb/vite-plugin-svg-icons 4 | */ 5 | 6 | import { resolve } from 'node:path'; 7 | 8 | import type { PluginOption } from 'vite'; 9 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; 10 | 11 | export function configSvgIconsPlugin({ isBuild }: { isBuild: boolean }) { 12 | const svgIconsPlugin = createSvgIconsPlugin({ 13 | iconDirs: [resolve(process.cwd(), 'src/assets/icons')], 14 | svgoOptions: isBuild, 15 | }); 16 | return svgIconsPlugin as PluginOption; 17 | } 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{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} 8 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/ModalHeader.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ title }} 4 | 5 | 6 | 22 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/index.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'; 2 | 3 | import FullScreen from './FullScreen.vue'; 4 | 5 | export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { 6 | loading: true, 7 | }); 8 | 9 | export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue')); 10 | 11 | export const Notify = createAsyncComponent(() => import('./notify/index.vue')); 12 | 13 | export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue')); 14 | 15 | export { FullScreen }; 16 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/QuickNav.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ item.title }} 7 | 8 | 9 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /src/views/demo/comp/flow-chart/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 25 | -------------------------------------------------------------------------------- /.env.analyze: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Whether to enable gzip or brotli compression 8 | # Optional: gzip | brotli | none 9 | # If you need multiple forms, you can use `,` to separate 10 | VITE_BUILD_COMPRESS = 'none' 11 | 12 | 13 | # Basic interface address SPA 14 | VITE_GLOB_API_URL=/basic-api 15 | 16 | # File upload address, optional 17 | # It can be forwarded by nginx or write the actual address directly 18 | VITE_GLOB_UPLOAD_URL=/upload 19 | 20 | # Interface prefix 21 | VITE_GLOB_API_URL_PREFIX= 22 | 23 | VITE_ENABLE_ANALYZE = true 24 | -------------------------------------------------------------------------------- /src/components/Application/src/useAppContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | 3 | import { createContext, useContext } from '@/hooks/core/useContext'; 4 | 5 | export interface AppProviderContextProps { 6 | prefixCls: Ref; 7 | isMobile: Ref; 8 | } 9 | 10 | const key: InjectionKey = Symbol(); 11 | 12 | export function createAppProviderContext(context: AppProviderContextProps) { 13 | return createContext(context, key); 14 | } 15 | 16 | export function useAppProviderContext() { 17 | return useContext(key); 18 | } 19 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/types.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocationNormalized } from 'vue-router'; 2 | 3 | import type { DropMenu } from '@/components/Dropdown/index'; 4 | 5 | export enum TabContentEnum { 6 | TAB_TYPE, 7 | EXTRA_TYPE, 8 | } 9 | 10 | export type { DropMenu }; 11 | 12 | export interface TabContentProps { 13 | tabItem: RouteLocationNormalized; 14 | type?: TabContentEnum; 15 | trigger?: ('click' | 'hover' | 'contextmenu')[]; 16 | } 17 | 18 | export enum MenuEventEnum { 19 | REFRESH_PAGE, 20 | CLOSE_CURRENT, 21 | CLOSE_LEFT, 22 | CLOSE_RIGHT, 23 | CLOSE_OTHER, 24 | CLOSE_ALL, 25 | SCALE, 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/web/useSortable.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'sortablejs'; 2 | import type { Ref } from 'vue'; 3 | import { nextTick, unref } from 'vue'; 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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/web.json", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "types": ["vite/client", "types/global.d.ts"], 7 | "paths": { 8 | "@/*": ["src/*"], 9 | "#/*": ["types/*"] 10 | } 11 | }, 12 | "include": [ 13 | "tests", 14 | "src", 15 | "types/**/*.d.ts", 16 | "src/**/*.ts", 17 | "src/**/*.d.ts", 18 | "src/**/*.tsx", 19 | "src/**/*.vue", 20 | "mock", 21 | "vite.config.ts" 22 | ], 23 | "exclude": ["node_modules", "tests/server/**/*.ts", "dist", "**/*.js"] 24 | } 25 | -------------------------------------------------------------------------------- /.env.docker: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = false 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # timeout(seconds) 8 | VITE_TIMEOUT = 15 9 | # Delete console 10 | VITE_DROP_CONSOLE = true 11 | 12 | # Whether to enable gzip or brotli compression 13 | # Optional: gzip | brotli | none 14 | # If you need multiple forms, you can use `,` to separate 15 | VITE_BUILD_COMPRESS = 'none' 16 | VITE_GLOB_API_URL="__vg_base_url" 17 | 18 | # File upload address, optional 19 | # It can be forwarded by nginx or write the actual address directly 20 | VITE_GLOB_UPLOAD_URL=/files/upload 21 | # Interface prefix 22 | VITE_GLOB_API_URL_PREFIX= 23 | -------------------------------------------------------------------------------- /src/api/sys/upload.ts: -------------------------------------------------------------------------------- 1 | import { useGlobSetting } from '@/hooks/setting'; 2 | import { defHttp } from '@/utils/http/axios'; 3 | import { UploadFileParams } from '#/axios'; 4 | 5 | import { UploadApiResult } from './model/uploadModel'; 6 | 7 | const { uploadUrl = '' } = useGlobSetting(); 8 | 9 | /** 10 | * @description: Upload interface 11 | */ 12 | export function uploadApi( 13 | params: UploadFileParams, 14 | onUploadProgress: (progressEvent: ProgressEvent) => void, 15 | ) { 16 | return defHttp.uploadFile( 17 | { 18 | url: uploadUrl, 19 | onUploadProgress, 20 | }, 21 | params, 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/design/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: 5 | opacity 0.1 ease-in-out, 6 | transform 0.15s ease-out; 7 | } 8 | 9 | .zoom-out-enter-from, 10 | .zoom-out-leave-to { 11 | opacity: 0; 12 | transform: scale(0); 13 | } 14 | 15 | // zoom-fade 16 | .zoom-fade-enter-active, 17 | .zoom-fade-leave-active { 18 | transition: 19 | transform 0.2s, 20 | opacity 0.3s ease-out; 21 | } 22 | 23 | .zoom-fade-enter-from { 24 | opacity: 0; 25 | transform: scale(0.92); 26 | } 27 | 28 | .zoom-fade-leave-to { 29 | opacity: 0; 30 | transform: scale(1.06); 31 | } 32 | -------------------------------------------------------------------------------- /src/api/demo/account.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '@/utils/http/axios'; 2 | 3 | import { GetAccountInfoModel } from './model/accountModel'; 4 | 5 | enum Api { 6 | ACCOUNT_INFO = '/account/getAccountInfo', 7 | SESSION_TIMEOUT = '/user/sessionTimeout', 8 | TOKEN_EXPIRED = '/user/tokenExpired', 9 | } 10 | 11 | // Get personal center-basic settings 12 | 13 | export const accountInfoApi = () => defHttp.get({ url: Api.ACCOUNT_INFO }); 14 | 15 | export const sessionTimeoutApi = () => defHttp.post({ url: Api.SESSION_TIMEOUT }); 16 | 17 | export const tokenExpiredApi = () => defHttp.post({ url: Api.TOKEN_EXPIRED }); 18 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { computed, ComputedRef, ref, unref, watch } from 'vue'; 2 | 3 | import type { BasicTableProps } from '../types/table'; 4 | 5 | export function useLoading(props: ComputedRef) { 6 | const loadingRef = ref(unref(props).loading); 7 | 8 | watch( 9 | () => unref(props).loading, 10 | (loading) => { 11 | loadingRef.value = loading; 12 | }, 13 | ); 14 | 15 | const getLoading = computed(() => unref(loadingRef)); 16 | 17 | function setLoading(loading: boolean) { 18 | loadingRef.value = loading; 19 | } 20 | 21 | return { getLoading, setLoading }; 22 | } 23 | -------------------------------------------------------------------------------- /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/logics/theme/dark.ts: -------------------------------------------------------------------------------- 1 | import { addClass, hasClass, removeClass } from '@/utils/domUtils'; 2 | 3 | export async function updateDarkTheme(mode: string | null = 'light') { 4 | const htmlRoot = document.getElementById('htmlRoot'); 5 | if (!htmlRoot) { 6 | return; 7 | } 8 | const hasDarkClass = hasClass(htmlRoot, 'dark'); 9 | if (mode === 'dark') { 10 | htmlRoot.setAttribute('data-theme', 'dark'); 11 | if (!hasDarkClass) { 12 | addClass(htmlRoot, 'dark'); 13 | } 14 | } else { 15 | htmlRoot.setAttribute('data-theme', 'light'); 16 | if (hasDarkClass) { 17 | removeClass(htmlRoot, 'dark'); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTable } from './src/BasicTable.vue'; 2 | export type { EditRecordRow } from './src/components/editable'; 3 | export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'; 4 | export { default as TableAction } from './src/components/TableAction.vue'; 5 | export { default as TableImg } from './src/components/TableImg.vue'; 6 | export { useTable } from './src/hooks/useTable'; 7 | export * from './src/types/pagination'; 8 | export * from './src/types/table'; 9 | export * from './src/types/tableAction'; 10 | export type { FormProps, FormSchema } from '@/components/Form/src/types/form'; 11 | -------------------------------------------------------------------------------- /src/views/demo/comp/drawer/Drawer2.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Drawer Info. 4 | 内部关闭drawer 5 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /src/components/Upload/src/ThumbUrl.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 27 | -------------------------------------------------------------------------------- /src/layouts/default/content/useContentContext.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, InjectionKey } from 'vue'; 2 | 3 | import { createContext, useContext } from '@/hooks/core/useContext'; 4 | 5 | export interface ContentContextProps { 6 | contentHeight: ComputedRef; 7 | setPageHeight: (height: number) => Promise; 8 | } 9 | 10 | const key: InjectionKey = Symbol(); 11 | 12 | export function createContentContext(context: ContentContextProps) { 13 | return createContext(context, key, { native: true }); 14 | } 15 | 16 | export function useContentContext() { 17 | return useContext(key); 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/component/usePageContext.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, InjectionKey, Ref } from 'vue'; 2 | 3 | import { createContext, useContext } from '@/hooks/core/useContext'; 4 | 5 | export interface PageContextProps { 6 | contentHeight: ComputedRef; 7 | pageHeight: Ref; 8 | setPageHeight: (height: number) => Promise; 9 | } 10 | 11 | const key: InjectionKey = Symbol(); 12 | 13 | export function createPageContext(context: PageContextProps) { 14 | return createContext(context, key, { native: true }); 15 | } 16 | 17 | export function usePageContext() { 18 | return useContext(key); 19 | } 20 | -------------------------------------------------------------------------------- /.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/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: _name || PARENT_LAYOUT_NAME, 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/hooks/setting/index.ts: -------------------------------------------------------------------------------- 1 | import { getAppEnvConfig } from '@/utils/env'; 2 | import type { GlobConfig } from '#/config'; 3 | 4 | export const useGlobSetting = (): Readonly => { 5 | const { VITE_GLOB_APP_TITLE, VITE_GLOB_API_URL, VITE_GLOB_API_URL_PREFIX, VITE_GLOB_UPLOAD_URL } = 6 | getAppEnvConfig(); 7 | 8 | // Take global configuration 9 | const glob: Readonly = { 10 | title: VITE_GLOB_APP_TITLE, 11 | apiUrl: VITE_GLOB_API_URL, 12 | shortName: VITE_GLOB_APP_TITLE.replace(/\s/g, '_').replace(/-/g, '_'), 13 | urlPrefix: VITE_GLOB_API_URL_PREFIX, 14 | uploadUrl: VITE_GLOB_UPLOAD_URL, 15 | }; 16 | return glob as Readonly; 17 | }; 18 | -------------------------------------------------------------------------------- /internal/ts-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vben/ts-config", 3 | "version": "1.0.0", 4 | "private": true, 5 | "homepage": "https://github.com/vbenjs/vue-vben-admin", 6 | "bugs": { 7 | "url": "https://github.com/vbenjs/vue-vben-admin/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/vbenjs/vue-vben-admin.git", 12 | "directory": "internal/ts-config" 13 | }, 14 | "license": "MIT", 15 | "files": [ 16 | "base.json", 17 | "node.json", 18 | "web.json", 19 | "node-server.json" 20 | ], 21 | "dependencies": { 22 | "@types/node": "^20.4.0", 23 | "@vben/types": "workspace:*", 24 | "vite": "^4.4.7" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 23 | -------------------------------------------------------------------------------- /src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | 3 | import appLocalePicker from './src/AppLocalePicker.vue'; 4 | import appLogo from './src/AppLogo.vue'; 5 | import appProvider from './src/AppProvider.vue'; 6 | import appSearch from './src/search/AppSearch.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 { ComputedRef, inject, provide } from 'vue'; 3 | 4 | import type { BasicTableProps, TableActionType } from '../types/table'; 5 | 6 | const key = Symbol('basic-table'); 7 | 8 | type Instance = TableActionType & { 9 | wrapRef: Ref>; 10 | getBindValues: ComputedRef; 11 | }; 12 | 13 | type RetInstance = Omit & { 14 | getBindValues: ComputedRef; 15 | }; 16 | 17 | export function createTableContext(instance: Instance) { 18 | provide(key, instance); 19 | } 20 | 21 | export function useTableContext(): RetInstance { 22 | return inject(key) as RetInstance; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Table/src/components/settings/RedoSetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ t('common.redo') }} 5 | 6 | 7 | 8 | 9 | 26 | -------------------------------------------------------------------------------- /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/views/form-design/hooks/useFormDesignState.ts: -------------------------------------------------------------------------------- 1 | import { inject, Ref } from 'vue'; 2 | 3 | import { IFormDesignMethods } from '../typings/form-type'; 4 | import { IFormConfig } from '../typings/v-form-component'; 5 | 6 | /** 7 | * 获取formDesign状态 8 | */ 9 | export function useFormDesignState() { 10 | const formConfig = inject('formConfig') as Ref; 11 | const formDesignMethods = inject('formDesignMethods') as IFormDesignMethods; 12 | return { formConfig, formDesignMethods }; 13 | } 14 | 15 | export function useFormModelState() { 16 | const formModel = inject('formModel') as Ref; 17 | const setFormModel = inject('setFormModelMethod') as (key: string, value: any) => void; 18 | return { formModel, setFormModel }; 19 | } 20 | -------------------------------------------------------------------------------- /internal/vite-config/src/config/common.ts: -------------------------------------------------------------------------------- 1 | import { presetTypography, presetUno } from 'unocss'; 2 | import UnoCSS from 'unocss/vite'; 3 | import { type UserConfig } from 'vite'; 4 | 5 | const commonConfig: (mode: string) => UserConfig = (mode) => ({ 6 | server: { 7 | host: true, 8 | }, 9 | esbuild: { 10 | drop: mode === 'procution' ? ['console', 'debugger'] : [], 11 | }, 12 | build: { 13 | reportCompressedSize: false, 14 | chunkSizeWarningLimit: 1500, 15 | rollupOptions: { 16 | // TODO: Prevent memory overflow 17 | maxParallelFileOps: 3, 18 | }, 19 | }, 20 | plugins: [ 21 | UnoCSS({ 22 | presets: [presetUno(), presetTypography()], 23 | }), 24 | ], 25 | }); 26 | 27 | export { commonConfig }; 28 | -------------------------------------------------------------------------------- /src/components/Preview/src/functional.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, render } from 'vue'; 2 | 3 | import { isClient } from '@/utils/is'; 4 | 5 | import ImgPreview from './Functional.vue'; 6 | import type { Options, Props } from './typing'; 7 | 8 | let instance: ReturnType | null = null; 9 | export function createImgPreview(options: Options) { 10 | if (!isClient) return; 11 | const propsData: Partial = {}; 12 | const container = document.createElement('div'); 13 | Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options); 14 | 15 | instance = createVNode(ImgPreview, propsData); 16 | render(instance, container); 17 | document.body.appendChild(container); 18 | return instance.component?.exposed; 19 | } 20 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/HeaderTrigger.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 19 | -------------------------------------------------------------------------------- /src/settings/localeSetting.ts: -------------------------------------------------------------------------------- 1 | import type { LocaleSetting, LocaleType } from '#/config'; 2 | 3 | import type { DropMenu } from '../components/Dropdown'; 4 | 5 | export const LOCALE: { [key: string]: LocaleType } = { 6 | ZH_CN: 'zh_CN', 7 | EN_US: 'en', 8 | }; 9 | 10 | export const localeSetting: LocaleSetting = { 11 | showPicker: true, 12 | // Locale 13 | locale: LOCALE.ZH_CN, 14 | // Default locale 15 | fallback: LOCALE.ZH_CN, 16 | // available Locales 17 | availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US], 18 | }; 19 | 20 | // locale list 21 | export const localeList: DropMenu[] = [ 22 | { 23 | text: '简体中文', 24 | event: LOCALE.ZH_CN, 25 | }, 26 | { 27 | text: 'English', 28 | event: LOCALE.EN_US, 29 | }, 30 | ]; 31 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/src/components/useSimpleMenuContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, Ref } from 'vue'; 2 | 3 | import { createContext, useContext } from '@/hooks/core/useContext'; 4 | import type { Emitter } from '@/utils/mitt'; 5 | 6 | export interface SimpleRootMenuContextProps { 7 | rootMenuEmitter: Emitter; 8 | activeName: Ref; 9 | } 10 | 11 | const key: InjectionKey = Symbol(); 12 | 13 | export function createSimpleRootMenuContext(context: SimpleRootMenuContextProps) { 14 | return createContext(context, key, { readonly: false, native: true }); 15 | } 16 | 17 | export function useSimpleRootMenuContext() { 18 | return useContext(key); 19 | } 20 | -------------------------------------------------------------------------------- /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/router/routes/modules/demo/flow.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@/hooks/web/useI18n'; 2 | import { LAYOUT } from '@/router/constant'; 3 | import type { AppRouteModule } from '@/router/types'; 4 | 5 | const charts: AppRouteModule = { 6 | path: '/flow', 7 | name: 'FlowDemo', 8 | component: LAYOUT, 9 | redirect: '/flow/flowChart', 10 | meta: { 11 | orderNo: 5000, 12 | icon: 'tabler:chart-dots', 13 | title: t('routes.demo.flow.name'), 14 | }, 15 | children: [ 16 | { 17 | path: 'flowChart', 18 | name: 'flowChartDemo', 19 | component: () => import('@/views/demo/comp/flow-chart/index.vue'), 20 | meta: { 21 | title: t('routes.demo.flow.flowChart'), 22 | }, 23 | }, 24 | ], 25 | }; 26 | 27 | export default charts; 28 | -------------------------------------------------------------------------------- /mock/demo/select-demo.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock'; 2 | 3 | import { resultSuccess } from '../_util'; 4 | 5 | const demoList = (keyword, count = 20) => { 6 | const result = { 7 | list: [] as any[], 8 | }; 9 | for (let index = 0; index < count; index++) { 10 | result.list.push({ 11 | name: `${keyword ?? ''}选项${index}`, 12 | id: `${index}`, 13 | }); 14 | } 15 | return result; 16 | }; 17 | 18 | export default [ 19 | { 20 | url: '/basic-api/select/getDemoOptions', 21 | timeout: 1000, 22 | method: 'get', 23 | response: ({ query }) => { 24 | const { keyword, count } = query; 25 | console.log(keyword); 26 | return resultSuccess(demoList(keyword, count)); 27 | }, 28 | }, 29 | ] as MockMethod[]; 30 | -------------------------------------------------------------------------------- /src/views/demo/table/MultipleHeader.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 29 | -------------------------------------------------------------------------------- /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/table/MergeHeader.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 30 | -------------------------------------------------------------------------------- /src/components/Button/src/props.ts: -------------------------------------------------------------------------------- 1 | const validColors = ['primary', 'error', 'warning', 'success', ''] as const; 2 | type ButtonColorType = (typeof validColors)[number]; 3 | 4 | export const buttonProps = { 5 | color: { 6 | type: String as PropType, 7 | validator: (v) => validColors.includes(v), 8 | default: '', 9 | }, 10 | loading: { type: Boolean }, 11 | disabled: { type: Boolean }, 12 | /** 13 | * Text before icon. 14 | */ 15 | preIcon: { type: String }, 16 | /** 17 | * Text after icon. 18 | */ 19 | postIcon: { type: String }, 20 | /** 21 | * preIcon and postIcon icon size. 22 | * @default: 14 23 | */ 24 | iconSize: { type: Number, default: 14 }, 25 | onClick: { type: Function as PropType<(...args) => any>, default: null }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/views/demo/feat/tabs/TabDetail.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ index }} - 详情页内容在此 4 | 5 | 6 | 7 | 30 | -------------------------------------------------------------------------------- /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/layouts/iframe/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 25 | -------------------------------------------------------------------------------- /src/views/demo/feat/tab-params/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Current Param : {{ params }} 4 | 5 | Keep Alive 6 | 7 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide/skin.shadowdom.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 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 8 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide-dark/skin.shadowdom.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 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 8 | -------------------------------------------------------------------------------- /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/router/routes/modules/about.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@/hooks/web/useI18n'; 2 | import { LAYOUT } from '@/router/constant'; 3 | import type { AppRouteModule } from '@/router/types'; 4 | 5 | const about: AppRouteModule = { 6 | path: '/about', 7 | name: 'About', 8 | component: LAYOUT, 9 | redirect: '/about/index', 10 | meta: { 11 | hideChildrenInMenu: true, 12 | icon: 'simple-icons:about-dot-me', 13 | title: t('routes.dashboard.about'), 14 | orderNo: 100000, 15 | }, 16 | children: [ 17 | { 18 | path: 'index', 19 | name: 'AboutPage', 20 | component: () => import('@/views/sys/about/index.vue'), 21 | meta: { 22 | title: t('routes.dashboard.about'), 23 | icon: 'simple-icons:about-dot-me', 24 | hideMenu: true, 25 | }, 26 | }, 27 | ], 28 | }; 29 | 30 | export default about; 31 | -------------------------------------------------------------------------------- /src/views/demo/feat/ripple/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | content 4 | 5 | 6 | 19 | 20 | 33 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | import BasicForm from './src/BasicForm.vue'; 2 | 3 | export { default as ApiCascader } from './src/components/ApiCascader.vue'; 4 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; 5 | export { default as ApiSelect } from './src/components/ApiSelect.vue'; 6 | export { default as ApiTransfer } from './src/components/ApiTransfer.vue'; 7 | export { default as ApiTree } from './src/components/ApiTree.vue'; 8 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; 9 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; 10 | export { useComponentRegister } from './src/hooks/useComponentRegister'; 11 | export { useForm } from './src/hooks/useForm'; 12 | export * from './src/types/form'; 13 | export * from './src/types/formItem'; 14 | 15 | export { BasicForm }; 16 | -------------------------------------------------------------------------------- /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/router/routes/modules/form-design/main.ts: -------------------------------------------------------------------------------- 1 | import { LAYOUT } from '@/router/constant'; 2 | import type { AppRouteModule } from '@/router/types'; 3 | 4 | const permission: AppRouteModule = { 5 | path: '/form-designer', 6 | name: 'Form-designer', 7 | component: LAYOUT, 8 | meta: { 9 | orderNo: 10000, 10 | icon: 'ion:build-outline', 11 | title: '表单设计', 12 | }, 13 | children: [ 14 | { 15 | path: 'design', 16 | name: 'Design', 17 | meta: { 18 | title: '表单设计', 19 | }, 20 | component: () => import('@/views/form-design/index.vue'), 21 | }, 22 | { 23 | path: 'example1', 24 | name: 'Example1', 25 | meta: { 26 | title: '示例', 27 | }, 28 | component: () => import('@/views/form-design/examples/baseForm.vue'), 29 | }, 30 | ], 31 | }; 32 | 33 | export default permission; 34 | -------------------------------------------------------------------------------- /src/views/sys/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 31 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useTableStyle.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef } from 'vue'; 2 | import { unref } from 'vue'; 3 | 4 | import { isFunction } from '@/utils/is'; 5 | 6 | import type { BasicTableProps, TableCustomRecord } from '../types/table'; 7 | 8 | export function useTableStyle(propsRef: ComputedRef, prefixCls: string) { 9 | function getRowClassName(record: TableCustomRecord, index: number) { 10 | const { striped, rowClassName } = unref(propsRef); 11 | const classNames: string[] = []; 12 | if (striped) { 13 | classNames.push((index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : ''); 14 | } 15 | if (rowClassName && isFunction(rowClassName)) { 16 | classNames.push(rowClassName(record, index)); 17 | } 18 | return classNames.filter((cls) => !!cls).join(' '); 19 | } 20 | 21 | return { getRowClassName }; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/helper.ts: -------------------------------------------------------------------------------- 1 | import { useI18n } from '@/hooks/web/useI18n'; 2 | 3 | import { ComponentType } from '../../types/componentType'; 4 | 5 | const { t } = useI18n(); 6 | 7 | /** 8 | * @description: 生成placeholder 9 | */ 10 | export function createPlaceholderMessage(component: ComponentType) { 11 | if (component.includes('Input') || component.includes('AutoComplete')) { 12 | return t('common.inputText'); 13 | } 14 | if (component.includes('Picker')) { 15 | return t('common.chooseText'); 16 | } 17 | 18 | if ( 19 | component.includes('Select') || 20 | component.includes('Checkbox') || 21 | component.includes('Radio') || 22 | component.includes('Switch') || 23 | component.includes('DatePicker') || 24 | component.includes('TimePicker') 25 | ) { 26 | return t('common.chooseText'); 27 | } 28 | return ''; 29 | } 30 | -------------------------------------------------------------------------------- /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 | const { config } = error.response; 12 | const { waitTime, count } = config?.requestOptions?.retryRequest ?? {}; 13 | config.__retryCount = config.__retryCount || 0; 14 | if (config.__retryCount >= count) { 15 | return Promise.reject(error); 16 | } 17 | config.__retryCount += 1; 18 | //请求返回后config的header不正确造成重试请求失败,删除返回headers采用默认headers 19 | delete config.headers; 20 | return this.delay(waitTime).then(() => axiosInstance(config)); 21 | } 22 | 23 | /** 24 | * 延迟 25 | */ 26 | private delay(waitTime: number) { 27 | return new Promise((resolve) => setTimeout(resolve, waitTime)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/views/form-design/examples/baseForm.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 38 | -------------------------------------------------------------------------------- /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/components/CodeEditor/src/codemirror/codeMirror.ts: -------------------------------------------------------------------------------- 1 | import './codemirror.css'; 2 | import 'codemirror/theme/idea.css'; 3 | import 'codemirror/theme/material-palenight.css'; 4 | // import 'codemirror/addon/lint/lint.css'; 5 | // modes 6 | import 'codemirror/mode/javascript/javascript'; 7 | import 'codemirror/mode/css/css'; 8 | import 'codemirror/mode/htmlmixed/htmlmixed'; 9 | 10 | import CodeMirror from 'codemirror'; 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/views/sys/error-log/DetailModal.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 30 | -------------------------------------------------------------------------------- /src/components/Menu/src/components/MenuItemContent.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ getI18nName }} 5 | 6 | 7 | 26 | -------------------------------------------------------------------------------- /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/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|webp)$/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/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/layouts/default/header/components/user-dropdown/DropMenuItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ text }} 6 | 7 | 8 | 9 | 28 | -------------------------------------------------------------------------------- /src/utils/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { CacheTypeEnum, TOKEN_KEY } from '@/enums/cacheEnum'; 2 | import projectSetting from '@/settings/projectSetting'; 3 | import { BasicKeys, Persistent } from '@/utils/cache/persistent'; 4 | 5 | const { permissionCacheType } = projectSetting; 6 | const isLocal = permissionCacheType === CacheTypeEnum.LOCAL; 7 | 8 | export function getToken() { 9 | return getAuthCache(TOKEN_KEY); 10 | } 11 | 12 | export function getAuthCache(key: BasicKeys) { 13 | const fn = isLocal ? Persistent.getLocal : Persistent.getSession; 14 | return fn(key) as T; 15 | } 16 | 17 | export function setAuthCache(key: BasicKeys, value) { 18 | const fn = isLocal ? Persistent.setLocal : Persistent.setSession; 19 | return fn(key, value, true); 20 | } 21 | 22 | export function clearAuthCache(immediate = true) { 23 | const fn = isLocal ? Persistent.clearLocal : Persistent.clearSession; 24 | return fn(immediate); 25 | } 26 | -------------------------------------------------------------------------------- /monorepo.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "@vben/eslint-config", 5 | "path": "internal/eslint-config" 6 | }, 7 | { 8 | "name": "@vben/prettier-config", 9 | "path": "internal/prettier-config" 10 | }, 11 | { 12 | "name": "@vben/stylelint-config", 13 | "path": "internal/stylelint-config" 14 | }, 15 | { 16 | "name": "@vben/ts-config", 17 | "path": "internal/ts-config" 18 | }, 19 | { 20 | "name": "@vben/vite-config", 21 | "path": "internal/vite-config" 22 | }, 23 | { 24 | "name": "@vben/hooks", 25 | "path": "packages/hooks" 26 | }, 27 | { 28 | "name": "@vben/types", 29 | "path": "packages/types" 30 | }, 31 | { 32 | "name": "root", 33 | "path": "." 34 | } 35 | ], 36 | "settings": { 37 | "typescript.tsdk": "root\\node_modules\\typescript\\lib" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/hooks/setting/useMultipleTabSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | 3 | import { useAppStore } from '@/store/modules/app'; 4 | import type { MultiTabsSetting } from '#/config'; 5 | 6 | export function useMultipleTabSetting() { 7 | const appStore = useAppStore(); 8 | 9 | const getShowMultipleTab = computed(() => appStore.getMultiTabsSetting.show); 10 | 11 | const getShowQuick = computed(() => appStore.getMultiTabsSetting.showQuick); 12 | 13 | const getShowRedo = computed(() => appStore.getMultiTabsSetting.showRedo); 14 | 15 | const getShowFold = computed(() => appStore.getMultiTabsSetting.showFold); 16 | 17 | function setMultipleTabSetting(multiTabsSetting: Partial) { 18 | appStore.setProjectConfig({ multiTabsSetting }); 19 | } 20 | return { 21 | setMultipleTabSetting, 22 | getShowMultipleTab, 23 | getShowQuick, 24 | getShowRedo, 25 | getShowFold, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Table/src/components/settings/FullScreenSetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ t('component.table.settingFullScreen') }} 5 | 6 | 7 | 8 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /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 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 40 | -------------------------------------------------------------------------------- /src/views/demo/comp/modal/Modal2.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 从内部关闭弹窗 9 | 从内部修改title 10 | 11 | 12 | 31 | -------------------------------------------------------------------------------- /src/views/demo/comp/verify/Rotate.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 28 | 33 | -------------------------------------------------------------------------------- /src/hooks/web/useFullContent.ts: -------------------------------------------------------------------------------- 1 | import { computed, unref } from 'vue'; 2 | import { useRouter } from 'vue-router'; 3 | 4 | import { useAppStore } from '@/store/modules/app'; 5 | 6 | /** 7 | * @description: Full screen display content 8 | */ 9 | export const useFullContent = () => { 10 | const appStore = useAppStore(); 11 | const router = useRouter(); 12 | const { currentRoute } = router; 13 | 14 | // Whether to display the content in full screen without displaying the menu 15 | const getFullContent = computed(() => { 16 | // Query parameters, the full screen is displayed when the address bar has a full parameter 17 | const route = unref(currentRoute); 18 | const query = route.query; 19 | if (query && Reflect.has(query, '__full__')) { 20 | return true; 21 | } 22 | // Return to the configuration in the configuration file 23 | return appStore.getProjectConfig.fullContent; 24 | }); 25 | 26 | return { getFullContent }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/ProjectCard.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 更多 5 | 6 | 7 | 8 | 9 | 10 | {{ item.title }} 11 | 12 | {{ item.desc }} 13 | 14 | {{ item.group }} 15 | {{ item.date }} 16 | 17 | 18 | 19 | 20 | 27 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/ModalFooter.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ cancelText }} 6 | 7 | 8 | 15 | {{ okText }} 16 | 17 | 18 | 19 | 20 | 37 | -------------------------------------------------------------------------------- /src/views/demo/comp/strength-meter/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 34 | -------------------------------------------------------------------------------- /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/views/sys/login/LoginFormTitle.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ getFormTitle }} 4 | 5 | 6 | 28 | -------------------------------------------------------------------------------- /src/assets/svg/preview/resume.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/components/TabRedo.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 29 | 34 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ index }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 26 | 33 | -------------------------------------------------------------------------------- /src/utils/helper/tsxHelper.tsx: -------------------------------------------------------------------------------- 1 | import { Slots } from 'vue'; 2 | 3 | import { isFunction } from '@/utils/is'; 4 | 5 | /** 6 | * @description: Get slot to prevent empty error 7 | */ 8 | export function getSlot(slots: Slots, slot = 'default', data?: any) { 9 | if (!slots || !Reflect.has(slots, slot)) { 10 | return null; 11 | } 12 | if (!isFunction(slots[slot])) { 13 | console.error(`${slot} is not a function!`); 14 | return null; 15 | } 16 | const slotFn = slots[slot]; 17 | if (!slotFn) return null; 18 | return slotFn(data); 19 | } 20 | 21 | /** 22 | * extends slots 23 | * @param slots slots 24 | * @param excludeKeys excludeKeys 25 | */ 26 | export function extendSlots(slots: Slots, excludeKeys: string[] = []) { 27 | const slotKeys = Object.keys(slots); 28 | const ret: any = {}; 29 | slotKeys.map((key) => { 30 | if (excludeKeys.includes(key)) { 31 | return null; 32 | } 33 | ret[key] = (data?: any) => getSlot(slots, key, data); 34 | }); 35 | return ret; 36 | } 37 | -------------------------------------------------------------------------------- /packages/hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vben/hooks", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/vbenjs/vue-vben-admin", 5 | "bugs": { 6 | "url": "https://github.com/vbenjs/vue-vben-admin/issues" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/vbenjs/vue-vben-admin.git", 11 | "directory": "packages/hooks" 12 | }, 13 | "license": "MIT", 14 | "sideEffects": false, 15 | "exports": { 16 | ".": { 17 | "default": "./src/index.ts" 18 | } 19 | }, 20 | "main": "./src/index.ts", 21 | "module": "./src/index.ts", 22 | "files": [ 23 | "dist" 24 | ], 25 | "scripts": { 26 | "//build": "pnpm unbuild", 27 | "//stub": "pnpm unbuild --stub", 28 | "clean": "pnpm rimraf .turbo node_modules dist", 29 | "lint": "pnpm eslint ." 30 | }, 31 | "dependencies": { 32 | "@vueuse/core": "^10.2.1", 33 | "vue": "^3.3.4" 34 | }, 35 | "devDependencies": { 36 | "@vben/types": "workspace:*" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 27 | -------------------------------------------------------------------------------- /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/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 | @import './entry.css'; 7 | 8 | input:-webkit-autofill { 9 | box-shadow: 0 0 0 1000px white inset !important; 10 | } 11 | 12 | :-webkit-autofill { 13 | transition: background-color 5000s ease-in-out 0s !important; 14 | } 15 | 16 | html { 17 | overflow: hidden; 18 | text-size-adjust: 100%; 19 | } 20 | 21 | html, 22 | body { 23 | width: 100%; 24 | height: 100%; 25 | overflow: visible; 26 | overflow-x: hidden; 27 | 28 | &.color-weak { 29 | filter: invert(80%); 30 | } 31 | 32 | &.gray-mode { 33 | filter: grayscale(100%); 34 | filter: progid:dximagetransform.microsoft.basicimage(grayscale=1); 35 | } 36 | } 37 | 38 | a:focus, 39 | a:active, 40 | button, 41 | div, 42 | svg, 43 | span { 44 | outline: none; 45 | } 46 | 47 | // 保持 和 windi 一样的全局样式,减少升级带来的影响 48 | ul { 49 | padding: 0; 50 | margin: 0; 51 | list-style: none; 52 | } 53 | -------------------------------------------------------------------------------- /src/directives/repeatClick.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prevent repeated clicks 3 | * @Example v-repeat-click="()=>{}" 4 | */ 5 | import type { Directive, DirectiveBinding } from 'vue'; 6 | 7 | import { on, once } from '@/utils/domUtils'; 8 | 9 | const repeatDirective: Directive = { 10 | beforeMount(el: Element, binding: DirectiveBinding) { 11 | let interval: Nullable = null; 12 | let startTime = 0; 13 | const handler = (): void => binding?.value(); 14 | const clear = (): void => { 15 | if (Date.now() - startTime < 100) { 16 | handler(); 17 | } 18 | interval && clearInterval(interval); 19 | interval = null; 20 | }; 21 | 22 | on(el, 'mousedown', (e: MouseEvent): void => { 23 | if ((e as any).button !== 0) return; 24 | startTime = Date.now(); 25 | once(document as any, 'mouseup', clear); 26 | interval && clearInterval(interval); 27 | interval = setInterval(handler, 100); 28 | }); 29 | }, 30 | }; 31 | 32 | export default repeatDirective; 33 | -------------------------------------------------------------------------------- /src/assets/svg/preview/unscale.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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 type { RouteLocationNormalized } from 'vue-router'; 6 | 7 | import { getRawRoute } from '@/utils'; 8 | import { mitt } from '@/utils/mitt'; 9 | 10 | const emitter = mitt(); 11 | 12 | const key = Symbol(); 13 | 14 | let lastChangeTab: RouteLocationNormalized; 15 | 16 | export function setRouteChange(lastChangeRoute: RouteLocationNormalized) { 17 | const r = getRawRoute(lastChangeRoute); 18 | emitter.emit(key, r); 19 | lastChangeTab = r; 20 | } 21 | 22 | export function listenerRouteChange( 23 | callback: (route: RouteLocationNormalized) => void, 24 | immediate = true, 25 | ) { 26 | emitter.on(key, callback); 27 | immediate && lastChangeTab && callback(lastChangeTab); 28 | } 29 | 30 | export function removeTabChangeListener() { 31 | emitter.clear(); 32 | } 33 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineApplicationConfig } from '@vben/vite-config'; 2 | 3 | export default defineApplicationConfig({ 4 | overrides: { 5 | optimizeDeps: { 6 | include: [ 7 | 'echarts/core', 8 | 'echarts/charts', 9 | 'echarts/components', 10 | 'echarts/renderers', 11 | '@iconify/iconify', 12 | 'ant-design-vue/es/locale/zh_CN', 13 | 'ant-design-vue/es/locale/en_US', 14 | ], 15 | }, 16 | server: { 17 | proxy: { 18 | '/basic-api': { 19 | target: 'http://localhost:3000', 20 | changeOrigin: true, 21 | ws: true, 22 | rewrite: (path) => path.replace(/^\/basic-api/, ''), 23 | // only https 24 | // secure: false 25 | }, 26 | '/upload': { 27 | target: 'http://localhost:3300/upload', 28 | changeOrigin: true, 29 | ws: true, 30 | rewrite: (path) => path.replace(/^\/upload/, ''), 31 | }, 32 | }, 33 | }, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /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/hooks/setting/useTransitionSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | 3 | import { useAppStore } from '@/store/modules/app'; 4 | import type { TransitionSetting } from '#/config'; 5 | 6 | export function useTransitionSetting() { 7 | const appStore = useAppStore(); 8 | 9 | const getEnableTransition = computed(() => appStore.getTransitionSetting?.enable); 10 | 11 | const getOpenNProgress = computed(() => appStore.getTransitionSetting?.openNProgress); 12 | 13 | const getOpenPageLoading = computed((): boolean => { 14 | return !!appStore.getTransitionSetting?.openPageLoading; 15 | }); 16 | 17 | const getBasicTransition = computed(() => appStore.getTransitionSetting?.basicTransition); 18 | 19 | function setTransitionSetting(transitionSetting: Partial) { 20 | appStore.setProjectConfig({ transitionSetting }); 21 | } 22 | return { 23 | setTransitionSetting, 24 | 25 | getEnableTransition, 26 | getOpenNProgress, 27 | getOpenPageLoading, 28 | getBasicTransition, 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /internal/ts-config/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Base", 4 | "compilerOptions": { 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "noImplicitThis": true, 9 | "strict": true, 10 | "noImplicitOverride": true, 11 | "noUnusedLocals": true, 12 | "esModuleInterop": true, 13 | "useUnknownInCatchVariables": false, 14 | "composite": false, 15 | "forceConsistentCasingInFileNames": true, 16 | "inlineSources": false, 17 | "isolatedModules": true, 18 | "skipLibCheck": true, 19 | "noUnusedParameters": true, 20 | "preserveWatchOutput": true, 21 | "experimentalDecorators": true, 22 | "resolveJsonModule": true, 23 | "removeComments": true, 24 | "allowImportingTsExtensions": true, 25 | "noEmit": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "sourceMap": false, 28 | "strictNullChecks": true 29 | }, 30 | "exclude": ["**/node_modules/**", "**/dist/**", "**/.turbo/**"] 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Description/src/useDescription.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, ref, unref } from 'vue'; 2 | 3 | import { isProdMode } from '@/utils/env'; 4 | 5 | import type { DescInstance, DescriptionProps, UseDescReturnType } from './typing'; 6 | 7 | export function useDescription(props?: Partial): UseDescReturnType { 8 | if (!getCurrentInstance()) { 9 | throw new Error('useDescription() can only be used inside setup() or functional components!'); 10 | } 11 | const desc = ref>(null); 12 | const loaded = ref(false); 13 | 14 | function register(instance: DescInstance) { 15 | if (unref(loaded) && isProdMode()) { 16 | return; 17 | } 18 | desc.value = instance; 19 | props && instance.setDescProps(props); 20 | loaded.value = true; 21 | } 22 | 23 | const methods: DescInstance = { 24 | setDescProps: (descProps: Partial): void => { 25 | unref(desc)?.setDescProps(descProps); 26 | }, 27 | }; 28 | 29 | return [register, methods]; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { useAttrs } from '@vben/hooks'; 2 | import { Modal } from 'ant-design-vue'; 3 | import { defineComponent, toRefs, unref } from 'vue'; 4 | 5 | import { extendSlots } from '@/utils/helper/tsxHelper'; 6 | 7 | import { useModalDragMove } from '../hooks/useModalDrag'; 8 | import { basicProps } from '../props'; 9 | 10 | export default defineComponent({ 11 | name: 'Modal', 12 | inheritAttrs: false, 13 | props: basicProps as any, 14 | emits: ['cancel'], 15 | setup(props, { slots, emit }) { 16 | const { open, draggable, destroyOnClose } = toRefs(props); 17 | const attrs = useAttrs(); 18 | useModalDragMove({ 19 | open, 20 | destroyOnClose, 21 | draggable, 22 | }); 23 | 24 | const onCancel = (e: Event) => { 25 | emit('cancel', e); 26 | }; 27 | 28 | return () => { 29 | const propsData = { ...unref(attrs), ...props, onCancel } as Recordable; 30 | return {extendSlots(slots)}; 31 | }; 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /src/router/routes/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@/hooks/web/useI18n'; 2 | import { LAYOUT } from '@/router/constant'; 3 | import type { AppRouteModule } from '@/router/types'; 4 | 5 | const dashboard: AppRouteModule = { 6 | path: '/dashboard', 7 | name: 'Dashboard', 8 | component: LAYOUT, 9 | redirect: '/dashboard/analysis', 10 | meta: { 11 | orderNo: 10, 12 | icon: 'ion:grid-outline', 13 | title: t('routes.dashboard.dashboard'), 14 | }, 15 | children: [ 16 | { 17 | path: 'analysis', 18 | name: 'Analysis', 19 | component: () => import('@/views/dashboard/analysis/index.vue'), 20 | meta: { 21 | // affix: true, 22 | title: t('routes.dashboard.analysis'), 23 | }, 24 | }, 25 | { 26 | path: 'workbench', 27 | name: 'Workbench', 28 | component: () => import('@/views/dashboard/workbench/index.vue'), 29 | meta: { 30 | title: t('routes.dashboard.workbench'), 31 | }, 32 | }, 33 | ], 34 | }; 35 | 36 | export default dashboard; 37 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/FullScreen.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 30 | -------------------------------------------------------------------------------- /src/router/guard/stateGuard.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router'; 2 | 3 | import { PageEnum } from '@/enums/pageEnum'; 4 | import { removeTabChangeListener } from '@/logics/mitt/routeChange'; 5 | import { useAppStore } from '@/store/modules/app'; 6 | import { useMultipleTabStore } from '@/store/modules/multipleTab'; 7 | import { usePermissionStore } from '@/store/modules/permission'; 8 | import { useUserStore } from '@/store/modules/user'; 9 | 10 | export function createStateGuard(router: Router) { 11 | router.afterEach((to) => { 12 | // Just enter the login page and clear the authentication information 13 | if (to.path === PageEnum.BASE_LOGIN) { 14 | const tabStore = useMultipleTabStore(); 15 | const userStore = useUserStore(); 16 | const appStore = useAppStore(); 17 | const permissionStore = usePermissionStore(); 18 | appStore.resetAllState(); 19 | permissionStore.resetState(); 20 | tabStore.resetState(); 21 | userStore.resetState(); 22 | removeTabChangeListener(); 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 35 | -------------------------------------------------------------------------------- /internal/prettier-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vben/prettier-config", 3 | "version": "1.0.0", 4 | "private": true, 5 | "homepage": "https://github.com/vbenjs/vue-vben-admin", 6 | "bugs": { 7 | "url": "https://github.com/vbenjs/vue-vben-admin/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/vbenjs/vue-vben-admin.git", 12 | "directory": "internal/stylelint-config" 13 | }, 14 | "license": "MIT", 15 | "exports": { 16 | ".": { 17 | "types": "./dist/index.d.ts", 18 | "import": "./dist/index.mjs", 19 | "require": "./dist/index.cjs" 20 | } 21 | }, 22 | "main": "./dist/index.cjs", 23 | "module": "./dist/index.mjs", 24 | "types": "./dist/index.d.ts", 25 | "files": [ 26 | "dist" 27 | ], 28 | "scripts": { 29 | "clean": "pnpm rimraf .turbo node_modules dist", 30 | "lint": "pnpm eslint .", 31 | "stub": "pnpm unbuild --stub" 32 | }, 33 | "dependencies": { 34 | "prettier": "^3.0.0", 35 | "prettier-plugin-packagejson": "^2.4.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mock/demo/tree-demo.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock'; 2 | 3 | import { resultSuccess } from '../_util'; 4 | 5 | const demoTreeList = (keyword) => { 6 | const result = { 7 | list: [] as Recordable[], 8 | }; 9 | for (let index = 0; index < 5; index++) { 10 | const children: Recordable[] = []; 11 | for (let j = 0; j < 3; j++) { 12 | children.push({ 13 | title: `${keyword ?? ''}选项${index}-${j}`, 14 | value: `${index}-${j}`, 15 | key: `${index}-${j}`, 16 | }); 17 | } 18 | result.list.push({ 19 | title: `${keyword ?? ''}选项${index}`, 20 | value: `${index}`, 21 | key: `${index}`, 22 | children, 23 | }); 24 | } 25 | return result; 26 | }; 27 | 28 | export default [ 29 | { 30 | url: '/basic-api/tree/getDemoOptions', 31 | timeout: 1000, 32 | method: 'get', 33 | response: ({ query }) => { 34 | const { keyword } = query; 35 | console.log(keyword); 36 | return resultSuccess(demoTreeList(keyword)); 37 | }, 38 | }, 39 | ] as MockMethod[]; 40 | -------------------------------------------------------------------------------- /src/components/Excel/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { BookType, JSON2SheetOpts, WritingOptions } 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 | sheetName?: string; 14 | json2sheetOpts?: JSON2SheetOpts; 15 | write2excelOpts?: WritingOptions; 16 | } 17 | 18 | export interface AoAToSheet { 19 | data: T[][]; 20 | header?: T[]; 21 | filename?: string; 22 | sheetName?: string; 23 | write2excelOpts?: WritingOptions; 24 | } 25 | 26 | export interface ExportModalResult { 27 | filename: string; 28 | bookType: BookType; 29 | } 30 | 31 | export interface JsonToMultipleSheet { 32 | sheetList: JsonToSheet[]; 33 | filename?: string; 34 | write2excelOpts?: WritingOptions; 35 | } 36 | 37 | export interface AoaToMultipleSheet { 38 | sheetList: AoAToSheet[]; 39 | filename?: string; 40 | write2excelOpts?: WritingOptions; 41 | } 42 | -------------------------------------------------------------------------------- /internal/vite-config/src/plugins/compress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated 3 | * https://github.com/anncwb/vite-plugin-compression 4 | */ 5 | import type { PluginOption } from 'vite'; 6 | import compressPlugin from 'vite-plugin-compression'; 7 | 8 | export function configCompressPlugin({ 9 | compress, 10 | deleteOriginFile = false, 11 | }: { 12 | compress: string; 13 | deleteOriginFile?: boolean; 14 | }): PluginOption[] { 15 | const compressList = compress.split(','); 16 | 17 | const plugins: PluginOption[] = []; 18 | 19 | if (compressList.includes('gzip')) { 20 | plugins.push( 21 | compressPlugin({ 22 | ext: '.gz', 23 | deleteOriginFile, 24 | }), 25 | ); 26 | } 27 | 28 | if (compressList.includes('brotli')) { 29 | plugins.push( 30 | compressPlugin({ 31 | ext: '.br', 32 | algorithm: 'brotliCompress', 33 | deleteOriginFile, 34 | }), 35 | ); 36 | } 37 | return plugins; 38 | } 39 | -------------------------------------------------------------------------------- /packages/hooks/src/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import { type AnyFunction } from '@vben/types'; 2 | import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'; 3 | 4 | interface UseWindowSizeOptions { 5 | wait?: number; 6 | once?: boolean; 7 | immediate?: boolean; 8 | listenerOptions?: AddEventListenerOptions | boolean; 9 | } 10 | 11 | function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) { 12 | const { wait = 150, immediate } = options; 13 | let handler = () => { 14 | fn(); 15 | }; 16 | const handleSize = useDebounceFn(handler, wait); 17 | handler = handleSize; 18 | 19 | const start = () => { 20 | if (immediate) { 21 | handler(); 22 | } 23 | window.addEventListener('resize', handler); 24 | }; 25 | 26 | const stop = () => { 27 | window.removeEventListener('resize', handler); 28 | }; 29 | 30 | tryOnMounted(() => { 31 | start(); 32 | }); 33 | 34 | tryOnUnmounted(() => { 35 | stop(); 36 | }); 37 | return { start, stop }; 38 | } 39 | 40 | export { useWindowSizeFn, type UseWindowSizeOptions }; 41 | -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /mock/_createProductionServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | 3 | // 问题描述 4 | // 1. `import.meta.globEager` 已被弃用, 需要升级vite版本,有兼容问题 5 | // 2. `vite-plugin-mock` 插件问题 https://github.com/vbenjs/vite-plugin-mock/issues/56 6 | 7 | // const modules: Record = import.meta.glob("./**/*.ts", { 8 | // import: "default", 9 | // eager: true, 10 | // }); 11 | 12 | // const mockModules = Object.keys(modules).reduce((pre, key) => { 13 | // if (!key.includes("/_")) { 14 | // pre.push(...modules[key]); 15 | // } 16 | // return pre; 17 | // }, [] as any[]); 18 | 19 | const modules = import.meta.glob('./**/*.ts', { eager: true }); 20 | 21 | const mockModules: any[] = []; 22 | Object.keys(modules).forEach((key) => { 23 | if (key.includes('/_')) { 24 | return; 25 | } 26 | mockModules.push(...(modules as Recordable)[key].default); 27 | }); 28 | 29 | /** 30 | * Used in a production environment. Need to manually import all modules 31 | */ 32 | export function setupProdMockServer() { 33 | createProdMockServer(mockModules); 34 | } 35 | -------------------------------------------------------------------------------- /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 { computed, ref, unref } 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/views/demo/comp/drawer/Drawer3.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 根据屏幕高度自适应 4 | 5 | btn 6 | 7 | 8 | btn2 9 | 10 | 11 | 12 | btn3 13 | 14 | 15 | 18 | 19 | 20 | 38 | -------------------------------------------------------------------------------- /src/views/demo/comp/card-list/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 按钮1 6 | 按钮2 7 | 8 | 9 | 10 | 11 | 34 | -------------------------------------------------------------------------------- /src/views/sys/login/QrCodeForm.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ t('sys.login.scanSign') }} 7 | 8 | {{ t('sys.login.backSignIn') }} 9 | 10 | 11 | 12 | 13 | 29 | -------------------------------------------------------------------------------- /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 | 4 | import { RoleEnum } from '@/enums/roleEnum'; 5 | 6 | export interface ActionItem extends ButtonProps { 7 | onClick?: Fn; 8 | label?: string; 9 | color?: 'success' | 'error' | 'warning'; 10 | icon?: string; 11 | popConfirm?: PopConfirm; 12 | disabled?: boolean; 13 | divider?: boolean; 14 | // 权限编码控制是否显示 15 | auth?: RoleEnum | RoleEnum[] | string | string[]; 16 | // 业务控制是否显示 17 | ifShow?: boolean | ((action: ActionItem) => boolean); 18 | tooltip?: string | TooltipProps; 19 | } 20 | 21 | export interface PopConfirm { 22 | title: string; 23 | okText?: string; 24 | cancelText?: string; 25 | confirm: Fn; 26 | cancel?: Fn; 27 | icon?: string; 28 | placement?: 29 | | 'top' 30 | | 'left' 31 | | 'right' 32 | | 'bottom' 33 | | 'topLeft' 34 | | 'topRight' 35 | | 'leftTop' 36 | | 'leftBottom' 37 | | 'rightTop' 38 | | 'rightBottom' 39 | | 'bottomLeft' 40 | | 'bottomRight'; 41 | } 42 | -------------------------------------------------------------------------------- /src/hooks/core/useContext.ts: -------------------------------------------------------------------------------- 1 | import { 2 | inject, 3 | InjectionKey, 4 | provide, 5 | reactive, 6 | readonly as defineReadonly, 7 | UnwrapRef, 8 | } from 'vue'; 9 | 10 | export interface CreateContextOptions { 11 | readonly?: boolean; 12 | createProvider?: boolean; 13 | native?: boolean; 14 | } 15 | 16 | type ShallowUnwrap = { 17 | [P in keyof T]: UnwrapRef; 18 | }; 19 | 20 | export function createContext( 21 | context: any, 22 | key: InjectionKey = Symbol(), 23 | options: CreateContextOptions = {}, 24 | ) { 25 | const { readonly = true, createProvider = false, native = false } = options; 26 | 27 | const state = reactive(context); 28 | const provideData = readonly ? defineReadonly(state) : state; 29 | !createProvider && provide(key, native ? context : provideData); 30 | 31 | return { 32 | state, 33 | }; 34 | } 35 | 36 | export function useContext(key: InjectionKey, native?: boolean): T; 37 | 38 | export function useContext( 39 | key: InjectionKey = Symbol(), 40 | defaultValue?: any, 41 | ): ShallowUnwrap { 42 | return inject(key, defaultValue || {}); 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Application/src/search/AppSearch.vue: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /apps/test-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "compile": "rimraf ./dist && tsup ./index.ts --dts --format cjs,esm ", 7 | "prod": "npx pm2 start ecosystem.config.js --env production", 8 | "restart": "pm2 restart ecosystem.config.js --env production", 9 | "start": "nodemon", 10 | "stop": "npx pm2 stop ecosystem.config.js" 11 | }, 12 | "dependencies": { 13 | "fs-extra": "^11.1.1", 14 | "koa": "^2.14.2", 15 | "koa-body": "^6.0.1", 16 | "koa-bodyparser": "^4.4.1", 17 | "koa-route": "^3.2.0", 18 | "koa-router": "^12.0.0", 19 | "koa-static": "^5.0.0", 20 | "koa-websocket": "^7.0.0", 21 | "koa2-cors": "^2.0.6" 22 | }, 23 | "devDependencies": { 24 | "@types/koa": "^2.13.6", 25 | "@types/koa-bodyparser": "^5.0.2", 26 | "@types/koa-router": "^7.4.4", 27 | "@types/node": "^20.4.0", 28 | "nodemon": "^3.0.1", 29 | "pm2": "^5.3.0", 30 | "rimraf": "^5.0.1", 31 | "ts-node": "^10.9.1", 32 | "tsconfig-paths": "^4.2.0", 33 | "tsup": "^7.1.0", 34 | "typescript": "^5.1.6" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/web/useTitle.ts: -------------------------------------------------------------------------------- 1 | import { useTitle as usePageTitle } from '@vueuse/core'; 2 | import { unref, watch } from 'vue'; 3 | import { useRouter } from 'vue-router'; 4 | 5 | import { useGlobSetting } from '@/hooks/setting'; 6 | import { useI18n } from '@/hooks/web/useI18n'; 7 | import { REDIRECT_NAME } from '@/router/constant'; 8 | import { useLocaleStore } from '@/store/modules/locale'; 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 | --------------------------------------------------------------------------------
Content Message
根据屏幕高度自适应
打开浏览器的network面板,可以看到发出了六次请求
9 | 10 |
12 | 13 |