├── .editorconfig
├── .env
├── .env.development
├── .env.production
├── .env.test
├── .gitignore
├── .husky
├── commit-msg
├── lintstagedrc.js
└── pre-commit
├── .prettierignore
├── .prettierrc.json
├── .stylelintignore
├── .stylelintrc.js
├── .versionrc
├── .vscode
├── extensions.json
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── TODO.md
├── commitlint.config.js
├── eslint.config.js
├── eslintrc-globals.js
├── index.html
├── node
├── compress.ts
├── getEnv.ts
├── optimize.ts
└── plugins.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
├── favicon.ico
└── tinymce
│ ├── langs
│ └── zh_CN.js
│ ├── plugins
│ ├── axupimgs
│ │ ├── loading.gif
│ │ ├── plugin.js
│ │ ├── plugin.min.js
│ │ └── upfiles.html
│ └── help
│ │ ├── index.js
│ │ ├── js
│ │ └── i18n
│ │ │ └── keynav
│ │ │ ├── ar.js
│ │ │ ├── bg_BG.js
│ │ │ ├── ca.js
│ │ │ ├── cs.js
│ │ │ ├── da.js
│ │ │ ├── de.js
│ │ │ ├── el.js
│ │ │ ├── en.js
│ │ │ ├── es.js
│ │ │ ├── eu.js
│ │ │ ├── fa.js
│ │ │ ├── fi.js
│ │ │ ├── fr_FR.js
│ │ │ ├── he_IL.js
│ │ │ ├── hi.js
│ │ │ ├── hr.js
│ │ │ ├── hu_HU.js
│ │ │ ├── id.js
│ │ │ ├── it.js
│ │ │ ├── ja.js
│ │ │ ├── kk.js
│ │ │ ├── ko_KR.js
│ │ │ ├── ms.js
│ │ │ ├── nb_NO.js
│ │ │ ├── nl.js
│ │ │ ├── pl.js
│ │ │ ├── pt_BR.js
│ │ │ ├── pt_PT.js
│ │ │ ├── ro.js
│ │ │ ├── ru.js
│ │ │ ├── sk.js
│ │ │ ├── sl_SI.js
│ │ │ ├── sv_SE.js
│ │ │ ├── th_TH.js
│ │ │ ├── tr.js
│ │ │ ├── uk.js
│ │ │ ├── vi.js
│ │ │ ├── zh_CN.js
│ │ │ └── zh_TW.js
│ │ ├── plugin.js
│ │ └── plugin.min.js
│ └── skins
│ ├── content
│ ├── dark
│ │ ├── content.css
│ │ ├── content.js
│ │ └── content.min.css
│ ├── default
│ │ ├── content.css
│ │ ├── content.js
│ │ └── content.min.css
│ ├── document
│ │ ├── content.css
│ │ ├── content.js
│ │ └── content.min.css
│ ├── tinymce-5-dark
│ │ ├── content.css
│ │ ├── content.js
│ │ └── content.min.css
│ ├── tinymce-5
│ │ ├── content.css
│ │ ├── content.js
│ │ └── content.min.css
│ └── writer
│ │ ├── content.css
│ │ ├── content.js
│ │ └── content.min.css
│ └── ui
│ ├── oxide-dark
│ ├── content.css
│ ├── content.inline.css
│ ├── content.inline.js
│ ├── content.inline.min.css
│ ├── content.js
│ ├── content.min.css
│ ├── skin.css
│ ├── skin.js
│ ├── skin.min.css
│ ├── skin.shadowdom.css
│ ├── skin.shadowdom.js
│ └── skin.shadowdom.min.css
│ ├── oxide
│ ├── content.css
│ ├── content.inline.css
│ ├── content.inline.js
│ ├── content.inline.min.css
│ ├── content.js
│ ├── content.min.css
│ ├── skin.css
│ ├── skin.js
│ ├── skin.min.css
│ ├── skin.shadowdom.css
│ ├── skin.shadowdom.js
│ └── skin.shadowdom.min.css
│ ├── tinymce-5-dark
│ ├── content.css
│ ├── content.inline.css
│ ├── content.inline.js
│ ├── content.inline.min.css
│ ├── content.js
│ ├── content.min.css
│ ├── skin.css
│ ├── skin.js
│ ├── skin.min.css
│ ├── skin.shadowdom.css
│ ├── skin.shadowdom.js
│ └── skin.shadowdom.min.css
│ └── tinymce-5
│ ├── content.css
│ ├── content.inline.css
│ ├── content.inline.js
│ ├── content.inline.min.css
│ ├── content.js
│ ├── content.min.css
│ ├── skin.css
│ ├── skin.js
│ ├── skin.min.css
│ ├── skin.shadowdom.css
│ ├── skin.shadowdom.js
│ └── skin.shadowdom.min.css
├── src
├── App.vue
├── api
│ ├── menu.ts
│ └── user.ts
├── assets
│ ├── iconfont
│ │ ├── demo.css
│ │ ├── demo_index.html
│ │ ├── iconfont.css
│ │ ├── iconfont.js
│ │ ├── iconfont.json
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── iconfont.woff2
│ ├── icons
│ │ ├── apply.svg
│ │ ├── battery-full.svg
│ │ ├── bell-ling.svg
│ │ ├── core
│ │ │ ├── bug.svg
│ │ │ ├── command.svg
│ │ │ ├── fullscreen-exit.svg
│ │ │ ├── fullscreen.svg
│ │ │ ├── header-dark.svg
│ │ │ ├── header-light.svg
│ │ │ ├── language.svg
│ │ │ ├── menu-dark.svg
│ │ │ ├── menu-light.svg
│ │ │ └── search.svg
│ │ ├── draft.svg
│ │ └── login
│ │ │ ├── alipay.svg
│ │ │ ├── qq.svg
│ │ │ ├── wechat.svg
│ │ │ └── weibo.svg
│ └── images
│ │ ├── default.png
│ │ ├── login
│ │ ├── login_bg.svg
│ │ ├── login_left.png
│ │ ├── login_left.svg
│ │ ├── login_left0.png
│ │ ├── login_left1.png
│ │ ├── login_left2.png
│ │ ├── login_left3.png
│ │ ├── login_left4.png
│ │ └── login_left5.png
│ │ ├── logo.png
│ │ ├── menu-theme
│ │ ├── dark.png
│ │ └── light.png
│ │ ├── msg
│ │ ├── msg01.png
│ │ ├── msg02.png
│ │ ├── msg03.png
│ │ ├── msg04.png
│ │ └── msg05.png
│ │ ├── notData.png
│ │ ├── status
│ │ ├── 403.png
│ │ ├── 404.png
│ │ └── 500.png
│ │ └── system-theme
│ │ ├── dark.png
│ │ ├── light.png
│ │ └── system.png
├── components
│ ├── core
│ │ ├── icon
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ ├── font-icon.vue
│ │ │ │ ├── iconify-offline.vue
│ │ │ │ ├── iconify-online.vue
│ │ │ │ └── svg-icon.vue
│ │ │ │ ├── icon.ts
│ │ │ │ └── index.vue
│ │ ├── image-verify-code
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── hooks.ts
│ │ │ │ └── index.vue
│ │ ├── pagination
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── index.scss
│ │ │ │ └── index.vue
│ │ ├── permission
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── auth.vue
│ │ │ │ └── role.vue
│ │ ├── switch-dark
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ └── index.vue
│ │ ├── tooltip
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ └── index.vue
│ │ ├── use-dialog
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── index.scss
│ │ │ │ ├── index.tsx
│ │ │ │ └── index.vue
│ │ └── use-drawer
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ ├── index.scss
│ │ │ ├── index.tsx
│ │ │ └── index.vue
│ ├── editor
│ │ ├── code-mirror
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── codeMirror5
│ │ │ │ ├── code-mirror.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── index.vue
│ │ │ │ ├── mode.ts
│ │ │ │ └── theme.ts
│ │ │ │ └── index.vue
│ │ ├── tinymce
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── config.ts
│ │ │ │ └── index.vue
│ │ └── wang-editor
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ ├── config.ts
│ │ │ └── index.vue
│ ├── index.ts
│ ├── pro
│ │ ├── form-item
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ ├── checkbox.vue
│ │ │ │ ├── radio.vue
│ │ │ │ ├── select.vue
│ │ │ │ └── tree.vue
│ │ │ │ ├── helper
│ │ │ │ ├── component-map.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── index.vue
│ │ │ │ └── types.ts
│ │ ├── form
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── composables
│ │ │ │ └── use-form.ts
│ │ │ │ ├── helper
│ │ │ │ └── index.ts
│ │ │ │ ├── index.vue
│ │ │ │ └── types.ts
│ │ ├── grid
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ └── grid-item.vue
│ │ │ │ └── index.vue
│ │ ├── image-viewer
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ └── index.vue
│ │ ├── pro-form
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ ├── checkbox-select.vue
│ │ │ │ ├── pro-form-item.vue
│ │ │ │ ├── tree.vue
│ │ │ │ ├── use-render-checkbox.tsx
│ │ │ │ ├── use-render-component.tsx
│ │ │ │ ├── use-render-radio.tsx
│ │ │ │ └── use-render-select.tsx
│ │ │ │ ├── helper
│ │ │ │ ├── component-map.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── hooks
│ │ │ │ └── use-pro-form.ts
│ │ │ │ ├── index.scss
│ │ │ │ ├── index.vue
│ │ │ │ └── interface
│ │ │ │ └── index.ts
│ │ ├── pro-search
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── hooks
│ │ │ │ └── use-pro-search.ts
│ │ │ │ └── index.vue
│ │ ├── pro-steps
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ └── index.vue
│ │ ├── pro-table
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ ├── col-setting.vue
│ │ │ │ ├── dialog-form.vue
│ │ │ │ ├── plugins
│ │ │ │ │ ├── header-filter.tsx
│ │ │ │ │ └── row-inline-edit.tsx
│ │ │ │ ├── table-column.vue
│ │ │ │ ├── table-main-header.vue
│ │ │ │ └── table-main.vue
│ │ │ │ ├── helper
│ │ │ │ ├── export.tsx
│ │ │ │ └── index.ts
│ │ │ │ ├── hooks
│ │ │ │ ├── use-pro-table.ts
│ │ │ │ ├── use-selection.ts
│ │ │ │ └── use-table.ts
│ │ │ │ ├── index.scss
│ │ │ │ ├── index.vue
│ │ │ │ └── interface
│ │ │ │ └── index.ts
│ │ ├── pro-transfer
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── icons
│ │ │ │ └── switch.svg
│ │ │ │ ├── index.vue
│ │ │ │ └── table.vue
│ │ └── tree-filter
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ ├── index.scss
│ │ │ └── index.vue
│ └── view
│ │ ├── count-to
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ ├── cropper
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ ├── drag-drawer
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── drag-drawer-trigger.vue
│ │ │ └── index.vue
│ │ ├── draggable-item
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ ├── draggable-list
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ ├── excel-upload
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ ├── flicker
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ ├── highlight
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ ├── icon-picker
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── data
│ │ │ ├── icons.ant-design.ts
│ │ │ ├── icons.ep.ts
│ │ │ └── icons.tdesign.ts
│ │ │ ├── index.scss
│ │ │ └── index.vue
│ │ ├── images-upload
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── image.vue
│ │ │ └── images.vue
│ │ ├── material-input
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── index.scss
│ │ │ └── index.vue
│ │ ├── qr-code
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ ├── seamless-scroll
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── index.vue
│ │ │ └── utils.ts
│ │ ├── split-pane
│ │ ├── index.ts
│ │ └── src
│ │ │ ├── index.vue
│ │ │ └── split-line.vue
│ │ ├── table-sort
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ ├── text-hover-effect
│ │ ├── index.ts
│ │ └── src
│ │ │ └── index.vue
│ │ └── video-player
│ │ ├── index.ts
│ │ └── src
│ │ ├── index.vue
│ │ └── video-player-viewer.vue
├── composables
│ ├── core
│ │ ├── css-module
│ │ │ ├── namespace.module.scss
│ │ │ └── namespace.module.scss.d.ts
│ │ ├── use-breadcrumb.ts
│ │ ├── use-browser-title.ts
│ │ ├── use-cache.ts
│ │ ├── use-menu.ts
│ │ ├── use-namespace.ts
│ │ ├── use-permission.ts
│ │ ├── use-route-fn.ts
│ │ ├── use-storage.ts
│ │ ├── use-theme.ts
│ │ ├── use-upgrade.ts
│ │ └── use-watch-css-var.ts
│ ├── index.ts
│ ├── use-boolean.ts
│ ├── use-clipboard.ts
│ ├── use-confirm.ts
│ ├── use-echarts.ts
│ └── use-validator.ts
├── config
│ ├── base-config.ts
│ ├── constant.ts
│ ├── env-config.ts
│ ├── index.ts
│ ├── symbols.ts
│ └── types
│ │ └── index.ts
├── directives
│ ├── index.ts
│ └── modules
│ │ ├── copy.ts
│ │ ├── debounce.ts
│ │ ├── draggable.ts
│ │ ├── long-press.ts
│ │ ├── permission
│ │ ├── auth.ts
│ │ └── role.ts
│ │ ├── throttle.ts
│ │ ├── water-marker.ts
│ │ └── waves
│ │ ├── index.ts
│ │ └── waves.css
├── enums
│ └── appEnum.ts
├── languages
│ ├── index.ts
│ └── locales
│ │ ├── en-US.ts
│ │ └── zh-CN.ts
├── layout
│ ├── base-layout.scss
│ ├── components
│ │ ├── Header
│ │ │ ├── components
│ │ │ │ ├── breadcrumb
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── collapse-trigger
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── error-log
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── fullscreen
│ │ │ │ │ └── index.vue
│ │ │ │ ├── global-search
│ │ │ │ │ ├── index.scss
│ │ │ │ │ ├── index.vue
│ │ │ │ │ ├── input.scss
│ │ │ │ │ ├── input.vue
│ │ │ │ │ └── select.vue
│ │ │ │ ├── language-select
│ │ │ │ │ └── index.vue
│ │ │ │ ├── light-dark-switch
│ │ │ │ │ ├── animation.ts
│ │ │ │ │ └── index.vue
│ │ │ │ ├── notification
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ └── user-avatar
│ │ │ │ │ ├── dropdown.vue
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ ├── header-left.vue
│ │ │ ├── header-right.vue
│ │ │ └── index.vue
│ │ ├── Loading
│ │ │ ├── index.scss
│ │ │ └── index.vue
│ │ ├── Menu
│ │ │ ├── index.vue
│ │ │ └── menu-item.vue
│ │ ├── header
│ │ │ ├── components
│ │ │ │ ├── breadcrumb
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── collapse-trigger
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── error-log
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── fullscreen
│ │ │ │ │ └── index.vue
│ │ │ │ ├── global-search
│ │ │ │ │ ├── index.scss
│ │ │ │ │ ├── index.vue
│ │ │ │ │ ├── input.scss
│ │ │ │ │ ├── input.vue
│ │ │ │ │ └── select.vue
│ │ │ │ ├── language-select
│ │ │ │ │ └── index.vue
│ │ │ │ ├── light-dark-switch
│ │ │ │ │ ├── animation.ts
│ │ │ │ │ └── index.vue
│ │ │ │ ├── notification
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ └── user-avatar
│ │ │ │ │ ├── dropdown.vue
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ ├── header-left.vue
│ │ │ ├── header-right.vue
│ │ │ └── index.vue
│ │ ├── iframe
│ │ │ ├── iframe-blank.vue
│ │ │ ├── iframe-view.vue
│ │ │ ├── index.vue
│ │ │ └── use-iframe.ts
│ │ ├── loading
│ │ │ ├── index.scss
│ │ │ └── index.vue
│ │ ├── menu
│ │ │ ├── index.vue
│ │ │ └── menu-item.vue
│ │ ├── page-content
│ │ │ ├── components
│ │ │ │ └── maximize.vue
│ │ │ └── index.vue
│ │ ├── tab-nav
│ │ │ ├── components
│ │ │ │ ├── more-button
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ └── right-menu
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ ├── tab-nav-classic
│ │ │ │ ├── index.scss
│ │ │ │ └── index.vue
│ │ │ ├── tab-nav-element
│ │ │ │ ├── index.scss
│ │ │ │ └── index.vue
│ │ │ ├── tab-nav-simple
│ │ │ │ └── index.vue
│ │ │ └── use-tab-nav.ts
│ │ ├── theme-panel
│ │ │ ├── components
│ │ │ │ ├── base-config-switch
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── browser-title-switch
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── global-theme-switch
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── index.ts
│ │ │ │ ├── layout-mode-switch
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ ├── menu-theme-switch
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ │ └── system-theme-switch
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── index.vue
│ │ │ └── index.vue
│ │ └── watermark
│ │ │ ├── index.scss
│ │ │ └── index.vue
│ ├── index-async.vue
│ ├── index.vue
│ ├── layout-classic
│ │ ├── index.scss
│ │ └── index.vue
│ ├── layout-columns
│ │ ├── index.scss
│ │ └── index.vue
│ ├── layout-horizontal
│ │ ├── index.scss
│ │ └── index.vue
│ ├── layout-iframe
│ │ ├── index.scss
│ │ └── index.vue
│ ├── layout-mixins
│ │ ├── index.scss
│ │ └── index.vue
│ ├── layout-vertical
│ │ ├── index.scss
│ │ └── index.vue
│ └── redirect.vue
├── main.ts
├── mock
│ ├── drag-item.ts
│ ├── drag-list.ts
│ ├── html.ts
│ ├── message.ts
│ ├── pro-table.ts
│ ├── table.ts
│ └── tinymce.ts
├── request
│ ├── axios-cancel.ts
│ ├── check-status.ts
│ ├── http-enum.ts
│ ├── index.ts
│ └── service-loading.ts
├── router
│ ├── before-close.ts
│ ├── guards
│ │ ├── after-each.ts
│ │ └── before-each.ts
│ ├── helper.ts
│ ├── index.ts
│ ├── routes-config.ts
│ └── routes
│ │ ├── components.ts
│ │ ├── details.ts
│ │ ├── directive.ts
│ │ ├── error.ts
│ │ ├── excel.ts
│ │ ├── frame.ts
│ │ ├── nested.ts
│ │ ├── outer-chain.ts
│ │ ├── permission.ts
│ │ ├── pro-components.ts
│ │ ├── pro-form.ts
│ │ ├── table.ts
│ │ └── tool.ts
├── stores
│ ├── core
│ │ ├── layout.ts
│ │ ├── route.ts
│ │ ├── setting.ts
│ │ └── user.ts
│ ├── error-log.ts
│ ├── index.ts
│ ├── message.ts
│ └── websocket.ts
├── styles
│ ├── common
│ │ ├── atomic.scss
│ │ ├── base.scss
│ │ ├── dark.scss
│ │ ├── menu-theme.scss
│ │ ├── scrollbar.scss
│ │ ├── theme-animation.scss
│ │ └── transition.scss
│ ├── element-plus
│ │ ├── el-dark.scss
│ │ ├── el-light.scss
│ │ └── el-ui.scss
│ ├── index.scss
│ ├── mixins
│ │ ├── bem.scss
│ │ ├── function.scss
│ │ ├── mixins.scss
│ │ └── namespace.scss
│ ├── normalize.css
│ ├── plugin.scss
│ └── variables.scss
├── types
│ ├── auto-components.d.ts
│ ├── auto-import.d.ts
│ ├── components.d.ts
│ ├── enhance.d.ts
│ ├── env.d.ts
│ ├── http.d.ts
│ ├── plugins.d.ts
│ ├── router.d.ts
│ └── window.d.ts
├── utils
│ ├── component
│ │ ├── install.ts
│ │ └── typescript.ts
│ ├── core
│ │ ├── color.ts
│ │ ├── error-handler.ts
│ │ ├── is.ts
│ │ ├── log.ts
│ │ ├── message.ts
│ │ ├── mitt-bus.ts
│ │ └── scroll-to.ts
│ ├── download.ts
│ ├── excel.ts
│ ├── helper.ts
│ ├── id-generator
│ │ ├── index.ts
│ │ └── snowflakes.ts
│ ├── index.ts
│ ├── nprogress.ts
│ ├── print.ts
│ ├── rich-editor.ts
│ └── tree.ts
└── views
│ ├── components
│ ├── animation-mixin
│ │ ├── button.vue
│ │ └── index.vue
│ ├── code-diff-editor
│ │ ├── index.vue
│ │ ├── newDoc.json
│ │ └── oldDoc.json
│ ├── code-mirror
│ │ └── index.vue
│ ├── count-to
│ │ └── index.vue
│ ├── cropper
│ │ └── index.vue
│ ├── drag-drawer
│ │ └── index.vue
│ ├── draggable-item
│ │ └── index.vue
│ ├── draggable-list
│ │ └── index.vue
│ ├── highlight
│ │ └── index.vue
│ ├── icon
│ │ └── index.vue
│ ├── image-viewer
│ │ └── index.vue
│ ├── message
│ │ └── index.vue
│ ├── org-tree
│ │ └── index.vue
│ ├── qr-code
│ │ └── index.vue
│ ├── seamless-scroll
│ │ └── index.vue
│ ├── split-pane
│ │ └── index.vue
│ ├── tinymce
│ │ └── index.vue
│ ├── tree-filter
│ │ └── index.vue
│ ├── upload-images
│ │ └── index.vue
│ ├── use-dialog
│ │ └── index.vue
│ ├── use-drawer
│ │ └── index.vue
│ ├── video-player
│ │ └── index.vue
│ └── wang-editor
│ │ └── index.vue
│ ├── directives
│ ├── copy
│ │ └── index.vue
│ ├── debounce
│ │ └── index.vue
│ ├── drag
│ │ └── index.vue
│ ├── long-press
│ │ └── index.vue
│ ├── throttle
│ │ └── index.vue
│ └── watermark
│ │ └── index.vue
│ ├── error
│ ├── 403.vue
│ ├── 404.vue
│ ├── 500.vue
│ └── index.scss
│ ├── errorLog
│ └── index.vue
│ ├── excel
│ ├── export-excel.vue
│ ├── merge-header.vue
│ ├── select-excel.vue
│ └── upload-excel.vue
│ ├── home
│ ├── components
│ │ ├── card-item.vue
│ │ ├── chart-bar.vue
│ │ ├── chart-line-bar.vue
│ │ ├── chart-line.vue
│ │ └── chart-pie.vue
│ ├── index-full.vue
│ └── index.vue
│ ├── login
│ ├── components
│ │ ├── forget.vue
│ │ ├── phone.vue
│ │ ├── qrCode.vue
│ │ └── register.vue
│ ├── index.scss
│ ├── index.vue
│ ├── loginForm.scss
│ ├── loginForm.vue
│ ├── rules.ts
│ └── verifyCode.ts
│ ├── message-center
│ └── index.vue
│ ├── nested
│ ├── menu1
│ │ ├── index.vue
│ │ ├── menu1-1
│ │ │ └── index.vue
│ │ ├── menu1-2
│ │ │ └── index.vue
│ │ └── menu1-3
│ │ │ ├── index.vue
│ │ │ ├── menu1-3-1
│ │ │ └── index.vue
│ │ │ └── menu1-3-2
│ │ │ └── index.vue
│ └── menu2
│ │ └── index.vue
│ ├── permission
│ ├── role-permission.vue
│ └── switch-permission.vue
│ ├── pro-components
│ ├── pro-form
│ │ ├── create-pro-form
│ │ │ └── index.vue
│ │ ├── detail-pro-form
│ │ │ ├── index.vue
│ │ │ └── options.ts
│ │ ├── simple-pro-form
│ │ │ └── index.vue
│ │ └── use-pro-form
│ │ │ └── index.vue
│ ├── pro-search
│ │ ├── create-pro-search.vue
│ │ ├── simple-pro-search.vue
│ │ └── use-pro-search.vue
│ ├── pro-steps
│ │ └── index.vue
│ ├── pro-table
│ │ ├── complex-pro-table
│ │ │ └── index.vue
│ │ ├── create-pro-table
│ │ │ └── index.vue
│ │ ├── detail-pro-table
│ │ │ └── index.vue
│ │ ├── simple-pro-table
│ │ │ └── index.vue
│ │ ├── tree-filter-pro-table
│ │ │ └── index.vue
│ │ └── use-pro-table
│ │ │ └── index.vue
│ └── pro-transfer
│ │ └── index.vue
│ ├── profile
│ ├── components
│ │ ├── account
│ │ │ └── index.vue
│ │ ├── editorInfo
│ │ │ └── index.vue
│ │ ├── timeline
│ │ │ └── index.vue
│ │ ├── user-avatar
│ │ │ ├── index.scss
│ │ │ └── index.vue
│ │ └── user-info
│ │ │ ├── index.scss
│ │ │ └── index.vue
│ └── index.vue
│ ├── table
│ ├── drag-table
│ │ └── index.vue
│ ├── dynamic-table
│ │ ├── fixedHeaderTable.vue
│ │ ├── index.vue
│ │ └── unFixedHeaderTable.vue
│ ├── inline-table
│ │ └── index.vue
│ ├── integration-table
│ │ └── index.vue
│ ├── operate-table
│ │ └── index.vue
│ ├── page-table
│ │ └── index.vue
│ ├── search-table
│ │ └── index.vue
│ └── sort-table
│ │ └── index.vue
│ ├── tabs
│ ├── hooks.ts
│ ├── index.vue
│ ├── params-detail.vue
│ └── query-detail.vue
│ └── tools
│ ├── clipboard
│ └── index.vue
│ ├── download
│ └── index.vue
│ ├── pdf
│ └── index.vue
│ ├── print
│ └── index.vue
│ ├── timeline
│ └── index.vue
│ ├── v-context-menu
│ ├── components
│ │ ├── basic.vue
│ │ ├── menuDynamic.vue
│ │ └── menuGroup.vue
│ └── index.vue
│ └── v-menus
│ ├── components
│ ├── useComponent.vue
│ ├── useDirective.vue
│ └── useFunction.vue
│ └── index.vue
├── tsconfig.json
├── vercel.json
└── vite.config.mts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # @see: http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*] # 表示所有文件适用
6 | charset = utf-8 # 设置文件字符集为 utf-8
7 | end_of_line = lf # 控制换行类型(lf | cr | crlf)
8 | insert_final_newline = true # 始终在文件末尾插入一个新行
9 | indent_style = tab # 缩进风格(tab | space)
10 | indent_size = 2 # 缩进大小
11 | max_line_length = 130 # 最大行长度
12 |
13 | [*.md] # 表示仅对 md 文件适用以下规则
14 | max_line_length = off # 关闭最大行长度限制
15 | trim_trailing_whitespace = false # 关闭末尾空格修剪
16 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # port
2 | VITE_PORT = 8099
3 |
4 | # open 运行 npm run dev 时自动打开浏览器
5 | VITE_OPEN = true
6 |
7 | # 是否生成包分析文件
8 | VITE_REPORT = false
9 |
10 | # 是否开启 gzip 压缩
11 | VITE_BUILD_GZIP = true
12 |
13 | # 是否删除生产环境 console
14 | VITE_DROP_CONSOLE = true
15 |
16 | # 是否删除生产环境 debugger
17 | VITE_DROP_DEBUGGER = true
18 |
19 | # 是否加载所有 element-plus 组件,false 则为按需加载
20 | VITE_LOAD_ALL_EP_COMPONENTS = false
21 |
22 | # 是否加载所有 element-plus 样式,false 则为按需加载
23 | VITE_LOAD_ALL_EP_STYLE = false
24 |
25 | # 打包输出目录,默认 dist
26 | VITE_OUT_DIR = "dist"
27 |
28 | # 是否生成 sourcemap 文件
29 | VITE_SOURCEMAP = false
30 |
31 | # 是否将 css 切割
32 | VITE_CSS_SPLIT = false
33 |
34 | # 是否启用 WebSocket
35 | VITE_WEBSOCKET = false
36 |
37 | # 路由权限模式(frontend | backend | both)
38 | VITE_ROUTE_ACCESS_MODE = frontend
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # 本地环境接口地址
2 | VITE_API_URL = '/api'
3 |
4 | # 静态文件获取根路径
5 | VITE_PUBLIC_PATH = "/"
6 |
7 | # 线上环境路由历史模式(Hash 模式传 "hash"、HTML5 模式传 "h5"、Hash 模式带 base 参数传 "hash, base 参数"、HTML5 模式带 base 参数传 "h5, base 参数"),如果填错或者不填,则默认 h5
8 | VITE_ROUTER_MODE = "h5, /"
9 |
10 | # 是否加载所有 element-plus 样式,false 则为按需加载
11 | VITE_LOAD_ALL_EP_STYLE = true
12 |
13 | # 是否加载所有 element-plus 组件,false 则为按需加载
14 | VITE_LOAD_ALL_EP_COMPONENTS = true
15 |
16 | # WebSocket 连接 URL,前提:在 .env 里将 VITE_WEBSOCKET 改为 true,开启 WebSocket
17 | VITE_WEBSOCKET_URL = ""
18 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # 线上环境接口地址
2 | VITE_API_URL = "/pro"
3 |
4 | # 静态文件获取根路径
5 | VITE_PUBLIC_PATH = "/"
6 |
7 | # 线上环境路由历史模式(Hash 模式传 "hash"、HTML5 模式传 "h5"、Hash 模式带 base 参数传 "hash, base 参数"、HTML5 模式带 base 参数传 "h5, base 参数"),如果填错或者不填,则默认 h5
8 | VITE_ROUTER_MODE = "h5, /"
9 |
10 | # 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
11 | # 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
12 | # 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
13 | # 开启压缩条件,.env 文件设置 VITE_BUILD_GZIP 为 true
14 | VITE_COMPRESSION = "none"
15 |
16 | # 打包输出目录,默认 dist
17 | VITE_OUT_DIR = "dist"
18 |
19 | # 是否生成 sourcemap 文件
20 | VITE_SOURCEMAP = false
21 |
22 | # 是否将 css 切割
23 | VITE_CSS_SPLIT = true
24 |
25 | # 是否加载所有 element-plus 样式,false 则为按需加载
26 | VITE_LOAD_ALL_EP_STYLE = false
27 |
28 | # 是否加载所有 element-plus 组件,false 则为按需加载
29 | VITE_LOAD_ALL_EP_COMPONENTS = false
30 |
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
1 | # 测试环境接口地址
2 | VITE_API_URL = "/test"
3 |
4 | # 静态文件获取根路径
5 | VITE_PUBLIC_PATH = "/"
6 |
7 | # 线上环境路由历史模式(Hash 模式传 "hash"、HTML5 模式传 "h5"、Hash 模式带 base 参数传 "hash, base 参数"、HTML5 模式带 base 参数传 "h5, base 参数"),如果填错或者不填,则默认 h5
8 | VITE_ROUTER_MODE = "h5, /"
9 |
10 | # 是否启用 gzip 压缩或 brotli 压缩(分两种情况,删除原始文件和不删除原始文件)
11 | # 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
12 | # 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
13 | # 开启压缩条件,.env 文件设置 VITE_BUILD_GZIP 为 true
14 | VITE_COMPRESSION = "gzip"
15 |
16 | # 打包输出目录,默认 dist
17 | VITE_OUT_DIR = "dist"
18 |
19 | # 是否生成 sourcemap 文件
20 | VITE_SOURCEMAP = false
21 |
22 | # 是否将 css 切割
23 | VITE_CSS_SPLIT = false
24 |
25 | # 是否加载所有 element-plus 样式,false 则为按需加载
26 | VITE_LOAD_ALL_EP_STYLE = false
27 |
28 | # 是否加载所有 element-plus 组件,false 则为按需加载
29 | VITE_LOAD_ALL_EP_COMPONENTS = false
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | !.vscode/settings.json
24 | .idea
25 | *.suo
26 | *.ntvs*
27 | *.njsproj
28 | *.sln
29 | *.sw?
30 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | npx --no-install commitlint --edit "$1"
4 |
--------------------------------------------------------------------------------
/.husky/lintstagedrc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
3 | "!(package)*.json": ["prettier --write--parser json"],
4 | "package.json": ["prettier --write"],
5 | "*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"],
6 | "*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"],
7 | "*.md": ["prettier --write"],
8 | };
9 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npm run lint:lint-staged
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /dist/*
2 | /html/*
3 | .local
4 | /node_modules/**
5 |
6 | **/*.svg
7 | **/*.sh
8 |
9 | /public/*
10 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "semi": true,
4 | "singleQuote": false,
5 | "useTabs": false,
6 | "trailingComma": "es5",
7 | "arrowParens": "avoid",
8 | "bracketSpacing": true,
9 | "proseWrap": "preserve",
10 | "endOfLine": "auto",
11 | "jsxSingleQuote": false,
12 | "htmlWhitespaceSensitivity": "ignore"
13 | }
14 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | /dist/*
2 | /html/*
3 | /public/*
4 | public/*
5 | /dist*
6 |
--------------------------------------------------------------------------------
/.versionrc:
--------------------------------------------------------------------------------
1 | {
2 | "types": [
3 | {
4 | "type": "feat",
5 | "section": "Features",
6 | "hidden": false
7 | },
8 | {
9 | "type": "fix",
10 | "section": "Bug Fixes",
11 | "hidden": false
12 | },
13 | {
14 | "type": "docs",
15 | "section": "Docs",
16 | "hidden": false
17 | },
18 | {
19 | "type": "style",
20 | "section": "Styling",
21 | "hidden": false
22 | },
23 | {
24 | "type": "refactor",
25 | "section": "Code Refactoring",
26 | "hidden": false
27 | },
28 | {
29 | "type": "perf",
30 | "section": "Performance Improvements",
31 | "hidden": false
32 | },
33 | {
34 | "type": "test",
35 | "section": "Tests",
36 | "hidden": false
37 | },
38 | {
39 | "type": "build",
40 | "section": "Build System",
41 | "hidden": false
42 | },
43 | {
44 | "type": "ci",
45 | "section": "CI",
46 | "hidden": false
47 | },
48 | {
49 | "type": "chore",
50 | "section": "Others",
51 | "hidden": false
52 | },
53 | {
54 | "type": "revert",
55 | "section": "Reverts",
56 | "hidden": false
57 | }
58 | ]
59 | }
60 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Vue.volar",
4 | "stylelint.vscode-stylelint",
5 | "dbaeumer.vscode-eslint",
6 | "esbenp.prettier-vscode",
7 | "syler.sass-indented"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Brian Liu
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.
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | ## TODO
2 |
3 | ## 特性
4 |
5 | ## 优化
6 |
7 | - 登录页适配暗黑模式
8 | - pro table 去掉 prefixClass,改为 proPage
9 | - 可以打开的最大动态路由 tab 优化
10 | - Tab 和 MainContent 隔离?
11 | - 系统升级公告版本升级后强制登录改为选择登录
12 |
13 | ## Bug
14 |
15 | - 搜索框跳转任何菜单都 404
16 | - 持久化标签页后,刷新页面图标丢失
17 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | teek-design-vue3
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/node/optimize.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 此文件作用于 `vite.config.ts` 的 `optimizeDeps.include` 依赖预构建配置项
3 | * 依赖预构建,`vite` 启动时会将下面 include 里的模块,编译成 esm 格式并缓存到 node_modules/.vite 文件夹,页面加载到对应模块时如果浏览器有缓存就读取浏览器缓存,如果没有会读取本地缓存并按需加载
4 | * 尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include里,否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存
5 | * 温馨提示:如果您使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite
6 | */
7 | const include = [
8 | "qs",
9 | "mitt",
10 | "xlsx",
11 | "axios",
12 | "pinia",
13 | "echarts",
14 | "vue-i18n",
15 | "sortablejs",
16 | "@vueuse/core",
17 | "@wangeditor/editor",
18 | "@wangeditor/editor-for-vue",
19 | "@wangeditor/plugin-upload-attachment",
20 | "countup",
21 | "file-saver",
22 | "pinia-plugin-persistedstate",
23 | "vue-clipboard3",
24 | "vue-cropper",
25 | "vue3-tree-org",
26 | "vuedraggable",
27 | "xgplayer",
28 | "vue3-menus",
29 | "v-contextmenu",
30 | "codemirror",
31 | "tinymce",
32 | "@tinymce/tinymce-vue",
33 | "v-code-diff",
34 | ];
35 |
36 | /**
37 | * 在预构建中强制排除的依赖项
38 | */
39 | const exclude = ["@iconify-icons/ant-design"];
40 |
41 | export { include, exclude };
42 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | "postcss-import": {},
4 | autoprefixer: {},
5 | ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}),
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/public/favicon.ico
--------------------------------------------------------------------------------
/public/tinymce/plugins/axupimgs/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/public/tinymce/plugins/axupimgs/loading.gif
--------------------------------------------------------------------------------
/public/tinymce/plugins/help/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "help" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/help')
5 | // ES2015:
6 | // import 'tinymce/plugins/help'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/tinymce/skins/content/dark/content.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('content/dark/content.css', "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 | //# sourceMappingURL=content.js.map
3 |
--------------------------------------------------------------------------------
/public/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 |
--------------------------------------------------------------------------------
/public/tinymce/skins/content/default/content.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('content/default/content.css', "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 | //# sourceMappingURL=content.js.map
3 |
--------------------------------------------------------------------------------
/public/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/tinymce/skins/content/document/content.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('content/document/content.css', "@media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}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 figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.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 | //# sourceMappingURL=content.js.map
3 |
--------------------------------------------------------------------------------
/public/tinymce/skins/content/document/content.min.css:
--------------------------------------------------------------------------------
1 | @media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}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 figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.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/tinymce/skins/content/tinymce-5-dark/content.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('content/tinymce-5-dark/content.css', "body{background-color:#2f3742;color:#dfe0e4;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 | //# sourceMappingURL=content.js.map
3 |
--------------------------------------------------------------------------------
/public/tinymce/skins/content/tinymce-5-dark/content.min.css:
--------------------------------------------------------------------------------
1 | body{background-color:#2f3742;color:#dfe0e4;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 |
--------------------------------------------------------------------------------
/public/tinymce/skins/content/tinymce-5/content.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('content/tinymce-5/content.css', "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 | //# sourceMappingURL=content.js.map
3 |
--------------------------------------------------------------------------------
/public/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 |
--------------------------------------------------------------------------------
/public/tinymce/skins/content/writer/content.js:
--------------------------------------------------------------------------------
1 | tinymce.Resource.add('content/writer/content.css', "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 | //# sourceMappingURL=content.js.map
3 |
--------------------------------------------------------------------------------
/public/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/tinymce/skins/ui/oxide-dark/skin.shadowdom.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll {
2 | overflow: hidden;
3 | }
4 | .tox-fullscreen {
5 | border: 0;
6 | height: 100%;
7 | margin: 0;
8 | overflow: hidden;
9 | overscroll-behavior: none;
10 | padding: 0;
11 | touch-action: pinch-zoom;
12 | width: 100%;
13 | }
14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {
15 | display: none;
16 | }
17 | .tox.tox-tinymce.tox-fullscreen,
18 | .tox-shadowhost.tox-fullscreen {
19 | left: 0;
20 | position: fixed;
21 | top: 0;
22 | z-index: 1200;
23 | }
24 | .tox.tox-tinymce.tox-fullscreen {
25 | background-color: transparent;
26 | }
27 | .tox-fullscreen .tox.tox-tinymce-aux,
28 | .tox-fullscreen ~ .tox.tox-tinymce-aux {
29 | z-index: 1201;
30 | }
31 |
--------------------------------------------------------------------------------
/public/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/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/tinymce/skins/ui/oxide/skin.shadowdom.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll {
2 | overflow: hidden;
3 | }
4 | .tox-fullscreen {
5 | border: 0;
6 | height: 100%;
7 | margin: 0;
8 | overflow: hidden;
9 | overscroll-behavior: none;
10 | padding: 0;
11 | touch-action: pinch-zoom;
12 | width: 100%;
13 | }
14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {
15 | display: none;
16 | }
17 | .tox.tox-tinymce.tox-fullscreen,
18 | .tox-shadowhost.tox-fullscreen {
19 | left: 0;
20 | position: fixed;
21 | top: 0;
22 | z-index: 1200;
23 | }
24 | .tox.tox-tinymce.tox-fullscreen {
25 | background-color: transparent;
26 | }
27 | .tox-fullscreen .tox.tox-tinymce-aux,
28 | .tox-fullscreen ~ .tox.tox-tinymce-aux {
29 | z-index: 1201;
30 | }
31 |
--------------------------------------------------------------------------------
/public/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 |
--------------------------------------------------------------------------------
/public/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 |
--------------------------------------------------------------------------------
/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll {
2 | overflow: hidden;
3 | }
4 | .tox-fullscreen {
5 | border: 0;
6 | height: 100%;
7 | margin: 0;
8 | overflow: hidden;
9 | overscroll-behavior: none;
10 | padding: 0;
11 | touch-action: pinch-zoom;
12 | width: 100%;
13 | }
14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {
15 | display: none;
16 | }
17 | .tox.tox-tinymce.tox-fullscreen,
18 | .tox-shadowhost.tox-fullscreen {
19 | left: 0;
20 | position: fixed;
21 | top: 0;
22 | z-index: 1200;
23 | }
24 | .tox.tox-tinymce.tox-fullscreen {
25 | background-color: transparent;
26 | }
27 | .tox-fullscreen .tox.tox-tinymce-aux,
28 | .tox-fullscreen ~ .tox.tox-tinymce-aux {
29 | z-index: 1201;
30 | }
31 |
--------------------------------------------------------------------------------
/public/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 |
--------------------------------------------------------------------------------
/public/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 |
--------------------------------------------------------------------------------
/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.css:
--------------------------------------------------------------------------------
1 | body.tox-dialog__disable-scroll {
2 | overflow: hidden;
3 | }
4 | .tox-fullscreen {
5 | border: 0;
6 | height: 100%;
7 | margin: 0;
8 | overflow: hidden;
9 | overscroll-behavior: none;
10 | padding: 0;
11 | touch-action: pinch-zoom;
12 | width: 100%;
13 | }
14 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {
15 | display: none;
16 | }
17 | .tox.tox-tinymce.tox-fullscreen,
18 | .tox-shadowhost.tox-fullscreen {
19 | left: 0;
20 | position: fixed;
21 | top: 0;
22 | z-index: 1200;
23 | }
24 | .tox.tox-tinymce.tox-fullscreen {
25 | background-color: transparent;
26 | }
27 | .tox-fullscreen .tox.tox-tinymce-aux,
28 | .tox-fullscreen ~ .tox.tox-tinymce-aux {
29 | z-index: 1201;
30 | }
31 |
--------------------------------------------------------------------------------
/public/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 |
--------------------------------------------------------------------------------
/public/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/api/menu.ts:
--------------------------------------------------------------------------------
1 | // import http from "@/request";
2 |
3 | export interface BackstageMenuList {
4 | imageIcon: string;
5 | menuCode: string;
6 | pagePath: string;
7 | menuName: string;
8 | menuUrl: string;
9 | parentMenuCode: string;
10 | seq: number;
11 | children?: BackstageMenuList[];
12 | }
13 |
14 | export const getMenuList = () => {
15 | // 模拟请求菜单
16 | return Promise.resolve([] as BackstageMenuList[]);
17 | // return http.request>({
18 | // url: "/getMenuList",
19 | // method: "get",
20 | // });
21 | };
22 |
--------------------------------------------------------------------------------
/src/api/user.ts:
--------------------------------------------------------------------------------
1 | import type { UserInfo } from "@/stores";
2 | import request from "@/request";
3 |
4 | export interface LoginParams {
5 | username: string;
6 | password: string;
7 | verifyCode?: string;
8 | }
9 |
10 | export interface Token {
11 | accessToken: string;
12 | refreshToken: string;
13 | }
14 |
15 | export const UserService = {
16 | // 登录
17 | login(params: LoginParams) {
18 | return request.post>("/auth/login", params);
19 | },
20 |
21 | // 获取用户信息
22 | getUserInfo() {
23 | return request.get>("/auth/getUserInfo");
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: iconfont; /* Project id 3129839 */
3 | src: url("iconfont.woff2?t=1691942317112") format("woff2"), url("iconfont.woff?t=1691942317112") format("woff"),
4 | url("iconfont.ttf?t=1691942317112") format("truetype");
5 | }
6 |
7 | .iconfont {
8 | font-family: iconfont !important;
9 | font-size: 16px;
10 | font-style: normal;
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | }
14 |
15 | .icon-dagouyouquan::before {
16 | content: "\e630";
17 | }
18 |
19 | .icon-gantanhao::before {
20 | content: "\e710";
21 | }
22 |
23 | .icon-info::before {
24 | content: "\e675";
25 | }
26 |
27 | .icon-cuowu::before {
28 | content: "\e7fa";
29 | }
30 |
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "3129839",
3 | "name": "tip-ui",
4 | "font_family": "iconfont",
5 | "css_prefix_text": "icon-",
6 | "description": "",
7 | "glyphs": [
8 | {
9 | "icon_id": "1166351",
10 | "name": "打勾_有圈",
11 | "font_class": "dagouyouquan",
12 | "unicode": "e630",
13 | "unicode_decimal": 58928
14 | },
15 | {
16 | "icon_id": "9974757",
17 | "name": "感叹号",
18 | "font_class": "gantanhao",
19 | "unicode": "e710",
20 | "unicode_decimal": 59152
21 | },
22 | {
23 | "icon_id": "11686796",
24 | "name": "info",
25 | "font_class": "info",
26 | "unicode": "e675",
27 | "unicode_decimal": 58997
28 | },
29 | {
30 | "icon_id": "12148932",
31 | "name": "错误",
32 | "font_class": "cuowu",
33 | "unicode": "e7fa",
34 | "unicode_decimal": 59386
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/iconfont/iconfont.woff2
--------------------------------------------------------------------------------
/src/assets/icons/core/bug.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/core/command.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/core/fullscreen-exit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/core/fullscreen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/core/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/login/qq.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/login/wechat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/default.png
--------------------------------------------------------------------------------
/src/assets/images/login/login_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/login/login_left.png
--------------------------------------------------------------------------------
/src/assets/images/login/login_left0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/login/login_left0.png
--------------------------------------------------------------------------------
/src/assets/images/login/login_left1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/login/login_left1.png
--------------------------------------------------------------------------------
/src/assets/images/login/login_left2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/login/login_left2.png
--------------------------------------------------------------------------------
/src/assets/images/login/login_left3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/login/login_left3.png
--------------------------------------------------------------------------------
/src/assets/images/login/login_left4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/login/login_left4.png
--------------------------------------------------------------------------------
/src/assets/images/login/login_left5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/login/login_left5.png
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/menu-theme/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/menu-theme/dark.png
--------------------------------------------------------------------------------
/src/assets/images/menu-theme/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/menu-theme/light.png
--------------------------------------------------------------------------------
/src/assets/images/msg/msg01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/msg/msg01.png
--------------------------------------------------------------------------------
/src/assets/images/msg/msg02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/msg/msg02.png
--------------------------------------------------------------------------------
/src/assets/images/msg/msg03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/msg/msg03.png
--------------------------------------------------------------------------------
/src/assets/images/msg/msg04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/msg/msg04.png
--------------------------------------------------------------------------------
/src/assets/images/msg/msg05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/msg/msg05.png
--------------------------------------------------------------------------------
/src/assets/images/notData.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/notData.png
--------------------------------------------------------------------------------
/src/assets/images/status/403.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/status/403.png
--------------------------------------------------------------------------------
/src/assets/images/status/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/status/404.png
--------------------------------------------------------------------------------
/src/assets/images/status/500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/status/500.png
--------------------------------------------------------------------------------
/src/assets/images/system-theme/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/system-theme/dark.png
--------------------------------------------------------------------------------
/src/assets/images/system-theme/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/system-theme/light.png
--------------------------------------------------------------------------------
/src/assets/images/system-theme/system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kele-Bingtang/kbt-vue3-admin/8c62c4ade081ae7ddb81db63dc11e41fa9774ca2/src/assets/images/system-theme/system.png
--------------------------------------------------------------------------------
/src/components/core/icon/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 | import SvgIcon from "./src/components/svg-icon.vue";
4 | import FontIcon from "./src/components/font-icon.vue";
5 | import IconifyOnline from "./src/components/iconify-online.vue";
6 | import IconifyOffline from "./src/components/iconify-offline.vue";
7 |
8 | export { SvgIcon, FontIcon, IconifyOnline, IconifyOffline };
9 |
10 | export * from "./src/icon";
11 |
12 | export const Icon = useInstall(index);
13 |
14 | export default index;
15 |
--------------------------------------------------------------------------------
/src/components/core/icon/src/components/font-icon.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | {{ icon }}
9 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/core/icon/src/components/iconify-offline.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/core/icon/src/components/iconify-online.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/core/icon/src/components/svg-icon.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
28 |
--------------------------------------------------------------------------------
/src/components/core/icon/src/icon.ts:
--------------------------------------------------------------------------------
1 | import type { Component } from "vue";
2 | import type { IconifyIcon } from "@iconify/vue";
3 |
4 | export interface IconProps {
5 | /**
6 | * 图标
7 | */
8 | icon?: string | Object | Component | IconifyIcon;
9 | /**
10 | * 图标类型
11 | */
12 | iconType?: "svg" | "unicode" | "iconfont" | "symbol" | "img" | "component" | "iconifyOffline" | "iconifyOnline";
13 | /**
14 | * 图标大小
15 | *
16 | * @default 'inherit'
17 | */
18 | size?: string | number;
19 | /**
20 | * 图标颜色
21 | *
22 | * @default 'inherit'
23 | */
24 | color?: string;
25 | /**
26 | * 图标是否可悬停
27 | *
28 | * @default false
29 | */
30 | hover?: boolean;
31 | /**
32 | * 图标悬停时的颜色,仅当 hover 为 true 时有效
33 | */
34 | hoverColor?: string;
35 | /**
36 | * img 标签的 alt,当 iconType 为 img 时生效
37 | */
38 | imgAlt?: string;
39 | /**
40 | * 是否使用鼠标手形
41 | */
42 | pointer?: boolean;
43 | /**
44 | * 自定义图标样式
45 | */
46 | style?: Record;
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/core/image-verify-code/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const ImageVerifyCode = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/core/image-verify-code/src/index.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/core/pagination/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index, { pageSetting, type Paging } from "./src/index.vue";
3 |
4 | export { pageSetting, type Paging };
5 |
6 | export const Pagination = useInstall(index);
7 |
8 | export default index;
9 |
--------------------------------------------------------------------------------
/src/components/core/pagination/src/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include b(pagination) {
5 | padding: 15px 2px 0;
6 | background: #ffffff;
7 |
8 | @include is(hidden) {
9 | display: none;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/core/permission/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import role from "./src/role.vue";
3 | import auth from "./src/auth.vue";
4 |
5 | export const Role = useInstall(role);
6 | export const Auth = useInstall(auth);
7 |
8 | export default [auth, role];
9 |
--------------------------------------------------------------------------------
/src/components/core/permission/src/auth.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/core/permission/src/role.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/core/switch-dark/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const SwitchDark = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/core/switch-dark/src/index.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/core/tooltip/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const Tooltip = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/core/use-dialog/index.ts:
--------------------------------------------------------------------------------
1 | import { initDialog, closeDialog, type WorkDialogProps } from "./src/index";
2 | import { getCurrentInstance, type ComponentInternalInstance } from "vue";
3 | import WorkDialog from "./src/index.vue";
4 |
5 | export { WorkDialog, type WorkDialogProps };
6 |
7 | export const useDialog = (ctx?: any) => {
8 | const thisAppContext = ctx || (getCurrentInstance() as ComponentInternalInstance);
9 | const { showDialog } = initDialog(thisAppContext);
10 |
11 | return { open: showDialog, close: closeDialog };
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/core/use-dialog/src/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include b(work-dialog) {
5 | &.#{$el-namespace}-dialog.is-fullscreen {
6 | --#{$el-namespace}-dialog-width: 100% !important;
7 | --#{$el-namespace}-dialog-margin-top: 0 !important;
8 |
9 | height: 100%;
10 | margin-bottom: 0;
11 | overflow: auto;
12 | }
13 |
14 | .tk-icon {
15 | margin-top: -7px;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/core/use-drawer/index.ts:
--------------------------------------------------------------------------------
1 | import { initDrawer, closeDrawer, type WorkDrawerProps } from "./src/index";
2 | import { getCurrentInstance, type ComponentInternalInstance } from "vue";
3 | import WorkDrawer from "./src/index.vue";
4 |
5 | export { WorkDrawer, type WorkDrawerProps };
6 |
7 | export const useDrawer = (ctx?: any) => {
8 | const thisAppContext = ctx || (getCurrentInstance() as ComponentInternalInstance);
9 | const { showDrawer } = initDrawer(thisAppContext);
10 |
11 | return { open: showDrawer, close: closeDrawer };
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/core/use-drawer/src/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | $prefix-class: #{$admin-namespace}-work-drawer;
5 |
6 | @include b(work-drawer) {
7 | &.#{$el-namespace}-drawer.is-fullscreen {
8 | width: 100% !important;
9 | height: 100%;
10 | margin-bottom: 0;
11 | overflow: auto;
12 | }
13 | }
14 |
15 | .#{$prefix-class} .#{$prefix-class}.#{$el-namespace}-drawer {
16 | .#{$el-namespace}-drawer__header {
17 | padding: 15px 20px 14px;
18 | margin-bottom: 0;
19 | border-bottom: 1px solid var(--#{$el-namespace}-border-color-lighter);
20 | }
21 |
22 | .#{$el-namespace}-drawer__footer {
23 | border-top: 1px solid var(--#{$el-namespace}-border-color-lighter);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/editor/code-mirror/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index, { type MergeCodeMirrorProps, type CodeMirrorProps } from "./src/index.vue";
3 |
4 | export { type MergeCodeMirrorProps, type CodeMirrorProps };
5 |
6 | export const CodeMirror = useInstall(index);
7 |
8 | export default index;
9 |
--------------------------------------------------------------------------------
/src/components/editor/code-mirror/src/codeMirror5/mode.ts:
--------------------------------------------------------------------------------
1 | // 编辑器代码格式
2 | import "codemirror/mode/javascript/javascript.js";
3 | import "codemirror/mode/htmlmixed/htmlmixed.js";
4 | import "codemirror/mode/css/css.js";
5 | import "codemirror/mode/vue/vue.js";
6 | import "codemirror/mode/yaml/yaml.js";
7 | import "codemirror/mode/xml/xml.js";
8 | import "codemirror/mode/sql/sql.js";
9 | import "codemirror/mode/python/python.js";
10 | import "codemirror/mode/markdown/markdown.js";
11 | import "codemirror/mode/shell/shell.js";
12 | import "codemirror/mode/clike/clike.js";
13 |
--------------------------------------------------------------------------------
/src/components/editor/tinymce/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index, { type UITheme } from "./src/index.vue";
3 |
4 | export { type UITheme };
5 |
6 | export const Tinymce = useInstall(index);
7 |
8 | export default index;
9 |
--------------------------------------------------------------------------------
/src/components/editor/tinymce/src/config.ts:
--------------------------------------------------------------------------------
1 | // Import plugins that you want to use
2 | // Detail plugins list see: https://www.tiny.cloud/apps/#core-plugins
3 | // Custom builds see: https://www.tiny.cloud/get-tiny/custom-builds/
4 | export const plugins =
5 | "accordion advlist anchor autolink autosave charmap code codesample directionality emoticons fullscreen help image axupimgs insertdatetime link lists media nonbreaking pagebreak preview save searchreplace table template visualblocks visualchars wordcount";
6 |
7 | // Here is the list of toolbar control components
8 | // Details see: https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
9 | export const toolbar =
10 | "undo redo | bold italic underline strikethrough | fontfamily fontsize blocks | alignleft aligncenter alignright alignjustify | outdent indent | accordion numlist bullist checklist | forecolor backcolor removeformat | blockquote subscript superscript | emoticons charmap | link image axupimgs media | pageembed template anchor code codesample | hr pagebreak | insertdatetime table | fullscreen preview save print searchreplace | ltr rtl";
11 |
--------------------------------------------------------------------------------
/src/components/editor/wang-editor/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index, { type FileInsertFnType, type ImageInsertFnType, type VideoInsertFnType } from "./src/index.vue";
3 |
4 | export { type FileInsertFnType, type ImageInsertFnType, type VideoInsertFnType };
5 |
6 | export const WangEditor = useInstall(index);
7 |
8 | export default index;
9 |
--------------------------------------------------------------------------------
/src/components/pro/form-item/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 | import Tree from "./src/components/tree.vue";
4 |
5 | export type { ProFormItemProps } from "./src/index.vue";
6 | export * from "./src/types";
7 | export * from "./src/helper";
8 |
9 | export { Tree };
10 |
11 | export const ProFormItem = useInstall(index);
12 |
13 | export default index;
14 |
--------------------------------------------------------------------------------
/src/components/pro/form-item/src/components/checkbox.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/pro/form-item/src/components/radio.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/pro/form-item/src/components/select.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
23 |
24 |
25 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/components/pro/form-item/src/helper/index.ts:
--------------------------------------------------------------------------------
1 | import { isFunction, isObject, isPromise } from "@/utils";
2 | import { isRef, type Reactive } from "vue";
3 |
4 | /**
5 | * 将连字符转换为大驼峰格式
6 | */
7 | export const hyphenToCamelCase = (val?: string) => {
8 | // 如果字符串中不包含连字符,直接返回
9 | if (!val || val.indexOf("-") === -1) return val || "";
10 |
11 | // 使用 split 方法按连字符分割字符串
12 | const parts = val.split("-");
13 | // 使用 map 对每个部分进行转换,首字母大写,其余部分小写
14 | const pascalCasedParts = parts.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase());
15 |
16 | // 使用 join 方法将转换后的部分合并成新的字符串
17 | return pascalCasedParts.join("");
18 | };
19 |
20 | /**
21 | * 格式化值
22 | */
23 | export const formatValue = async (
24 | value: T | Promise | Ref | Reactive | ComputedRef | ((...arg: any) => Promise),
25 | params: any[] = []
26 | ): Promise => {
27 | if (value === undefined) return value;
28 |
29 | if (isRef(value)) return unref(value);
30 | if (isFunction(value)) return await (value as any)(...params);
31 | if (isObject(value)) return { ...value };
32 | if (isPromise(value)) return await value;
33 |
34 | return value;
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/pro/form/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 | export type { ProFormProps, ProFormOnEmits } from "./src/index.vue";
4 |
5 | export * from "./src/types";
6 | export * from "./src/helper";
7 | export { useProForm } from "./src/composables/use-form";
8 |
9 | export const ProForm = useInstall(index);
10 |
11 | export default index;
12 |
--------------------------------------------------------------------------------
/src/components/pro/grid/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 | import gridItem from "./src/components/grid-item.vue";
4 |
5 | export { type GridProps, type BreakPoint, type Responsive } from "./src/index.vue";
6 | export { type GridItemProps } from "./src/components/grid-item.vue";
7 | export const Grid = useInstall(index);
8 | export const GridItem = useInstall(gridItem);
9 |
10 | export type GridInstance = InstanceType;
11 |
12 | export default index;
13 |
--------------------------------------------------------------------------------
/src/components/pro/image-viewer/index.ts:
--------------------------------------------------------------------------------
1 | import ImageViewer from "./src/index.vue";
2 | import { createVNode, render, type VNode } from "vue";
3 | import type { ImageViewerProps } from "./src/index.vue";
4 |
5 | export { ImageViewer };
6 |
7 | let instance: VNode | null = null;
8 |
9 | export const createImageViewer = (options: ImageViewerProps & { show?: boolean }) => {
10 | if (typeof window === "undefined") return;
11 | const {
12 | urlList,
13 | initialIndex = 0,
14 | infinite = true,
15 | hideOnClickModal = false,
16 | teleported = false,
17 | zIndex = 2000,
18 | show = true,
19 | } = options;
20 |
21 | const propsData: Partial = {};
22 | const container = document.createElement("div");
23 | propsData.urlList = urlList;
24 | propsData.initialIndex = initialIndex;
25 | propsData.infinite = infinite;
26 | propsData.hideOnClickModal = hideOnClickModal;
27 | propsData.teleported = teleported;
28 | propsData.zIndex = zIndex;
29 | propsData.modelValue = show;
30 |
31 | document.body.appendChild(container);
32 | instance = createVNode(ImageViewer, propsData);
33 | render(instance, container);
34 | };
35 |
--------------------------------------------------------------------------------
/src/components/pro/pro-form/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 | export type { ProFormProps, ProFormOnEmits } from "./src/index.vue";
4 | import Tree from "./src/components/tree.vue";
5 | import ProFormItem from "./src/components/pro-form-item.vue";
6 |
7 | export * from "./src/interface";
8 | export * from "./src/helper";
9 | export { useProForm } from "./src/hooks/use-pro-form";
10 |
11 | export { Tree, ProFormItem };
12 |
13 | export const ProForm = useInstall(index);
14 |
15 | export default index;
16 |
--------------------------------------------------------------------------------
/src/components/pro/pro-form/src/components/use-render-checkbox.tsx:
--------------------------------------------------------------------------------
1 | import { ElCheckbox, ElCheckboxButton } from "element-plus";
2 | import { ComponentNameEnum, type FormFieldNamesProps, type FormColumnProps } from "../interface";
3 | import { hyphenToCamelCase } from "../helper";
4 |
5 | export const useRenderCheckbox = () => {
6 | const renderCheckboxOptions = (
7 | columnEnum: Record[],
8 | fieldNames: FormFieldNamesProps,
9 | column: FormColumnProps
10 | ) => {
11 | const Component =
12 | hyphenToCamelCase(column.el) === ComponentNameEnum.EL_CHECKBOX_BUTTON ? ElCheckboxButton : ElCheckbox;
13 | return columnEnum.map((col: any) => {
14 | return (
15 |
21 | );
22 | });
23 | };
24 |
25 | return {
26 | renderCheckboxOptions,
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/src/components/pro/pro-form/src/components/use-render-component.tsx:
--------------------------------------------------------------------------------
1 | import type { FormColumnProps } from "@/components";
2 | import { getProp, hyphenToCamelCase, setProp } from "../helper";
3 | import type { ComputedRef, ModelRef, defineComponent } from "vue";
4 | import { componentMap } from "../helper/component-map";
5 | import type { FormFieldNamesProps, PascalCaseComponentName } from "../interface";
6 |
7 | export const useRenderComponent = (
8 | model: ModelRef, string>,
9 | componentProps: ComputedRef>,
10 | column: FormColumnProps,
11 | columnEnum: Record[],
12 | fieldNames: FormFieldNamesProps
13 | ) => {
14 | const renderComponent = () => {
15 | // 注册自定义的组件
16 | const Component = componentMap[hyphenToCamelCase(column.el) as PascalCaseComponentName] as ReturnType<
17 | typeof defineComponent
18 | >;
19 |
20 | return (
21 | setProp(model.value, column.prop, v)}
24 | {...componentProps.value}
25 | enum={columnEnum}
26 | fieldNames={fieldNames}
27 | >
28 | );
29 | };
30 |
31 | return { renderComponent };
32 | };
33 |
--------------------------------------------------------------------------------
/src/components/pro/pro-form/src/components/use-render-radio.tsx:
--------------------------------------------------------------------------------
1 | import { ElRadio, ElRadioButton } from "element-plus";
2 | import { ComponentNameEnum, type FormFieldNamesProps, type FormColumnProps } from "../interface";
3 | import { hyphenToCamelCase } from "../helper";
4 |
5 | export const useRenderRadio = () => {
6 | const renderRadioOptions = (
7 | columnEnum: Record[],
8 | fieldNames: FormFieldNamesProps,
9 | column: FormColumnProps
10 | ) => {
11 | const Component = hyphenToCamelCase(column.el) === ComponentNameEnum.EL_RADIO_BUTTON ? ElRadioButton : ElRadio;
12 |
13 | return columnEnum.map(col => {
14 | return (
15 |
21 | );
22 | });
23 | };
24 |
25 | return {
26 | renderRadioOptions,
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/src/components/pro/pro-form/src/components/use-render-select.tsx:
--------------------------------------------------------------------------------
1 | import { ElOption, ElOptionGroup } from "element-plus";
2 | import type { FormFieldNamesProps } from "../interface";
3 |
4 | export const useRenderSelect = () => {
5 | // 渲染 select options
6 | const renderSelectOptions = (columnEnum: Record[], fieldNames: FormFieldNamesProps) => {
7 | return columnEnum.map(col => {
8 | if (col?.options?.length) {
9 | return (
10 |
11 | {{
12 | default: () => col.options.map(option => renderSelectOptionItem(option, fieldNames)),
13 | }}
14 |
15 | );
16 | }
17 |
18 | return renderSelectOptionItem(col, fieldNames);
19 | });
20 | };
21 |
22 | const renderSelectOptionItem = (col: Record, fieldNames: FormFieldNamesProps) => {
23 | return (
24 |
30 | );
31 | };
32 |
33 | return {
34 | renderSelectOptions,
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/src/components/pro/pro-form/src/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | // #{$el-namespace} 默认为 el,如果组件迁移到其他项目,且项目架构与此项目不同,则请修改 #{$el-namespace} 为 el
5 | @include b(pro-form) {
6 | &.#{$el-namespace}-form {
7 | width: 100%;
8 | margin-top: 10px;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/pro/pro-search/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 | export type {
4 | ProSearchProps,
5 | ProSearchSchemaProps,
6 | ActionPosition,
7 | ProSearchExpose,
8 | ProSearchOnEmits,
9 | } from "./src/index.vue";
10 |
11 | export { useProSearch } from "./src/hooks/use-pro-search";
12 |
13 | export const ProSearch = useInstall(index);
14 |
15 | export type ProSearchInstance = InstanceType;
16 |
17 | export default index;
18 |
--------------------------------------------------------------------------------
/src/components/pro/pro-steps/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export { type Step } from "./src/index.vue";
5 |
6 | export const ProSteps = useInstall(index);
7 |
8 | export default index;
9 |
--------------------------------------------------------------------------------
/src/components/pro/pro-table/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 | import tableMain from "./src/components/table-main.vue";
4 |
5 | export * from "./src/interface";
6 | export * from "./src/helper";
7 | export { type DialogForm, type ProTableOnEmits } from "./src/index.vue";
8 | export { type DialogFormColumnProps } from "./src/components/dialog-form.vue";
9 | export { useProTable } from "./src/hooks/use-pro-table";
10 |
11 | export const ProTable = useInstall(index);
12 | export const TableMain = useInstall(tableMain);
13 |
14 | export default index;
15 |
--------------------------------------------------------------------------------
/src/components/pro/pro-table/src/hooks/use-selection.ts:
--------------------------------------------------------------------------------
1 | import { ref, computed } from "vue";
2 |
3 | /**
4 | * @description 表格多选数据操作
5 | * @param {String} rowKey 当表格可以多选时,所指定的 id
6 | * */
7 | export const useSelection = (rowKey: string = "id") => {
8 | const isSelected = ref(false);
9 | const selectedList = ref[]>([]);
10 |
11 | // 当前选中的所有 ids 数组
12 | const selectedListIds = computed((): string[] => {
13 | const ids: string[] = [];
14 | selectedList.value.forEach(item => ids.push(item[rowKey]));
15 | return ids;
16 | });
17 |
18 | /**
19 | * @description 多选操作
20 | * @param {Array} rowArr 当前选择的所有数据
21 | * @return void
22 | */
23 | const selectionChange = (rowArr: Record[]) => {
24 | rowArr.length ? (isSelected.value = true) : (isSelected.value = false);
25 | selectedList.value = rowArr;
26 | };
27 |
28 | return {
29 | isSelected,
30 | selectedList,
31 | selectedListIds,
32 | selectionChange,
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/src/components/pro/pro-transfer/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 | import table from "./src/table.vue";
4 |
5 | export const ProTransfer = useInstall(index);
6 | export const ProTableTransfer = useInstall(table);
7 |
8 | export default index;
9 |
--------------------------------------------------------------------------------
/src/components/pro/tree-filter/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const TreeFilter = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/pro/tree-filter/src/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include e(tree-filter) {
5 | box-sizing: border-box;
6 | width: 220px;
7 | height: 100%;
8 | padding: 18px;
9 | margin-right: 10px;
10 |
11 | @include e(title) {
12 | margin: 0 0 15px;
13 | font-size: 18px;
14 | font-weight: bold;
15 | color: var(--#{$el-namespace}-color-info-dark-2);
16 | letter-spacing: 0.5px;
17 | }
18 |
19 | .#{$el-namespace}-input {
20 | margin: 0 0 15px;
21 | }
22 |
23 | .#{$el-namespace}-scrollbar {
24 | :deep(.#{$el-namespace}-tree) {
25 | height: 80%;
26 | overflow: auto;
27 |
28 | .#{$el-namespace}-tree-node__content {
29 | height: 33px;
30 | }
31 | }
32 |
33 | :deep(.#{$el-namespace}-tree--highlight-current) {
34 | .#{$el-namespace}-tree-node.is-current > .#{$el-namespace}-tree-node__content {
35 | background-color: var(--#{$el-namespace}-color-primary);
36 |
37 | .#{$el-namespace}-tree-node__label,
38 | .#{$el-namespace}-tree-node__expand-icon {
39 | color: white;
40 | }
41 |
42 | .is-leaf {
43 | color: transparent;
44 | }
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/view/count-to/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const CountTo = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/cropper/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const Cropper = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/drag-drawer/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const DragDrawer = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/drag-drawer/src/drag-drawer-trigger.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
--------------------------------------------------------------------------------
/src/components/view/draggable-item/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const DraggableItem = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/draggable-list/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const DraggableList = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/excel-upload/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const UploadExcel = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/flicker/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./src/index";
2 |
--------------------------------------------------------------------------------
/src/components/view/flicker/src/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include b(point-flicker) {
5 | position: relative;
6 | width: var(--point-width);
7 | height: var(--point-height);
8 | background: var(--point-background);
9 | border-radius: var(--point-border-radius);
10 |
11 | &::after {
12 | background: var(--point-background);
13 | }
14 |
15 | &::before,
16 | &::after {
17 | position: absolute;
18 | top: 0;
19 | left: 0;
20 | width: 100%;
21 | height: 100%;
22 | content: "";
23 | border-radius: var(--point-border-radius);
24 | animation: flicker 1.2s ease-out infinite;
25 | }
26 |
27 | @keyframes flicker {
28 | 0% {
29 | opacity: 1;
30 | transform: scale(0.5);
31 | }
32 |
33 | 30% {
34 | opacity: 1;
35 | }
36 |
37 | 100% {
38 | opacity: 0;
39 | transform: scale(var(--point-scale));
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/view/flicker/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, h } from "vue";
2 | import "./index.scss";
3 | import { useNamespace } from "@/composables";
4 |
5 | const ns = useNamespace("point-flicker");
6 |
7 | export interface attrsType {
8 | width?: string; // 可选 string 宽
9 | height?: string; // 可选 string 高
10 | borderRadius?: number | string; // 可选 number | string 传0为方形、传50%或者不传为圆形
11 | background?: string; // 可选 string 闪烁颜色
12 | scale?: number | string; // 可选 number | string 闪烁范围,默认2,值越大闪烁范围越大
13 | }
14 |
15 | /**
16 | * 圆点、方形闪烁动画组件
17 | */
18 | export const useFlicker = (attrs?: attrsType): Component => {
19 | return defineComponent({
20 | name: "Flicker",
21 | render() {
22 | return h(
23 | "div",
24 | {
25 | class: `${ns.b()}`,
26 | style: {
27 | "--point-width": attrs?.width ?? "12px",
28 | "--point-height": attrs?.height ?? "12px",
29 | "--point-background": attrs?.background ?? `var(--${ns.elNamespace}-color-primary)`,
30 | "--point-border-radius": attrs?.borderRadius ?? "50%",
31 | "--point-scale": attrs?.scale ?? "2",
32 | },
33 | },
34 | {
35 | default: () => [],
36 | }
37 | );
38 | },
39 | });
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/view/highlight/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const Highlight = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/icon-picker/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const IconPicker = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/icon-picker/src/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include b(icon-picker) {
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | }
9 |
10 | @include b(icon-picker) {
11 | @include e(icons) {
12 | box-sizing: border-box;
13 | display: flex;
14 | flex-wrap: wrap;
15 |
16 | @include e(icon) {
17 | box-sizing: border-box;
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | margin: 2px;
22 | cursor: pointer;
23 | transition: all 0.3s ease 0s;
24 |
25 | &:hover {
26 | border-color: var(--#{$el-namespace}-color-primary) !important;
27 | }
28 | }
29 | }
30 |
31 | @include e(pagination) {
32 | position: absolute;
33 | bottom: 0;
34 | left: 0;
35 | display: flex;
36 | align-items: center;
37 | height: 50px;
38 | padding-right: var(--#{$el-namespace}-popover-padding);
39 | padding-left: var(--#{$el-namespace}-popover-padding);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/view/images-upload/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import image from "./src/image.vue";
3 | import images from "./src/images.vue";
4 |
5 | export const ImageUpload = useInstall(image);
6 | export const ImagesUpload = useInstall(images);
7 |
8 | export default [image, images];
9 |
--------------------------------------------------------------------------------
/src/components/view/material-input/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const MaterialInput = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/qr-code/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const QrCode = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/seamless-scroll/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const SeamlessScroll = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/split-pane/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const SplitPane = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/table-sort/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const TableSort = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/text-hover-effect/index.ts:
--------------------------------------------------------------------------------
1 | import { useInstall } from "@/utils";
2 | import index from "./src/index.vue";
3 |
4 | export const TextHoverEffect = useInstall(index);
5 |
6 | export default index;
7 |
--------------------------------------------------------------------------------
/src/components/view/video-player/index.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, render, type VNode } from "vue";
2 | import index from "./src/index.vue";
3 | import VideoPlayerViewer, { type VideoPlayerViewerProps } from "./src/video-player-viewer.vue";
4 | import { useSimpleUuid } from "@/utils";
5 | import { useInstall } from "@/utils";
6 |
7 | export const VideoPlayer = useInstall(index);
8 |
9 | export default index;
10 |
11 | export { VideoPlayerViewer };
12 |
13 | let instance: VNode | null = null;
14 |
15 | export const createVideoViewer = (options: { url: string; poster?: string; show?: boolean }) => {
16 | if (typeof window === "undefined") return;
17 | const { url, poster } = options;
18 |
19 | const propsData: Partial = {};
20 | const container = document.createElement("div");
21 | const id = useSimpleUuid();
22 | container.id = id;
23 | propsData.url = url;
24 | propsData.poster = poster;
25 | propsData.modelValue = true;
26 | propsData.id = id;
27 |
28 | document.body.appendChild(container);
29 | instance = createVNode(VideoPlayerViewer, propsData);
30 | render(instance, container);
31 | };
32 |
--------------------------------------------------------------------------------
/src/components/view/video-player/src/index.vue:
--------------------------------------------------------------------------------
1 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/composables/core/css-module/namespace.module.scss:
--------------------------------------------------------------------------------
1 | :export {
2 | // 命名空间
3 | namespace: $admin-namespace;
4 | elNamespace: $el-namespace;
5 | }
6 |
--------------------------------------------------------------------------------
/src/composables/core/css-module/namespace.module.scss.d.ts:
--------------------------------------------------------------------------------
1 | export interface ScssVariables {
2 | [x: string]: unknown;
3 | // 暗色主题
4 | namespace: string; // 自定义根节点 class 前缀
5 | elNamespace: string; // Element Plus 组件 class 前缀
6 | }
7 |
8 | export const variables: ScssVariables;
9 |
10 | export default variables;
11 |
--------------------------------------------------------------------------------
/src/composables/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./core/use-route-fn";
2 | export * from "./core/use-menu";
3 | export * from "./core/use-breadcrumb";
4 | export * from "./core/use-browser-title";
5 | export * from "./core/use-permission";
6 | export * from "./core/use-theme";
7 | export * from "./core/use-namespace";
8 | export * from "./core/use-storage";
9 | export * from "./core/use-cache";
10 | export * from "./core/use-watch-css-var";
11 | export * from "./core/use-upgrade";
12 |
13 | export * from "./use-boolean";
14 | export * from "./use-echarts";
15 | export * from "./use-confirm";
16 | export * from "./use-validator";
17 | export * from "./use-clipboard";
18 |
--------------------------------------------------------------------------------
/src/composables/use-boolean.ts:
--------------------------------------------------------------------------------
1 | import { ref } from "vue";
2 |
3 | export function useBoolean(initValue = false) {
4 | const bool = ref(initValue);
5 |
6 | function setBool(value: boolean) {
7 | bool.value = value;
8 | }
9 | function setTrue() {
10 | setBool(true);
11 | }
12 | function setFalse() {
13 | setBool(false);
14 | }
15 | function toggle() {
16 | setBool(!bool.value);
17 | }
18 |
19 | return {
20 | bool,
21 | setBool,
22 | setTrue,
23 | setFalse,
24 | toggle,
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/composables/use-clipboard.ts:
--------------------------------------------------------------------------------
1 | import { isClient } from "@/utils";
2 | import { ref } from "vue";
3 |
4 | /**
5 | * 复制文本到剪贴板
6 | */
7 | export const useClipboard = (timeout = 1500) => {
8 | const copied = ref(false);
9 | const text = ref("");
10 | const isSupported = ref(false);
11 |
12 | if (isClient && !!navigator.clipboard && !!document.execCommand) isSupported.value = true;
13 | else isSupported.value = true;
14 |
15 | const copy = async (str: string, size = -1) => {
16 | if (navigator.clipboard) {
17 | return await navigator.clipboard.writeText(str).then(() => {
18 | text.value = str;
19 | copied.value = true;
20 | resetCopied();
21 | });
22 | }
23 | const input = document.createElement("input");
24 | input.setAttribute("readonly", "readonly");
25 | input.setAttribute("value", str);
26 | document.body.appendChild(input);
27 | input.select();
28 | if (size > 0) input.setSelectionRange(0, size); // 限制选择内容大小
29 | if (document.execCommand("copy")) {
30 | text.value = str;
31 | document.execCommand("copy");
32 | copied.value = true;
33 | resetCopied();
34 | }
35 | document.body.removeChild(input);
36 | };
37 |
38 | const resetCopied = () => {
39 | setTimeout(() => {
40 | copied.value = false;
41 | }, timeout);
42 | };
43 |
44 | return { copy, text, copied, isSupported };
45 | };
46 |
47 | export type UseClipboardReturn = ReturnType;
48 |
--------------------------------------------------------------------------------
/src/composables/use-confirm.ts:
--------------------------------------------------------------------------------
1 | import { ElMessageBox, ElMessage } from "element-plus";
2 |
3 | /**
4 | * @description 操作单条数据信息 (二次确认【删除、禁用、启用、重置密码】)
5 | * @param {Function} api 操作数据接口的api方法 (必传)
6 | * @param {Object} params 携带的操作数据参数 {id,params} (必传)
7 | * @param {String} message 提示信息 (必传)
8 | * @param {String} confirmType icon类型 (不必传,默认为 warning)
9 | * @returns {Promise}
10 | */
11 | export const useConfirm = (
12 | fallback: () => void,
13 | message: string,
14 | confirmType: "" | "success" | "warning" | "info" | "error" = "warning"
15 | ) => {
16 | return new Promise(resolve => {
17 | ElMessageBox.confirm(`${message}?`, "温馨提示", {
18 | confirmButtonText: "确定",
19 | cancelButtonText: "取消",
20 | type: confirmType,
21 | draggable: true,
22 | }).then(async () => {
23 | fallback();
24 | ElMessage({
25 | type: "success",
26 | message: `${message}成功!`,
27 | });
28 | resolve(true);
29 | });
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/src/composables/use-echarts.ts:
--------------------------------------------------------------------------------
1 | import type * as echarts from "echarts";
2 | import { onActivated, onBeforeUnmount, onDeactivated } from "vue";
3 | import { useDebounceFn } from "@vueuse/core";
4 |
5 | /**
6 | * @description 使用 Echarts(只是为了添加图表响应式)
7 | * @param {Element} myChart Echarts实 例(必传)
8 | * @param {Object} options 绘制 Echarts 的参数(必传)
9 | * @return void
10 | * */
11 | export const useEcharts = (myChart: echarts.ECharts, options: echarts.EChartsCoreOption) => {
12 | if (options && typeof options === "object") {
13 | myChart.setOption(options);
14 | }
15 | const echartsResize = useDebounceFn(() => {
16 | myChart && myChart.resize();
17 | }, 100);
18 |
19 | window.addEventListener("resize", echartsResize);
20 |
21 | onBeforeUnmount(() => {
22 | window.removeEventListener("resize", echartsResize);
23 | myChart.dispose();
24 | });
25 |
26 | onActivated(() => {
27 | window.addEventListener("resize", echartsResize);
28 | });
29 |
30 | onDeactivated(() => {
31 | window.removeEventListener("resize", echartsResize);
32 | myChart.dispose();
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/src/config/constant.ts:
--------------------------------------------------------------------------------
1 | export const HOME_URL = "/home";
2 | export const HOME_NAME = "Home";
3 | export const LOGIN_URL = "/login";
4 | export const LOGIN_NAME = "Login";
5 | export const LAYOUT_NAME = "Layout";
6 | export const REDIRECT_NAME = "Redirect";
7 | export const NOT_FOUND = "NotFound";
8 |
9 | export const tableStatusFilter = (status: string): "success" | "info" | "danger" => {
10 | const statusMap: Record = {
11 | Enable: "success",
12 | Disable: "info",
13 | Deleted: "danger",
14 | };
15 | return statusMap[status];
16 | };
17 |
18 | export const mobileMaxWidthMedia = "(max-width: 768px)";
19 |
--------------------------------------------------------------------------------
/src/config/env-config.ts:
--------------------------------------------------------------------------------
1 | import type { SystemConfig } from "./types";
2 | import { createBaseConfig } from "./base-config";
3 |
4 | const baseConfig = createBaseConfig();
5 |
6 | /**
7 | * 根据环境变量动态配置 base-config
8 | * 开发环境:development
9 | * 生产环境:production
10 | */
11 | export const envConfig: SystemConfig =
12 | process.env.NODE_ENV === "development" ? { ...baseConfig, systemInfo: { name: "Teek Design Pro" } } : baseConfig;
13 |
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | import { createBaseConfig } from "./base-config";
2 | import { envConfig } from "./env-config";
3 |
4 | export * from "./constant";
5 | export * from "./symbols";
6 |
7 | // 系统配置
8 | const config = {
9 | ...createBaseConfig(),
10 | ...envConfig,
11 | };
12 |
13 | // 冻结对象防止运行时修改
14 | export default Object.freeze(config);
15 |
--------------------------------------------------------------------------------
/src/config/symbols.ts:
--------------------------------------------------------------------------------
1 | import type { LayoutSizeEnum } from "@/enums/appEnum";
2 | import type { useWebSocketStore } from "@/stores";
3 | import type { InjectionKey, Ref } from "vue";
4 |
5 | interface ConfigGlobal {
6 | size: Ref;
7 | }
8 |
9 | export const ConfigGlobalKey: InjectionKey = Symbol("ConfigGlobal");
10 | export const RefreshPageKey: InjectionKey<(value?: boolean) => void> = Symbol("RefreshPage");
11 | export const WebSocketKey: InjectionKey> = Symbol("WebSocket");
12 | export const RefreshIFrameKey: InjectionKey<() => void> = Symbol("RefreshIFrameKey");
13 | export const OpenSearchDialogKey: InjectionKey<() => void> = Symbol("OpenSearchDialog");
14 | export const OpenThemePanelKey: InjectionKey<() => void> = Symbol("OpenThemePanel");
15 |
--------------------------------------------------------------------------------
/src/directives/index.ts:
--------------------------------------------------------------------------------
1 | import type { App } from "vue";
2 | import copy from "./modules/copy";
3 | import waterMarker from "./modules/water-marker";
4 | import draggable from "./modules/draggable";
5 | import debounce from "./modules/debounce";
6 | import throttle from "./modules/throttle";
7 | import longPress from "./modules/long-press";
8 | import waves from "./modules/waves";
9 | import role from "./modules/permission/role";
10 | import auth from "./modules/permission/auth";
11 |
12 | const directivesList: any = {
13 | // Custom directives
14 | copy,
15 | waterMarker,
16 | draggable,
17 | debounce,
18 | throttle,
19 | longPress,
20 | waves,
21 | role,
22 | auth,
23 | };
24 |
25 | const directives = {
26 | install: function (app: App) {
27 | Object.keys(directivesList).forEach(key => {
28 | // 注册所有自定义指令
29 | app.directive(key, directivesList[key]);
30 | });
31 | },
32 | };
33 |
34 | export default directives;
35 |
--------------------------------------------------------------------------------
/src/directives/modules/debounce.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * v-debounce
3 | * 按钮防抖指令,可自行扩展至input
4 | * 接收参数:function 类型或者 {onClick: 接收参数:function, time: 2000} 类型
5 | *
6 | *
7 | *
8 | */
9 | import type { Directive, DirectiveBinding } from "vue";
10 |
11 | interface ElType extends HTMLElement {
12 | __handleClick__: () => any;
13 | }
14 |
15 | const debounce: Directive = {
16 | mounted(el: ElType, binding: DirectiveBinding) {
17 | const { value } = binding;
18 | if (typeof value !== "function" && typeof value?.onClick !== "function") {
19 | throw Error("callback must be a function");
20 | }
21 |
22 | const onClick = value.onClick || value;
23 | const time = value.time || 500;
24 |
25 | let timer: NodeJS.Timeout | null = null;
26 | el.__handleClick__ = function () {
27 | if (timer) clearInterval(timer);
28 |
29 | timer = setTimeout(() => {
30 | onClick();
31 | }, time);
32 | };
33 | el.addEventListener("click", el.__handleClick__);
34 | },
35 | beforeUnmount(el: ElType) {
36 | el.removeEventListener("click", el.__handleClick__);
37 | },
38 | };
39 |
40 | export default debounce;
41 |
--------------------------------------------------------------------------------
/src/directives/modules/long-press.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * v-longpress
3 | * 长按指令,长按时触发事件
4 | */
5 | import type { Directive, DirectiveBinding } from "vue";
6 |
7 | const directive: Directive = {
8 | mounted(el: HTMLElement, binding: DirectiveBinding) {
9 | if (typeof binding.value !== "function") {
10 | throw Error("callback must be a function");
11 | }
12 | // 定义变量
13 | let pressTimer: any = null;
14 | // 创建计时器( 2秒后执行函数 )
15 | const start = (e: any) => {
16 | if (e.button) {
17 | if (e.type === "click" && e.button !== 0) {
18 | return;
19 | }
20 | }
21 | if (pressTimer === null) {
22 | pressTimer = setTimeout(() => {
23 | handler(e);
24 | }, 1000);
25 | }
26 | };
27 | // 取消计时器
28 | const cancel = () => {
29 | if (pressTimer !== null) {
30 | clearTimeout(pressTimer);
31 | pressTimer = null;
32 | }
33 | };
34 | // 运行函数
35 | const handler = (e: MouseEvent | TouchEvent) => {
36 | binding.value(e);
37 | };
38 | // 添加事件监听器
39 | el.addEventListener("mousedown", start);
40 | el.addEventListener("touchstart", start);
41 | // 取消计时器
42 | el.addEventListener("click", cancel);
43 | el.addEventListener("mouseout", cancel);
44 | el.addEventListener("touchend", cancel);
45 | el.addEventListener("touchcancel", cancel);
46 | },
47 | };
48 |
49 | export default directive;
50 |
--------------------------------------------------------------------------------
/src/directives/modules/permission/auth.ts:
--------------------------------------------------------------------------------
1 | import type { Directive } from "vue";
2 | import { usePermissionNoSetup } from "@/composables";
3 |
4 | const auth: Directive = {
5 | mounted(el, binding) {
6 | const { hasAuth } = usePermissionNoSetup();
7 | const { value } = binding;
8 | if (value) !hasAuth(value) && el.parentNode?.removeChild(el);
9 | else throw new Error("need auths! Like v-auth=\"['btn.add','btn.edit']\"");
10 | },
11 | };
12 |
13 | export default auth;
14 |
--------------------------------------------------------------------------------
/src/directives/modules/permission/role.ts:
--------------------------------------------------------------------------------
1 | import type { Directive } from "vue";
2 | import { usePermission } from "@/composables";
3 |
4 | const permission: Directive = {
5 | mounted(el, binding) {
6 | const { hasRole } = usePermission();
7 | const { value } = binding;
8 | if (value) !hasRole(value) && el.parentNode?.removeChild(el);
9 | else throw new Error("need roles! Like v-role=\"['admin','visitor']\"");
10 | },
11 | };
12 |
13 | export default permission;
14 |
--------------------------------------------------------------------------------
/src/directives/modules/throttle.ts:
--------------------------------------------------------------------------------
1 | /*
2 | 需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
3 |
4 | 思路:
5 | 1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮
6 | 2、将需要触发的方法绑定在指令上
7 | 使用:给 Dom 加上 v-throttle 及回调函数即可
8 |
9 |
10 |
11 | */
12 | import type { Directive, DirectiveBinding } from "vue";
13 |
14 | interface ElType extends HTMLElement {
15 | __handleClick__: () => any;
16 | disabled: boolean;
17 | }
18 |
19 | const throttle: Directive = {
20 | mounted(el: ElType, binding: DirectiveBinding) {
21 | const { value } = binding;
22 | if (typeof value !== "function" && typeof value?.onClick !== "function") {
23 | throw Error("callback must be a function");
24 | }
25 |
26 | const onClick = value.onClick || value;
27 | const time = value.time || 2000;
28 |
29 | let timer: NodeJS.Timeout | null = null;
30 | el.__handleClick__ = function () {
31 | if (timer) clearTimeout(timer);
32 |
33 | if (!el.classList.contains("is-disabled")) {
34 | el.classList.add("is-disabled");
35 | onClick();
36 | timer = setTimeout(() => {
37 | el.classList.remove("is-disabled");
38 | }, time);
39 | }
40 | };
41 | el.addEventListener("click", el.__handleClick__);
42 | },
43 | beforeUnmount(el: ElType) {
44 | el.removeEventListener("click", el.__handleClick__);
45 | },
46 | };
47 |
48 | export default throttle;
49 |
--------------------------------------------------------------------------------
/src/directives/modules/water-marker.ts:
--------------------------------------------------------------------------------
1 | /*
2 | 需求:给整个页面添加背景水印。
3 |
4 | 思路:
5 | 1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
6 | 2、将其设置为背景图片,从而实现页面或组件水印效果
7 | 使用:设置水印文案,颜色,字体大小即可
8 |
9 | */
10 |
11 | import type { Directive, DirectiveBinding } from "vue";
12 | const addWaterMarker: Directive = (str: string, parentNode: any, font: any, textColor: string) => {
13 | // 水印文字,父元素,字体,文字颜色
14 | const can: HTMLCanvasElement = document.createElement("canvas");
15 | parentNode.appendChild(can);
16 | can.width = 205;
17 | can.height = 140;
18 | can.style.display = "none";
19 | const cans = can.getContext("2d") as CanvasRenderingContext2D;
20 | cans.rotate((-20 * Math.PI) / 180);
21 | if (!font.includes(" ")) font = font + " Microsoft JhengHei";
22 | cans.font = font || "16px Microsoft JhengHei";
23 | cans.fillStyle = textColor || "rgba(180, 180, 180, 0.3)";
24 | cans.textAlign = "left";
25 | cans.textBaseline = "Middle" as CanvasTextBaseline;
26 | cans.fillText(str, can.width / 10, can.height / 2);
27 | parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";
28 | };
29 |
30 | const waterMarker = {
31 | mounted(el: DirectiveBinding, binding: DirectiveBinding) {
32 | addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor);
33 | },
34 | };
35 |
36 | export default waterMarker;
37 |
--------------------------------------------------------------------------------
/src/directives/modules/waves/waves.css:
--------------------------------------------------------------------------------
1 | .waves-ripple {
2 | position: absolute;
3 | pointer-events: none;
4 | user-select: none;
5 | background-color: rgb(0 0 0 / 15%);
6 | background-clip: padding-box;
7 | border-radius: 100%;
8 | opacity: 1;
9 | transform: scale(0);
10 | }
11 |
12 | .waves-ripple.z-active {
13 | opacity: 0;
14 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
15 | transition: opacity 1.2s ease-out, transform 0.6s ease-out;
16 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
17 | transform: scale(2);
18 | }
19 |
--------------------------------------------------------------------------------
/src/enums/appEnum.ts:
--------------------------------------------------------------------------------
1 | // 系统级别枚举
2 | export enum LayoutModeEnum {
3 | Vertical = "vertical",
4 | Classic = "classic",
5 | Horizontal = "horizontal",
6 | Columns = "columns",
7 | IFrame = "iframe",
8 | Mixins = "mixins",
9 | }
10 |
11 | export enum TabNavModeEnum {
12 | Simple = "simple",
13 | Classic = "classic",
14 | Element = "element",
15 | }
16 |
17 | export enum SystemThemeEnum {
18 | Dark = "dark",
19 | Light = "light",
20 | System = "system",
21 | }
22 |
23 | export enum MenuThemeEnum {
24 | Dark = "dark",
25 | Light = "light",
26 | }
27 |
28 | export enum TitleModeEnum {
29 | ProjectPage = "projectPage",
30 | UsernamePage = "usernamePage",
31 | Project = "project",
32 | Page = "page",
33 | }
34 |
35 | export enum LanguageEnum {
36 | ZhCn = "zh-CN",
37 | EnUs = "en-US",
38 | }
39 |
40 | export enum LayoutSizeEnum {
41 | Default = "default",
42 | Small = "small",
43 | Large = "large",
44 | }
45 |
46 | export enum PageTransitionEnum {
47 | None = "",
48 | Fade = "fade",
49 | SlideLeft = "slide-left",
50 | SlideTop = "Slide-top",
51 | SlideBottom = "slide-bottom",
52 | }
53 |
54 | export enum HeaderStyleEnum {
55 | Page = "page",
56 | Bg = "bg",
57 | Line = "line",
58 | BgLine = "bgLine",
59 | }
60 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/breadcrumb/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(breadcrumb) {
5 | margin-left: 10px;
6 | overflow: hidden;
7 |
8 | @include e(link) {
9 | display: inline-flex;
10 | align-items: center;
11 | color: cssVar(layout-header-breadcrumb-link-color);
12 |
13 | &:not(.no-click):hover {
14 | color: cssVar(main-color);
15 | }
16 |
17 | @include no(click) {
18 | color: cssVar(layout-header-breadcrumb-text-color);
19 | }
20 | }
21 |
22 | @include e(icon) {
23 | margin-right: 6px;
24 | font-size: 16px;
25 |
26 | &.svg-icon {
27 | font-size: 12px;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/collapse-trigger/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 | @use "@/styles/mixins/namespace" as *;
4 |
5 | @include b(collapse-trigger) {
6 | width: 45px;
7 | height: 100%;
8 | font-size: 20px;
9 | cursor: pointer;
10 | user-select: none;
11 | transition: background-color var(--#{$el-namespace}-transition-duration);
12 |
13 | &:hover {
14 | background-color: cssVar(gray-200);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/collapse-trigger/index.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/error-log/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include b(error-badge) {
5 | :deep(.#{$el-namespace}-badge__content.is-fixed) {
6 | top: 11px;
7 | right: 7px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/error-log/index.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/fullscreen/index.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/global-search/input.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(global-search-input) {
5 | position: relative;
6 | display: flex;
7 | align-items: center;
8 | margin-right: 12px;
9 |
10 | @include e(input) {
11 | box-sizing: border-box;
12 | display: flex;
13 | align-items: center;
14 | justify-content: space-between;
15 | width: 160px;
16 | height: 34px;
17 | padding: 0 10px;
18 | cursor: pointer;
19 | border: 1px solid cssVar(border-dashed-color);
20 | border-radius: calc(cssVar(radius) / 2 + 2px);
21 | }
22 |
23 | @include e(content) {
24 | span {
25 | margin-left: 10px;
26 | font-size: 13px;
27 | font-weight: 400;
28 | color: cssVar(gray-500);
29 | }
30 | }
31 |
32 | @include e(keydown) {
33 | display: flex;
34 | align-items: center;
35 | height: 20px;
36 | padding: 0 6px;
37 | color: cssVar(gray-500);
38 | background-color: cssVar(bg-color);
39 | border: 1px solid cssVar(border-dashed-color);
40 | border-radius: 4px;
41 |
42 | i {
43 | font-size: 12px;
44 | }
45 |
46 | span {
47 | margin-left: 2px;
48 | font-size: 12px;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/global-search/input.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ $t("_headerBar.search") }}
25 |
26 |
27 |
28 | Crtl
29 |
30 | k
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/light-dark-switch/animation.ts:
--------------------------------------------------------------------------------
1 | import { useTheme } from "@/composables";
2 | import { SystemThemeEnum } from "@/enums/appEnum";
3 | import { useSettingStore } from "@/stores";
4 | import { setCssVar } from "@/utils";
5 |
6 | const { Dark, Light } = SystemThemeEnum;
7 |
8 | /**
9 | * 主题切换动画
10 | */
11 | export const switchThemeWithAnimation = (e: any) => {
12 | const x = e.clientX;
13 | const y = e.clientY;
14 | // 计算鼠标点击位置距离视窗的最大圆半径
15 | const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
16 |
17 | // 设置 CSS 变量
18 | setCssVar("--x", x + "px");
19 | setCssVar("--y", y + "px");
20 | setCssVar("--r", endRadius + "px");
21 |
22 | if (document.startViewTransition) document.startViewTransition(() => toggleTheme());
23 | else toggleTheme();
24 | };
25 |
26 | /**
27 | * 切换主题
28 | */
29 | const toggleTheme = () => {
30 | const { changeSystemTheme } = useTheme();
31 | const { isDark } = useSettingStore();
32 |
33 | changeSystemTheme(isDark ? Light : Dark);
34 | };
35 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/light-dark-switch/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/layout/components/Header/components/user-avatar/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(user-avatar) {
5 | @include e(wrapper) {
6 | padding-top: 5px;
7 | }
8 |
9 | @include e(head) {
10 | padding: 0 0 4px;
11 | }
12 |
13 | @include e(avatar) {
14 | width: 35px;
15 | height: 35px;
16 | cursor: pointer;
17 | border-radius: 50%;
18 | }
19 |
20 | @include e(info) {
21 | width: calc(100% - 60px);
22 | height: 100%;
23 | margin-left: 15px;
24 |
25 | .name {
26 | font-size: 14px;
27 | font-weight: 500;
28 | color: cssVar(gray-800);
29 | }
30 |
31 | .email {
32 | margin-top: 3px;
33 | font-size: 12px;
34 | color: cssVar(gray-500);
35 | }
36 | }
37 |
38 | @include e(menu) {
39 | row-gap: 5px;
40 | padding: 0;
41 | margin-top: 10px;
42 |
43 | li {
44 | padding: 6px 4px;
45 | cursor: pointer;
46 | user-select: none;
47 | border-radius: 6px;
48 |
49 | &:hover {
50 | background-color: rgb(cssVar(gray-200-rgb), 0.7);
51 | }
52 |
53 | .icon {
54 | display: block;
55 | width: 25px;
56 | font-size: 16px;
57 | color: var(text-gray-800);
58 | }
59 |
60 | .label {
61 | font-size: 14px;
62 | color: cssVar(text-gray-800);
63 | }
64 | }
65 | }
66 |
67 | .#{$el-namespace}-divider {
68 | margin: 10px 0;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/layout/components/Header/header-left.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
36 |
--------------------------------------------------------------------------------
/src/layout/components/Header/index.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/layout/components/Loading/index.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/breadcrumb/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(breadcrumb) {
5 | margin-left: 10px;
6 | overflow: hidden;
7 |
8 | @include e(link) {
9 | display: inline-flex;
10 | align-items: center;
11 | color: cssVar(layout-header-breadcrumb-link-color);
12 |
13 | &:not(.no-click):hover {
14 | color: cssVar(main-color);
15 | }
16 |
17 | @include no(click) {
18 | color: cssVar(layout-header-breadcrumb-text-color);
19 | }
20 | }
21 |
22 | @include e(icon) {
23 | margin-right: 6px;
24 | font-size: 16px;
25 |
26 | &.svg-icon {
27 | font-size: 12px;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/collapse-trigger/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 | @use "@/styles/mixins/namespace" as *;
4 |
5 | @include b(collapse-trigger) {
6 | width: 45px;
7 | height: 100%;
8 | font-size: 20px;
9 | cursor: pointer;
10 | user-select: none;
11 | transition: background-color var(--#{$el-namespace}-transition-duration);
12 |
13 | &:hover {
14 | background-color: cssVar(gray-200);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/collapse-trigger/index.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/error-log/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include b(error-badge) {
5 | :deep(.#{$el-namespace}-badge__content.is-fixed) {
6 | top: 11px;
7 | right: 7px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/error-log/index.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/fullscreen/index.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/global-search/input.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(global-search-input) {
5 | position: relative;
6 | display: flex;
7 | align-items: center;
8 | margin-right: 12px;
9 |
10 | @include e(input) {
11 | box-sizing: border-box;
12 | display: flex;
13 | align-items: center;
14 | justify-content: space-between;
15 | width: 160px;
16 | height: 34px;
17 | padding: 0 10px;
18 | cursor: pointer;
19 | border: 1px solid cssVar(border-dashed-color);
20 | border-radius: calc(cssVar(radius) / 2 + 2px);
21 | }
22 |
23 | @include e(content) {
24 | span {
25 | margin-left: 10px;
26 | font-size: 13px;
27 | font-weight: 400;
28 | color: cssVar(gray-500);
29 | }
30 | }
31 |
32 | @include e(keydown) {
33 | display: flex;
34 | align-items: center;
35 | height: 20px;
36 | padding: 0 6px;
37 | color: cssVar(gray-500);
38 | background-color: cssVar(bg-color);
39 | border: 1px solid cssVar(border-dashed-color);
40 | border-radius: 4px;
41 |
42 | i {
43 | font-size: 12px;
44 | }
45 |
46 | span {
47 | margin-left: 2px;
48 | font-size: 12px;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/global-search/input.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ $t("_headerBar.search") }}
25 |
26 |
27 |
28 | Crtl
29 |
30 | k
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/light-dark-switch/animation.ts:
--------------------------------------------------------------------------------
1 | import { useTheme } from "@/composables";
2 | import { SystemThemeEnum } from "@/enums/appEnum";
3 | import { useSettingStore } from "@/stores";
4 | import { setCssVar } from "@/utils";
5 |
6 | const { Dark, Light } = SystemThemeEnum;
7 |
8 | /**
9 | * 主题切换动画
10 | */
11 | export const switchThemeWithAnimation = (e: any) => {
12 | const x = e.clientX;
13 | const y = e.clientY;
14 | // 计算鼠标点击位置距离视窗的最大圆半径
15 | const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
16 |
17 | // 设置 CSS 变量
18 | setCssVar("--x", x + "px");
19 | setCssVar("--y", y + "px");
20 | setCssVar("--r", endRadius + "px");
21 |
22 | if (document.startViewTransition) document.startViewTransition(() => toggleTheme());
23 | else toggleTheme();
24 | };
25 |
26 | /**
27 | * 切换主题
28 | */
29 | const toggleTheme = () => {
30 | const { changeSystemTheme } = useTheme();
31 | const { isDark } = useSettingStore();
32 |
33 | changeSystemTheme(isDark ? Light : Dark);
34 | };
35 |
--------------------------------------------------------------------------------
/src/layout/components/header/components/light-dark-switch/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/layout/components/header/header-left.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
36 |
--------------------------------------------------------------------------------
/src/layout/components/header/index.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/layout/components/iframe/iframe-blank.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/layout/components/loading/index.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
--------------------------------------------------------------------------------
/src/layout/components/page-content/components/maximize.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
53 |
--------------------------------------------------------------------------------
/src/layout/components/tab-nav/components/more-button/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/function" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | .#{$el-namespace}-button {
5 | color: cssVar(layout-tab-text-color);
6 |
7 | &:hover {
8 | color: cssVar(main-color);
9 | background-color: cssVar(gray-200) !important;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/layout/components/tab-nav/components/right-menu/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(right-menu) {
5 | position: absolute;
6 | z-index: 4000;
7 | padding: 5px 0;
8 | margin: 0;
9 | font-size: 12px;
10 | font-weight: 400;
11 | color: #333333;
12 | list-style-type: none;
13 | background: #ffffff;
14 | border-radius: 4px;
15 | box-shadow: 2px 2px 3px 0 rgb(0 0 0 / 30%);
16 |
17 | li {
18 | display: flex;
19 | align-items: center;
20 | padding: 0 12px;
21 | margin: 0;
22 | line-height: 31px;
23 | cursor: pointer;
24 |
25 | &:hover {
26 | color: cssVar(main-color);
27 | }
28 |
29 | .right-menu-icon {
30 | margin-right: 8px;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/layout/components/tab-nav/tab-nav-simple/index.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/layout/components/theme-panel/components/base-config-switch/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include b(base-config-switch) {
5 | @include e(item) {
6 | display: flex;
7 | align-items: center;
8 | justify-content: space-between;
9 | margin: 14px 0;
10 |
11 | span {
12 | font-size: 14px;
13 | }
14 |
15 | .#{$el-namespace}-select {
16 | width: 40%;
17 | }
18 | .#{$el-namespace}-input-number {
19 | width: 40%;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/layout/components/theme-panel/components/browser-title-switch/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 |
3 | @include b(browser-title-switch) {
4 | display: flex;
5 | flex-wrap: wrap;
6 | align-items: center;
7 | justify-content: space-between;
8 | margin: 14px 0;
9 |
10 | span {
11 | font-size: 14px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/layout/components/theme-panel/components/global-theme-switch/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 |
4 | @include b(global-theme-select) {
5 | @include e(item) {
6 | display: flex;
7 | align-items: center;
8 | justify-content: space-between;
9 | margin: 14px 0;
10 |
11 | span {
12 | font-size: 14px;
13 | }
14 |
15 | .#{$el-namespace}-select {
16 | width: 40%;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/layout/components/theme-panel/components/index.ts:
--------------------------------------------------------------------------------
1 | import BaseConfigSwitch from "./base-config-switch/index.vue";
2 | import BrowserTitleSwitch from "./browser-title-switch/index.vue";
3 | import GlobalThemeSwitch from "./global-theme-switch/index.vue";
4 | import LayoutModeSwitch from "./layout-mode-switch/index.vue";
5 | import MenuThemeSwitch from "./menu-theme-switch/index.vue";
6 | import SystemThemeSwitch from "./system-theme-switch/index.vue";
7 |
8 | export {
9 | BaseConfigSwitch,
10 | BrowserTitleSwitch,
11 | GlobalThemeSwitch,
12 | LayoutModeSwitch,
13 | MenuThemeSwitch,
14 | SystemThemeSwitch,
15 | };
16 |
--------------------------------------------------------------------------------
/src/layout/components/theme-panel/components/menu-theme-switch/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 | @use "@/styles/mixins/namespace" as *;
4 |
5 | @include b(menu-theme-switch) {
6 | @include e(item) {
7 | position: relative;
8 | width: calc(100% / 3 - 10px);
9 | }
10 |
11 | @include e(box) {
12 | height: 55px;
13 | border-radius: 8px;
14 | box-shadow: 0 2px 8px 0 cssVar(gray-400);
15 | transition: box-shadow var(--#{$el-namespace}-transition-duration-fast);
16 |
17 | &:hover {
18 | box-shadow: 0 2px 8px 0 cssVar(gray-600);
19 | }
20 |
21 | img {
22 | width: 100%;
23 | height: 100%;
24 | border-radius: 8px;
25 | }
26 |
27 | @include is(active) {
28 | box-shadow: 0 0 0 2px cssVar(main-color) !important;
29 | }
30 | }
31 |
32 | @include m(icon) {
33 | position: absolute;
34 | top: 30px;
35 | right: 10px;
36 | color: cssVar(main-color);
37 | transition: all var(--#{$el-namespace}-transition-duration-fast);
38 | }
39 |
40 | @include m(name) {
41 | margin: 10px 0;
42 | font-size: 14px;
43 | text-align: center;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/layout/components/theme-panel/components/system-theme-switch/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 | @use "@/styles/mixins/namespace" as *;
4 |
5 | @include b(system-theme-switch) {
6 | @include e(item) {
7 | position: relative;
8 | width: calc(100% / 3 - 10px);
9 | cursor: pointer;
10 | }
11 |
12 | @include e(box) {
13 | height: 55px;
14 | border-radius: 8px;
15 | box-shadow: 0 2px 8px 0 cssVar(gray-400);
16 | transition: box-shadow var(--#{$el-namespace}-transition-duration-fast);
17 |
18 | &:hover {
19 | box-shadow: 0 2px 8px 0 cssVar(gray-600);
20 | }
21 |
22 | img {
23 | width: 100%;
24 | height: 100%;
25 | border-radius: 8px;
26 | }
27 |
28 | @include is(active) {
29 | box-shadow: 0 0 0 2px cssVar(main-color) !important;
30 | }
31 | }
32 |
33 | @include m(icon) {
34 | position: absolute;
35 | top: 30px;
36 | right: 10px;
37 | color: cssVar(main-color);
38 | transition: all var(--#{$el-namespace}-transition-duration-fast);
39 | }
40 |
41 | @include m(name) {
42 | margin: 10px 0;
43 | font-size: 14px;
44 | text-align: center;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/layout/components/watermark/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 |
3 | @include b(watermark) {
4 | position: fixed;
5 | top: 0;
6 | left: 0;
7 | width: 100vw;
8 | height: 100vh;
9 | pointer-events: none;
10 | }
11 |
--------------------------------------------------------------------------------
/src/layout/components/watermark/index.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/src/layout/layout-classic/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(classic-layout) {
5 | @include e(header-left) {
6 | gap: 20px;
7 | }
8 |
9 | /* 内容区减去 header 高度,否则无法滚动 */
10 | @include e(content) {
11 | height: calc(100% - cssVar(layout-header-height));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/layout/layout-horizontal/index.scss:
--------------------------------------------------------------------------------
1 | // @use "@/styles/mixins/bem" as *;
2 |
3 | // @include b(horizontal-layout) {
4 | // }
5 |
--------------------------------------------------------------------------------
/src/layout/layout-iframe/index.scss:
--------------------------------------------------------------------------------
1 | // @use "@/styles/mixins/bem" as *;
2 |
3 | // @include b(iframe-layout) {
4 | // }
5 |
--------------------------------------------------------------------------------
/src/layout/layout-mixins/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(mixins-layout) {
5 | .has-trigger {
6 | visibility: hidden;
7 | }
8 |
9 | @include joins(layout-logo) {
10 | margin-right: 10px;
11 | }
12 |
13 | /* 内容区减去 header 高度,否则无法滚动 */
14 | @include e(content) {
15 | height: calc(100% - cssVar(layout-header-height));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/layout/layout-vertical/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(vertical-layout) {
5 | /* 折叠按钮添加左间距 */
6 | .#{$el-namespace}-header {
7 | padding-left: 10px !important;
8 | }
9 |
10 | @include e(drawer-model) {
11 | position: fixed;
12 | top: 0;
13 | left: 0;
14 | z-index: 99;
15 | display: block;
16 | width: 100%;
17 | height: 100vh;
18 | background: rgb(0 0 0 / 50%);
19 | transition: opacity 0.2s ease-in-out;
20 | }
21 |
22 | @media (max-width: $device-ipad) {
23 | @include joins(layout-aside) {
24 | position: fixed;
25 | top: 0;
26 | left: 0;
27 | z-index: 100;
28 | display: flex;
29 | height: 100vh;
30 | overflow: auto;
31 | user-select: none;
32 | scrollbar-width: none;
33 | }
34 |
35 | @include is(collapse) {
36 | @include joins(layout-aside) {
37 | width: 0 !important;
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/layout/redirect.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/mock/drag-item.ts:
--------------------------------------------------------------------------------
1 | export interface DragList {
2 | id: string;
3 | name: string;
4 | }
5 |
6 | export const list1: Array = [
7 | {
8 | id: "list1_0",
9 | name: "前端技术",
10 | },
11 | {
12 | id: "list1_1",
13 | name: "后端技术",
14 | },
15 | {
16 | id: "list1_2",
17 | name: "运维技术",
18 | },
19 | ];
20 |
21 | export const list2: Array = [
22 | {
23 | id: "list2_0",
24 | name: "上班摸鱼",
25 | },
26 | {
27 | id: "list2_1",
28 | name: "下班游戏",
29 | },
30 | {
31 | id: "list2_2",
32 | name: "熬夜睡觉",
33 | },
34 | ];
35 |
36 | export const list3: Array = [
37 | {
38 | id: "list3_0",
39 | name: "学生毕业",
40 | },
41 | {
42 | id: "list3_1",
43 | name: "青春毕业",
44 | },
45 | {
46 | id: "list3_2",
47 | name: "努力工作",
48 | },
49 | {
50 | id: "list3_2",
51 | name: "佛系生活",
52 | },
53 | ];
54 |
--------------------------------------------------------------------------------
/src/mock/drag-list.ts:
--------------------------------------------------------------------------------
1 | export interface DragList {
2 | id: string;
3 | name: string;
4 | }
5 |
6 | export const list1: Array = [
7 | {
8 | id: "list1_0",
9 | name: "与现实生活的流程保持一致",
10 | },
11 | {
12 | id: "list1_1",
13 | name: "遵循用户习惯的语言",
14 | },
15 | {
16 | id: "list1_2",
17 | name: "所有的元素和结构需保持一致",
18 | },
19 | ];
20 |
21 | export const list2: Array = [
22 | {
23 | id: "list2_0",
24 | name: "设计样式、图标和文本",
25 | },
26 | {
27 | id: "list2_1",
28 | name: "当你醒来时, 青春早已消失无踪",
29 | },
30 | {
31 | id: "list2_2",
32 | name: "邪恶无法抵消高贵的品质",
33 | },
34 | ];
35 |
--------------------------------------------------------------------------------
/src/request/check-status.ts:
--------------------------------------------------------------------------------
1 | import { message } from "@/utils";
2 |
3 | /**
4 | * @description 校验网络请求状态码
5 | * @param {Number} status
6 | * @return void
7 | */
8 | export const checkStatus = (status: number): void => {
9 | switch (status) {
10 | case 400:
11 | message.error("请求失败!请您稍后重试");
12 | break;
13 | case 401:
14 | message.error("登录失效!请您重新登录");
15 | break;
16 | case 403:
17 | message.error("当前账号无权限访问!");
18 | break;
19 | case 404:
20 | message.error("你所访问的资源不存在!");
21 | break;
22 | case 405:
23 | message.error("请求方式错误!请您稍后重试");
24 | break;
25 | case 408:
26 | message.error("请求超时!请您稍后重试");
27 | break;
28 | case 500:
29 | message.error("服务异常!");
30 | break;
31 | case 502:
32 | message.error("网关错误!");
33 | break;
34 | case 503:
35 | message.error("服务不可用!");
36 | break;
37 | case 504:
38 | message.error("网关超时!");
39 | break;
40 | default:
41 | message.error("请求失败!");
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/src/request/http-enum.ts:
--------------------------------------------------------------------------------
1 | // 请求枚举配置
2 | /**
3 | * @description 请求配置
4 | */
5 | export enum ResultEnum {
6 | SUCCESS = 200,
7 | ERROR = 500,
8 | OVERDUE = 401,
9 | TIMEOUT = 10000,
10 | TYPE = "success",
11 | }
12 |
13 | /**
14 | * @description 请求方法
15 | */
16 | export enum RequestEnum {
17 | GET = "GET",
18 | POST = "POST",
19 | PATCH = "PATCH",
20 | PUT = "PUT",
21 | DELETE = "DELETE",
22 | }
23 |
24 | /**
25 | * @description 常用的 contentTyp 类型
26 | */
27 | export enum ContentTypeEnum {
28 | // json
29 | JSON = "application/json;charset=UTF-8",
30 | // text
31 | TEXT = "text/plain;charset=UTF-8",
32 | // form-data 一般配合qs
33 | FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8",
34 | // 单文件 form-data 上传
35 | FILE_FORM_DATA = "application/form-data;charset=UTF-8",
36 | // 多文件 form-data 上传
37 | Multi_FILE_FORM_DATA = "multipart/form-data;charset=UTF-8",
38 | }
39 |
--------------------------------------------------------------------------------
/src/request/service-loading.ts:
--------------------------------------------------------------------------------
1 | import { ElLoading } from "element-plus";
2 |
3 | /* 全局请求 loading(服务方式调用) */
4 | let loadingInstance: ReturnType;
5 |
6 | /**
7 | * @description 开启 Loading
8 | * */
9 | const startLoading = () => {
10 | loadingInstance = ElLoading.service({
11 | fullscreen: true,
12 | lock: true,
13 | text: "Loading",
14 | background: "rgba(0, 0, 0, 0.7)",
15 | });
16 | };
17 |
18 | /**
19 | * @description 结束 Loading
20 | * */
21 | const endLoading = () => {
22 | loadingInstance.close();
23 | };
24 |
25 | /**
26 | * @description 显示全屏加载
27 | * */
28 | let needLoadingRequestCount = 0;
29 | export const showFullScreenLoading = () => {
30 | if (needLoadingRequestCount === 0) startLoading();
31 | needLoadingRequestCount++;
32 | return tryHideFullScreenLoading;
33 | };
34 |
35 | /**
36 | * @description 隐藏全屏加载
37 | * */
38 | export const tryHideFullScreenLoading = () => {
39 | if (needLoadingRequestCount <= 0) return;
40 | needLoadingRequestCount--;
41 | if (needLoadingRequestCount === 0) {
42 | endLoading();
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/src/router/before-close.ts:
--------------------------------------------------------------------------------
1 | import type { RouteLocationNormalizedLoaded } from "vue-router";
2 | import { ElMessageBox } from "element-plus";
3 |
4 | type BeforeCloseType = (value: unknown) => void;
5 |
6 | interface BeforeClose {
7 | [key: string]: (resolve: BeforeCloseType, route: RouteLocationNormalizedLoaded) => void;
8 | before_close_normal: (resolve: BeforeCloseType, route: RouteLocationNormalizedLoaded) => void;
9 | }
10 |
11 | const beforeClose: BeforeClose = {
12 | before_close_normal: (resolve: any, route: RouteLocationNormalizedLoaded) => {
13 | if (route.query.noBeforeClose) return resolve(true);
14 | ElMessageBox.confirm("确定要关闭这一页吗", "提示", {
15 | confirmButtonText: "确定",
16 | cancelButtonText: "取消",
17 | type: "warning",
18 | })
19 | .then(() => {
20 | resolve(true);
21 | })
22 | .catch(() => {
23 | resolve(false);
24 | });
25 | },
26 | };
27 |
28 | export default beforeClose;
29 |
--------------------------------------------------------------------------------
/src/router/guards/after-each.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from "vue-router";
2 | import { NProgress } from "@/utils";
3 |
4 | /**
5 | * 路由跳转结束
6 | **/
7 | export const afterEach = (router: Router) => {
8 | router.afterEach(() => {
9 | NProgress.done();
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/src/router/routes/details.ts:
--------------------------------------------------------------------------------
1 | import { StarFilled } from "@element-plus/icons-vue";
2 | import type { RouteLocationNormalizedLoaded } from "vue-router";
3 |
4 | const detailsRoutes: RouterConfigRaw = {
5 | path: "/arg",
6 | name: "Arg",
7 | redirect: "/arg/params/1",
8 | meta: {
9 | hideInMenu: true,
10 | hideInBread: true,
11 | },
12 | children: [
13 | {
14 | path: "query",
15 | name: "Query",
16 | component: "/tabs/query-detail",
17 | meta: {
18 | title: (route: RouteLocationNormalizedLoaded) => `{{ _route.Query }}-${route.query.id}`,
19 | icon: StarFilled,
20 | beforeCloseName: "before_close_normal",
21 | },
22 | },
23 | {
24 | path: "params/:id",
25 | name: "Params",
26 | component: "/tabs/params-detail",
27 | meta: {
28 | title: (route: RouteLocationNormalizedLoaded) => `{{ _route.Params }}-${route.params.id}`,
29 | icon: StarFilled,
30 | beforeCloseName: "before_close_normal",
31 | dynamicLevel: 3,
32 | },
33 | },
34 | ],
35 | };
36 |
37 | export default detailsRoutes;
38 |
--------------------------------------------------------------------------------
/src/router/routes/error.ts:
--------------------------------------------------------------------------------
1 | import { WarningFilled, StarFilled } from "@element-plus/icons-vue";
2 |
3 | const errorRoutes: RouterConfigRaw = {
4 | path: "/error",
5 | name: "Error",
6 | redirect: "/error/403",
7 | meta: {
8 | title: "错误页面",
9 | icon: WarningFilled,
10 | },
11 | children: [
12 | {
13 | path: "403",
14 | name: "403",
15 | component: () => import("@/views/error/403.vue"),
16 | meta: {
17 | title: "403 页面",
18 | icon: StarFilled,
19 | },
20 | },
21 | {
22 | path: "404",
23 | name: "404",
24 | component: () => import("@/views/error/404.vue"),
25 | meta: {
26 | title: "404 页面",
27 | icon: StarFilled,
28 | },
29 | },
30 | {
31 | path: "500",
32 | name: "500",
33 | component: () => import("@/views/error/500.vue"),
34 | meta: {
35 | title: "500 页面",
36 | icon: StarFilled,
37 | },
38 | },
39 | ],
40 | };
41 |
42 | export default errorRoutes;
43 |
--------------------------------------------------------------------------------
/src/router/routes/excel.ts:
--------------------------------------------------------------------------------
1 | import { Tickets, StarFilled } from "@element-plus/icons-vue";
2 |
3 | const excelRoutes: RouterConfigRaw = {
4 | path: "/excel",
5 | redirect: "/excel/export-excel",
6 | name: "Excel",
7 | meta: {
8 | title: "Excel",
9 | icon: Tickets,
10 | },
11 | children: [
12 | {
13 | path: "export-excel",
14 | component: () => import("@/views/excel/export-excel.vue"),
15 | name: "ExportExcel",
16 | meta: { title: "Excel 导出", icon: StarFilled },
17 | },
18 | {
19 | path: "export-selected-excel",
20 | component: () => import("@/views/excel/select-excel.vue"),
21 | name: "SelectExcel",
22 | meta: { title: "指定数据导出", icon: StarFilled },
23 | },
24 | {
25 | path: "export-merge-header",
26 | component: () => import("@/views/excel/merge-header.vue"),
27 | name: "MergeHeader",
28 | meta: { title: "合并表头导出", icon: StarFilled },
29 | },
30 | {
31 | path: "upload-excel",
32 | component: () => import("@/views/excel/upload-excel.vue"),
33 | name: "UploadExcel",
34 | meta: { title: "Excel 导入", icon: StarFilled },
35 | },
36 | ],
37 | };
38 |
39 | export default excelRoutes;
40 |
--------------------------------------------------------------------------------
/src/router/routes/outer-chain.ts:
--------------------------------------------------------------------------------
1 | import { Link, StarFilled } from "@element-plus/icons-vue";
2 |
3 | const outerChainRoutes: RouterConfigRaw = {
4 | path: "/outer-chain",
5 | name: "OuterChain",
6 | meta: {
7 | title: "外部链接",
8 | icon: Link,
9 | rank: 100,
10 | },
11 | children: [
12 | {
13 | path: "https://github.com/Kele-Bingtang/teek-design-vue3",
14 | name: "Github",
15 | meta: {
16 | title: "Github",
17 | icon: "SVG-github",
18 | },
19 | },
20 | {
21 | path: "https://vue2-admin.youngkbt.cn/",
22 | name: "Vue2Admin",
23 | meta: {
24 | title: "Kbt Vue2 Admin",
25 | icon: StarFilled,
26 | },
27 | },
28 | {
29 | path: "https://notes.youngkbt.cn/",
30 | name: "Notes",
31 | meta: {
32 | title: "我的博客",
33 | icon: StarFilled,
34 | },
35 | },
36 | ],
37 | };
38 |
39 | export default outerChainRoutes;
40 |
--------------------------------------------------------------------------------
/src/router/routes/permission.ts:
--------------------------------------------------------------------------------
1 | import { StarFilled, Lock } from "@element-plus/icons-vue";
2 |
3 | const permissionRoutes = {
4 | path: "/permission",
5 | redirect: "/permission/switch",
6 | name: "Permission",
7 | meta: {
8 | title: "权限",
9 | icon: Lock,
10 | roles: ["admin", "visitor"],
11 | alwaysShowRoot: true,
12 | },
13 | children: [
14 | {
15 | path: "switch",
16 | component: () => import("@/views/permission/switch-permission.vue"),
17 | name: "SwitchPermission",
18 | meta: {
19 | title: "权限切换",
20 | auths: ["btn_add", "btn_edit", "btn_delete"],
21 | icon: StarFilled,
22 | },
23 | },
24 | {
25 | path: "role",
26 | component: () => import("@/views/permission/role-permission.vue"),
27 | name: "RolePermission",
28 | meta: {
29 | title: "权限编辑",
30 | roles: ["admin"],
31 | icon: StarFilled,
32 | },
33 | },
34 | ],
35 | };
36 |
37 | export default permissionRoutes;
38 |
--------------------------------------------------------------------------------
/src/router/routes/pro-form.ts:
--------------------------------------------------------------------------------
1 | import { Box, StarFilled } from "@element-plus/icons-vue";
2 |
3 | const proForm: RouterConfigRaw = {
4 | path: "/pro-form",
5 | redirect: "/pro-form/simple",
6 | name: "ProForm",
7 | meta: {
8 | title: "超级表单",
9 | icon: Box,
10 | },
11 | children: [
12 | {
13 | path: "simple",
14 | component: () => import("@/views/proForm/simpleProForm/index.vue"),
15 | name: "SimpleProForm",
16 | meta: { title: "使用 ProForm", icon: StarFilled },
17 | },
18 | {
19 | path: "detail",
20 | component: () => import("@/views/proForm/detailProForm/index.vue"),
21 | name: "DetailProForm",
22 | meta: { title: "详情 ProForm", icon: StarFilled },
23 | },
24 | ],
25 | };
26 |
27 | export default proForm;
28 |
--------------------------------------------------------------------------------
/src/stores/core/route.ts:
--------------------------------------------------------------------------------
1 | import { ref, computed } from "vue";
2 | import { defineStore } from "pinia";
3 | import { useRouteFn } from "@/composables";
4 | import { staticRoutes, errorRouter, notFoundRouter } from "@/router/routes-config";
5 | import { HOME_NAME } from "@/config";
6 |
7 | export const useRouteStore = defineStore("routeStore", () => {
8 | const loadedRouteList = ref([]);
9 | const flatRouteList = ref([]);
10 |
11 | const { processRouteMeta, findRouteByName, filterFlatRoutes, ascending } = useRouteFn();
12 |
13 | const homeRoute = computed(() => findRouteByName(loadedRouteList.value, HOME_NAME)); // 路由里首页的 name 值,必须填且正确,默认为 Home
14 |
15 | const loadPermissionRoutes = (routers: RouterConfig[]) => {
16 | loadedRouteList.value = ascending(
17 | processRouteMeta(staticRoutes).concat(routers).concat(errorRouter).concat(notFoundRouter)
18 | );
19 | flatRouteList.value = filterFlatRoutes(
20 | processRouteMeta(staticRoutes).concat(routers).concat(errorRouter).concat(notFoundRouter) as RouterConfig[]
21 | );
22 |
23 | return flatRouteList.value;
24 | };
25 |
26 | return {
27 | loadedRouteList,
28 | homeRoute,
29 | flatRouteList,
30 |
31 | loadPermissionRoutes,
32 | };
33 | });
34 |
--------------------------------------------------------------------------------
/src/styles/common/base.scss:
--------------------------------------------------------------------------------
1 | @use "../mixins/namespace" as *;
2 |
3 | /* Global scss */
4 | body {
5 | height: 100%;
6 | margin: 0;
7 | overflow: hidden;
8 | font-family:
9 | -apple-system, BlinkMacSystemFont, "PingFang SC", "Segoe UI", "Helvetica Neue", Helvetica, "Hiragino Sans GB",
10 | "Microsoft YaHei", Arial, sans-serif;
11 | -moz-osx-font-smoothing: grayscale;
12 | -webkit-font-smoothing: antialiased;
13 | color: var(--#{$admin-namespace}-text-gray-700);
14 | }
15 |
16 | html {
17 | height: 100%;
18 | }
19 |
20 | #app {
21 | height: 100%;
22 | }
23 |
24 | *,
25 | *::before,
26 | *::after {
27 | box-sizing: border-box;
28 | }
29 |
30 | a,
31 | a:focus,
32 | a:hover {
33 | color: inherit;
34 | text-decoration: none;
35 | outline: none;
36 | }
37 |
38 | div:focus {
39 | outline: none;
40 | }
41 |
--------------------------------------------------------------------------------
/src/styles/common/menu-theme.scss:
--------------------------------------------------------------------------------
1 | @use "../mixins/namespace" as *;
2 | @use "../mixins/mixins" as *;
3 |
4 | $text-color: #babbbd;
5 |
6 | /* 菜单主题样式 */
7 | .#{$admin-namespace}-layout {
8 | /* dark 模式 */
9 | &-aside.is-dark {
10 | @include set-css-var(layout-aside-bg-color, #191a23);
11 | @include set-css-var(layout-aside-bg-active-color, var(--#{$el-namespace}-color-primary-light-2));
12 | @include set-css-var(layout-aside-bg-hover-color, #26272f);
13 | @include set-css-var(layout-aside-left-bg-color, var(--#{$el-namespace}-color-primary-light-2));
14 | @include set-css-var(layout-aside-text-color, #{$text-color});
15 | @include set-css-var(layout-aside-text-active-color, #ffffff);
16 | @include set-css-var(layout-aside-text-hover-color, #ffffff);
17 | @include set-css-var(layout-aside-icon-color, #{$text-color});
18 | @include set-css-var(layout-aside-icon-active-color, #ffffff);
19 | @include set-css-var(layout-aside-text-active-weight, 400);
20 | }
21 |
22 | /* dark 模式下修改 logo 样式(经典布局不修改) */
23 | &:has(&-aside.is-dark):not(.#{$admin-namespace}-classic-layout) {
24 | .#{$admin-namespace}-layout-logo {
25 | background-color: #191a23;
26 |
27 | span {
28 | color: #{$text-color};
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/styles/common/scrollbar.scss:
--------------------------------------------------------------------------------
1 | @use "../mixins/namespace" as *;
2 | @use "../mixins/function" as *;
3 |
4 | /* 滚动条 */
5 | ::-webkit-scrollbar {
6 | width: 8px;
7 | height: 0;
8 | }
9 |
10 | /* 滚动条的轨道 */
11 | ::-webkit-scrollbar-track {
12 | background-color: cssVar(text-gray-100);
13 | }
14 |
15 | /* 滚动条的滑块按钮 */
16 | ::-webkit-scrollbar-thumb {
17 | background-color: #cccccc;
18 | border-radius: 5px;
19 | transition: all 0.2s;
20 | }
21 |
22 | ::-webkit-scrollbar-thumb:hover {
23 | background-color: #b0abab;
24 | }
25 |
26 | /* 滚动条的上下两端的按钮 */
27 | ::-webkit-scrollbar-button {
28 | width: 0;
29 | height: 0;
30 | }
31 |
32 | .dark {
33 | ::-webkit-scrollbar-track {
34 | background-color: cssVar(bg-color);
35 | }
36 |
37 | ::-webkit-scrollbar-thumb {
38 | background-color: rgb(var(--#{$admin-namespace}-gray-300-rgb), 0.8);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/styles/common/theme-animation.scss:
--------------------------------------------------------------------------------
1 | // 定义基础变量
2 | $bg-animation-color-light: #000000;
3 | $bg-animation-color-dark: #ffffff;
4 | $bg-animation-duration: 0.5s;
5 |
6 | html {
7 | // View transition
8 | &::view-transition-old(*) {
9 | animation: none;
10 | }
11 |
12 | &::view-transition-new(*) {
13 | animation: clip #{$bg-animation-duration ease-in};
14 | }
15 |
16 | &::view-transition-old(root) {
17 | z-index: 1;
18 | }
19 |
20 | &::view-transition-new(root) {
21 | z-index: 9999;
22 | }
23 |
24 | &.dark {
25 | &::view-transition-old(*) {
26 | animation: clip #{$bg-animation-duration ease-in reverse};
27 | }
28 |
29 | &::view-transition-new(*) {
30 | animation: none;
31 | }
32 |
33 | &::view-transition-old(root) {
34 | z-index: 9999;
35 | }
36 |
37 | &::view-transition-new(root) {
38 | z-index: 1;
39 | }
40 | }
41 | }
42 |
43 | // 定义动画
44 | @keyframes clip {
45 | from {
46 | clip-path: circle(0% at var(--x) var(--y));
47 | }
48 |
49 | to {
50 | clip-path: circle(var(--r) at var(--x) var(--y));
51 | }
52 | }
53 |
54 | // body 相关样式
55 | body {
56 | background-color: $bg-animation-color-light;
57 | }
58 |
59 | .dark {
60 | body {
61 | background-color: $bg-animation-color-dark;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/styles/element-plus/el-dark.scss:
--------------------------------------------------------------------------------
1 | @use "../mixins/namespace" as *;
2 |
3 | // 自定义 ElementPlus 暗黑主题
4 | @forward "element-plus/theme-chalk/src/dark/var.scss" //
5 | with (
6 | $colors: (
7 | //
8 | "white": #ffffff,
9 | "black": #000000,
10 | "success": ("base": #13deb9),
11 | "warning": ("base": #ffae1f),
12 | "danger": ("base": #ff4d4f),
13 | "error": ("base": #fa896b),
14 | )
15 | );
16 |
17 | @use "element-plus/theme-chalk/src/dark/css-vars.scss" as *;
18 |
19 | $text-size: rgb(255 255 255 / 70%);
20 |
21 | html.dark {
22 | --#{$el-namespace}-text-color-regular: #{$text-size};
23 | }
24 |
--------------------------------------------------------------------------------
/src/styles/element-plus/el-light.scss:
--------------------------------------------------------------------------------
1 | @use "../mixins/namespace" as *;
2 |
3 | // 自定义 ElementPlus 亮色主题
4 | @forward "element-plus/theme-chalk/src/common/var.scss" with (
5 | $colors: (
6 | "white": #ffffff,
7 | "black": #000000,
8 | "success": (
9 | "base": #13deb9,
10 | ),
11 | "warning": (
12 | "base": #ffae1f,
13 | ),
14 | "danger": (
15 | "base": #ff4d4f,
16 | ),
17 | "error": (
18 | "base": #fa896b,
19 | ),
20 | ),
21 | $button: (
22 | "hover-bg-color": var(--#{$admin-namespace}-color-primary-light-9),
23 | "hover-border-color": var(--#{$admin-namespace}-color-primary),
24 | "border-color": var(--#{$admin-namespace}-color-primary),
25 | "text-color": var(--#{$admin-namespace}-color-primary),
26 | ),
27 | $dialog: (
28 | "border-radius": "14px",
29 | ),
30 | $messagebox: (
31 | "border-radius": "12px",
32 | ),
33 | $popover: (
34 | "padding": "14px",
35 | "border-radius": "10px",
36 | )
37 | );
38 |
39 | @use "element-plus/theme-chalk/src/index.scss" as *;
40 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @forward "variables";
2 |
3 | @forward "common/base";
4 | @forward "common/dark";
5 | @forward "common/atomic";
6 | @forward "common/scrollbar";
7 | @forward "common/transition";
8 | @forward "common/theme-animation";
9 | @forward "common/menu-theme";
10 |
11 | @forward "mixins/namespace";
12 |
13 | /* 自定义原子类 */
14 | @for $i from 1 through 6 {
15 | .m-#{$i * 5} {
16 | margin: ($i * 5) + px;
17 | }
18 | .mt-#{$i * 5} {
19 | margin-top: ($i * 5) + px;
20 | }
21 | .mb-#{$i * 5} {
22 | margin-bottom: ($i * 5) + px;
23 | }
24 | .ml-#{$i * 5} {
25 | margin-left: ($i * 5) + px;
26 | }
27 | .mr-#{$i * 5} {
28 | margin-right: ($i * 5) + px;
29 | }
30 |
31 | .p-#{$i * 5} {
32 | padding: ($i * 5) + px;
33 | }
34 | .pt-#{$i * 5} {
35 | padding-top: ($i * 5) + px;
36 | }
37 | .pb-#{$i * 5} {
38 | padding-bottom: ($i * 5) + px;
39 | }
40 | .pl-#{$i * 5} {
41 | padding-left: ($i * 5) + px;
42 | }
43 | .pr-#{$i * 5} {
44 | padding-right: ($i * 5) + px;
45 | }
46 | }
47 |
48 | @for $i from 14 through 24 {
49 | .fz-#{$i} {
50 | font-size: ($i) + px;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/styles/mixins/mixins.scss:
--------------------------------------------------------------------------------
1 | @use "./function" as *;
2 |
3 | @mixin set-css-var($name, $value) {
4 | #{joinVar($name)}: #{$value};
5 | }
6 |
7 | /**
8 | * 溢出省略号
9 | */
10 | @mixin ellipsis($rowCount: 1) {
11 | @if $rowCount <= 1 {
12 | overflow: hidden;
13 | text-overflow: ellipsis;
14 | white-space: nowrap;
15 | } @else {
16 | display: -webkit-box;
17 | min-width: 0;
18 | overflow: hidden;
19 | text-overflow: ellipsis;
20 | -webkit-line-clamp: $rowCount;
21 | -webkit-box-orient: vertical;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/styles/mixins/namespace.scss:
--------------------------------------------------------------------------------
1 | $admin-namespace: tk !default;
2 |
3 | // el 命名空间
4 | $el-namespace: el !default;
5 | $common-separator: "-" !default;
6 | $element-separator: "__" !default;
7 | $modifier-separator: "--" !default;
8 | $is-state-prefix: "is-" !default;
9 | $has-state-prefix: "has-" !default;
10 | $no-state-prefix: "no-" !default;
11 |
12 | // 自定义 EP 样式命名空间
13 | @forward "element-plus/theme-chalk/src/mixins/config.scss" with (
14 | $namespace: #{$el-namespace}
15 | );
16 |
--------------------------------------------------------------------------------
/src/types/components.d.ts:
--------------------------------------------------------------------------------
1 | // vue 实例全局属性
2 | declare module "vue" {
3 | export interface GlobalComponents {
4 | Role: (typeof import("./components/Permission/role.vue"))["default"];
5 | Auth: (typeof import("../components/core/permission/src/auth.vue"))["default"];
6 | Icon: (typeof import("./components/Icon/index.vue"))["default"];
7 | }
8 |
9 | interface ComponentCustomProperties {
10 | // element plus 的变量
11 | scope: any;
12 | $index: number;
13 | }
14 | }
15 |
16 | export {};
17 |
--------------------------------------------------------------------------------
/src/types/http.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace http {
2 | interface Response {
3 | code: number; // 状态码
4 | status: string; // 状态码信息
5 | message: string; // 消息
6 | data: T; // 数据
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/types/plugins.d.ts:
--------------------------------------------------------------------------------
1 | declare module "v-contextmenu";
2 |
--------------------------------------------------------------------------------
/src/types/window.d.ts:
--------------------------------------------------------------------------------
1 | interface Log {
2 | base: (text: any) => void;
3 | info: (textOrTitle: any, content?: any) => void;
4 | primary: (textOrTitle: any, content?: any) => void;
5 | success: (textOrTitle: any, content?: any) => void;
6 | warning: (textOrTitle: any, content?: any) => void;
7 | danger: (textOrTitle: any, content?: any) => void;
8 | error: (content: any) => void;
9 | table: (data: any[]) => void;
10 | picture: (url: string, scale?: number) => void;
11 | }
12 |
13 | declare global {
14 | interface Navigator {
15 | browserLanguage: string;
16 | }
17 | interface Window {
18 | webkitCancelAnimationFrame;
19 | mozCancelAnimationFrame;
20 | oCancelAnimationFrame;
21 | msCancelAnimationFrame;
22 | webkitRequestAnimationFrame;
23 | mozRequestAnimationFrame;
24 | oRequestAnimationFrame;
25 | msRequestAnimationFrame;
26 | log: Log;
27 | }
28 |
29 | /**
30 | * 平台的名称、版本、依赖、最后构建时间的类型提示
31 | */
32 | const __APP_INFO__: {
33 | pkg: {
34 | name: string;
35 | version: string;
36 | dependencies: Record;
37 | devDependencies: Record;
38 | };
39 | lastBuildTime: string;
40 | };
41 |
42 | const log: Log;
43 | }
44 |
45 | export {}; // 扩展 global 而不是覆盖
46 |
--------------------------------------------------------------------------------
/src/utils/component/install.ts:
--------------------------------------------------------------------------------
1 | import type { App, Directive } from "vue";
2 | import type { UseInstallWithPlugin, UseInstallWithContext } from "./typescript";
3 |
4 | export const useInstall = >(main: T, extra?: E) => {
5 | (main as UseInstallWithPlugin).install = (app: App): void => {
6 | for (const comp of [main, ...Object.values(extra ?? {})]) {
7 | app.component(comp.name, comp);
8 | }
9 | };
10 |
11 | if (extra) {
12 | for (const [key, comp] of Object.entries(extra)) {
13 | (main as any)[key] = comp;
14 | }
15 | }
16 | return main as UseInstallWithPlugin & E;
17 | };
18 |
19 | export const useInstallFunction = (fn: T, name: string) => {
20 | (fn as UseInstallWithPlugin).install = (app: App) => {
21 | (fn as UseInstallWithContext)._context = app._context;
22 | app.config.globalProperties[name] = fn;
23 | };
24 |
25 | return fn as UseInstallWithContext;
26 | };
27 |
28 | export const useInstallDirective = (directive: T, name: string) => {
29 | (directive as UseInstallWithPlugin).install = (app: App): void => {
30 | app.directive(name, directive);
31 | };
32 |
33 | return directive as UseInstallWithPlugin;
34 | };
35 |
--------------------------------------------------------------------------------
/src/utils/component/typescript.ts:
--------------------------------------------------------------------------------
1 | import type { AppContext, Plugin } from "vue";
2 |
3 | export type UseInstallWithPlugin = T & Plugin;
4 |
5 | export type UseInstallWithContext = UseInstallWithPlugin & {
6 | _context: AppContext | null;
7 | };
8 |
--------------------------------------------------------------------------------
/src/utils/core/error-handler.ts:
--------------------------------------------------------------------------------
1 | import { ElNotification } from "element-plus";
2 | import { isArray } from "@/utils";
3 | import { useErrorLogStore } from "@/stores";
4 | import SystemConfig from "@/config";
5 |
6 | /**
7 | * @description 检查当前环境是否符合错误日志的运行
8 | */
9 | export const checkNeed = () => {
10 | const { env } = SystemConfig.layoutConfig.errorLog;
11 | const node_env = import.meta.env.MODE;
12 | if (isArray(env) && node_env) return env.includes(node_env);
13 | return false;
14 | };
15 |
16 | /**
17 | * @description 捕获错误回调
18 | */
19 | export const errorHandler = (error: any, vm: ComponentPublicInstance | null, info: string) => {
20 | if (!checkNeed()) return;
21 | const errorStore = useErrorLogStore();
22 | errorStore.addErrorLog({
23 | error,
24 | vm,
25 | info,
26 | url: window.location.href,
27 | hasRead: false,
28 | });
29 | // 过滤 HTTP 请求错误
30 | if (error.status || error.status === 0) return false;
31 | const errorMap: Record = {
32 | InternalError: "Javascript引擎内部错误",
33 | ReferenceError: "未找到对象",
34 | TypeError: "使用了错误的类型或对象",
35 | RangeError: "使用内置对象时,参数超范围",
36 | SyntaxError: "语法错误",
37 | EvalError: "错误的使用了Eval",
38 | URIError: "URI错误",
39 | };
40 | const errorName = errorMap[error.name] || "未知错误";
41 | ElNotification({
42 | title: errorName,
43 | message: error,
44 | type: "error",
45 | duration: 3000,
46 | });
47 | };
48 |
--------------------------------------------------------------------------------
/src/utils/core/mitt-bus.ts:
--------------------------------------------------------------------------------
1 | import mitt from "mitt";
2 |
3 | const mittBus = mitt();
4 |
5 | export default mittBus;
6 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./core/error-handler";
2 | export * from "./core/message";
3 | export * from "./core/scroll-to";
4 | export * from "./core/color";
5 | export * from "./core/is";
6 |
7 | export * from "./component/install";
8 | export * from "./download";
9 | export * from "./excel";
10 | export * from "./helper";
11 | export * from "./tree";
12 | export * from "./id-generator";
13 |
14 | import mittBus from "./core/mitt-bus";
15 | import log from "./core/log";
16 | import NProgress from "./nprogress";
17 | import Print from "./print";
18 |
19 | export { NProgress, Print, mittBus, log };
20 |
--------------------------------------------------------------------------------
/src/utils/nprogress.ts:
--------------------------------------------------------------------------------
1 | import NProgress from "nprogress";
2 | import "nprogress/nprogress.css";
3 |
4 | NProgress.configure({
5 | easing: "ease", // 动画方式
6 | speed: 500, // 递增进度条的速度
7 | showSpinner: true, // 是否显示加载 ico
8 | trickleSpeed: 200, // 自动递增间隔
9 | minimum: 0.3, // 初始化时的最小百分比
10 | });
11 |
12 | export default NProgress;
13 |
--------------------------------------------------------------------------------
/src/views/components/highlight/index.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | 我是 高亮 组件,点击我 试试看
14 |
15 |
16 |
17 |
18 | 文本标签。`string` 类型,默认为 `span`
19 | 高亮的关键词。`string[]` 类型,默认为 `[]`
20 |
21 | 高亮关键词的颜色。`string` 类型,默认 `var(--#{$el-namespace}-color-primary)`
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/views/directives/copy/index.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | 在标签上加 v-copy="data",data 为复制的内容。
10 |
11 |
12 |
13 | 复制
14 |
15 |
16 |
17 |
18 | 如果需要点击事件触发复制,请看
19 | 文本复制
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/views/directives/debounce/index.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 | 防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
13 | 在标签上加 v-debounce="debounceClick",其中 debounceClick 为防抖的事件。
14 | 防抖按钮 (0.5 秒后执行)
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/views/directives/drag/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
拖拽指令
6 |
在标签上加 v-draggable,就可以对该标签进行拖拽,只能加在基础标签,自定义标签不支持
7 |
我可以拖拽哦~
8 |
9 |
10 |
11 |
35 |
--------------------------------------------------------------------------------
/src/views/directives/long-press/index.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 | 在标签上加 v-longPress="longPress",其中 longPress 为长按的事件
12 | 长按 2 秒触发事件
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/views/directives/throttle/index.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 | 节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
12 | 在标签上加 v-throttle="throttleClick",其中 throttleClick 为节流的事件。
13 | 节流按钮 (每隔 1S 秒后执行)
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/views/directives/watermark/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 | 在标签上加 v-waterMarker="{ text: 'Kbt Admin', font: '16px', textColor: 'rgba(180, 180, 180, 0.6)' }"
11 |
12 | 水印文字
13 | 水印大写,支持添加字体
14 | 水印文字颜色
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/views/error/403.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |

16 |
17 |
403
18 | 抱歉,您无权访问该页面~🙅♂️🙅♀️
19 | 返回首页
20 |
21 |
22 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/src/views/error/404.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |

16 |
17 |
404
18 | 抱歉,您访问的页面不存在~🤷♂️🤷♀️
19 | 返回首页
20 |
21 |
22 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/src/views/error/500.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |

16 |
17 |
500
18 | 抱歉,您的网络不见了~🤦♂️🤦♀️
19 | 返回首页
20 |
21 |
22 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/src/views/error/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 | @use "@/styles/mixins/function" as *;
4 | @use "@/styles/variables" as *;
5 |
6 | @include b(error-page) {
7 | gap: 120px;
8 | width: 100%;
9 | height: 100%;
10 |
11 | @include e(detail) {
12 | display: flex;
13 | flex-direction: column;
14 |
15 | h2,
16 | h4 {
17 | padding: 0;
18 | margin: 0;
19 | }
20 |
21 | h2 {
22 | font-size: 60px;
23 | line-height: 1;
24 | color: cssVar(text-gray-700);
25 | }
26 |
27 | h4 {
28 | margin: 10px 0 20px;
29 | font-size: 19px;
30 | font-weight: normal;
31 | color: cssVar(text-gray-500);
32 | }
33 |
34 | // #{$el-namespace} 默认为 el,如果组件迁移到其他项目,且项目架构与此项目不同,则请修改 #{$el-namespace} 为 el
35 | .#{$el-namespace}-button {
36 | width: 100px;
37 | }
38 | }
39 |
40 | @media (max-width: $device-ipad) {
41 | flex-direction: column;
42 | gap: 60px;
43 | margin: 0 !important;
44 |
45 | @include e(detail) {
46 | align-items: center;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/views/excel/upload-excel.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
38 |
--------------------------------------------------------------------------------
/src/views/login/components/qrCode.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 扫码后点击"确认",即可完成登录
14 |
15 |
16 | 返回
17 |
18 |
19 |
20 |
25 |
--------------------------------------------------------------------------------
/src/views/login/loginForm.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/namespace" as *;
3 | @use "@/styles/variables" as *;
4 |
5 | @include b(login-form) {
6 | .#{$el-namespace}-form-item {
7 | margin-bottom: 20px;
8 |
9 | .#{$el-namespace}-button {
10 | margin-left: 0;
11 | }
12 |
13 | @include e(item) {
14 | flex-wrap: wrap;
15 | gap: 20px;
16 | width: 100%;
17 | }
18 |
19 | @include e(third-item) {
20 | display: flex;
21 | align-items: center;
22 | justify-content: space-evenly;
23 | width: 100%;
24 | cursor: pointer;
25 |
26 | .svg-icon:hover {
27 | color: var(--#{$el-namespace}-color-primary);
28 | }
29 | }
30 |
31 | :deep(.#{$el-namespace}-input-group__append) {
32 | padding: 0;
33 | }
34 | }
35 |
36 | @include e(btn) {
37 | gap: 20px;
38 | width: 100%;
39 |
40 | @media (max-width: $device-phone) {
41 | flex-wrap: wrap;
42 | justify-content: center;
43 | }
44 |
45 | .#{$el-namespace}-button {
46 | width: 185px;
47 |
48 | @media (max-width: $device-phone) {
49 | width: 100%;
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/views/login/verifyCode.ts:
--------------------------------------------------------------------------------
1 | import type { FormInstance, FormItemProp } from "element-plus";
2 |
3 | export const useVerifyCode = () => {
4 | const isDisabled = ref(false);
5 | const text = ref("");
6 | let timer: ReturnType | null;
7 |
8 | const start = async (formEl: FormInstance | null, props: FormItemProp, time = 60) => {
9 | if (!formEl) return;
10 | const initTime = time;
11 | await formEl.validateField(props, isValid => {
12 | if (isValid) {
13 | stop();
14 | timer = setInterval(() => {
15 | if (time > 0) {
16 | text.value = `${time}`;
17 | isDisabled.value = true;
18 | time -= 1;
19 | } else {
20 | text.value = "";
21 | isDisabled.value = false;
22 | stop();
23 | time = initTime;
24 | }
25 | }, 1000);
26 | }
27 | });
28 | };
29 |
30 | const stop = () => {
31 | if (timer) {
32 | clearInterval(timer);
33 | timer = null;
34 | }
35 | };
36 |
37 | const end = () => {
38 | text.value = "";
39 | isDisabled.value = false;
40 | stop();
41 | };
42 |
43 | return {
44 | isDisabled,
45 | text,
46 | start,
47 | end,
48 | };
49 | };
50 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-3/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-3/menu1-3-1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-3/menu1-3-2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/nested/menu2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/pro-components/pro-form/detail-pro-form/index.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 | 超级表单详情页
13 |
14 |
15 |
16 |
17 | {{ model }}
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/views/pro-components/pro-steps/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 | 横向
14 |
19 | 竖向
20 |
23 | 点状式
24 |
29 | 箭头式
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/views/profile/components/user-avatar/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(user-avatar) {
5 | position: relative;
6 | display: inline-block;
7 | height: 100%;
8 |
9 | @include e(header) {
10 | &:hover::after {
11 | position: absolute;
12 | inset: 0;
13 | font-size: 24px;
14 | font-style: normal;
15 | line-height: 110px;
16 | color: #eeeeee;
17 | cursor: pointer;
18 | content: "+";
19 | background: rgb(0 0 0 / 50%);
20 | border-radius: 50%;
21 | -webkit-font-smoothing: antialiased;
22 | -moz-osx-font-smoothing: grayscale;
23 | }
24 | }
25 |
26 | @include e(avatar) {
27 | width: 100px;
28 | height: 100px;
29 | border-radius: 50%;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/views/profile/components/user-info/index.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/mixins/bem" as *;
2 | @use "@/styles/mixins/function" as *;
3 |
4 | @include b(user-info) {
5 | color: cssVar(text-gray-700);
6 |
7 | @include e(head) {
8 | text-align: center;
9 |
10 | @include m(avatar) {
11 | width: 100px;
12 | height: 100px;
13 | border-radius: 50%;
14 | }
15 |
16 | @include m(name) {
17 | margin-top: 20px;
18 | margin-bottom: 0;
19 | font-size: 22px;
20 | color: cssVar(text-gray-800);
21 | }
22 |
23 | @include m(desc) {
24 | margin-top: 10px;
25 | font-size: 14px;
26 | }
27 | }
28 |
29 | @include e(info) {
30 | margin-top: 20px;
31 | text-align: center;
32 |
33 | ul {
34 | padding: 0 20px;
35 | }
36 |
37 | span {
38 | font-size: 14px;
39 | }
40 | }
41 |
42 | @include e(tag) {
43 | margin-top: 20px;
44 | text-align: center;
45 |
46 | p {
47 | font-size: 15px;
48 | font-weight: 500;
49 | color: var(--art-text-gray-800);
50 | }
51 |
52 | span {
53 | display: inline-flex;
54 | padding: 3px 6px;
55 | margin: 0 10px 10px 0;
56 | font-size: 12px;
57 | text-wrap: wrap;
58 | background: cssVar(main-bg-color);
59 | border: 1px solid cssVar(border-color);
60 | border-radius: 2px;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/views/profile/index.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/views/table/dynamic-table/fixedHeaderTable.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 | date
23 | address
24 | status
25 | title
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {{ row[col] }}
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/views/table/dynamic-table/index.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
固定表头
9 |
10 |
不固定表头
11 |
12 |
13 |
14 |
15 |
21 |
--------------------------------------------------------------------------------
/src/views/table/dynamic-table/unFixedHeaderTable.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 | date
12 | address
13 | status
14 | title
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{ row[col] }}
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/views/tabs/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useRouter, useRoute, type LocationQueryValue } from "vue-router";
2 |
3 | export function useDetail() {
4 | const route = useRoute();
5 | const router = useRouter();
6 | const id = route.query?.id ? route.query?.id : route.params?.id;
7 |
8 | function toDetail(
9 | index: number | string | string[] | number[] | LocationQueryValue[],
10 | model: string,
11 | noBeforeClose = false
12 | ) {
13 | if (model === "query") {
14 | // 路由跳转
15 | router.push({
16 | name: "Query",
17 | query: noBeforeClose ? { id: String(index) } : { id: String(index), noBeforeClose: "true" },
18 | });
19 | } else {
20 | router.push({
21 | name: "Params",
22 | params: { id: String(index) },
23 | query: noBeforeClose ? {} : { noBeforeClose: "true" },
24 | });
25 | }
26 | }
27 |
28 | return { toDetail, id, router };
29 | }
30 |
--------------------------------------------------------------------------------
/src/views/tabs/params-detail.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | {{ id }} - 详情页内容在此(params 传参)
9 |
10 |
--------------------------------------------------------------------------------
/src/views/tabs/query-detail.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | {{ id }} - 详情页内容在此(query 传参)
9 |
10 |
--------------------------------------------------------------------------------
/src/views/tools/v-context-menu/index.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 | v-contextmenu 插件
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/views/tools/v-menus/components/useComponent.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 | 组件方式打开菜单
20 |
21 |
22 |
26 |
27 |
28 |
40 |
--------------------------------------------------------------------------------
/src/views/tools/v-menus/components/useDirective.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | 指令方式打开菜单(左击)
11 |
12 |
13 |
25 |
--------------------------------------------------------------------------------
/src/views/tools/v-menus/components/useFunction.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | 事件方式打开菜单
14 |
15 |
16 |
28 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [{ "source": "/(.*)", "destination": "/" }]
3 | }
4 |
--------------------------------------------------------------------------------