├── .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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/core/command.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icons/core/fullscreen-exit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icons/core/fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icons/core/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icons/login/qq.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icons/login/wechat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | 14 | -------------------------------------------------------------------------------- /src/components/core/icon/src/components/iconify-offline.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /src/components/core/icon/src/components/iconify-online.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/components/core/icon/src/components/svg-icon.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 20 | -------------------------------------------------------------------------------- /src/components/core/permission/src/role.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 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 | 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 | 29 | -------------------------------------------------------------------------------- /src/components/pro/form-item/src/components/radio.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /src/components/pro/form-item/src/components/select.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 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 | 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 | 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 | 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 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/layout/components/Header/components/fullscreen/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 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 | 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 | 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 | 25 | 26 | 36 | -------------------------------------------------------------------------------- /src/layout/components/Header/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /src/layout/components/Loading/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 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 | 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 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/layout/components/header/components/fullscreen/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 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 | 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 | 19 | -------------------------------------------------------------------------------- /src/layout/components/header/header-left.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | 26 | 36 | -------------------------------------------------------------------------------- /src/layout/components/header/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /src/layout/components/iframe/iframe-blank.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/layout/components/loading/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /src/layout/components/page-content/components/maximize.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 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 | 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 | 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 | 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 | 27 | -------------------------------------------------------------------------------- /src/views/directives/copy/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 24 | -------------------------------------------------------------------------------- /src/views/directives/debounce/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /src/views/directives/drag/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 35 | -------------------------------------------------------------------------------- /src/views/directives/long-press/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /src/views/directives/throttle/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /src/views/directives/watermark/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | -------------------------------------------------------------------------------- /src/views/error/403.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /src/views/error/404.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /src/views/error/500.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 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 | 31 | 32 | 38 | -------------------------------------------------------------------------------- /src/views/login/components/qrCode.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 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 | 14 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-3/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-3/menu1-3-1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-3/menu1-3-2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/pro-components/pro-form/detail-pro-form/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | -------------------------------------------------------------------------------- /src/views/pro-components/pro-steps/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 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 | 42 | -------------------------------------------------------------------------------- /src/views/table/dynamic-table/fixedHeaderTable.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 38 | -------------------------------------------------------------------------------- /src/views/table/dynamic-table/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /src/views/table/dynamic-table/unFixedHeaderTable.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 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 | 10 | -------------------------------------------------------------------------------- /src/views/tabs/query-detail.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/views/tools/v-context-menu/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | -------------------------------------------------------------------------------- /src/views/tools/v-menus/components/useComponent.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | 28 | 40 | -------------------------------------------------------------------------------- /src/views/tools/v-menus/components/useDirective.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 25 | -------------------------------------------------------------------------------- /src/views/tools/v-menus/components/useFunction.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | 16 | 28 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/(.*)", "destination": "/" }] 3 | } 4 | --------------------------------------------------------------------------------