├── .nvmrc ├── src ├── utils │ ├── global.ts │ ├── permission.ts │ ├── navigate.ts │ ├── request.ts │ ├── docs.ts │ ├── useTree.ts │ └── fixtures.ts ├── layouts │ ├── home.vue │ ├── admin │ │ ├── NavbarMenus.vue │ │ ├── Sidebar.vue │ │ ├── Logo.vue │ │ ├── NavBreadcrumb.vue │ │ ├── MenuItem.vue │ │ ├── SidebarMenus.vue │ │ ├── Tagbar.vue │ │ ├── Navbar.vue │ │ └── Config.vue │ ├── mix.vue │ └── default.vue ├── public │ ├── favicon.ico │ ├── image │ │ ├── bg.jpg │ │ ├── datav │ │ │ └── bg.png │ │ ├── detail-bg.png │ │ ├── primevue.png │ │ └── logo │ │ │ └── admin3.png │ ├── static │ │ └── Nuxtjs-Cheat-Sheet.pdf │ └── js │ │ └── tinymce │ │ ├── langs │ │ └── README.md │ │ ├── skins │ │ ├── ui │ │ │ ├── oxide │ │ │ │ └── skin.shadowdom.min.css │ │ │ ├── oxide-dark │ │ │ │ └── skin.shadowdom.min.css │ │ │ ├── tinymce-5 │ │ │ │ └── skin.shadowdom.min.css │ │ │ └── tinymce-5-dark │ │ │ │ └── skin.shadowdom.min.css │ │ └── content │ │ │ ├── default │ │ │ └── content.min.css │ │ │ ├── tinymce-5 │ │ │ └── content.min.css │ │ │ ├── writer │ │ │ └── content.min.css │ │ │ ├── dark │ │ │ └── content.min.css │ │ │ ├── tinymce-5-dark │ │ │ └── content.min.css │ │ │ └── document │ │ │ └── content.min.css │ │ ├── plugins │ │ ├── code │ │ │ └── plugin.min.js │ │ ├── visualblocks │ │ │ └── plugin.min.js │ │ ├── nonbreaking │ │ │ └── plugin.min.js │ │ ├── pagebreak │ │ │ └── plugin.min.js │ │ ├── save │ │ │ └── plugin.min.js │ │ ├── preview │ │ │ └── plugin.min.js │ │ ├── autoresize │ │ │ └── plugin.min.js │ │ ├── anchor │ │ │ └── plugin.min.js │ │ ├── insertdatetime │ │ │ └── plugin.min.js │ │ ├── autolink │ │ │ └── plugin.min.js │ │ ├── autosave │ │ │ └── plugin.min.js │ │ ├── advlist │ │ │ └── plugin.min.js │ │ ├── importcss │ │ │ └── plugin.min.js │ │ ├── directionality │ │ │ └── plugin.min.js │ │ ├── quickbars │ │ │ └── plugin.min.js │ │ ├── visualchars │ │ │ └── plugin.min.js │ │ └── emoticons │ │ │ └── plugin.min.js │ │ └── license.txt ├── pages │ ├── index.vue │ ├── Admin │ │ ├── index.vue │ │ └── Welcome.vue │ ├── Demo │ │ ├── Pdf.vue │ │ ├── CountUp.vue │ │ ├── Table │ │ │ ├── DynamicRow.vue │ │ │ ├── InlineEdit.vue │ │ │ └── Draggable.vue │ │ ├── ChinaAreaCascader.vue │ │ ├── Print.vue │ │ ├── ScrollNotice.vue │ │ ├── Icon.vue │ │ ├── ElementPlus.vue │ │ ├── DataView.vue │ │ └── TinyMCE.vue │ └── Home │ │ └── index.vue ├── plugins │ ├── vueDataUI.ts │ └── svgIcons.ts ├── app.config.ts ├── components │ ├── Admin │ │ ├── AdminChinaAreaCascader.vue │ │ └── AdminContainer.vue │ ├── global │ │ └── Icon.vue │ └── Statistic │ │ ├── StatisticNumber.vue │ │ └── StatisticCircle.vue ├── assets │ ├── svg │ │ ├── shimo-logo.svg │ │ └── train.svg │ ├── css │ │ ├── normalize.css │ │ ├── main.css │ │ └── admin-vars.css │ └── primeLocaleCN.ts ├── app.vue ├── middleware │ └── auth.global.ts └── composables │ ├── permission.ts │ ├── user.ts │ └── config.ts ├── server-mock ├── db │ ├── users_data.json │ └── routers_data.json ├── routes │ └── mock-api │ │ ├── refreshToken.post.ts │ │ ├── login.post.ts │ │ └── routers.get.ts ├── plugins │ └── initDB.ts └── utils │ └── mockEvent.ts ├── .gitignore ├── bump.config.js ├── eslint.config.js ├── .vscode ├── extensions.json ├── vue.code-snippets └── settings.json ├── tsconfig.json ├── deploy.sh ├── unocss.config.ts ├── LICENSE ├── README.md ├── README.en-US.md ├── nuxt.config.ts ├── package.json ├── cliff.toml ├── CHANGELOG.md └── UPDATENOTE.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /src/utils/global.ts: -------------------------------------------------------------------------------- 1 | export const svgIcons = ref() 2 | -------------------------------------------------------------------------------- /src/layouts/home.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirefan/admin3/HEAD/src/public/favicon.ico -------------------------------------------------------------------------------- /src/public/image/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirefan/admin3/HEAD/src/public/image/bg.jpg -------------------------------------------------------------------------------- /src/public/image/datav/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirefan/admin3/HEAD/src/public/image/datav/bg.png -------------------------------------------------------------------------------- /src/public/image/detail-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirefan/admin3/HEAD/src/public/image/detail-bg.png -------------------------------------------------------------------------------- /src/public/image/primevue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirefan/admin3/HEAD/src/public/image/primevue.png -------------------------------------------------------------------------------- /src/public/image/logo/admin3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirefan/admin3/HEAD/src/public/image/logo/admin3.png -------------------------------------------------------------------------------- /src/public/static/Nuxtjs-Cheat-Sheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirefan/admin3/HEAD/src/public/static/Nuxtjs-Cheat-Sheet.pdf -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /server-mock/db/users_data.json: -------------------------------------------------------------------------------- 1 | [{"username":"模拟用户","roles":["admin"],"accessToken":"mocked-access-token","maxAge":60,"refreshToken":"mockedRefreshedToken.adminRefresh"}] -------------------------------------------------------------------------------- /src/plugins/vueDataUI.ts: -------------------------------------------------------------------------------- 1 | import { VueUiXy } from 'vue-data-ui' 2 | 3 | export default defineNuxtPlugin((nuxtApp) => { 4 | nuxtApp.vueApp.component('VueUiXy', VueUiXy) 5 | }) 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # for offline pack-util 2 | node_modules_pack 3 | 4 | yarn.lock 5 | 6 | node_modules 7 | *.log* 8 | .nuxt 9 | .nitro 10 | .cache 11 | .output 12 | .env 13 | dist 14 | -------------------------------------------------------------------------------- /src/public/js/tinymce/langs/README.md: -------------------------------------------------------------------------------- 1 | This is where language files should be placed. 2 | 3 | Please DO NOT translate these directly use this service: https://www.transifex.com/projects/p/tinymce/ 4 | -------------------------------------------------------------------------------- /src/pages/Admin/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /bump.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'bumpp' 2 | 3 | export default defineConfig({ 4 | files: [ 5 | 'README.md', 6 | 'package.json', 7 | 'src/app.config.ts', 8 | ], 9 | }) 10 | -------------------------------------------------------------------------------- /src/plugins/svgIcons.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtPlugin(() => { 2 | svgIcons.value = import.meta.glob('assets/svg/**/*.svg', { 3 | query: '?raw', 4 | import: 'default', 5 | eager: false, 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu( 4 | { 5 | unocss: true, 6 | }, 7 | { 8 | rules: { 9 | 'unocss/order': 'error', 10 | }, 11 | }, 12 | ) 13 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "streetsidesoftware.code-spell-checker", 4 | "usernamehw.errorlens", 5 | "dbaeumer.vscode-eslint", 6 | "antfu.unocss", 7 | "vue.volar" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json", 4 | "compilerOptions": { 5 | "types": [ 6 | "element-plus/global" 7 | ], 8 | "allowJs": true, 9 | "strict": true, 10 | "noImplicitAny": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 拉取最新代码 4 | git fetch 5 | git pull 6 | 7 | # 编译 8 | rm -f yarn.lock 9 | rm -rf node_modules/ 10 | yarn install 11 | yarn build 12 | 13 | # 重启应用 14 | pm2 restart admin3 15 | 16 | # 首次启动应用 17 | # NUXT_PUBLIC_ICONIFY_PROVIDER=http://10.102.81.133:7001 PORT=8001 pm2 start .output/server/index.mjs --name admin3 18 | -------------------------------------------------------------------------------- /server-mock/routes/mock-api/refreshToken.post.ts: -------------------------------------------------------------------------------- 1 | /** 模拟刷新token接口 */ 2 | export default defineMockEventHandler(async () => { 3 | // mock login api response 4 | return { 5 | accessToken: 'mocked-access-token', // 模拟访问 token 6 | refreshToken: 'mockedRefreshedToken.adminRefresh', // 模拟刷新 token 7 | maxAge: 60, // 过期时间, 单位: 秒, 默认 1 分钟过期, 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /src/layouts/admin/NavbarMenus.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | title: 'Admin3 管理后台', 3 | version: '2.1.2', 4 | layout: 'default', 5 | primePreset: 'Aura', 6 | primaryColor: 'sky', 7 | surfaceColor: 'slate', 8 | rippleActive: true, 9 | sidebarDark: false, 10 | sidebarWidth: 250, 11 | sidebarCollapse: false, 12 | navBreadcrumb: true, 13 | tagbar: true, 14 | transitionType: 'slide', 15 | }) 16 | -------------------------------------------------------------------------------- /server-mock/routes/mock-api/login.post.ts: -------------------------------------------------------------------------------- 1 | /* 模拟登录 */ 2 | export default defineMockEventHandler(async (event) => { 3 | const body = await readBody(event) 4 | const storage = useStorage() 5 | const mockedUsers = await storage.getItem('db:users_data.json') as any[] 6 | const user = mockedUsers[0] 7 | /* 模拟用户登录的返回 */ 8 | return { 9 | ...user, 10 | username: body?.username || '模拟用户', 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /server-mock/routes/mock-api/routers.get.ts: -------------------------------------------------------------------------------- 1 | /* 路由接口 */ 2 | export default defineMockEventHandler(async () => { 3 | /* 模拟获取 routers 菜单 */ 4 | const routersData = await useStorage().getItem('db:routers_data.json') 5 | 6 | /* 进行真实请求 */ 7 | // const apiBase = useRuntimeConfig().public.apiBase 8 | // const routersData = await useFetch(apiBase + '/routers', { method: 'post' }) 9 | 10 | return routersData 11 | }) 12 | -------------------------------------------------------------------------------- /server-mock/plugins/initDB.ts: -------------------------------------------------------------------------------- 1 | import * as init_data from '@/utils/fixtures' 2 | 3 | export default defineNitroPlugin(async () => { 4 | const storage = useStorage() 5 | for (const data of Object.entries(init_data)) { 6 | const dbData = await storage.getItem(`db:${data[0]}.json`) 7 | if (!dbData) 8 | await storage.setItem(`db:${data[0]}.json`, data[1]) 9 | } 10 | 11 | // eslint-disable-next-line no-console 12 | console.log('Mocked Database is ready.') 13 | }) 14 | -------------------------------------------------------------------------------- /src/public/js/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 | -------------------------------------------------------------------------------- /server-mock/utils/mockEvent.ts: -------------------------------------------------------------------------------- 1 | import type { EventHandler, EventHandlerRequest } from 'h3' 2 | 3 | export function defineMockEventHandler(handler: EventHandler): EventHandler { 4 | return defineEventHandler(async (event) => { 5 | try { 6 | const response = await handler(event) 7 | return { 8 | success: true, 9 | code: 200, 10 | msg: '请求成功', 11 | data: response, 12 | } 13 | } 14 | catch (err) { 15 | return err 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/public/js/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 | -------------------------------------------------------------------------------- /src/public/js/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/public/js/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /src/components/Admin/AdminChinaAreaCascader.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /unocss.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, 3 | presetAttributify, 4 | presetIcons, 5 | presetTypography, 6 | presetUno, 7 | transformerDirectives, 8 | transformerVariantGroup, 9 | } from 'unocss' 10 | 11 | export default defineConfig({ 12 | safelist: [], 13 | content: { 14 | pipeline: { 15 | include: [ 16 | /\.(ts|vue|html)($|\?)/, 17 | ], 18 | }, 19 | }, 20 | presets: [ 21 | presetUno(), 22 | presetAttributify(), 23 | presetIcons({ scale: 1.2 }), 24 | presetTypography(), 25 | ], 26 | transformers: [ 27 | transformerDirectives(), 28 | transformerVariantGroup(), 29 | ], 30 | }) 31 | -------------------------------------------------------------------------------- /src/layouts/admin/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 28 | -------------------------------------------------------------------------------- /src/layouts/mix.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 31 | -------------------------------------------------------------------------------- /src/assets/svg/shimo-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/utils/permission.ts: -------------------------------------------------------------------------------- 1 | import { storeToRefs } from 'pinia' 2 | 3 | export function checkPermission(permissions: string | string[]): boolean { 4 | const permissionStore = usePermissionStore() 5 | const { userPermissions } = storeToRefs(permissionStore) 6 | const permissionsToCheck = Array.isArray(permissions) ? permissions : [permissions] 7 | return permissionsToCheck.some(permission => 8 | userPermissions.value.map(item => item.label).includes(permission), 9 | ) 10 | } 11 | 12 | export function checkRole(roles: string | string[]): boolean { 13 | const permissionStore = usePermissionStore() 14 | const { userRoles } = storeToRefs(permissionStore) 15 | const rolesToCheck = Array.isArray(roles) ? roles : [roles] 16 | return rolesToCheck.some(role => 17 | userRoles.value.map(item => item.label).includes(role), 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/Demo/Pdf.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/code/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}(); -------------------------------------------------------------------------------- /src/layouts/admin/Logo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 31 | -------------------------------------------------------------------------------- /src/utils/navigate.ts: -------------------------------------------------------------------------------- 1 | import { storeToRefs } from 'pinia' 2 | 3 | export function closeAndNavigateTo(to: any) { 4 | const permissionStore = usePermissionStore() 5 | const { currentMenu } = storeToRefs(permissionStore) 6 | permissionStore.removeMenuTag(currentMenu.value) 7 | return navigateTo(to) 8 | } 9 | 10 | export function closeAndNavigateBack() { 11 | const permissionStore = usePermissionStore() 12 | const { currentMenu } = storeToRefs(permissionStore) 13 | permissionStore.removeMenuTag(currentMenu.value) 14 | const router = useRouter() 15 | return router.back() 16 | } 17 | 18 | export function openUrl(url: any) { 19 | const a = document.createElement('a') 20 | a.setAttribute('href', url) 21 | a.setAttribute('target', '_blank') 22 | a.setAttribute('id', 'temp-link') 23 | document.body.appendChild(a) 24 | a.click() 25 | document.body.removeChild(document.getElementById('temp-link')!) 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Admin/AdminContainer.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | -------------------------------------------------------------------------------- /src/assets/css/normalize.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | background-color: var(--doc-bg); 6 | transition: background-color var(--t-color); 7 | } 8 | 9 | html.dark { 10 | color-scheme: dark; 11 | } 12 | 13 | html { 14 | font-size: 16px; 15 | } 16 | 17 | body { 18 | font-family: var(--font-family); 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | font-size: 1rem; 22 | color: var(--doc-text); 23 | } 24 | 25 | a { 26 | font-weight: 500; 27 | color: var(--doc-text-accent); 28 | text-decoration: none; 29 | overflow-wrap: break-word; 30 | } 31 | 32 | p a code { 33 | font-weight: 400; 34 | color: var(--doc-text-accent); 35 | } 36 | 37 | kbd, 38 | code { 39 | font-family: var(--font-family-code); 40 | margin: 0; 41 | padding: 0.25rem 0.5rem; 42 | font-size: 0.9em; 43 | background-color: var(--doc-bg-light); 44 | border-radius: 3px; 45 | overflow-wrap: break-word; 46 | transition: background-color var(--t-color); 47 | } 48 | 49 | :root { 50 | scroll-behavior: smooth 51 | } 52 | -------------------------------------------------------------------------------- /src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 vampirefan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/public/js/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 | -------------------------------------------------------------------------------- /src/public/js/tinymce/skins/content/tinymce-5/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /src/components/global/Icon.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 38 | 39 | 45 | -------------------------------------------------------------------------------- /src/public/js/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 | -------------------------------------------------------------------------------- /src/public/js/tinymce/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/public/js/tinymce/skins/content/dark/content.min.css: -------------------------------------------------------------------------------- 1 | body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /src/public/js/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 | -------------------------------------------------------------------------------- /src/layouts/admin/NavBreadcrumb.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 38 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/visualblocks/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const s=(t,s,o)=>{t.dom.toggleClass(t.getBody(),"mce-visualblocks"),o.set(!o.get()),((t,s)=>{t.dispatch("VisualBlocks",{state:s})})(t,o.get())},o=("visualblocks_default_state",t=>t.options.get("visualblocks_default_state"));const e=(t,s)=>o=>{o.setActive(s.get());const e=t=>o.setActive(t.state);return t.on("VisualBlocks",e),()=>t.off("VisualBlocks",e)};t.add("visualblocks",((t,l)=>{(t=>{(0,t.options.register)("visualblocks_default_state",{processor:"boolean",default:!1})})(t);const a=(t=>{let s=!1;return{get:()=>s,set:t=>{s=t}}})();((t,o,e)=>{t.addCommand("mceVisualBlocks",(()=>{s(t,0,e)}))})(t,0,a),((t,s)=>{const o=()=>t.execCommand("mceVisualBlocks");t.ui.registry.addToggleButton("visualblocks",{icon:"visualblocks",tooltip:"Show blocks",onAction:o,onSetup:e(t,s)}),t.ui.registry.addToggleMenuItem("visualblocks",{text:"Show blocks",icon:"visualblocks",onAction:o,onSetup:e(t,s)})})(t,a),((t,e,l)=>{t.on("PreviewFormats AfterPreviewFormats",(s=>{l.get()&&t.dom.toggleClass(t.getBody(),"mce-visualblocks","afterpreviewformats"===s.type)})),t.on("init",(()=>{o(t)&&s(t,0,l)}))})(t,0,a)}))}(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Admin 3 4 |

5 | 6 | [English](./README.en-US.md) | **中文** 7 | 8 | ## 简介 9 | 10 | `admin3` 是一个基于 `Nuxt 3` 的后台管理模板,使用最新的`Nuxt 3`、`TypeScript`、`Vue 3`、`Pinia`、`Element-Plus`、`PrimeVue`、`Unocss` 等主流技术。 11 | 12 | **当前版本:** 2.1.2 13 | 14 | **演示地址:** [https://admin3.netlify.app/](https://admin3.netlify.app/) 15 | 16 | **文档地址:** [https://admin3-docs.netlify.app/doc/guide](https://admin3-docs.netlify.app/doc/guide) 17 | 18 | **Github:** [https://github.com/vampirefan/admin3](https://github.com/vampirefan/admin3) 19 | 20 | ## 环境框架 21 | 22 | - Nuxt 3, **`Node.js` 版本要 `>18.18`**, 集成 `Vue 3` 23 | - TypeScript, 拥抱 `any`, 用起来再说。 24 | - pinia, 状态管理 25 | - vueuse, 可复用的函数式组件 26 | - eslint, ["@antfu/eslint-config"], 不必再安装和配置 `prettier` 27 | - czg, 交互式提交(commitizen) 28 | 29 | ## UI 框架 30 | 31 | - Element-Plus 32 | - PrimeVue 33 | - unocss, ["@unocss/nuxt"] 34 | - tailwind preset 35 | - iconify, ["@iconify-json/carbon", "@iconify-json/ep", "@iconify-json/logos", "@iconify-json/twemoji"] 36 | 37 | ## 开发 38 | ``` 39 | clone 40 | yarn install 41 | yarn dev 42 | ``` 43 | -------------------------------------------------------------------------------- /src/public/js/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 | -------------------------------------------------------------------------------- /README.en-US.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Admin 3 4 |

5 | 6 | **English** | [中文](./README.md) 7 | 8 | ## Introduce 9 | A Vue3 admin template, powered by `Nuxt 3`, `TypeScript`, `Vue 3`, `Pinia`, `Element-Plus`, `PrimeVue`, `Unocss`. 10 | 11 | **demo:** [https://admin3.netlify.app/](https://admin3.netlify.app/) 12 | 13 | **docs:** [https://admin3-docs.netlify.app/doc/guide](https://admin3-docs.netlify.app/doc/guide) 14 | 15 | **Github:** [https://github.com/vampirefan/admin3](https://github.com/vampirefan/admin3) 16 | 17 | ## Frameworks 18 | - Nuxt 3,needs `Node.js > 18.18`, contains `Vue 3` 19 | - TypeScript, use `any`, don't be shy 20 | - pinia, state management 21 | - vueuse, collection of Essential Vue Composition Utilities 22 | - eslint, ["@antfu/eslint-config"], no more `prettier` 23 | - czg, a cool commitizen tool 24 | 25 | ## UIs 26 | - Element-Plus 27 | - PrimeVue 28 | - Unocss, ["@unocss/nuxt"] 29 | - tailwind preset 30 | - iconify, ["@iconify-json/carbon", "@iconify-json/ep", "@iconify-json/logos", "@iconify-json/twemoji"] 31 | 32 | ## Useage 33 | ``` 34 | clone 35 | yarn install 36 | yarn dev 37 | ``` 38 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/nonbreaking/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=n=>e=>typeof e===n,a=e("boolean"),o=e("number"),t=n=>e=>e.options.get(n),i=t("nonbreaking_force_tab"),r=t("nonbreaking_wrap"),s=(n,e)=>{let a="";for(let o=0;o{const a=r(n)||n.plugins.visualchars?`${s(" ",e)}`:s(" ",e);n.undoManager.transact((()=>n.insertContent(a)))};var l=tinymce.util.Tools.resolve("tinymce.util.VK");n.add("nonbreaking",(n=>{(n=>{const e=n.options.register;e("nonbreaking_force_tab",{processor:n=>a(n)?{value:n?3:0,valid:!0}:o(n)?{value:n,valid:!0}:{valid:!1,message:"Must be a boolean or number."},default:!1}),e("nonbreaking_wrap",{processor:"boolean",default:!0})})(n),(n=>{n.addCommand("mceNonBreaking",(()=>{c(n,1)}))})(n),(n=>{const e=()=>n.execCommand("mceNonBreaking");n.ui.registry.addButton("nonbreaking",{icon:"non-breaking",tooltip:"Nonbreaking space",onAction:e}),n.ui.registry.addMenuItem("nonbreaking",{icon:"non-breaking",text:"Nonbreaking space",onAction:e})})(n),(n=>{const e=i(n);e>0&&n.on("keydown",(a=>{if(a.keyCode===l.TAB&&!a.isDefaultPrevented()){if(a.shiftKey)return;a.preventDefault(),a.stopImmediatePropagation(),c(n,e)}}))})(n)}))}(); -------------------------------------------------------------------------------- /.vscode/vue.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | // Place your admin3 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 3 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 4 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 5 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 6 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 7 | // Placeholders with the same ids are connected. 8 | // Example: 9 | // "Print to console": { 10 | // "scope": "javascript,typescript", 11 | // "prefix": "log", 12 | // "body": [ 13 | // "console.log('$1');", 14 | // "$2" 15 | // ], 16 | // "description": "Log output to console" 17 | // } 18 | "vue-setup-script": { 19 | "prefix": "script-setup", 20 | "body": [ 21 | "" 25 | ], 26 | "description": "vue-setup-script" 27 | }, 28 | "vue-new-page": { 29 | "prefix": "newpagevue", 30 | "body": [ 31 | "", 33 | "", 34 | "", 39 | ], 40 | "description": "new-vue-page" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import type { SearchParameters } from 'ofetch' 2 | import { ElMessage } from 'element-plus' 3 | import { $fetch } from 'ofetch' 4 | 5 | interface ApiOptions { 6 | method?: 'get' | 'post' | 'put' | 'patch' | 'delete' 7 | body?: RequestInit['body'] | Record 8 | params?: SearchParameters 9 | query?: SearchParameters 10 | headers?: any[] 11 | } 12 | 13 | export function $api(url: string, opts?: ApiOptions) { 14 | const { apiBase } = useRuntimeConfig().public 15 | const target = url.startsWith('http') ? url : `${apiBase}${url}` 16 | 17 | return $fetch(target, { 18 | ...opts, 19 | async onRequest({ options }) { 20 | const userStore = useUserStore() 21 | if (userStore.authToken) { 22 | options.headers = { 23 | ...options.headers, 24 | token: userStore.authToken, 25 | } as any 26 | } 27 | }, 28 | async onRequestError({ error }) { 29 | if (error) 30 | ElMessage.error(`请求出错: ${error}`) 31 | }, 32 | async onResponse({ response }) { 33 | const { success, code, msg, data } = response._data 34 | if (success || code === 200) { 35 | /* 将 返回数据中的 data 作为返回值 */ 36 | response._data = data 37 | } 38 | else if (msg) { 39 | ElMessage.error(msg) 40 | } 41 | }, 42 | async onResponseError({ error }) { 43 | if (error) 44 | ElMessage.error(`返回出错: ${error}`) 45 | }, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /server-mock/db/routers_data.json: -------------------------------------------------------------------------------- 1 | [{"name":"demo","path":"/admin/demo","meta":{"title":"演示demo","icon":"i-carbon-tool-box"},"children":[{"name":"demo-table","path":"/admin/demo/table","meta":{"title":"表格用法","icon":"i-carbon-cross-tab"},"children":[{"name":"demo-table-draggable","path":"/admin/demo/table/draggable","meta":{"title":"可拖拽表格","icon":"i-carbon-move"}},{"name":"demo-table-inline-edit","path":"/admin/demo/table/inline-edit","meta":{"title":"行内编辑","icon":"i-ep-edit-pen"}},{"name":"demo-table-dynamic-row","path":"/admin/demo/table/dynamic-row","meta":{"title":"动态增减行","icon":"i-carbon-row-insert"}}]},{"name":"demo-element-plus","path":"/admin/demo/element-plus","meta":{"title":"Element 组件","icon":"i-ep-element-plus"}},{"name":"demo-china-area-cascader","path":"/admin/demo/china-area-cascader","meta":{"title":"省市区选择器","icon":"i-carbon-map"}},{"name":"demo-count-up","path":"/admin/demo/count-up","meta":{"title":"数字动画","icon":"i-carbon-character-whole-number"}},{"name":"demo-scroll-notice","path":"/admin/demo/scroll-notice","meta":{"title":"滚动通知","icon":"i-carbon-star-review"}},{"name":"demo-animate-background","path":"/admin/demo/animate-background","meta":{"title":"动画页面背景","icon":"i-ep-magic-stick"}},{"name":"demo-pdf","path":"/admin/demo/pdf","meta":{"title":"PDF 预览","icon":"i-carbon-document-pdf"}},{"name":"demo-tinyMCE","path":"/admin/demo/tinyMCE","meta":{"title":"富文本编辑器","icon":"i-carbon-language"}},{"name":"demo-icon","path":"/admin/demo/icon","meta":{"title":"Iconify 在线图标","icon":"i-carbon-face-satisfied"}}]}] -------------------------------------------------------------------------------- /src/app.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 37 | 38 | 65 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/pagebreak/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env");const t=e=>a=>a.options.get(e),r=t("pagebreak_separator"),n=t("pagebreak_split_block"),o="mce-pagebreak",s=e=>{const t=``;return e?`

${t}

`:t};e.add("pagebreak",(e=>{(e=>{const a=e.options.register;a("pagebreak_separator",{processor:"string",default:"\x3c!-- pagebreak --\x3e"}),a("pagebreak_split_block",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mcePageBreak",(()=>{e.insertContent(s(n(e)))}))})(e),(e=>{const a=()=>e.execCommand("mcePageBreak");e.ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:a}),e.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:a})})(e),(e=>{const a=r(e),t=()=>n(e),c=new RegExp(a.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,(e=>"\\"+e)),"gi");e.on("BeforeSetContent",(e=>{e.content=e.content.replace(c,s(t()))})),e.on("PreInit",(()=>{e.serializer.addNodeFilter("img",(r=>{let n,s,c=r.length;for(;c--;)if(n=r[c],s=n.attr("class"),s&&-1!==s.indexOf(o)){const r=n.parent;if(r&&e.schema.getBlockElements()[r.name]&&t()){r.type=3,r.value=a,r.raw=!0,n.remove();continue}n.type=3,n.value=a,n.raw=!0}}))}))})(e),(e=>{e.on("ResolveName",(a=>{"IMG"===a.target.nodeName&&e.dom.hasClass(a.target,o)&&(a.name="pagebreak")}))})(e)}))}(); -------------------------------------------------------------------------------- /src/middleware/auth.global.ts: -------------------------------------------------------------------------------- 1 | import { storeToRefs } from 'pinia' 2 | 3 | const whiteList = ['/', '/Home*'] 4 | 5 | function pathToRegExp(path: string) { 6 | const pattern = path.replace(/\//g, '\/').replace(/\*/g, '.*') 7 | return new RegExp(`^${pattern}$`) 8 | } 9 | 10 | export default defineNuxtRouteMiddleware(async (to: any) => { 11 | 12 | const userStore = useUserStore() 13 | const { authToken, userInfo } = storeToRefs(userStore) 14 | if (authToken.value && userInfo.value.username) { 15 | const permissionStore = usePermissionStore() 16 | const { menus, taggedMenus, currentMenu } = storeToRefs(permissionStore) 17 | // if (menus.value.length === 0) 18 | await permissionStore.generateMenus() 19 | 20 | const tagged = taggedMenus.value.find(menu => menu.path === to.path) 21 | if (tagged) 22 | currentMenu.value = tagged 23 | if (to.matched.length > 0 && to.matched[0].path) { 24 | /* Hack: 去掉浏览器自动给 href 末尾添加的 '/' */ 25 | if (to.path.endsWith('/') && to.fullPath.endsWith('/') && to.href.endsWith('/')) { 26 | to.path = to.path.slice(0, -1) 27 | to.fullPath = to.fullPath.slice(0, -1) 28 | to.href = to.href.slice(0, -1) 29 | } 30 | 31 | const menuItem = useTreeFind(menus.value, (menu: any) => to.matched[0].path.replace(/[()]/g, '') === menu.path) 32 | if (!tagged && menuItem) 33 | permissionStore.addMenuTag({ ...menuItem }) 34 | } 35 | } 36 | 37 | /* 路径不在白名单内,重定向至登陆页面 */ 38 | else if (!whiteList.some(path => pathToRegExp(path).test(to.path))) { 39 | return navigateTo('/') 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/save/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const n=("function",e=>"function"==typeof e);var o=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),t=tinymce.util.Tools.resolve("tinymce.util.Tools");const a=e=>n=>n.options.get(e),c=a("save_enablewhendirty"),i=a("save_onsavecallback"),s=a("save_oncancelcallback"),r=(e,n)=>{e.notificationManager.open({text:n,type:"error"})},l=e=>n=>{const o=()=>{n.setEnabled(!c(e)||e.isDirty())};return o(),e.on("NodeChange dirty",o),()=>e.off("NodeChange dirty",o)};e.add("save",(e=>{(e=>{const n=e.options.register;n("save_enablewhendirty",{processor:"boolean",default:!0}),n("save_onsavecallback",{processor:"function"}),n("save_oncancelcallback",{processor:"function"})})(e),(e=>{e.ui.registry.addButton("save",{icon:"save",tooltip:"Save",enabled:!1,onAction:()=>e.execCommand("mceSave"),onSetup:l(e)}),e.ui.registry.addButton("cancel",{icon:"cancel",tooltip:"Cancel",enabled:!1,onAction:()=>e.execCommand("mceCancel"),onSetup:l(e)}),e.addShortcut("Meta+S","","mceSave")})(e),(e=>{e.addCommand("mceSave",(()=>{(e=>{const t=o.DOM.getParent(e.id,"form");if(c(e)&&!e.isDirty())return;e.save();const a=i(e);if(n(a))return a.call(e,e),void e.nodeChanged();t?(e.setDirty(!1),t.onsubmit&&!t.onsubmit()||("function"==typeof t.submit?t.submit():r(e,"Error: Form submit field collision.")),e.nodeChanged()):r(e,"Error: No form element found.")})(e)})),e.addCommand("mceCancel",(()=>{(e=>{const o=t.trim(e.startContent),a=s(e);n(a)?a.call(e,e):e.resetContent(o)})(e)}))})(e)}))}(); -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import Aura from '@primevue/themes/aura' 2 | import { primeLocaleCN } from './src/assets/primeLocaleCN' 3 | 4 | const baseUrl = '/' 5 | 6 | export default defineNuxtConfig({ 7 | ssr: false, 8 | devtools: { enabled: false }, 9 | srcDir: 'src/', 10 | 11 | /* 模拟后端 */ 12 | serverDir: 'server-mock/', 13 | 14 | nitro: { 15 | devStorage: { 16 | db: { 17 | driver: 'fs', 18 | base: 'server-mock/db', 19 | }, 20 | }, 21 | }, 22 | 23 | runtimeConfig: { 24 | public: { 25 | iconifyProvider: 'https://api.iconify.design', 26 | apiBase: '/mock-api', // 'http://localhost:8001/mock-api' 27 | }, 28 | }, 29 | 30 | app: { 31 | baseURL: baseUrl, 32 | }, 33 | 34 | /* 禁用载入的 nuxt loading 动画 */ 35 | spaLoadingTemplate: false, 36 | 37 | experimental: { 38 | /* 加快首次启动速度 */ 39 | watcher: 'chokidar', 40 | /* 生成静态文件 */ 41 | payloadExtraction: false, 42 | }, 43 | 44 | css: [ 45 | '@/assets/css/main.css', 46 | ], 47 | 48 | modules: [ 49 | '@unocss/nuxt', 50 | '@element-plus/nuxt', 51 | '@primevue/nuxt-module', 52 | '@vueuse/nuxt', 53 | ['@pinia/nuxt', { autoImports: ['defineStore'] }], 54 | ], 55 | 56 | elementPlus: { 57 | defaultLocale: 'zh-cn', 58 | globalConfig: { 59 | size: 'default', 60 | }, 61 | }, 62 | 63 | primevue: { 64 | options: { 65 | locale: primeLocaleCN, 66 | theme: { 67 | preset: Aura, 68 | }, 69 | }, 70 | composables: { 71 | include: '*', 72 | }, 73 | }, 74 | 75 | compatibilityDate: '2024-09-30', 76 | }) 77 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // ========== FileNesting Config ========== 3 | "explorer.fileNesting.enabled": true, 4 | "explorer.fileNesting.expand": false, 5 | "explorer.fileNesting.patterns": { 6 | "README.md": "LICENSE, *.md, *.sh, cliff.toml", 7 | "nuxt.config.ts": "*config.ts, *config.json, .env.*", 8 | "package.json": ".gitignore, yarn.lock, *config.js" 9 | }, 10 | // ========== ESlint Config ========== 11 | "editor.formatOnSave": false, 12 | "editor.codeActionsOnSave": { 13 | "source.fixAll.eslint": "explicit", 14 | "source.organizeImports": "never" 15 | }, 16 | "eslint.validate": [ 17 | "javascript", 18 | "typescript", 19 | "vue", 20 | "html", 21 | "markdown", 22 | "json", 23 | "jsonc", 24 | "yaml" 25 | ], 26 | // ========== cSpell Config ========== 27 | "cSpell.words": [ 28 | "antfu", 29 | "Attributify", 30 | "bumpp", 31 | "cascader", 32 | "Commitizen", 33 | "Cpath", 34 | "Csvg", 35 | "datav", 36 | "dimm", 37 | "docus", 38 | "Gitee", 39 | "iconify", 40 | "Jian", 41 | "keyid", 42 | "nuxt", 43 | "nuxtjs", 44 | "octocat", 45 | "ofetch", 46 | "pinceau", 47 | "Pinia", 48 | "Prefixs", 49 | "primevue", 50 | "shiki", 51 | "shimo", 52 | "sortablejs", 53 | "ssjson", 54 | "tada", 55 | "taze", 56 | "tiktok", 57 | "tinymce", 58 | "todos", 59 | "topo", 60 | "twemoji", 61 | "uncategorized", 62 | "unocss", 63 | "unstorage", 64 | "vitepress", 65 | "vitesse", 66 | "vuepress", 67 | "vueuse", 68 | "Vuex", 69 | "Zhongsong" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/docs.ts: -------------------------------------------------------------------------------- 1 | export const sourceUrl = 'https://github.com/vampirefan/admin3' 2 | export const guideUrl = 'https://admin3-docs.netlify.app' 3 | 4 | export const docList = [ 5 | { label: 'Nuxt3', icon: 'i-logos-nuxt-icon', url: 'https://nuxt.com/' }, 6 | { label: 'Vue3', icon: 'i-logos-vue', url: 'https://cn.vuejs.org/' }, 7 | { label: 'ElementPlus', icon: 'i-logos-element', url: 'https://element-plus.org/' }, 8 | { label: 'PrimeVue', image: '/image/primevue.png', url: 'https://primevue.org/' }, 9 | { label: 'VueUse', icon: 'i-logos-vueuse', url: 'https://vueuse.org/' }, 10 | { label: 'Typescript', icon: 'i-logos-typescript-icon', url: 'https://www.typescriptlang.org/' }, 11 | { label: 'Eslint', icon: 'i-logos-eslint', url: 'https://github.com/antfu/eslint-config' }, 12 | { label: 'Unocss', icon: 'i-logos-unocss', url: 'https://unocss.dev/' }, 13 | { label: 'TailwindCSS', icon: 'i-logos-tailwindcss-icon', url: 'https://tailwindcss.com/' }, 14 | { label: 'Iconify', icon: 'i-simple-icons-iconify', url: 'https://iconify.design/docs/' }, 15 | ] 16 | 17 | export const showcase = { 18 | label: '快速链接', 19 | list: [ 20 | { label: 'Admin3 文档', image: '/image/logo/admin3.png', url: 'https://admin3-docs.netlify.app' }, 21 | { label: 'Admin3 源码', icon: 'i-logos-github-octocat', url: 'https://github.com/vampirefan/admin3' }, 22 | ], 23 | } 24 | 25 | export const pdfFileUrlOptions = [{ 26 | label: '本地 /static/Nuxtjs-Cheat-Sheet.pdf', 27 | value: '/static/Nuxtjs-Cheat-Sheet.pdf', 28 | }, { 29 | label: '在线 https://education.github.com/git-cheat-sheet-education.pdf', 30 | value: 'https://education.github.com/git-cheat-sheet-education.pdf', 31 | }] 32 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/preview/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=e=>t=>t.options.get(e),i=n("content_style"),s=n("content_css_cors"),c=n("body_class"),r=n("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const n=(e=>{var n;let l="";const a=e.dom.encode,d=null!==(n=i(e))&&void 0!==n?n:"";l+='';const m=s(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(t=>{l+='"})),d&&(l+='");const y=r(e),u=c(e),v=' 22 | 23 | 46 | 47 | 68 | -------------------------------------------------------------------------------- /src/assets/css/main.css: -------------------------------------------------------------------------------- 1 | @import "admin-vars.css"; 2 | @import "normalize.css"; 3 | 4 | 5 | :root { 6 | /* font vars */ 7 | --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 8 | --font-family-code: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 9 | 10 | /* transition vars */ 11 | --t-transform: 0.3s ease; 12 | 13 | /* override element-plus vars */ 14 | --el-text-color-secondary: var(--admin-color-800); 15 | --el-border-color-lighter: var(--admin-color-200); 16 | --el-fill-color-light: var(--admin-color-100); 17 | --el-border-color: var(--admin-color-200); 18 | 19 | /* primevue */ 20 | --primary-text-color: var(--p-primary-600); 21 | --primary-color: var(--p-primary-color); 22 | --primary-contrast-color: var(--p-primary-contrast-color); 23 | --primary-hover-color: var(--p-primary-hover-color); 24 | --text-color: var(--p-surface-700); 25 | --text-secondary-color: var(--p-surface-500); 26 | --topbar-sticky-background: rgba(255, 255, 255, .7); 27 | --mobile-menu-background: #ffffff; 28 | --card-border: 1px solid var(--p-surface-200); 29 | --card-background: #ffffff; 30 | --border-color: var(--p-surface-200); 31 | --ground-background: var(--p-surface-50); 32 | --overlay-background: #ffffff; 33 | --hover-background: var(--p-surface-100); 34 | --code-background: var(--p-surface-950); 35 | --high-contrast-text-color: var(--p-surface-900); 36 | --hover-border-color: var(--p-surface-400); 37 | --mark-background: var(--p-surface-200); 38 | --mark-text-color: var(--p-surface-700); 39 | --selection-background: var(--p-surface-200); 40 | --selection-text-color: var(--p-surface-950); 41 | --code-button-text-color: var(--p-surface-300); 42 | --logo-color: var(--text-secondary-color); 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin3", 3 | "type": "module", 4 | "version": "2.1.2", 5 | "anthor": "vampirefan", 6 | "packageManager": "yarn@1.22.22", 7 | "license": "MIT", 8 | "repository": "https://github.com/vampirefan/admin3.git", 9 | "engines": { 10 | "node": ">=18.18.0" 11 | }, 12 | "scripts": { 13 | "build": "nuxt build", 14 | "dev": "nuxt dev --port=8000 --host --no-qr --open", 15 | "generate": "nuxt generate", 16 | "preview": "nuxt preview --port=8000", 17 | "reinstall": "yarn cache clean && rmdir /s /q node_modules && del yarn.lock && yarn install", 18 | "commit": "git add . && czg && git push", 19 | "release": "bumpp", 20 | "taze": "taze -wIr", 21 | "changelog": "git-cliff --output CHANGELOG.md" 22 | }, 23 | "dependencies": { 24 | "@element-plus/nuxt": "^1.1.1", 25 | "@iconify/vue": "^4.3.0", 26 | "@primevue/nuxt-module": "4.2.5", 27 | "@primevue/themes": "4.2.5", 28 | "@province-city-china/level": "^8.5.8", 29 | "@tinymce/tinymce-vue": "^6.1.0", 30 | "element-plus": "^2.9.8", 31 | "html2pdf.js": "^0.10.3", 32 | "pdfobject": "^2.3.1", 33 | "pinia": "^3.0.2", 34 | "pinyin-pro": "^3.26.0", 35 | "primevue": "4.2.5", 36 | "print-js": "^1.6.0", 37 | "shiki": "^3.3.0", 38 | "sortablejs": "^1.15.6" 39 | }, 40 | "devDependencies": { 41 | "@antfu/eslint-config": "^4.12.0", 42 | "@iconify/json": "^2.2.331", 43 | "@pinia/nuxt": "^0.11.0", 44 | "@types/pdfobject": "^2.2.5", 45 | "@types/sortablejs": "^1.15.8", 46 | "@unocss/eslint-plugin": "^0.65.4", 47 | "@unocss/nuxt": "^0.65.4", 48 | "@vueuse/nuxt": "^13.1.0", 49 | "bumpp": "^10.1.0", 50 | "czg": "^1.11.1", 51 | "eslint": "^9.25.1", 52 | "git-cliff": "^2.8.0", 53 | "nuxt": "^3.16.2", 54 | "taze": "^19.0.4", 55 | "typescript": "^5.8.3", 56 | "vue-data-ui": "^2.6.41" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/layouts/admin/MenuItem.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 66 | 67 | 73 | -------------------------------------------------------------------------------- /src/components/Statistic/StatisticNumber.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 52 | 53 | 96 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/autoresize/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),n=o("min_height"),s=o("max_height"),i=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),l=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},a=(e,t,o,n)=>{var s;const i=parseInt(null!==(s=e.getStyle(t,o,n))&&void 0!==s?s:"",10);return isNaN(i)?0:i},g=(e,o,i)=>{var c;const u=e.dom,d=e.getDoc();if(!d)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void l(e,!0);const f=d.documentElement,m=r(e),p=null!==(c=n(e))&&void 0!==c?c:e.getElement().offsetHeight;let h=p;const v=a(u,f,"margin-top",!0),y=a(u,f,"margin-bottom",!0);let C=f.offsetHeight+v+y+m;C<0&&(C=0);const S=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+S>p&&(h=C+S);const z=s(e);if(z&&h>z?(h=z,l(e,!0)):l(e,!1),h!==o.get()){const n=h-o.get();if(u.setStyle(e.getContainer(),"height",h+"px"),o.set(h),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(i)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&n<0&&g(e,o,i)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const t=(e=>{let t=0;return{get:()=>t,set:e=>{t=e}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{g(e,t)}))})(e,t),((e,t)=>{e.on("init",(()=>{const t=i(e),o=e.dom;o.setStyles(e.getDoc().documentElement,{height:"auto"}),o.setStyles(e.getBody(),{paddingLeft:t,paddingRight:t,"min-height":0})})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(o=>{g(e,t,o)}))})(e,t)}}))}(); -------------------------------------------------------------------------------- /src/pages/Demo/Table/DynamicRow.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 74 | -------------------------------------------------------------------------------- /src/pages/Demo/Table/InlineEdit.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 79 | 80 | 82 | -------------------------------------------------------------------------------- /src/composables/permission.ts: -------------------------------------------------------------------------------- 1 | /* 可以在这里定义默认的首页 */ 2 | import { defineStore } from 'pinia' 3 | 4 | export const constantRoutes: any[] = [{ 5 | path: '/Admin/Welcome', 6 | meta: { 7 | icon: 'i-ep-menu', 8 | title: '首页', 9 | affix: true, 10 | }, 11 | }] 12 | export const usePermissionStore = defineStore('permission', () => { 13 | const menus = ref([]) 14 | const userRoles = ref(useLocalStorage('admin3-user-roles', [])) 15 | const userPermissions = ref(useLocalStorage('admin3-user-permissions', [])) 16 | /* 如果想帮用户保存 taggedMenus ,可以将其写入 localStorage */ 17 | // const taggedMenus = ref(useLocalStorage('tagged-menus', constantRoutes)) 18 | const taggedMenus = ref([...constantRoutes]) 19 | const currentMenu = ref() 20 | 21 | async function generateMenus() { 22 | /* 利用模拟后端返回路由菜单数据 */ 23 | // const routersResponse = await $api('/routers') 24 | 25 | /* 前端直接返回路由菜单数据,routers_data 在文件 `@/utils/fixtures.ts` 中 */ 26 | const routersResponse = routers_data 27 | menus.value = constantRoutes.concat(routersResponse) 28 | } 29 | 30 | function addMenuTag(menuTag: any) { 31 | taggedMenus.value.push(menuTag) 32 | currentMenu.value = menuTag 33 | } 34 | 35 | function removeMenuTag(menuTag: any) { 36 | const menuIndex = taggedMenus.value.findIndex(menu => menu.path === menuTag.path) 37 | /* 至少保留一个 MenuTag */ 38 | if (taggedMenus.value.length > 1) 39 | taggedMenus.value.splice(menuIndex, 1) 40 | 41 | /* 如果不是第一个 tag, 则返回前一个 MenuTag;否则返回第一个 MenuTag */ 42 | if (menuIndex > 0) { 43 | currentMenu.value = taggedMenus.value[menuIndex - 1] 44 | return currentMenu.value 45 | } 46 | else { 47 | currentMenu.value = taggedMenus.value[0] 48 | return currentMenu.value 49 | } 50 | } 51 | 52 | function removeAllTaggedMenus() { 53 | if (taggedMenus.value.length > 1) 54 | taggedMenus.value.splice(1, taggedMenus.value.length - 1) 55 | currentMenu.value = taggedMenus.value[0] 56 | return currentMenu.value 57 | } 58 | 59 | return { 60 | userRoles, 61 | userPermissions, 62 | menus, 63 | taggedMenus, 64 | currentMenu, 65 | generateMenus, 66 | addMenuTag, 67 | removeMenuTag, 68 | removeAllTaggedMenus, 69 | } 70 | }) 71 | -------------------------------------------------------------------------------- /src/composables/user.ts: -------------------------------------------------------------------------------- 1 | import { skipHydrate } from 'pinia' 2 | 3 | /** 4 | * 无感刷新 token 5 | * refreshToken 的过期时间(比如30天)应大于 accessToken 的过期时间(比如2小时) 6 | * 在 cookie(过期自动销毁)里存放: { auth-token: accessToken } 7 | * 在 localStorage(浏览器关闭自动销毁)里存放:{ user-info: { username, roles, refreshToken, maxAge } } 8 | */ 9 | // const authTokenOption = { maxAge: 2 * 60 * 60 } 10 | const userInfoInit = { username: '', roles: [] as Array, refreshToken: '', maxAge: 60 } 11 | 12 | export const useUserStore = defineStore('user', () => { 13 | /* state */ 14 | const authToken = ref(useLocalStorage('admin3-auth-token', '')) 15 | const userInfo = ref(useLocalStorage('admin3-user-info', userInfoInit)) 16 | 17 | /* actions */ 18 | /* 登入 */ 19 | async function login(loginData: any) { 20 | /* 利用模拟后端返回用户登录结果 */ 21 | const loginResponse = await $api('/login', { method: 'post', body: loginData }) 22 | /* 前端直接模拟返回用户登录结果 */ 23 | // const loginResponse = { 24 | // ...users_data[0], 25 | // username: loginData.username 26 | // } 27 | 28 | if (loginResponse) { 29 | const { username, roles, accessToken, maxAge, refreshToken } = loginResponse 30 | authToken.value = accessToken 31 | userInfo.value = { username, roles, refreshToken, maxAge } 32 | } 33 | return loginResponse 34 | } 35 | 36 | /* 前端登出(不调用接口) */ 37 | async function logOut() { 38 | authToken.value = '' 39 | userInfo.value = { ...userInfoInit } 40 | navigateTo('/') 41 | } 42 | 43 | /* 刷新 token */ 44 | async function handRefreshToken(refreshData: any) { 45 | const refreshTokenResponse: any = await $api('/refreshToken', { method: 'post', body: refreshData }) 46 | if (refreshTokenResponse) { 47 | const { accessToken, maxAge, refreshToken } = refreshTokenResponse 48 | authToken.value = accessToken 49 | userInfo.value.refreshToken = refreshToken 50 | userInfo.value.maxAge = maxAge 51 | } 52 | return refreshTokenResponse 53 | } 54 | 55 | /** 56 | * 为了让 userInfo 能先从客户端的 LocalStorage 中取值。使用 skipHydrate() 的辅助函数。 57 | * 详见:https://pinia.vuejs.org/zh/cookbook/composables.html#ssr 58 | */ 59 | return { 60 | authToken, 61 | userInfo: skipHydrate(userInfo), 62 | login, 63 | logOut, 64 | handRefreshToken, 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /src/assets/svg/train.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/anchor/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=("allow_html_in_named_anchor",e=>e.options.get("allow_html_in_named_anchor"));const a="a:not([href])",r=e=>!e,i=e=>e.getAttribute("id")||e.getAttribute("name")||"",l=e=>(e=>"a"===e.nodeName.toLowerCase())(e)&&!e.getAttribute("href")&&""!==i(e),s=e=>e.dom.getParent(e.selection.getStart(),a),d=(e,a)=>{const r=s(e);r?((e,t,o)=>{o.removeAttribute("name"),o.id=t,e.addVisual(),e.undoManager.add()})(e,a,r):((e,a)=>{e.undoManager.transact((()=>{n(e)||e.selection.collapse(!0),e.selection.isCollapsed()?e.insertContent(e.dom.createHTML("a",{id:a})):((e=>{const n=e.dom;t(n).walk(e.selection.getRng(),(e=>{o.each(e,(e=>{var t;l(t=e)&&!t.firstChild&&n.remove(e,!1)}))}))})(e),e.formatter.remove("namedAnchor",void 0,void 0,!0),e.formatter.apply("namedAnchor",{value:a}),e.addVisual())}))})(e,a),e.focus()},c=e=>(e=>r(e.attr("href"))&&!r(e.attr("id")||e.attr("name")))(e)&&!e.firstChild,m=e=>t=>{for(let o=0;o{(e=>{(0,e.options.register)("allow_html_in_named_anchor",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreInit",(()=>{e.parser.addNodeFilter("a",m("false")),e.serializer.addNodeFilter("a",m(null))}))})(e),(e=>{e.addCommand("mceAnchor",(()=>{(e=>{const t=(e=>{const t=s(e);return t?i(t):""})(e);e.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:t=>{((e,t)=>/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(d(e,t),!0):(e.windowManager.alert("ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1))(e,t.getData().id)&&t.close()}})})(e)}))})(e),(e=>{const t=()=>e.execCommand("mceAnchor");e.ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:t,onSetup:t=>e.selection.selectorChangedWithUnbind("a:not([href])",t.setActive).unbind}),e.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:t})})(e),e.on("PreInit",(()=>{(e=>{e.formatter.register("namedAnchor",{inline:"a",selector:a,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:(e,t,o)=>l(e)})})(e)}))}))}(); -------------------------------------------------------------------------------- /src/layouts/admin/SidebarMenus.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 51 | 52 | 77 | -------------------------------------------------------------------------------- /src/components/Statistic/StatisticCircle.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 36 | 37 | 100 | -------------------------------------------------------------------------------- /src/assets/css/admin-vars.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* layout vars */ 3 | --admin-navbar-height: 4rem; 4 | --admin-tagbar-height: 2.5rem; 5 | --admin-sidebar-width: 220px; 6 | --admin-sidebar-collapse-width: calc(var(--el-menu-icon-width) + var(--el-menu-base-level-padding) * 2); 7 | 8 | /* 基础配色:这里直接使用 primevue 的配色,也可以使用下方我精心调制的 庄重蓝、若依灰 2 种配色 */ 9 | --admin-color-50: var(--p-primary-50); 10 | --admin-color-100: var(--p-primary-100); 11 | --admin-color-200: var(--p-primary-200); 12 | --admin-color-300: var(--p-primary-300); 13 | --admin-color-400: var(--p-primary-400); 14 | --admin-color-500: var(--p-primary-500); 15 | --admin-color-600: var(--p-primary-600); 16 | --admin-color-700: var(--p-primary-700); 17 | --admin-color-800: var(--p-primary-800); 18 | --admin-color-900: var(--p-primary-900); 19 | --admin-color-950: var(--p-primary-950); 20 | 21 | /* 庄重蓝 */ 22 | /* --admin-color-50: #f3f8fc; 23 | --admin-color-100: #e5eef9; 24 | --admin-color-200: #c5dcf2; 25 | --admin-color-300: #92bde7; 26 | --admin-color-400: #589cd8; 27 | --admin-color-500: #327fc5; 28 | --admin-color-600: #2364a6; 29 | --admin-color-700: #1d5087; 30 | --admin-color-800: #1c4570; 31 | --admin-color-900: #1c3b5e; 32 | --admin-color-950: #13263e; */ 33 | 34 | /* 若依灰 */ 35 | /* --admin-color-50: #f5f7fa; 36 | --admin-color-100: #eaeef4; 37 | --admin-color-200: #d1dae6; 38 | --admin-color-300: #a9b9d0; 39 | --admin-color-400: #7b95b5; 40 | --admin-color-500: #5b789c; 41 | --admin-color-600: #476082; 42 | --admin-color-700: #3a4d6a; 43 | --admin-color-800: #324157; 44 | --admin-color-900: #2e3a4c; 45 | --admin-color-950: #1f2532; */ 46 | 47 | /* 丛林绿 */ 48 | /* --admin-color-50: #f4f7fb; 49 | --admin-color-100: #e8eff6; 50 | --admin-color-200: #ccddeb; 51 | --admin-color-300: #9fc2da; 52 | --admin-color-400: #82b6d9; 53 | --admin-color-500: #73aacf; 54 | --admin-color-600: #6ca1c4; 55 | --admin-color-700: #4985ae; 56 | --admin-color-800: #386c93; 57 | --admin-color-900: #2e5676; 58 | --admin-color-950: #294963; */ 59 | 60 | /* sidebar dark as default */ 61 | --admin-sidebar-bg-color: var(--admin-color-800); 62 | --admin-sidebar-hover-bg-color: var(--admin-color-50); 63 | --admin-sidebar-text-color: var(--admin-color-50); 64 | --admin-sidebar-hover-text-color: var(--admin-color-900); 65 | 66 | /* navbar dark as default */ 67 | --admin-navbar-bg-color: var(--admin-color-900); 68 | 69 | /* transition */ 70 | --admin-transition-duration: 0.3s; 71 | --admin-transition-function-ease-in-out-bezier: cubic-bezier(0.645,0.045,0.355,1); 72 | } 73 | -------------------------------------------------------------------------------- /src/pages/Demo/ChinaAreaCascader.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 71 | 72 | 82 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/insertdatetime/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),a=t("insertdatetime_dateformat"),r=t("insertdatetime_timeformat"),n=t("insertdatetime_formats"),s=t("insertdatetime_element"),i="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),o="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),l="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),m="January February March April May June July August September October November December".split(" "),c=(e,t)=>{if((e=""+e).length(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=t.replace("%D","%m/%d/%Y")).replace("%r","%I:%M:%S %p")).replace("%Y",""+a.getFullYear())).replace("%y",""+a.getYear())).replace("%m",c(a.getMonth()+1,2))).replace("%d",c(a.getDate(),2))).replace("%H",""+c(a.getHours(),2))).replace("%M",""+c(a.getMinutes(),2))).replace("%S",""+c(a.getSeconds(),2))).replace("%I",""+((a.getHours()+11)%12+1))).replace("%p",a.getHours()<12?"AM":"PM")).replace("%B",""+e.translate(m[a.getMonth()]))).replace("%b",""+e.translate(l[a.getMonth()]))).replace("%A",""+e.translate(o[a.getDay()]))).replace("%a",""+e.translate(i[a.getDay()]))).replace("%%","%"),u=(e,t)=>{if(s(e)){const a=d(e,t);let r;r=/%[HMSIp]/.test(t)?d(e,"%Y-%m-%dT%H:%M"):d(e,"%Y-%m-%d");const n=e.dom.getParent(e.selection.getStart(),"time");n?((e,t,a,r)=>{const n=e.dom.create("time",{datetime:a},r);e.dom.replace(n,t),e.selection.select(n,!0),e.selection.collapse(!1)})(e,n,r,a):e.insertContent('")}else e.insertContent(d(e,t))};var p=tinymce.util.Tools.resolve("tinymce.util.Tools");e.add("insertdatetime",(e=>{(e=>{const t=e.options.register;t("insertdatetime_dateformat",{processor:"string",default:e.translate("%Y-%m-%d")}),t("insertdatetime_timeformat",{processor:"string",default:e.translate("%H:%M:%S")}),t("insertdatetime_formats",{processor:"string[]",default:["%H:%M:%S","%Y-%m-%d","%I:%M:%S %p","%D"]}),t("insertdatetime_element",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mceInsertDate",((t,r)=>{u(e,null!=r?r:a(e))})),e.addCommand("mceInsertTime",((t,a)=>{u(e,null!=a?a:r(e))}))})(e),(e=>{const t=n(e),a=(e=>{let t=e;return{get:()=>t,set:e=>{t=e}}})((e=>{const t=n(e);return t.length>0?t[0]:r(e)})(e)),s=t=>e.execCommand("mceInsertDate",!1,t);e.ui.registry.addSplitButton("insertdatetime",{icon:"insert-time",tooltip:"Insert date/time",select:e=>e===a.get(),fetch:a=>{a(p.map(t,(t=>({type:"choiceitem",text:d(e,t),value:t}))))},onAction:e=>{s(a.get())},onItemAction:(e,t)=>{a.set(t),s(t)}});const i=e=>()=>{a.set(e),s(e)};e.ui.registry.addNestedMenuItem("insertdatetime",{icon:"insert-time",text:"Date/time",getSubmenuItems:()=>p.map(t,(t=>({type:"menuitem",text:d(e,t),onAction:i(t)})))})})(e)}))}(); -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/autolink/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),n=t("autolink_pattern"),o=t("link_default_target"),r=t("link_default_protocol"),a=t("allow_unsafe_link_target"),s=("string",e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(a=o.constructor)||void 0===a?void 0:a.name)===r.name)?"string":t;var n,o,r,a})(e));const l=(void 0,e=>undefined===e);const i=e=>!(e=>null==e)(e),c=Object.hasOwnProperty,d=e=>"\ufeff"===e;var u=tinymce.util.Tools.resolve("tinymce.dom.TextSeeker");const f=e=>/^[(\[{ \u00a0]$/.test(e),g=(e,t,n)=>{for(let o=t-1;o>=0;o--){const t=e.charAt(o);if(!d(t)&&n(t))return o}return-1},m=(e,t)=>{var o;const a=e.schema.getVoidElements(),s=n(e),{dom:i,selection:d}=e;if(null!==i.getParent(d.getNode(),"a[href]"))return null;const m=d.getRng(),k=u(i,(e=>{return i.isBlock(e)||(t=a,n=e.nodeName.toLowerCase(),c.call(t,n))||"false"===i.getContentEditable(e);var t,n})),{container:p,offset:y}=((e,t)=>{let n=e,o=t;for(;1===n.nodeType&&n.childNodes[o];)n=n.childNodes[o],o=3===n.nodeType?n.data.length:n.childNodes.length;return{container:n,offset:o}})(m.endContainer,m.endOffset),h=null!==(o=i.getParent(p,i.isBlock))&&void 0!==o?o:i.getRoot(),w=k.backwards(p,y+t,((e,t)=>{const n=e.data,o=g(n,t,(r=f,e=>!r(e)));var r,a;return-1===o||(a=n[o],/[?!,.;:]/.test(a))?o:o+1}),h);if(!w)return null;let v=w.container;const _=k.backwards(w.container,w.offset,((e,t)=>{v=e;const n=g(e.data,t,f);return-1===n?n:n+1}),h),A=i.createRng();_?A.setStart(_.container,_.offset):A.setStart(v,0),A.setEnd(w.container,w.offset);const C=A.toString().replace(/\uFEFF/g,"").match(s);if(C){let t=C[0];return $="www.",(b=t).length>=$.length&&b.substr(0,0+$.length)===$?t=r(e)+"://"+t:((e,t,n=0,o)=>{const r=e.indexOf(t,n);return-1!==r&&(!!l(o)||r+t.length<=o)})(t,"@")&&!(e=>/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(e))(t)&&(t="mailto:"+t),{rng:A,url:t}}var b,$;return null},k=(e,t)=>{const{dom:n,selection:r}=e,{rng:l,url:i}=t,c=r.getBookmark();r.setRng(l);const d="createlink",u={command:d,ui:!1,value:i};if(!e.dispatch("BeforeExecCommand",u).isDefaultPrevented()){e.getDoc().execCommand(d,!1,i),e.dispatch("ExecCommand",u);const t=o(e);if(s(t)){const o=r.getNode();n.setAttrib(o,"target",t),"_blank"!==t||a(e)||n.setAttrib(o,"rel","noopener")}}r.moveToBookmark(c),e.nodeChanged()},p=e=>{const t=m(e,-1);i(t)&&k(e,t)},y=p;e.add("autolink",(e=>{(e=>{const t=e.options.register;t("autolink_pattern",{processor:"regexp",default:new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i")}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"})})(e),(e=>{e.on("keydown",(t=>{13!==t.keyCode||t.isDefaultPrevented()||(e=>{const t=m(e,0);i(t)&&k(e,t)})(e)})),e.on("keyup",(t=>{32===t.keyCode?p(e):(48===t.keyCode&&t.shiftKey||221===t.keyCode)&&y(e)}))})(e)}))}(); -------------------------------------------------------------------------------- /src/utils/useTree.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Find in tree arrays 3 | * @param { Array } tree { [prop:string]:any, children:any[] }[] 4 | * @param { Function } func find function 5 | */ 6 | export function useTreeFind(tree: any[], func: any): any { 7 | for (const data of tree) { 8 | if (func(data)) 9 | return data 10 | if (data.children) { 11 | const res = useTreeFind(data.children, func) 12 | if (res) 13 | return res 14 | } 15 | } 16 | return null 17 | } 18 | 19 | /** 20 | * Find in tree arrays 21 | * @param { Array } tree { [prop:string]:any, children:any[] }[] 22 | * @param { Function } func find function 23 | */ 24 | export function useTreeFindParent(tree: any[], func: any): any { 25 | const isNil = (array: any) => !(Array.isArray(array) && array.length) 26 | for (const node of tree) { 27 | if (func(node)) 28 | return tree 29 | if (!isNil(node.children)) { 30 | const res = useTreeFind(node.children, func) 31 | if (res) 32 | return node 33 | } 34 | } 35 | return null 36 | } 37 | 38 | /** 39 | * Find Path in tree arrays 40 | * @param { Array } tree { [prop:string]:any, children:any[] }[] 41 | * @param { Function } func find function 42 | */ 43 | export function useTreeFindPath(tree: any[], func: any, path = [] as any[]): any[] { 44 | if (!tree) 45 | return [] 46 | for (const data of tree) { 47 | path.push(data) 48 | if (func(data)) 49 | return path 50 | if (data.children) { 51 | const findChildren = useTreeFindPath(data.children, func, path) 52 | if (findChildren.length) 53 | return findChildren 54 | } 55 | path.pop() 56 | } 57 | return [] 58 | } 59 | 60 | /** 61 | * Map func for tree arrays 62 | * @param { Array } tree { [prop:string]:any, children:any[] }[] 63 | * @param { Function } func map function 64 | */ 65 | export function useTreeMap(tree: any[], func: any) { 66 | return tree.map((data: any) => { 67 | const result = func(data) 68 | if (data.children) 69 | result.children = useTreeMap(data.children, func) 70 | return result 71 | }) 72 | } 73 | 74 | /** 75 | * Foreach tree arrays 前序遍历 node 节点 76 | * @param { Array } tree { [prop:string]:any, children:any[] }[] 77 | * @param { Function } func callback function 78 | */ 79 | export function useTreeForeachPreOrder(tree: any[], func: any, level = 0) { 80 | const isNil = (array: any) => !(Array.isArray(array) && array.length) 81 | for (const node of tree) { 82 | node.level = level 83 | func(node) 84 | if (!isNil(node.children)) 85 | useTreeForeachPreOrder(node.children, func, level + 1) 86 | } 87 | } 88 | 89 | /** 90 | * 列表结构转树结构 91 | * @param { Array } tree { [prop:string]:any, children:any[] }[] 92 | * @param { Function } func callback function 93 | */ 94 | 95 | export function useListToTree(list: any[], id = 'id', parent = 'parentId') { 96 | const info = list.reduce((map, node) => { 97 | map[node[id]] = node 98 | node.children = [] 99 | return map 100 | }, {}) 101 | return list.filter((node) => { 102 | info[node[parent]] && info[node[parent]].children.push(node) 103 | return !node[parent] 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/autosave/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=("string",t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(r=o=t,(a=String).prototype.isPrototypeOf(r)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===a.name)?"string":e;var r,o,a,s})(t));const r=(void 0,t=>undefined===t);var o=tinymce.util.Tools.resolve("tinymce.util.Delay"),a=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),s=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=t=>{const e=/^(\d+)([ms]?)$/.exec(t);return(e&&e[2]?{s:1e3,m:6e4}[e[2]]:1)*parseInt(t,10)},i=t=>e=>e.options.get(t),u=i("autosave_ask_before_unload"),l=i("autosave_restore_when_empty"),c=i("autosave_interval"),d=i("autosave_retention"),m=t=>{const e=document.location;return t.options.get("autosave_prefix").replace(/{path}/g,e.pathname).replace(/{query}/g,e.search).replace(/{hash}/g,e.hash).replace(/{id}/g,t.id)},v=(t,e)=>{if(r(e))return t.dom.isEmpty(t.getBody());{const r=s.trim(e);if(""===r)return!0;{const e=(new DOMParser).parseFromString(r,"text/html");return t.dom.isEmpty(e)}}},f=t=>{var e;const r=parseInt(null!==(e=a.getItem(m(t)+"time"))&&void 0!==e?e:"0",10)||0;return!((new Date).getTime()-r>d(t)&&(p(t,!1),1))},p=(t,e)=>{const r=m(t);a.removeItem(r+"draft"),a.removeItem(r+"time"),!1!==e&&(t=>{t.dispatch("RemoveDraft")})(t)},g=t=>{const e=m(t);!v(t)&&t.isDirty()&&(a.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),a.setItem(e+"time",(new Date).getTime().toString()),(t=>{t.dispatch("StoreDraft")})(t))},y=t=>{var e;const r=m(t);f(t)&&(t.setContent(null!==(e=a.getItem(r+"draft"))&&void 0!==e?e:"",{format:"raw"}),(t=>{t.dispatch("RestoreDraft")})(t))};var D=tinymce.util.Tools.resolve("tinymce.EditorManager");const h=t=>e=>{e.setEnabled(f(t));const r=()=>e.setEnabled(f(t));return t.on("StoreDraft RestoreDraft RemoveDraft",r),()=>t.off("StoreDraft RestoreDraft RemoveDraft",r)};t.add("autosave",(t=>((t=>{const r=t.options.register,o=t=>{const r=e(t);return r?{value:n(t),valid:r}:{valid:!1,message:"Must be a string."}};r("autosave_ask_before_unload",{processor:"boolean",default:!0}),r("autosave_prefix",{processor:"string",default:"tinymce-autosave-{path}{query}{hash}-{id}-"}),r("autosave_restore_when_empty",{processor:"boolean",default:!1}),r("autosave_interval",{processor:o,default:"30s"}),r("autosave_retention",{processor:o,default:"20m"})})(t),(t=>{t.editorManager.on("BeforeUnload",(t=>{let e;s.each(D.get(),(t=>{t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&u(t)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))})),e&&(t.preventDefault(),t.returnValue=e)}))})(t),(t=>{(t=>{const e=c(t);o.setEditorInterval(t,(()=>{g(t)}),e)})(t);const e=()=>{(t=>{t.undoManager.transact((()=>{y(t),p(t)})),t.focus()})(t)};t.ui.registry.addButton("restoredraft",{tooltip:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)}),t.ui.registry.addMenuItem("restoredraft",{text:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)})})(t),t.on("init",(()=>{l(t)&&t.dom.isEmpty(t.getBody())&&y(t)})),(t=>({hasDraft:()=>f(t),storeDraft:()=>g(t),restoreDraft:()=>y(t),removeDraft:e=>p(t,e),isEmpty:e=>v(t,e)}))(t))))}(); -------------------------------------------------------------------------------- /src/pages/Demo/Table/Draggable.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 108 | -------------------------------------------------------------------------------- /src/utils/fixtures.ts: -------------------------------------------------------------------------------- 1 | export const routers_data = [ 2 | { 3 | name: 'Demo', /* 路由名称(唯一) */ 4 | path: '/Demo', /* 路由地址(必填) */ 5 | meta: { 6 | title: '演示 DEMO', /* 菜单标题 */ 7 | icon: 'i-carbon-tool-box', /* 菜单图标(iconify 使用 'i-',svg 使用 'svg-') */ 8 | }, 9 | children: [{ 10 | name: 'Demo-Table', 11 | path: '/Demo/Table', 12 | meta: { 13 | title: '表格用法', 14 | icon: 'i-carbon-cross-tab', 15 | }, 16 | children: [{ 17 | name: 'Demo-Table-Draggable', 18 | path: '/Demo/Table/Draggable', 19 | meta: { 20 | title: '可拖拽表格', 21 | icon: 'i-carbon-move', 22 | }, 23 | }, { 24 | name: 'Demo-Table-InlineEdit', 25 | path: '/Demo/Table/InlineEdit', 26 | meta: { 27 | title: '行内编辑', 28 | icon: 'i-ep-edit-pen', 29 | }, 30 | }, { 31 | name: 'Demo-Table-DynamicRow', 32 | path: '/Demo/Table/DynamicRow', 33 | meta: { 34 | title: '动态增减行', 35 | icon: 'i-carbon-row-insert', 36 | }, 37 | }], 38 | }, { 39 | name: 'Demo-DataView', 40 | path: '/Demo/DataView', 41 | meta: { 42 | title: '展示大屏', 43 | icon: 'i-carbon-screen', 44 | }, 45 | }, { 46 | name: 'Demo-ElementPlus', 47 | path: '/Demo/ElementPlus', 48 | meta: { 49 | title: 'Element 组件', 50 | icon: 'i-ep-element-plus', 51 | }, 52 | }, { 53 | name: 'Demo-PrimeVue', 54 | path: '/Demo/PrimeVue', 55 | meta: { 56 | title: 'PrimeVue 组件', 57 | icon: 'i-prime-prime', 58 | }, 59 | }, { 60 | name: 'Demo-ChinaAreaCascader', 61 | path: '/Demo/ChinaAreaCascader', 62 | meta: { 63 | title: '省市区选择器', 64 | icon: 'i-carbon-map', 65 | }, 66 | }, { 67 | name: 'Demo-CountUp', 68 | path: '/Demo/CountUp', 69 | meta: { 70 | title: '数字动画', 71 | icon: 'i-carbon-character-whole-number', 72 | // noCache: true, 73 | }, 74 | }, { 75 | name: 'Demo-ScrollNotice', 76 | path: '/Demo/ScrollNotice', 77 | meta: { 78 | title: '滚动通知', 79 | icon: 'i-carbon-star-review', 80 | }, 81 | }, { 82 | name: 'Demo-Pdf', 83 | path: '/Demo/Pdf', 84 | meta: { 85 | title: 'PDF 预览', 86 | icon: 'i-carbon-document-pdf', 87 | }, 88 | }, { 89 | name: 'Demo-TinyMCE', 90 | path: '/Demo/TinyMCE', 91 | meta: { 92 | title: '富文本编辑器', 93 | icon: 'i-carbon-language', 94 | }, 95 | }, { 96 | name: 'Demo-Print', 97 | path: '/Demo/Print', 98 | meta: { 99 | title: '区域打印', 100 | icon: 'i-carbon-printer', 101 | }, 102 | }, { 103 | name: 'Demo-Icon', 104 | path: '/Demo/Icon', 105 | meta: { 106 | title: 'Iconify 在线图标', 107 | icon: 'i-carbon-face-satisfied', 108 | }, 109 | }], 110 | }, 111 | ] 112 | export const users_data = [ 113 | { 114 | username: '模拟用户', // 模拟登录用户 115 | roles: ['admin'], // 模拟角色 116 | accessToken: 'mocked-access-token', // 模拟访问 token 117 | maxAge: 60, // 过期时间, 单位: 秒, 默认 1 分钟过期, 118 | refreshToken: 'mockedRefreshedToken.adminRefresh', // 模拟刷新 token 119 | }, 120 | ] 121 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff ~ default configuration file 2 | # https://git-cliff.org/docs/configuration 3 | # 4 | # Lines starting with "#" are comments. 5 | # Configuration options are organized into tables and keys. 6 | # See documentation for more information on available options. 7 | 8 | [changelog] 9 | # template for the changelog header 10 | header = """ 11 | # Changelog\n 12 | All notable changes to this project will be documented in this file.\n 13 | """ 14 | # template for the changelog body 15 | # https://keats.github.io/tera/docs/#introduction 16 | body = """ 17 | {% if version %}\ 18 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 19 | {% else %}\ 20 | ## [unreleased] 21 | {% endif %}\ 22 | {% for group, commits in commits | group_by(attribute="group") %} 23 | ### {{ group | striptags | trim | upper_first }} 24 | {% for commit in commits %} 25 | - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ 26 | {% if commit.breaking %}[**breaking**] {% endif %}\ 27 | {{ commit.message | upper_first }}\ 28 | {% endfor %} 29 | {% endfor %}\n 30 | """ 31 | # template for the changelog footer 32 | footer = """ 33 | 34 | """ 35 | # remove the leading and trailing s 36 | trim = true 37 | # postprocessors 38 | postprocessors = [ 39 | # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL 40 | ] 41 | # render body even when there are no releases to process 42 | # render_always = true 43 | # output file path 44 | # output = "test.md" 45 | 46 | [git] 47 | # parse the commits based on https://www.conventionalcommits.org 48 | conventional_commits = true 49 | # filter out the commits that are not conventional 50 | filter_unconventional = true 51 | # process each line of a commit as an individual commit 52 | split_commits = false 53 | # regex for preprocessing the commit messages 54 | commit_preprocessors = [ 55 | # Replace issue numbers 56 | #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, 57 | # Check spelling of the commit with https://github.com/crate-ci/typos 58 | # If the spelling is incorrect, it will be automatically fixed. 59 | #{ pattern = '.*', replace_command = 'typos --write-changes -' }, 60 | ] 61 | # regex for parsing and grouping commits 62 | commit_parsers = [ 63 | { message = "^feat", group = "🚀 Features" }, 64 | { message = "^fix", group = "🩹 Fixes" }, 65 | { message = "^doc", group = "📚 Documentation" }, 66 | { message = "^perf", group = "⚡ Performance" }, 67 | { message = "^refactor", group = "🚜 Refactor" }, 68 | { message = "^style", group = "🎨 Styling" }, 69 | { message = "^test", group = "🧪 Testing" }, 70 | { message = "^chore\\(release\\): prepare for", skip = true }, 71 | { message = "^chore\\(deps.*\\)", skip = true }, 72 | { message = "^chore\\(pr\\)", skip = true }, 73 | { message = "^chore\\(pull\\)", skip = true }, 74 | { message = "^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, 75 | { body = ".*security", group = "🛡️ Security" }, 76 | { message = "^revert", group = "◀️ Revert" }, 77 | { message = ".*", group = "💼 Other" }, 78 | ] 79 | # filter out the commits that are not matched by commit parsers 80 | filter_commits = false 81 | # sort the tags topologically 82 | topo_order = true 83 | # sort the commits inside sections by oldest/newest order 84 | sort_commits = "oldest" 85 | -------------------------------------------------------------------------------- /src/layouts/admin/Tagbar.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 54 | 55 | 114 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/advlist/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=(t,e,r)=>{const s="UL"===e?"InsertUnorderedList":"InsertOrderedList";t.execCommand(s,!1,!1===r?null:{"list-style-type":r})},r=t=>e=>e.options.get(t),s=r("advlist_number_styles"),n=r("advlist_bullet_styles"),l=t=>null==t,i=t=>!l(t);var o=tinymce.util.Tools.resolve("tinymce.util.Tools");class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return i(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=t=>i(t)&&/^(TH|TD)$/.test(t.nodeName),d=t=>l(t)||"default"===t?"":t,g=(t,e)=>r=>{const s=s=>{r.setActive(((t,e,r)=>{const s=((t,e)=>{for(let r=0;re=>i(e)&&/^(OL|UL|DL)$/.test(e.nodeName)&&((t,e)=>t.dom.isChildOf(e,t.getBody()))(t,e))(t));return l.length>0&&l[0].nodeName===r})(t,s,e)),r.setEnabled(!((t,e)=>{const r=t.dom.getParent(e,"ol,ul,dl");return((t,e)=>null!==e&&"false"===t.dom.getContentEditableParent(e))(t,r)})(t,s.element))};return t.on("NodeChange",s),()=>t.off("NodeChange",s)},h=(t,r,s,n,l,i)=>{i.length>1?((t,r,s,n,l,i)=>{t.ui.registry.addSplitButton(r,{tooltip:s,icon:"OL"===l?"ordered-list":"unordered-list",presets:"listpreview",columns:3,fetch:t=>{t(o.map(i,(t=>{const e="OL"===l?"num":"bull",r="disc"===t||"decimal"===t?"default":t,s=d(t),n=(t=>t.replace(/\-/g," ").replace(/\b\w/g,(t=>t.toUpperCase())))(t);return{type:"choiceitem",value:s,icon:"list-"+e+"-"+r,text:n}})))},onAction:()=>t.execCommand(n),onItemAction:(r,s)=>{e(t,l,s)},select:e=>{const r=(t=>{const e=t.dom.getParent(t.selection.getNode(),"ol,ul"),r=t.dom.getStyle(e,"listStyleType");return a.from(r)})(t);return r.map((t=>e===t)).getOr(!1)},onSetup:g(t,l)})})(t,r,s,n,l,i):((t,r,s,n,l,i)=>{t.ui.registry.addToggleButton(r,{active:!1,tooltip:s,icon:"OL"===l?"ordered-list":"unordered-list",onSetup:g(t,l),onAction:()=>t.queryCommandState(n)||""===i?t.execCommand(n):e(t,l,i)})})(t,r,s,n,l,d(i[0]))};t.add("advlist",(t=>{t.hasPlugin("lists")?((t=>{const e=t.options.register;e("advlist_number_styles",{processor:"string[]",default:"default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman".split(",")}),e("advlist_bullet_styles",{processor:"string[]",default:"default,circle,square".split(",")})})(t),(t=>{h(t,"numlist","Numbered list","InsertOrderedList","OL",s(t)),h(t,"bullist","Bullet list","InsertUnorderedList","UL",n(t))})(t),(t=>{t.addCommand("ApplyUnorderedListStyle",((r,s)=>{e(t,"UL",s["list-style-type"])})),t.addCommand("ApplyOrderedListStyle",((r,s)=>{e(t,"OL",s["list-style-type"])}))})(t)):console.error("Please use the Lists plugin together with the Advanced List plugin.")}))}(); -------------------------------------------------------------------------------- /src/pages/Demo/Print.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 106 | 107 | 140 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/importcss/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(s=r=e,(o=String).prototype.isPrototypeOf(s)||(null===(n=r.constructor)||void 0===n?void 0:n.name)===o.name)?"string":t;var s,r,o,n})(t)===e,s=t("string"),r=t("object"),o=t("array"),n=("function",e=>"function"==typeof e);var c=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),i=tinymce.util.Tools.resolve("tinymce.EditorManager"),l=tinymce.util.Tools.resolve("tinymce.Env"),a=tinymce.util.Tools.resolve("tinymce.util.Tools");const p=e=>t=>t.options.get(e),u=p("importcss_merge_classes"),m=p("importcss_exclusive"),f=p("importcss_selector_converter"),y=p("importcss_selector_filter"),d=p("importcss_groups"),h=p("importcss_append"),_=p("importcss_file_filter"),g=p("skin"),v=p("skin_url"),b=Array.prototype.push,x=/^\.(?:ephox|tiny-pageembed|mce)(?:[.-]+\w+)+$/,T=e=>s(e)?t=>-1!==t.indexOf(e):e instanceof RegExp?t=>e.test(t):e,S=(e,t)=>{let s={};const r=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(t);if(!r)return;const o=r[1],n=r[2].substr(1).split(".").join(" "),c=a.makeMap("a,img");return r[1]?(s={title:t},e.schema.getTextBlockElements()[o]?s.block=o:e.schema.getBlockElements()[o]||c[o.toLowerCase()]?s.selector=o:s.inline=o):r[2]&&(s={inline:"span",title:t.substr(1),classes:n}),u(e)?s.classes=n:s.attributes={class:n},s},k=(e,t)=>null===t||m(e),w=e=>{e.on("init",(()=>{const t=(()=>{const e=[],t=[],s={};return{addItemToGroup:(e,r)=>{s[e]?s[e].push(r):(t.push(e),s[e]=[r])},addItem:t=>{e.push(t)},toFormats:()=>{return(r=t,n=e=>{const t=s[e];return 0===t.length?[]:[{title:e,items:t}]},(e=>{const t=[];for(let s=0,r=e.length;s{const s=e.length,r=new Array(s);for(let o=0;oa.map(e,(e=>a.extend({},e,{original:e,selectors:{},filter:T(e.filter)}))))(d(e)),u=(t,s)=>{if(((e,t,s,r)=>!(k(e,s)?t in r:t in s.selectors))(e,t,s,r)){((e,t,s,r)=>{k(e,s)?r[t]=!0:s.selectors[t]=!0})(e,t,s,r);const o=((e,t,s,r)=>{let o;const n=f(e);return o=r&&r.selector_converter?r.selector_converter:n||(()=>S(e,s)),o.call(t,s,r)})(e,e.plugins.importcss,t,s);if(o){const t=o.name||c.DOM.uniqueId();return e.formatter.register(t,o),{title:o.title,format:t}}}return null};a.each(((e,t,r)=>{const o=[],n={},c=(t,n)=>{let p,u=t.href;if(u=(e=>{const t=l.cacheSuffix;return s(e)&&(e=e.replace("?"+t,"").replace("&"+t,"")),e})(u),u&&(!r||r(u,n))&&!((e,t)=>{const s=g(e);if(s){const r=v(e),o=r?e.documentBaseURI.toAbsolute(r):i.baseURL+"/skins/ui/"+s,n=i.baseURL+"/skins/content/";return t===o+"/content"+(e.inline?".inline":"")+".min.css"||-1!==t.indexOf(n)}return!1})(e,u)){a.each(t.imports,(e=>{c(e,!0)}));try{p=t.cssRules||t.rules}catch(e){}a.each(p,(e=>{e.styleSheet?c(e.styleSheet,!0):e.selectorText&&a.each(e.selectorText.split(","),(e=>{o.push(a.trim(e))}))}))}};a.each(e.contentCSS,(e=>{n[e]=!0})),r||(r=(e,t)=>t||n[e]);try{a.each(t.styleSheets,(e=>{c(e)}))}catch(e){}return o})(e,e.getDoc(),T(_(e))),(e=>{if(!x.test(e)&&(!n||n(e))){const s=((e,t)=>a.grep(e,(e=>!e.filter||e.filter(t))))(p,e);if(s.length>0)a.each(s,(s=>{const r=u(e,s);r&&t.addItemToGroup(s.title,r)}));else{const s=u(e,null);s&&t.addItem(s)}}}));const m=t.toFormats();e.dispatch("addStyleModifications",{items:m,replace:!h(e)})}))};e.add("importcss",(e=>((e=>{const t=e.options.register,o=e=>s(e)||n(e)||r(e);t("importcss_merge_classes",{processor:"boolean",default:!0}),t("importcss_exclusive",{processor:"boolean",default:!0}),t("importcss_selector_converter",{processor:"function"}),t("importcss_selector_filter",{processor:o}),t("importcss_file_filter",{processor:o}),t("importcss_groups",{processor:"object[]"}),t("importcss_append",{processor:"boolean",default:!1})})(e),w(e),(e=>({convertSelectorToFormat:t=>S(e,t)}))(e))))}(); -------------------------------------------------------------------------------- /src/pages/Demo/ScrollNotice.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 103 | 104 | 116 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/directionality/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,o=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(o=r=t,(n=String).prototype.isPrototypeOf(o)||(null===(i=r.constructor)||void 0===i?void 0:i.name)===n.name)?"string":e;var o,r,n,i})(t),r=e("boolean"),n=t=>!(t=>null==t)(t),i=e("function"),s=e("number"),l=(!1,()=>false);class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return n(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=(t,e)=>{for(let o=0,r=t.length;o{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},d=c,h=(t,e)=>{const o=t.dom;if(1!==o.nodeType)return!1;{const t=o;if(void 0!==t.matches)return t.matches(e);if(void 0!==t.msMatchesSelector)return t.msMatchesSelector(e);if(void 0!==t.webkitMatchesSelector)return t.webkitMatchesSelector(e);if(void 0!==t.mozMatchesSelector)return t.mozMatchesSelector(e);throw new Error("Browser lacks native selectors")}};"undefined"!=typeof window?window:Function("return this;")();const m=t=>e=>(t=>t.dom.nodeType)(e)===t,g=m(1),f=m(3),v=m(9),p=m(11),y=(t,e)=>{t.dom.removeAttribute(e)},w=i(Element.prototype.attachShadow)&&i(Node.prototype.getRootNode)?t=>d(t.dom.getRootNode()):t=>v(t)?t:d(t.dom.ownerDocument),N=t=>d(t.dom.host),b=t=>{const e=f(t)?t.dom.parentNode:t.dom;if(null==e||null===e.ownerDocument)return!1;const o=e.ownerDocument;return(t=>{const e=w(t);return p(o=e)&&n(o.dom.host)?a.some(e):a.none();var o})(d(e)).fold((()=>o.body.contains(e)),(r=b,i=N,t=>r(i(t))));var r,i},S=t=>"rtl"===((t,e)=>{const o=t.dom,r=window.getComputedStyle(o).getPropertyValue(e);return""!==r||b(t)?r:((t,e)=>(t=>void 0!==t.style&&i(t.style.getPropertyValue))(t)?t.style.getPropertyValue(e):"")(o,e)})(t,"direction")?"rtl":"ltr",A=(t,e)=>((t,o)=>((t,e)=>{const o=[];for(let r=0,n=t.length;r{const o=t.length,r=new Array(o);for(let n=0;nh(t,e))))(t),T=("li",t=>g(t)&&"li"===t.dom.nodeName.toLowerCase());const C=(t,e)=>{const n=t.selection.getSelectedBlocks();n.length>0&&(u(n,(t=>{const n=d(t),c=T(n),m=((t,e)=>{return(e?(o=t,r="ol,ul",((t,e,o)=>{let n=t.dom;const s=i(o)?o:l;for(;n.parentNode;){n=n.parentNode;const t=d(n);if(h(t,r))return a.some(t);if(s(t))break}return a.none()})(o,0,n)):a.some(t)).getOr(t);var o,r,n})(n,c);var f;(f=m,(t=>a.from(t.dom.parentNode).map(d))(f).filter(g)).each((t=>{if(S(t)!==e?((t,e,n)=>{((t,e,n)=>{if(!(o(n)||r(n)||s(n)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",n,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,n+"")})(t.dom,e,n)})(m,"dir",e):S(m)!==e&&y(m,"dir"),c){const t=A(m,"li[dir]");u(t,(t=>y(t,"dir")))}}))})),t.nodeChanged())},D=(t,e)=>o=>{const r=t=>{const r=d(t.element);o.setActive(S(r)===e)};return t.on("NodeChange",r),()=>t.off("NodeChange",r)};t.add("directionality",(t=>{(t=>{t.addCommand("mceDirectionLTR",(()=>{C(t,"ltr")})),t.addCommand("mceDirectionRTL",(()=>{C(t,"rtl")}))})(t),(t=>{t.ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:()=>t.execCommand("mceDirectionLTR"),onSetup:D(t,"ltr")}),t.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:()=>t.execCommand("mceDirectionRTL"),onSetup:D(t,"rtl")})})(t)}))}(); -------------------------------------------------------------------------------- /src/assets/primeLocaleCN.ts: -------------------------------------------------------------------------------- 1 | export const primeLocaleCN = { 2 | accept: '是', 3 | addRule: '添加规则', 4 | am: '上午', 5 | apply: '应用', 6 | cancel: '取消', 7 | choose: '选择', 8 | chooseDate: '选择日期', 9 | chooseMonth: '选择月份', 10 | chooseYear: '选择年份', 11 | clear: '清除', 12 | completed: '已完成', 13 | contains: '包含', 14 | custom: '自定义', 15 | dateAfter: '日期晚于', 16 | dateBefore: '日期早于', 17 | dateFormat: 'yy/mm/dd', 18 | dateIs: '日期为', 19 | dateIsNot: '日期不为', 20 | dayNames: [ 21 | '星期日', 22 | '星期一', 23 | '星期二', 24 | '星期三', 25 | '星期四', 26 | '星期五', 27 | '星期六', 28 | ], 29 | dayNamesMin: [ 30 | '日', 31 | '一', 32 | '二', 33 | '三', 34 | '四', 35 | '五', 36 | '六', 37 | ], 38 | dayNamesShort: [ 39 | '周日', 40 | '周一', 41 | '周二', 42 | '周三', 43 | '周四', 44 | '周五', 45 | '周六', 46 | ], 47 | emptyFilterMessage: '未找到结果', 48 | emptyMessage: '无可用选项', 49 | emptySearchMessage: '未找到结果', 50 | emptySelectionMessage: '未选择任何项', 51 | endsWith: '以...结尾', 52 | equals: '等于', 53 | fileSizeTypes: [ 54 | 'B', 55 | 'KB', 56 | 'MB', 57 | 'GB', 58 | 'TB', 59 | 'PB', 60 | 'EB', 61 | 'ZB', 62 | 'YB', 63 | ], 64 | filter: '筛选', 65 | firstDayOfWeek: 0, 66 | gt: '大于', 67 | gte: '大于等于', 68 | lt: '小于', 69 | lte: '小于等于', 70 | matchAll: '全部匹配', 71 | matchAny: '任意匹配', 72 | medium: '中', 73 | monthNames: [ 74 | '一月', 75 | '二月', 76 | '三月', 77 | '四月', 78 | '五月', 79 | '六月', 80 | '七月', 81 | '八月', 82 | '九月', 83 | '十月', 84 | '十一月', 85 | '十二月', 86 | ], 87 | monthNamesShort: [ 88 | '1月', 89 | '2月', 90 | '3月', 91 | '4月', 92 | '5月', 93 | '6月', 94 | '7月', 95 | '8月', 96 | '9月', 97 | '10月', 98 | '11月', 99 | '12月', 100 | ], 101 | nextDecade: '下一个十年', 102 | nextHour: '下一小时', 103 | nextMinute: '下一分钟', 104 | nextMonth: '下个月', 105 | nextSecond: '下一秒', 106 | nextYear: '下一年', 107 | noFilter: '无筛选', 108 | notContains: '不包含', 109 | notEquals: '不等于', 110 | now: '现在', 111 | passwordPrompt: '输入密码', 112 | pending: '待处理', 113 | pm: '下午', 114 | prevDecade: '上一个十年', 115 | prevHour: '上一小时', 116 | prevMinute: '上一分钟', 117 | prevMonth: '上个月', 118 | prevSecond: '上一秒', 119 | prevYear: '上一年', 120 | reject: '否', 121 | removeRule: '移除规则', 122 | searchMessage: '有 {0} 条结果可用', 123 | selectionMessage: '已选择 {0} 项', 124 | showMonthAfterYear: true, 125 | startsWith: '以...开始', 126 | strong: '强', 127 | today: '今天', 128 | upload: '上传', 129 | weak: '弱', 130 | weekHeader: '周', 131 | aria: { 132 | cancelEdit: '取消编辑', 133 | close: '关闭', 134 | collapseLabel: '折叠', 135 | collapseRow: '折叠行', 136 | editRow: '编辑行', 137 | expandLabel: '展开', 138 | expandRow: '展开行', 139 | falseLabel: '否', 140 | filterConstraint: '筛选条件', 141 | filterOperator: '筛选运算符', 142 | firstPageLabel: '首页', 143 | gridView: '网格视图', 144 | hideFilterMenu: '隐藏筛选菜单', 145 | jumpToPageDropdownLabel: '跳至页面下拉框', 146 | jumpToPageInputLabel: '跳至页面输入框', 147 | lastPageLabel: '尾页', 148 | listView: '列表视图', 149 | moveAllToSource: '全部移至源', 150 | moveAllToTarget: '全部移至目标', 151 | moveBottom: '移至底部', 152 | moveDown: '下移', 153 | moveTop: '移至顶部', 154 | moveToSource: '移至源', 155 | moveToTarget: '移至目标', 156 | moveUp: '上移', 157 | navigation: '导航', 158 | next: '下一个', 159 | nextPageLabel: '下一页', 160 | nullLabel: '未选择', 161 | otpLabel: '请输入一次性密码字符 {0}', 162 | pageLabel: '{page}', 163 | passwordHide: '隐藏密码', 164 | passwordShow: '显示密码', 165 | previous: '上一个', 166 | previousPageLabel: '上一页', 167 | removeLabel: '消除', 168 | rotateLeft: '向左旋转', 169 | rotateRight: '向右旋转', 170 | rowsPerPageLabel: '每页行数', 171 | saveEdit: '保存编辑', 172 | scrollTop: '滚动到顶部', 173 | selectAll: '已选择所有项目', 174 | selectLabel: '选择', 175 | selectRow: '选择行', 176 | showFilterMenu: '显示筛选菜单', 177 | slide: '滑动', 178 | slideNumber: '{slideNumber}', 179 | star: '1颗星', 180 | stars: '{star}颗星', 181 | trueLabel: '是', 182 | unselectAll: '全部取消选择', 183 | unselectLabel: '取消选择', 184 | unselectRow: '取消选择行', 185 | zoomImage: '查看大图', 186 | zoomIn: '放大', 187 | zoomOut: '缩小', 188 | }, 189 | } 190 | -------------------------------------------------------------------------------- /src/pages/Demo/Icon.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 110 | -------------------------------------------------------------------------------- /src/pages/Admin/Welcome.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 116 | 117 | 124 | -------------------------------------------------------------------------------- /src/layouts/admin/Navbar.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 75 | 76 | 133 | -------------------------------------------------------------------------------- /src/composables/config.ts: -------------------------------------------------------------------------------- 1 | import { updatePreset, updateSurfacePalette } from '@primevue/themes' 2 | import { useTitle } from '@vueuse/core' 3 | import { type Action, ElMessageBox } from 'element-plus' 4 | import { defineStore, skipHydrate } from 'pinia' 5 | 6 | export const useConfigStore = defineStore('config', () => { 7 | const appConfig = useAppConfig() 8 | const config = ref(useLocalStorage('admin3-app-config', appConfig)) 9 | 10 | function initConfig() { 11 | config.value = { ...appConfig, ...config.value } 12 | setTitle(config.value.title) 13 | setVersion(appConfig.version) 14 | setLayout(config.value.layout) 15 | setSidebarDark(config.value.sidebarDark) 16 | setSidebarWidth(config.value.sidebarWidth) 17 | setSidebarCollapse(config.value.sidebarCollapse) 18 | setNavBreadcrumb(config.value.navBreadcrumb) 19 | setTagbar(config.value.tagbar) 20 | setTransitionType(config.value.transitionType) 21 | setPrimePreset(config.value.primePreset) 22 | setPrimaryColor(config.value.primaryColor) 23 | setSurfaceColor(config.value.surfaceColor) 24 | } 25 | function resetConfig() { 26 | config.value = { ...appConfig } 27 | initConfig() 28 | } 29 | 30 | function setTitle(title: string | undefined) { 31 | useTitle().value = title 32 | config.value.title = title 33 | } 34 | 35 | function setVersion(version: string) { 36 | /* 版本号更新判断可以通过比对config.value.version(缓存中的版本号) 和 app.config.version(应用部署的版本号) 来实现 */ 37 | if (config.value.version !== version) { 38 | ElMessageBox.alert(`当前版本:${config.value.version},发现新版本:${version},请点击确定进行更新!`, '网站更新', { 39 | callback: (action: Action) => { 40 | if (action === 'confirm') { 41 | config.value.version = version 42 | location.reload() 43 | } 44 | }, 45 | }) 46 | } 47 | } 48 | 49 | function setLayout(layout: string) { 50 | config.value.layout = layout 51 | } 52 | 53 | function setSidebarDark(sidebarDark: boolean) { 54 | if (sidebarDark) { 55 | useCssVar('--admin-sidebar-bg-color').value = `var(--admin-color-700)` 56 | useCssVar('--admin-sidebar-hover-bg-color').value = `var(--admin-color-50)` 57 | useCssVar('--admin-sidebar-text-color').value = `var(--admin-color-50)` 58 | useCssVar('--admin-sidebar-hover-text-color').value = `var(--admin-color-900)` 59 | useCssVar('--admin-navbar-bg-color').value = `var(--admin-color-900)` 60 | } 61 | else { 62 | useCssVar('--admin-sidebar-bg-color').value = `var(--admin-color-50)` 63 | useCssVar('--admin-sidebar-hover-bg-color').value = `var(--admin-color-100)` 64 | useCssVar('--admin-sidebar-text-color').value = `var(--admin-color-900)` 65 | useCssVar('--admin-sidebar-hover-text-color').value = `var(--admin-color-900)` 66 | useCssVar('--admin-navbar-bg-color').value = `var(--admin-color-50)` 67 | } 68 | } 69 | 70 | function setSidebarWidth(width: number | any) { 71 | useCssVar('--admin-sidebar-width').value = `${width}px` 72 | config.value.sidebarWidth = width 73 | } 74 | 75 | function setSidebarCollapse(collapse: boolean | any) { 76 | if (collapse) 77 | useCssVar('--admin-sidebar-width').value = useCssVar('--admin-sidebar-collapse-width').value 78 | else 79 | useCssVar('--admin-sidebar-width').value = `${config.value.sidebarWidth}px` 80 | config.value.sidebarCollapse = collapse 81 | } 82 | 83 | function setNavBreadcrumb(navBreadcrumb: boolean | any) { 84 | config.value.navBreadcrumb = navBreadcrumb 85 | } 86 | 87 | function setTagbar(tagbar: boolean | any) { 88 | if (tagbar) 89 | useCssVar('--admin-tagbar-height').value = '2.5rem' 90 | else 91 | useCssVar('--admin-tagbar-height').value = '0rem' 92 | config.value.tagbar = tagbar 93 | } 94 | 95 | function setTransitionType(transitionType: string) { 96 | config.value.transitionType = transitionType 97 | } 98 | 99 | function setPrimePreset(preset: string) { 100 | config.value.primePreset = preset 101 | changePrimePreset(preset) 102 | } 103 | function setPrimaryColor(color: string) { 104 | config.value.primaryColor = color 105 | updatePreset(getPresetExt.value) 106 | } 107 | function setSurfaceColor(color: string) { 108 | config.value.surfaceColor = color 109 | const palette = primeVueSurfaceOptions.find(item => item.name === color)?.palette 110 | if (palette) 111 | updateSurfacePalette(palette) 112 | } 113 | 114 | return { 115 | config: skipHydrate(config), 116 | initConfig, 117 | resetConfig, 118 | setTitle, 119 | setVersion, 120 | setLayout, 121 | setSidebarDark, 122 | setSidebarWidth, 123 | setSidebarCollapse, 124 | setNavBreadcrumb, 125 | setTagbar, 126 | setTransitionType, 127 | setPrimePreset, 128 | setPrimaryColor, 129 | setSurfaceColor, 130 | } 131 | }) 132 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/quickbars/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>typeof t===e,o=("string",e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(o=n=e,(r=String).prototype.isPrototypeOf(o)||(null===(i=n.constructor)||void 0===i?void 0:i.name)===r.name)?"string":t;var o,n,r,i})(e));const n=t("boolean"),r=t("function"),i=e=>t=>t.options.get(e),s=i("quickbars_selection_toolbar"),a=i("quickbars_insert_toolbar"),l=i("quickbars_image_toolbar");let c=0;var u=tinymce.util.Tools.resolve("tinymce.Env"),d=tinymce.util.Tools.resolve("tinymce.util.Delay");const m=e=>{e.ui.registry.addButton("quickimage",{icon:"image",tooltip:"Insert image",onAction:()=>{(e=>new Promise((t=>{const o=document.createElement("input");o.type="file",o.accept="image/*",o.style.position="fixed",o.style.left="0",o.style.top="0",o.style.opacity="0.001",document.body.appendChild(o),o.addEventListener("change",(e=>{t(Array.prototype.slice.call(e.target.files))}));const n=r=>{const i=()=>{var e;t([]),null===(e=o.parentNode)||void 0===e||e.removeChild(o)};u.os.isAndroid()&&"remove"!==r.type?d.setEditorTimeout(e,i,0):i(),e.off("focusin remove",n)};e.on("focusin remove",n),o.click()})))(e).then((t=>{if(t.length>0){const o=t[0];(e=>new Promise((t=>{const o=new FileReader;o.onloadend=()=>{t(o.result.split(",")[1])},o.readAsDataURL(e)})))(o).then((t=>{((e,t,o)=>{const n=e.editorUpload.blobCache,r=n.create((e=>{const t=(new Date).getTime(),o=Math.floor(1e9*Math.random());return c++,"mceu_"+o+c+String(t)})(),o,t);n.add(r),e.insertContent(e.dom.createHTML("img",{src:r.blobUri()}))})(e,t,o)}))}}))}}),e.ui.registry.addButton("quicktable",{icon:"table",tooltip:"Insert table",onAction:()=>{((e,t,o)=>{e.execCommand("mceInsertTable",!1,{rows:2,columns:2})})(e)}})},g=(!1,()=>false);class h{constructor(e,t){this.tag=e,this.value=t}static some(e){return new h(!0,e)}static none(){return h.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?h.some(e(this.value)):h.none()}bind(e){return this.tag?e(this.value):h.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:h.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return null==e?h.none():h.some(e)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}h.singletonNone=new h(!1),"undefined"!=typeof window?window:Function("return this;")();var b=(e,t,o,n,i)=>e(o,n)?h.some(o):r(i)&&i(o)?h.none():t(o,n,i);const v=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},p=v,f=(e,t)=>{const o=e.dom;if(1!==o.nodeType)return!1;{const e=o;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}},y=(e,t,o)=>{let n=e.dom;const i=r(o)?o:g;for(;n.parentNode;){n=n.parentNode;const e=p(n);if(t(e))return h.some(e);if(i(e))break}return h.none()},k=(e,t,o)=>y(e,(e=>f(e,t)),o),w=e=>{const t=a(e);t.length>0&&e.ui.registry.addContextToolbar("quickblock",{predicate:t=>{const o=p(t),n=e.schema.getTextBlockElements(),r=t=>t.dom===e.getBody();return!((e,t)=>{const o=e.dom;return!(!o||!o.hasAttribute)&&o.hasAttribute("data-mce-bogus")})(o)&&((e,t,o)=>b(((e,t)=>f(e,t)),k,e,'table,[data-mce-bogus="all"]',o))(o,0,r).fold((()=>((e,t,o)=>((e,t,o)=>b(((e,t)=>t(e)),y,e,t,o))(e,t,o).isSome())(o,(t=>t.dom.nodeName.toLowerCase()in n&&e.dom.isEmpty(t.dom)),r)),g)},items:t,position:"line",scope:"editor"})};e.add("quickbars",(e=>{(e=>{const t=e.options.register,r=e=>t=>{const r=n(t)||o(t);return r?n(t)?{value:t?e:"",valid:r}:{value:t.trim(),valid:r}:{valid:!1,message:"Must be a boolean or string."}},i="bold italic | quicklink h2 h3 blockquote";t("quickbars_selection_toolbar",{processor:r(i),default:i});const s="quickimage quicktable";t("quickbars_insert_toolbar",{processor:r(s),default:s});const a="alignleft aligncenter alignright";t("quickbars_image_toolbar",{processor:r(a),default:a})})(e),m(e),w(e),(e=>{const t=e=>"IMG"===e.nodeName||"FIGURE"===e.nodeName&&/image/i.test(e.className),o=l(e);o.length>0&&e.ui.registry.addContextToolbar("imageselection",{predicate:t,items:o,position:"node"});const n=s(e);n.length>0&&e.ui.registry.addContextToolbar("textselection",{predicate:o=>!t(o)&&!e.selection.isCollapsed()&&(t=>"false"!==e.dom.getContentEditableParent(t))(o),items:n,position:"selection",scope:"editor"})})(e)}))}(); -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/visualchars/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,n=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(n=o=t,(s=String).prototype.isPrototypeOf(n)||(null===(r=o.constructor)||void 0===r?void 0:r.name)===s.name)?"string":e;var n,o,s,r})(t),o=(null,t=>null===t);const s=e("boolean"),r=e("number");class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return null==t?a.none():a.some(t)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const i=(t,e)=>{for(let n=0,o=t.length;n{const n=l(t);for(let o=0,s=n.length;ot.dom.nodeValue,d=t=>3===(t=>t.dom.nodeType)(t),h=(t,e,o)=>{((t,e,o)=>{if(!(n(o)||s(o)||r(o)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",o,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,o+"")})(t.dom,e,o)},g=(t,e)=>{t.dom.removeAttribute(e)},m=(t,e)=>{const n=((t,e)=>{const n=t.dom.getAttribute(e);return null===n?void 0:n})(t,e);return void 0===n||""===n?[]:n.split(" ")},v=t=>void 0!==t.dom.classList,p=t=>{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},f=p,y={"\xa0":"nbsp","\xad":"shy"},b=(t,e)=>{let n="";return u(t,((t,e)=>{n+=e})),new RegExp("["+n+"]",e?"g":"")},w=b(y),A=b(y,!0),k=(t=>{let e="";return u(t,(t=>{e&&(e+=","),e+="span.mce-"+t})),e})(y),N="mce-nbsp",C=t=>''+t+"",T=t=>{const e=c(t);return d(t)&&n(e)&&w.test(e)},O=(t,e)=>{let n=[];const o=((t,e)=>{const n=t.length,o=new Array(n);for(let s=0;s{e(t)&&(n=n.concat([t])),n=n.concat(O(t,e))})),n},B=t=>"span"===t.nodeName.toLowerCase()&&t.classList.contains("mce-nbsp-wrap"),S=(t,e)=>{const n=t.dom,o=O(f(e),T);i(o,(e=>{var o;const s=e.dom.parentNode;if(B(s))r=f(s),a=N,v(r)?r.dom.classList.add(a):((t,e)=>{((t,e,n)=>{const o=m(t,e).concat([n]);h(t,e,o.join(" "))})(t,"class",e)})(r,a);else{const s=n.encode(null!==(o=c(e))&&void 0!==o?o:"").replace(A,C),r=n.create("div",{},s);let a;for(;a=r.lastChild;)n.insertAfter(a,e.dom);t.dom.remove(e.dom)}var r,a}))},V=(t,e)=>{const n=t.dom.select(k,e);i(n,(e=>{var n,o;B(e)?(n=f(e),o=N,v(n)?n.dom.classList.remove(o):((t,e)=>{((t,e,n)=>{const o=((t,e)=>{const o=[];for(let e=0,s=t.length;e0?h(t,e,o.join(" ")):g(t,e)})(t,"class",e)})(n,o),(t=>{const e=v(t)?t.dom.classList:(t=>m(t,"class"))(t);0===e.length&&g(t,"class")})(n)):t.dom.remove(e,!0)}))},E=t=>{const e=t.getBody(),n=t.selection.getBookmark();let o=((t,e)=>{for(;t.parentNode;){if(t.parentNode===e)return e;t=t.parentNode}})(t.selection.getNode(),e);o=void 0!==o?o:e,V(t,o),S(t,o),t.selection.moveToBookmark(n)},L=(t,e)=>{((t,e)=>{t.dispatch("VisualChars",{state:e})})(t,e.get());const n=t.getBody();!0===e.get()?S(t,n):V(t,n)},_=("visualchars_default_state",t=>t.options.get("visualchars_default_state"));const j=(t,e)=>{const n=((t,e)=>{let n=null;return{cancel:()=>{o(n)||(clearTimeout(n),n=null)},throttle:(...e)=>{o(n)&&(n=setTimeout((()=>{n=null,t.apply(null,e)}),300))}}})((()=>{E(t)}));t.on("keydown",(o=>{!0===e.get()&&(13===o.keyCode?E(t):n.throttle())})),t.on("remove",n.cancel)},x=(t,e)=>n=>{n.setActive(e.get());const o=t=>n.setActive(t.state);return t.on("VisualChars",o),()=>t.off("VisualChars",o)};t.add("visualchars",(t=>{(t=>{(0,t.options.register)("visualchars_default_state",{processor:"boolean",default:!1})})(t);const e=(t=>{let e=t;return{get:()=>e,set:t=>{e=t}}})(_(t));return((t,e)=>{t.addCommand("mceVisualChars",(()=>{((t,e)=>{e.set(!e.get());const n=t.selection.getBookmark();L(t,e),t.selection.moveToBookmark(n)})(t,e)}))})(t,e),((t,e)=>{const n=()=>t.execCommand("mceVisualChars");t.ui.registry.addToggleButton("visualchars",{tooltip:"Show invisible characters",icon:"visualchars",onAction:n,onSetup:x(t,e)}),t.ui.registry.addToggleMenuItem("visualchars",{text:"Show invisible characters",icon:"visualchars",onAction:n,onSetup:x(t,e)})})(t,e),j(t,e),((t,e)=>{t.on("init",(()=>{L(t,e)}))})(t,e),(t=>({isEnabled:()=>t.get()}))(e)}))}(); -------------------------------------------------------------------------------- /src/pages/Home/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 120 | 121 | 150 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [2.1.1] - 2024-12-24 6 | 7 | ### 🚀 Features 8 | 9 | - 利用 vue data ui 实现动态图表功能 10 | 11 | ### ⚙️ Miscellaneous Tasks 12 | 13 | - 添加版本日志 14 | - Release v2.1.1 15 | 16 | ## [2.1.0] - 2024-12-24 17 | 18 | ### 🚀 Features 19 | 20 | - 使用 git-cliff 自动生成更新文档 21 | - 实现“深色侧边栏”配置,直接调整侧边栏的深浅主题 22 | 23 | ### ⚙️ Miscellaneous Tasks 24 | 25 | - Update deps 26 | - Release v2.1.0 27 | 28 | ## [2.0.6] - 2024-12-16 29 | 30 | ### 🚀 Features 31 | 32 | - 更新滚动通知组件 33 | - 省市区选择组件说明样式优化 34 | 35 | ### 🐛 Bug Fixes 36 | 37 | - Pdf 在线文档地址更新 38 | - Unocss lint 39 | - 布局样式不能为空 40 | - 优化双栏配色不一致,优化primevue演示走马灯 41 | - 双栏配色不一致 42 | - 双栏 el-menu-active-color 未生效 43 | 44 | ### 🎨 Styling 45 | 46 | - 优化首页样式 47 | 48 | ### ⚙️ Miscellaneous Tasks 49 | 50 | - Release v2.0.6 51 | 52 | ## [2.0.5] - 2024-10-16 53 | 54 | ### ⚙️ Miscellaneous Tasks 55 | 56 | - 更新项目文档地址 57 | - 更新 bump.config.js 58 | - Release v2.0.5 59 | 60 | ## [2.0.4] - 2024-10-16 61 | 62 | ### 🐛 Bug Fixes 63 | 64 | - Primevue logo image missing 65 | 66 | ### ⚙️ Miscellaneous Tasks 67 | 68 | - Release v2.0.4 69 | 70 | ## [2.0.3] - 2024-10-16 71 | 72 | ### 🚀 Features 73 | 74 | - Add content module to support markdown files render 75 | - :sparkles: add guide & doc css from vitepress, support dark mode with element-plus. 76 | - Add styles for content render 77 | - Content markdown render ready to use 78 | - Add new logo 79 | - Add doc sidebar toc support 80 | - Sublink for navbar 81 | - Add svg icon support 82 | - Add userStore for login 83 | - Login/logout on index 84 | - Add gh-pages 85 | - Use Icon component support both svg & iconify icons 86 | - Add permission middleware to support routers 87 | - Navbar Breadcrumb support 88 | - Add icon demo 89 | - Add element-plus demo 90 | - Icon can be searched by name in icon.vue 91 | - Add config to localstorage 92 | - Add config for sidebarWidth & themeDark 93 | - Sidebar can be Collapsed 94 | - NavBreadcrumb in config 95 | - Keep-alive support 96 | - Add tagsView 97 | - Transition for page 98 | - Add storage support in server 99 | - Add count up demo 100 | - Add china-area-cascader demo 101 | - Add ChinaAreaCascader component 102 | - Add pdf demo 103 | - Add tinymce demo 104 | - Add autofocus and enter key listener for index 105 | - Add draggable table demo 106 | - Add dynamic-row and inline-edit table demo 107 | - Add scroll-notice demo 108 | - Add pinyin search demo 109 | - Use provider for iconify 110 | - Add animate-background demo 111 | - Update 112 | - 更新支持 primevue 113 | - IconifyProvider url not right 114 | 115 | ### 🐛 Bug Fixes 116 | 117 | - Fix content prose table border not show 118 | - Add some icons in unocss config safelist 119 | - Gh-pages 120 | - Gh-pages 121 | - Gh-pages action 122 | - Change baseurl to /admin3/ 123 | - Move logo file from public to assets 124 | - Add postcss-nesting package 125 | - Use localstorage on userStore 126 | - Add IconifyOnline for online icons 127 | - Online icons from api 128 | - Use navigateTo() fix redirect action 129 | - Delete ghost type for container style 130 | - Sidebar css style not work properly 131 | - Doc nav navigation not work properly 132 | - Use default in memory storage for deploy on netlify 133 | - Doc toc scrollbar style 134 | - Admin container css conflict with unocss 135 | - Write iconify provider into runtime config 136 | - Add type module to package.json 137 | - Vueuse storage error when open ssrhandle 138 | - Update github link 139 | - Move to App.vue 140 | - Sidebar show 141 | - Transition move to app.vue 142 | - Table demo not show well 143 | - Logo display better 144 | - Z-index problem 145 | - AdminIcon style 146 | 147 | ### 💼 Other 148 | 149 | - Add commitlint support 150 | - Make yarn as packageManager 151 | - 使用 node 18 152 | 153 | ### 🚜 Refactor 154 | 155 | - Merge guide page into doc 156 | - Remove postcss-nesting & change some styles 157 | - Register Icon components globally with global folder 158 | - Move layout componets to /layouts 159 | - Add useTree utils 160 | 161 | ### 📚 Documentation 162 | 163 | - :memo: update readme 164 | - Add doc/guide 165 | - Update readme 166 | - Update readme 167 | - Update readme 168 | - Update dev note 169 | - Add vue-code-style 170 | - Update directory-structure, routing, vue-code-style doc 171 | - Update readme 172 | - Update readme 173 | - Update docs 174 | - Add docker help 175 | - Update readme 176 | - Update readme 177 | - Update dev log 178 | - Update readme 179 | - 更新 readme 180 | - 更新 changelog 181 | - 更新 readme 182 | 183 | ### 🎨 Styling 184 | 185 | - Change eslint config back to @antfu, better for .md files 186 | - :lipstick: use czg instead of commitizen for git commit 187 | - Gitignore & eslintrc 188 | - Use storeToRefs() instead of $subscribe() in pinia 189 | 190 | ### ⚙️ Miscellaneous Tasks 191 | 192 | - Remove all commitlint 193 | - Index.vue css 194 | - Use yarn instead of npm 195 | - Remove github workflow 196 | - Add Dockerfile 197 | - Move image from asset/ to public/ 198 | - Update packages 199 | - Add element-plus types support 200 | - Update packages 201 | - Update package 202 | - Add docker script 203 | - Release v2.0.3 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/pages/Demo/ElementPlus.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 151 | 152 | 157 | -------------------------------------------------------------------------------- /UPDATENOTE.md: -------------------------------------------------------------------------------- 1 | # 开发日志 2 | 3 | ## 2024.10.16 4 | - :tada: **Admin3 2.0.0 更新!!** 5 | - 更新支持 primevue 6 | - 利用 primevue 实现自定义动态主题、自定义主题色、自定义背景色 7 | - 添加 primevue 示例页面 8 | - 修复侧边栏动画不同步 9 | - 将文档摘除,删除`@nuxt/content`模块,单独建立 admin3-docs 文档项目,让本项目更“干净” 10 | - 优化图标组件,组件名 `` 改为 ``,本地 svg 文件引入作为 plugins 只引入一次 11 | - 更新所有依赖:nuxt->3.13.2, element-plus->2.8.4, primevue->4.1.0 12 | 13 | 14 | ## 2023.11.27 15 | - 更新 layout 配置功能 16 | - 更新 colorTheme 配置功能 17 | - 优化后台首页 18 | - 优化富文本编辑器演示页面 19 | 20 | ## 2023.11.24 21 | - 更新至 nuxt@3.8.2 22 | - 调整项目文件夹结构,源码放入 `/src` 文件夹,模拟后端从 `/server` 移至 `/server-mock` 文件夹。 23 | - 优化请求的封装,见 `/src/utils/request.ts`。 24 | - 去除 content 模块,将文档内容移除本项目,本项目仅作为前端模板使用,使项目初始化更加干净。 25 | 26 | ## 2022.2.23 27 | - 更新至 nuxt@3.2.2 28 | - 修复一些 pdf 在线预览、表格拖拽的 bug 29 | 30 | ## 2022.12.21 31 | - 新增 tinymce 富文本编辑器的 demo,像之前说的,决定项目中不做无新功能性的组件封装,这里直接引用了 tinymce 官方的组件,需要封装的话建议开发者自行封装。富文本编辑器我也是在网上找了好些,主要想实现从 word 中粘贴文本过来时保留样式,像 quill 、 wangeditor 等好些都不行。最后还是选取了最火的 tinymce 作为示例。 32 | - 新增可拖拽表格的 demo 33 | 34 | ## 2022.12.20 35 | - 封装了省市区选择器,并新增了各种场景 demo。 36 | - 新增了一些 demo 注解。 37 | 38 | ## 2022.12.19 39 | - 还是想把项目文档结构层次清晰标准一些,把 layout 中的一次性组件移至 layout 文件夹下,并对 component 文件夹下的组件进行了统一命名。 40 | - 新增省市区选择器 demo 41 | - 删除了针对 el-pagination 的封装。决定该项目中不做无新功能性的组件封装,这样会产生新的 api 而影响二次开发。尽量以 demo 的形式丰富组件。 42 | 43 | ## 2022.12.16 44 | - 做了些文档的更新,搬运了 Nuxt3 的项目结构文档、Vue3 代码风格文档、更新了路由文档、配置文档。 45 | - 新增数字动画 demo(直接使用了 `vueuse` 的 `useTransition()` 函数。 46 | 47 | ## 2022.12.15 48 | - `nuxt` 使用的 `nitro` 服务器自带 `storage layer`([unstorage](https://github.com/unjs/unstorage)),可以先不纠结使用轻量级数据库了,可以直接使用 `unstorage` 的 `fs driver` 就实现了我梦寐以求的利用文本建立可增删改查的轻量数据库存储功能。:grinning: 49 | - 可惜 netlify 不支持 fs 文件写入功能,不过利用 `unstorage` 默认的 `in memory driver` 可以在 netlify 上使用。:thumbsup: 50 | 51 | ## 2022.12.14 52 | - 完成页面的 `transition` 动画,本来想用 `nuxt` 自带的 transition 功能,但感觉还是 keepalive 的 bug 导致实现不了,所以直接用的 Vue 的 `` 组件,通过给页面主内容加了一个 `v-show` 判断,然后包裹在 `` 组件中顺利实现。并添加了不同的动画选项放在布局设置中自定义切换。 53 | - 下步该做后端 `mock` 和轻量级数据库的集成了,还在纠结要不要用 `mongodb`。 54 | 55 | ## 2022.12.13 56 | - 完成 tagsView 功能,开启了全局 keepalive,等 nuxt3 修复了 keepalive 的 bug 后再加入路由的选择性缓存功能。 57 | - 修复了前面的一些 bug:`configStore` 中要优先读取 localstorage 中的数据时要用 `??` 而不是 `||`,否则不能正确获得 `false` 值。 58 | 59 | ## 2022.12.12 60 | - 今天本来想把 tagsView 功能做了的,结果捣鼓了一天的 keep-alive, 最后发现是 nuxt3 的一个 bug:[https://github.com/nuxt/framework/issues/8367](https://github.com/nuxt/framework/issues/8367),希望赶紧能修复。 61 | 62 | ## 2022.12.11 63 | - :tada: 将 App.config 写进 Localstorage,实现边栏颜色、主体深色、边栏宽度的页面布局自定义并缓存。黑边栏配白主体还是挺好看的。 64 | - 发现 `useToggle()` 返回的是一个 `function`,使用 `call()` 才能执行,也不知道用的对不对,不过总算实现流畅的切换了。 65 | - 更新 `pinia` 使用方法,用 `storeToRefs()` 方法来获得 State 的 ref ,比 `$subscribe` 方法更简洁。 66 | - 实现侧边菜单折叠。 67 | - 实现顶栏面包屑导航显示/隐藏。 68 | 69 | ## 2022.12.10 70 | - `Icon` 演示页面中新增根据图表名称搜索功能。 71 | 72 | ## 2022.12.09 73 | - 使用 NavigateTo() 并设置 `{ replace,true }` 可以实现重定向(不记录 router 历史) 74 | - 新增 Element-Plus 的演示页 75 | - 新增布局组件 ``,实现固定 `header`, `footer`, 内容填满主区域,页面有较多内容时会在侧面产生滚动条。 76 | ## 2022.12.08 77 | - 实现图标演示页面,新增了 `IconifyOnline` 组件,实现在线图标引入。搞了好久图标引入,离线的只能按需引入,否则打包过大会出问题。 78 | - 新增 `AdminContainer` 组件,实现主体内容的 flex 布局,使`header` 和 `footer` 插槽能够固定,中间使用 element-plus 的 `scroll`组件。 79 | 80 | ## 2022.12.07 81 | - 将 `SvgIcon` 组件改写成通用的 `Icon` 组件,即可以使用 `i-` 引入 `iconify` 图标,又可以使用 `svg-` 引入 `svg` 图标 82 | - 实现了后端接口传路由:`@/server/api/routers.get.ts` 83 | - 添加路由守卫:`@/middleware/auth.global.ts` 84 | - 实现边栏菜单、顶栏导航面包屑 85 | 86 | ## 2022.12.06 87 | - 感觉每天都在学习 `css`,更新了后台管理导航的 `layout`,把使用指南的所有样式变量加上了 `doc` 前缀予以区分,并 `markdown` 渲染的样式单独组件中。 88 | - 准备开始撸后台管理的路由 89 | 90 | ## 2022.12.05 91 | - 今天遇到一个大坑:`localStorage` 只存在于客户端中,基于 `ssr` 的 `nuxt` 要使用的话,要注意两点: 92 | 1. 页面中要使用 `` 93 | 2. `pinia` 中要用到 `skipHydrate()` 的辅助函数 94 | 95 | ## 2022.12.04 96 | - 放弃 `gh-pages` 了,改用 `netlify` 零配置成功!!而且开启了 `ssr`。 97 | - 添加 `Dockerfile` 98 | 99 | ## 2022.12.03 100 | - 调研了 `token` 无感刷新,参照别人代码把 `login`, `logout` 功能实现了,在 `pinia` 中使用 `useCookie` 和 `useSessionStorage`,感觉还是一知半解,后面发现问题再改吧。 101 | - 试了好久使用 `github actions` 来将代码演示跑在 `gh-pages` 分支,甚至改了项目的 `baseurl` 和 `ssr`,想让项目生成纯静态页面,感觉还是 `baseurl` 的问题,最后没成功。 102 | 103 | ## 2022.12.02 104 | - 一直在排查 `npm` 包管理问题,安装 `pinia` 之后各种报错,按照网上的 `npm i pinia -f` 也不管用。最后放弃 `npm`,使用 `yarn` 后流畅无比,我以后是 `yarn` 粉了。 105 | 106 | ## 2022.12.01 107 | - 实现文档的 `toc` 和 `navigation` 功能。 108 | 109 | ## 2022.11.30 110 | - 进一步优化 `content` 中 `markdown` 的 `css` 渲染。参考 `@nuxt-themes/docus`,结合`vuepress` 漂亮了不少,新增组件:`Alert.vue`(对应`vuepress`中的`'tip', 'warning', 'danger', 'details'`), `Badge.vue`,`List.vue`。 111 | - 自己搞了个 `logo`,好丑,以后再改吧。 112 | 113 | ## 2022.11.29 114 | - 完成 `content` 中 `markdown` 的 `css` 渲染。参考 `@nuxt-themes/typography`,它用了一个 `pinceau` 的插件,可以在 ` 148 | -------------------------------------------------------------------------------- /src/public/js/tinymce/plugins/emoticons/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.3.1 (2022-12-06) 3 | */ 4 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>t===e,o=e(null),n=e(void 0),s=()=>{},r=()=>!1;class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return null==t?a.none():a.some(t)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const i=(t,e)=>{const o=t.length,n=new Array(o);for(let s=0;s{let e=t;return{get:()=>e,set:t=>{e=t}}},c=Object.keys,u=Object.hasOwnProperty,g=(t,e)=>{const o=c(t);for(let n=0,s=o.length;nu.call(t,e),h=(d=(t,e)=>e,(...t)=>{if(0===t.length)throw new Error("Can't merge zero objects");const e={};for(let o=0;o{const t=(t=>{const e=l(a.none()),o=()=>e.get().each(t);return{clear:()=>{o(),e.set(a.none())},isSet:()=>e.get().isSome(),get:()=>e.get(),set:t=>{o(),e.set(a.some(t))}}})(s);return{...t,on:e=>t.get().each(e)}},v=(t,e,o=0,s)=>{const r=t.indexOf(e,o);return-1!==r&&(!!n(s)||r+e.length<=s)};var y=tinymce.util.Tools.resolve("tinymce.Resource");const f=t=>e=>e.options.get(t),b=f("emoticons_database"),w=f("emoticons_database_url"),_=f("emoticons_database_id"),j=f("emoticons_append"),C=f("emoticons_images_url"),k="All",A={symbols:"Symbols",people:"People",animals_and_nature:"Animals and Nature",food_and_drink:"Food and Drink",activity:"Activity",travel_and_places:"Travel and Places",objects:"Objects",flags:"Flags",user:"User Defined"},O=(t,e)=>m(t,e)?t[e]:e,x=t=>{const e=j(t);return o=t=>({keywords:[],category:"user",...t}),((t,e)=>{const o={};return g(t,((t,n)=>{const s=e(t,n);o[s.k]=s.v})),o})(e,((t,e)=>({k:e,v:o(t)})));var o},L=(t,e)=>v(t.title.toLowerCase(),e)||((t,o)=>{for(let o=0,s=t.length;o{const n=[],s=e.toLowerCase(),a=o.fold((()=>r),(t=>e=>e>=t));for(let o=0;o{const n={pattern:"",results:T(e.listAll(),"",a.some(300))},s=l(k),r=((t,e)=>{let n=null;const s=()=>{o(n)||(clearTimeout(n),n=null)};return{cancel:s,throttle:(...e)=>{s(),n=setTimeout((()=>{n=null,t.apply(null,e)}),200)}}})((t=>{(t=>{const o=t.getData(),n=s.get(),r=e.listCategory(n),i=T(r,o.pattern,n===k?a.some(300):a.none());t.setData({results:i})})(t)})),c={label:"Search",type:"input",name:D},u={type:"collection",name:"results"},g=()=>({title:"Emojis",size:"normal",body:{type:"tabpanel",tabs:i(e.listCategories(),(t=>({title:t,name:t,items:[c,u]})))},initialData:n,onTabChange:(t,e)=>{s.set(e.newTabName),r.throttle(t)},onChange:r.throttle,onAction:(e,o)=>{"results"===o.name&&(((t,e)=>{t.insertContent(e)})(t,o.value),e.close())},buttons:[{type:"cancel",text:"Close",primary:!0}]}),m=t.windowManager.open(g());m.focus(D),e.hasLoaded()||(m.block("Loading emojis..."),e.waitForLoad().then((()=>{m.redial(g()),r.throttle(m),m.focus(D),m.unblock()})).catch((t=>{m.redial({title:"Emojis",body:{type:"panel",items:[{type:"alertbanner",level:"error",icon:"warning",text:"Could not load emojis"}]},buttons:[{type:"cancel",text:"Close",primary:!0}],initialData:{pattern:"",results:[]}}),m.focus(D),m.unblock()})))};t.add("emoticons",((t,e)=>{((t,e)=>{const o=t.options.register;o("emoticons_database",{processor:"string",default:"emojis"}),o("emoticons_database_url",{processor:"string",default:`${e}/js/${b(t)}${t.suffix}.js`}),o("emoticons_database_id",{processor:"string",default:"tinymce.plugins.emoticons"}),o("emoticons_append",{processor:"object",default:{}}),o("emoticons_images_url",{processor:"string",default:"https://twemoji.maxcdn.com/v/13.0.1/72x72/"})})(t,e);const o=((t,e,o)=>{const n=p(),s=p(),r=C(t),i=t=>{return o="=o.length&&e.substr(0,0+o.length)===o?t.char.replace(/src="([^"]+)"/,((t,e)=>`src="${r}${e}"`)):t.char;var e,o};t.on("init",(()=>{y.load(o,e).then((e=>{const o=x(t);(t=>{const e={},o=[];g(t,((t,n)=>{const s={title:n,keywords:t.keywords,char:i(t),category:O(A,t.category)},r=void 0!==e[s.category]?e[s.category]:[];e[s.category]=r.concat([s]),o.push(s)})),n.set(e),s.set(o)})(h(e,o))}),(t=>{console.log(`Failed to load emojis: ${t}`),n.set({}),s.set([])}))}));const l=()=>s.get().getOr([]),u=()=>n.isSet()&&s.isSet();return{listCategories:()=>[k].concat(c(n.get().getOr({}))),hasLoaded:u,waitForLoad:()=>u()?Promise.resolve(!0):new Promise(((t,o)=>{let n=15;const s=setInterval((()=>{u()?(clearInterval(s),t(!0)):(n--,n<0&&(console.log("Could not load emojis from url: "+e),clearInterval(s),o(!1)))}),100)})),listAll:l,listCategory:t=>t===k?l():n.get().bind((e=>a.from(e[t]))).getOr([])}})(t,w(t),_(t));((t,e)=>{t.addCommand("mceEmoticons",(()=>E(t,e)))})(t,o),(t=>{const e=()=>t.execCommand("mceEmoticons");t.ui.registry.addButton("emoticons",{tooltip:"Emojis",icon:"emoji",onAction:e}),t.ui.registry.addMenuItem("emoticons",{text:"Emojis...",icon:"emoji",onAction:e})})(t),((t,e)=>{t.ui.registry.addAutocompleter("emoticons",{trigger:":",columns:"auto",minChars:2,fetch:(t,o)=>e.waitForLoad().then((()=>{const n=e.listAll();return T(n,t,a.some(o))})),onAction:(e,o,n)=>{t.selection.setRng(o),t.insertContent(n),e.hide()}})})(t,o),(t=>{t.on("PreInit",(()=>{t.parser.addAttributeFilter("data-emoticon",(t=>{((t,e)=>{for(let e=0,n=t.length;e 2 | import type { VueUiCarouselTableConfig, VueUiCarouselTableDataset, VueUiDonutConfig, VueUiDonutDatasetItem, VueUiDumbbellConfig, VueUiDumbbellDataset, VueUiXyConfig, VueUiXyDatasetItem } from 'vue-data-ui' 3 | import dayjs from 'dayjs' 4 | import { VueUiCarouselTable, VueUiDonut, VueUiDumbbell } from 'vue-data-ui' 5 | 6 | const screen = ref(null) 7 | 8 | const { isFullscreen, toggle: toggleFullscreen } = useFullscreen(screen) 9 | const currentDateTime = ref(dayjs().format('YYYY-MM-DD HH:mm:ss')) 10 | 11 | useIntervalFn(() => { 12 | currentDateTime.value = dayjs().format('YYYY-MM-DD HH:mm:ss') 13 | }, 1000) 14 | 15 | const configXy: VueUiXyConfig = { 16 | showTable: false, 17 | chart: { 18 | userOptions: { show: false }, 19 | padding: { bottom: 0 }, 20 | backgroundColor: 'transparent', 21 | legend: { color: 'white' }, 22 | grid: { labels: { color: 'white', xAxisLabels: { show: false } } }, 23 | }, 24 | } 25 | const datasetXy: VueUiXyDatasetItem[] = [ 26 | { 27 | name: '数据1', 28 | series: Array.from({ length: 50 }, () => (Math.random() * 100)), 29 | type: 'bar', 30 | }, 31 | { 32 | name: '数据2', 33 | series: Array.from({ length: 50 }, () => (Math.random() * -100)), 34 | type: 'line', 35 | color: 'rgb(66,211,146)', 36 | 37 | dataLabels: false, 38 | useArea: true, 39 | scaleSteps: 10, 40 | }, 41 | { 42 | name: '数据3', 43 | series: Array.from({ length: 50 }, (_, i) => (-50 + 10 * Math.E ** (i / 18))), 44 | type: 'line', 45 | useProgression: true, 46 | }, 47 | ] 48 | 49 | const configTable: VueUiCarouselTableConfig | any = { 50 | showTable: false, 51 | responsiveBreakpoint: 200, 52 | style: { backgroundColor: 'transparent' }, 53 | userOptions: { show: false }, 54 | thead: { tr: { height: 50, style: { color: 'white', backgroundColor: 'transparent' } } }, 55 | tbody: { 56 | backgroundColor: 'transparent', 57 | tr: { 58 | visible: 8, 59 | height: 40, 60 | style: { color: 'white', backgroundColor: 'transparent' }, 61 | td: { alternateColor: true, alternateOpacity: 0.1, style: { backgroundColor: '#000A2A' } }, 62 | }, 63 | 64 | }, 65 | scrollbar: { hide: true }, 66 | } 67 | const datasetTable: VueUiCarouselTableDataset = { 68 | head: ['字段1', '字段2', '字段3'], 69 | body: Array.from({ length: 20 }, (_, i) => [String(i + 1), `${String(i + 1)}-1`, `${String(i + 1)}-2`]), 70 | } 71 | 72 | const configDonut: VueUiDonutConfig = { 73 | userOptions: { show: false }, 74 | table: { show: false }, 75 | style: { chart: { 76 | color: 'white', 77 | backgroundColor: 'transparent', 78 | legend: { show: false }, 79 | layout: { 80 | labels: { 81 | name: { color: 'white' }, 82 | percentage: { color: 'white' }, 83 | hollow: { total: { color: 'white', value: { color: 'white' } }, average: { show: false } }, 84 | }, 85 | }, 86 | } }, 87 | } 88 | const datasetDonut: VueUiDonutDatasetItem[] = [ 89 | { name: '数据1', values: [100] }, 90 | { name: '数据2', values: [200] }, 91 | { name: '数据3', values: [300, 1] }, 92 | ] 93 | 94 | const configDumbbell: VueUiDumbbellConfig = { 95 | userOptions: { show: false }, 96 | table: { show: false }, 97 | style: { 98 | chart: { 99 | backgroundColor: 'transparent', 100 | labels: { xAxisLabels: { color: '#cccccc', fontSize: 10 }, yAxisLabels: { color: '#cccccc', fontSize: 10 } }, 101 | legend: { backgroundColor: 'transparent', color: '#cccccc', labelStart: '2014', labelEnd: '2024' }, 102 | }, 103 | }, 104 | } 105 | const datasetDumbbell: VueUiDumbbellDataset[] = [ 106 | { name: '数据1', start: 5000, end: 9100 }, 107 | { name: '数据2', start: 3000, end: 4200 }, 108 | { name: '数据3', start: 1000, end: 2000 }, 109 | ] 110 | 111 | 112 | 163 | 164 | 208 | -------------------------------------------------------------------------------- /src/pages/Demo/TinyMCE.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 130 | 131 | 219 | --------------------------------------------------------------------------------