build(projects): 根据最新版main代码更新thin分支,去除plugins等包文件和多余的views

This commit is contained in:
zhang771 2023-06-02 20:34:37 +08:00
parent ff5bf62989
commit e3867db4cc
80 changed files with 13 additions and 6288 deletions

View File

@ -43,7 +43,7 @@
"terminal.integrated.fontWeight": 500,
"terminal.integrated.tabs.enabled": true,
"workbench.iconTheme": "material-icon-theme",
"workbench.colorTheme": "One Dark Pro",
"workbench.colorTheme": "GitHub Light Colorblind (Beta)",
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},

File diff suppressed because it is too large Load Diff

View File

@ -65,30 +65,21 @@
"colord": "2.9.3",
"crypto-js": "4.1.1",
"dayjs": "1.11.7",
"echarts": "5.4.2",
"form-data": "4.0.0",
"lodash-es": "4.17.21",
"naive-ui": "2.34.4",
"pinia": "2.1.3",
"print-js": "1.6.0",
"qs": "6.11.2",
"swiper": "9.3.2",
"ua-parser-js": "1.0.35",
"vditor": "3.9.3",
"vue": "3.3.4",
"vue-i18n": "9.2.2",
"vue-router": "4.2.2",
"vuedraggable": "4.1.0",
"wangeditor": "4.7.15",
"xgplayer": "3.0.2"
"vue-router": "4.2.2"
},
"devDependencies": {
"@amap/amap-jsapi-types": "0.0.13",
"@iconify/json": "2.2.72",
"@iconify/vue": "4.1.1",
"@soybeanjs/cli": "0.4.1",
"@soybeanjs/vite-plugin-vue-page-route": "0.0.5",
"@types/bmapgl": "0.0.7",
"@types/crypto-js": "4.1.1",
"@types/node": "20.2.5",
"@types/qs": "6.9.7",

View File

@ -36,9 +36,6 @@ dependencies:
dayjs:
specifier: 1.11.7
version: 1.11.7
echarts:
specifier: 5.4.2
version: 5.4.2
form-data:
specifier: 4.0.0
version: 4.0.0
@ -51,21 +48,15 @@ dependencies:
pinia:
specifier: 2.1.3
version: 2.1.3(typescript@5.0.4)(vue@3.3.4)
print-js:
specifier: 1.6.0
version: 1.6.0
qs:
specifier: 6.11.2
version: 6.11.2
swiper:
specifier: 9.3.2
version: 9.3.2
soybean-admin:
specifier: 'link:'
version: 'link:'
ua-parser-js:
specifier: 1.0.35
version: 1.0.35
vditor:
specifier: 3.9.3
version: 3.9.3
vue:
specifier: 3.3.4
version: 3.3.4
@ -75,20 +66,8 @@ dependencies:
vue-router:
specifier: 4.2.2
version: 4.2.2(vue@3.3.4)
vuedraggable:
specifier: 4.1.0
version: 4.1.0(vue@3.3.4)
wangeditor:
specifier: 4.7.15
version: 4.7.15
xgplayer:
specifier: 3.0.2
version: 3.0.2(core-js@3.30.2)
devDependencies:
'@amap/amap-jsapi-types':
specifier: 0.0.13
version: 0.0.13
'@iconify/json':
specifier: 2.2.72
version: 2.2.72
@ -101,9 +80,6 @@ devDependencies:
'@soybeanjs/vite-plugin-vue-page-route':
specifier: 0.0.5
version: 0.0.5
'@types/bmapgl':
specifier: 0.0.7
version: 0.0.7
'@types/crypto-js':
specifier: 4.1.1
version: 4.1.1
@ -191,10 +167,6 @@ devDependencies:
packages:
/@amap/amap-jsapi-types@0.0.13:
resolution: {integrity: sha512-hwp36URjQT9vDTmoUPYph3SEAiOvoUB+PGK0jZeZamgvaxew7rgc1XZWL/HphyMfRAqKwIYsAvXm9v8DTSjjzA==}
dev: true
/@ampproject/remapping@2.2.1:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
@ -1617,14 +1589,6 @@ packages:
resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==}
dev: true
/@babel/runtime-corejs3@7.22.3:
resolution: {integrity: sha512-6bdmknScYKmt8I9VjsJuKKGr+TwUb555FTf6tT1P/ANlCjTHCiYLhiQ4X/O7J731w5NOqu8c1aYHEVuOwPz7jA==}
engines: {node: '>=6.9.0'}
dependencies:
core-js-pure: 3.30.2
regenerator-runtime: 0.13.11
dev: false
/@babel/runtime@7.22.3:
resolution: {integrity: sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==}
engines: {node: '>=6.9.0'}
@ -2549,10 +2513,6 @@ packages:
minimatch: 9.0.1
dev: true
/@types/bmapgl@0.0.7:
resolution: {integrity: sha512-3R0wFbZtynfHBJq0v477amaNH3t2u2CzBo46ViIPDdOTEJJ+Ma/ql4X8tS2XjDZcZhDAr6QDWoqV8SZvp6STvA==}
dev: true
/@types/crypto-js@4.1.1:
resolution: {integrity: sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==}
dev: true
@ -3905,16 +3865,6 @@ packages:
browserslist: 4.21.7
dev: true
/core-js-pure@3.30.2:
resolution: {integrity: sha512-p/npFUJXXBkCCTIlEGBdghofn00jWG6ZOtdoIXSJmAu2QBvN0IqpZXWweOytcwE6cfx8ZvVUy1vw8zxhe4Y2vg==}
requiresBuild: true
dev: false
/core-js@3.30.2:
resolution: {integrity: sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==}
requiresBuild: true
dev: false
/cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
@ -4121,13 +4071,6 @@ packages:
resolution: {integrity: sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==}
dev: false
/d@1.0.1:
resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==}
dependencies:
es5-ext: 0.10.62
type: 1.2.0
dev: false
/dagre@0.8.5:
resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
dependencies:
@ -4135,12 +4078,6 @@ packages:
lodash: 4.17.21
dev: false
/danmu.js@1.1.8:
resolution: {integrity: sha512-GIFSHqJ+HFTGLLaL2BHMPBaOuPY1bWPwC0Pvi/V06uMIoxNTyEGxMuoO2SzNHsDvKC/r252zR9T/Gwx93AaKfw==}
dependencies:
event-emitter: 0.3.5
dev: false
/date-fns-tz@1.3.8(date-fns@2.30.0):
resolution: {integrity: sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==}
peerDependencies:
@ -4317,10 +4254,6 @@ packages:
resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==}
dev: false
/diff-match-patch@1.0.5:
resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
dev: false
/dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@ -4405,10 +4338,6 @@ packages:
engines: {node: '>=12'}
dev: true
/downloadjs@1.4.7:
resolution: {integrity: sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==}
dev: false
/duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
dev: true
@ -4417,13 +4346,6 @@ packages:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
/echarts@5.4.2:
resolution: {integrity: sha512-2W3vw3oI2tWJdyAz+b8DuWS0nfXtSDqlDmqgin/lfzbkB01cuMEN66KWBlmur3YMp5nEDEEt5s23pllnAzB4EA==}
dependencies:
tslib: 2.3.0
zrender: 5.4.3
dev: false
/ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: true
@ -4554,31 +4476,6 @@ packages:
is-symbol: 1.0.4
dev: true
/es5-ext@0.10.62:
resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==}
engines: {node: '>=0.10'}
requiresBuild: true
dependencies:
es6-iterator: 2.0.3
es6-symbol: 3.1.3
next-tick: 1.1.0
dev: false
/es6-iterator@2.0.3:
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
dependencies:
d: 1.0.1
es5-ext: 0.10.62
es6-symbol: 3.1.3
dev: false
/es6-symbol@3.1.3:
resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==}
dependencies:
d: 1.0.1
ext: 1.7.0
dev: false
/esbuild-android-64@0.14.54:
resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
engines: {node: '>=12'}
@ -5280,17 +5177,6 @@ packages:
engines: {node: '>= 0.6'}
dev: true
/event-emitter@0.3.5:
resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
dependencies:
d: 1.0.1
es5-ext: 0.10.62
dev: false
/eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
dev: false
/evtd@0.2.4:
resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==}
dev: false
@ -5340,12 +5226,6 @@ packages:
- supports-color
dev: true
/ext@1.7.0:
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
dependencies:
type: 2.7.2
dev: false
/extend-shallow@2.0.1:
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'}
@ -7236,10 +7116,6 @@ packages:
engines: {node: '>= 0.6'}
dev: true
/next-tick@1.1.0:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
dev: false
/node-fetch-native@1.1.1:
resolution: {integrity: sha512-9VvspTSUp2Sxbl+9vbZTlFGq9lHwE8GDVVekxx6YsNd1YH59sb3Ba8v3Y3cD8PkLNcileGGcA21PFjVl0jzDaw==}
dev: true
@ -7909,10 +7785,6 @@ packages:
engines: {node: ^14.13.1 || >=16.0.0}
dev: true
/print-js@1.6.0:
resolution: {integrity: sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg==}
dev: false
/proc-log@3.0.0:
resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@ -8583,10 +8455,6 @@ packages:
smart-buffer: 4.2.0
dev: true
/sortablejs@1.14.0:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
dev: false
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
@ -8676,10 +8544,6 @@ packages:
extend-shallow: 3.0.2
dev: true
/ssr-window@4.0.2:
resolution: {integrity: sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==}
dev: false
/ssri@10.0.4:
resolution: {integrity: sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@ -8950,13 +8814,6 @@ packages:
stable: 0.1.8
dev: true
/swiper@9.3.2:
resolution: {integrity: sha512-Kj9Z4kXRmJR3YT/Wj+XLWj8P6IcRt+WG38uL8M3/Wny7+6sV0TlP9vnE1X+Co9c7VzNooojWGnFa+Wf/9+CUMA==}
engines: {node: '>= 4.7.0'}
dependencies:
ssr-window: 4.0.2
dev: false
/synckit@0.8.5:
resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==}
engines: {node: ^14.18.0 || >=16.0.0}
@ -9094,10 +8951,6 @@ packages:
/tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
/tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
dev: false
/tslib@2.5.2:
resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==}
@ -9170,14 +9023,6 @@ packages:
engines: {node: '>=12.20'}
dev: true
/type@1.2.0:
resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==}
dev: false
/type@2.7.2:
resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
dev: false
/typed-array-length@1.0.4:
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
dependencies:
@ -9475,12 +9320,6 @@ packages:
vue: 3.3.4
dev: false
/vditor@3.9.3:
resolution: {integrity: sha512-w3ZSa8SQaZNFEO7XeHsJEW6NoXH4FgqU1BHVYfQ2tGk1y9rQMVWYJOXegO25QhgnAIF4qtm/AoylUg4b5mW7cg==}
dependencies:
diff-match-patch: 1.0.5
dev: false
/vite-plugin-compression@0.5.1(vite@4.3.9):
resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==}
peerDependencies:
@ -9688,15 +9527,6 @@ packages:
'@vue/server-renderer': 3.3.4(vue@3.3.4)
'@vue/shared': 3.3.4
/vuedraggable@4.1.0(vue@3.3.4):
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
peerDependencies:
vue: ^3.0.1
dependencies:
sortablejs: 1.14.0
vue: 3.3.4
dev: false
/vueuc@0.4.51(vue@3.3.4):
resolution: {integrity: sha512-pLiMChM4f+W8czlIClGvGBYo656lc2Y0/mXFSCydcSmnCR1izlKPGMgiYBGjbY9FDkFG8a2HEVz7t0DNzBWbDw==}
peerDependencies:
@ -9712,14 +9542,6 @@ packages:
vue: 3.3.4
dev: false
/wangeditor@4.7.15:
resolution: {integrity: sha512-aPTdREd8BxXVyJ5MI+LU83FQ7u1EPd341iXIorRNYSOvoimNoZ4nPg+yn3FGbB93/owEa6buLw8wdhYnMCJQLg==}
dependencies:
'@babel/runtime': 7.22.3
'@babel/runtime-corejs3': 7.22.3
tslib: 2.5.2
dev: false
/webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
dev: true
@ -9862,6 +9684,7 @@ packages:
/workbox-cacheable-response@6.6.0:
resolution: {integrity: sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==}
deprecated: workbox-background-sync@6.6.0
dependencies:
workbox-core: 6.6.0
dev: true
@ -9992,28 +9815,6 @@ packages:
engines: {node: '>=12'}
dev: true
/xgplayer-subtitles@1.1.1(core-js@3.30.2):
resolution: {integrity: sha512-GYzrK/e4ydAATP3Xg06sXYliiSCcyNIqqQSwnWbs7pw+cc5NwyrYXuLfa3Bp9skIxT6pT+A7qTicUps58N3eEQ==}
peerDependencies:
core-js: '>=3.12.1'
dependencies:
core-js: 3.30.2
eventemitter3: 4.0.7
dev: false
/xgplayer@3.0.2(core-js@3.30.2):
resolution: {integrity: sha512-vgSo5exPlyl7BxxxOWeYA1+x1nLmZDiWDjPJmaaxeW9jFghGBSf3XP+yLKSOJmKOlTw/LpmOcNAsJxqNhi6Bzw==}
peerDependencies:
core-js: '>=3.12.1'
dependencies:
core-js: 3.30.2
danmu.js: 1.1.8
delegate: 3.2.0
downloadjs: 1.4.7
eventemitter3: 4.0.7
xgplayer-subtitles: 1.1.1(core-js@3.30.2)
dev: false
/xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
@ -10064,9 +9865,3 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: true
/zrender@5.4.3:
resolution: {integrity: sha512-DRUM4ZLnoaT0PBVvGBDO9oWIDBKFdAVieNWxWwK0niYzJCMwGchRk21/hsE+RKkIveH3XHCyvXcJDkgLVvfizQ==}
dependencies:
tslib: 2.3.0
dev: false

View File

@ -1,174 +0,0 @@
import { nextTick, effectScope, onScopeDispose, ref, watch } from 'vue';
import type { ComputedRef, Ref } from 'vue';
import * as echarts from 'echarts/core';
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
import type {
BarSeriesOption,
GaugeSeriesOption,
LineSeriesOption,
PictorialBarSeriesOption,
PieSeriesOption,
RadarSeriesOption,
ScatterSeriesOption
} from 'echarts/charts';
import {
DatasetComponent,
GridComponent,
LegendComponent,
TitleComponent,
ToolboxComponent,
TooltipComponent,
TransformComponent
} from 'echarts/components';
import type {
DatasetComponentOption,
GridComponentOption,
LegendComponentOption,
TitleComponentOption,
ToolboxComponentOption,
TooltipComponentOption
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { useElementSize } from '@vueuse/core';
import { useThemeStore } from '@/store';
export type ECOption = echarts.ComposeOption<
| BarSeriesOption
| LineSeriesOption
| PieSeriesOption
| ScatterSeriesOption
| PictorialBarSeriesOption
| RadarSeriesOption
| GaugeSeriesOption
| TitleComponentOption
| LegendComponentOption
| TooltipComponentOption
| GridComponentOption
| ToolboxComponentOption
| DatasetComponentOption
>;
echarts.use([
TitleComponent,
LegendComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
ToolboxComponent,
BarChart,
LineChart,
PieChart,
ScatterChart,
PictorialBarChart,
RadarChart,
GaugeChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
/**
* Echarts hooks函数
* @param options -
* @param renderFun - ()
* @description
*/
export function useEcharts(
options: Ref<ECOption> | ComputedRef<ECOption>,
renderFun?: (chartInstance: echarts.ECharts) => void
) {
const theme = useThemeStore();
const domRef = ref<HTMLElement>();
const initialSize = { width: 0, height: 0 };
const { width, height } = useElementSize(domRef, initialSize);
let chart: echarts.ECharts | null = null;
function canRender() {
return initialSize.width > 0 && initialSize.height > 0;
}
function isRendered() {
return Boolean(domRef.value && chart);
}
function update(updateOptions: ECOption) {
if (isRendered()) {
chart?.clear();
chart!.setOption({ ...updateOptions, backgroundColor: 'transparent' });
}
}
async function render() {
if (domRef.value) {
const chartTheme = theme.darkMode ? 'dark' : 'light';
await nextTick();
chart = echarts.init(domRef.value, chartTheme);
if (renderFun) {
renderFun(chart);
}
update(options.value);
}
}
function resize() {
chart?.resize();
}
function destroy() {
chart?.dispose();
}
function updateTheme() {
destroy();
render();
}
const scope = effectScope();
scope.run(() => {
watch([width, height], ([newWidth, newHeight]) => {
initialSize.width = newWidth;
initialSize.height = newHeight;
if (newWidth === 0 && newHeight === 0) {
// 节点被删除 将chart置为空
chart = null;
}
if (canRender()) {
if (!isRendered()) {
render();
} else {
resize();
}
}
});
watch(
options,
newValue => {
update(newValue);
},
{ deep: true }
);
watch(
() => theme.darkMode,
() => {
updateTheme();
}
);
});
onScopeDispose(() => {
destroy();
scope.stop();
});
return {
domRef
};
}

View File

@ -2,5 +2,4 @@ export * from './system';
export * from './router';
export * from './layout';
export * from './events';
export * from './echarts';
export * from './icon';

View File

@ -1,3 +1,2 @@
export * from './service';
export * from './regexp';
export * from './map-sdk';

View File

@ -1,8 +0,0 @@
/** 百度地图sdk地址 */
export const BAIDU_MAP_SDK_URL = `https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1`;
/** 高德地图sdk地址 */
export const GAODE_MAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=e7bd02bd504062087e6563daf4d6721d';
/** 腾讯地图sdk地址 */
export const TENCENT_MAP_SDK_URL = 'https://map.qq.com/api/gljs?v=1.exp&key=A6DBZ-KXPLW-JKSRY-ONZF4-CPHY3-K6BL7';

View File

@ -1,8 +1,5 @@
import 'uno.css';
import '@soybeanjs/vue-materials/dist/style.css';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import 'virtual:svg-icons-register';
import '../styles/css/global.css';

View File

@ -1,17 +0,0 @@
const about1: AuthRoute.Route = {
name: 'about',
path: '/about',
component: 'self',
meta: {
title: '关于',
i18nTitle: 'message.routes.about',
requiresAuth: true,
keepAlive: true,
singleLayout: 'basic',
permissions: ['super', 'admin', 'user'],
icon: 'fluent:book-information-24-regular',
order: 10
}
};
export default about1;

View File

@ -1,38 +0,0 @@
const authDemo: AuthRoute.Route = {
name: 'auth-demo',
path: '/auth-demo',
component: 'basic',
children: [
{
name: 'auth-demo_permission',
path: '/auth-demo/permission',
component: 'self',
meta: {
title: '权限切换',
i18nTitle: 'message.routes.auth-demo.permission',
requiresAuth: true,
icon: 'ic:round-construction'
}
},
{
name: 'auth-demo_super',
path: '/auth-demo/super',
component: 'self',
meta: {
title: '超级管理员可见',
i18nTitle: 'message.routes.auth-demo.super',
requiresAuth: true,
permissions: ['super'],
icon: 'ic:round-supervisor-account'
}
}
],
meta: {
title: '权限示例',
i18nTitle: 'message.routes.auth-demo._value',
icon: 'ic:baseline-security',
order: 5
}
};
export default authDemo;

View File

@ -1,48 +0,0 @@
const component: AuthRoute.Route = {
name: 'component',
path: '/component',
component: 'basic',
children: [
{
name: 'component_button',
path: '/component/button',
component: 'self',
meta: {
title: '按钮',
i18nTitle: 'message.routes.component.button',
requiresAuth: true,
icon: 'mdi:button-cursor'
}
},
{
name: 'component_card',
path: '/component/card',
component: 'self',
meta: {
title: '卡片',
i18nTitle: 'message.routes.component.card',
requiresAuth: true,
icon: 'mdi:card-outline'
}
},
{
name: 'component_table',
path: '/component/table',
component: 'self',
meta: {
title: '表格',
i18nTitle: 'message.routes.component.table',
requiresAuth: true,
icon: 'mdi:table-large'
}
}
],
meta: {
title: '组件示例',
i18nTitle: 'message.routes.component._value',
icon: 'cib:app-store',
order: 3
}
};
export default component;

View File

@ -1,70 +0,0 @@
const document: AuthRoute.Route = {
name: 'document',
path: '/document',
component: 'basic',
children: [
{
name: 'document_vue',
path: '/document/vue',
component: 'self',
meta: {
title: 'vue文档',
i18nTitle: 'message.routes.document.vue',
requiresAuth: true,
icon: 'logos:vue'
}
},
{
name: 'document_vite',
path: '/document/vite',
component: 'self',
meta: {
title: 'vite文档',
i18nTitle: 'message.routes.document.vite',
requiresAuth: true,
icon: 'logos:vitejs'
}
},
{
name: 'document_naive',
path: '/document/naive',
component: 'self',
meta: {
title: 'naive文档',
i18nTitle: 'message.routes.document.naive',
requiresAuth: true,
icon: 'logos:naiveui'
}
},
{
name: 'document_project',
path: '/document/project',
component: 'self',
meta: {
title: '项目文档',
i18nTitle: 'message.routes.document.project',
requiresAuth: true,
localIcon: 'logo'
}
},
{
name: 'document_project-link',
path: '/document/project-link',
meta: {
title: '项目文档(外链)',
i18nTitle: 'message.routes.document.project-link',
requiresAuth: true,
localIcon: 'logo',
href: 'https://docs.soybean.pro/'
}
}
],
meta: {
title: '文档',
i18nTitle: 'message.routes.document._value',
icon: 'mdi:file-document-multiple-outline',
order: 2
}
};
export default document;

View File

@ -1,48 +0,0 @@
const exception: AuthRoute.Route = {
name: 'exception',
path: '/exception',
component: 'basic',
children: [
{
name: 'exception_403',
path: '/exception/403',
component: 'self',
meta: {
title: '异常页403',
i18nTitle: 'message.routes.exception.403',
requiresAuth: true,
icon: 'ic:baseline-block'
}
},
{
name: 'exception_404',
path: '/exception/404',
component: 'self',
meta: {
title: '异常页404',
i18nTitle: 'message.routes.exception.404',
requiresAuth: true,
icon: 'ic:baseline-web-asset-off'
}
},
{
name: 'exception_500',
path: '/exception/500',
component: 'self',
meta: {
title: '异常页500',
i18nTitle: 'message.routes.exception.500',
requiresAuth: true,
icon: 'ic:baseline-wifi-off'
}
}
],
meta: {
i18nTitle: 'message.routes.exception._value',
title: '异常页',
icon: 'ant-design:exception-outlined',
order: 7
}
};
export default exception;

View File

@ -1,51 +0,0 @@
const functionRoute: AuthRoute.Route = {
name: 'function',
path: '/function',
component: 'basic',
children: [
{
name: 'function_tab',
path: '/function/tab',
component: 'self',
meta: {
title: 'Tab',
i18nTitle: 'message.routes.function.tab',
requiresAuth: true,
icon: 'ic:round-tab'
}
},
{
name: 'function_tab-detail',
path: '/function/tab-detail',
component: 'self',
meta: {
title: 'Tab Detail',
requiresAuth: true,
hide: true,
activeMenu: 'function_tab',
icon: 'ic:round-tab'
}
},
{
name: 'function_tab-multi-detail',
path: '/function/tab-multi-detail',
component: 'self',
meta: {
title: 'Tab Multi Detail',
requiresAuth: true,
hide: true,
multiTab: true,
activeMenu: 'function_tab',
icon: 'ic:round-tab'
}
}
],
meta: {
title: '功能',
i18nTitle: 'message.routes.function._value',
icon: 'icon-park-outline:all-application',
order: 6
}
};
export default functionRoute;

View File

@ -1,59 +0,0 @@
const management: AuthRoute.Route = {
name: 'management',
path: '/management',
component: 'basic',
children: [
{
name: 'management_auth',
path: '/management/auth',
component: 'self',
meta: {
title: '权限管理',
i18nTitle: 'message.routes.management.auth',
requiresAuth: true,
icon: 'ic:baseline-security'
}
},
{
name: 'management_role',
path: '/management/role',
component: 'self',
meta: {
title: '角色管理',
i18nTitle: 'message.routes.management.role',
requiresAuth: true,
icon: 'carbon:user-role'
}
},
{
name: 'management_user',
path: '/management/user',
component: 'self',
meta: {
title: '用户管理',
i18nTitle: 'message.routes.management.user',
requiresAuth: true,
icon: 'ic:round-manage-accounts'
}
},
{
name: 'management_route',
path: '/management/route',
component: 'self',
meta: {
title: '路由管理',
i18nTitle: 'message.routes.management.route',
requiresAuth: true,
icon: 'material-symbols:route'
}
}
],
meta: {
title: '系统管理',
i18nTitle: 'message.routes.management._value',
icon: 'carbon:cloud-service-management',
order: 9
}
};
export default management;

View File

@ -1,61 +0,0 @@
const multiMenu: AuthRoute.Route = {
name: 'multi-menu',
path: '/multi-menu',
component: 'basic',
children: [
{
name: 'multi-menu_first',
path: '/multi-menu/first',
component: 'multi',
children: [
{
name: 'multi-menu_first_second',
path: '/multi-menu/first/second',
component: 'self',
meta: {
title: '二级菜单',
i18nTitle: 'message.routes.multi-menu.first.second',
requiresAuth: true,
icon: 'mdi:menu'
}
},
{
name: 'multi-menu_first_second-new',
path: '/multi-menu/first/second-new',
component: 'multi',
children: [
{
name: 'multi-menu_first_second-new_third',
path: '/multi-menu/first/second-new/third',
component: 'self',
meta: {
title: '三级菜单',
i18nTitle: 'message.routes.multi-menu.first.second-new.third',
requiresAuth: true,
icon: 'mdi:menu'
}
}
],
meta: {
title: '二级菜单(有子菜单)',
i18nTitle: 'message.routes.multi-menu.first.second-new._value',
icon: 'mdi:menu'
}
}
],
meta: {
title: '一级菜单',
i18nTitle: 'message.routes.multi-menu.first._value',
icon: 'mdi:menu'
}
}
],
meta: {
title: '多级菜单',
i18nTitle: 'message.routes.multi-menu._value',
icon: 'carbon:menu',
order: 8
}
};
export default multiMenu;

View File

@ -1,149 +0,0 @@
const plugin: AuthRoute.Route = {
name: 'plugin',
path: '/plugin',
component: 'basic',
children: [
{
name: 'plugin_charts',
path: '/plugin/charts',
component: 'multi',
children: [
{
name: 'plugin_charts_echarts',
path: '/plugin/charts/echarts',
component: 'self',
meta: {
title: 'ECharts',
i18nTitle: 'message.routes.plugin.charts.echarts',
requiresAuth: true,
icon: 'simple-icons:apacheecharts'
}
},
{
name: 'plugin_charts_antv',
path: '/plugin/charts/antv',
component: 'self',
meta: {
title: 'AntV',
i18nTitle: 'message.routes.plugin.charts.antv',
requiresAuth: true,
icon: 'simple-icons:antdesign'
}
}
],
meta: {
title: '图表',
i18nTitle: 'message.routes.plugin.charts._value',
icon: 'mdi:chart-areaspline'
}
},
{
name: 'plugin_map',
path: '/plugin/map',
component: 'self',
meta: {
title: '地图',
i18nTitle: 'message.routes.plugin.map',
requiresAuth: true,
icon: 'mdi:map'
}
},
{
name: 'plugin_video',
path: '/plugin/video',
component: 'self',
meta: {
title: '视频',
i18nTitle: 'message.routes.plugin.video',
requiresAuth: true,
icon: 'mdi:video'
}
},
{
name: 'plugin_editor',
path: '/plugin/editor',
component: 'multi',
children: [
{
name: 'plugin_editor_quill',
path: '/plugin/editor/quill',
component: 'self',
meta: {
title: '富文本编辑器',
i18nTitle: 'message.routes.plugin.editor.quill',
requiresAuth: true,
icon: 'mdi:file-document-edit-outline'
}
},
{
name: 'plugin_editor_markdown',
path: '/plugin/editor/markdown',
component: 'self',
meta: {
title: 'markdown编辑器',
i18nTitle: 'message.routes.plugin.editor.markdown',
requiresAuth: true,
icon: 'ri:markdown-line'
}
}
],
meta: {
title: '编辑器',
i18nTitle: 'message.routes.plugin.editor._value',
icon: 'icon-park-outline:editor'
}
},
{
name: 'plugin_swiper',
path: '/plugin/swiper',
component: 'self',
meta: {
title: 'Swiper插件',
i18nTitle: 'message.routes.plugin.swiper',
requiresAuth: true,
icon: 'simple-icons:swiper'
}
},
{
name: 'plugin_copy',
path: '/plugin/copy',
component: 'self',
meta: {
title: '剪贴板',
i18nTitle: 'message.routes.plugin.copy',
requiresAuth: true,
icon: 'mdi:clipboard-outline'
}
},
{
name: 'plugin_icon',
path: '/plugin/icon',
component: 'self',
meta: {
title: '图标',
i18nTitle: 'message.routes.plugin.icon',
requiresAuth: true,
localIcon: 'custom-icon'
}
},
{
name: 'plugin_print',
path: '/plugin/print',
component: 'self',
meta: {
title: '打印',
i18nTitle: 'message.routes.plugin.print',
requiresAuth: true,
icon: 'mdi:printer'
}
}
],
meta: {
title: '插件示例',
i18nTitle: 'message.routes.plugin._value',
icon: 'clarity:plugin-line',
order: 4
}
};
export default plugin;

View File

@ -22,54 +22,9 @@ declare namespace PageRoute {
| 'constant-page'
| 'login'
| 'not-found'
| 'about'
| 'auth-demo'
| 'auth-demo_permission'
| 'auth-demo_super'
| 'component'
| 'component_button'
| 'component_card'
| 'component_table'
| 'dashboard'
| 'dashboard_analysis'
| 'dashboard_workbench'
| 'document'
| 'document_naive'
| 'document_project-link'
| 'document_project'
| 'document_vite'
| 'document_vue'
| 'exception'
| 'exception_403'
| 'exception_404'
| 'exception_500'
| 'function'
| 'function_tab-detail'
| 'function_tab-multi-detail'
| 'function_tab'
| 'management'
| 'management_auth'
| 'management_role'
| 'management_route'
| 'management_user'
| 'multi-menu'
| 'multi-menu_first'
| 'multi-menu_first_second-new'
| 'multi-menu_first_second-new_third'
| 'multi-menu_first_second'
| 'plugin'
| 'plugin_charts'
| 'plugin_charts_antv'
| 'plugin_charts_echarts'
| 'plugin_copy'
| 'plugin_editor'
| 'plugin_editor_markdown'
| 'plugin_editor_quill'
| 'plugin_icon'
| 'plugin_map'
| 'plugin_print'
| 'plugin_swiper'
| 'plugin_video';
| 'dashboard_workbench';
/**
* last degree route key, which has the page file
@ -77,46 +32,6 @@ declare namespace PageRoute {
*/
type LastDegreeRouteKey = Extract<
RouteKey,
| '403'
| '404'
| '500'
| 'constant-page'
| 'login'
| 'not-found'
| 'about'
| 'auth-demo_permission'
| 'auth-demo_super'
| 'component_button'
| 'component_card'
| 'component_table'
| 'dashboard_analysis'
| 'dashboard_workbench'
| 'document_naive'
| 'document_project-link'
| 'document_project'
| 'document_vite'
| 'document_vue'
| 'exception_403'
| 'exception_404'
| 'exception_500'
| 'function_tab-detail'
| 'function_tab-multi-detail'
| 'function_tab'
| 'management_auth'
| 'management_role'
| 'management_route'
| 'management_user'
| 'multi-menu_first_second-new_third'
| 'multi-menu_first_second'
| 'plugin_charts_antv'
| 'plugin_charts_echarts'
| 'plugin_copy'
| 'plugin_editor_markdown'
| 'plugin_editor_quill'
| 'plugin_icon'
| 'plugin_map'
| 'plugin_print'
| 'plugin_swiper'
| 'plugin_video'
'403' | '404' | '500' | 'constant-page' | 'login' | 'not-found' | 'dashboard_analysis' | 'dashboard_workbench'
>;
}

View File

@ -1,19 +0,0 @@
<template>
<n-card title="开发环境依赖" :bordered="false" size="small" class="rounded-16px shadow-sm">
<n-descriptions label-placement="left" bordered size="small">
<n-descriptions-item v-for="item in devDependencies" :key="item.name" :label="item.name">
{{ item.version }}
</n-descriptions-item>
</n-descriptions>
</n-card>
</template>
<script setup lang="ts">
import { pkgJson } from './model';
defineOptions({ name: 'DevDependency' });
const { devDependencies } = pkgJson;
</script>
<style scoped></style>

View File

@ -1,6 +0,0 @@
import ProjectIntroduction from './project-introduction.vue';
import ProjectInfo from './project-info.vue';
import ProDependency from './pro-dependency.vue';
import DevDependency from './dev-dependency.vue';
export { ProjectIntroduction, ProjectInfo, ProDependency, DevDependency };

View File

@ -1,39 +0,0 @@
import pkg from '~/package.json';
/** npm依赖包版本信息 */
export interface PkgVersionInfo {
name: string;
version: string;
}
interface Package {
name: string;
version: string;
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
[key: string]: any;
}
interface PkgJson {
name: string;
version: string;
dependencies: PkgVersionInfo[];
devDependencies: PkgVersionInfo[];
}
const pkgWithType = pkg as Package;
function transformVersionData(tuple: [string, string]): PkgVersionInfo {
const [name, version] = tuple;
return {
name,
version
};
}
export const pkgJson: PkgJson = {
name: pkgWithType.name,
version: pkgWithType.version,
dependencies: Object.entries(pkgWithType.dependencies).map(item => transformVersionData(item)),
devDependencies: Object.entries(pkgWithType.devDependencies).map(item => transformVersionData(item))
};

View File

@ -1,19 +0,0 @@
<template>
<n-card title="生产环境依赖" :bordered="false" size="small" class="rounded-16px shadow-sm">
<n-descriptions label-placement="left" bordered size="small">
<n-descriptions-item v-for="item in dependencies" :key="item.name" :label="item.name">
{{ item.version }}
</n-descriptions-item>
</n-descriptions>
</n-card>
</template>
<script setup lang="ts">
import { pkgJson } from './model';
defineOptions({ name: 'ProDependency' });
const { dependencies } = pkgJson;
</script>
<style scoped></style>

View File

@ -1,29 +0,0 @@
<template>
<n-card title="项目信息" :bordered="false" size="small" class="rounded-16px shadow-sm">
<n-descriptions label-placement="left" bordered size="small" :column="2">
<n-descriptions-item label="版本">
<n-tag type="primary">{{ version }}</n-tag>
</n-descriptions-item>
<n-descriptions-item label="最后编译时间">
<n-tag type="primary">{{ latestBuildTime }}</n-tag>
</n-descriptions-item>
<n-descriptions-item label="Github地址">
<a class="text-primary" href="https://github.com/honghuangdc/soybean-admin" target="_blank">Github地址</a>
</n-descriptions-item>
<n-descriptions-item label="预览地址">
<a class="text-primary" href="https://soybean.pro" target="_blank">预览地址</a>
</n-descriptions-item>
</n-descriptions>
</n-card>
</template>
<script setup lang="ts">
import { pkgJson } from './model';
defineOptions({ name: 'ProjectInfo' });
const { version } = pkgJson;
const latestBuildTime = PROJECT_BUILD_TIME;
</script>
<style scoped></style>

View File

@ -1,14 +0,0 @@
<template>
<n-card title="关于" :bordered="false" size="large" class="rounded-16px shadow-sm">
<p class="leading-24px">
Soybean Admin 是一个基于 Vue3ViteNaive UITypeScript
的中后台解决方案它使用了最新的前端技术栈并提炼了典型的业务模型页面包括二次封装组件动态菜单权限校验粒子化权限控制等功能它可以帮助你快速搭建企业级中后台项目相信不管是从新技术使用还是其他方面都能帮助到你
</p>
</n-card>
</template>
<script setup lang="ts">
defineOptions({ name: 'ProjectIntroduction' });
</script>
<style scoped></style>

View File

@ -1,14 +0,0 @@
<template>
<n-space :vertical="true" :size="16">
<project-introduction />
<project-info />
<pro-dependency />
<dev-dependency />
</n-space>
</template>
<script setup lang="ts">
import { DevDependency, ProDependency, ProjectInfo, ProjectIntroduction } from './components';
</script>
<style scoped></style>

View File

@ -1,55 +0,0 @@
<template>
<div class="h-full">
<n-card title="权限切换" class="h-full shadow-sm rounded-16px">
<div class="pb-12px">
<n-gradient-text type="primary" :size="20">当前用户的权限{{ auth.userInfo.userRole }}</n-gradient-text>
</div>
<n-select
:value="auth.userInfo.userRole"
class="w-120px"
size="small"
:options="options"
@update:value="auth.updateUserRole"
/>
<div class="py-12px">
<n-gradient-text type="primary" :size="20">权限指令 v-permission</n-gradient-text>
</div>
<div>
<n-button v-permission="'super'" class="mr-12px">super可见</n-button>
<n-button v-permission="'admin'" class="mr-12px">admin可见</n-button>
<n-button v-permission="['admin', 'user']">admin和test可见</n-button>
</div>
<div class="py-12px">
<n-gradient-text type="primary" :size="20">权限函数 hasPermission</n-gradient-text>
</div>
<n-space>
<n-button v-if="hasPermission('super')">super可见</n-button>
<n-button v-if="hasPermission('admin')">admin可见</n-button>
<n-button v-if="hasPermission(['admin', 'user'])">admin和user可见</n-button>
</n-space>
</n-card>
</div>
</template>
<script setup lang="ts">
import { watch } from 'vue';
import type { SelectOption } from 'naive-ui';
import { userRoleOptions } from '@/constants';
import { useAppStore, useAuthStore } from '@/store';
import { usePermission } from '@/composables';
const app = useAppStore();
const auth = useAuthStore();
const { hasPermission } = usePermission();
const options: SelectOption[] = userRoleOptions;
watch(
() => auth.userInfo.userRole,
async () => {
app.reloadPage();
}
);
</script>
<style scoped></style>

View File

@ -1,9 +0,0 @@
<template>
<div class="h-full">
<n-card title="当前页面只有super才能看到" class="h-full shadow-sm rounded-16px"></n-card>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -1,575 +0,0 @@
<template>
<div>
<n-card title="按钮" class="h-full shadow-sm rounded-16px">
<n-grid cols="s:1 m:2" responsive="screen" :x-gap="16" :y-gap="16">
<n-grid-item v-for="item in buttonExample" :key="item.id">
<n-card :title="item.label" class="min-h-180px">
<p v-if="item.desc" class="pb-16px">{{ item.desc }}</p>
<n-space>
<n-button
v-for="button in item.buttons"
:key="button.id"
v-bind="button.props"
:style="`--icon-margin: ${button.props.circle ? 0 : 6}px`"
>
<template v-if="button.icon" #icon>
<svg-icon :icon="button.icon" />
</template>
{{ button.label }}
</n-button>
</n-space>
</n-card>
</n-grid-item>
<n-grid-item class="h-180px">
<n-card title="加载中" class="h-full">
<p class="pb-16px">按钮有加载状态</p>
<n-space>
<n-button :loading="loading" type="primary" @click="startLoading">开始加载</n-button>
<n-button @click="endLoading">取消加载</n-button>
</n-space>
</n-card>
</n-grid-item>
</n-grid>
</n-card>
</div>
</template>
<script setup lang="ts">
import type { ButtonProps } from 'naive-ui';
import { useLoading } from '@/hooks';
interface ButtonDetail {
id: number;
props: ButtonProps & { href?: string; target?: string };
label?: string;
icon?: string;
}
interface ButtonExample {
id: number;
label: string;
buttons: ButtonDetail[];
desc?: string;
}
const { loading, startLoading, endLoading } = useLoading();
const buttonExample: ButtonExample[] = [
{
id: 0,
label: '基础',
buttons: [
{
id: 0,
props: {},
label: 'Default'
},
{
id: 1,
props: { type: 'tertiary' },
label: 'Tertiary'
},
{
id: 2,
props: { type: 'primary' },
label: 'Primary'
},
{
id: 3,
props: { type: 'info' },
label: 'Info'
},
{
id: 4,
props: { type: 'success' },
label: 'Success'
},
{
id: 5,
props: { type: 'warning' },
label: 'Warning'
},
{
id: 6,
props: { type: 'error' },
label: 'Error'
}
],
desc: '按钮的 type 分别为 default、primary、info、success、warning 和 error。'
},
{
id: 1,
label: '次要按钮',
buttons: [
{
id: 0,
props: { strong: true, secondary: true },
label: 'Default'
},
{
id: 1,
props: { strong: true, secondary: true, type: 'tertiary' },
label: 'Tertiary'
},
{
id: 2,
props: { strong: true, secondary: true, type: 'primary' },
label: 'Primary'
},
{
id: 3,
props: { strong: true, secondary: true, type: 'info' },
label: 'Info'
},
{
id: 4,
props: { strong: true, secondary: true, type: 'success' },
label: 'Success'
},
{
id: 5,
props: { strong: true, secondary: true, type: 'warning' },
label: 'Warning'
},
{
id: 6,
props: { strong: true, secondary: true, type: 'error' },
label: 'Error'
},
{
id: 7,
props: { strong: true, secondary: true, round: true },
label: 'Default'
},
{
id: 8,
props: { strong: true, secondary: true, round: true, type: 'tertiary' },
label: 'Tertiary'
},
{
id: 9,
props: { strong: true, secondary: true, round: true, type: 'primary' },
label: 'Primary'
},
{
id: 10,
props: { strong: true, secondary: true, round: true, type: 'info' },
label: 'Info'
},
{
id: 11,
props: { strong: true, secondary: true, round: true, type: 'success' },
label: 'Success'
},
{
id: 12,
props: { strong: true, secondary: true, round: true, type: 'warning' },
label: 'Warning'
},
{
id: 13,
props: { strong: true, secondary: true, round: true, type: 'error' },
label: 'Error'
}
]
},
{
id: 2,
label: '次次要按钮',
buttons: [
{
id: 0,
props: { tertiary: true },
label: 'Default'
},
{
id: 1,
props: { tertiary: true, type: 'primary' },
label: 'Primary'
},
{
id: 2,
props: { tertiary: true, type: 'info' },
label: 'Info'
},
{
id: 3,
props: { tertiary: true, type: 'success' },
label: 'Success'
},
{
id: 4,
props: { tertiary: true, type: 'warning' },
label: 'Warning'
},
{
id: 5,
props: { tertiary: true, type: 'error' },
label: 'Error'
},
{
id: 6,
props: { tertiary: true, round: true },
label: 'Default'
},
{
id: 7,
props: { tertiary: true, round: true, type: 'primary' },
label: 'Primary'
},
{
id: 8,
props: { tertiary: true, round: true, type: 'info' },
label: 'Info'
},
{
id: 9,
props: { tertiary: true, round: true, type: 'success' },
label: 'Success'
},
{
id: 10,
props: { tertiary: true, round: true, type: 'warning' },
label: 'Warning'
},
{
id: 11,
props: { tertiary: true, round: true, type: 'error' },
label: 'Error'
}
]
},
{
id: 3,
label: '次次次要按钮',
buttons: [
{
id: 0,
props: { quaternary: true },
label: 'Default'
},
{
id: 1,
props: { quaternary: true, type: 'primary' },
label: 'Primary'
},
{
id: 2,
props: { quaternary: true, type: 'info' },
label: 'Info'
},
{
id: 3,
props: { quaternary: true, type: 'success' },
label: 'Success'
},
{
id: 4,
props: { quaternary: true, type: 'warning' },
label: 'Warning'
},
{
id: 5,
props: { quaternary: true, type: 'error' },
label: 'Error'
},
{
id: 6,
props: { quaternary: true, round: true },
label: 'Default'
},
{
id: 7,
props: { quaternary: true, round: true, type: 'primary' },
label: 'Primary'
},
{
id: 8,
props: { quaternary: true, round: true, type: 'info' },
label: 'Info'
},
{
id: 9,
props: { quaternary: true, round: true, type: 'success' },
label: 'Success'
},
{
id: 10,
props: { quaternary: true, round: true, type: 'warning' },
label: 'Warning'
},
{
id: 11,
props: { quaternary: true, round: true, type: 'error' },
label: 'Error'
}
]
},
{
id: 4,
label: '虚线按钮',
buttons: [
{
id: 0,
props: { dashed: true },
label: 'Default'
},
{
id: 1,
props: { dashed: true, type: 'tertiary' },
label: 'Tertiary'
},
{
id: 2,
props: { dashed: true, type: 'primary' },
label: 'Primary'
},
{
id: 3,
props: { dashed: true, type: 'info' },
label: 'Info'
},
{
id: 4,
props: { dashed: true, type: 'success' },
label: 'Success'
},
{
id: 5,
props: { dashed: true, type: 'warning' },
label: 'Warning'
},
{
id: 6,
props: { dashed: true, type: 'error' },
label: 'Error'
}
]
},
{
id: 5,
label: '尺寸',
buttons: [
{
id: 0,
props: { size: 'tiny', strong: true },
label: '小小'
},
{
id: 1,
props: { size: 'small', strong: true },
label: '小'
},
{
id: 2,
props: { size: 'medium', strong: true },
label: '不小'
},
{
id: 3,
props: { size: 'large', strong: true },
label: '不不小'
}
]
},
{
id: 6,
label: '文本按钮',
buttons: [
{
id: 0,
props: { text: true },
label: '那车头依然吐着烟',
icon: 'mdi:train'
}
]
},
{
id: 7,
label: '自定义标签按钮',
buttons: [
{
id: 0,
props: {
text: true,
tag: 'a',
href: 'https://github.com/honghuangdc/soybean-admin',
target: '_blank',
type: 'primary'
},
label: 'soybean-admin'
}
],
desc: '你可以把按钮渲染成不同的标签,比如 a标签 。'
},
{
id: 8,
label: '按钮禁用',
buttons: [
{
id: 0,
props: {
disabled: true
},
label: '不许点'
}
],
desc: '按钮可以被禁用'
},
{
id: 9,
label: '图标按钮',
buttons: [
{
id: 0,
props: {
secondary: true,
strong: true
},
label: '+100元',
icon: 'mdi:cash-100'
},
{
id: 0,
props: {
iconPlacement: 'right',
secondary: true,
strong: true
},
label: '+100元',
icon: 'mdi:cash-100'
}
],
desc: '在按钮上使用图标。'
},
{
id: 10,
label: '不同形状按钮',
buttons: [
{
id: 0,
props: {
circle: true
},
icon: 'mdi:cash-100'
},
{
id: 1,
props: {
round: true
},
label: '圆角'
},
{
id: 2,
props: {},
label: '方'
}
],
desc: '按钮拥有不同的形状。'
},
{
id: 11,
label: '透明背景按钮',
buttons: [
{
id: 0,
props: { ghost: true },
label: 'Default'
},
{
id: 1,
props: { ghost: true, type: 'tertiary' },
label: 'Tertiary'
},
{
id: 2,
props: { ghost: true, type: 'primary' },
label: 'Primary'
},
{
id: 3,
props: { ghost: true, type: 'info' },
label: 'Info'
},
{
id: 4,
props: { ghost: true, type: 'success' },
label: 'Success'
},
{
id: 5,
props: { ghost: true, type: 'warning' },
label: 'Warning'
},
{
id: 6,
props: { ghost: true, type: 'error' },
label: 'Error'
}
],
desc: 'Ghost 按钮有透明的背景。'
},
{
id: 12,
label: '自定义颜色',
buttons: [
{
id: 0,
props: {
color: '#8a2be2'
},
label: '#8a2be2',
icon: 'ic:baseline-color-lens'
},
{
id: 1,
props: {
color: '#ff69b4'
},
label: '#ff69b4',
icon: 'ic:baseline-color-lens'
},
{
id: 2,
props: {
color: '#8a2be2',
ghost: true
},
label: '#8a2be2',
icon: 'ic:baseline-color-lens'
},
{
id: 3,
props: {
color: '#ff69b4',
ghost: true
},
label: '#ff69b4',
icon: 'ic:baseline-color-lens'
},
{
id: 4,
props: {
color: '#8a2be2',
text: true
},
label: '#8a2be2',
icon: 'ic:baseline-color-lens'
},
{
id: 5,
props: {
color: '#ff69b4',
text: true
},
label: '#ff69b4',
icon: 'ic:baseline-color-lens'
}
],
desc: '这两个颜色看起来像毒蘑菇。'
}
];
</script>
<style scoped></style>

View File

@ -1,42 +0,0 @@
<template>
<div>
<n-card title="卡片" class="h-full shadow-sm rounded-16px">
<n-space :vertical="true">
<n-card title="基本用法">
<p class="pb-16px">基础卡片</p>
<n-card title="卡片">卡片内容</n-card>
</n-card>
<n-card title="尺寸">
<p class="pb-16px">卡片有 smallmediumlargehuge 尺寸</p>
<n-space vertical>
<n-card title="小卡片" size="small">卡片内容</n-card>
<n-card title="中卡片" size="medium">卡片内容</n-card>
<n-card title="大卡片" size="large">卡片内容</n-card>
<n-card title="超大卡片" size="huge">卡片内容</n-card>
</n-space>
</n-card>
<n-card title="文本按钮">
<p class="pb-16px">
content footer 可以被 hard soft 分段action 可以被分段分段分割线会在区域的上方出现
</p>
<n-card
title="卡片分段示例"
:segmented="{
content: true,
footer: 'soft'
}"
>
<template #header-extra>#header-extra</template>
卡片内容
<template #footer>#footer</template>
<template #action>#action</template>
</n-card>
</n-card>
</n-space>
</n-card>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -1,107 +0,0 @@
<template>
<div class="h-full overflow-hidden">
<n-card title="表格" class="h-full shadow-sm rounded-16px">
<n-space :vertical="true">
<n-space>
<n-button @click="getDataSource">有数据</n-button>
<n-button @click="getEmptyDataSource">空数据</n-button>
</n-space>
<loading-empty-wrapper class="h-480px" :loading="loading" :empty="empty">
<n-data-table :columns="columns" :data="dataSource" :flex-height="true" class="h-480px" />
</loading-empty-wrapper>
</n-space>
</n-card>
</div>
</template>
<script setup lang="tsx">
import { onMounted, ref } from 'vue';
import { NSpace, NButton, NPopconfirm } from 'naive-ui';
import type { DataTableColumn } from 'naive-ui';
import { useLoadingEmpty } from '@/hooks';
import { getRandomInteger } from '@/utils';
interface DataSource {
name: string;
age: number;
address: string;
}
const { loading, startLoading, endLoading, empty, setEmpty } = useLoadingEmpty();
const columns: DataTableColumn[] = [
{
title: 'Name',
key: 'name',
align: 'center'
},
{
title: 'Age',
key: 'age',
align: 'center'
},
{
title: 'Address',
key: 'address',
align: 'center'
},
{
key: 'action',
title: 'Action',
align: 'center',
render: () => {
return (
<NSpace justify={'center'}>
<NButton size={'small'} onClick={() => {}}>
编辑
</NButton>
<NPopconfirm onPositiveClick={() => {}}>
{{
default: () => '确认删除',
trigger: () => <NButton size={'small'}>删除</NButton>
}}
</NPopconfirm>
</NSpace>
);
}
}
];
const dataSource = ref<DataSource[]>([]);
function createDataSource(): DataSource[] {
return Array(100)
.fill(1)
.map((_item, index) => {
return {
name: `Name${index}`,
age: getRandomInteger(30, 20),
address: '中国'
};
});
}
function getDataSource() {
startLoading();
setTimeout(() => {
dataSource.value = createDataSource();
endLoading();
setEmpty(!dataSource.value.length);
}, 1000);
}
function getEmptyDataSource() {
startLoading();
setTimeout(() => {
dataSource.value = [];
endLoading();
setEmpty(!dataSource.value.length);
}, 1000);
}
onMounted(() => {
getDataSource();
});
</script>
<style scoped></style>

View File

@ -1,136 +0,0 @@
<template>
<n-grid :x-gap="16" :y-gap="16" :item-responsive="true">
<n-grid-item span="0:24 640:24 1024:8">
<n-card title="时间线" :bordered="false" class="h-full rounded-16px shadow-sm">
<n-timeline>
<n-timeline-item v-for="item in timelines" :key="item.type" v-bind="item" />
</n-timeline>
</n-card>
</n-grid-item>
<n-grid-item span="0:24 640:24 1024:16">
<n-card title="表格" :bordered="false" class="h-full rounded-16px shadow-sm">
<n-data-table size="small" :columns="columns" :data="tableData" />
</n-card>
</n-grid-item>
</n-grid>
</template>
<script setup lang="ts">
import { h } from 'vue';
import { NTag } from 'naive-ui';
defineOptions({ name: 'DashboardAnalysisBottomPart' });
interface TimelineData {
type: 'default' | 'info' | 'success' | 'warning' | 'error';
title: string;
content: string;
time: string;
}
interface TableData {
key: number;
name: string;
age: number;
address: string;
tags: string[];
}
const timelines: TimelineData[] = [
{ type: 'default', title: '啊', content: '', time: '2021-10-10 20:46' },
{ type: 'success', title: '成功', content: '哪里成功', time: '2021-10-10 20:46' },
{ type: 'error', title: '错误', content: '哪里错误', time: '2021-10-10 20:46' },
{ type: 'warning', title: '警告', content: '哪里警告', time: '2021-10-10 20:46' },
{ type: 'info', title: '信息', content: '是的', time: '2021-10-10 20:46' }
];
const columns = [
{
title: 'Name',
key: 'name'
},
{
title: 'Age',
key: 'age'
},
{
title: 'Address',
key: 'address'
},
{
title: 'Tags',
key: 'tags',
render(row: TableData) {
const tags = row.tags.map(tagKey => {
return h(
NTag,
{
style: {
marginRight: '6px'
},
type: 'info'
},
{
default: () => tagKey
}
);
});
return tags;
}
}
];
const tableData: TableData[] = [
{
key: 0,
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer']
},
{
key: 1,
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['wow']
},
{
key: 2,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher']
},
{
key: 3,
name: 'Soybean',
age: 25,
address: 'China Shenzhen',
tags: ['handsome', 'programmer']
},
{
key: 4,
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer']
},
{
key: 5,
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['wow']
},
{
key: 6,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher']
}
];
</script>
<style scoped></style>

View File

@ -1,25 +0,0 @@
<template>
<div class="p-16px rounded-16px text-white" :style="{ backgroundImage: gradientStyle }">
<slot></slot>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
/** 渐变开始的颜色 */
startColor?: string;
/** 渐变结束的颜色 */
endColor?: string;
}
const props = withDefaults(defineProps<Props>(), {
startColor: '#56cdf3',
endColor: '#719de3'
});
const gradientStyle = computed(() => `linear-gradient(to bottom right, ${props.startColor}, ${props.endColor})`);
</script>
<style scoped></style>

View File

@ -1,3 +0,0 @@
import GradientBg from './gradient-bg.vue';
export { GradientBg };

View File

@ -1,70 +0,0 @@
<template>
<n-grid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<n-grid-item v-for="item in cardData" :key="item.id">
<gradient-bg class="h-100px" :start-color="item.colors[0]" :end-color="item.colors[1]">
<h3 class="text-16px">{{ item.title }}</h3>
<div class="flex justify-between pt-12px">
<svg-icon :icon="item.icon" class="text-32px" />
<count-to
:prefix="item.unit"
:start-value="1"
:end-value="item.value"
class="text-30px text-white dark:text-dark"
/>
</div>
</gradient-bg>
</n-grid-item>
</n-grid>
</template>
<script setup lang="ts">
import { GradientBg } from './components';
defineOptions({ name: 'DashboardAnalysisDataCard' });
interface CardData {
id: string;
title: string;
value: number;
unit: string;
colors: [string, string];
icon: string;
}
const cardData: CardData[] = [
{
id: 'visit',
title: '访问量',
value: 1000000,
unit: '',
colors: ['#ec4786', '#b955a4'],
icon: 'ant-design:bar-chart-outlined'
},
{
id: 'amount',
title: '成交额',
value: 234567.89,
unit: '$',
colors: ['#865ec0', '#5144b4'],
icon: 'ant-design:money-collect-outlined'
},
{
id: 'download',
title: '下载数',
value: 666666,
unit: '',
colors: ['#56cdf3', '#719de3'],
icon: 'carbon:document-download'
},
{
id: 'trade',
title: '成交数',
value: 999999,
unit: '',
colors: ['#fcbc25', '#f68057'],
icon: 'ant-design:trademark-circle-outlined'
}
];
</script>
<style scoped></style>

View File

@ -1,5 +0,0 @@
import TopChart from './top-chart/index.vue';
import DataCard from './data-card/index.vue';
import BottomPart from './bottom-part/index.vue';
export { TopChart, DataCard, BottomPart };

View File

@ -1,184 +0,0 @@
<template>
<n-grid :x-gap="16" :y-gap="16" :item-responsive="true">
<n-grid-item span="0:24 640:24 1024:6">
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div class="w-full h-360px py-12px">
<h3 class="text-16px font-bold">Dashboard</h3>
<p class="text-#aaa">Overview Of Lasted Month</p>
<h3 class="pt-32px text-24px font-bold">
<count-to prefix="$" :start-value="0" :end-value="7754" />
</h3>
<p class="text-#aaa">Current Month Earnings</p>
<h3 class="pt-32px text-24px font-bold">
<count-to :start-value="0" :end-value="1234" />
</h3>
<p class="text-#aaa">Current Month Sales</p>
<n-button class="mt-24px whitespace-pre-wrap" type="primary">Last Month Summary</n-button>
</div>
</n-card>
</n-grid-item>
<n-grid-item span="0:24 640:24 1024:10">
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="lineRef" class="w-full h-360px"></div>
</n-card>
</n-grid-item>
<n-grid-item span="0:24 640:24 1024:8">
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="pieRef" class="w-full h-360px"></div>
</n-card>
</n-grid-item>
</n-grid>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import type { Ref } from 'vue';
import { type ECOption, useEcharts } from '@/composables';
defineOptions({ name: 'DashboardAnalysisTopCard' });
const lineOptions = ref<ECOption>({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['下载量', '注册数']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00']
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
color: '#8e9dff',
name: '下载量',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#8e9dff'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [4623, 6145, 6268, 6411, 1890, 4251, 2978, 3880, 3606, 4311]
},
{
color: '#26deca',
name: '注册数',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#26deca'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [2208, 2016, 2916, 4512, 8281, 2008, 1963, 2367, 2956, 678]
}
]
}) as Ref<ECOption>;
const { domRef: lineRef } = useEcharts(lineOptions);
const pieOptions = ref<ECOption>({
tooltip: {
trigger: 'item'
},
legend: {
bottom: '1%',
left: 'center',
itemStyle: {
borderWidth: 0
}
},
series: [
{
color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
name: '时间安排',
type: 'pie',
radius: ['45%', '75%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 1
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '12'
}
},
labelLine: {
show: false
},
data: [
{ value: 20, name: '学习' },
{ value: 10, name: '娱乐' },
{ value: 30, name: '工作' },
{ value: 40, name: '休息' }
]
}
]
}) as Ref<ECOption>;
const { domRef: pieRef } = useEcharts(pieOptions);
</script>
<style scoped></style>

View File

@ -1,13 +1,7 @@
<template>
<n-space :vertical="true" :size="16">
<top-chart />
<data-card />
<bottom-part />
</n-space>
<div>analysis</div>
</template>
<script lang="ts" setup>
import { BottomPart, DataCard, TopChart } from './components';
</script>
<script lang="ts" setup></script>
<style scoped></style>

View File

@ -1,4 +0,0 @@
import WorkbenchHeader from './workbench-header/index.vue';
import WorkbenchMain from './workbench-main/index.vue';
export { WorkbenchHeader, WorkbenchMain };

View File

@ -1,50 +0,0 @@
<template>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div class="flex-y-center justify-between">
<div class="flex-y-center">
<icon-local-avatar class="text-70px" />
<div class="pl-12px">
<h3 class="text-18px font-semibold">早安{{ auth.userInfo.userName }}, 今天又是充满活力的一天</h3>
<p class="leading-30px text-#999">今日多云转晴20 - 25</p>
</div>
</div>
<n-space :size="24" :wrap="false">
<n-statistic v-for="item in statisticData" :key="item.id" class="whitespace-nowrap" v-bind="item"></n-statistic>
</n-space>
</div>
</n-card>
</template>
<script setup lang="ts">
import { useAuthStore } from '@/store';
defineOptions({ name: 'DashboardWorkbenchHeader' });
const auth = useAuthStore();
interface StatisticData {
id: number;
label: string;
value: string;
}
const statisticData: StatisticData[] = [
{
id: 0,
label: '项目数',
value: '25'
},
{
id: 1,
label: '待办',
value: '4/16'
},
{
id: 2,
label: '消息',
value: '12'
}
];
</script>
<style scoped></style>

View File

@ -1,4 +0,0 @@
import TechnologyCard from './technology-card.vue';
import ShortcutsCard from './shortcuts-card.vue';
export { TechnologyCard, ShortcutsCard };

View File

@ -1,25 +0,0 @@
<template>
<div
class="flex-col-center h-120px p-12px border-1px border-#efeff5 dark:border-#ffffff17 rounded-4px hover:shadow-sm cursor-pointer"
>
<svg-icon :icon="icon" :style="{ color: iconColor }" class="text-30px" />
<p class="py-8px text-16px">{{ label }}</p>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'DashboardWorkbenchMainShortcutsCard' });
interface Props {
/** 快捷操作名称 */
label: string;
/** 图标 */
icon: string;
/** 图标颜色 */
iconColor: string;
}
defineProps<Props>();
</script>
<style scoped></style>

View File

@ -1,42 +0,0 @@
<template>
<div
class="h-120px p-4px border-1px border-#efeff5 dark:border-#ffffff17 rounded-4px hover:shadow-sm cursor-pointer"
@click="handleOpenSite"
>
<header class="flex-y-center">
<svg-icon :icon="icon" :style="{ color: iconColor }" class="text-30px" />
<h3 class="pl-12px text-18px font-semibold">{{ name }}</h3>
</header>
<p class="py-8px h-56px text-#999">{{ description }}</p>
<div class="flex justify-end">
<span>{{ author }}</span>
</div>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'DashboardWorkbenchMainTechnologyCard' });
interface Props {
/** 技术名称 */
name: string;
/** 技术描述 */
description: string;
/** 技术作者 */
author: string;
/** 技术官网 */
site: string;
/** 技术图标 */
icon: string;
/** 图标颜色 */
iconColor?: string;
}
const props = defineProps<Props>();
function handleOpenSite() {
window.open(props.site, '_blank');
}
</script>
<style scoped></style>

View File

@ -1,146 +0,0 @@
<template>
<n-grid :item-responsive="true" :x-gap="16" :y-gap="16">
<n-grid-item span="0:24 640:24 1024:16">
<n-space :vertical="true" :size="16">
<n-card title="项目主要技术栈" :bordered="false" size="small" class="shadow-sm rounded-16px">
<template #header-extra>
<a class="text-primary" href="javascript:;">更多技术栈</a>
</template>
<n-grid :item-responsive="true" responsive="screen" cols="m:2 l:3" :x-gap="8" :y-gap="8">
<n-grid-item v-for="item in technology" :key="item.id">
<technology-card v-bind="item" />
</n-grid-item>
</n-grid>
</n-card>
<n-card title="动态" :bordered="false" size="small" class="shadow-sm rounded-16px">
<template #header-extra>
<a class="text-primary" href="javascript:;">更多动态</a>
</template>
<n-list>
<n-list-item v-for="item in activity" :key="item.id">
<template #prefix>
<icon-local-avatar class="text-48px" />
</template>
<n-thing :title="item.content" :description="item.time" />
</n-list-item>
</n-list>
</n-card>
</n-space>
</n-grid-item>
<n-grid-item span="0:24 640:24 1024:8">
<n-space :vertical="true" :size="16">
<n-card title="快捷操作" :bordered="false" size="small" class="shadow-sm rounded-16px">
<n-grid :item-responsive="true" responsive="screen" cols="m:2 l:3" :x-gap="8" :y-gap="8">
<n-grid-item v-for="item in shortcuts" :key="item.id">
<shortcuts-card v-bind="item" />
</n-grid-item>
</n-grid>
</n-card>
<n-card title="创意" :bordered="false" size="small" class="shadow-sm rounded-16px">
<div class="flex-center h-380px">
<icon-local-banner class="text-400px sm:text-320px text-primary" />
</div>
</n-card>
</n-space>
</n-grid-item>
</n-grid>
</template>
<script setup lang="ts">
import { ShortcutsCard, TechnologyCard } from './components';
defineOptions({ name: 'DashboardWorkbenchMain' });
interface Technology {
id: number;
name: string;
description: string;
author: string;
site: string;
icon: string;
iconColor?: string;
}
const technology: Technology[] = [
{
id: 0,
name: 'Vue',
description: '一套用于构建用户界面的渐进式框架',
author: '尤雨溪 - Evan You',
site: 'https://v3.cn.vuejs.org/',
icon: 'logos:vue'
},
{
id: 1,
name: 'TypeScript',
description: 'JavaScript类型的超集它可以编译成纯JavaScript',
author: '微软 - Microsoft',
site: 'https://www.typescriptlang.org/',
icon: 'logos:typescript-icon'
},
{
id: 2,
name: 'Vite',
description: '下一代前端开发与构建工具',
author: '尤雨溪 - Evan You',
site: 'https://vitejs.cn/',
icon: 'logos:vitejs'
},
{
id: 3,
name: 'NaiveUI',
description: '一个 Vue 3 组件库',
author: '图森未来 - TuSimple',
site: 'https://www.naiveui.com/zh-CN/os-theme',
icon: 'logos:naiveui'
},
{
id: 4,
name: 'UnoCSS',
description: '下一代实用优先的CSS框架',
author: 'Anthony Fu',
site: 'https://uno.antfu.me/?s=',
icon: 'logos:unocss'
},
{
id: 5,
name: 'Pinia',
description: 'vue状态管理框架支持vue2、vue3',
author: 'Posva',
site: 'https://pinia.esm.dev/',
icon: 'noto:pineapple'
}
];
interface Activity {
id: number;
content: string;
time: string;
}
const activity: Activity[] = [
{ id: 4, content: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!', time: '2021-11-07 22:45:32' },
{ id: 3, content: 'Soybean 正在忙于为soybean-admin写项目说明文档', time: '2021-11-03 20:33:31' },
{ id: 2, content: 'Soybean 准备为soybean-admin 1.0的发布做充分的准备工作!', time: '2021-10-31 22:43:12' },
{ id: 1, content: '@yanbowe 向soybean-admin提交了一个bug多标签栏不会自适应。', time: '2021-10-27 10:24:54' },
{ id: 0, content: 'Soybean 在2021年5月28日创建了开源项目soybean-admin', time: '2021-05-28 22:22:22' }
];
interface Shortcuts {
id: number;
label: string;
icon: string;
iconColor: string;
}
const shortcuts: Shortcuts[] = [
{ id: 0, label: '主控台', icon: 'mdi:desktop-mac-dashboard', iconColor: '#409eff' },
{ id: 1, label: '系统管理', icon: 'ic:outline-settings', iconColor: '#7238d1' },
{ id: 2, label: '权限管理', icon: 'mdi:family-tree', iconColor: '#f56c6c' },
{ id: 3, label: '组件', icon: 'fluent:app-store-24-filled', iconColor: '#19a2f1' },
{ id: 4, label: '表格', icon: 'mdi:table-large', iconColor: '#fab251' },
{ id: 5, label: '图表', icon: 'mdi:chart-areaspline', iconColor: '#8aca6b' }
];
</script>
<style scoped></style>

View File

@ -1,12 +1,7 @@
<template>
<n-space :vertical="true" :size="16">
<workbench-header />
<workbench-main />
</n-space>
<div>workbench</div>
</template>
<script lang="ts" setup>
import { WorkbenchHeader, WorkbenchMain } from './components';
</script>
<script lang="ts" setup></script>
<style scoped></style>

View File

@ -1,13 +0,0 @@
<template>
<div class="h-full">
<iframe class="wh-full" :src="src"></iframe>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const src = ref('https://www.naiveui.com/zh-CN/os-theme/docs/introduction');
</script>
<style scoped></style>

View File

@ -1,7 +0,0 @@
<template>
<div></div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -1,13 +0,0 @@
<template>
<div class="h-full">
<iframe class="wh-full" :src="src"></iframe>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const src = ref('https://docs.soybean.pro/');
</script>
<style scoped></style>

View File

@ -1,13 +0,0 @@
<template>
<div class="h-full">
<iframe class="wh-full" :src="src"></iframe>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const src = ref('https://cn.vitejs.dev/');
</script>
<style scoped></style>

View File

@ -1,13 +0,0 @@
<template>
<div class="h-full">
<iframe class="wh-full" :src="src"></iframe>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const src = ref('https://v3.cn.vuejs.org/');
</script>
<style scoped></style>

View File

@ -1,7 +0,0 @@
<template>
<exception-base type="403" />
</template>
<script lang="ts" setup></script>
<style scoped></style>

View File

@ -1,7 +0,0 @@
<template>
<exception-base type="404" />
</template>
<script lang="ts" setup></script>
<style scoped></style>

View File

@ -1,7 +0,0 @@
<template>
<exception-base type="500" />
</template>
<script lang="ts" setup></script>
<style scoped></style>

View File

@ -1,27 +0,0 @@
<template>
<n-space :vertical="true" :size="16">
<n-card title="Tab Detail" :bordered="false" size="small" class="shadow-sm rounded-16px">
<n-space :vertical="true" :size="12">
<div>当前路由的描述数据(meta)</div>
<div>{{ route.meta }}</div>
<n-button @click="handleToTab">返回Tab</n-button>
</n-space>
</n-card>
</n-space>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { routeName } from '@/router';
import { useRouterPush } from '@/composables';
const { routerPush } = useRouterPush();
const route = useRoute();
function handleToTab() {
routerPush({ name: routeName('function_tab') });
}
</script>
<style scoped></style>

View File

@ -1,28 +0,0 @@
<template>
<n-space :vertical="true" :size="16">
<n-card title="Tab Detail" :bordered="false" size="small" class="shadow-sm rounded-16px">
<n-space :vertical="true" :size="12">
<div>当前路由的描述数据(meta)</div>
<div>{{ route.meta }}</div>
<div>当前路由的查询数据(query)</div>
<div>{{ route.query }}</div>
<n-button @click="handleToTab">返回Tab</n-button>
</n-space>
</n-card>
</n-space>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { routeName } from '@/router';
import { useRouterPush } from '@/composables';
const route = useRoute();
const { routerPush } = useRouterPush();
function handleToTab() {
routerPush({ name: routeName('function_tab') });
}
</script>
<style scoped></style>

View File

@ -1,44 +0,0 @@
<template>
<n-space :vertical="true" :size="16">
<n-card title="Tab Home" :bordered="false" size="small" class="shadow-sm rounded-16px">
<n-space :vertical="true" :size="12">
<n-button @click="handleToTabDetail">跳转Tab Detail</n-button>
<n-button @click="handleToTabMultiDetail(1)">跳转Tab Multi Detail 1</n-button>
<n-button @click="handleToTabMultiDetail(2)">跳转Tab Multi Detail 2</n-button>
<n-input-group>
<n-input v-model:value="title" />
<n-button type="primary" @click="handleSetTitle">设置当前Tab页标题</n-button>
</n-input-group>
</n-space>
</n-card>
</n-space>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { routeName } from '@/router';
import { useTabStore } from '@/store';
import { useRouterPush } from '@/composables';
const { routerPush } = useRouterPush();
const tabStore = useTabStore();
const title = ref('');
function handleToTabDetail() {
routerPush({ name: routeName('function_tab-detail'), query: { name: 'abc' }, hash: '#DEMO_HASH' });
}
function handleToTabMultiDetail(num: number) {
routerPush({ name: routeName('function_tab-multi-detail'), query: { name: 'abc', num }, hash: '#DEMO_HASH' });
}
function handleSetTitle() {
if (!title.value) {
window.$message?.warning('请输入要设置的标题名称');
} else {
tabStore.setActiveTabTitle(title.value);
}
}
</script>
<style scoped></style>

View File

@ -10,39 +10,6 @@ export const views: Record<
'constant-page': () => import('./_builtin/constant-page/index.vue'),
login: () => import('./_builtin/login/index.vue'),
'not-found': () => import('./_builtin/not-found/index.vue'),
about: () => import('./about/index.vue'),
'auth-demo_permission': () => import('./auth-demo/permission/index.vue'),
'auth-demo_super': () => import('./auth-demo/super/index.vue'),
component_button: () => import('./component/button/index.vue'),
component_card: () => import('./component/card/index.vue'),
component_table: () => import('./component/table/index.vue'),
dashboard_analysis: () => import('./dashboard/analysis/index.vue'),
dashboard_workbench: () => import('./dashboard/workbench/index.vue'),
document_naive: () => import('./document/naive/index.vue'),
'document_project-link': () => import('./document/project-link/index.vue'),
document_project: () => import('./document/project/index.vue'),
document_vite: () => import('./document/vite/index.vue'),
document_vue: () => import('./document/vue/index.vue'),
exception_403: () => import('./exception/403/index.vue'),
exception_404: () => import('./exception/404/index.vue'),
exception_500: () => import('./exception/500/index.vue'),
'function_tab-detail': () => import('./function/tab-detail/index.vue'),
'function_tab-multi-detail': () => import('./function/tab-multi-detail/index.vue'),
function_tab: () => import('./function/tab/index.vue'),
management_auth: () => import('./management/auth/index.vue'),
management_role: () => import('./management/role/index.vue'),
management_route: () => import('./management/route/index.vue'),
management_user: () => import('./management/user/index.vue'),
'multi-menu_first_second-new_third': () => import('./multi-menu/first/second-new/third/index.vue'),
'multi-menu_first_second': () => import('./multi-menu/first/second/index.vue'),
plugin_charts_antv: () => import('./plugin/charts/antv/index.vue'),
plugin_charts_echarts: () => import('./plugin/charts/echarts/index.vue'),
plugin_copy: () => import('./plugin/copy/index.vue'),
plugin_editor_markdown: () => import('./plugin/editor/markdown/index.vue'),
plugin_editor_quill: () => import('./plugin/editor/quill/index.vue'),
plugin_icon: () => import('./plugin/icon/index.vue'),
plugin_map: () => import('./plugin/map/index.vue'),
plugin_print: () => import('./plugin/print/index.vue'),
plugin_swiper: () => import('./plugin/swiper/index.vue'),
plugin_video: () => import('./plugin/video/index.vue')
dashboard_workbench: () => import('./dashboard/workbench/index.vue')
};

View File

@ -1,7 +0,0 @@
<template>
<div>权限管理</div>
</template>
<script setup lang="tsx"></script>
<style scoped></style>

View File

@ -1,7 +0,0 @@
<template>
<div>角色管理</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -1,7 +0,0 @@
<template>
<div>路由管理</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -1,69 +0,0 @@
<template>
<n-popover placement="bottom" trigger="click">
<template #trigger>
<n-button size="small" type="primary">
<icon-ant-design-setting-outlined class="mr-4px text-16px" />
表格列设置
</n-button>
</template>
<div class="w-180px">
<vue-draggable v-model="list" item-key="key">
<template #item="{ element }">
<div v-if="element.key" class="flex-y-center h-36px px-12px hover:bg-primary_active">
<icon-mdi-drag class="mr-8px text-20px cursor-move" />
<n-checkbox v-model:checked="element.checked">
{{ element.title }}
</n-checkbox>
</div>
</template>
</vue-draggable>
</div>
</n-popover>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import type { DataTableColumn } from 'naive-ui';
import VueDraggable from 'vuedraggable';
type Column = DataTableColumn<UserManagement.User>;
interface Props {
columns: Column[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'update:columns', columns: Column[]): void;
}
const emit = defineEmits<Emits>();
type List = Column & { checked?: boolean };
const list = ref(initList());
function initList(): List[] {
return props.columns.map(item => ({ ...item, checked: true }));
}
watch(
list,
newValue => {
const newColumns = newValue.filter(item => item.checked);
const columns: Column[] = newColumns.map(item => {
const column = { ...item };
delete column.checked;
return column;
}) as Column[];
emit('update:columns', columns);
},
{ deep: true }
);
</script>
<style scoped></style>

View File

@ -1,150 +0,0 @@
<template>
<n-modal v-model:show="modalVisible" preset="card" :title="title" class="w-700px">
<n-form ref="formRef" label-placement="left" :label-width="80" :model="formModel" :rules="rules">
<n-grid :cols="24" :x-gap="18">
<n-form-item-grid-item :span="12" label="用户名" path="userName">
<n-input v-model:value="formModel.userName" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="年龄" path="age">
<n-input-number v-model:value="formModel.age" clearable />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="性别" path="gender">
<n-radio-group v-model:value="formModel.gender">
<n-radio v-for="item in genderOptions" :key="item.value" :value="item.value">{{ item.label }}</n-radio>
</n-radio-group>
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="手机号" path="phone">
<n-input v-model:value="formModel.phone" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="邮箱" path="email">
<n-input v-model:value="formModel.email" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="状态" path="userStatus">
<n-select v-model:value="formModel.userStatus" :options="userStatusOptions" />
</n-form-item-grid-item>
</n-grid>
<n-space class="w-full pt-16px" :size="24" justify="end">
<n-button class="w-72px" @click="closeModal">取消</n-button>
<n-button class="w-72px" type="primary" @click="handleSubmit">确定</n-button>
</n-space>
</n-form>
</n-modal>
</template>
<script setup lang="ts">
import { ref, computed, reactive, watch } from 'vue';
import type { FormInst, FormItemRule } from 'naive-ui';
import { genderOptions, userStatusOptions } from '@/constants';
import { formRules, createRequiredFormRule } from '@/utils';
export interface Props {
/** 弹窗可见性 */
visible: boolean;
/**
* 弹窗类型
* add: 新增
* edit: 编辑
*/
type?: 'add' | 'edit';
/** 编辑的表格行数据 */
editData?: UserManagement.User | null;
}
export type ModalType = NonNullable<Props['type']>;
defineOptions({ name: 'TableActionModal' });
const props = withDefaults(defineProps<Props>(), {
type: 'add',
editData: null
});
interface Emits {
(e: 'update:visible', visible: boolean): void;
}
const emit = defineEmits<Emits>();
const modalVisible = computed({
get() {
return props.visible;
},
set(visible) {
emit('update:visible', visible);
}
});
const closeModal = () => {
modalVisible.value = false;
};
const title = computed(() => {
const titles: Record<ModalType, string> = {
add: '添加用户',
edit: '编辑用户'
};
return titles[props.type];
});
const formRef = ref<HTMLElement & FormInst>();
type FormModel = Pick<UserManagement.User, 'userName' | 'age' | 'gender' | 'phone' | 'email' | 'userStatus'>;
const formModel = reactive<FormModel>(createDefaultFormModel());
const rules: Record<keyof FormModel, FormItemRule | FormItemRule[]> = {
userName: createRequiredFormRule('请输入用户名'),
age: createRequiredFormRule('请输入年龄'),
gender: createRequiredFormRule('请选择性别'),
phone: formRules.phone,
email: formRules.email,
userStatus: createRequiredFormRule('请选择用户状态')
};
function createDefaultFormModel(): FormModel {
return {
userName: '',
age: null,
gender: null,
phone: '',
email: null,
userStatus: null
};
}
function handleUpdateFormModel(model: Partial<FormModel>) {
Object.assign(formModel, model);
}
function handleUpdateFormModelByModalType() {
const handlers: Record<ModalType, () => void> = {
add: () => {
const defaultFormModel = createDefaultFormModel();
handleUpdateFormModel(defaultFormModel);
},
edit: () => {
if (props.editData) {
handleUpdateFormModel(props.editData);
}
}
};
handlers[props.type]();
}
async function handleSubmit() {
await formRef.value?.validate();
window.$message?.success('新增成功!');
closeModal();
}
watch(
() => props.visible,
newValue => {
if (newValue) {
handleUpdateFormModelByModalType();
}
}
);
</script>
<style scoped></style>

View File

@ -1,203 +0,0 @@
<template>
<div class="h-full overflow-hidden">
<n-card title="用户管理" :bordered="false" class="rounded-16px shadow-sm">
<n-space class="pb-12px" justify="space-between">
<n-space>
<n-button type="primary" @click="handleAddTable">
<icon-ic-round-plus class="mr-4px text-20px" />
新增
</n-button>
<n-button type="error">
<icon-ic-round-delete class="mr-4px text-20px" />
删除
</n-button>
<n-button type="success">
<icon-uil:export class="mr-4px text-20px" />
导出Excel
</n-button>
</n-space>
<n-space align="center" :size="18">
<n-button size="small" type="primary" @click="getTableData">
<icon-mdi-refresh class="mr-4px text-16px" :class="{ 'animate-spin': loading }" />
刷新表格
</n-button>
<column-setting v-model:columns="columns" />
</n-space>
</n-space>
<n-data-table :columns="columns" :data="tableData" :loading="loading" :pagination="pagination" />
<table-action-modal v-model:visible="visible" :type="modalType" :edit-data="editData" />
</n-card>
</div>
</template>
<script setup lang="tsx">
import { reactive, ref } from 'vue';
import type { Ref } from 'vue';
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui';
import type { DataTableColumns, PaginationProps } from 'naive-ui';
import { genderLabels, userStatusLabels } from '@/constants';
import { fetchUserList } from '@/service';
import { useBoolean, useLoading } from '@/hooks';
import TableActionModal from './components/table-action-modal.vue';
import type { ModalType } from './components/table-action-modal.vue';
import ColumnSetting from './components/column-setting.vue';
const { loading, startLoading, endLoading } = useLoading(false);
const { bool: visible, setTrue: openModal } = useBoolean();
const tableData = ref<UserManagement.User[]>([]);
function setTableData(data: UserManagement.User[]) {
tableData.value = data;
}
async function getTableData() {
startLoading();
const { data } = await fetchUserList();
if (data) {
setTimeout(() => {
setTableData(data);
endLoading();
}, 1000);
}
}
const columns: Ref<DataTableColumns<UserManagement.User>> = ref([
{
type: 'selection',
align: 'center'
},
{
key: 'index',
title: '序号',
align: 'center'
},
{
key: 'userName',
title: '用户名',
align: 'center'
},
{
key: 'age',
title: '用户年龄',
align: 'center'
},
{
key: 'gender',
title: '性别',
align: 'center',
render: row => {
if (row.gender) {
const tagTypes: Record<UserManagement.GenderKey, NaiveUI.ThemeColor> = {
'0': 'success',
'1': 'warning'
};
return <NTag type={tagTypes[row.gender]}>{genderLabels[row.gender]}</NTag>;
}
return <span></span>;
}
},
{
key: 'phone',
title: '手机号码',
align: 'center'
},
{
key: 'email',
title: '邮箱',
align: 'center'
},
{
key: 'userStatus',
title: '状态',
align: 'center',
render: row => {
if (row.userStatus) {
const tagTypes: Record<UserManagement.UserStatusKey, NaiveUI.ThemeColor> = {
'1': 'success',
'2': 'error',
'3': 'warning',
'4': 'default'
};
return <NTag type={tagTypes[row.userStatus]}>{userStatusLabels[row.userStatus]}</NTag>;
}
return <span></span>;
}
},
{
key: 'actions',
title: '操作',
align: 'center',
render: row => {
return (
<NSpace justify={'center'}>
<NButton size={'small'} onClick={() => handleEditTable(row.id)}>
编辑
</NButton>
<NPopconfirm onPositiveClick={() => handleDeleteTable(row.id)}>
{{
default: () => '确认删除',
trigger: () => <NButton size={'small'}>删除</NButton>
}}
</NPopconfirm>
</NSpace>
);
}
}
]) as Ref<DataTableColumns<UserManagement.User>>;
const modalType = ref<ModalType>('add');
function setModalType(type: ModalType) {
modalType.value = type;
}
const editData = ref<UserManagement.User | null>(null);
function setEditData(data: UserManagement.User | null) {
editData.value = data;
}
function handleAddTable() {
openModal();
setModalType('add');
}
function handleEditTable(rowId: string) {
const findItem = tableData.value.find(item => item.id === rowId);
if (findItem) {
setEditData(findItem);
}
setModalType('edit');
openModal();
}
function handleDeleteTable(rowId: string) {
window.$message?.info(`点击了删除rowId为${rowId}`);
}
const pagination: PaginationProps = reactive({
page: 1,
pageSize: 10,
showSizePicker: true,
pageSizes: [10, 15, 20, 25, 30],
onChange: (page: number) => {
pagination.page = page;
},
onUpdatePageSize: (pageSize: number) => {
pagination.pageSize = pageSize;
pagination.page = 1;
}
});
function init() {
getTableData();
}
//
init();
</script>
<style scoped></style>

View File

@ -1,9 +0,0 @@
<template>
<div class="h-full">
<n-card title="多级菜单 - 三级菜单" class="h-full shadow-sm rounded-16px"></n-card>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -1,9 +0,0 @@
<template>
<div class="h-full">
<n-card title="多级菜单 - 二级菜单" class="h-full shadow-sm rounded-16px"></n-card>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -1,482 +0,0 @@
<template>
<n-space :vertical="true" :size="16">
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="pieRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="lineRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="barRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="scatterRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="areaRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="radarRef" class="h-400px"></div>
</n-card>
</n-space>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import DataSet from '@antv/data-set';
import { Chart } from '@antv/g2';
const pieRef = ref<HTMLElement>();
const lineRef = ref<HTMLElement>();
const barRef = ref<HTMLElement>();
const scatterRef = ref<HTMLElement>();
const areaRef = ref<HTMLElement>();
const radarRef = ref<HTMLElement>();
function renderPieChart() {
if (!pieRef.value) return;
const data = [
{ item: 'rose 1', count: 40, percent: 0.4 },
{ item: 'rose 2', count: 40, percent: 0.4 },
{ item: 'rose 3', count: 40, percent: 0.4 },
{ item: 'rose 4', count: 40, percent: 0.4 },
{ item: 'rose 5', count: 21, percent: 0.21 },
{ item: 'rose 6', count: 17, percent: 0.17 },
{ item: 'rose 7', count: 13, percent: 0.13 },
{ item: 'rose 8', count: 9, percent: 0.09 }
];
const chart = new Chart({
container: pieRef.value,
autoFit: true
});
chart.data(data);
chart.coordinate('theta', {
radius: 0.85
});
chart.scale('percent', {
formatter: (val: number) => `${val * 100}%`
});
chart.tooltip({
showTitle: false,
showMarkers: false
});
chart.legend({ position: 'top' });
chart.axis(false); //
chart
.interval()
.adjust('stack')
.position('percent')
.color('item')
.label('percent', {
offset: -40,
style: {
textAlign: 'center',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
fill: '#fff'
}
})
.tooltip('item*percent', (item, percent) => {
return {
name: item,
value: `${percent * 100}%`
};
})
.style({
lineWidth: 1,
stroke: '#fff'
});
chart.interaction('element-single-selected');
chart.render();
}
function renderLineChart() {
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/terrorism.json')
.then(res => res.json())
.then(data => {
const ds = new DataSet();
if (!lineRef.value) return;
const chart = new Chart({
container: lineRef.value,
autoFit: true,
syncViewPadding: true
});
chart.scale({
Deaths: {
sync: true,
nice: true
},
death: {
sync: true,
nice: true
}
});
const dv1 = ds.createView().source(data);
dv1.transform({
type: 'map',
callback: (row: any) => {
const currentRow = { ...row };
if (typeof row.Deaths === 'string') {
currentRow.Deaths = row.Deaths.replace(',', '');
}
currentRow.Deaths = parseInt(row.Deaths, 10);
currentRow.death = row.Deaths;
currentRow.year = row.Year;
return currentRow;
}
});
const view1 = chart.createView();
view1.data(dv1.rows);
view1.axis('Year', {
subTickLine: {
count: 3,
length: 3
},
tickLine: {
length: 6
}
});
view1.axis('Deaths', {
label: {
formatter: text => {
return text.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}
}
});
view1.line().position('Year*Deaths');
const dv2 = ds.createView().source(dv1.rows);
dv2.transform({
type: 'regression',
method: 'polynomial',
fields: ['year', 'death'],
bandwidth: 0.1,
as: ['year', 'death']
});
const view2 = chart.createView();
view2.axis(false);
view2.data(dv2.rows);
view2
.line()
.position('year*death')
.style({
stroke: '#969696',
lineDash: [3, 3]
})
.tooltip(false);
view1.annotation().text({
content: '趋势线',
position: ['1970', 2500],
style: {
fill: '#8c8c8c',
fontSize: 14,
fontWeight: 300
},
offsetY: -70
});
chart.render();
});
}
function renderBarChart() {
if (!barRef.value) return;
const data = [
{ type: '未知', value: 654, percent: 0.02 },
{ type: '17 岁以下', value: 654, percent: 0.02 },
{ type: '18-24 岁', value: 4400, percent: 0.2 },
{ type: '25-29 岁', value: 5300, percent: 0.24 },
{ type: '30-39 岁', value: 6200, percent: 0.28 },
{ type: '40-49 岁', value: 3300, percent: 0.14 },
{ type: '50 岁以上', value: 1500, percent: 0.06 }
];
const chart = new Chart({
container: barRef.value,
autoFit: true,
height: 500,
padding: [50, 20, 50, 20]
});
chart.data(data);
chart.scale('value', {
alias: '销售额(万)'
});
chart.axis('type', {
tickLine: {
alignTick: false
}
});
chart.axis('value', false);
chart.tooltip({
showMarkers: false
});
chart.interval().position('type*value');
chart.interaction('element-active');
//
data.forEach(item => {
chart
.annotation()
.text({
position: [item.type, item.value],
content: item.value,
style: {
textAlign: 'center'
},
offsetY: -30
})
.text({
position: [item.type, item.value],
content: `${(item.percent * 100).toFixed(0)}%`,
style: {
textAlign: 'center'
},
offsetY: -12
});
});
chart.render();
}
function renderScatterChart() {
const colorMap = {
Asia: '#1890FF',
Americas: '#2FC25B',
Europe: '#FACC14',
Oceania: '#223273'
};
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/bubble.json')
.then(res => res.json())
.then(data => {
if (!scatterRef.value) return;
const chart = new Chart({
container: scatterRef.value,
autoFit: true,
height: 500
});
chart.data(data);
//
chart.scale({
LifeExpectancy: {
alias: '人均寿命(年)',
nice: true
},
Population: {
type: 'pow',
alias: '人口总数'
},
GDP: {
alias: '人均国内生产总值($)',
nice: true
},
Country: {
alias: '国家/地区'
}
});
chart.axis('GDP', {
label: {
formatter(value) {
return `${(Number(value) / 1000).toFixed(0)}k`;
} //
}
});
chart.tooltip({
showTitle: false,
showMarkers: false
});
chart.legend('Population', false); // Population Country
chart
.point()
.position('GDP*LifeExpectancy')
.size('Population', [4, 65])
.color('continent', val => {
const key = val as keyof typeof colorMap;
return colorMap[key];
})
.shape('circle')
.tooltip('Country*Population*GDP*LifeExpectancy')
.style('continent', val => {
const key = val as keyof typeof colorMap;
return {
lineWidth: 1,
strokeOpacity: 1,
fillOpacity: 0.3,
opacity: 0.65,
stroke: colorMap[key]
};
});
chart.interaction('element-active');
chart.render();
});
}
function renderAreaChart() {
if (!areaRef.value) return;
const data = [
{ country: 'Asia', year: '1750', value: 502 },
{ country: 'Asia', year: '1800', value: 635 },
{ country: 'Asia', year: '1850', value: 809 },
{ country: 'Asia', year: '1900', value: 5268 },
{ country: 'Asia', year: '1950', value: 4400 },
{ country: 'Asia', year: '1999', value: 3634 },
{ country: 'Asia', year: '2050', value: 947 },
{ country: 'Africa', year: '1750', value: 106 },
{ country: 'Africa', year: '1800', value: 107 },
{ country: 'Africa', year: '1850', value: 111 },
{ country: 'Africa', year: '1900', value: 1766 },
{ country: 'Africa', year: '1950', value: 221 },
{ country: 'Africa', year: '1999', value: 767 },
{ country: 'Africa', year: '2050', value: 133 },
{ country: 'Europe', year: '1750', value: 163 },
{ country: 'Europe', year: '1800', value: 203 },
{ country: 'Europe', year: '1850', value: 276 },
{ country: 'Europe', year: '1900', value: 628 },
{ country: 'Europe', year: '1950', value: 547 },
{ country: 'Europe', year: '1999', value: 729 },
{ country: 'Europe', year: '2050', value: 408 },
{ country: 'Oceania', year: '1750', value: 200 },
{ country: 'Oceania', year: '1800', value: 200 },
{ country: 'Oceania', year: '1850', value: 200 },
{ country: 'Oceania', year: '1900', value: 460 },
{ country: 'Oceania', year: '1950', value: 230 },
{ country: 'Oceania', year: '1999', value: 300 },
{ country: 'Oceania', year: '2050', value: 300 }
];
const chart = new Chart({
container: areaRef.value,
autoFit: true,
height: 500
});
chart.data(data);
chart.scale('year', {
type: 'linear',
tickInterval: 50
});
chart.scale('value', {
nice: true
});
chart.tooltip({
showCrosshairs: true,
shared: true
});
chart.area().adjust('stack').position('year*value').color('country');
chart.line().adjust('stack').position('year*value').color('country');
chart.interaction('element-highlight');
chart.render();
}
function renderRadarChart() {
if (!radarRef.value) return;
const data = [
{ item: 'Design', a: 70, b: 30 },
{ item: 'Development', a: 60, b: 70 },
{ item: 'Marketing', a: 50, b: 60 },
{ item: 'Users', a: 40, b: 50 },
{ item: 'Test', a: 60, b: 70 },
{ item: 'Language', a: 70, b: 50 },
{ item: 'Technology', a: 50, b: 40 },
{ item: 'Support', a: 30, b: 40 },
{ item: 'Sales', a: 60, b: 40 },
{ item: 'UX', a: 50, b: 60 }
];
const { DataView } = DataSet;
const dv = new DataView().source(data);
dv.transform({
type: 'fold',
fields: ['a', 'b'], //
key: 'user', // key
value: 'score' // value
});
const chart = new Chart({
container: radarRef.value,
autoFit: true,
height: 500
});
chart.data(dv.rows);
chart.scale('score', {
min: 0,
max: 80
});
chart.coordinate('polar', {
radius: 0.8
});
chart.tooltip({
shared: true,
showCrosshairs: true,
crosshairs: {
line: {
style: {
lineDash: [4, 4],
stroke: '#333'
}
}
}
});
chart.axis('item', {
line: null,
tickLine: null,
grid: {
line: {
style: {
lineDash: null
}
}
}
});
chart.axis('score', {
line: null,
tickLine: null,
grid: {
line: {
type: 'line',
style: {
lineDash: null
}
}
}
});
chart.line().position('item*score').color('user').size(2);
chart.point().position('item*score').color('user').shape('circle').size(4).style({
stroke: '#fff',
lineWidth: 1,
fillOpacity: 1
});
chart.area().position('item*score').color('user');
chart.render();
}
function init() {
renderPieChart();
renderLineChart();
renderBarChart();
renderScatterChart();
renderAreaChart();
renderRadarChart();
}
onMounted(() => {
init();
});
</script>
<style scoped></style>

View File

@ -1,783 +0,0 @@
<template>
<n-space :vertical="true" :size="16">
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="pieRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="lineRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="barRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="pictorialBarRef" class="h-600px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="scatterRef" class="h-600px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="radarRef" class="h-400px"></div>
</n-card>
<n-card :bordered="false" class="rounded-16px shadow-sm">
<div ref="gaugeRef" class="h-640px"></div>
</n-card>
</n-space>
</template>
<script setup lang="ts">
import { onUnmounted, ref } from 'vue';
import type { Ref } from 'vue';
import { graphic } from 'echarts';
import { type ECOption, useEcharts } from '@/composables';
const pieOptions = ref<ECOption>({
legend: {},
toolbox: {
show: true,
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true }
}
},
series: [
{
name: 'Nightingale Chart',
type: 'pie',
radius: [50, 150],
center: ['50%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 8
},
data: [
{ value: 40, name: 'rose 1' },
{ value: 38, name: 'rose 2' },
{ value: 32, name: 'rose 3' },
{ value: 30, name: 'rose 4' },
{ value: 28, name: 'rose 5' },
{ value: 26, name: 'rose 6' },
{ value: 22, name: 'rose 7' },
{ value: 18, name: 'rose 8' }
]
}
]
}) as Ref<ECOption>;
const { domRef: pieRef } = useEcharts(pieOptions);
const lineOptions = ref<ECOption>({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
title: {
text: 'Stacked Line'
},
legend: {
data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
color: '#37a2da',
name: 'Email',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#37a2da'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [120, 132, 101, 134, 90, 230, 210]
},
{
color: '#9fe6b8',
name: 'Union Ads',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#9fe6b8'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [220, 182, 191, 234, 290, 330, 310]
},
{
color: '#fedb5c',
name: 'Video Ads',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#fedb5c'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [150, 232, 201, 154, 190, 330, 410]
},
{
color: '#fb7293',
name: 'Direct',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#fb7293'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [320, 332, 301, 334, 390, 330, 320]
},
{
color: '#e7bcf3',
name: 'Search Engine',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#e7bcf3'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}) as Ref<ECOption>;
const { domRef: lineRef } = useEcharts(lineOptions);
const barOptions = ref<ECOption>({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
color: '#8378ea',
showBackground: true,
barGap: 100,
itemStyle: {
borderRadius: [40, 40, 0, 0]
},
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)'
}
}
]
}) as Ref<ECOption>;
const { domRef: barRef } = useEcharts(barOptions);
const pictorialBarOption = ref<ECOption>(getPictorialBarOption()) as Ref<ECOption>;
const { domRef: pictorialBarRef } = useEcharts(pictorialBarOption);
function getPictorialBarOption(): ECOption {
const category: string[] = [];
let dottedBase = Number(new Date());
const lineData: number[] = [];
const barData: number[] = [];
for (let i = 0; i < 20; i += 1) {
const date = new Date((dottedBase += 3600 * 24 * 1000));
category.push([date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-'));
const b = Math.random() * 200;
const d = Math.random() * 200;
barData.push(b);
lineData.push(d + b);
}
const options: ECOption = {
backgroundColor: '#0f375f',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['line', 'bar'],
textStyle: {
color: '#ccc'
}
},
xAxis: {
data: category,
axisLine: {
lineStyle: {
color: '#ccc'
}
}
},
yAxis: {
splitLine: { show: false },
axisLine: {
lineStyle: {
color: '#ccc'
}
}
},
series: [
{
name: 'line',
type: 'line',
smooth: true,
showAllSymbol: true,
symbol: 'emptyCircle',
symbolSize: 15,
data: lineData
},
{
name: 'bar',
type: 'bar',
barWidth: 10,
itemStyle: {
borderRadius: 5,
color: new graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#14c8d4' },
{ offset: 1, color: '#43eec6' }
])
},
data: barData
},
{
name: 'line',
type: 'bar',
barGap: '-100%',
barWidth: 10,
itemStyle: {
color: new graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(20,200,212,0.5)' },
{ offset: 0.2, color: 'rgba(20,200,212,0.2)' },
{ offset: 1, color: 'rgba(20,200,212,0)' }
])
},
z: -12,
data: lineData
},
{
name: 'dotted',
type: 'pictorialBar',
symbol: 'rect',
itemStyle: {
color: '#0f375f'
},
symbolRepeat: true,
symbolSize: [12, 4],
symbolMargin: 1,
z: -10,
data: lineData
}
]
};
return options;
}
const scatterOptions = ref<ECOption>(getScatterOption()) as Ref<ECOption>;
const { domRef: scatterRef } = useEcharts(scatterOptions);
function getScatterOption() {
// prettier-ignore
const hours = ['12a', '1a', '2a', '3a', '4a', '5a', '6a', '7a', '8a', '9a','10a','11a', '12p', '1p', '2p', '3p', '4p', '5p', '6p', '7p', '8p', '9p', '10p', '11p'];
// prettier-ignore
const days = ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday'];
// prettier-ignore
const data: [number, number, number][] = [[0,0,5],[0,1,1],[0,2,0],[0,3,0],[0,4,0],[0,5,0],[0,6,0],[0,7,0],[0,8,0],[0,9,0],[0,10,0],[0,11,2],[0,12,4],[0,13,1],[0,14,1],[0,15,3],[0,16,4],[0,17,6],[0,18,4],[0,19,4],[0,20,3],[0,21,3],[0,22,2],[0,23,5],[1,0,7],[1,1,0],[1,2,0],[1,3,0],[1,4,0],[1,5,0],[1,6,0],[1,7,0],[1,8,0],[1,9,0],[1,10,5],[1,11,2],[1,12,2],[1,13,6],[1,14,9],[1,15,11],[1,16,6],[1,17,7],[1,18,8],[1,19,12],[1,20,5],[1,21,5],[1,22,7],[1,23,2],[2,0,1],[2,1,1],[2,2,0],[2,3,0],[2,4,0],[2,5,0],[2,6,0],[2,7,0],[2,8,0],[2,9,0],[2,10,3],[2,11,2],[2,12,1],[2,13,9],[2,14,8],[2,15,10],[2,16,6],[2,17,5],[2,18,5],[2,19,5],[2,20,7],[2,21,4],[2,22,2],[2,23,4],[3,0,7],[3,1,3],[3,2,0],[3,3,0],[3,4,0],[3,5,0],[3,6,0],[3,7,0],[3,8,1],[3,9,0],[3,10,5],[3,11,4],[3,12,7],[3,13,14],[3,14,13],[3,15,12],[3,16,9],[3,17,5],[3,18,5],[3,19,10],[3,20,6],[3,21,4],[3,22,4],[3,23,1],[4,0,1],[4,1,3],[4,2,0],[4,3,0],[4,4,0],[4,5,1],[4,6,0],[4,7,0],[4,8,0],[4,9,2],[4,10,4],[4,11,4],[4,12,2],[4,13,4],[4,14,4],[4,15,14],[4,16,12],[4,17,1],[4,18,8],[4,19,5],[4,20,3],[4,21,7],[4,22,3],[4,23,0],[5,0,2],[5,1,1],[5,2,0],[5,3,3],[5,4,0],[5,5,0],[5,6,0],[5,7,0],[5,8,2],[5,9,0],[5,10,4],[5,11,1],[5,12,5],[5,13,10],[5,14,5],[5,15,7],[5,16,11],[5,17,6],[5,18,0],[5,19,5],[5,20,3],[5,21,4],[5,22,2],[5,23,0],[6,0,1],[6,1,0],[6,2,0],[6,3,0],[6,4,0],[6,5,0],[6,6,0],[6,7,0],[6,8,0],[6,9,0],[6,10,1],[6,11,0],[6,12,2],[6,13,1],[6,14,3],[6,15,4],[6,16,0],[6,17,0],[6,18,0],[6,19,0],[6,20,1],[6,21,2],[6,22,2],[6,23,6]];
const title: echarts.TitleComponentOption[] = [];
const singleAxis: echarts.SingleAxisComponentOption[] = [];
const series: echarts.ScatterSeriesOption[] = [];
days.forEach((day, idx) => {
title.push({
textBaseline: 'middle',
top: `${((idx + 0.5) * 100) / 7}%`,
text: day
});
singleAxis.push({
left: 150,
type: 'category',
boundaryGap: false,
data: hours,
top: `${(idx * 100) / 7 + 5}%`,
height: `${100 / 7 - 10}%`,
axisLabel: {
interval: 2
}
});
series.push({
singleAxisIndex: idx,
coordinateSystem: 'singleAxis',
type: 'scatter',
data: [],
symbolSize(dataItem) {
return dataItem[1] * 4;
}
});
});
data.forEach(dataItem => {
(series as any)[dataItem[0]].data.push([dataItem[1], dataItem[2]]);
});
const option: ECOption = {
tooltip: {
position: 'top'
},
title,
singleAxis,
series: series as any
};
return option;
}
const radarOptions = ref<ECOption>({
title: {
text: 'Multiple Radar'
},
tooltip: {
trigger: 'axis'
},
legend: {
left: 'center',
data: ['A Software', 'A Phone', 'Another Phone', 'Precipitation', 'Evaporation']
},
radar: [
{
indicator: [
{ name: 'Brand', max: 100 },
{ name: 'Content', max: 100 },
{ name: 'Usability', max: 100 },
{ name: 'Function', max: 100 }
],
center: ['25%', '40%'],
radius: 80
},
{
indicator: [
{ name: 'Look', max: 100 },
{ name: 'Photo', max: 100 },
{ name: 'System', max: 100 },
{ name: 'Performance', max: 100 },
{ name: 'Screen', max: 100 }
],
radius: 80,
center: ['50%', '60%']
},
{
indicator: (() => {
const res = [];
for (let i = 1; i <= 12; i += 1) {
res.push({ name: `${i}`, max: 100 });
}
return res;
})(),
center: ['75%', '40%'],
radius: 80
}
],
series: [
{
type: 'radar',
tooltip: {
trigger: 'item'
},
areaStyle: {},
data: [
{
value: [60, 73, 85, 40],
name: 'A Software'
}
]
},
{
type: 'radar',
radarIndex: 1,
areaStyle: {},
data: [
{
value: [85, 90, 90, 95, 95],
name: 'A Phone'
},
{
value: [95, 80, 95, 90, 93],
name: 'Another Phone'
}
]
},
{
type: 'radar',
radarIndex: 2,
areaStyle: {},
data: [
{
name: 'Precipitation',
value: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 75.6, 82.2, 48.7, 18.8, 6.0, 2.3]
},
{
name: 'Evaporation',
value: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 35.6, 62.2, 32.6, 20.0, 6.4, 3.3]
}
]
}
]
}) as Ref<ECOption>;
const { domRef: radarRef } = useEcharts(radarOptions);
const gaugeOptions = ref<ECOption>({
series: [
{
name: 'hour',
type: 'gauge',
startAngle: 90,
endAngle: -270,
min: 0,
max: 12,
splitNumber: 12,
clockwise: true,
axisLine: {
lineStyle: {
width: 15,
color: [[1, 'rgba(0,0,0,0.7)']],
shadowColor: 'rgba(0, 0, 0, 0.5)',
shadowBlur: 15
}
},
splitLine: {
lineStyle: {
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 3,
shadowOffsetX: 1,
shadowOffsetY: 2
}
},
axisLabel: {
fontSize: 50,
distance: 25,
formatter(value) {
if (value === 0) {
return '';
}
return `${value}`;
}
},
anchor: {
show: true,
icon: 'path://M532.8,70.8C532.8,70.8,532.8,70.8,532.8,70.8L532.8,70.8C532.7,70.8,532.8,70.8,532.8,70.8z M456.1,49.6c-2.2-6.2-8.1-10.6-15-10.6h-37.5v10.6h37.5l0,0c2.9,0,5.3,2.4,5.3,5.3c0,2.9-2.4,5.3-5.3,5.3v0h-22.5c-1.5,0.1-3,0.4-4.3,0.9c-4.5,1.6-8.1,5.2-9.7,9.8c-0.6,1.7-0.9,3.4-0.9,5.3v16h10.6v-16l0,0l0,0c0-2.7,2.1-5,4.7-5.3h10.3l10.4,21.2h11.8l-10.4-21.2h0c6.9,0,12.8-4.4,15-10.6c0.6-1.7,0.9-3.5,0.9-5.3C457,53,456.7,51.2,456.1,49.6z M388.9,92.1h11.3L381,39h-3.6h-11.3L346.8,92v0h11.3l3.9-10.7h7.3h7.7l3.9-10.6h-7.7h-7.3l7.7-21.2v0L388.9,92.1z M301,38.9h-10.6v53.1H301V70.8h28.4l3.7-10.6H301V38.9zM333.2,38.9v10.6v10.7v31.9h10.6V38.9H333.2z M249.5,81.4L249.5,81.4L249.5,81.4c-2.9,0-5.3-2.4-5.3-5.3h0V54.9h0l0,0c0-2.9,2.4-5.3,5.3-5.3l0,0l0,0h33.6l3.9-10.6h-37.5c-1.9,0-3.6,0.3-5.3,0.9c-4.5,1.6-8.1,5.2-9.7,9.7c-0.6,1.7-0.9,3.5-0.9,5.3l0,0v21.3c0,1.9,0.3,3.6,0.9,5.3c1.6,4.5,5.2,8.1,9.7,9.7c1.7,0.6,3.5,0.9,5.3,0.9h33.6l3.9-10.6H249.5z M176.8,38.9v10.6h49.6l3.9-10.6H176.8z M192.7,81.4L192.7,81.4L192.7,81.4c-2.9,0-5.3-2.4-5.3-5.3l0,0v-5.3h38.9l3.9-10.6h-53.4v10.6v5.3l0,0c0,1.9,0.3,3.6,0.9,5.3c1.6,4.5,5.2,8.1,9.7,9.7c1.7,0.6,3.4,0.9,5.3,0.9h23.4h10.2l3.9-10.6l0,0H192.7z M460.1,38.9v10.6h21.4v42.5h10.6V49.6h17.5l3.8-10.6H460.1z M541.6,68.2c-0.2,0.1-0.4,0.3-0.7,0.4C541.1,68.4,541.4,68.3,541.6,68.2L541.6,68.2z M554.3,60.2h-21.6v0l0,0c-2.9,0-5.3-2.4-5.3-5.3c0-2.9,2.4-5.3,5.3-5.3l0,0l0,0h33.6l3.8-10.6h-37.5l0,0c-6.9,0-12.8,4.4-15,10.6c-0.6,1.7-0.9,3.5-0.9,5.3c0,1.9,0.3,3.7,0.9,5.3c2.2,6.2,8.1,10.6,15,10.6h21.6l0,0c2.9,0,5.3,2.4,5.3,5.3c0,2.9-2.4,5.3-5.3,5.3l0,0h-37.5v10.6h37.5c6.9,0,12.8-4.4,15-10.6c0.6-1.7,0.9-3.5,0.9-5.3c0-1.9-0.3-3.7-0.9-5.3C567.2,64.6,561.3,60.2,554.3,60.2z',
showAbove: false,
offsetCenter: [0, '-35%'],
size: 120,
keepAspect: true,
itemStyle: {
color: '#707177'
}
},
pointer: {
icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
width: 12,
length: '55%',
offsetCenter: [0, '8%'],
itemStyle: {
color: '#C0911F',
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 8,
shadowOffsetX: 2,
shadowOffsetY: 4
}
},
detail: {
show: false
},
title: {
offsetCenter: [0, '30%']
},
data: [
{
value: 0
}
]
},
{
name: 'minute',
type: 'gauge',
startAngle: 90,
endAngle: -270,
min: 0,
max: 60,
clockwise: true,
axisLine: {
show: false
},
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: false
},
pointer: {
icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
width: 8,
length: '70%',
offsetCenter: [0, '8%'],
itemStyle: {
color: '#C0911F',
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 8,
shadowOffsetX: 2,
shadowOffsetY: 4
}
},
anchor: {
show: true,
size: 20,
showAbove: false,
itemStyle: {
borderWidth: 15,
borderColor: '#C0911F',
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 8,
shadowOffsetX: 2,
shadowOffsetY: 4
}
},
detail: {
show: false
},
title: {
offsetCenter: ['0%', '-40%']
},
data: [
{
value: 0
}
]
},
{
name: 'second',
type: 'gauge',
startAngle: 90,
endAngle: -270,
min: 0,
max: 60,
animationEasingUpdate: 'bounceOut',
clockwise: true,
axisLine: {
show: false
},
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: false
},
pointer: {
icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
width: 4,
length: '85%',
offsetCenter: [0, '8%'],
itemStyle: {
color: '#C0911F',
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 8,
shadowOffsetX: 2,
shadowOffsetY: 4
}
},
anchor: {
show: true,
size: 15,
showAbove: true,
itemStyle: {
color: '#C0911F',
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 8,
shadowOffsetX: 2,
shadowOffsetY: 4
}
},
detail: {
show: false
},
title: {
offsetCenter: ['0%', '-40%']
},
data: [
{
value: 0
}
]
}
]
}) as Ref<ECOption>;
let intervalId: NodeJS.Timer;
const { domRef: gaugeRef } = useEcharts(gaugeOptions, chart => {
intervalId = setInterval(() => {
const date = new Date();
const second = date.getSeconds();
const minute = date.getMinutes() + second / 60;
const hour = (date.getHours() % 12) + minute / 60;
chart.setOption({
animationDurationUpdate: 300,
series: [
{
name: 'hour',
animation: hour !== 0,
data: [{ value: hour }]
},
{
name: 'minute',
animation: minute !== 0,
data: [{ value: minute }]
},
{
animation: second !== 0,
name: 'second',
data: [{ value: second }]
}
]
});
}, 1000);
});
function clearClock() {
clearInterval(intervalId);
}
onUnmounted(() => {
clearClock();
});
</script>
<style scoped></style>

View File

@ -1,33 +0,0 @@
<template>
<div class="h-full">
<n-card title="文本复制" class="h-full shadow-sm rounded-16px">
<n-input-group>
<n-input v-model:value="source" placeholder="请输入要复制的内容吧" />
<n-button type="primary" @click="handleCopy">复制</n-button>
</n-input-group>
</n-card>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useClipboard } from '@vueuse/core';
const source = ref('');
const { copy, isSupported } = useClipboard();
function handleCopy() {
if (!isSupported) {
window.$message?.error('您的浏览器不支持Clipboard API');
return;
}
if (!source.value) {
window.$message?.error('请输入要复制的内容');
return;
}
copy(source.value);
window.$message?.success(`复制成功:${source.value}`);
}
</script>
<style scoped></style>

View File

@ -1,50 +0,0 @@
<template>
<div class="h-full">
<n-card title="markdown插件" class="shadow-sm rounded-16px">
<div ref="domRef"></div>
<template #footer>
<github-link link="https://github.com/Vanessa219/vditor" />
</template>
</n-card>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue';
import Vditor from 'vditor';
import 'vditor/dist/index.css';
import { useThemeStore } from '@/store';
const theme = useThemeStore();
const vditor = ref<Vditor>();
const domRef = ref<HTMLElement>();
function renderVditor() {
if (!domRef.value) return;
vditor.value = new Vditor(domRef.value, {
minHeight: 400,
theme: theme.darkMode ? 'dark' : 'classic',
icon: 'material',
cache: { enable: false }
});
}
const stopHandle = watch(
() => theme.darkMode,
newValue => {
const themeMode = newValue ? 'dark' : 'classic';
vditor.value?.setTheme(themeMode);
}
);
onMounted(() => {
renderVditor();
});
onUnmounted(() => {
stopHandle();
});
</script>
<style scoped></style>

View File

@ -1,45 +0,0 @@
<template>
<div class="h-full">
<n-card title="富文本插件" class="shadow-sm rounded-16px">
<div ref="domRef" class="bg-white dark:bg-dark"></div>
<template #footer>
<github-link link="https://github.com/wangeditor-team/wangEditor" />
</template>
</n-card>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import WangEditor from 'wangeditor';
const editor = ref<WangEditor>();
const domRef = ref<HTMLElement>();
function renderWangEditor() {
editor.value = new WangEditor(domRef.value);
setEditorConfig();
editor.value.create();
}
function setEditorConfig() {
if (editor.value?.config?.zIndex) {
editor.value.config.zIndex = 10;
}
}
onMounted(() => {
renderWangEditor();
});
</script>
<style scoped>
:deep(.w-e-toolbar) {
background: inherit !important;
border-color: #999 !important;
}
:deep(.w-e-text-container) {
background: inherit;
border-color: #999 !important;
}
</style>

View File

@ -1,32 +0,0 @@
export const icons = [
'mdi:emoticon',
'mdi:ab-testing',
'ph:alarm',
'ph:android-logo',
'ph:align-bottom',
'ph:archive-box-light',
'uil:basketball',
'uil:brightness-plus',
'uil:capture',
'mdi:apps-box',
'mdi:alert',
'mdi:airballoon',
'mdi:airplane-edit',
'mdi:alpha-f-box-outline',
'mdi:arm-flex-outline',
'ic:baseline-10mp',
'ic:baseline-access-time',
'ic:baseline-brightness-4',
'ic:baseline-brightness-5',
'ic:baseline-credit-card',
'ic:baseline-filter-1',
'ic:baseline-filter-2',
'ic:baseline-filter-3',
'ic:baseline-filter-4',
'ic:baseline-filter-5',
'ic:baseline-filter-6',
'ic:baseline-filter-7',
'ic:baseline-filter-8',
'ic:baseline-filter-9',
'ic:baseline-filter-9-plus'
];

View File

@ -1,51 +0,0 @@
<template>
<div class="h-full">
<n-card title="Icon组件示例" class="shadow-sm rounded-16px">
<div class="grid grid-cols-10">
<template v-for="item in icons" :key="item">
<div class="mt-5px flex-x-center">
<svg-icon :icon="item" class="text-30px" />
</div>
</template>
</div>
<div class="mt-50px">
<h1 class="mb-20px text-18px font-500">Icon图标选择器</h1>
<icon-select v-model:value="selectValue" :icons="icons" />
</div>
<template #footer>
<web-site-link label="iconify地址" link="https://icones.js.org/" class="mt-10px" />
</template>
</n-card>
<n-card title="自定义图标示例" class="mt-10px shadow-sm rounded-16px">
<div class="pb-12px text-16px">
在src/assets/svg-icon文件夹下的svg文件通过在template里面以 icon-local-{文件名} 直接渲染,
其中icon-local为.env文件里的 VITE_ICON_LOCAL_PREFFIX
</div>
<div class="grid grid-cols-10">
<div class="mt-5px flex-x-center">
<icon-local-activity class="text-40px text-success" />
</div>
<div class="mt-5px flex-x-center">
<icon-local-cast class="text-20px text-error" />
</div>
</div>
<div class="py-12px text-16px">通过SvgIcon组件动态渲染, 菜单通过meta的localIcon属性渲染自定义图标</div>
<div class="grid grid-cols-10">
<div v-for="(fileName, index) in localIcons" :key="index" class="mt-5px flex-x-center">
<svg-icon :local-icon="fileName" class="text-30px text-primary" />
</div>
</div>
</n-card>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { icons } from './icons';
const selectValue = ref('');
const localIcons = ['custom-icon', 'activity', 'at-sign', 'cast', 'chrome', 'copy', 'wind'];
</script>
<style scoped></style>

View File

@ -1,30 +0,0 @@
<template>
<div ref="domRef" class="w-full h-full"></div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useScriptTag } from '@vueuse/core';
import { BAIDU_MAP_SDK_URL } from '@/config';
defineOptions({ name: 'BaiduMap' });
const { load } = useScriptTag(BAIDU_MAP_SDK_URL);
const domRef = ref<HTMLDivElement>();
async function renderMap() {
await load(true);
if (!domRef.value) return;
const map = new BMap.Map(domRef.value);
const point = new BMap.Point(114.05834626586915, 22.546789983033168);
map.centerAndZoom(point, 15);
map.enableScrollWheelZoom();
}
onMounted(() => {
renderMap();
});
</script>
<style scoped></style>

View File

@ -1,32 +0,0 @@
<template>
<div ref="domRef" class="w-full h-full"></div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useScriptTag } from '@vueuse/core';
import { GAODE_MAP_SDK_URL } from '@/config';
defineOptions({ name: 'GaodeMap' });
const { load } = useScriptTag(GAODE_MAP_SDK_URL);
const domRef = ref<HTMLDivElement>();
async function renderMap() {
await load(true);
if (!domRef.value) return;
const map = new AMap.Map(domRef.value, {
zoom: 11,
center: [114.05834626586915, 22.546789983033168],
viewMode: '3D'
});
map.getCenter();
}
onMounted(() => {
renderMap();
});
</script>
<style scoped></style>

View File

@ -1,5 +0,0 @@
import BaiduMap from './baidu-map.vue';
import GaodeMap from './gaode-map.vue';
import TencentMap from './tencent-map.vue';
export { BaiduMap, GaodeMap, TencentMap };

View File

@ -1,32 +0,0 @@
<template>
<div ref="domRef" class="w-full h-full"></div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useScriptTag } from '@vueuse/core';
import { TENCENT_MAP_SDK_URL } from '@/config';
defineOptions({ name: 'TencentMap' });
const { load } = useScriptTag(TENCENT_MAP_SDK_URL);
const domRef = ref<HTMLDivElement | null>(null);
async function renderMap() {
await load(true);
if (!domRef.value) return;
// eslint-disable-next-line no-new
new TMap.Map(domRef.value, {
center: new TMap.LatLng(39.98412, 116.307484),
zoom: 11,
viewMode: '3D'
});
}
onMounted(() => {
renderMap();
});
</script>
<style scoped></style>

View File

@ -1,30 +0,0 @@
<template>
<div class="h-full">
<n-card title="地图插件" class="h-full shadow-sm rounded-16px" content-style="overflow:hidden">
<n-tabs type="line" class="flex-col-stretch h-full" pane-class="flex-1-hidden">
<n-tab-pane v-for="item in maps" :key="item.id" :name="item.id" :tab="item.label">
<component :is="item.component" />
</n-tab-pane>
</n-tabs>
</n-card>
</div>
</template>
<script setup lang="ts">
import type { Component } from 'vue';
import { BaiduMap, GaodeMap, TencentMap } from './components';
interface Map {
id: string;
label: string;
component: Component;
}
const maps: Map[] = [
{ id: 'gaode', label: '高德地图', component: GaodeMap },
{ id: 'tencent', label: '腾讯地图', component: TencentMap },
{ id: 'baidu', label: '百度地图', component: BaiduMap }
];
</script>
<style scoped></style>

View File

@ -1,39 +0,0 @@
<template>
<div class="h-full">
<n-card title="打印" class="shadow-sm rounded-16px">
<n-button type="primary" class="mr-10px" @click="printTable">打印表格</n-button>
<n-button type="primary" @click="printImage">打印图片</n-button>
<template #footer>
<github-link label="printJS" link="https://github.com/crabbly/Print.js" class="mt-10px" />
</template>
</n-card>
</div>
</template>
<script lang="ts" setup>
import printJS from 'print-js';
function printTable() {
printJS({
printable: [
{ name: 'soybean', wechat: 'honghuangdc', remark: '欢迎来技术交流' },
{ name: 'soybean', wechat: 'honghuangdc', remark: '欢迎来技术交流' }
],
properties: ['name', 'wechat', 'remark'],
type: 'json'
});
}
function printImage() {
printJS({
printable: [
'https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg',
'https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg'
],
type: 'image',
header: 'Multiple Images',
imageStyle: 'width:100%;'
});
}
</script>
<style scoped></style>

View File

@ -1,109 +0,0 @@
<template>
<div>
<n-card title="Swiper插件" class="shadow-sm rounded-16px">
<n-space :vertical="true">
<github-link link="https://github.com/nolimits4web/swiper" />
<web-site-link label="vue3版文档地址" link="https://swiperjs.com/vue" />
<web-site-link label="插件demo地址" link="https://swiperjs.com/demos" />
</n-space>
<n-space :vertical="true">
<div v-for="item in swiperExample" :key="item.id">
<h3 class="py-24px text-24px font-bold">{{ item.label }}</h3>
<swiper v-bind="item.options">
<swiper-slide v-for="i in 5" :key="i">
<div class="flex-center h-240px border-1px border-#999 text-18px font-bold">Slide{{ i }}</div>
</swiper-slide>
</swiper>
</div>
</n-space>
</n-card>
</div>
</template>
<script setup lang="ts">
import SwiperCore, { Navigation, Pagination } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import type { SwiperOptions } from 'swiper';
type SwiperExampleOptions = Pick<
SwiperOptions,
'navigation' | 'pagination' | 'scrollbar' | 'slidesPerView' | 'slidesPerGroup' | 'spaceBetween' | 'direction' | 'loop'
>;
interface SwiperExample {
id: number;
label: string;
options: Partial<SwiperExampleOptions>;
}
SwiperCore.use([Navigation, Pagination]);
const swiperExample: SwiperExample[] = [
{ id: 0, label: 'Default', options: {} },
{
id: 1,
label: 'Navigation',
options: {
navigation: true
}
},
{
id: 2,
label: 'Pagination',
options: {
pagination: true
}
},
{
id: 3,
label: 'Pagination dynamic',
options: {
pagination: { dynamicBullets: true }
}
},
{
id: 4,
label: 'Pagination progress',
options: {
navigation: true,
pagination: {
type: 'progressbar'
}
}
},
{
id: 5,
label: 'Pagination fraction',
options: {
navigation: true,
pagination: {
type: 'fraction'
}
}
},
{
id: 6,
label: 'Slides per view',
options: {
pagination: {
clickable: true
},
slidesPerView: 3,
spaceBetween: 30
}
},
{
id: 7,
label: 'Infinite loop',
options: {
navigation: true,
pagination: {
clickable: true
},
loop: true
}
}
];
</script>
<style scoped></style>

View File

@ -1,39 +0,0 @@
<template>
<div class="h-full">
<n-card title="视频播放器插件" class="h-full shadow-sm rounded-16px">
<div ref="domRef" class=""></div>
</n-card>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import Player from 'xgplayer';
const domRef = ref<HTMLElement>();
const player = ref<Player>();
function renderXgPlayer() {
if (!domRef.value) return;
const url = 'https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/byted-player-videos/1.0.0/xgplayer-demo.mp4';
player.value = new Player({
el: domRef.value,
url,
playbackRate: [0.5, 0.75, 1, 1.5, 2],
fluid: true
});
}
function destroyXgPlayer() {
player.value?.destroy();
}
onMounted(() => {
renderXgPlayer();
});
onUnmounted(() => {
destroyXgPlayer();
});
</script>
<style scoped></style>