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

+14 -22
View File
@@ -9,12 +9,12 @@ import {
EllipsisVertical,
QrCodeOutline
} from '@vicons/ionicons5'
import miaoDirectory from './views/miaoDirectory.vue'
import VirtualPage from './class/VirtualPage'
import miaoDirectory from '@/views/miaoDirectory.vue'
import VirtualDirectory, { VirtualFile } from './class/VirtualDirectory'
import MiaoMask from './components/miaoMask.vue'
import config from './config'
import useVirtualPages from './hooks/useVirtualPages'
import init from './hooks/init'
const { baseUrl } = config
@@ -46,21 +46,11 @@ const createView = (
currentFiles?: VirtualFile[],
index?: number
) => {
index ?? (index = views.length)
views.splice(
index,
0,
reactive<VirtualPage>(
new VirtualPage(component, {
directorys: [...(currentDirectorys ?? [])],
files: [...(currentFiles ?? [])]
})
)
)
views.push(component, currentDirectorys, currentFiles, index)
}
const deleteView = (index: number) => {
views.splice(index, 1)
views.deleteView(index)
if (views.length === 0) {
createView(miaoDirectory, [rootDirectory])
}
@@ -68,14 +58,14 @@ const deleteView = (index: number) => {
// 点击标题是隐藏其他标签或显示其他标签,还挺好用的
const handleClickTitle = (index: number) => {
let count = views.filter((v) => v.visible).length
let op = !views[index].visible || count > 1 ? 'hideOthers' : 'showOthers'
let count = views._views.filter((v) => v.visible).length
let op = !views._views[index].visible || count > 1 ? 'hideOthers' : 'showOthers'
let _index = 0
while (_index < views.length) {
while (_index < views._views.length) {
if (_index !== index) {
views[_index].visible = op === 'hideOthers' ? false : true
views._views[_index].visible = op === 'hideOthers' ? false : true
} else {
views[_index].visible = true
views._views[_index].visible = true
}
_index += 1
}
@@ -113,6 +103,7 @@ const handleMenuSelect = (key: string) => {
}
onMounted(() => {
init()
createView(miaoDirectory, [rootDirectory])
})
</script>
@@ -125,7 +116,7 @@ onMounted(() => {
<transition-group name="tab">
<div
class="tab"
v-for="(view, index) of views"
v-for="(view, index) of views._views"
:key="view.uid">
<span
class="tab-title"
@@ -189,12 +180,13 @@ onMounted(() => {
<transition-group name="page">
<div
class="view-container-item"
v-for="(view, index) of views"
v-for="(view, index) of views._views"
v-show="view.visible"
:key="view.uid">
<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"
:color="view.color"
@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 useMiaoFetchApi from "@/hooks/useMiaoFetchApi";
// import { debounce } from "lodash";
@@ -23,6 +24,10 @@ export class VirtualFile {
get path() {
return `${this.parent.path}${this.name}`;
}
get url() {
return `${config.api.get}${this.parent.path}${this.name}`
}
}
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 generateId from "@/hooks/generateId";
@@ -13,7 +13,7 @@ const getRandomWebSafeColor = (() => {
})();
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.currentDirectorys = initObjects?.directorys ?? [];
this.currentFiles = initObjects?.files ?? [];
@@ -43,7 +43,7 @@ export default class VirtualPage {
if (this._title) {
return this._title;
}
return this.currentDirectorys[0].name;
return this?.currentDirectorys[0]?.name ?? this?.currentFiles[0]?.name ?? "未命名标签";
}
setTitle(title: string) {
@@ -61,3 +61,63 @@ export default class VirtualPage {
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>
<div class="miao-item" draggable="true" ref="itemRef" @dragstart="handleDragStart">
<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>
<Icon class="item-main-icon" v-show="clientWidth > 250"></Icon>
<div class="item-main-info">
@@ -42,6 +42,7 @@ import {
import { computed, onMounted, ref } from 'vue'
import dateFormatter from '@/hooks/dateFormatter'
import { NEllipsis } from 'naive-ui'
import usePluginCenter from '@/hooks/usePluginCenter'
// import useMiaoFetchApi from '@/hooks/useMiaoFetchApi'
// const miaoFetchApi = useMiaoFetchApi()
@@ -50,7 +51,7 @@ const props = defineProps<{
item: VirtualDirectory | VirtualFile
name?: string
color: string
selected: boolean
selectedItem: (VirtualDirectory | VirtualFile)[]
}>()
const emit = defineEmits<{
@@ -61,6 +62,8 @@ const emit = defineEmits<{
onSelected: []
}>()
const pluginCenter = usePluginCenter()
const itemType = ref<'file' | 'directory'>(props.item.type)
const mainRef = ref<HTMLDivElement>()
@@ -70,6 +73,10 @@ const clientWidth = ref<number>(666)
const name = props.name ?? props.item.name
const selected = computed(() => {
return props.selectedItem.includes(props.item)
})
const time = computed(() => {
return dateFormatter(
new Date(props.item.stats.mtimeMs),
@@ -78,6 +85,12 @@ const time = 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 = [
{
label: '重命名',
@@ -98,7 +111,7 @@ const dropDownOptions = computed(() => {
]
)
}
if (props.selected === false) {
if (selected.value === false) {
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
})
@@ -130,6 +147,15 @@ const handleDropdownSelect = (key: string) => {
emit('delete')
} else if (key === 'select') {
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;
}
}
.item-main-selected {
background-color: rgba(0, 128, 207, 0.32);
&: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"
let views: Reactive<VirtualPage[]>
let views: Reactive<VirtualPages>
export default function useViretualPages(){
if(views === undefined) {
views = reactive<VirtualPage[]>([])
// views = reactive<VirtualPage[]>([])
views = reactive(new VirtualPages())
}
return views
}
File diff suppressed because it is too large. Load diff
+6 -11
View File
@@ -43,14 +43,14 @@
<n-scrollbar>
<transition-group name="dirItem">
<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)"
@drag-start="handleItemDragStart" @rename="handleItemRename(dir)"
@on-selected="handleItemSelect(dir)" />
</transition-group>
<transition-group name="dirItem">
<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)"
@delete="handleItemDelete(file)" @drag-start="handleItemDragStart"
@rename="handleItemRename(file)" @on-selected="handleItemSelect(file)" />
@@ -159,23 +159,18 @@ class SelectedItem {
// 已选择的item,用于多选功能
const selectedItem = reactive(new SelectedItem())
const currentObjects = defineModel<VirtualDirectory[]>('currentObjects', {
const currentDirectorys = defineModel<VirtualDirectory[]>('currentDirectorys', {
required: true
})
// 目前展示的Dir
const currentDirectory = computed(() => {
return currentObjects.value[0]
return currentDirectorys.value[0]
})
// 当前组件在views中的序号
const index = computed(() => {
for (let i in views) {
if (views[i].uid === props.id) {
return parseInt(i)
}
}
return 0
return views.getIndex(props.id)
})
// 用于展示的数据,后续会增加排序等操作
@@ -208,7 +203,7 @@ const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => {
} else {
virtualDirectory.update()
}
currentObjects.value[0] = virtualDirectory
currentDirectorys.value[0] = virtualDirectory
}
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>