├── packages
├── .gitkeep
├── types
│ ├── src
│ │ ├── index.ts
│ │ └── utils.ts
│ ├── .eslintrc.cjs
│ ├── tsconfig.json
│ ├── build.config.ts
│ └── package.json
└── hooks
│ ├── .eslintrc.cjs
│ ├── tsconfig.json
│ ├── src
│ ├── index.ts
│ ├── onMountedOrActivated.ts
│ ├── useRefs.ts
│ └── useWindowSizeFn.ts
│ ├── build.config.ts
│ └── package.json
├── .stylelintignore
├── .env
├── types
├── tree.d.ts
├── vueuseCore.d.ts
├── codemirror.d.ts
├── utils.d.ts
├── module.d.ts
└── index.d.ts
├── src
├── components
│ ├── Tree
│ │ ├── style
│ │ │ ├── index.ts
│ │ │ └── index.less
│ │ ├── index.ts
│ │ └── src
│ │ │ └── TreeIcon.ts
│ ├── SimpleMenu
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── types.ts
│ │ │ └── components
│ │ │ ├── types.ts
│ │ │ └── useSimpleMenuContext.ts
│ ├── Menu
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── types.ts
│ │ │ └── components
│ │ │ ├── BasicMenuItem.vue
│ │ │ └── MenuItemContent.vue
│ ├── CodeEditor
│ │ ├── src
│ │ │ └── typing.ts
│ │ └── index.ts
│ ├── JsonPreview
│ │ ├── src
│ │ │ ├── typing.ts
│ │ │ └── JsonPreview.vue
│ │ └── index.ts
│ ├── Preview
│ │ ├── index.ts
│ │ └── src
│ │ │ └── functional.ts
│ ├── Markdown
│ │ ├── src
│ │ │ ├── typing.ts
│ │ │ └── getTheme.ts
│ │ └── index.ts
│ ├── ContextMenu
│ │ ├── index.ts
│ │ └── src
│ │ │ └── typing.ts
│ ├── Icon
│ │ └── 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
│ ├── VirtualScroll
│ │ └── index.ts
│ ├── ClickOutSide
│ │ ├── index.ts
│ │ └── src
│ │ │ └── ClickOutSide.vue
│ ├── EllipsisText
│ │ └── index.ts
│ ├── StrengthMeter
│ │ └── index.ts
│ ├── Dropdown
│ │ ├── index.ts
│ │ └── src
│ │ │ └── typing.ts
│ ├── Loading
│ │ ├── index.ts
│ │ └── src
│ │ │ └── typing.ts
│ ├── Cropper
│ │ ├── src
│ │ │ └── typing.ts
│ │ └── index.ts
│ ├── Scrollbar
│ │ ├── index.ts
│ │ └── src
│ │ │ └── types.d.ts
│ ├── registerGlobComp.ts
│ ├── Description
│ │ ├── index.ts
│ │ └── src
│ │ │ └── useDescription.ts
│ ├── Drawer
│ │ └── index.ts
│ ├── Page
│ │ └── index.ts
│ ├── CountDown
│ │ ├── index.ts
│ │ └── src
│ │ │ └── useCountdown.ts
│ ├── Upload
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── components
│ │ │ └── ThumbUrl.vue
│ │ │ └── helper.ts
│ ├── Application
│ │ ├── src
│ │ │ ├── search
│ │ │ │ ├── AppSearchKeyItem.vue
│ │ │ │ └── AppSearch.vue
│ │ │ └── useAppContext.ts
│ │ └── 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
│ │ │ │ │ └── CellComponent.ts
│ │ │ │ └── DictionaryTag.vue
│ │ │ ├── hooks
│ │ │ │ ├── useLoading.ts
│ │ │ │ ├── useTableContext.ts
│ │ │ │ └── useTableStyle.ts
│ │ │ ├── helper.ts
│ │ │ └── const.ts
│ │ └── index.ts
│ ├── Modal
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── components
│ │ │ ├── ModalHeader.vue
│ │ │ ├── Modal.tsx
│ │ │ └── ModalFooter.vue
│ │ │ └── hooks
│ │ │ ├── useModalContext.ts
│ │ │ └── useModalFullScreen.ts
│ ├── Basic
│ │ └── index.ts
│ ├── Container
│ │ ├── index.ts
│ │ └── src
│ │ │ └── typing.ts
│ ├── Excel
│ │ ├── index.ts
│ │ └── src
│ │ │ └── typing.ts
│ ├── Button
│ │ ├── index.ts
│ │ └── src
│ │ │ └── props.ts
│ └── Prompt
│ │ └── index.ts
├── logics
│ ├── theme
│ │ ├── index.ts
│ │ ├── updateGrayMode.ts
│ │ ├── updateColorWeak.ts
│ │ └── util.ts
│ └── mitt
│ │ └── routeChange.ts
├── enums
│ ├── commonEnum.ts
│ ├── roleEnum.ts
│ ├── pageEnum.ts
│ ├── sizeEnum.ts
│ ├── exceptionEnum.ts
│ ├── breakpointEnum.ts
│ ├── httpEnum.ts
│ ├── cacheEnum.ts
│ ├── menuEnum.ts
│ └── appEnum.ts
├── views
│ ├── sys
│ │ ├── exception
│ │ │ └── index.ts
│ │ ├── iframe
│ │ │ └── FrameBlank.vue
│ │ ├── profile
│ │ │ └── data.ts
│ │ ├── lock
│ │ │ └── index.vue
│ │ ├── redirect
│ │ │ └── index.vue
│ │ └── login
│ │ │ └── QrCodeForm.vue
│ ├── dashboard
│ │ ├── analysis
│ │ │ ├── components
│ │ │ │ ├── props.ts
│ │ │ │ └── SiteAnalysis.vue
│ │ │ ├── index.vue
│ │ │ └── data.ts
│ │ └── workbench
│ │ │ ├── index.vue
│ │ │ └── components
│ │ │ ├── ProjectCard.vue
│ │ │ └── QuickNav.vue
│ └── mcms
│ │ └── emailProvider
│ │ └── email.data.ts
├── design
│ ├── config.less
│ ├── ant
│ │ ├── popconfirm.less
│ │ └── input.less
│ ├── transition
│ │ ├── index.less
│ │ ├── base.less
│ │ ├── scale.less
│ │ ├── zoom.less
│ │ ├── slide.less
│ │ └── scroll.less
│ ├── var
│ │ ├── index.less
│ │ ├── easing.less
│ │ └── breakpoint.less
│ ├── index.less
│ └── public.less
├── locales
│ └── lang
│ │ ├── en
│ │ └── routes
│ │ │ ├── basic.ts
│ │ │ └── system.ts
│ │ ├── zh-CN
│ │ ├── routes
│ │ │ ├── basic.ts
│ │ │ └── system.ts
│ │ └── antdLocale
│ │ │ └── DatePicker.ts
│ │ ├── en.ts
│ │ └── zh_CN.ts
├── assets
│ ├── images
│ │ ├── logo.png
│ │ ├── header.jpg
│ │ └── login-page-bg-2.png
│ └── svg
│ │ └── preview
│ │ ├── resume.svg
│ │ └── unscale.svg
├── api
│ ├── sys
│ │ ├── model
│ │ │ ├── uploadModel.ts
│ │ │ ├── captcha.ts
│ │ │ ├── taskLogModel.ts
│ │ │ ├── dictionaryModel.ts
│ │ │ ├── positionModel.ts
│ │ │ ├── roleModel.ts
│ │ │ ├── taskModel.ts
│ │ │ ├── tokenModel.ts
│ │ │ ├── apiModel.ts
│ │ │ ├── departmentModel.ts
│ │ │ ├── dictionaryDetailModel.ts
│ │ │ ├── configurationModel.ts
│ │ │ ├── authorityModel.ts
│ │ │ └── oauthProviderModel.ts
│ │ ├── upload.ts
│ │ └── initialize.ts
│ ├── fms
│ │ ├── initialize.ts
│ │ └── model
│ │ │ ├── fileTagModel.ts
│ │ │ ├── cloudFileTagModel.ts
│ │ │ ├── cloudFileModel.ts
│ │ │ ├── storageProviderModel.ts
│ │ │ └── fileModel.ts
│ ├── member
│ │ ├── initialize.ts
│ │ └── model
│ │ │ ├── memberRankModel.ts
│ │ │ └── memberModel.ts
│ ├── mcms
│ │ ├── model
│ │ │ ├── messageModel.ts
│ │ │ ├── smsLogModel.ts
│ │ │ ├── emailLogModel.ts
│ │ │ ├── smsProviderModel.ts
│ │ │ └── emailProviderModel.ts
│ │ └── messageSender.ts
│ └── model
│ │ └── baseModel.ts
├── settings
│ ├── siteSetting.ts
│ ├── encryptionSetting.ts
│ └── localeSetting.ts
├── utils
│ ├── log.ts
│ ├── copyTextToClipboard.ts
│ ├── object.ts
│ ├── dateUtil.ts
│ ├── http
│ │ └── axios
│ │ │ └── axiosRetry.ts
│ ├── is.ts
│ ├── uuid.ts
│ ├── propTypes.ts
│ └── helper
│ │ └── tsxHelper.tsx
├── hooks
│ ├── web
│ │ ├── useDesign.ts
│ │ ├── useAppInject.ts
│ │ ├── useContextMenu.ts
│ │ ├── useSortable.ts
│ │ ├── useFullContent.ts
│ │ ├── usePagination.ts
│ │ ├── useTitle.ts
│ │ └── useScript.ts
│ ├── setting
│ │ ├── useDarkModeTheme.ts
│ │ ├── index.ts
│ │ ├── useTransitionSetting.ts
│ │ └── useMultipleTabSetting.ts
│ ├── component
│ │ └── usePageContext.ts
│ └── core
│ │ └── useContext.ts
├── store
│ ├── index.ts
│ └── modules
│ │ ├── role.ts
│ │ └── lock.ts
├── directives
│ ├── index.ts
│ ├── ripple
│ │ └── index.less
│ ├── permission.ts
│ ├── repeatClick.ts
│ └── ellipsis.ts
├── layouts
│ ├── default
│ │ ├── setting
│ │ │ ├── index.vue
│ │ │ └── components
│ │ │ │ └── index.ts
│ │ ├── trigger
│ │ │ ├── SiderTrigger.vue
│ │ │ ├── index.vue
│ │ │ └── HeaderTrigger.vue
│ │ ├── header
│ │ │ └── components
│ │ │ │ ├── index.ts
│ │ │ │ ├── user-dropdown
│ │ │ │ └── DropMenuItem.vue
│ │ │ │ ├── FullScreen.vue
│ │ │ │ └── ErrorAction.vue
│ │ ├── tabs
│ │ │ ├── types.ts
│ │ │ └── components
│ │ │ │ ├── SettingButton.vue
│ │ │ │ └── TabRedo.vue
│ │ └── content
│ │ │ └── useContentContext.ts
│ ├── iframe
│ │ └── index.vue
│ └── page
│ │ └── transition.ts
└── router
│ ├── constant.ts
│ ├── guard
│ └── stateGuard.ts
│ └── index.ts
├── .browserslistrc
├── internal
├── ts-config
│ ├── .eslintignore
│ ├── vue-app.json
│ ├── node.json
│ ├── node-server.json
│ ├── package.json
│ └── base.json
├── vite-config
│ ├── src
│ │ ├── index.ts
│ │ ├── plugins
│ │ │ ├── visualizer.ts
│ │ │ ├── html.ts
│ │ │ ├── mock.ts
│ │ │ ├── svgSprite.ts
│ │ │ └── compress.ts
│ │ ├── utils
│ │ │ └── hash.ts
│ │ └── config
│ │ │ └── common.ts
│ ├── .eslintrc.cjs
│ ├── .eslintignore
│ ├── tsconfig.json
│ └── build.config.ts
├── eslint-config
│ ├── .eslintrc.cjs
│ ├── .eslintignore
│ ├── tsconfig.json
│ └── build.config.ts
└── stylelint-config
│ ├── .eslintrc.cjs
│ ├── .eslintignore
│ ├── tsconfig.json
│ └── build.config.ts
├── pnpm-workspace.yaml
├── .dockerignore
├── .stylelintrc.cjs
├── public
├── favicon.ico
└── resource
│ ├── img
│ ├── pwa-192x192.png
│ └── pwa-512x512.png
│ └── tinymce
│ ├── langs
│ └── README.md
│ ├── skins
│ ├── ui
│ │ ├── oxide
│ │ │ ├── skin.shadowdom.min.css
│ │ │ └── skin.shadowdom.js
│ │ ├── oxide-dark
│ │ │ ├── skin.shadowdom.min.css
│ │ │ └── skin.shadowdom.js
│ │ ├── tinymce-5
│ │ │ ├── skin.shadowdom.min.css
│ │ │ └── skin.shadowdom.js
│ │ └── tinymce-5-dark
│ │ │ ├── skin.shadowdom.min.css
│ │ │ └── skin.shadowdom.js
│ └── content
│ │ ├── default
│ │ └── content.min.css
│ │ ├── tinymce-5
│ │ └── content.min.css
│ │ ├── writer
│ │ └── content.min.css
│ │ └── dark
│ │ └── content.min.css
│ ├── plugins
│ └── code
│ │ └── plugin.min.js
│ └── license.txt
├── .eslintrc.cjs
├── .gitpod.yml
├── .prettierignore
├── .husky
└── pre-commit
├── uno.config.ts
├── .eslintignore
├── Dockerfile
├── .npmrc
├── .vscode
├── launch.json
└── extensions.json
├── .editorconfig
├── turbo.json
├── .env.development
├── .prettierrc.cjs
├── .gitattributes
├── .env.production
├── .gitignore
├── mock
└── _createProductionServer.ts
├── .env.test
├── .env.analyze
├── .chglog
└── config.yml
├── .github
└── workflows
│ ├── node.js.yml
│ └── eslint.yml
├── tsconfig.json
├── Makefile
└── .originallicense
└── LICENSE
/packages/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | dist
2 | public
3 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # spa-title
2 | VITE_GLOB_APP_TITLE = Simple Admin
--------------------------------------------------------------------------------
/packages/types/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './utils';
2 |
--------------------------------------------------------------------------------
/types/tree.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@axolo/tree-array';
2 |
--------------------------------------------------------------------------------
/types/vueuseCore.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@vueuse/core';
2 |
--------------------------------------------------------------------------------
/src/components/Tree/style/index.ts:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/types/codemirror.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@codemirror/lang-json';
2 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 | not ie 11
5 |
--------------------------------------------------------------------------------
/internal/ts-config/.eslintignore:
--------------------------------------------------------------------------------
1 |
2 | *.sh
3 | node_modules
4 | dist
5 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'internal/*'
3 | - 'packages/*'
4 |
--------------------------------------------------------------------------------
/src/logics/theme/index.ts:
--------------------------------------------------------------------------------
1 | export async function changeTheme(_color: string) {}
2 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .husky/
3 | build/
4 | public/
5 | src/
6 | tests/
7 | types/
--------------------------------------------------------------------------------
/src/enums/commonEnum.ts:
--------------------------------------------------------------------------------
1 | export const ZERO_UUID = '00000000-0000-0000-0000-000000000000';
2 |
--------------------------------------------------------------------------------
/src/views/sys/exception/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Exception } from './Exception.vue';
2 |
--------------------------------------------------------------------------------
/src/components/SimpleMenu/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SimpleMenu } from './src/SimpleMenu.vue';
2 |
--------------------------------------------------------------------------------
/src/design/config.less:
--------------------------------------------------------------------------------
1 | @import (reference) 'color.less';
2 | @import (reference) 'var/index.less';
3 |
--------------------------------------------------------------------------------
/.stylelintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@vben/stylelint-config'],
4 | };
5 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/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/components/CodeEditor/src/typing.ts:
--------------------------------------------------------------------------------
1 | export enum MODE {
2 | JSON = 'json',
3 | YAML = 'yaml',
4 | }
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/packages/hooks/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@vben/eslint-config/strict'],
4 | };
5 |
--------------------------------------------------------------------------------
/packages/types/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@vben/eslint-config/strict'],
4 | };
5 |
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/HEAD/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/header.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/HEAD/src/assets/images/header.jpg
--------------------------------------------------------------------------------
/internal/eslint-config/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@vben/eslint-config/strict'],
4 | };
5 |
--------------------------------------------------------------------------------
/internal/vite-config/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@vben/eslint-config/strict'],
4 | };
5 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@vben'],
4 | rules: {
5 | 'no-undef': 'off',
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | ports:
2 | - port: 3344
3 | onOpen: open-preview
4 | tasks:
5 | - init: pnpm install
6 | command: pnpm run dev
7 |
--------------------------------------------------------------------------------
/internal/stylelint-config/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@vben/eslint-config/strict'],
4 | };
5 |
--------------------------------------------------------------------------------
/public/resource/img/pwa-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/HEAD/public/resource/img/pwa-192x192.png
--------------------------------------------------------------------------------
/public/resource/img/pwa-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/HEAD/public/resource/img/pwa-512x512.png
--------------------------------------------------------------------------------
/src/enums/roleEnum.ts:
--------------------------------------------------------------------------------
1 | export enum RoleEnum {
2 | // super admin
3 | SUPER = '001',
4 |
5 | // tester
6 | STUFF = '002',
7 | }
8 |
--------------------------------------------------------------------------------
/internal/vite-config/.eslintignore:
--------------------------------------------------------------------------------
1 |
2 | *.sh
3 | node_modules
4 | *.md
5 | *.woff
6 | *.ttf
7 | .turbo
8 | dist
9 | package.json
10 |
--------------------------------------------------------------------------------
/src/assets/images/login-page-bg-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/HEAD/src/assets/images/login-page-bg-2.png
--------------------------------------------------------------------------------
/internal/eslint-config/.eslintignore:
--------------------------------------------------------------------------------
1 |
2 | *.sh
3 | node_modules
4 | *.md
5 | *.woff
6 | *.ttf
7 | .turbo
8 | dist
9 | package.json
10 |
--------------------------------------------------------------------------------
/internal/stylelint-config/.eslintignore:
--------------------------------------------------------------------------------
1 |
2 | *.sh
3 | node_modules
4 | *.md
5 | *.woff
6 | *.ttf
7 | .turbo
8 | dist
9 | package.json
10 |
--------------------------------------------------------------------------------
/src/components/JsonPreview/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 { default as ImagePreview } from './src/Preview.vue';
2 | export { createImgPreview } from './src/functional';
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/Markdown/src/typing.ts:
--------------------------------------------------------------------------------
1 | import Vditor from 'vditor';
2 |
3 | export interface MarkDownActionType {
4 | getVditor: () => Vditor;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/ContextMenu/index.ts:
--------------------------------------------------------------------------------
1 | export { createContextMenu, destroyContextMenu } from './src/createContextMenu';
2 |
3 | export * from './src/typing';
4 |
--------------------------------------------------------------------------------
/src/components/Icon/index.ts:
--------------------------------------------------------------------------------
1 | import SvgIcon from './src/SvgIcon.vue';
2 | import IconPicker from './src/IconPicker.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/components/Time/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils/index';
2 | import time from './src/Time.vue';
3 |
4 | export const Time = withInstall(time);
5 |
--------------------------------------------------------------------------------
/packages/hooks/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "@vben/ts-config/vue-app.json",
4 | "include": ["src"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "@vben/ts-config/vue-app.json",
4 | "include": ["src"]
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/CountTo/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import countTo from './src/CountTo.vue';
3 |
4 | export const CountTo = withInstall(countTo);
5 |
--------------------------------------------------------------------------------
/src/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/CardList/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import cardList from './src/CardList.vue';
3 |
4 | export const CardList = withInstall(cardList);
5 |
--------------------------------------------------------------------------------
/src/components/Tinymce/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils/index';
2 | import tinymce from './src/Editor.vue';
3 |
4 | export const Tinymce = withInstall(tinymce);
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | PATH="/usr/local/bin:$PATH"
3 |
4 | pnpm run lint
5 |
6 | # Format and submit code according to lintstagedrc.js configuration
7 | # npm run lint
8 |
--------------------------------------------------------------------------------
/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/Authority/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import authority from './src/Authority.vue';
3 |
4 | export const Authority = withInstall(authority);
5 |
--------------------------------------------------------------------------------
/src/components/FlowChart/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import flowChart from './src/FlowChart.vue';
3 |
4 | export const FlowChart = withInstall(flowChart);
5 |
--------------------------------------------------------------------------------
/uno.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, presetTypography, presetUno } from 'unocss';
2 |
3 | export default defineConfig({
4 | presets: [presetUno(), presetTypography()],
5 | });
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/VirtualScroll/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils/index';
2 | import vScroll from './src/VirtualScroll.vue';
3 |
4 | export const VScroll = withInstall(vScroll);
5 |
--------------------------------------------------------------------------------
/src/components/ClickOutSide/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import clickOutSide from './src/ClickOutSide.vue';
3 |
4 | export const ClickOutSide = withInstall(clickOutSide);
5 |
--------------------------------------------------------------------------------
/src/components/EllipsisText/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import ellipsisText from './src/EllipsisText.vue';
3 |
4 | export const EllipsisText = withInstall(ellipsisText);
5 |
--------------------------------------------------------------------------------
/public/resource/tinymce/langs/README.md:
--------------------------------------------------------------------------------
1 | This is where language files should be placed.
2 |
3 | Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce
4 |
--------------------------------------------------------------------------------
/src/components/StrengthMeter/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import strengthMeter from './src/StrengthMeter.vue';
3 |
4 | export const StrengthMeter = withInstall(strengthMeter);
5 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 |
2 | *.sh
3 | node_modules
4 | *.md
5 | *.woff
6 | *.ttf
7 | .vscode
8 | .idea
9 | dist
10 | /public
11 | /docs
12 | .husky
13 | .local
14 | /bin
15 | Dockerfile
16 | package.json
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:1.27.0-alpine
2 |
3 | COPY dist/ /usr/share/nginx/html/
4 | COPY deploy/default.conf /etc/nginx/conf.d/
5 |
6 | LABEL MAINTAINER="yuansu.china.work@gmail.com"
7 |
8 | EXPOSE 80
9 |
--------------------------------------------------------------------------------
/src/components/Dropdown/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import dropdown from './src/Dropdown.vue';
3 |
4 | export * from './src/typing';
5 | export const Dropdown = withInstall(dropdown);
6 |
--------------------------------------------------------------------------------
/src/components/Loading/index.ts:
--------------------------------------------------------------------------------
1 | import Loading from './src/Loading.vue';
2 |
3 | export { Loading };
4 | export { useLoading } from './src/useLoading';
5 | export { createLoading } from './src/createLoading';
6 |
--------------------------------------------------------------------------------
/src/components/Cropper/src/typing.ts:
--------------------------------------------------------------------------------
1 | import type Cropper from 'cropperjs';
2 |
3 | export interface CropendResult {
4 | imgBase64: string;
5 | imgInfo: Cropper.Data;
6 | }
7 |
8 | export type { Cropper };
9 |
--------------------------------------------------------------------------------
/src/components/Scrollbar/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * copy from element-ui
3 | */
4 |
5 | import Scrollbar from './src/Scrollbar.vue';
6 |
7 | export { Scrollbar };
8 | export type { ScrollbarType } from './src/types';
9 |
--------------------------------------------------------------------------------
/src/design/ant/popconfirm.less:
--------------------------------------------------------------------------------
1 | // 修复气泡确认框内的按钮在内容宽度不够换行的情况
2 | // 初始问题发现在 2.10.1 版本 固定列页面 http://ip:port/#/comp/table/fixedColumn
3 | .ant-popconfirm {
4 | &-buttons {
5 | white-space: nowrap;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/api/sys/model/uploadModel.ts:
--------------------------------------------------------------------------------
1 | export interface UploadApiResp {
2 | msg: string;
3 | code: number;
4 | data: UploadInfo;
5 | }
6 |
7 | export interface UploadInfo {
8 | name: string;
9 | url: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/JsonPreview/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import jsonPreview from './src/JsonPreview.vue';
3 |
4 | export const JsonPreview = withInstall(jsonPreview);
5 |
6 | export * from './src/typing';
7 |
--------------------------------------------------------------------------------
/src/components/Tree/index.ts:
--------------------------------------------------------------------------------
1 | import BasicTree from './src/BasicTree.vue';
2 | import './style';
3 |
4 | export { BasicTree };
5 | export type { ContextMenuItem } from '@/hooks/web/useContextMenu';
6 | export * from './src/types/tree';
7 |
--------------------------------------------------------------------------------
/src/components/Dropdown/src/typing.ts:
--------------------------------------------------------------------------------
1 | export interface DropMenu {
2 | onClick?: Fn;
3 | to?: string;
4 | icon?: string;
5 | event: string | number;
6 | text: string;
7 | disabled?: boolean;
8 | divider?: boolean;
9 | }
10 |
--------------------------------------------------------------------------------
/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/components/CodeEditor/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import codeEditor from './src/CodeEditor.vue';
3 | import { MODE } from './src/typing';
4 |
5 | export const CodeEditor = withInstall(codeEditor);
6 | export const LangMode = MODE;
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/components/registerGlobComp.ts:
--------------------------------------------------------------------------------
1 | import type { App } from 'vue';
2 | import { Button } from './Button';
3 | import { Input, Layout } from 'ant-design-vue';
4 |
5 | export function registerGlobComp(app: App) {
6 | app.use(Input).use(Button).use(Layout);
7 | }
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/views/sys/profile/data.ts:
--------------------------------------------------------------------------------
1 | export interface FormData {
2 | avatar: string;
3 | nickname: string;
4 | email: string;
5 | mobile: string;
6 | }
7 |
8 | export interface ChangePasswordReq {
9 | oldPassword: string;
10 | newPassword: string;
11 | }
12 |
--------------------------------------------------------------------------------
/.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/api/sys/model/captcha.ts:
--------------------------------------------------------------------------------
1 | export interface CaptchaResp {
2 | captchaId: string;
3 | imgPath: string;
4 | }
5 |
6 | export interface GetEmailCaptchaReq {
7 | email: string;
8 | }
9 |
10 | export interface GetSmsCaptchaReq {
11 | phoneNumber: string;
12 | }
13 |
--------------------------------------------------------------------------------
/internal/eslint-config/build.config.ts:
--------------------------------------------------------------------------------
1 | import { defineBuildConfig } from 'unbuild';
2 |
3 | export default defineBuildConfig({
4 | clean: true,
5 | entries: ['src/index', 'src/strict'],
6 | declaration: true,
7 | rollup: {
8 | emitCJS: true,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/src/components/Description/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import description from './src/Description.vue';
3 |
4 | export * from './src/typing';
5 | export { useDescription } from './src/useDescription';
6 | export const Description = withInstall(description);
7 |
--------------------------------------------------------------------------------
/src/components/Drawer/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import basicDrawer from './src/BasicDrawer.vue';
3 |
4 | export const BasicDrawer = withInstall(basicDrawer);
5 | export * from './src/typing';
6 | export { useDrawer, useDrawerInner } from './src/useDrawer';
7 |
--------------------------------------------------------------------------------
/src/components/FlowChart/src/enum.ts:
--------------------------------------------------------------------------------
1 | export enum ToolbarTypeEnum {
2 | ZOOM_IN = 'zoomIn',
3 | ZOOM_OUT = 'zoomOut',
4 | RESET_ZOOM = 'resetZoom',
5 |
6 | UNDO = 'undo',
7 | REDO = 'redo',
8 |
9 | SNAPSHOT = 'snapshot',
10 | VIEW_DATA = 'viewData',
11 | }
12 |
--------------------------------------------------------------------------------
/src/logics/theme/updateGrayMode.ts:
--------------------------------------------------------------------------------
1 | import { toggleClass } from './util';
2 |
3 | /**
4 | * Change project gray mode status
5 | * @param gray
6 | */
7 | export function updateGrayMode(gray: boolean) {
8 | toggleClass(gray, 'gray-mode', document.documentElement);
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Loading/src/typing.ts:
--------------------------------------------------------------------------------
1 | import { SizeEnum } from '@/enums/sizeEnum';
2 |
3 | export interface LoadingProps {
4 | tip: string;
5 | size: SizeEnum;
6 | absolute: boolean;
7 | loading: boolean;
8 | background: string;
9 | theme: 'dark' | 'light';
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/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 |
--------------------------------------------------------------------------------
/src/settings/siteSetting.ts:
--------------------------------------------------------------------------------
1 | // github repo url
2 | export const GITHUB_URL = 'https://github.com/suyuan32/simple-admin-core';
3 |
4 | // vue-vben-admin-next-doc
5 | export const DOC_URL = 'https://doc.ryansu.tech/';
6 |
7 | // site url
8 | export const SITE_URL = 'https://doc.ryansu.tech/';
9 |
--------------------------------------------------------------------------------
/internal/ts-config/vue-app.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Vue Application",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "jsx": "preserve",
7 | "lib": ["ESNext", "DOM"],
8 | "noImplicitAny": false
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/CountDown/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import countButton from './src/CountButton.vue';
3 | import countdownInput from './src/CountdownInput.vue';
4 |
5 | export const CountdownInput = withInstall(countdownInput);
6 | export const CountButton = withInstall(countButton);
7 |
--------------------------------------------------------------------------------
/src/components/Upload/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import basicUpload from './src/BasicUpload.vue';
3 | import uploadImage from './src/components/ImageUpload.vue';
4 |
5 | export const ImageUpload = withInstall(uploadImage);
6 | export const BasicUpload = withInstall(basicUpload);
7 |
--------------------------------------------------------------------------------
/src/utils/log.ts:
--------------------------------------------------------------------------------
1 | const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
2 |
3 | export function warn(message: string) {
4 | console.warn(`[${projectName} warn]:${message}`);
5 | }
6 |
7 | export function error(message: string) {
8 | throw new Error(`[${projectName} error]:${message}`);
9 | }
10 |
--------------------------------------------------------------------------------
/src/hooks/web/useDesign.ts:
--------------------------------------------------------------------------------
1 | import { useAppProviderContext } from '@/components/Application';
2 |
3 | export function useDesign(scope: string) {
4 | const values = useAppProviderContext();
5 | return {
6 | prefixCls: `${values.prefixCls}-${scope}`,
7 | prefixVar: values.prefixCls,
8 | };
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 |
--------------------------------------------------------------------------------
/.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:5173",
9 | "webRoot": "${workspaceFolder}/src",
10 | "sourceMaps": true
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Application/src/search/AppSearchKeyItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
--------------------------------------------------------------------------------
/src/components/Markdown/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import markDown from './src/Markdown.vue';
3 | import markDownViewer from './src/MarkdownViewer.vue';
4 |
5 | export const MarkDown = withInstall(markDown);
6 | export const MarkdownViewer = withInstall(markDownViewer);
7 | export * from './src/typing';
8 |
--------------------------------------------------------------------------------
/src/hooks/web/useAppInject.ts:
--------------------------------------------------------------------------------
1 | import { useAppProviderContext } from '@/components/Application';
2 | import { computed, unref } from 'vue';
3 |
4 | export function useAppInject() {
5 | const values = useAppProviderContext();
6 |
7 | return {
8 | getIsMobile: computed(() => unref(values.isMobile)),
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/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 | "noEmit": true,
10 | "baseUrl": "./"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Cropper/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import cropperImage from './src/Cropper.vue';
3 | import avatarCropper from './src/CropperAvatar.vue';
4 |
5 | export * from './src/typing';
6 | export const CropperImage = withInstall(cropperImage);
7 | export const CropperAvatar = withInstall(avatarCropper);
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "vue.volar",
4 | "dbaeumer.vscode-eslint",
5 | "stylelint.vscode-stylelint",
6 | "esbenp.prettier-vscode",
7 | "mrmlnc.vscode-less",
8 | "lokalise.i18n-ally",
9 | "antfu.iconify",
10 | "antfu.unocss",
11 | "mikestead.dotenv"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/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/components/Verify/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils/index';
2 | import basicDragVerify from './src/DragVerify.vue';
3 | import rotateDragVerify from './src/ImgRotate.vue';
4 |
5 | export const BasicDragVerify = withInstall(basicDragVerify);
6 | export const RotateDragVerify = withInstall(rotateDragVerify);
7 | export * from './src/typing';
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset=utf-8
5 | end_of_line=lf
6 | insert_final_newline=true
7 | indent_style=space
8 | indent_size=2
9 | max_line_length = 100
10 |
11 | [*.{yml,yaml,json}]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
18 | [Makefile]
19 | indent_style = tab
20 |
--------------------------------------------------------------------------------
/src/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/store/index.ts:
--------------------------------------------------------------------------------
1 | import type { App } from 'vue';
2 | import { createPinia } from 'pinia';
3 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
4 |
5 | const store = createPinia();
6 | store.use(piniaPluginPersistedstate);
7 |
8 | export function setupStore(app: App) {
9 | app.use(store);
10 | }
11 |
12 | export { store };
13 |
--------------------------------------------------------------------------------
/src/components/JsonPreview/src/JsonPreview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
--------------------------------------------------------------------------------
/src/components/Menu/src/types.ts:
--------------------------------------------------------------------------------
1 | export type Key = string | number;
2 | export interface MenuState {
3 | // 默认选中的列表
4 | defaultSelectedKeys: Key[];
5 |
6 | // 缩进
7 | inlineIndent?: number;
8 |
9 | // 展开数组
10 | openKeys: Key[];
11 |
12 | // 当前选中的菜单项 key 数组
13 | selectedKeys: Key[];
14 |
15 | // 收缩状态下展开的数组
16 | collapsedOpenKeys: Key[];
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Modal/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import './src/index.less';
3 | import basicModal from './src/BasicModal.vue';
4 |
5 | export const BasicModal = withInstall(basicModal);
6 | export { useModalContext } from './src/hooks/useModalContext';
7 | export { useModal, useModalInner } from './src/hooks/useModal';
8 | export * from './src/typing';
9 |
--------------------------------------------------------------------------------
/src/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/components/Basic/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import basicArrow from './src/BasicArrow.vue';
3 | import basicTitle from './src/BasicTitle.vue';
4 | import basicHelp from './src/BasicHelp.vue';
5 |
6 | export const BasicArrow = withInstall(basicArrow);
7 | export const BasicTitle = withInstall(basicTitle);
8 | export const BasicHelp = withInstall(basicHelp);
9 |
--------------------------------------------------------------------------------
/src/components/Container/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import collapseContainer from './src/collapse/CollapseContainer.vue';
3 | import scrollContainer from './src/ScrollContainer.vue';
4 |
5 | export const CollapseContainer = withInstall(collapseContainer);
6 | export const ScrollContainer = withInstall(scrollContainer);
7 |
8 | export * from './src/typing';
9 |
--------------------------------------------------------------------------------
/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/components/Excel/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import impExcel from './src/ImportExcel.vue';
3 | import expExcelModal from './src/ExportExcelModal.vue';
4 |
5 | export const ImpExcel = withInstall(impExcel);
6 | export const ExpExcelModal = withInstall(expExcelModal);
7 | export * from './src/typing';
8 | export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel';
9 |
--------------------------------------------------------------------------------
/src/locales/lang/zh-CN/antdLocale/DatePicker.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | lang: {
3 | shortWeekDays: ['日', '一', '二', '三', '四', '五', '六'],
4 | shortMonths: [
5 | '1月',
6 | '2月',
7 | '3月',
8 | '4月',
9 | '5月',
10 | '6月',
11 | '7月',
12 | '8月',
13 | '9月',
14 | '10月',
15 | '11月',
16 | '12月',
17 | ],
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/src/components/Table/src/components/EditTableHeaderIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
--------------------------------------------------------------------------------
/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/locales/lang/en.ts:
--------------------------------------------------------------------------------
1 | import { genMessage } from '../helper';
2 | import antdLocale from 'ant-design-vue/es/locale/en_US';
3 |
4 | const modules = import.meta.glob('./en/**/*.{json,ts,js}', { eager: true });
5 | export default {
6 | message: {
7 | ...genMessage(modules as Recordable, 'en'),
8 | antdLocale,
9 | },
10 | dateLocale: null,
11 | dateLocaleName: 'en',
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/copyTextToClipboard.ts:
--------------------------------------------------------------------------------
1 | import { message } from 'ant-design-vue';
2 |
3 | export function copyText(text: string, prompt: string | null = '已成功复制到剪切板!') {
4 | navigator.clipboard.writeText(text).then(
5 | function () {
6 | prompt && message.success(prompt);
7 | },
8 | function (error: Error) {
9 | message.error('复制失败!' + error.message);
10 | },
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/FlowChart/src/types.ts:
--------------------------------------------------------------------------------
1 | import { NodeConfig } from '@logicflow/core';
2 | import { ToolbarTypeEnum } from './enum';
3 |
4 | export interface NodeItem extends NodeConfig {
5 | icon: string;
6 | }
7 |
8 | export interface ToolbarConfig {
9 | type?: string | ToolbarTypeEnum;
10 | tooltip?: string | boolean;
11 | icon?: string;
12 | disabled?: boolean;
13 | separate?: boolean;
14 | }
15 |
--------------------------------------------------------------------------------
/src/api/fms/initialize.ts:
--------------------------------------------------------------------------------
1 | import { defHttp } from '@/utils/http/axios';
2 | import { BaseResp } from '@/api/model/baseModel';
3 |
4 | enum Api {
5 | InitializeDatabase = '/fms-api/init/database',
6 | }
7 |
8 | /**
9 | * @description: initialize the file manager database
10 | */
11 |
12 | export const initializeFileDatabase = () => {
13 | return defHttp.get({ url: Api.InitializeDatabase });
14 | };
15 |
--------------------------------------------------------------------------------
/src/design/transition/scale.less:
--------------------------------------------------------------------------------
1 | .scale-transition {
2 | .transition-default();
3 |
4 | &-enter-from,
5 | &-leave,
6 | &-leave-to {
7 | transform: scale(0);
8 | opacity: 0;
9 | }
10 | }
11 |
12 | .scale-rotate-transition {
13 | .transition-default();
14 |
15 | &-enter-from,
16 | &-leave,
17 | &-leave-to {
18 | transform: scale(0) rotate(-45deg);
19 | opacity: 0;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Scrollbar/src/types.d.ts:
--------------------------------------------------------------------------------
1 | export interface BarMapItem {
2 | offset: string;
3 | scroll: string;
4 | scrollSize: string;
5 | size: string;
6 | key: string;
7 | axis: string;
8 | client: string;
9 | direction: string;
10 | }
11 | export interface BarMap {
12 | vertical: BarMapItem;
13 | horizontal: BarMapItem;
14 | }
15 |
16 | export interface ScrollbarType {
17 | wrap: ElRef;
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Tree/src/TreeIcon.ts:
--------------------------------------------------------------------------------
1 | import type { VNode } from 'vue';
2 | import { h } from 'vue';
3 | import Icon from '@/components/Icon/Icon.vue';
4 | import { isString } from 'remeda';
5 |
6 | export const TreeIcon = ({ icon }: { icon: VNode | string | undefined }) => {
7 | if (!icon) return null;
8 | if (isString(icon)) {
9 | return h(Icon, { icon, class: 'mr-1' });
10 | }
11 | return h(Icon);
12 | };
13 |
--------------------------------------------------------------------------------
/src/api/sys/model/taskLogModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: TaskLog info response
5 | */
6 | export interface TaskLogInfo {
7 | id?: number;
8 | startedAt?: number;
9 | finishedAt?: number;
10 | result?: number;
11 | }
12 |
13 | /**
14 | * @description: TaskLog list response
15 | */
16 |
17 | export type TaskLogListResp = BaseListResp;
18 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # Whether to open mock
2 | VITE_USE_MOCK = false
3 |
4 | # public path
5 | VITE_PUBLIC_PATH = /
6 |
7 | VITE_BUILD_COMPRESS = 'none'
8 |
9 | # Delete console
10 | VITE_DROP_CONSOLE = false
11 |
12 | # Basic interface address SPA
13 | VITE_GLOB_API_URL=
14 |
15 | # File upload address, optional
16 | VITE_GLOB_UPLOAD_URL=/fms-api/cloud_file/upload
17 |
18 | # Interface prefix
19 | VITE_GLOB_API_URL_PREFIX=
20 |
--------------------------------------------------------------------------------
/src/components/Form/src/hooks/useComponentRegister.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentType } from '../types/index';
2 | import { tryOnUnmounted } from '@vueuse/core';
3 | import { add, del } from '../componentMap';
4 | import type { Component } from 'vue';
5 |
6 | export function useComponentRegister(compName: ComponentType, comp: Component) {
7 | add(compName, comp);
8 | tryOnUnmounted(() => {
9 | del(compName);
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/src/api/member/initialize.ts:
--------------------------------------------------------------------------------
1 | import { defHttp } from '@/utils/http/axios';
2 | import { BaseResp } from '@/api/model/baseModel';
3 |
4 | enum Api {
5 | InitializeMMSDatabase = '/mms-api/init/database',
6 | }
7 |
8 | /**
9 | * @description: initialize the member management service database
10 | */
11 |
12 | export const initializeMMSDatabase = () => {
13 | return defHttp.get({ url: Api.InitializeMMSDatabase });
14 | };
15 |
--------------------------------------------------------------------------------
/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 SHOULD_ENABLE_STORAGE_ENCRYPTION = !isDevMode();
14 |
--------------------------------------------------------------------------------
/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/api/fms/model/fileTagModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Tag info response
5 | */
6 | export interface TagInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | status?: number;
11 | name?: string;
12 | remark?: string;
13 | }
14 |
15 | /**
16 | * @description: Tag list response
17 | */
18 |
19 | export type TagListResp = BaseListResp;
20 |
--------------------------------------------------------------------------------
/src/utils/object.ts:
--------------------------------------------------------------------------------
1 | export function get(obj: object, path: string, defaultValue?: any) {
2 | const travel = (regexp: RegExp) =>
3 | String.prototype.split
4 | .call(path, regexp)
5 | .filter(Boolean)
6 | .reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj);
7 | const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/);
8 | return result === undefined || result === obj ? defaultValue : result;
9 | }
10 |
--------------------------------------------------------------------------------
/.prettierrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | semi: true,
4 | vueIndentScriptAndStyle: true,
5 | singleQuote: true,
6 | trailingComma: 'all',
7 | proseWrap: 'never',
8 | htmlWhitespaceSensitivity: 'strict',
9 | endOfLine: 'auto',
10 | plugins: ['prettier-plugin-packagejson'],
11 | overrides: [
12 | {
13 | files: '.*rc',
14 | options: {
15 | parser: 'json',
16 | },
17 | },
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/src/components/FlowChart/src/useFlowContext.ts:
--------------------------------------------------------------------------------
1 | import type LogicFlow from '@logicflow/core';
2 |
3 | import { provide, inject } from 'vue';
4 |
5 | const key = Symbol('flow-chart');
6 |
7 | type Instance = {
8 | logicFlow: LogicFlow;
9 | };
10 |
11 | export function createFlowChartContext(instance: Instance) {
12 | provide(key, instance);
13 | }
14 |
15 | export function useFlowChartContext(): Instance {
16 | return inject(key) as Instance;
17 | }
18 |
--------------------------------------------------------------------------------
/src/directives/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Configure and register global directives
3 | */
4 | import type { App } from 'vue';
5 | import { setupPermissionDirective } from './permission';
6 | import { setupLoadingDirective } from './loading';
7 | import { setupEllipsisDirective } from './ellipsis';
8 |
9 | export function setupGlobDirectives(app: App) {
10 | setupPermissionDirective(app);
11 | setupLoadingDirective(app);
12 | setupEllipsisDirective(app);
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Menu/src/components/BasicMenuItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
16 |
--------------------------------------------------------------------------------
/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-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/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 | import type { ExtractPropTypes } from 'vue';
3 | import button from './src/BasicButton.vue';
4 | import popConfirmButton from './src/PopConfirmButton.vue';
5 | import { buttonProps } from './src/props';
6 |
7 | export const Button = withInstall(button);
8 | export const PopConfirmButton = withInstall(popConfirmButton);
9 | export declare type ButtonProps = Partial>;
10 |
--------------------------------------------------------------------------------
/src/directives/ripple/index.less:
--------------------------------------------------------------------------------
1 | .ripple-container {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | width: 0;
6 | height: 0;
7 | overflow: hidden;
8 | pointer-events: none;
9 | }
10 |
11 | .ripple-effect {
12 | position: relative;
13 | z-index: 9999;
14 | width: 1px;
15 | height: 1px;
16 | margin-top: 0;
17 | margin-left: 0;
18 | transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
19 | border-radius: 50%;
20 | pointer-events: none;
21 | }
22 |
--------------------------------------------------------------------------------
/src/views/sys/lock/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
--------------------------------------------------------------------------------
/src/layouts/default/setting/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
--------------------------------------------------------------------------------
/src/layouts/default/trigger/SiderTrigger.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
--------------------------------------------------------------------------------
/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/api/mcms/model/messageModel.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description: Send email message request
3 | */
4 | export interface SendEmailReq {
5 | target: string;
6 | subject: string;
7 | content: string;
8 | provider?: string;
9 | }
10 |
11 | /**
12 | * @description: Send sms message request
13 | */
14 | export interface SendSmsReq {
15 | phoneNumber: string;
16 | params: string;
17 | templateId?: string;
18 | appId?: string;
19 | signName?: string;
20 | provider?: string;
21 | }
22 |
--------------------------------------------------------------------------------
/src/api/member/model/memberRankModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: MemberRank info response
5 | */
6 | export interface MemberRankInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | name?: string;
11 | description?: string;
12 | remark?: string;
13 | }
14 |
15 | /**
16 | * @description: MemberRank list response
17 | */
18 |
19 | export type MemberRankListResp = BaseListResp;
20 |
--------------------------------------------------------------------------------
/src/api/fms/model/cloudFileTagModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: CloudFileTag info response
5 | */
6 | export interface CloudFileTagInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | status?: number;
11 | name?: string;
12 | remark?: string;
13 | }
14 |
15 | /**
16 | * @description: CloudFileTag list response
17 | */
18 |
19 | export type CloudFileTagListResp = BaseListResp;
20 |
--------------------------------------------------------------------------------
/src/components/Modal/src/components/ModalHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ props.title }}
4 |
5 |
6 |
17 |
--------------------------------------------------------------------------------
/src/api/mcms/model/smsLogModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: SmsLog info response
5 | */
6 | export interface SmsLogInfo {
7 | id?: string;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | phoneNumber?: string;
11 | content?: string;
12 | sendStatus?: number;
13 | provider?: string;
14 | }
15 |
16 | /**
17 | * @description: SmsLog list response
18 | */
19 |
20 | export type SmsLogListResp = BaseListResp;
21 |
--------------------------------------------------------------------------------
/.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/sys/model/dictionaryModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Dictionary info response
5 | */
6 | export interface DictionaryInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | title?: string;
11 | name?: string;
12 | status?: number;
13 | desc?: string;
14 | }
15 |
16 | /**
17 | * @description: Dictionary list response
18 | */
19 |
20 | export type DictionaryListResp = BaseListResp;
21 |
--------------------------------------------------------------------------------
/src/layouts/default/trigger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
16 |
--------------------------------------------------------------------------------
/src/api/sys/model/positionModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Position info response
5 | */
6 | export interface PositionInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | status?: number;
11 | sort?: number;
12 | name?: string;
13 | code?: string;
14 | remark?: string;
15 | }
16 |
17 | /**
18 | * @description: Position list response
19 | */
20 |
21 | export type PositionListResp = BaseListResp;
22 |
--------------------------------------------------------------------------------
/src/components/ClickOutSide/src/ClickOutSide.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
21 |
--------------------------------------------------------------------------------
/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%;
8 | max-width: 100%;
9 | }
10 | }
11 |
12 | .ant-input-affix-wrapper .ant-input-suffix {
13 | right: 9px;
14 | }
15 |
16 | .ant-input-clear-icon {
17 | margin-right: 5px;
18 | }
19 |
20 | .ant-input-affix-wrapper-textarea-with-clear-btn {
21 | padding: 0 !important;
22 |
23 | textarea.ant-input {
24 | padding: 4px;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/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 | // initialize database page
7 | BASE_INITIAL_PAGE = '/init',
8 | // error page path
9 | ERROR_PAGE = '/exception',
10 | // error log page path
11 | ERROR_LOG_PAGE = '/error-log/list',
12 | // oauth callback page
13 | OAUTH_CALLBACK = '/oauth/login/callback',
14 | }
15 |
16 | export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight';
17 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # Whether to open mock
2 | VITE_USE_MOCK = false
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=
15 |
16 | # File upload address, optional
17 | VITE_GLOB_UPLOAD_URL=/fms-api/cloud_file/upload
18 |
19 | # Interface prefix
20 | VITE_GLOB_API_URL_PREFIX=
21 |
--------------------------------------------------------------------------------
/src/api/sys/model/roleModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Role info response
5 | */
6 | export interface RoleInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | status?: number;
11 | name?: string;
12 | code?: string;
13 | defaultRouter?: string;
14 | remark?: string;
15 | sort?: number;
16 | }
17 |
18 | /**
19 | * @description: Role list response
20 | */
21 |
22 | export type RoleListResp = BaseListResp;
23 |
--------------------------------------------------------------------------------
/src/components/Container/src/typing.ts:
--------------------------------------------------------------------------------
1 | export type ScrollType = 'default' | 'main';
2 |
3 | export interface CollapseContainerOptions {
4 | canExpand?: boolean;
5 | title?: string;
6 | helpMessage?: Array | string;
7 | }
8 | export interface ScrollContainerOptions {
9 | enableScroll?: boolean;
10 | type?: ScrollType;
11 | }
12 |
13 | export type ScrollActionType = RefType<{
14 | scrollBottom: () => void;
15 | getScrollWrap: () => Nullable;
16 | scrollTo: (top: number) => void;
17 | }>;
18 |
--------------------------------------------------------------------------------
/src/hooks/web/useContextMenu.ts:
--------------------------------------------------------------------------------
1 | import { onUnmounted, getCurrentInstance } from 'vue';
2 | import { createContextMenu, destroyContextMenu } from '@/components/ContextMenu';
3 | import type { ContextMenuItem } from '@/components/ContextMenu';
4 |
5 | export type { ContextMenuItem };
6 | export function useContextMenu(authRemove = true) {
7 | if (getCurrentInstance() && authRemove) {
8 | onUnmounted(() => {
9 | destroyContextMenu();
10 | });
11 | }
12 | return [createContextMenu, destroyContextMenu];
13 | }
14 |
--------------------------------------------------------------------------------
/types/module.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import { DefineComponent } from 'vue';
3 |
4 | const Component: DefineComponent<{}, {}, any>;
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | node-jiti
3 | .DS_Store
4 | dist
5 | .cache
6 | .turbo
7 |
8 | tests/server/static
9 | tests/server/static/upload
10 |
11 | .local
12 | # local env files
13 | .env.local
14 | .env.*.local
15 | .eslintcache
16 |
17 | # Log files
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 | pnpm-debug.log*
22 |
23 | # Editor directories and files
24 | .idea
25 | # .vscode
26 | *.suo
27 | *.ntvs*
28 | *.njsproj
29 | *.sln
30 | *.sw?
31 |
32 | package-lock.json
33 | pnpm-lock.yaml
34 |
35 | .history
36 |
--------------------------------------------------------------------------------
/src/api/mcms/model/emailLogModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: EmailLog info response
5 | */
6 | export interface EmailLogInfo {
7 | id?: string;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | target?: string;
11 | subject?: string;
12 | content?: string;
13 | sendStatus?: number;
14 | provider?: string;
15 | }
16 |
17 | /**
18 | * @description: EmailLog list response
19 | */
20 |
21 | export type EmailLogListResp = BaseListResp;
22 |
--------------------------------------------------------------------------------
/src/api/sys/model/taskModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Task info response
5 | */
6 | export interface TaskInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | status?: number;
11 | name?: string;
12 | taskGroup?: string;
13 | cronExpression?: string;
14 | pattern?: string;
15 | payload?: string;
16 | }
17 |
18 | /**
19 | * @description: Task list response
20 | */
21 |
22 | export type TaskListResp = BaseListResp;
23 |
--------------------------------------------------------------------------------
/src/api/sys/model/tokenModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Token info response
5 | */
6 | export interface TokenInfo {
7 | id?: string;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | status?: number;
11 | uuid?: string;
12 | token?: string;
13 | source?: string;
14 | expiredAt?: number;
15 | username?: string;
16 | }
17 |
18 | /**
19 | * @description: Token list response
20 | */
21 |
22 | export type TokenListResp = BaseListResp;
23 |
--------------------------------------------------------------------------------
/src/views/dashboard/analysis/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
--------------------------------------------------------------------------------
/src/api/mcms/model/smsProviderModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: SmsProvider info response
5 | */
6 | export interface SmsProviderInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | name?: string;
11 | secretId?: string;
12 | secretKey?: string;
13 | region?: string;
14 | isDefault?: boolean;
15 | }
16 |
17 | /**
18 | * @description: SmsProvider list response
19 | */
20 |
21 | export type SmsProviderListResp = BaseListResp;
22 |
--------------------------------------------------------------------------------
/src/api/sys/model/apiModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Api info response
5 | */
6 | export interface ApiInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | trans?: string;
11 | path: string;
12 | description?: string;
13 | group: string;
14 | method: string;
15 | isRequired: boolean;
16 | serviceName: string;
17 | }
18 |
19 | /**
20 | * @description: Api list response
21 | */
22 |
23 | export type ApiListResp = BaseListResp;
24 |
--------------------------------------------------------------------------------
/src/enums/sizeEnum.ts:
--------------------------------------------------------------------------------
1 | export enum SizeEnum {
2 | DEFAULT = 'default',
3 | SMALL = 'small',
4 | LARGE = 'large',
5 | }
6 |
7 | export enum SizeNumberEnum {
8 | DEFAULT = 48,
9 | SMALL = 16,
10 | LARGE = 64,
11 | }
12 |
13 | export const sizeMap: Map = (() => {
14 | const map = new Map();
15 | map.set(SizeEnum.DEFAULT, SizeNumberEnum.DEFAULT);
16 | map.set(SizeEnum.SMALL, SizeNumberEnum.SMALL);
17 | map.set(SizeEnum.LARGE, SizeNumberEnum.LARGE);
18 | return map;
19 | })();
20 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/ui/oxide/skin.shadowdom.min.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}
2 |
--------------------------------------------------------------------------------
/src/components/Modal/src/hooks/useModalContext.ts:
--------------------------------------------------------------------------------
1 | import { InjectionKey } from 'vue';
2 | import { createContext, useContext } from '@/hooks/core/useContext';
3 |
4 | export interface ModalContextProps {
5 | redoModalHeight: () => void;
6 | }
7 |
8 | const key: InjectionKey = Symbol();
9 |
10 | export function createModalContext(context: ModalContextProps) {
11 | return createContext(context, key);
12 | }
13 |
14 | export function useModalContext() {
15 | return useContext(key);
16 | }
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}
2 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}
2 |
--------------------------------------------------------------------------------
/src/locales/lang/zh-CN/routes/system.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | systemManagementTitle: '系统管理',
3 | menuManagementTitle: '菜单管理',
4 | roleManagementTitle: '角色管理',
5 | apiManagementTitle: 'API管理',
6 | userManagementTitle: '用户管理',
7 | fileManagementTitle: '文件管理',
8 | userProfileTitle: '用户个人信息',
9 | dictionaryManagementTitle: '字典管理',
10 | dictionaryDetailManagementTitle: '键值管理',
11 | oauthManagement: 'Oauth管理',
12 | tokenManagement: 'Token管理',
13 | otherPages: '其他页面',
14 | positionManagement: '职位管理',
15 | taskManagement: '定时任务管理',
16 | };
17 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/mock/_createProductionServer.ts:
--------------------------------------------------------------------------------
1 | import { createProdMockServer } from 'vite-plugin-mock/client';
2 |
3 | const modules = import.meta.glob('./**/*.ts', { eager: true });
4 |
5 | const mockModules: any[] = [];
6 | Object.keys(modules).forEach((key) => {
7 | if (key.includes('/_')) {
8 | return;
9 | }
10 | mockModules.push(...(modules as Recordable)[key].default);
11 | });
12 |
13 | /**
14 | * Used in a production environment. Need to manually import all modules
15 | */
16 | export function setupProdMockServer() {
17 | createProdMockServer(mockModules);
18 | }
19 |
--------------------------------------------------------------------------------
/src/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/hooks/setting/useDarkModeTheme.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue';
2 | import { theme } from 'ant-design-vue';
3 | import { useRootSetting } from '@/hooks/setting/useRootSetting';
4 | import { ThemeEnum } from '@/enums/appEnum';
5 |
6 | export function useDarkModeTheme() {
7 | const { getDarkMode } = useRootSetting();
8 | const { darkAlgorithm } = theme;
9 | const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK);
10 | const darkTheme = {
11 | algorithm: [darkAlgorithm],
12 | };
13 |
14 | return {
15 | isDark,
16 | darkTheme,
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/locales/lang/en/routes/system.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | systemManagementTitle: 'System',
3 | menuManagementTitle: 'Menu',
4 | roleManagementTitle: 'Role',
5 | apiManagementTitle: 'API',
6 | userManagementTitle: 'User',
7 | fileManagementTitle: 'File',
8 | userProfileTitle: 'Profile',
9 | dictionaryManagementTitle: 'Dictionary',
10 | dictionaryDetailManagementTitle: 'Key/Value',
11 | oauthManagement: 'Oauth',
12 | tokenManagement: 'Token',
13 | otherPages: 'Other Pages',
14 | positionManagement: 'Position',
15 | taskManagement: 'Scheduled Task',
16 | };
17 |
--------------------------------------------------------------------------------
/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 | function strToHex(str: string) {
8 | const result: string[] = [];
9 | for (let i = 0; i < str.length; ++i) {
10 | const hex = str.charCodeAt(i).toString(16);
11 | result.push(('000' + hex).slice(-4));
12 | }
13 | return result.join('').toUpperCase();
14 | }
15 |
16 | export { createContentHash, strToHex };
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/components/Form/src/hooks/useFormContext.ts:
--------------------------------------------------------------------------------
1 | import type { InjectionKey } from 'vue';
2 | import { createContext, useContext } from '@/hooks/core/useContext';
3 |
4 | export interface FormContextProps {
5 | resetAction: () => Promise;
6 | submitAction: () => Promise;
7 | }
8 |
9 | const key: InjectionKey = Symbol();
10 |
11 | export function createFormContext(context: FormContextProps) {
12 | return createContext(context, key);
13 | }
14 |
15 | export function useFormContext() {
16 | return useContext(key);
17 | }
18 |
--------------------------------------------------------------------------------
/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/api/fms/model/cloudFileModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: CloudFile info response
5 | */
6 | export interface CloudFileInfo {
7 | id?: string;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | state?: boolean;
11 | name?: string;
12 | url?: string;
13 | size?: number;
14 | fileType?: number;
15 | userId?: string;
16 | providerId?: number;
17 | tagIds?: number[];
18 | }
19 |
20 | /**
21 | * @description: CloudFile list response
22 | */
23 |
24 | export type CloudFileListResp = BaseListResp;
25 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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=/sys-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=/fms-api/upload
19 |
20 | # Interface prefix
21 | VITE_GLOB_API_URL_PREFIX=
22 |
--------------------------------------------------------------------------------
/src/enums/exceptionEnum.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description: Exception related enumeration
3 | */
4 | export enum ExceptionEnum {
5 | // page not access
6 | PAGE_NOT_ACCESS = 403,
7 |
8 | // page not found
9 | PAGE_NOT_FOUND = 404,
10 |
11 | // error
12 | ERROR = 500,
13 |
14 | // net work error
15 | NET_WORK_ERROR = 10000,
16 |
17 | // No data on the page. In fact, it is not an exception page
18 | PAGE_NOT_DATA = 10100,
19 | }
20 |
21 | export enum ErrorTypeEnum {
22 | VUE = 'vue',
23 | SCRIPT = 'script',
24 | RESOURCE = 'resource',
25 | AJAX = 'ajax',
26 | PROMISE = 'promise',
27 | }
28 |
--------------------------------------------------------------------------------
/src/layouts/default/header/components/index.ts:
--------------------------------------------------------------------------------
1 | import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
2 | import FullScreen from './FullScreen.vue';
3 |
4 | export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), {
5 | loading: true,
6 | });
7 |
8 | export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue'));
9 |
10 | export const Notify = createAsyncComponent(() => import('./notify/index.vue'));
11 |
12 | export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue'));
13 |
14 | export { FullScreen };
15 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/api/member/model/memberModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Member info response
5 | */
6 | export interface MemberInfo {
7 | id?: string;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | status?: number;
11 | username?: string;
12 | password?: string;
13 | nickname?: string;
14 | rankId?: number;
15 | mobile?: string;
16 | email?: string;
17 | avatar?: string;
18 | expiredAt?: number;
19 | }
20 |
21 | /**
22 | * @description: Member list response
23 | */
24 |
25 | export type MemberListResp = BaseListResp;
26 |
--------------------------------------------------------------------------------
/src/locales/lang/zh_CN.ts:
--------------------------------------------------------------------------------
1 | import { genMessage } from '../helper';
2 | import antdLocale from 'ant-design-vue/es/locale/zh_CN';
3 | import { deepMerge } from '/@/utils';
4 |
5 | const modules = import.meta.glob('./zh-CN/**/*.{json,ts,js}', { eager: true });
6 |
7 | export default {
8 | message: {
9 | ...genMessage(modules as Recordable, 'zh-CN'),
10 | antdLocale: {
11 | ...antdLocale,
12 | DatePicker: deepMerge(
13 | antdLocale.DatePicker,
14 | genMessage(modules as Recordable, 'zh-CN').antdLocale.DatePicker,
15 | ),
16 | },
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/.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 | import { createContext, useContext } from '@/hooks/core/useContext';
3 |
4 | export interface AppProviderContextProps {
5 | prefixCls: Ref;
6 | isMobile: Ref;
7 | }
8 |
9 | const key: InjectionKey = Symbol();
10 |
11 | export function createAppProviderContext(context: AppProviderContextProps) {
12 | return createContext(context, key);
13 | }
14 |
15 | export function useAppProviderContext() {
16 | return useContext(key);
17 | }
18 |
--------------------------------------------------------------------------------
/src/layouts/default/tabs/types.ts:
--------------------------------------------------------------------------------
1 | import type { DropMenu } from '@/components/Dropdown/index';
2 | import type { RouteLocationNormalized } from 'vue-router';
3 |
4 | export enum TabContentEnum {
5 | TAB_TYPE,
6 | EXTRA_TYPE,
7 | }
8 |
9 | export type { DropMenu };
10 |
11 | export interface TabContentProps {
12 | tabItem: RouteLocationNormalized;
13 | type?: TabContentEnum;
14 | trigger?: ('click' | 'hover' | 'contextmenu')[];
15 | }
16 |
17 | export enum MenuEventEnum {
18 | REFRESH_PAGE,
19 | CLOSE_CURRENT,
20 | CLOSE_LEFT,
21 | CLOSE_RIGHT,
22 | CLOSE_OTHER,
23 | CLOSE_ALL,
24 | SCALE,
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Table/src/hooks/useLoading.ts:
--------------------------------------------------------------------------------
1 | import { ref, ComputedRef, unref, computed, watch } from 'vue';
2 | import type { BasicTableProps } from '../types/table';
3 |
4 | export function useLoading(props: ComputedRef) {
5 | const loadingRef = ref(unref(props).loading);
6 |
7 | watch(
8 | () => unref(props).loading,
9 | (loading) => {
10 | loadingRef.value = loading;
11 | },
12 | );
13 |
14 | const getLoading = computed(() => unref(loadingRef));
15 |
16 | function setLoading(loading: boolean) {
17 | loadingRef.value = loading;
18 | }
19 |
20 | return { getLoading, setLoading };
21 | }
22 |
--------------------------------------------------------------------------------
/src/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 | transform: scale(0);
12 | opacity: 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 | transform: scale(0.92);
25 | opacity: 0;
26 | }
27 |
28 | .zoom-fade-leave-to {
29 | transform: scale(1.06);
30 | opacity: 0;
31 | }
32 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/ui/oxide/skin.shadowdom.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('ui/default/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}")
2 | //# sourceMappingURL=skin.shadowdom.js.map
3 |
--------------------------------------------------------------------------------
/src/api/sys/model/departmentModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Department info response
5 | */
6 | export interface DepartmentInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | trans?: string;
11 | status?: number;
12 | sort?: number;
13 | name?: string;
14 | ancestors?: string;
15 | leader?: string;
16 | phone?: string;
17 | email?: string;
18 | remark?: string;
19 | parentId?: number;
20 | }
21 |
22 | /**
23 | * @description: Department list response
24 | */
25 |
26 | export type DepartmentListResp = BaseListResp;
27 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/ui/oxide-dark/skin.shadowdom.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('ui/dark/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}")
2 | //# sourceMappingURL=skin.shadowdom.js.map
3 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/ui/tinymce-5/skin.shadowdom.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('ui/tinymce-5/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}")
2 | //# sourceMappingURL=skin.shadowdom.js.map
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('ui/tinymce-5-dark/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}")
2 | //# sourceMappingURL=skin.shadowdom.js.map
3 |
--------------------------------------------------------------------------------
/src/api/model/baseModel.ts:
--------------------------------------------------------------------------------
1 | export interface BaseListReq {
2 | page: number;
3 | pageSize: number;
4 | }
5 |
6 | export interface BaseListResp {
7 | data: T[];
8 | total: number;
9 | }
10 |
11 | export interface BaseDataResp {
12 | code: number;
13 | msg: string;
14 | data: T;
15 | }
16 |
17 | export interface BaseResp {
18 | code?: number;
19 | msg: string;
20 | }
21 |
22 | export interface BaseIDReq {
23 | id?: number;
24 | }
25 |
26 | export interface BaseIDsReq {
27 | ids: number[];
28 | }
29 |
30 | export interface BaseUUIDReq {
31 | id: string;
32 | }
33 |
34 | export interface BaseUUIDsReq {
35 | ids: string[];
36 | }
37 |
--------------------------------------------------------------------------------
/src/layouts/default/content/useContentContext.ts:
--------------------------------------------------------------------------------
1 | import type { InjectionKey, ComputedRef } from 'vue';
2 | import { createContext, useContext } from '@/hooks/core/useContext';
3 |
4 | export interface ContentContextProps {
5 | contentHeight: ComputedRef;
6 | setPageHeight: (height: number) => Promise;
7 | }
8 |
9 | const key: InjectionKey = Symbol();
10 |
11 | export function createContentContext(context: ContentContextProps) {
12 | return createContext(context, key, { native: true });
13 | }
14 |
15 | export function useContentContext() {
16 | return useContext(key);
17 | }
18 |
--------------------------------------------------------------------------------
/packages/hooks/src/useRefs.ts:
--------------------------------------------------------------------------------
1 | import type { ComponentPublicInstance, Ref } from 'vue';
2 | import { onBeforeUpdate, shallowRef } from 'vue';
3 |
4 | function useRefs(): {
5 | refs: Ref;
6 | setRefs: (index: number) => (el: Element | ComponentPublicInstance | null) => void;
7 | } {
8 | const refs = shallowRef([]) as Ref;
9 |
10 | onBeforeUpdate(() => {
11 | refs.value = [];
12 | });
13 |
14 | const setRefs = (index: number) => (el: Element | ComponentPublicInstance | null) => {
15 | refs.value[index] = el as T;
16 | };
17 |
18 | return {
19 | refs,
20 | setRefs,
21 | };
22 | }
23 |
24 | export { useRefs };
25 |
--------------------------------------------------------------------------------
/src/components/Table/index.ts:
--------------------------------------------------------------------------------
1 | export { default as BasicTable } from './src/BasicTable.vue';
2 | export { default as TableAction } from './src/components/TableAction.vue';
3 | export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
4 | export { default as TableImg } from './src/components/TableImg.vue';
5 |
6 | export * from './src/types/table';
7 | export * from './src/types/pagination';
8 | export * from './src/types/tableAction';
9 | export { useTable } from './src/hooks/useTable';
10 | export type { FormSchema, FormProps } from '@/components/Form/src/types/form';
11 | export type { EditRecordRow } from './src/components/editable';
12 |
--------------------------------------------------------------------------------
/src/hooks/component/usePageContext.ts:
--------------------------------------------------------------------------------
1 | import type { InjectionKey, ComputedRef, Ref } from 'vue';
2 | import { createContext, useContext } from '@/hooks/core/useContext';
3 |
4 | export interface PageContextProps {
5 | contentHeight: ComputedRef;
6 | pageHeight: Ref;
7 | setPageHeight: (height: number) => Promise;
8 | }
9 |
10 | const key: InjectionKey = Symbol();
11 |
12 | export function createPageContext(context: PageContextProps) {
13 | return createContext(context, key, { native: true });
14 | }
15 |
16 | export function usePageContext() {
17 | return useContext(key);
18 | }
19 |
--------------------------------------------------------------------------------
/src/hooks/web/useSortable.ts:
--------------------------------------------------------------------------------
1 | import { nextTick, unref } from 'vue';
2 | import type { Ref } from 'vue';
3 | import type { Options } from 'sortablejs';
4 |
5 | export function useSortable(el?: HTMLElement | Ref, options?: Options) {
6 | function initSortable() {
7 | nextTick(async () => {
8 | el = unref(el);
9 |
10 | if (!el) return;
11 |
12 | const Sortable = (await import('sortablejs')).default;
13 | Sortable.create(el, {
14 | animation: 100,
15 | delay: 400,
16 | delayOnTouchOnly: true,
17 | ...options,
18 | });
19 | });
20 | }
21 |
22 | return { initSortable };
23 | }
24 |
--------------------------------------------------------------------------------
/.chglog/config.yml:
--------------------------------------------------------------------------------
1 | style: github
2 | template: CHANGELOG.tpl.md
3 | info:
4 | title: CHANGELOG
5 | repository_url: https://github.com/suyuan32/simple-admin-backend-ui
6 | options:
7 | commits:
8 | # filters:
9 | # Type:
10 | # - feat
11 | # - fix
12 | # - perf
13 | # - refactor
14 | commit_groups:
15 | # title_maps:
16 | # feat: Features
17 | # fix: Bug Fixes
18 | # perf: Performance Improvements
19 | # refactor: Code Refactoring
20 | header:
21 | pattern: "^(\\w*)\\:\\s(.*)$"
22 | pattern_maps:
23 | - Type
24 | - Subject
25 | notes:
26 | keywords:
27 | - BREAKING CHANGE
28 |
--------------------------------------------------------------------------------
/src/api/mcms/model/emailProviderModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: EmailProvider info response
5 | */
6 | export interface EmailProviderInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | name?: string;
11 | authType?: number;
12 | emailAddr?: string;
13 | password?: string;
14 | hostName?: string;
15 | identify?: string;
16 | secret?: string;
17 | port?: number;
18 | tls?: boolean;
19 | isDefault?: boolean;
20 | }
21 |
22 | /**
23 | * @description: EmailProvider list response
24 | */
25 |
26 | export type EmailProviderListResp = BaseListResp;
27 |
--------------------------------------------------------------------------------
/src/api/sys/model/dictionaryDetailModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: DictionaryDetail info response
5 | */
6 | export interface DictionaryDetailInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | status?: number;
11 | title?: string;
12 | key?: string;
13 | value?: string;
14 | }
15 |
16 | /**
17 | * @description: DictionaryDetail list response
18 | */
19 |
20 | export type DictionaryDetailListResp = BaseListResp;
21 |
22 | /**
23 | * @description: Dictionary name request
24 | */
25 | export interface DictionaryNameReq {
26 | name: string;
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Table/src/components/settings/RedoSetting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ t('common.redo') }}
5 |
6 |
7 |
8 |
9 |
22 |
--------------------------------------------------------------------------------
/src/layouts/default/tabs/components/SettingButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
--------------------------------------------------------------------------------
/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 = 320,
12 | SM = 640,
13 | MD = 768,
14 | LG = 960,
15 | XL = 1280,
16 | XXL = 1536,
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 |
--------------------------------------------------------------------------------
/src/api/sys/upload.ts:
--------------------------------------------------------------------------------
1 | import { UploadApiResp } from '@/api/sys/model/uploadModel';
2 | import { defHttp } from '@/utils/http/axios';
3 | import { UploadFileParams } from '/#/axios';
4 | import { useGlobSetting } from '@/hooks/setting';
5 | import { AxiosProgressEvent } from 'axios';
6 |
7 | const { uploadUrl = '' } = useGlobSetting();
8 |
9 | /**
10 | * @description: Upload interface
11 | */
12 | export function uploadApi(
13 | params: UploadFileParams,
14 | onUploadProgress: (progressEvent: AxiosProgressEvent) => void,
15 | ) {
16 | return defHttp.uploadFile(
17 | {
18 | url: uploadUrl,
19 | onUploadProgress,
20 | },
21 | params,
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/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 | "type": "module",
16 | "files": [
17 | "base.json",
18 | "node.json",
19 | "vue-app.json",
20 | "node-server.json"
21 | ],
22 | "dependencies": {
23 | "@types/node": "^20.17.8",
24 | "vite": "^5.4.11"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/hooks/setting/index.ts:
--------------------------------------------------------------------------------
1 | import type { GlobConfig } from '/#/config';
2 |
3 | import { getAppEnvConfig } from '@/utils/env';
4 |
5 | export const useGlobSetting = (): Readonly => {
6 | const { VITE_GLOB_APP_TITLE, VITE_GLOB_API_URL, VITE_GLOB_API_URL_PREFIX, VITE_GLOB_UPLOAD_URL } =
7 | getAppEnvConfig();
8 |
9 | // Take global configuration
10 | const glob: Readonly = {
11 | title: VITE_GLOB_APP_TITLE,
12 | apiUrl: VITE_GLOB_API_URL,
13 | shortName: VITE_GLOB_APP_TITLE.replace(/\s/g, '_').replace(/-/g, '_'),
14 | urlPrefix: VITE_GLOB_API_URL_PREFIX,
15 | uploadUrl: VITE_GLOB_UPLOAD_URL,
16 | };
17 | return glob as Readonly;
18 | };
19 |
--------------------------------------------------------------------------------
/src/api/fms/model/storageProviderModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: StorageProvider info response
5 | */
6 | export interface StorageProviderInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | state?: boolean;
11 | name?: string;
12 | bucket?: string;
13 | endpoint?: string;
14 | secretId?: string;
15 | secretKey?: string;
16 | folder?: string;
17 | region?: string;
18 | isDefault?: boolean;
19 | useCdn?: boolean;
20 | cdnUrl?: string;
21 | }
22 |
23 | /**
24 | * @description: StorageProvider list response
25 | */
26 |
27 | export type StorageProviderListResp = BaseListResp;
28 |
--------------------------------------------------------------------------------
/src/enums/httpEnum.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description: Request result set
3 | */
4 | export enum ResultEnum {
5 | SUCCESS = 0,
6 | ERROR = -1,
7 | TIMEOUT = 401,
8 | TYPE = 'success',
9 | }
10 |
11 | /**
12 | * @description: request method
13 | */
14 | export enum RequestEnum {
15 | GET = 'GET',
16 | POST = 'POST',
17 | PUT = 'PUT',
18 | DELETE = 'DELETE',
19 | }
20 |
21 | /**
22 | * @description: contentType
23 | */
24 | export enum ContentTypeEnum {
25 | // json
26 | JSON = 'application/json;charset=UTF-8',
27 | // form-data qs
28 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
29 | // form-data upload
30 | FORM_DATA = 'multipart/form-data;charset=UTF-8',
31 | }
32 |
--------------------------------------------------------------------------------
/src/store/modules/role.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia';
2 | import { RoleInfo } from '@/api/sys/model/roleModel';
3 | import { getRoleList } from '@/api/sys/role';
4 |
5 | interface RoleState {
6 | roleInfo: RoleInfo[];
7 | }
8 |
9 | export const useRoleStore = defineStore('app-role', {
10 | state: (): RoleState => ({
11 | roleInfo: [],
12 | }),
13 | getters: {
14 | getRoleInfo(): RoleInfo[] {
15 | return this.roleInfo;
16 | },
17 | },
18 | actions: {
19 | async getRoleInfoFromServer() {
20 | const roleInfo = await getRoleList({
21 | page: 1,
22 | pageSize: 1000,
23 | });
24 | this.roleInfo = roleInfo.data.data;
25 | },
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/api/sys/model/configurationModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: Configuration info response
5 | */
6 | export interface ConfigurationInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | sort?: number;
11 | state?: boolean;
12 | name?: string;
13 | key?: string;
14 | value?: string;
15 | category?: string;
16 | remark?: string;
17 | }
18 |
19 | /**
20 | * @description: Configuration list response
21 | */
22 |
23 | export type ConfigurationListResp = BaseListResp;
24 |
25 | export interface ConfigurationListReq {
26 | page: number;
27 | pageSize: number;
28 | category?: string;
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/Application/index.ts:
--------------------------------------------------------------------------------
1 | import { withInstall } from '@/utils';
2 |
3 | import appLogo from './src/AppLogo.vue';
4 | import appProvider from './src/AppProvider.vue';
5 | import appSearch from './src/search/AppSearch.vue';
6 | import appLocalePicker from './src/AppLocalePicker.vue';
7 | import appDarkModeToggle from './src/AppDarkModeToggle.vue';
8 |
9 | export { useAppProviderContext } from './src/useAppContext';
10 |
11 | export const AppLogo = withInstall(appLogo);
12 | export const AppProvider = withInstall(appProvider);
13 | export const AppSearch = withInstall(appSearch);
14 | export const AppLocalePicker = withInstall(appLocalePicker);
15 | export const AppDarkModeToggle = withInstall(appDarkModeToggle);
16 |
--------------------------------------------------------------------------------
/src/components/Table/src/hooks/useTableContext.ts:
--------------------------------------------------------------------------------
1 | import type { Ref } from 'vue';
2 | import type { BasicTableProps, TableActionType } from '../types/table';
3 | import { provide, inject, ComputedRef } from 'vue';
4 |
5 | const key = Symbol('basic-table');
6 |
7 | type Instance = TableActionType & {
8 | wrapRef: Ref>;
9 | getBindValues: ComputedRef;
10 | };
11 |
12 | type RetInstance = Omit & {
13 | getBindValues: ComputedRef;
14 | };
15 |
16 | export function createTableContext(instance: Instance) {
17 | provide(key, instance);
18 | }
19 |
20 | export function useTableContext(): RetInstance {
21 | return inject(key) as RetInstance;
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Preview/src/functional.ts:
--------------------------------------------------------------------------------
1 | import type { Options, Props } from './typing';
2 | import ImgPreview from './Functional.vue';
3 | import { isClient } from '@/utils/is';
4 | import { createVNode, render } from 'vue';
5 |
6 | let instance: ReturnType | null = null;
7 | export function createImgPreview(options: Options) {
8 | if (!isClient) return;
9 | const propsData: Partial = {};
10 | const container = document.createElement('div');
11 | Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options);
12 |
13 | instance = createVNode(ImgPreview, propsData);
14 | render(instance, container);
15 | document.body.appendChild(container);
16 | return instance.component?.exposed;
17 | }
18 |
--------------------------------------------------------------------------------
/src/layouts/default/trigger/HeaderTrigger.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
18 |
--------------------------------------------------------------------------------
/src/layouts/iframe/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
22 |
--------------------------------------------------------------------------------
/src/router/constant.ts:
--------------------------------------------------------------------------------
1 | export const REDIRECT_NAME = 'Redirect';
2 |
3 | export const PARENT_LAYOUT_NAME = 'ParentLayout';
4 |
5 | export const PAGE_NOT_FOUND_NAME = 'PageNotFound';
6 | export const PAGE_NOT_FOUND_NAME_CHILDREN = 'PageNotFoundChildren';
7 |
8 | export const EXCEPTION_COMPONENT = () => import('@/views/sys/exception/Exception.vue');
9 |
10 | /**
11 | * @description: default layout
12 | */
13 | export const LAYOUT = () => import('@/layouts/default/index.vue');
14 |
15 | /**
16 | * @description: parent-layout
17 | */
18 | export const getParentLayout = (_name?: string) => {
19 | return () =>
20 | new Promise((resolve) => {
21 | resolve({
22 | name: _name || PARENT_LAYOUT_NAME,
23 | });
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/src/settings/localeSetting.ts:
--------------------------------------------------------------------------------
1 | import type { DropMenu } from '../components/Dropdown';
2 | import type { LocaleSetting, LocaleType } from '/#/config';
3 |
4 | export const LOCALE: { [key: string]: LocaleType } = {
5 | ZH_CN: 'zh_CN',
6 | EN_US: 'en',
7 | };
8 |
9 | export const localeSetting: LocaleSetting = {
10 | showPicker: true,
11 | // Locale
12 | locale: LOCALE.ZH_CN,
13 | // Default locale
14 | fallback: LOCALE.ZH_CN,
15 | // available Locales
16 | availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US],
17 | };
18 |
19 | // locale list
20 | export const localeList: DropMenu[] = [
21 | {
22 | text: '简体中文',
23 | event: LOCALE.ZH_CN,
24 | },
25 | {
26 | text: 'English',
27 | event: LOCALE.EN_US,
28 | },
29 | ];
30 |
--------------------------------------------------------------------------------
/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 === 'production' ? ['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/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/design/transition/slide.less:
--------------------------------------------------------------------------------
1 | .slide-y-transition {
2 | .transition-default();
3 |
4 | &-enter-from,
5 | &-leave-to {
6 | transform: translateY(-15px);
7 | opacity: 0;
8 | }
9 | }
10 |
11 | .slide-y-reverse-transition {
12 | .transition-default();
13 |
14 | &-enter-from,
15 | &-leave-to {
16 | transform: translateY(15px);
17 | opacity: 0;
18 | }
19 | }
20 |
21 | .slide-x-transition {
22 | .transition-default();
23 |
24 | &-enter-from,
25 | &-leave-to {
26 | transform: translateX(-15px);
27 | opacity: 0;
28 | }
29 | }
30 |
31 | .slide-x-reverse-transition {
32 | .transition-default();
33 |
34 | &-enter-from,
35 | &-leave-to {
36 | transform: translateX(15px);
37 | opacity: 0;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [master, dev]
6 | pull_request:
7 | branches: [master, dev]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [18.x, 20.x]
16 |
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v3
20 |
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 |
26 | - name: Install pnpm
27 | run: npm install -g pnpm
28 |
29 | - name: Install dependencies
30 | run: pnpm install
31 |
32 | - name: Build
33 | run: pnpm build
34 |
--------------------------------------------------------------------------------
/src/views/dashboard/workbench/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "@vben/ts-config/vue-app.json",
4 | "compilerOptions": {
5 | "baseUrl": ".",
6 | "declaration": false,
7 | "types": ["vite/client"],
8 | "paths": {
9 | "/@/*": ["src/*"],
10 | "/#/*": ["types/*"],
11 | "@/*": ["src/*"],
12 | "#/*": ["types/*"]
13 | }
14 | },
15 | "include": [
16 | "tests/**/*.ts",
17 | "src/**/*.ts",
18 | "src/**/*.d.ts",
19 | "src/**/*.tsx",
20 | "src/**/*.vue",
21 | "types/**/*.d.ts",
22 | "types/**/*.ts",
23 | "build/**/*.ts",
24 | "build/**/*.d.ts",
25 | "mock/**/*.ts",
26 | "vite.config.ts"
27 | ],
28 | "exclude": ["node_modules", "tests/server/**/*.ts", "dist", "**/*.js"]
29 | }
30 |
--------------------------------------------------------------------------------
/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 = ReturnType;
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 |
--------------------------------------------------------------------------------
/src/components/Upload/src/components/ThumbUrl.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
19 |
30 |
--------------------------------------------------------------------------------
/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/components/Modal/src/hooks/useModalFullScreen.ts:
--------------------------------------------------------------------------------
1 | import { computed, Ref, ref, unref } from 'vue';
2 |
3 | export interface UseFullScreenContext {
4 | wrapClassName: Ref;
5 | modalWrapperRef: Ref;
6 | extHeightRef: Ref;
7 | }
8 |
9 | export function useFullScreen(context: UseFullScreenContext) {
10 | const fullScreenRef = ref(false);
11 |
12 | const getWrapClassName = computed(() => {
13 | const clsName = unref(context.wrapClassName) || '';
14 | return unref(fullScreenRef) ? `fullscreen-modal ${clsName} ` : unref(clsName);
15 | });
16 |
17 | function handleFullScreen(e: Event) {
18 | e && e.stopPropagation();
19 | fullScreenRef.value = !unref(fullScreenRef);
20 | }
21 | return { getWrapClassName, handleFullScreen, fullScreenRef };
22 | }
23 |
--------------------------------------------------------------------------------
/src/views/dashboard/workbench/components/ProjectCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ t(item[0]) }} : {{ item[1] }}
6 |
7 |
8 |
9 |
10 |
24 |
--------------------------------------------------------------------------------
/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, Array] as PropType<(() => any) | (() => any)[]>, default: null },
26 | };
27 |
--------------------------------------------------------------------------------
/src/utils/http/axios/axiosRetry.ts:
--------------------------------------------------------------------------------
1 | import { AxiosError, AxiosInstance } from 'axios';
2 | /**
3 | * 请求重试机制
4 | */
5 |
6 | export class AxiosRetry {
7 | /**
8 | * 重试
9 | */
10 | retry(axiosInstance: AxiosInstance, error: AxiosError) {
11 | // @ts-ignore
12 | const { config } = error.response;
13 | const { waitTime, count } = config?.requestOptions?.retryRequest ?? {};
14 | config.__retryCount = config.__retryCount || 0;
15 | if (config.__retryCount >= count) {
16 | return Promise.reject(error);
17 | }
18 | config.__retryCount += 1;
19 | return this.delay(waitTime).then(() => axiosInstance(config));
20 | }
21 |
22 | /**
23 | * 延迟
24 | */
25 | private delay(waitTime: number) {
26 | return new Promise((resolve) => setTimeout(resolve, waitTime));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/is.ts:
--------------------------------------------------------------------------------
1 | const toString = Object.prototype.toString;
2 |
3 | export function is(val: unknown, type: string) {
4 | return toString.call(val) === `[object ${type}]`;
5 | }
6 |
7 | export function isDef(val?: T): val is T {
8 | return typeof val !== 'undefined';
9 | }
10 |
11 | export function isWindow(val: any): val is Window {
12 | return typeof window !== 'undefined' && is(val, 'Window');
13 | }
14 |
15 | export const isServer = typeof window === 'undefined';
16 |
17 | export const isClient = !isServer;
18 |
19 | export function isHttpUrl(path: string): boolean {
20 | const reg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?/;
21 | return reg.test(path);
22 | }
23 |
24 | export function upperFirst(str: string): string {
25 | return str.charAt(0).toUpperCase() + str.slice(1);
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Table/src/helper.ts:
--------------------------------------------------------------------------------
1 | import { ROW_KEY } from './const';
2 | import type { BasicTableProps } from './types/table';
3 |
4 | export function parseRowKey(
5 | rowKey: BasicTableProps['rowKey'],
6 | record: RecordType,
7 | autoCreateKey?: boolean,
8 | ): number | string {
9 | if (autoCreateKey) {
10 | return ROW_KEY;
11 | } else {
12 | if (typeof rowKey === 'string') {
13 | return rowKey;
14 | } else if (rowKey) {
15 | return rowKey(record);
16 | } else {
17 | return ROW_KEY;
18 | }
19 | }
20 | }
21 |
22 | export function parseRowKeyValue(
23 | rowKey: BasicTableProps['rowKey'],
24 | record: RecordType,
25 | autoCreateKey?: boolean,
26 | ): number | string {
27 | return record[parseRowKey(rowKey, record, autoCreateKey)];
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/uuid.ts:
--------------------------------------------------------------------------------
1 | const hexList: string[] = [];
2 | for (let i = 0; i <= 15; i++) {
3 | hexList[i] = i.toString(16);
4 | }
5 |
6 | export function buildUUID(): string {
7 | let uuid = '';
8 | for (let i = 1; i <= 36; i++) {
9 | if (i === 9 || i === 14 || i === 19 || i === 24) {
10 | uuid += '-';
11 | } else if (i === 15) {
12 | uuid += 4;
13 | } else if (i === 20) {
14 | uuid += hexList[(Math.random() * 4) | 8];
15 | } else {
16 | uuid += hexList[(Math.random() * 16) | 0];
17 | }
18 | }
19 | return uuid.replace(/-/g, '');
20 | }
21 |
22 | let unique = 0;
23 | export function buildShortUUID(prefix = ''): string {
24 | const time = Date.now();
25 | const random = Math.floor(Math.random() * 1000000000);
26 | unique++;
27 | return prefix + '_' + random + unique + String(time);
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/Tinymce/src/tinymce.ts:
--------------------------------------------------------------------------------
1 | export const plugins = [
2 | 'advlist',
3 | 'anchor',
4 | 'autolink',
5 | 'autosave',
6 | 'autoresize',
7 | 'code',
8 | 'codesample',
9 | 'directionality',
10 | 'fullscreen',
11 | 'insertdatetime',
12 | 'link',
13 | 'lists',
14 | 'media',
15 | 'nonbreaking',
16 | 'pagebreak',
17 | 'preview',
18 | 'save',
19 | 'searchreplace',
20 | 'visualblocks',
21 | 'visualchars',
22 | 'wordcount',
23 | 'image',
24 | ];
25 |
26 | export const toolbar = [
27 | 'fontsizeselect lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample',
28 | 'hr bullist numlist link preview anchor pagebreak insertdatetime media image forecolor backcolor fullscreen',
29 | ];
30 |
--------------------------------------------------------------------------------
/src/views/sys/redirect/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
33 |
--------------------------------------------------------------------------------
/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/components/Table/src/hooks/useTableStyle.ts:
--------------------------------------------------------------------------------
1 | import type { ComputedRef } from 'vue';
2 | import type { BasicTableProps, TableCustomRecord } from '../types/table';
3 | import { unref } from 'vue';
4 | import { isFunction } from 'remeda';
5 |
6 | export function useTableStyle(propsRef: ComputedRef, prefixCls: string) {
7 | function getRowClassName(record: TableCustomRecord, index: number) {
8 | const { striped, rowClassName } = unref(propsRef);
9 | const classNames: string[] = [];
10 | if (striped) {
11 | classNames.push((index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : '');
12 | }
13 | if (rowClassName && isFunction(rowClassName)) {
14 | classNames.push(rowClassName(record, index));
15 | }
16 | return classNames.filter((cls) => !!cls).join(' ');
17 | }
18 |
19 | return { getRowClassName };
20 | }
21 |
--------------------------------------------------------------------------------
/src/layouts/default/header/components/user-dropdown/DropMenuItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ text }}
6 |
7 |
8 |
9 |
26 |
--------------------------------------------------------------------------------
/packages/types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vben/types",
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/types"
12 | },
13 | "license": "MIT",
14 | "sideEffects": false,
15 | "type": "module",
16 | "exports": {
17 | ".": {
18 | "default": "./src/index.ts"
19 | }
20 | },
21 | "main": "./src/index.ts",
22 | "module": "./src/index.ts",
23 | "files": [
24 | "dist"
25 | ],
26 | "scripts": {
27 | "//build": "pnpm unbuild",
28 | "//stub": "pnpm unbuild --stub",
29 | "clean": "pnpm rimraf .turbo node_modules dist",
30 | "lint": "pnpm eslint ."
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/Table/src/components/settings/FullScreenSetting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ t('component.table.settingFullScreen') }}
5 |
6 |
7 |
8 |
9 |
10 |
21 |
--------------------------------------------------------------------------------
/src/components/Table/src/components/editable/helper.ts:
--------------------------------------------------------------------------------
1 | import { ComponentType } from '../../types/componentType';
2 | import { useI18n } from '@/hooks/web/useI18n';
3 |
4 | const { t } = useI18n();
5 |
6 | /**
7 | * @description: 生成placeholder
8 | */
9 | export function createPlaceholderMessage(component: ComponentType) {
10 | if (component.includes('Input') || component.includes('AutoComplete')) {
11 | return t('common.inputText');
12 | }
13 | if (component.includes('Picker')) {
14 | return t('common.chooseText');
15 | }
16 |
17 | if (
18 | component.includes('Select') ||
19 | component.includes('Checkbox') ||
20 | component.includes('Radio') ||
21 | component.includes('Switch') ||
22 | component.includes('DatePicker') ||
23 | component.includes('TimePicker')
24 | ) {
25 | return t('common.chooseText');
26 | }
27 | return '';
28 | }
29 |
--------------------------------------------------------------------------------
/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": "bundler",
8 | "strict": true,
9 | "declaration": true,
10 | "noImplicitOverride": true,
11 | "noUnusedLocals": true,
12 | "esModuleInterop": true,
13 | "useUnknownInCatchVariables": false,
14 | "composite": false,
15 | "declarationMap": false,
16 | "forceConsistentCasingInFileNames": true,
17 | "inlineSources": false,
18 | "isolatedModules": true,
19 | "skipLibCheck": true,
20 | "noUnusedParameters": false,
21 | "preserveWatchOutput": true,
22 | "experimentalDecorators": true,
23 | "resolveJsonModule": true,
24 | "removeComments": true
25 | },
26 | "exclude": ["**/node_modules/**", "**/dist/**"]
27 | }
28 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/resource/tinymce/plugins/code/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 6.8.3 (2024-02-08)
3 | */
4 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}();
--------------------------------------------------------------------------------
/src/api/sys/model/authorityModel.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * author: Ryan Su
3 | * @description:
4 | */
5 |
6 | import { BaseListResp } from '../../model/baseModel';
7 |
8 | export interface MenuAuthorityInfo {
9 | roleId: number;
10 | menuIds: number[];
11 | }
12 |
13 | /**
14 | * author: Ryan Su
15 | * @description: this interface is used to get the api list for authorization
16 | */
17 |
18 | export interface ApiListReq {
19 | page: number;
20 | pageSize: number;
21 | path?: string;
22 | group?: string;
23 | description?: string;
24 | method?: string;
25 | }
26 |
27 | /**
28 | * author: Ryan Su
29 | * @description:
30 | */
31 |
32 | export interface ApiAuthorityReq {
33 | roleId: number;
34 | data: ApiAuthorityInfo[];
35 | }
36 |
37 | export interface ApiAuthorityInfo {
38 | path: string;
39 | method: string;
40 | }
41 |
42 | export type ApiAuthorityResp = BaseListResp;
43 |
--------------------------------------------------------------------------------
/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/api/fms/model/fileModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseListResp } from '../../model/baseModel';
2 |
3 | /**
4 | * author: Ryan Su
5 | * @description: file info response
6 | */
7 | export interface fileInfo {
8 | id: string;
9 | createdAt?: number;
10 | name: string;
11 | fileType: string;
12 | size: number;
13 | path: string;
14 | publicPath: string;
15 | tagIds: number[];
16 | }
17 |
18 | /**
19 | * author: Ryan Su
20 | * @description: file list response
21 | */
22 |
23 | export type FileListResp = BaseListResp;
24 |
25 | /**
26 | * author: Ryan Su
27 | * @description: change status request
28 | */
29 | export interface changeStatusReq {
30 | id: string;
31 | status: boolean;
32 | }
33 |
34 | /**
35 | * author: Ryan Su
36 | * @description: update file info request
37 | */
38 | export interface updateFileInfoReq {
39 | id: string;
40 | name: string;
41 | tagIds: number[];
42 | }
43 |
--------------------------------------------------------------------------------
/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/tabs/components/TabRedo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
26 |
31 |
--------------------------------------------------------------------------------
/src/design/var/breakpoint.less:
--------------------------------------------------------------------------------
1 | // =================================
2 | // ==============屏幕断点============
3 | // =================================
4 |
5 | // Extra small screen / phone
6 | @screen-xs: 320px;
7 | @screen-xs-min: @screen-xs;
8 |
9 | // Small screen / tablet
10 | @screen-sm: 640px;
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: 960px;
19 | @screen-lg-min: @screen-lg;
20 |
21 | // Extra large screen / full hd
22 | @screen-xl: 1280px;
23 | @screen-xl-min: @screen-xl;
24 |
25 | // Extra extra large screen / large desktop
26 | @screen-2xl: 1536px;
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 |
39 |
--------------------------------------------------------------------------------
/src/api/mcms/messageSender.ts:
--------------------------------------------------------------------------------
1 | import { defHttp } from '@/utils/http/axios';
2 | import { ErrorMessageMode } from '/#/axios';
3 | import { BaseResp } from '@/api/model/baseModel';
4 | import { SendEmailReq, SendSmsReq } from './model/messageModel';
5 |
6 | enum Api {
7 | SendEmail = '/sys-api/email/send',
8 | SendSms = '/sys-api/sms/send',
9 | }
10 |
11 | /**
12 | * @description: Send Email
13 | */
14 |
15 | export const sendEmail = (params: SendEmailReq, mode: ErrorMessageMode = 'notice') => {
16 | return defHttp.post(
17 | { url: Api.SendEmail, params },
18 | { errorMessageMode: mode, successMessageMode: mode },
19 | );
20 | };
21 |
22 | /**
23 | * @description: Send Sms
24 | */
25 |
26 | export const sendSms = (params: SendSmsReq, mode: ErrorMessageMode = 'notice') => {
27 | return defHttp.post(
28 | { url: Api.SendSms, params },
29 | { errorMessageMode: mode, successMessageMode: mode },
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/components/Modal/src/components/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from 'ant-design-vue';
2 | import { defineComponent, toRefs, unref } from 'vue';
3 | import { basicProps } from '../props';
4 | import { useModalDragMove } from '../hooks/useModalDrag';
5 | import { extendSlots } from '@/utils/helper/tsxHelper';
6 |
7 | export default defineComponent({
8 | name: 'Modal',
9 | inheritAttrs: false,
10 | props: basicProps as any,
11 | emits: ['cancel'],
12 | setup(props, { slots, emit, attrs }) {
13 | const { open, draggable, destroyOnClose } = toRefs(props);
14 | useModalDragMove({
15 | open,
16 | destroyOnClose,
17 | draggable,
18 | });
19 |
20 | const onCancel = (e: Event) => {
21 | emit('cancel', e);
22 | };
23 |
24 | return () => {
25 | const propsData = { ...unref(attrs), ...props, onCancel } as Recordable;
26 | return {extendSlots(slots)};
27 | };
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/src/utils/propTypes.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties, VNodeChild } from 'vue';
2 | import { createTypes, VueTypeValidableDef, VueTypesInterface, toValidableType } from 'vue-types';
3 |
4 | export type VueNode = VNodeChild | JSX.Element;
5 |
6 | type PropTypes = VueTypesInterface & {
7 | readonly style: VueTypeValidableDef;
8 | readonly VNodeChild: VueTypeValidableDef;
9 | };
10 | const newPropTypes = createTypes({
11 | func: undefined,
12 | bool: undefined,
13 | string: undefined,
14 | number: undefined,
15 | object: undefined,
16 | integer: undefined,
17 | }) as PropTypes;
18 |
19 | class propTypes extends newPropTypes {
20 | static override get style() {
21 | return toValidableType('style', {
22 | type: [String, Object],
23 | });
24 | }
25 |
26 | static override get VNodeChild() {
27 | return toValidableType('VNodeChild', {
28 | type: undefined,
29 | });
30 | }
31 | }
32 | export { propTypes };
33 |
--------------------------------------------------------------------------------
/src/components/Modal/src/components/ModalFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ props.cancelText }}
6 |
7 |
8 |
15 | {{ props.okText }}
16 |
17 |
18 |
19 |
20 |
34 |
--------------------------------------------------------------------------------
/src/hooks/web/useFullContent.ts:
--------------------------------------------------------------------------------
1 | import { computed, unref } from 'vue';
2 |
3 | import { useAppStore } from '@/store/modules/app';
4 |
5 | import { useRouter } from 'vue-router';
6 |
7 | /**
8 | * @description: Full screen display content
9 | */
10 | export const useFullContent = () => {
11 | const appStore = useAppStore();
12 | const router = useRouter();
13 | const { currentRoute } = router;
14 |
15 | // Whether to display the content in full screen without displaying the menu
16 | const getFullContent = computed(() => {
17 | // Query parameters, the full screen is displayed when the address bar has a full parameter
18 | const route = unref(currentRoute);
19 | const query = route.query;
20 | if (query && Reflect.has(query, '__full__')) {
21 | return true;
22 | }
23 | // Return to the configuration in the configuration file
24 | return appStore.getProjectConfig.fullContent;
25 | });
26 |
27 | return { getFullContent };
28 | };
29 |
--------------------------------------------------------------------------------
/src/enums/cacheEnum.ts:
--------------------------------------------------------------------------------
1 | export const LOCALE_KEY = 'LOCALE__';
2 |
3 | // user info key
4 | export const USER_INFO_KEY = 'USER__INFO__';
5 |
6 | // dictionary info key
7 | export const DICT_INFO_KEY = 'DICT__INFO__';
8 |
9 | // role info key
10 | export const ROLES_KEY = 'ROLES__KEY__';
11 |
12 | // role name key
13 | export const ROLES_NAME_KEY = 'ROLES__NAME__KEY__';
14 |
15 | // project config key
16 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__';
17 | export const API_ADDRESS = 'API_ADDRESS__';
18 |
19 | // lock info
20 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__';
21 |
22 | export const MULTIPLE_TABS_KEY = 'MULTIPLE_TABS__KEY__';
23 |
24 | export const APP_DARK_MODE_KEY = '__APP__DARK__MODE__';
25 |
26 | // base global local key
27 | export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__';
28 |
29 | // base global session key
30 | export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__';
31 |
32 | export enum CacheTypeEnum {
33 | SESSION,
34 | LOCAL,
35 | }
36 |
--------------------------------------------------------------------------------
/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/assets/svg/preview/resume.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/api/sys/initialize.ts:
--------------------------------------------------------------------------------
1 | import { defHttp } from '@/utils/http/axios';
2 | import { BaseResp } from '@/api/model/baseModel';
3 |
4 | enum Api {
5 | InitializeDatabase = '/sys-api/core/init/database',
6 | InitializeJobDatabase = '/sys-api/core/init/job_database',
7 | InitializeMcmsDatabase = '/sys-api/core/init/mcms_database',
8 | }
9 |
10 | /**
11 | * @description: initialize the core database
12 | */
13 |
14 | export const initialzeCoreDatabase = () => {
15 | return defHttp.get({ url: Api.InitializeDatabase });
16 | };
17 |
18 | /**
19 | * @description: initialize the job management service database
20 | */
21 |
22 | export const initializeJobDatabase = () => {
23 | return defHttp.get({ url: Api.InitializeJobDatabase });
24 | };
25 |
26 | /**
27 | * @description: initialize the message center management service database
28 | */
29 |
30 | export const initializeMcmsDatabase = () => {
31 | return defHttp.get({ url: Api.InitializeMcmsDatabase });
32 | };
33 |
--------------------------------------------------------------------------------
/src/layouts/default/header/components/FullScreen.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
28 |
--------------------------------------------------------------------------------
/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/store/modules/lock.ts:
--------------------------------------------------------------------------------
1 | import type { LockInfo } from '/#/store';
2 |
3 | import { defineStore } from 'pinia';
4 | import { LOCK_INFO_KEY } from '@/enums/cacheEnum';
5 |
6 | interface LockState {
7 | lockInfo: Nullable;
8 | }
9 |
10 | export const useLockStore = defineStore({
11 | id: 'app-lock',
12 | state: (): LockState => {
13 | return {
14 | lockInfo: null,
15 | };
16 | },
17 | getters: {
18 | getLockInfo(state): Nullable {
19 | return state.lockInfo;
20 | },
21 | },
22 | actions: {
23 | setLockInfo(info: LockInfo) {
24 | this.lockInfo = Object.assign({}, this.lockInfo, info);
25 | },
26 | resetLockInfo() {
27 | this.lockInfo = null;
28 | },
29 | // Unlock
30 | async unLock(password?: string) {
31 | if (this.lockInfo?.pwd === password) {
32 | this.resetLockInfo();
33 | return true;
34 | }
35 | return false;
36 | },
37 | },
38 | persist: {
39 | key: LOCK_INFO_KEY,
40 | },
41 | });
42 |
--------------------------------------------------------------------------------
/src/components/Description/src/useDescription.ts:
--------------------------------------------------------------------------------
1 | import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing';
2 | import { ref, getCurrentInstance, unref } from 'vue';
3 | import { isProdMode } from '@/utils/env';
4 |
5 | export function useDescription(props?: Partial): UseDescReturnType {
6 | if (!getCurrentInstance()) {
7 | throw new Error('useDescription() can only be used inside setup() or functional components!');
8 | }
9 | const desc = ref>(null);
10 | const loaded = ref(false);
11 |
12 | function register(instance: DescInstance) {
13 | if (unref(loaded) && isProdMode()) {
14 | return;
15 | }
16 | desc.value = instance;
17 | props && instance.setDescProps(props);
18 | loaded.value = true;
19 | }
20 |
21 | const methods: DescInstance = {
22 | setDescProps: (descProps: Partial): void => {
23 | unref(desc)?.setDescProps(descProps);
24 | },
25 | };
26 |
27 | return [register, methods];
28 | }
29 |
--------------------------------------------------------------------------------
/src/directives/repeatClick.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Prevent repeated clicks
3 | * @Example v-repeat-click="()=>{}"
4 | */
5 | import { on, once } from '@/utils/domUtils';
6 | import type { Directive, DirectiveBinding } from 'vue';
7 |
8 | const repeatDirective: Directive = {
9 | beforeMount(el: Element, binding: DirectiveBinding) {
10 | let interval: Nullable = null;
11 | let startTime = 0;
12 | const handler = (): void => binding?.value();
13 | const clear = (): void => {
14 | if (Date.now() - startTime < 100) {
15 | handler();
16 | }
17 | interval && clearInterval(interval);
18 | interval = null;
19 | };
20 |
21 | on(el, 'mousedown', (e: Event): void => {
22 | if ((e as MouseEvent).button !== 0) return;
23 | startTime = Date.now();
24 | once(document as any, 'mouseup', clear);
25 | interval && clearInterval(interval);
26 | interval = setInterval(handler, 100);
27 | });
28 | },
29 | };
30 |
31 | export default repeatDirective;
32 |
--------------------------------------------------------------------------------
/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 | "type": "module",
16 | "exports": {
17 | ".": {
18 | "default": "./src/index.ts"
19 | }
20 | },
21 | "main": "./src/index.ts",
22 | "module": "./src/index.ts",
23 | "files": [
24 | "dist"
25 | ],
26 | "scripts": {
27 | "//build": "pnpm unbuild",
28 | "//stub": "pnpm unbuild --stub",
29 | "clean": "pnpm rimraf .turbo node_modules dist",
30 | "lint": "pnpm eslint ."
31 | },
32 | "dependencies": {
33 | "@vueuse/core": "^10.11.1",
34 | "vue": "^3.5.13"
35 | },
36 | "devDependencies": {
37 | "@vben/types": "workspace:*"
38 | }
39 | }
--------------------------------------------------------------------------------
/src/assets/svg/preview/unscale.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/Table/src/components/DictionaryTag.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ options?.find((item) => item.value == value)?.label }}
3 |
4 |
32 |
--------------------------------------------------------------------------------
/src/views/dashboard/workbench/components/QuickNav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ t(item.title) }}
7 |
8 |
9 |
10 |
11 |
28 |
--------------------------------------------------------------------------------
/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 type { TransitionSetting } from '/#/config';
2 |
3 | import { computed } from 'vue';
4 |
5 | import { useAppStore } from '@/store/modules/app';
6 |
7 | export function useTransitionSetting() {
8 | const appStore = useAppStore();
9 |
10 | const getEnableTransition = computed(() => appStore.getTransitionSetting?.enable);
11 |
12 | const getOpenNProgress = computed(() => appStore.getTransitionSetting?.openNProgress);
13 |
14 | const getOpenPageLoading = computed((): boolean => {
15 | return !!appStore.getTransitionSetting?.openPageLoading;
16 | });
17 |
18 | const getBasicTransition = computed(() => appStore.getTransitionSetting?.basicTransition);
19 |
20 | function setTransitionSetting(transitionSetting: Partial) {
21 | appStore.setProjectConfig({ transitionSetting });
22 | }
23 | return {
24 | setTransitionSetting,
25 |
26 | getEnableTransition,
27 | getOpenNProgress,
28 | getOpenPageLoading,
29 | getBasicTransition,
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/Form/index.ts:
--------------------------------------------------------------------------------
1 | import BasicForm from './src/BasicForm.vue';
2 |
3 | export * from './src/types/form';
4 | export * from './src/types/formItem';
5 |
6 | export { useComponentRegister } from './src/hooks/useComponentRegister';
7 | export { useForm } from './src/hooks/useForm';
8 |
9 | export { default as ApiSelect } from './src/components/ApiSelect.vue';
10 | export { default as ApiMultipleSelect } from './src/components/ApiMultipleSelect.vue';
11 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
12 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
13 | export { default as ApiTree } from './src/components/ApiTree.vue';
14 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
15 | export { default as ApiCascader } from './src/components/ApiCascader.vue';
16 | export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
17 | export { default as DictionarySelect } from './src/components/DictionarySelect.vue';
18 |
19 | export { BasicForm };
20 |
--------------------------------------------------------------------------------
/src/router/guard/stateGuard.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from 'vue-router';
2 | import { useAppStore } from '@/store/modules/app';
3 | import { useMultipleTabStore } from '@/store/modules/multipleTab';
4 | import { useUserStore } from '@/store/modules/user';
5 | import { usePermissionStore } from '@/store/modules/permission';
6 | import { PageEnum } from '@/enums/pageEnum';
7 | import { removeTabChangeListener } from '@/logics/mitt/routeChange';
8 |
9 | export function createStateGuard(router: Router) {
10 | router.afterEach((to) => {
11 | // Just enter the login page and clear the authentication information
12 | if (to.path === PageEnum.BASE_LOGIN) {
13 | const tabStore = useMultipleTabStore();
14 | const userStore = useUserStore();
15 | const appStore = useAppStore();
16 | const permissionStore = usePermissionStore();
17 | appStore.resetAllState();
18 | permissionStore.resetState();
19 | tabStore.resetState();
20 | userStore.resetState();
21 | removeTabChangeListener();
22 | }
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/eslint.yml:
--------------------------------------------------------------------------------
1 | name: ESLint
2 |
3 | on:
4 | push:
5 | branches: ['master', 'dev']
6 | pull_request:
7 | branches: ['master', 'dev']
8 | schedule:
9 | - cron: '0 0 * * Mon'
10 |
11 | jobs:
12 | eslint:
13 | name: Run eslint scanning
14 | runs-on: ubuntu-latest
15 | permissions:
16 | contents: read
17 | security-events: write
18 | actions: read
19 |
20 | steps:
21 | - name: Checkout code
22 | uses: actions/checkout@v3
23 |
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 |
29 | - name: Install pnpm
30 | run: npm install -g pnpm
31 |
32 | - name: Install ESLint
33 | run: |
34 | npm install -g eslint
35 | npm install -g @microsoft/eslint-formatter-sarif
36 |
37 | - name: Install dependencies
38 | run: pnpm install
39 |
40 | - name: Run ESLint
41 | run: |
42 | pnpm lint
43 |
--------------------------------------------------------------------------------
/src/hooks/setting/useMultipleTabSetting.ts:
--------------------------------------------------------------------------------
1 | import type { MultiTabsSetting } from '/#/config';
2 |
3 | import { computed } from 'vue';
4 |
5 | import { useAppStore } from '@/store/modules/app';
6 |
7 | export function useMultipleTabSetting() {
8 | const appStore = useAppStore();
9 |
10 | const getShowMultipleTab = computed(() => appStore.getMultiTabsSetting.show);
11 |
12 | const getShowQuick = computed(() => appStore.getMultiTabsSetting.showQuick);
13 |
14 | const getShowRedo = computed(() => appStore.getMultiTabsSetting.showRedo);
15 |
16 | const getShowFold = computed(() => appStore.getMultiTabsSetting.showFold);
17 |
18 | const getAutoCollapse = computed(() => appStore.getMultiTabsSetting.autoCollapse);
19 |
20 | function setMultipleTabSetting(multiTabsSetting: Partial) {
21 | appStore.setProjectConfig({ multiTabsSetting });
22 | }
23 | return {
24 | setMultipleTabSetting,
25 | getShowMultipleTab,
26 | getShowQuick,
27 | getShowRedo,
28 | getShowFold,
29 | getAutoCollapse,
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/Prompt/index.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, VNode, defineComponent, h, render, reactive } from 'vue';
2 | import { PromptProps, genFormSchemas } from './state';
3 | import Dialog from './dialog.vue';
4 |
5 | export function createPrompt(props: PromptProps) {
6 | let vm: Nullable = null;
7 | const data = reactive({
8 | ...props,
9 | addFormSchemas: genFormSchemas({
10 | label: props.label,
11 | required: props.required,
12 | inputType: props.inputType,
13 | defaultValue: props.defaultValue,
14 | }),
15 | });
16 | const DialogWrap = defineComponent({
17 | render() {
18 | return h(Dialog, { ...data } as any);
19 | },
20 | });
21 |
22 | vm = createVNode(DialogWrap);
23 |
24 | render(vm, document.createElement('div'));
25 |
26 | function close() {
27 | if (vm?.el && vm.el.parentNode) {
28 | vm.el.parentNode.removeChild(vm.el);
29 | }
30 | }
31 |
32 | return {
33 | vm,
34 | close,
35 | get $el() {
36 | return vm?.el as HTMLElement;
37 | },
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/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 transparent inset;
10 | -webkit-text-fill-color: @text-color-base;
11 | }
12 |
13 | :-webkit-autofill {
14 | transition: background-color 5000s ease-in-out 0s !important;
15 | }
16 |
17 | html {
18 | overflow: hidden;
19 | text-size-adjust: 100%;
20 | }
21 |
22 | html,
23 | body {
24 | position: relative;
25 | width: 100%;
26 | height: 100%;
27 | overflow: visible;
28 | overflow-x: hidden;
29 |
30 | &.color-weak {
31 | filter: invert(80%);
32 | }
33 |
34 | &.gray-mode {
35 | filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
36 | }
37 | }
38 |
39 | a:focus,
40 | a:active,
41 | button,
42 | div,
43 | svg,
44 | span {
45 | outline: none;
46 | }
47 |
48 | // 保持 和 windi 一样的全局样式,减少升级带来的影响
49 | ul {
50 | margin: 0;
51 | padding: 0;
52 | list-style: none;
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/Excel/src/typing.ts:
--------------------------------------------------------------------------------
1 | import type { JSON2SheetOpts, WritingOptions, BookType } from 'xlsx';
2 |
3 | export interface ExcelData {
4 | header: string[];
5 | results: T[];
6 | meta: { sheetName: string };
7 | }
8 |
9 | export interface JsonToSheet {
10 | data: T[];
11 | header?: T;
12 | filename?: string;
13 | 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 |
--------------------------------------------------------------------------------
/src/logics/mitt/routeChange.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Used to monitor routing changes to change the status of menus and tabs. There is no need to monitor the route, because the route status change is affected by the page rendering time, which will be slow
3 | */
4 |
5 | import { mitt } from '@/utils/mitt';
6 | import type { RouteLocationNormalized } from 'vue-router';
7 | import { getRawRoute } from '@/utils';
8 |
9 | const key = Symbol();
10 |
11 | const emitter = mitt<{
12 | [key]: RouteLocationNormalized;
13 | }>();
14 |
15 | let lastChangeTab: RouteLocationNormalized;
16 |
17 | export function setRouteChange(lastChangeRoute: RouteLocationNormalized) {
18 | const r = getRawRoute(lastChangeRoute);
19 | emitter.emit(key, r);
20 | lastChangeTab = r;
21 | }
22 |
23 | export function listenerRouteChange(
24 | callback: (route: RouteLocationNormalized) => void,
25 | immediate = true,
26 | ) {
27 | emitter.on(key, callback);
28 | immediate && lastChangeTab && callback(lastChangeTab);
29 | }
30 |
31 | export function removeTabChangeListener() {
32 | emitter.clear();
33 | }
34 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | VERSION=$(shell git describe --tags --always)
2 |
3 | .PHONY: docker
4 | docker: # Compile and build the docker | 编译并构建 docker 镜像
5 | pnpm install
6 | pnpm build
7 | docker build -f Dockerfile -t ${DOCKER_USERNAME}/backend-ui:${VERSION} .
8 |
9 | .PHONY: docker-not-build
10 | docker-not-build: # Build the docker without compiling | 不编译直接构建镜像
11 | docker build -f Dockerfile -t ${DOCKER_USERNAME}/backend-ui:${VERSION} .
12 |
13 | .PHONY: publish-docker
14 | publish-docker: # Publish the docker | 发布镜像
15 | docker push ${DOCKER_USERNAME}/backend-ui:${VERSION}
16 |
17 | .PHONY: run-docker
18 | run-docker: # Run the docker image | 运行 docker 镜像
19 | docker volume create backendui
20 | docker run -d --name ${DOCKER_USERNAME}/backend-ui:${VERSION} -p 80:80 -v backendui:/etc/nginx --network docker-compose_simple-admin ${DOCKER_USERNAME}/backendui:${VERSION}
21 |
22 | .PHONY: help
23 | help: # Show help | 显示帮助
24 | @grep -E '^[a-zA-Z0-9 -]+:.*#' Makefile | sort | while read -r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done
25 |
--------------------------------------------------------------------------------
/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 | display: flex;
21 | position: relative;
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 | display: flex;
39 | position: absolute;
40 | //top: 2px;
41 | right: 3px;
42 | }
43 |
44 | &__action {
45 | visibility: hidden;
46 | margin-left: 4px;
47 | }
48 |
49 | &-header {
50 | border-bottom: 1px solid @border-color-base;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/utils/helper/tsxHelper.tsx:
--------------------------------------------------------------------------------
1 | import { Slots } from 'vue';
2 | import { RenderOpts } from '@/components/Form';
3 | import { isFunction } from 'remeda';
4 |
5 | /**
6 | * @description: Get slot to prevent empty error
7 | */
8 | export function getSlot(slots: Slots, slot = 'default', data?: any, opts?: RenderOpts) {
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 | const params = { ...data, ...opts };
19 | return slotFn(params);
20 | }
21 |
22 | /**
23 | * extends slots
24 | * @param slots
25 | * @param excludeKeys
26 | */
27 | export function extendSlots(slots: Slots, excludeKeys: string[] = []) {
28 | const slotKeys = Object.keys(slots);
29 | const ret: any = {};
30 | slotKeys.map((key) => {
31 | if (excludeKeys.includes(key)) {
32 | return null;
33 | }
34 | ret[key] = (data?: any) => getSlot(slots, key, data);
35 | });
36 | return ret;
37 | }
38 |
--------------------------------------------------------------------------------
/src/hooks/web/usePagination.ts:
--------------------------------------------------------------------------------
1 | import type { Ref } from 'vue';
2 | import { ref, unref, computed } from 'vue';
3 |
4 | function pagination(list: T[], pageNo: number, pageSize: number): T[] {
5 | const offset = (pageNo - 1) * Number(pageSize);
6 | const ret =
7 | offset + Number(pageSize) >= list.length
8 | ? list.slice(offset, list.length)
9 | : list.slice(offset, offset + Number(pageSize));
10 | return ret;
11 | }
12 |
13 | export function usePagination(list: Ref, pageSize: number) {
14 | const currentPage = ref(1);
15 | const pageSizeRef = ref(pageSize);
16 |
17 | const getPaginationList = computed(() => {
18 | return pagination(unref(list), unref(currentPage), unref(pageSizeRef));
19 | });
20 |
21 | const getTotal = computed(() => {
22 | return unref(list).length;
23 | });
24 |
25 | function setCurrentPage(page: number) {
26 | currentPage.value = page;
27 | }
28 |
29 | function setPageSize(pageSize: number) {
30 | pageSizeRef.value = pageSize;
31 | }
32 |
33 | return { setCurrentPage, getTotal, setPageSize, getPaginationList };
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/Application/src/search/AppSearch.vue:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/src/components/Table/src/types/tableAction.ts:
--------------------------------------------------------------------------------
1 | import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
2 | import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
3 | import { RoleEnum } from '@/enums/roleEnum';
4 |
5 | export interface ActionItem extends ButtonProps {
6 | onClick?: Fn;
7 | label?: string;
8 | color?: 'success' | 'error' | 'warning';
9 | icon?: string;
10 | popConfirm?: PopConfirm;
11 | disabled?: boolean;
12 | divider?: boolean;
13 | // 权限编码控制是否显示
14 | auth?: RoleEnum | RoleEnum[] | string | string[];
15 | // 业务控制是否显示
16 | ifShow?: boolean | ((action: ActionItem) => boolean);
17 | tooltip?: string | TooltipProps;
18 | }
19 |
20 | export interface PopConfirm {
21 | title: string;
22 | okText?: string;
23 | cancelText?: string;
24 | confirm: Fn;
25 | cancel?: Fn;
26 | icon?: string;
27 | placement?:
28 | | 'top'
29 | | 'left'
30 | | 'right'
31 | | 'bottom'
32 | | 'topLeft'
33 | | 'topRight'
34 | | 'leftTop'
35 | | 'leftBottom'
36 | | 'rightTop'
37 | | 'rightBottom'
38 | | 'bottomLeft'
39 | | 'bottomRight';
40 | }
41 |
--------------------------------------------------------------------------------
/src/hooks/core/useContext.ts:
--------------------------------------------------------------------------------
1 | import {
2 | InjectionKey,
3 | provide,
4 | inject,
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 = true, 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/hooks/web/useTitle.ts:
--------------------------------------------------------------------------------
1 | import { watch, unref } from 'vue';
2 | import { useI18n } from '@/hooks/web/useI18n';
3 | import { useTitle as usePageTitle } from '@vueuse/core';
4 | import { useGlobSetting } from '@/hooks/setting';
5 | import { useRouter } from 'vue-router';
6 | import { useLocaleStore } from '@/store/modules/locale';
7 | import { REDIRECT_NAME } from '@/router/constant';
8 |
9 | /**
10 | * Listening to page changes and dynamically changing site titles
11 | */
12 | export function useTitle() {
13 | const { title } = useGlobSetting();
14 | const { t } = useI18n();
15 | const { currentRoute } = useRouter();
16 | const localeStore = useLocaleStore();
17 |
18 | const pageTitle = usePageTitle();
19 |
20 | watch(
21 | [() => currentRoute.value.path, () => localeStore.getLocale],
22 | () => {
23 | const route = unref(currentRoute);
24 |
25 | if (route.name === REDIRECT_NAME) {
26 | return;
27 | }
28 |
29 | const tTitle = t(route?.meta?.title as string);
30 | pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`;
31 | },
32 | { immediate: true },
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/views/mcms/emailProvider/email.data.ts:
--------------------------------------------------------------------------------
1 | import { getEmailProviderList } from '@/api/mcms/emailProvider';
2 | import { FormSchema } from '@/components/Table';
3 | import { useI18n } from '@/hooks/web/useI18n';
4 |
5 | const { t } = useI18n();
6 |
7 | export const formSchema: FormSchema[] = [
8 | {
9 | field: 'target',
10 | label: t('mcms.email.targetAddress'),
11 | component: 'Input',
12 | required: true,
13 | },
14 | {
15 | field: 'subject',
16 | label: t('mcms.email.subject'),
17 | component: 'Input',
18 | required: true,
19 | },
20 | {
21 | field: 'content',
22 | label: t('mcms.email.content'),
23 | component: 'InputTextArea',
24 | required: true,
25 | },
26 | {
27 | field: 'provider',
28 | label: t('mcms.emailLog.provider'),
29 | component: 'ApiSelect',
30 | required: true,
31 | defaultValue: 'tencent',
32 | componentProps: {
33 | api: getEmailProviderList,
34 | params: {
35 | page: 1,
36 | pageSize: 1000,
37 | },
38 | resultField: 'data.data',
39 | labelField: 'name',
40 | valueField: 'name',
41 | },
42 | },
43 | ];
44 |
--------------------------------------------------------------------------------
/.originallicense/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-present, Vben
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/api/sys/model/oauthProviderModel.ts:
--------------------------------------------------------------------------------
1 | import { BaseDataResp, BaseListResp } from '@/api/model/baseModel';
2 |
3 | /**
4 | * @description: OauthProvider info response
5 | */
6 | export interface OauthProviderInfo {
7 | id?: number;
8 | createdAt?: number;
9 | updatedAt?: number;
10 | name?: string;
11 | clientId?: string;
12 | clientSecret?: string;
13 | redirectUrl?: string;
14 | scopes?: string;
15 | authUrl?: string;
16 | tokenUrl?: string;
17 | authStyle?: number;
18 | infoUrl?: string;
19 | }
20 |
21 | /**
22 | * @description: OauthProvider list response
23 | */
24 |
25 | export type OauthProviderListResp = BaseListResp;
26 |
27 | /**
28 | * author: Ryan Su
29 | * @description: Oauth log in request parameters
30 | */
31 | export interface OauthLoginReq {
32 | state: string;
33 | provider: string;
34 | }
35 |
36 | /**
37 | * author: Ryan Su
38 | * @description: redirect response
39 | */
40 | export type RedirectResp = BaseDataResp;
41 |
42 | /**
43 | * author: Ryan Su
44 | * @description: redirect information
45 | */
46 | export interface RedirectInfo {
47 | URL: string;
48 | }
49 |
--------------------------------------------------------------------------------
/src/layouts/default/header/components/ErrorAction.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
35 |
--------------------------------------------------------------------------------
/src/views/sys/login/QrCodeForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
{{ t('sys.login.scanSign') }}
11 |
14 |
15 |
16 |
17 |
31 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/content/default/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/content/tinymce-5/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/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 |
54 | export enum ParentIdEnum {
55 | DEFAULT = 1000000,
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/Menu/src/components/MenuItemContent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ getI18nName }}
6 |
7 |
8 |
32 |
--------------------------------------------------------------------------------
/src/design/transition/scroll.less:
--------------------------------------------------------------------------------
1 | .scroll-y-transition {
2 | .transition-default();
3 |
4 | &-enter-from,
5 | &-leave-to {
6 | opacity: 0;
7 | }
8 |
9 | &-enter-from {
10 | transform: translateY(-15px);
11 | }
12 |
13 | &-leave-to {
14 | transform: translateY(15px);
15 | }
16 | }
17 |
18 | .scroll-y-reverse-transition {
19 | .transition-default();
20 |
21 | &-enter-from,
22 | &-leave-to {
23 | opacity: 0;
24 | }
25 |
26 | &-enter-from {
27 | transform: translateY(15px);
28 | }
29 |
30 | &-leave-to {
31 | transform: translateY(-15px);
32 | }
33 | }
34 |
35 | .scroll-x-transition {
36 | .transition-default();
37 |
38 | &-enter-from,
39 | &-leave-to {
40 | opacity: 0;
41 | }
42 |
43 | &-enter-from {
44 | transform: translateX(-15px);
45 | }
46 |
47 | &-leave-to {
48 | transform: translateX(15px);
49 | }
50 | }
51 |
52 | .scroll-x-reverse-transition {
53 | .transition-default();
54 |
55 | &-enter-from,
56 | &-leave-to {
57 | opacity: 0;
58 | }
59 |
60 | &-enter-from {
61 | transform: translateX(15px);
62 | }
63 |
64 | &-leave-to {
65 | transform: translateX(-15px);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/packages/types/src/utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 任意类型的异步函数
3 | */
4 | type AnyPromiseFunction = (...arg: any[]) => PromiseLike;
5 |
6 | /**
7 | * 任意类型的普通函数
8 | */
9 | type AnyNormalFunction = (...arg: any[]) => any;
10 |
11 | /**
12 | * 任意类型的函数
13 | */
14 | type AnyFunction = AnyNormalFunction | AnyPromiseFunction;
15 |
16 | /**
17 | * T | null 包装
18 | */
19 | type Nullable = T | null;
20 |
21 | /**
22 | * T | Not null 包装
23 | */
24 | type NonNullable = T extends null | undefined ? never : T;
25 |
26 | /**
27 | * 字符串类型对象
28 | */
29 | type Recordable = Record;
30 |
31 | /**
32 | * 字符串类型对象(只读)
33 | */
34 | interface ReadonlyRecordable {
35 | readonly [key: string]: T;
36 | }
37 |
38 | /**
39 | * setTimeout 返回值类型
40 | */
41 | type TimeoutHandle = ReturnType;
42 |
43 | /**
44 | * setInterval 返回值类型
45 | */
46 | type IntervalHandle = ReturnType;
47 |
48 | export {
49 | type AnyFunction,
50 | type AnyNormalFunction,
51 | type AnyPromiseFunction,
52 | type IntervalHandle,
53 | type NonNullable,
54 | type Nullable,
55 | type ReadonlyRecordable,
56 | type Recordable,
57 | type TimeoutHandle,
58 | };
59 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/content/writer/content.min.css:
--------------------------------------------------------------------------------
1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/public/resource/tinymce/license.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/hooks/web/useScript.ts:
--------------------------------------------------------------------------------
1 | import { onMounted, onUnmounted, ref } from 'vue';
2 |
3 | interface ScriptOptions {
4 | src: string;
5 | }
6 |
7 | export function useScript(opts: ScriptOptions) {
8 | const isLoading = ref(false);
9 | const error = ref(false);
10 | const success = ref(false);
11 | let script: HTMLScriptElement;
12 |
13 | const promise = new Promise((resolve, reject) => {
14 | onMounted(() => {
15 | script = document.createElement('script');
16 | script.type = 'text/javascript';
17 | script.onload = function () {
18 | isLoading.value = false;
19 | success.value = true;
20 | error.value = false;
21 | resolve('');
22 | };
23 |
24 | script.onerror = function (err) {
25 | isLoading.value = false;
26 | success.value = false;
27 | error.value = true;
28 | reject(err);
29 | };
30 |
31 | script.src = opts.src;
32 | document.head.appendChild(script);
33 | });
34 | });
35 |
36 | onUnmounted(() => {
37 | script && script.remove();
38 | });
39 |
40 | return {
41 | isLoading,
42 | error,
43 | success,
44 | toPromise: () => promise,
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/src/design/public.less:
--------------------------------------------------------------------------------
1 | #app {
2 | width: 100%;
3 | height: 100%;
4 | }
5 |
6 | // =================================
7 | // ==============scrollbar==========
8 | // =================================
9 |
10 | ::-webkit-scrollbar {
11 | width: 7px;
12 | height: 8px;
13 | }
14 |
15 | // ::-webkit-scrollbar-track {
16 | // background: transparent;
17 | // }
18 |
19 | ::-webkit-scrollbar-track {
20 | background-color: rgb(0 0 0 / 5%);
21 | }
22 |
23 | ::-webkit-scrollbar-thumb {
24 | // background-color: rgba(144, 147, 153, 0.3);
25 | border-radius: 6px;
26 | // background: rgba(0, 0, 0, 0.6);
27 | background-color: rgb(144 147 153 / 30%);
28 | box-shadow: inset 0 0 6px rgb(0 0 0 / 20%);
29 | }
30 |
31 | ::-webkit-scrollbar-thumb:hover {
32 | background-color: @border-color-dark;
33 | }
34 |
35 | // =================================
36 | // ==============nprogress==========
37 | // =================================
38 | #nprogress {
39 | pointer-events: none;
40 |
41 | .bar {
42 | position: fixed;
43 | z-index: 99999;
44 | top: 0;
45 | left: 0;
46 | width: 100%;
47 | height: 2px;
48 | opacity: 0.75;
49 | background-color: @primary-color;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/directives/ellipsis.ts:
--------------------------------------------------------------------------------
1 | import type { CSSProperties, DirectiveBinding, ObjectDirective, App } from 'vue';
2 |
3 | interface IValue {
4 | width?: number;
5 | line?: number;
6 | }
7 |
8 | interface IOptions {
9 | [key: string]: CSSProperties;
10 | }
11 |
12 | const cssProperties: IOptions = {
13 | single: {
14 | overflow: 'hidden',
15 | textOverflow: 'ellipsis',
16 | whiteSpace: 'nowrap',
17 | },
18 | multiple: {
19 | display: '-webkit-box',
20 | overflow: 'hidden',
21 | wordBreak: 'break-all',
22 | },
23 | };
24 |
25 | const Ellipsis: ObjectDirective = {
26 | mounted(el: HTMLElement, binding: DirectiveBinding>) {
27 | const { value = [100, 1], arg = 'single' } = binding;
28 | const [width, line] = value;
29 | Object.entries(cssProperties[arg]).forEach(([key, value]) => {
30 | el.style[key] = value;
31 | });
32 | el.style.width = `${width}px`;
33 | if (arg === 'multiple') {
34 | el.style.webkitLineClamp = `${line}`;
35 | el.style.flexDirection = 'column';
36 | }
37 | },
38 | };
39 | export function setupEllipsisDirective(app: App) {
40 | app.directive('ellipsis', Ellipsis);
41 | }
42 | export default Ellipsis;
43 |
--------------------------------------------------------------------------------
/src/components/CountDown/src/useCountdown.ts:
--------------------------------------------------------------------------------
1 | import { ref, unref } from 'vue';
2 | import { tryOnUnmounted } from '@vueuse/core';
3 |
4 | export function useCountdown(count: number) {
5 | const currentCount = ref(count);
6 |
7 | const isStart = ref(false);
8 |
9 | let timerId: ReturnType | null;
10 |
11 | function clear() {
12 | timerId && window.clearInterval(timerId);
13 | }
14 |
15 | function stop() {
16 | isStart.value = false;
17 | clear();
18 | timerId = null;
19 | }
20 |
21 | function start() {
22 | if (unref(isStart) || !!timerId) {
23 | return;
24 | }
25 | isStart.value = true;
26 | timerId = setInterval(() => {
27 | if (unref(currentCount) === 1) {
28 | stop();
29 | currentCount.value = count;
30 | } else {
31 | currentCount.value -= 1;
32 | }
33 | }, 1000);
34 | }
35 |
36 | function reset() {
37 | currentCount.value = count;
38 | stop();
39 | }
40 |
41 | function restart() {
42 | reset();
43 | start();
44 | }
45 |
46 | tryOnUnmounted(() => {
47 | reset();
48 | });
49 |
50 | return { start, reset, restart, clear, stop, currentCount, isStart };
51 | }
52 |
--------------------------------------------------------------------------------
/public/resource/tinymce/skins/content/dark/content.min.css:
--------------------------------------------------------------------------------
1 | body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}
2 |
--------------------------------------------------------------------------------
/src/components/SimpleMenu/src/components/useSimpleMenuContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from '@/hooks/core/useContext';
2 | import type { Emitter } from '@/utils/mitt';
3 | import type { ComponentInternalInstance, InjectionKey, Ref } from 'vue';
4 |
5 | export type MenuEmitterEvents = {
6 | 'on-update-opened':
7 | | (string | number)[]
8 | | {
9 | opened: boolean;
10 | parent?: ComponentInternalInstance | null;
11 | uidList: number[];
12 | };
13 | 'on-menu-item-select': string | number;
14 | 'open-name-change': {
15 | name: string | number;
16 | opened: boolean;
17 | };
18 | 'on-update-active-name:submenu': number[];
19 | };
20 |
21 | export interface SimpleRootMenuContextProps {
22 | rootMenuEmitter: Emitter;
23 | activeName: Ref;
24 | }
25 |
26 | const key: InjectionKey = Symbol();
27 |
28 | export function createSimpleRootMenuContext(context: SimpleRootMenuContextProps) {
29 | return createContext(context, key, { readonly: false, native: true });
30 | }
31 |
32 | export function useSimpleRootMenuContext() {
33 | return useContext(key);
34 | }
35 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createWebHistory, RouteRecordRaw, createRouter } from 'vue-router';
2 | import type { App } from 'vue';
3 |
4 | import { basicRoutes } from './routes';
5 |
6 | // 白名单应该包含基本静态路由
7 | const WHITE_NAME_LIST: string[] = [];
8 | const getRouteNames = (array: any[]) =>
9 | array.forEach((item) => {
10 | WHITE_NAME_LIST.push(item.name);
11 | getRouteNames(item.children || []);
12 | });
13 | getRouteNames(basicRoutes);
14 |
15 | // app router
16 | // 创建一个可以被 Vue 应用程序使用的路由实例
17 | export const router = createRouter({
18 | // 创建一个历史记录。
19 | history: createWebHistory(import.meta.env.VITE_PUBLIC_PATH),
20 | // 应该添加到路由的初始路由列表。
21 | routes: basicRoutes as unknown as RouteRecordRaw[],
22 | // 是否应该禁止尾部斜杠。默认为假
23 | strict: true,
24 | scrollBehavior: () => ({ left: 0, top: 0 }),
25 | });
26 |
27 | // reset router
28 | export function resetRouter() {
29 | router.getRoutes().forEach((route) => {
30 | const { name } = route;
31 | if (name && !WHITE_NAME_LIST.includes(name as string)) {
32 | router.hasRoute(name) && router.removeRoute(name);
33 | }
34 | });
35 | }
36 |
37 | // config router
38 | // 配置路由器
39 | export function setupRouter(app: App) {
40 | app.use(router);
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Table/src/components/editable/CellComponent.ts:
--------------------------------------------------------------------------------
1 | import type { defineComponent } from 'vue';
2 | import type { ComponentType } from '../../types/componentType';
3 | import { componentMap } from '@/components/Table/src/componentMap';
4 |
5 | import { Popover } from 'ant-design-vue';
6 | import { h } from 'vue';
7 |
8 | export interface ComponentProps {
9 | component: ComponentType;
10 | rule: boolean;
11 | popoverVisible: boolean;
12 | ruleMessage: string;
13 | getPopupContainer?: Fn;
14 | }
15 |
16 | export const CellComponent = (
17 | {
18 | component = 'Input',
19 | rule = true,
20 | ruleMessage,
21 | popoverVisible,
22 | getPopupContainer,
23 | }: ComponentProps,
24 | { attrs },
25 | ) => {
26 | const Comp = componentMap.get(component) as typeof defineComponent;
27 |
28 | const DefaultComp = h(Comp, attrs);
29 | if (!rule) {
30 | return DefaultComp;
31 | }
32 | return h(
33 | Popover,
34 | {
35 | overlayClassName: 'edit-cell-rule-popover',
36 | open: popoverVisible,
37 | ...(getPopupContainer ? { getPopupContainer } : {}),
38 | },
39 | {
40 | default: () => DefaultComp,
41 | content: () => ruleMessage,
42 | },
43 | );
44 | };
45 |
--------------------------------------------------------------------------------