feat: 1.新增视频播放插件。2.新增插件中心

This commit is contained in:
539943419 committed 2024-08-24 14:20:33 +08:00
1 parent f5e0bd4a56
commit 1f6c924e11
13 files changed
+2465 -44

No files matched your search

+2 -1
View File
@@ -15,7 +15,8 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"naive-ui": "^2.39.0", "naive-ui": "^2.39.0",
"sass": "^1.77.8", "sass": "^1.77.8",
"vue": "^3.4.35" "vue": "^3.4.35",
"xgplayer": "^3.0.20"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.7",
+128
View File
@@ -26,6 +26,9 @@ importers:
vue: vue:
specifier: ^3.4.35 specifier: ^3.4.35
version: 3.4.37(typescript@5.5.4) version: 3.4.37(typescript@5.5.4)
xgplayer:
specifier: ^3.0.20
version: 3.0.20(core-js@3.38.1)
devDependencies: devDependencies:
'@types/lodash': '@types/lodash':
specifier: ^4.17.7 specifier: ^4.17.7
@@ -430,6 +433,9 @@ packages:
computeds@0.0.1: computeds@0.0.1:
resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
core-js@3.38.1:
resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==}
css-render@0.15.14: css-render@0.15.14:
resolution: {integrity: sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==} resolution: {integrity: sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==}
@@ -439,6 +445,13 @@ packages:
csstype@3.1.3: csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
d@1.0.2:
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
engines: {node: '>=0.12'}
danmu.js@1.1.13:
resolution: {integrity: sha512-knFd0/cB2HA4FFWiA7eB2suc5vCvoHdqio33FyyCSfP7C+1A+zQcTvnvwfxaZhrxsGj4qaQI2I8XiTqedRaVmg==}
date-fns-tz@2.0.1: date-fns-tz@2.0.1:
resolution: {integrity: sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==} resolution: {integrity: sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==}
peerDependencies: peerDependencies:
@@ -455,21 +468,51 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
delegate@3.2.0:
resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
downloadjs@1.4.7:
resolution: {integrity: sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==}
entities@5.0.0: entities@5.0.0:
resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==} resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
es5-ext@0.10.64:
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
engines: {node: '>=0.10'}
es6-iterator@2.0.3:
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
es6-symbol@3.1.4:
resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
engines: {node: '>=0.12'}
esbuild@0.21.5: esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'} engines: {node: '>=12'}
hasBin: true hasBin: true
esniff@2.0.1:
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
engines: {node: '>=0.10'}
estree-walker@2.0.2: estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
event-emitter@0.3.5:
resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
evtd@0.2.4: evtd@0.2.4:
resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==} resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==}
ext@1.7.0:
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
fill-range@7.1.1: fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -561,6 +604,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
next-tick@1.1.0:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
normalize-path@3.0.0: normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -622,6 +668,9 @@ packages:
treemate@0.3.11: treemate@0.3.11:
resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==} resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==}
type@2.7.3:
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
typescript@5.5.4: typescript@5.5.4:
resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
@@ -700,6 +749,16 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.0.11 vue: ^3.0.11
xgplayer-subtitles@3.0.20:
resolution: {integrity: sha512-I1bjsIY+aKOrhYQspLdneOkYg+Vf4cJVGPnDSFnNebnxXl9Mhz5SEpWGzYizMYxL9UvsQ9pgjeEY0o4hkwM+kQ==}
peerDependencies:
core-js: '>=3.12.1'
xgplayer@3.0.20:
resolution: {integrity: sha512-UNKZJRyODOZGdka83ao8fI18xdhzOV8qG4aNEOOkuOQbXFXfXsJMr/dazRHFP+uXmTqiCXr568euee3ch7CS7g==}
peerDependencies:
core-js: '>=3.12.1'
snapshots: snapshots:
'@babel/helper-string-parser@7.24.8': {} '@babel/helper-string-parser@7.24.8': {}
@@ -1003,6 +1062,8 @@ snapshots:
computeds@0.0.1: {} computeds@0.0.1: {}
core-js@3.38.1: {}
css-render@0.15.14: css-render@0.15.14:
dependencies: dependencies:
'@emotion/hash': 0.8.0 '@emotion/hash': 0.8.0
@@ -1012,6 +1073,15 @@ snapshots:
csstype@3.1.3: {} csstype@3.1.3: {}
d@1.0.2:
dependencies:
es5-ext: 0.10.64
type: 2.7.3
danmu.js@1.1.13:
dependencies:
event-emitter: 0.3.5
date-fns-tz@2.0.1(date-fns@2.30.0): date-fns-tz@2.0.1(date-fns@2.30.0):
dependencies: dependencies:
date-fns: 2.30.0 date-fns: 2.30.0
@@ -1024,8 +1094,30 @@ snapshots:
delayed-stream@1.0.0: {} delayed-stream@1.0.0: {}
delegate@3.2.0: {}
downloadjs@1.4.7: {}
entities@5.0.0: {} entities@5.0.0: {}
es5-ext@0.10.64:
dependencies:
es6-iterator: 2.0.3
es6-symbol: 3.1.4
esniff: 2.0.1
next-tick: 1.1.0
es6-iterator@2.0.3:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
es6-symbol: 3.1.4
es6-symbol@3.1.4:
dependencies:
d: 1.0.2
ext: 1.7.0
esbuild@0.21.5: esbuild@0.21.5:
optionalDependencies: optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5 '@esbuild/aix-ppc64': 0.21.5
@@ -1052,10 +1144,28 @@ snapshots:
'@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5 '@esbuild/win32-x64': 0.21.5
esniff@2.0.1:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
event-emitter: 0.3.5
type: 2.7.3
estree-walker@2.0.2: {} estree-walker@2.0.2: {}
event-emitter@0.3.5:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
eventemitter3@4.0.7: {}
evtd@0.2.4: {} evtd@0.2.4: {}
ext@1.7.0:
dependencies:
type: 2.7.3
fill-range@7.1.1: fill-range@7.1.1:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
@@ -1143,6 +1253,8 @@ snapshots:
nanoid@3.3.7: {} nanoid@3.3.7: {}
next-tick@1.1.0: {}
normalize-path@3.0.0: {} normalize-path@3.0.0: {}
path-browserify@1.0.1: {} path-browserify@1.0.1: {}
@@ -1207,6 +1319,8 @@ snapshots:
treemate@0.3.11: {} treemate@0.3.11: {}
type@2.7.3: {}
typescript@5.5.4: {} typescript@5.5.4: {}
undici-types@6.13.0: {} undici-types@6.13.0: {}
@@ -1266,3 +1380,17 @@ snapshots:
vdirs: 0.1.8(vue@3.4.37(typescript@5.5.4)) vdirs: 0.1.8(vue@3.4.37(typescript@5.5.4))
vooks: 0.2.12(vue@3.4.37(typescript@5.5.4)) vooks: 0.2.12(vue@3.4.37(typescript@5.5.4))
vue: 3.4.37(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
xgplayer-subtitles@3.0.20(core-js@3.38.1):
dependencies:
core-js: 3.38.1
eventemitter3: 4.0.7
xgplayer@3.0.20(core-js@3.38.1):
dependencies:
core-js: 3.38.1
danmu.js: 1.1.13
delegate: 3.2.0
downloadjs: 1.4.7
eventemitter3: 4.0.7
xgplayer-subtitles: 3.0.20(core-js@3.38.1)
+14 -22
View File
@@ -9,12 +9,12 @@ import {
EllipsisVertical, EllipsisVertical,
QrCodeOutline QrCodeOutline
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import miaoDirectory from './views/miaoDirectory.vue' import miaoDirectory from '@/views/miaoDirectory.vue'
import VirtualPage from './class/VirtualPage'
import VirtualDirectory, { VirtualFile } from './class/VirtualDirectory' import VirtualDirectory, { VirtualFile } from './class/VirtualDirectory'
import MiaoMask from './components/miaoMask.vue' import MiaoMask from './components/miaoMask.vue'
import config from './config' import config from './config'
import useVirtualPages from './hooks/useVirtualPages' import useVirtualPages from './hooks/useVirtualPages'
import init from './hooks/init'
const { baseUrl } = config const { baseUrl } = config
@@ -46,21 +46,11 @@ const createView = (
currentFiles?: VirtualFile[], currentFiles?: VirtualFile[],
index?: number index?: number
) => { ) => {
index ?? (index = views.length) views.push(component, currentDirectorys, currentFiles, index)
views.splice(
index,
0,
reactive<VirtualPage>(
new VirtualPage(component, {
directorys: [...(currentDirectorys ?? [])],
files: [...(currentFiles ?? [])]
})
)
)
} }
const deleteView = (index: number) => { const deleteView = (index: number) => {
views.splice(index, 1) views.deleteView(index)
if (views.length === 0) { if (views.length === 0) {
createView(miaoDirectory, [rootDirectory]) createView(miaoDirectory, [rootDirectory])
} }
@@ -68,14 +58,14 @@ const deleteView = (index: number) => {
// 点击标题是隐藏其他标签或显示其他标签,还挺好用的 // 点击标题是隐藏其他标签或显示其他标签,还挺好用的
const handleClickTitle = (index: number) => { const handleClickTitle = (index: number) => {
let count = views.filter((v) => v.visible).length let count = views._views.filter((v) => v.visible).length
let op = !views[index].visible || count > 1 ? 'hideOthers' : 'showOthers' let op = !views._views[index].visible || count > 1 ? 'hideOthers' : 'showOthers'
let _index = 0 let _index = 0
while (_index < views.length) { while (_index < views._views.length) {
if (_index !== index) { if (_index !== index) {
views[_index].visible = op === 'hideOthers' ? false : true views._views[_index].visible = op === 'hideOthers' ? false : true
} else { } else {
views[_index].visible = true views._views[_index].visible = true
} }
_index += 1 _index += 1
} }
@@ -113,6 +103,7 @@ const handleMenuSelect = (key: string) => {
} }
onMounted(() => { onMounted(() => {
init()
createView(miaoDirectory, [rootDirectory]) createView(miaoDirectory, [rootDirectory])
}) })
</script> </script>
@@ -125,7 +116,7 @@ onMounted(() => {
<transition-group name="tab"> <transition-group name="tab">
<div <div
class="tab" class="tab"
v-for="(view, index) of views" v-for="(view, index) of views._views"
:key="view.uid"> :key="view.uid">
<span <span
class="tab-title" class="tab-title"
@@ -189,12 +180,13 @@ onMounted(() => {
<transition-group name="page"> <transition-group name="page">
<div <div
class="view-container-item" class="view-container-item"
v-for="(view, index) of views" v-for="(view, index) of views._views"
v-show="view.visible" v-show="view.visible"
:key="view.uid"> :key="view.uid">
<component <component
:is="view.component" :is="view.component"
v-model:current-objects="view.currentDirectorys" v-model:current-directorys="view.currentDirectorys"
v-model:current-files="view.currentFiles"
:id="view.uid" :id="view.uid"
:color="view.color" :color="view.color"
@exit="deleteView(index)"></component> @exit="deleteView(index)"></component>
+60
View File
@@ -0,0 +1,60 @@
import useViretualPages from "@/hooks/useVirtualPages"
import VirtualDirectory, { VirtualFile } from "./VirtualDirectory"
type plugin = {
// 用于显示的名字
name: string
// 判断是否可以使用这个组件
// 使用组件即调用下面的func
filter: (VDirectorys: VirtualDirectory[], VFiles: VirtualFile[]) => boolean
func: (VDirectorys: VirtualDirectory[], VFiles: VirtualFile[]) => void
}
export default class PluginCenter {
constructor() {
this.pluginsMap = {}
}
pluginsMap: {
[key: string]: plugin
}
register(key: string, plugin: plugin){
this.pluginsMap[key] = plugin
console.log(this.pluginsMap)
}
/**
* 便捷地注册一个组件为插件,触发时直接用新页面打开
*/
registerComponent(key: string,name: string, component: any, filter:(VDirectorys: VirtualDirectory[], VFiles: VirtualFile[]) => boolean): void {
this.register(key, {
name,
filter,
func: (VDirectorys: VirtualDirectory[], VFiles: VirtualFile[]) => {
if(filter(VDirectorys, VFiles) === false){
return
}
const views = useViretualPages()
views.push(component, VDirectorys, VFiles)
}
})
}
getUsablePlugin(VDirectorys: VirtualDirectory[], VFiles: VirtualFile[]) {
const usableList = []
for(let key of Object.keys(this.pluginsMap)) {
if(this.pluginsMap[key].filter(VDirectorys, VFiles)) {
usableList.push(key)
}
}
return usableList.map(key=>({
key,
...this.pluginsMap[key]
}))
}
usePlugin(key: string, VDirectorys: VirtualDirectory[], VFiles: VirtualFile[]) {
this.pluginsMap[key].func(VDirectorys, VFiles)
}
}
+5
View File
@@ -1,3 +1,4 @@
import config from "@/config";
import generateId from "@/hooks/generateId"; import generateId from "@/hooks/generateId";
import useMiaoFetchApi from "@/hooks/useMiaoFetchApi"; import useMiaoFetchApi from "@/hooks/useMiaoFetchApi";
// import { debounce } from "lodash"; // import { debounce } from "lodash";
@@ -23,6 +24,10 @@ export class VirtualFile {
get path() { get path() {
return `${this.parent.path}${this.name}`; return `${this.parent.path}${this.name}`;
} }
get url() {
return `${config.api.get}${this.parent.path}${this.name}`
}
} }
class VirtualDirectory { class VirtualDirectory {
+63 -3
View File
@@ -1,4 +1,4 @@
import { shallowRef } from "vue"; import { reactive, shallowRef } from "vue";
import VirtualDirectory, { VirtualFile } from "./VirtualDirectory"; import VirtualDirectory, { VirtualFile } from "./VirtualDirectory";
import generateId from "@/hooks/generateId"; import generateId from "@/hooks/generateId";
@@ -13,7 +13,7 @@ const getRandomWebSafeColor = (() => {
})(); })();
export default class VirtualPage { export default class VirtualPage {
constructor(component: any, initObjects?:{ directorys?: VirtualDirectory[], files?: VirtualFile[]}) { constructor(component: any, initObjects?: { directorys?: VirtualDirectory[]; files?: VirtualFile[] }) {
this.component = shallowRef(component); this.component = shallowRef(component);
this.currentDirectorys = initObjects?.directorys ?? []; this.currentDirectorys = initObjects?.directorys ?? [];
this.currentFiles = initObjects?.files ?? []; this.currentFiles = initObjects?.files ?? [];
@@ -43,7 +43,7 @@ export default class VirtualPage {
if (this._title) { if (this._title) {
return this._title; return this._title;
} }
return this.currentDirectorys[0].name; return this?.currentDirectorys[0]?.name ?? this?.currentFiles[0]?.name ?? "未命名标签";
} }
setTitle(title: string) { setTitle(title: string) {
@@ -61,3 +61,63 @@ export default class VirtualPage {
this.refreshKey = Math.random(); this.refreshKey = Math.random();
} }
} }
export class VirtualPages {
constructor() {
this._views = reactive<VirtualPage[]>([]);
}
_views: VirtualPage[];
get length() {
return this._views.length;
}
/**
* 查询一个页面位于_views中的次序
* @param {any} id 要查询的页面的id
* @returns {any}
*/
getIndex(id: number) {
for (let i in this._views) {
if (this._views[i].uid === id) {
return parseInt(i);
}
}
return -1;
}
/**
* 根据id得到view
* @param {any} id 要查询的页面的id
*/
getView(id: number) {
for (let v of this._views) {
if (v.uid === id) {
return v;
}
}
return undefined;
}
/**
* 推入一个新的页面
*/
push(component: any, currentDirectorys?: VirtualDirectory[], currentFiles?: VirtualFile[], index?: number) {
index ?? (index = this.length);
this._views.splice(
index,
0,
reactive<VirtualPage>(
new VirtualPage(component, {
directorys: [...(currentDirectorys ?? [])],
files: [...(currentFiles ?? [])]
})
)
);
}
/**
* 根据次序删除一个页面
*/
deleteView(index: number) {
this._views.splice(index, 1);
}
}
@@ -1,7 +1,7 @@
<template> <template>
<div class="miao-item" draggable="true" ref="itemRef" @dragstart="handleDragStart"> <div class="miao-item" draggable="true" ref="itemRef" @dragstart="handleDragStart">
<miaoContextMenu :options="dropDownOptions" :touch-time-out="500" @select="handleDropdownSelect"> <miaoContextMenu :options="dropDownOptions" :touch-time-out="500" @select="handleDropdownSelect">
<div class="item-main" :class="props.selected ? 'item-main-selected' : ''" ref="mainRef"> <div class="item-main" :class="selected ? 'item-main-selected' : ''" ref="mainRef">
<div class="item-main-front" :style="{ backgroundColor: props.color }"></div> <div class="item-main-front" :style="{ backgroundColor: props.color }"></div>
<Icon class="item-main-icon" v-show="clientWidth > 250"></Icon> <Icon class="item-main-icon" v-show="clientWidth > 250"></Icon>
<div class="item-main-info"> <div class="item-main-info">
@@ -42,6 +42,7 @@ import {
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import dateFormatter from '@/hooks/dateFormatter' import dateFormatter from '@/hooks/dateFormatter'
import { NEllipsis } from 'naive-ui' import { NEllipsis } from 'naive-ui'
import usePluginCenter from '@/hooks/usePluginCenter'
// import useMiaoFetchApi from '@/hooks/useMiaoFetchApi' // import useMiaoFetchApi from '@/hooks/useMiaoFetchApi'
// const miaoFetchApi = useMiaoFetchApi() // const miaoFetchApi = useMiaoFetchApi()
@@ -50,7 +51,7 @@ const props = defineProps<{
item: VirtualDirectory | VirtualFile item: VirtualDirectory | VirtualFile
name?: string name?: string
color: string color: string
selected: boolean selectedItem: (VirtualDirectory | VirtualFile)[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -61,6 +62,8 @@ const emit = defineEmits<{
onSelected: [] onSelected: []
}>() }>()
const pluginCenter = usePluginCenter()
const itemType = ref<'file' | 'directory'>(props.item.type) const itemType = ref<'file' | 'directory'>(props.item.type)
const mainRef = ref<HTMLDivElement>() const mainRef = ref<HTMLDivElement>()
@@ -70,6 +73,10 @@ const clientWidth = ref<number>(666)
const name = props.name ?? props.item.name const name = props.name ?? props.item.name
const selected = computed(() => {
return props.selectedItem.includes(props.item)
})
const time = computed(() => { const time = computed(() => {
return dateFormatter( return dateFormatter(
new Date(props.item.stats.mtimeMs), new Date(props.item.stats.mtimeMs),
@@ -78,6 +85,12 @@ const time = computed(() => {
}) })
const dropDownOptions = computed(() => { const dropDownOptions = computed(() => {
const items = [...props.selectedItem]
if (!selected.value) {
items.push(props.item)
}
const vDirs = items.filter(v => v.type === 'directory')
const vFiles = items.filter(v => v.type === 'file')
const options = [ const options = [
{ {
label: '重命名', label: '重命名',
@@ -98,7 +111,7 @@ const dropDownOptions = computed(() => {
] ]
) )
} }
if (props.selected === false) { if (selected.value === false) {
options.push( options.push(
...[ ...[
{ {
@@ -117,6 +130,10 @@ const dropDownOptions = computed(() => {
] ]
) )
} }
options.push(...pluginCenter.getUsablePlugin(vDirs, vFiles).map(v => ({
label: `插件:${v.name}`,
key: `plugin:${v.key}`
})))
return options return options
}) })
@@ -130,6 +147,15 @@ const handleDropdownSelect = (key: string) => {
emit('delete') emit('delete')
} else if (key === 'select') { } else if (key === 'select') {
emit('onSelected') emit('onSelected')
} else if (key.startsWith('plugin:')) {
const items = [...props.selectedItem]
if (!selected.value) {
items.push(props.item)
}
const vDirs = items.filter(v => v.type === 'directory')
const vFiles = items.filter(v => v.type === 'file')
const plugin = key.split(':')[1]
pluginCenter.usePlugin(plugin, vDirs, vFiles)
} }
} }
@@ -305,10 +331,13 @@ onMounted(() => {
background-color: #d2d2d2; background-color: #d2d2d2;
} }
} }
.item-main-selected { .item-main-selected {
background-color: rgba(0, 128, 207, 0.32); background-color: rgba(0, 128, 207, 0.32);
&:hover { &:hover {
background-color: rgba(0, 128, 207, 0.64);; background-color: rgba(0, 128, 207, 0.64);
;
} }
} }
} }
+22
View File
@@ -0,0 +1,22 @@
import MiaoDirectory from "@/views/miaoDirectory.vue";
import usePluginCenter from "./usePluginCenter";
import MiaoVideoPlayer from "@/views/miaoVideoPlayer.vue";
export default function init() {
const pluginCenter = usePluginCenter();
pluginCenter.registerComponent("miaoDirectory", "文件夹", MiaoDirectory, (vDirs, vFiles) => {
if (vFiles.length) {
return false;
}
return vDirs.length === 1;
});
pluginCenter.registerComponent("miaoVideoPlayer", "视频", MiaoVideoPlayer, (vDirs, vFiles) => {
if (vDirs.length !== 0) {
return false;
}
if (vFiles.length === 0) {
return false;
}
return vFiles.length === 1 && vFiles[0].name.endsWith("mp4");
});
}
+11
View File
@@ -0,0 +1,11 @@
import PluginCenter from "@/class/PluginCenter";
import { reactive } from "vue";
let pluginCenter: PluginCenter
export default function usePluginCenter(){
if(!pluginCenter) {
pluginCenter = reactive(new PluginCenter())
}
return pluginCenter
}
+4 -3
View File
@@ -1,11 +1,12 @@
import VirtualPage from "@/class/VirtualPage" import { VirtualPages } from "@/class/VirtualPage"
import { Reactive, reactive } from "vue" import { Reactive, reactive } from "vue"
let views: Reactive<VirtualPage[]> let views: Reactive<VirtualPages>
export default function useViretualPages(){ export default function useViretualPages(){
if(views === undefined) { if(views === undefined) {
views = reactive<VirtualPage[]>([]) // views = reactive<VirtualPage[]>([])
views = reactive(new VirtualPages())
} }
return views return views
} }
File diff suppressed because it is too large. Load diff
+6 -11
View File
@@ -43,14 +43,14 @@
<n-scrollbar> <n-scrollbar>
<transition-group name="dirItem"> <transition-group name="dirItem">
<miaoDirectoryItem v-for="dir in showData_directory" :item="dir" :key="dir.uid" <miaoDirectoryItem v-for="dir in showData_directory" :item="dir" :key="dir.uid"
:color="props.color" :selected="selectedItem.value.includes(dir)" :color="props.color" :selectedItem="selectedItem.value"
@delete="handleItemDelete(dir)" @click="handleItemClick(dir)" @delete="handleItemDelete(dir)" @click="handleItemClick(dir)"
@drag-start="handleItemDragStart" @rename="handleItemRename(dir)" @drag-start="handleItemDragStart" @rename="handleItemRename(dir)"
@on-selected="handleItemSelect(dir)" /> @on-selected="handleItemSelect(dir)" />
</transition-group> </transition-group>
<transition-group name="dirItem"> <transition-group name="dirItem">
<miaoDirectoryItem v-for="file in showData_files" :item="file" :key="file.uid" <miaoDirectoryItem v-for="file in showData_files" :item="file" :key="file.uid"
:color="props.color" :selected="selectedItem.value.includes(file)" :color="props.color" :selectedItem="selectedItem.value"
@click="handleItemClick(file)" @download="handleItemDownload(file)" @click="handleItemClick(file)" @download="handleItemDownload(file)"
@delete="handleItemDelete(file)" @drag-start="handleItemDragStart" @delete="handleItemDelete(file)" @drag-start="handleItemDragStart"
@rename="handleItemRename(file)" @on-selected="handleItemSelect(file)" /> @rename="handleItemRename(file)" @on-selected="handleItemSelect(file)" />
@@ -159,23 +159,18 @@ class SelectedItem {
// 已选择的item,用于多选功能 // 已选择的item,用于多选功能
const selectedItem = reactive(new SelectedItem()) const selectedItem = reactive(new SelectedItem())
const currentObjects = defineModel<VirtualDirectory[]>('currentObjects', { const currentDirectorys = defineModel<VirtualDirectory[]>('currentDirectorys', {
required: true required: true
}) })
// 目前展示的Dir // 目前展示的Dir
const currentDirectory = computed(() => { const currentDirectory = computed(() => {
return currentObjects.value[0] return currentDirectorys.value[0]
}) })
// 当前组件在views中的序号 // 当前组件在views中的序号
const index = computed(() => { const index = computed(() => {
for (let i in views) { return views.getIndex(props.id)
if (views[i].uid === props.id) {
return parseInt(i)
}
}
return 0
}) })
// 用于展示的数据,后续会增加排序等操作 // 用于展示的数据,后续会增加排序等操作
@@ -208,7 +203,7 @@ const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => {
} else { } else {
virtualDirectory.update() virtualDirectory.update()
} }
currentObjects.value[0] = virtualDirectory currentDirectorys.value[0] = virtualDirectory
} }
const reload = () => { const reload = () => {
+100
View File
@@ -0,0 +1,100 @@
<template>
<miao-drop-handler @on-virtual-files="handleDropVFiles">
<div class="player-container">
<div ref="xgPlayer" id="xgplayer">
</div>
</div>
</miao-drop-handler>
</template>
<script lang="ts" setup>
import { VirtualFile } from '@/class/VirtualDirectory';
import miaoDropHandler from '@/components/miaoDropHandler.vue';
import { onMounted, ref } from 'vue';
import useViretualPages from '@/hooks/useVirtualPages';
import XGPlayer from 'xgplayer'
import '@/style/xgplayer.scss'
const props = defineProps<{
id: number
}>()
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
required: true
})
const views = useViretualPages()
const view = views.getView(props.id)
const videoPath = ref<string>('');
const xgPlayer = ref()
const setVideo = (() => {
let player: any
return (url: string) => {
if (player) {
player.src = url
} else {
player = new XGPlayer({
el: xgPlayer.value,
url,
width: '100%',
height: '100%',
fluid: true,
pip: true,
videoFillMode: 'contain',
autoplay: true,
download: true,
screenShot: {
saveImg: true,
quality: 0.92,
type: 'image/png',
format: '.png'
},
lang: 'zh-cn',
playbackRate: [0.1, 0.3, 0.5, 0.75, 1, 1.5, 2, 3, 5, 10],
playNext: {
urlList: [
],
}
})
}
}
})()
const handleDropVFiles = (files: VirtualFile[]) => {
console.log(files)
view && (view.currentFiles[0] = files[0])
videoPath.value = files[0].url;
setVideo(videoPath.value)
};
onMounted(() => {
if(currentFiles.value.length) {
setVideo(currentFiles.value[0].url)
}
});
</script>
<style scoped lang="scss">
.player-container {
width: 100%;
height: 100%;
position: relative;
background-color: grey;
display: flex;
flex-direction: column;
justify-content: center;
#xgplayer {
width: 100%;
height: 500px;
background-color: black;
}
}
.video-js {
width: 100%;
background-color: rgb(0, 0, 0);
}
</style>