feat: 添加主页插件支持

This commit is contained in:
539943419 committed 2024-09-30 23:21:05 +08:00
1 parent 1f42785b4a
commit 90a068939d
11 files changed
+168 -66

No files matched your search

+35 -20
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { h, onMounted, provide, reactive, ref, shallowRef, useTemplateRef } from 'vue'
import { computed, onMounted, provide, reactive, ref, shallowRef, useTemplateRef } from 'vue'
import { NIcon, NScrollbar, NDropdown } from 'naive-ui'
import {
EyeOffOutline,
@@ -7,7 +7,8 @@ import {
CloseOutline,
CopyOutline,
EllipsisVertical,
QrCodeOutline
QrCodeOutline,
ExtensionPuzzleOutline
} from '@vicons/ionicons5'
import VirtualDirectory, { VirtualFile } from './class/VirtualDirectory'
import MiaoMask from './components/miaoMask.vue'
@@ -16,7 +17,9 @@ import config from './config'
import useVirtualPages from './hooks/useVirtualPages'
import init from './hooks/init'
import usePluginCenter from './hooks/usePluginCenter'
import PluginCenter from './class/PluginCenter'
import PluginCenter, { PluginGroup } from './class/PluginCenter'
import { renderIcon } from './hooks/miaoTools'
import { DropdownMixedOption } from 'naive-ui/es/dropdown/src/interface'
const { baseUrl } = config
@@ -32,7 +35,7 @@ const modalData = shallowRef<{
const messageProviderRef = useTemplateRef('messageProviderRef')
const views = useVirtualPages()
let pluginCenter: PluginCenter
let pluginCenter = ref<PluginCenter>()
const rootDirectory = reactive(
new VirtualDirectory({
@@ -49,17 +52,18 @@ const rootDirectory = reactive(
// component就是一个vue组件,类似于miaoDirectory
const createView = (
component: any,
name: string,
currentDirectories?: VirtualDirectory[],
currentFiles?: VirtualFile[],
index?: number
) => {
views.push(component, currentDirectories, currentFiles, { index })
views.push(component, name, currentDirectories, currentFiles, { index })
}
const deleteView = (index: number) => {
views.deleteView(index)
if (views.length === 0) {
pluginCenter.usePlugin('miaoDirectory', [rootDirectory], [])
pluginCenter.value && pluginCenter.value.usePlugin('miaoDirectory', [rootDirectory], [])
}
}
@@ -79,22 +83,28 @@ const handleClickTitle = (index: number) => {
}
}
// 顶部菜单相关
function renderIcon(icon: any) {
return () => {
return h(NIcon, null, {
default: () => h(icon)
})
}
}
const openMenuOption = [
const openMenuOption = computed(() => [
{
label: '分享',
key: 'share',
icon: renderIcon(QrCodeOutline)
}
]
}, pluginCenter.value ? {
label: '插件',
key: 'plugin',
children: [
...pluginCenter.value
.getUsablePlugin([], [], {
group: [PluginGroup.mainMenu]
})
.map((v) => ({
label: `${v.name}`,
key: `plugin:${v.key}`,
icon: v.icon ? renderIcon(v.icon) : undefined
}))
],
icon: renderIcon(ExtensionPuzzleOutline)
} : undefined
].filter(v => v) as DropdownMixedOption[])
const handleMenuSelect = async (key: string) => {
if (key === 'share') {
@@ -107,14 +117,18 @@ const handleMenuSelect = async (key: string) => {
errorCorrectionLevel: 'H'
}
}
} else if (key.startsWith('plugin:')) {
const pluginName = key.split(':')[1]
pluginCenter.value && pluginCenter.value.usePlugin(pluginName, [], [])
}
}
onMounted(async () => {
// @ts-ignore
await init(messageProviderRef.value?.alertTip)
pluginCenter = usePluginCenter()
pluginCenter.usePlugin('miaoDirectory', [rootDirectory], [])
pluginCenter.value = usePluginCenter()
// console.log({ pluginCenter })
pluginCenter.value.usePlugin('miaoDirectory', [rootDirectory], [])
})
// @ts-ignore
@@ -146,6 +160,7 @@ provide('rootDirectory', rootDirectory)
<n-icon class="tag-control-icon icon" @click="
createView(
view.component,
view.name,
view.currentDirectories,
view.currentFiles,
index + 1
+17 -10
View File
@@ -2,7 +2,7 @@ import useVirtualPages from "@/hooks/useVirtualPages";
import VirtualDirectory, { VirtualFile } from "./VirtualDirectory";
import { ShallowRef, shallowRef } from "vue";
import { alertTipType, usePluginHooksType } from "@/types/type";
import VirtualPage from "./VirtualPage";
import VirtualPage, { VirtualPages } from "./VirtualPage";
export enum PluginGroup {
default = "default",
@@ -36,7 +36,7 @@ export type Plugin = {
priority: number;
// 判断在未禁用时是否可以使用这个组件
// 使用组件即调用下面的func
filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean;
filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[], VPages: VirtualPages) => boolean;
func: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[], hooks: usePluginHooksType, ...args: any[]) => void;
};
@@ -56,7 +56,7 @@ export default class PluginCenter {
}
/**
* 便捷地注册一个组件为插件,触发时直接用新页面打开
* 便捷地注册一个组件为插件,触发时直接用新页面打开,页面名为key
*/
registerComponent(option: registerComponentOption): void {
const { key, name, getComponent, icon, filter, group = [PluginGroup.default], disable = false, exitConfirm = false, single = false, priority = 0 } = option;
@@ -83,12 +83,13 @@ export default class PluginCenter {
timeout: 2000
});
}
if (single && views.find(_component) !== undefined) {
const view = views.find(_component) as VirtualPage;
const _findRes = views.find(_component) as VirtualPage[];
if (single && _findRes.length !== 0) {
const view = _findRes[0];
view.currentFiles = [...view.currentFiles, ...VFiles];
view.currentDirectories = [...view.currentDirectories, ...VDirectories];
} else {
views.push(_component, VDirectories, VFiles, {
views.push(_component, key, VDirectories, VFiles, {
VirtualPageOption: {
exitConfirm,
allowCopy: single === false
@@ -108,26 +109,32 @@ export default class PluginCenter {
) {
const { group = [PluginGroup.default] } = option ?? {};
const usableList = [];
const views = useVirtualPages();
for (let key of Object.keys(this.pluginsMap)) {
const _plugin = this.pluginsMap[key];
if (!_plugin.disable && _plugin.group.filter(v => group.includes(v)).length > 0 && _plugin.filter(VDirectories, VFiles)) {
if (!_plugin.disable && _plugin.group.filter(v => group.includes(v)).length > 0 && _plugin.filter(VDirectories, VFiles, views)) {
usableList.push(key);
}
}
const res = usableList.map(key => ({
key,
...this.pluginsMap[key]
}))
}));
res.sort((a, b) => b.priority - a.priority);
return res
return res;
}
usePlugin(key: string, VDirectories?: VirtualDirectory[], VFiles?: VirtualFile[], ...args: any[]): void;
// usePlugin(key: string, VPages: VirtualPages, ...args: any[]) : void
usePlugin(key: string, VDirectories?: VirtualDirectory[], VFiles?: VirtualFile[], ...args: any[]) {
this.pluginsMap[key].func(
VDirectories ?? [],
VFiles ?? [],
{
globalAlertTip: this.globalAlertTip
globalAlertTip: this.globalAlertTip,
getViews: useVirtualPages,
getPluginCenter: () => this
},
...args
);
+23 -6
View File
@@ -30,13 +30,15 @@ const getRandomWebSafeColor = (() => {
export default class VirtualPage {
constructor(
component: any,
initObjects?: {
component: Component,
initObjects: {
name: string
directories?: VirtualDirectory[]
files?: VirtualFile[]
option?: VirtualPageOption
}
) {
this.name = initObjects?.name
this.component = shallowRef(component)
this.currentDirectories = initObjects?.directories ?? []
this.currentFiles = initObjects?.files ?? []
@@ -48,7 +50,9 @@ export default class VirtualPage {
this.allowCopy = initObjects?.option?.allowCopy ?? true
}
// 页面使用的组件
component: any
component: Component
// 页面名
name: string
// 是否可见
visible: boolean
// 当前正在使用的文件夹
@@ -135,7 +139,8 @@ export class VirtualPages {
* 推入一个新的页面
*/
push(
component: any,
component: Component,
name: string,
currentDirectories?: VirtualDirectory[],
currentFiles?: VirtualFile[],
option?: ViewsPushOption
@@ -146,6 +151,7 @@ export class VirtualPages {
0,
reactive<VirtualPage>(
new VirtualPage(component, {
name,
directories: [...(currentDirectories ?? [])],
files: [...(currentFiles ?? [])],
option: VirtualPageOption
@@ -163,7 +169,18 @@ export class VirtualPages {
/**
* 返回第一个使用此组件的页面
*/
find(component: Component) {
return this._views.find(v=>v.component === component)
find(component: Component) : VirtualPage[]
/**
* 通过页面名查找
*/
find(name: string) : VirtualPage[]
find(input: unknown) {
console.log(this)
if (typeof input === 'string') {
return this._views.filter(v => v.component.name === input);
} else {
return this._views.filter(v => v.component === input);
}
}
}
+5 -5
View File
@@ -3,7 +3,7 @@ import { Plugin, registerComponentOption } from "@/class/PluginCenter";
import { alertTipType, PluginExportType } from "@/types/type";
const initPlugin = async (globalAlertTip: alertTipType) => {
initPluginCenter(globalAlertTip)
initPluginCenter(globalAlertTip);
const modulesFiles = import.meta.glob("@/plugins/**/export.ts");
const pluginCenter = usePluginCenter();
const promiseArray = [];
@@ -12,11 +12,11 @@ const initPlugin = async (globalAlertTip: alertTipType) => {
new Promise(async resolve => {
const _config = pluginConfig as () => Promise<{
default: registerComponentOption | Plugin;
type: PluginExportType
key?: string
type: PluginExportType;
key?: string;
}>;
const {type, key} = await _config()
if(type === PluginExportType.component) {
const { type, key } = await _config();
if (type === PluginExportType.component) {
// @ts-ignore
resolve(pluginCenter.registerComponent((await _config()).default));
} else if (type === PluginExportType.default) {
@@ -25,3 +25,5 @@ const pluginConfig: Plugin = {
export default pluginConfig;
export const type = PluginExportType.default;
export const key = 'miaoCopy';
+23
View File
@@ -0,0 +1,23 @@
import { Plugin, PluginGroup } from "@/class/PluginCenter";
import fullScreen from "./fullScreen";
import { ExpandOutline } from "@vicons/ionicons5";
import { PluginExportType } from "@/types/type";
import { shallowRef } from "vue";
const pluginConfig: Plugin = {
name: "全屏",
icon: shallowRef(ExpandOutline),
filter: () => {
return true;
},
disable: false,
group: [PluginGroup.mainMenu],
func: fullScreen,
priority: -1
};
export default pluginConfig;
export const type = PluginExportType.default;
export const key = "fullScreen";
+4
View File
@@ -0,0 +1,4 @@
const fullScreen = () => {
document.body.requestFullscreen()
}
export default fullScreen
+18 -17
View File
@@ -1,35 +1,36 @@
import { registerComponentOption } from '@/class/PluginCenter'
import { PluginExportType } from '@/types/type'
import Disc from '@vicons/ionicons5/es/Disc'
import { registerComponentOption } from "@/class/PluginCenter";
import { PluginExportType } from "@/types/type";
import Disc from "@vicons/ionicons5/es/Disc";
const pluginConfig: registerComponentOption = {
key: 'miaoMusicPlayer',
name: '音乐',
key: "miaoMusicPlayer",
name: "音乐",
icon: Disc,
getComponent: async () =>
(await import('./miaoMusicPlayer.vue')).default,
getComponent: async () => (await import("./miaoMusicPlayer.vue")).default,
filter: (vDirs, vFiles) => {
if (vDirs.length === 0 && vFiles.length === 0) {
return true;
}
if (vDirs.length !== 0) {
return false
return false;
}
if (vFiles.length === 0) {
return false
return false;
}
for (let i of vFiles) {
for (let s of ['mp3', 'flac', 'ogg', 'mp4']) {
for (let s of ["mp3", "flac", "ogg", "mp4"]) {
if (i.name.endsWith(s)) {
return true
return true;
}
}
}
return false
return false;
},
disable: false,
single: true,
priority: 5,
}
priority: 5
};
export default pluginConfig
export default pluginConfig;
export const type = PluginExportType.component
export const type = PluginExportType.component;
@@ -0,0 +1,26 @@
import { Plugin, PluginGroup } from "@/class/PluginCenter";
import { PluginExportType } from "@/types/type";
import { shallowRef } from "vue";
import Disc from "@vicons/ionicons5/es/Disc";
const pluginConfig: Plugin = {
name: "音乐",
icon: shallowRef(Disc),
filter: (_, __, views) => {
return views.find("miaoMusicPlayer").length === 0;
},
disable: false,
group: [PluginGroup.mainMenu],
func(_, __, hooks) {
const { getPluginCenter } = hooks;
const PluginCenter = getPluginCenter();
PluginCenter.usePlugin("miaoMusicPlayer", [], []);
},
priority: -1
};
export default pluginConfig;
export const type = PluginExportType.default;
export const key = "musicPlayer-mainMenu";
+13 -6
View File
@@ -1,3 +1,6 @@
import PluginCenter from "@/class/PluginCenter";
import { VirtualPages } from "@/class/VirtualPage";
export type stats = {
atimeMs: number; // 上次访问时间(毫秒)
birthtimeMs: number; // 创建时间(毫秒)
@@ -21,19 +24,23 @@ export type directory = {
parent?: directory;
};
export type alertTipType = ((content: string, options?: AlertTipMessageOptions) => (newContent: string, newOptions?: AlertTipMessageOptions) => void)
export type alertTipType = (content: string, options?: AlertTipMessageOptions) => (newContent: string, newOptions?: AlertTipMessageOptions) => void;
export interface AlertTipMessageOptions {
type?: 'info' | 'success' | 'error'
timeout?: number
id?: number
type?: "info" | "success" | "error";
timeout?: number;
id?: number;
}
export enum PluginExportType {
default = 'default',
component = 'component'
default = "default",
component = "component"
}
export interface usePluginHooksType {
globalAlertTip: alertTipType;
getViews: () => VirtualPages;
getPluginCenter: () => PluginCenter;
}
export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
+2 -2
View File
@@ -2,14 +2,14 @@ import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
// import { viteSingleFile } from 'vite-plugin-singlefile'
// import { visualizer } from 'rollup-plugin-visualizer'
import { visualizer } from 'rollup-plugin-visualizer'
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [
vue(),
// visualizer()
visualizer()
// viteSingleFile()
],
resolve: {