feat: 添加主页插件支持

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

No files matched your search

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