This commit is contained in:
539943419 committed 2024-09-26 17:02:41 +08:00
commit 96ca8fcf4c
32 files changed
+607 -374

No files matched your search

+1
View File
@@ -27,6 +27,7 @@
"aes-js": "^3.1.2", "aes-js": "^3.1.2",
"axios": "^1.7.3", "axios": "^1.7.3",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"copy-to-clipboard": "^3.3.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"naive-ui": "^2.39.0", "naive-ui": "^2.39.0",
+15
View File
@@ -53,6 +53,9 @@ importers:
codemirror: codemirror:
specifier: ^6.0.1 specifier: ^6.0.1
version: 6.0.1(@lezer/common@1.2.1) version: 6.0.1(@lezer/common@1.2.1)
copy-to-clipboard:
specifier: ^3.3.3
version: 3.3.3
lodash: lodash:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
@@ -672,6 +675,9 @@ packages:
console-control-strings@1.1.0: console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
core-js@3.38.1: core-js@3.38.1:
resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==}
@@ -1103,6 +1109,9 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
tr46@0.0.3: tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -1877,6 +1886,10 @@ snapshots:
console-control-strings@1.1.0: console-control-strings@1.1.0:
optional: true optional: true
copy-to-clipboard@3.3.3:
dependencies:
toggle-selection: 1.0.6
core-js@3.38.1: {} core-js@3.38.1: {}
crelt@1.0.6: {} crelt@1.0.6: {}
@@ -2371,6 +2384,8 @@ snapshots:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
toggle-selection@1.0.6: {}
tr46@0.0.3: tr46@0.0.3:
optional: true optional: true
+32 -51
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { h, onMounted, reactive, ref, shallowRef } from 'vue' import { h, 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,
@@ -11,10 +11,12 @@ import {
} 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'
import MiaoMessageProvider from './components/miaoAlertTipProvider.vue'
import config from './config' 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'
const { baseUrl } = config const { baseUrl } = config
@@ -25,8 +27,10 @@ const modalData = shallowRef<{
props: any props: any
}>() }>()
const messageProviderRef = useTemplateRef('messageProviderRef')
const views = useVirtualPages() const views = useVirtualPages()
const pluginCenter = usePluginCenter() let pluginCenter: PluginCenter
const rootDirectory = reactive( const rootDirectory = reactive(
new VirtualDirectory({ new VirtualDirectory({
@@ -105,49 +109,40 @@ const handleMenuSelect = async (key: string) => {
} }
onMounted(async () => { onMounted(async () => {
await init() // @ts-ignore
await init(messageProviderRef.value?.alertTip)
pluginCenter = usePluginCenter()
pluginCenter.usePlugin('miaoDirectory', [rootDirectory], []) pluginCenter.usePlugin('miaoDirectory', [rootDirectory], [])
// createView(miaoDirectory, [rootDirectory]) console.log(rootDirectory.tree)
}) })
// @ts-ignore
provide('globalAlertTip', messageProviderRef.value?.alertTip)
provide('rootDirectory', rootDirectory)
</script> </script>
<template> <template>
<div class="view"> <div class="view">
<miao-message-provider ref="messageProviderRef">
<div class="view-controller"> <div class="view-controller">
<n-scrollbar x-scrollable> <n-scrollbar x-scrollable>
<div class="tabs-container"> <div class="tabs-container">
<transition-group name="tab"> <transition-group name="tab">
<div <div class="tab" v-for="(view, index) of views._views" :key="view.id">
class="tab" <span class="tab-title" :title="view.title" @click="handleClickTitle(index)">
v-for="(view, index) of views._views"
:key="view.id">
<span
class="tab-title"
:title="view.title"
@click="handleClickTitle(index)">
{{ {{
view.title.length > 5 && 0 view.title.length > 5 && 0
? `${view.title.substring(0, 5)}...` ? `${view.title.substring(0, 5)}...`
: view.title : view.title
}} }}
</span> </span>
<div <div class="tab-point" :style="{ backgroundColor: view.color }"></div>
class="tab-point"
:style="{ backgroundColor: view.color }"></div>
<div class="tab-control"> <div class="tab-control">
<n-icon <n-icon class="tag-control-icon icon" @click="view.switchShow()">
class="tag-control-icon icon" <EyeOutline v-show="view.visible" class="icon-inner" />
@click="view.switchShow()"> <EyeOffOutline v-show="!view.visible" class="icon-inner" />
<EyeOutline
v-show="view.visible"
class="icon-inner" />
<EyeOffOutline
v-show="!view.visible"
class="icon-inner" />
</n-icon> </n-icon>
<n-icon <n-icon class="tag-control-icon icon" @click="
class="tag-control-icon icon"
@click="
createView( createView(
view.component, view.component,
view.currentDirectories, view.currentDirectories,
@@ -157,9 +152,7 @@ onMounted(async () => {
"> ">
<CopyOutline /> <CopyOutline />
</n-icon> </n-icon>
<n-icon <n-icon class="tag-control-icon icon" @click="deleteView(index)">
class="tag-control-icon icon"
@click="deleteView(index)">
<CloseOutline class="icon-inner" /> <CloseOutline class="icon-inner" />
</n-icon> </n-icon>
</div> </div>
@@ -169,10 +162,7 @@ onMounted(async () => {
</n-scrollbar> </n-scrollbar>
<div class="view-controller-menu"> <div class="view-controller-menu">
<div class="view-controller-menu-item"> <div class="view-controller-menu-item">
<n-dropdown <n-dropdown trigger="click" :options="openMenuOption" @select="handleMenuSelect">
trigger="click"
:options="openMenuOption"
@select="handleMenuSelect">
<n-icon class="icon"> <n-icon class="icon">
<EllipsisVertical class="icon-inner" /> <EllipsisVertical class="icon-inner" />
</n-icon> </n-icon>
@@ -182,30 +172,20 @@ onMounted(async () => {
</div> </div>
<div class="view-container"> <div class="view-container">
<transition-group name="page"> <transition-group name="page">
<div <div class="view-container-item" v-for="(view, index) of views._views" v-show="view.visible"
class="view-container-item"
v-for="(view, index) of views._views"
v-show="view.visible"
:key="view.id"> :key="view.id">
<component <component :is="view.component" v-model:current-directories="view.currentDirectories"
:is="view.component" v-model:current-files="view.currentFiles" :id="view.id" :color="view.color" :view="view"
v-model:current-directories="view.currentDirectories" :views="views" @exit="deleteView(index)"></component>
v-model:current-files="view.currentFiles"
:id="view.id"
:color="view.color"
:view="view"
:views="views"
@exit="deleteView(index)"></component>
</div> </div>
</transition-group> </transition-group>
</div> </div>
</miao-message-provider>
</div> </div>
<!-- 模态框展示,用来显示分享二维码,设置菜单之类的东西 --> <!-- 模态框展示,用来显示分享二维码,设置菜单之类的东西 -->
<MiaoMask v-model:show="showModal" @click="showModal = false"> <MiaoMask v-model:show="showModal" @click="showModal = false">
<component <component @click="(e: any) => e.stopPropagation()" :is="modalData?.component" v-bind="modalData?.props">
@click="(e: any) => e.stopPropagation()" </component>
:is="modalData?.component"
v-bind="modalData?.props"></component>
</MiaoMask> </MiaoMask>
</template> </template>
@@ -369,6 +349,7 @@ $tag-width: 170px;
.view { .view {
.view-controller { .view-controller {
.tabs-container { .tabs-container {
.tab-move, .tab-move,
.tab-enter-active { .tab-enter-active {
transition: all 0.2s ease; transition: all 0.2s ease;
+60 -65
View File
@@ -1,124 +1,119 @@
import useVirtualPages from '@/hooks/useVirtualPages' 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";
export enum PluginGroup { export enum PluginGroup {
default = 'default', default = "default",
mainMenu = 'mainMenu' mainMenu = "mainMenu"
} }
export interface registerComponentOption { export interface registerComponentOption {
key: string key: string;
name: string name: string;
icon?: any icon?: any;
getComponent: () => Promise<any> getComponent: () => Promise<any>;
group?: PluginGroup[] group?: PluginGroup[];
filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean;
disable?: boolean disable?: boolean;
exitConfirm?: boolean exitConfirm?: boolean;
} }
type Plugin = { export type Plugin = {
// 用于显示的名字 // 用于显示的名字
name: string name: string;
// 代表插件的icon // 代表插件的icon
icon?: ShallowRef<any> icon?: ShallowRef<any>;
// 是否禁用插件 // 是否禁用插件
disable: boolean disable: boolean;
// 插件所属的group,用于区分插件使用的场合 // 插件所属的group,用于区分插件使用的场合
group: PluginGroup[] group: PluginGroup[];
// 判断在未禁用时是否可以使用这个组件 // 判断在未禁用时是否可以使用这个组件
// 使用组件即调用下面的func // 使用组件即调用下面的func
filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean;
func: ( func: (
VDirectories: VirtualDirectory[], VDirectories: VirtualDirectory[],
VFiles: VirtualFile[], VFiles: VirtualFile[],
hooks: usePluginHooksType,
...args: any[] ...args: any[]
) => void ) => void;
} };
export default class PluginCenter { export default class PluginCenter {
constructor() { constructor(globalAlertTip: alertTipType) {
this.pluginsMap = {} this.pluginsMap = {};
this.globalAlertTip = globalAlertTip;
} }
pluginsMap: { pluginsMap: {
[key: string]: Plugin [key: string]: Plugin;
} };
globalAlertTip: alertTipType;
register(key: string, plugin: Plugin) { register(key: string, plugin: Plugin) {
this.pluginsMap[key] = plugin this.pluginsMap[key] = plugin;
} }
/** /**
* 便捷地注册一个组件为插件,触发时直接用新页面打开 * 便捷地注册一个组件为插件,触发时直接用新页面打开
*/ */
registerComponent(option: registerComponentOption): void { registerComponent(option: registerComponentOption): void {
const { const { key, name, getComponent, icon, filter, group = [PluginGroup.default], disable = false, exitConfirm = false } = option;
key, let _component: any;
name, const views = useVirtualPages();
getComponent,
icon,
filter,
group = [PluginGroup.default],
disable = false,
exitConfirm = false
} = option
let _component: any
const views = useVirtualPages()
this.register(key, { this.register(key, {
name, name,
filter, filter,
icon: shallowRef(icon), icon: shallowRef(icon),
group, group,
disable, disable,
func: async ( func: async (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => {
VDirectories: VirtualDirectory[],
VFiles: VirtualFile[]
) => {
if (filter(VDirectories, VFiles) === false) { if (filter(VDirectories, VFiles) === false) {
return return;
}
if (_component === undefined) {
const setAlertTip = key !== "miaoDirectory" && this.globalAlertTip(`加载 ${name} 插件中`);
_component = await getComponent();
setAlertTip &&
setAlertTip(`加载 ${name} 插件完成`, {
type: "success",
timeout: 2000
});
} }
if (_component === undefined) _component = await getComponent()
views.push(_component, VDirectories, VFiles, { views.push(_component, VDirectories, VFiles, {
VirtualPageOption: { VirtualPageOption: {
exitConfirm exitConfirm
} }
}) });
} }
}) });
} }
getUsablePlugin( getUsablePlugin(
VDirectories: VirtualDirectory[], VDirectories: VirtualDirectory[],
VFiles: VirtualFile[], VFiles: VirtualFile[],
option?: { option?: {
group?: PluginGroup[] group?: PluginGroup[];
} }
) { ) {
const { group = [PluginGroup.default] } = option ?? {} const { group = [PluginGroup.default] } = option ?? {};
const usableList = [] const usableList = [];
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 ( if (!_plugin.disable && _plugin.group.filter(v => group.includes(v)).length > 0 && _plugin.filter(VDirectories, VFiles)) {
!_plugin.disable && usableList.push(key);
_plugin.group.filter((v) => group.includes(v)).length > 0 &&
_plugin.filter(VDirectories, VFiles)
) {
usableList.push(key)
} }
} }
return usableList.map((key) => ({ return usableList.map(key => ({
key, key,
...this.pluginsMap[key] ...this.pluginsMap[key]
})) }));
} }
usePlugin( usePlugin(key: string, VDirectories?: VirtualDirectory[], VFiles?: VirtualFile[], ...args: any[]) {
key: string, this.pluginsMap[key].func(VDirectories ?? [], VFiles ?? [], {
VDirectories?: VirtualDirectory[], globalAlertTip: this.globalAlertTip
VFiles?: VirtualFile[], }, ...args);
...args: any[]
) {
this.pluginsMap[key].func(VDirectories ?? [], VFiles ?? [], ...args)
} }
} }
+34 -1
View File
@@ -6,6 +6,21 @@ import type { stats, file, directory } from "@/types/type.ts";
const miaoFetchApi = useMiaoFetchApi(); const miaoFetchApi = useMiaoFetchApi();
export interface Tree {
name: string;
type: 'directory';
path: string;
stats: stats;
files?: {
name: string;
type: 'file';
size: number;
stats: stats;
url: string;
}[];
directories?: Tree[];
}
export class VirtualFile { export class VirtualFile {
constructor(info: file, parent: VirtualDirectory) { constructor(info: file, parent: VirtualDirectory) {
this.name = info.name; this.name = info.name;
@@ -26,7 +41,7 @@ export class VirtualFile {
} }
get url() { get url() {
return `${config.api.get}${this.parent.path}${this.name}` return `${config.api.get}${this.parent.path}${this.name}`;
} }
} }
@@ -96,6 +111,24 @@ class VirtualDirectory {
return this.parent.getParent(layer - 1); return this.parent.getParent(layer - 1);
} }
get tree(): Tree {
return {
name: this.name,
type: this.type,
path: this.path,
stats: this.stats,
files:
this.files?.map(v => ({
name: v.name,
type: v.type,
size: v.size,
stats: v.stats,
url: v.url
})) ?? [],
directories: this.directories?.map(v => v.tree) ?? []
};
}
updateContent(content: (file | directory)[]) { updateContent(content: (file | directory)[]) {
this.files = this.files ?? []; this.files = this.files ?? [];
this.directories = this.directories ?? []; this.directories = this.directories ?? [];
@@ -18,12 +18,7 @@ import generateId from '@/hooks/generateId';
import { NIcon } from 'naive-ui'; import { NIcon } from 'naive-ui';
import { computed, onMounted, ref, shallowReactive } from 'vue'; import { computed, onMounted, ref, shallowReactive } from 'vue';
import { InformationCircle, CheckmarkCircle, CloseCircle } from '@vicons/ionicons5' import { InformationCircle, CheckmarkCircle, CloseCircle } from '@vicons/ionicons5'
import { AlertTipMessageOptions } from '@/types/type';
interface MessageOptions {
type?: 'info' | 'success' | 'error'
timeout?: number
id?: number
}
interface Message { interface Message {
icon: any, icon: any,
@@ -48,7 +43,7 @@ const messageList = computed(() => {
...messageMap[v], ...messageMap[v],
})) }))
}) })
const message = (content: string, options?: MessageOptions) => { const alertTip = (content: string, options?: AlertTipMessageOptions) => {
let timer: any let timer: any
const { timeout = 0, type = 'info', id = generateId() } = options ?? {} const { timeout = 0, type = 'info', id = generateId() } = options ?? {}
if (timeout > 0) { if (timeout > 0) {
@@ -63,10 +58,10 @@ const message = (content: string, options?: MessageOptions) => {
content, content,
timer timer
} }
return (newContent: string, newOptions?: MessageOptions) => { return (newContent: string, newOptions?: AlertTipMessageOptions) => {
newOptions = newOptions ?? {} newOptions = newOptions ?? {}
newOptions.id = id newOptions.id = id
message(newContent, newOptions) alertTip(newContent, newOptions)
} }
} }
@@ -86,7 +81,7 @@ onMounted(async () => {
}) })
defineExpose({ message }); defineExpose({ alertTip });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -105,6 +100,7 @@ defineExpose({ message });
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
overflow: visible; overflow: visible;
z-index: 70582140539943419;
.miao-message { .miao-message {
display: flex; display: flex;
+15
View File
@@ -0,0 +1,15 @@
import VirtualDirectory, { Tree, VirtualFile } from "@/class/VirtualDirectory";
/**
* 通过VirtualDirectory的.tree重建文件夹树
* @param {any} data:Tree
* @returns {any}
*/
const buildFromTree = (data: Tree) => {
const root = new VirtualDirectory(data)
root.files = data.files?.map(v=>new VirtualFile(v, root))
root.directories = data.directories?.map(v=>buildFromTree(v))
return root
}
export default buildFromTree;
+3 -2
View File
@@ -1,5 +1,6 @@
import { alertTipType } from "@/types/type";
import initPlugin from "./init/initPlugin"; import initPlugin from "./init/initPlugin";
export default async function init() { export default async function init(globalAlertTip: alertTipType) {
await initPlugin() await initPlugin(globalAlertTip)
} }
+26 -10
View File
@@ -1,16 +1,32 @@
import usePluginCenter, { initPluginCenter } from "@/hooks/usePluginCenter";
import { Plugin, registerComponentOption } from "@/class/PluginCenter";
import { alertTipType, PluginExportType } from "@/types/type";
import usePluginCenter from '@/hooks/usePluginCenter' const initPlugin = async (globalAlertTip: alertTipType) => {
import { registerComponentOption } from '@/class/PluginCenter' initPluginCenter(globalAlertTip)
const modulesFiles = import.meta.glob("@/plugins/**/export.ts");
const initPlugin = async () => { const pluginCenter = usePluginCenter();
const modulesFiles = import.meta.glob('@/plugins/**/export.ts') const promiseArray = [];
const pluginCenter = usePluginCenter()
for (let pluginConfig of Object.values(modulesFiles)) { for (let pluginConfig of Object.values(modulesFiles)) {
promiseArray.push(
new Promise(async resolve => {
const _config = pluginConfig as () => Promise<{ const _config = pluginConfig as () => Promise<{
default: registerComponentOption default: registerComponentOption | Plugin;
}> type: PluginExportType
pluginCenter.registerComponent((await _config()).default) key?: string
}>;
const {type, key} = await _config()
if(type === PluginExportType.component) {
// @ts-ignore
resolve(pluginCenter.registerComponent((await _config()).default));
} else if (type === PluginExportType.default) {
// @ts-ignore
resolve(pluginCenter.register(key as string, (await _config()).default));
} }
})
);
} }
await Promise.all(promiseArray);
};
export default initPlugin export default initPlugin;
+127 -144
View File
@@ -1,4 +1,4 @@
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory' import VirtualDirectory, { VirtualFile } from "@/class/VirtualDirectory";
import { import {
getOption, getOption,
getResponse, getResponse,
@@ -8,88 +8,89 @@ import {
queryOption, queryOption,
renameOption, renameOption,
cutOption, cutOption,
mkdirOption mkdirOption,
} from '@/types/fetch' getFileOption
import Config from '@/config' } from "@/types/fetch";
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios' import Config from "@/config";
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
export const useMiaoFetch = (option?: miaoFetchConfig) => { export const useMiaoFetch = (option?: miaoFetchConfig) => {
const retry = option?.retry ?? 0 const retry = option?.retry ?? 0;
const controller = new AbortController() const controller = new AbortController();
let flag = true let flag = true;
return { return {
miaoFetch: async ( miaoFetch: async (config: AxiosRequestConfig<any>): Promise<AxiosResponse<any, any>> => {
config: AxiosRequestConfig<any> let counter = 0;
): Promise<AxiosResponse<any, any>> => { let response: any;
let counter = 0 let errList = [];
let response: any
let errList = []
while (flag && counter <= retry) { while (flag && counter <= retry) {
try { try {
response = await axios({ response = await axios({
...config, ...config,
signal: controller.signal signal: controller.signal
}) });
return response return response;
} catch (_e) { } catch (_e) {
errList.push(_e) errList.push(_e);
counter += 1 counter += 1;
} }
} }
throw errList throw errList;
}, },
/** /**
* 强制终止miaoFetch * 强制终止miaoFetch
*/ */
abortMiaoFetch: () => { abortMiaoFetch: () => {
flag = false flag = false;
controller.abort() controller.abort();
}
}
} }
};
};
const miaoFetchApi = { const miaoFetchApi = {
/** /**
* 获取对应文件夹下的数据 * 获取对应文件夹下的数据
*/ */
async get( async get(vDirectory: VirtualDirectory, option?: getOption): Promise<getResponse> {
vDirectory: VirtualDirectory, const { baseUrl, api } = Config;
option?: getOption const url = `${baseUrl}${api.get}${vDirectory.path}`;
): Promise<getResponse> { const retry = option?.retry ?? 0;
const { baseUrl, api } = Config
const url = `${baseUrl}${api.get}${vDirectory.path}`
const retry = option?.retry ?? 0
const { miaoFetch } = useMiaoFetch({ const { miaoFetch } = useMiaoFetch({
retry retry
}) });
const response = await miaoFetch({ const response = await miaoFetch({
url url
}) });
return response.data return response.data;
}, },
/** /**
* 获取对应文件下的数据 * 获取对应文件下的数据
*/ */
async getFile( getFile(vFile: VirtualFile, option?: getFileOption) {
vFile: VirtualFile,
option?: getOption
) {
// const { baseUrl, api } = Config // const { baseUrl, api } = Config
const url = vFile.url const url = vFile.url;
const retry = option?.retry ?? 0 const retry = option?.retry ?? 0;
const axiosOption = option?.axiosOption ?? {} const axiosOption = option?.axiosOption ?? {};
const { miaoFetch } = useMiaoFetch({ const { miaoFetch, abortMiaoFetch: abort } = useMiaoFetch({
retry retry
}) });
const response = await miaoFetch({ const response = miaoFetch({
url, url,
responseType: 'arraybuffer', responseType: "arraybuffer",
onDownloadProgress(progressEvent) {
option && option.onProcess && option.onProcess(progressEvent.total ? progressEvent.loaded / progressEvent.total : -1);
},
...axiosOption ...axiosOption
}) });
return new Uint8Array(response.data) return {
response: new Promise<Uint8Array>(async resolve => {
resolve(new Uint8Array((await response).data));
}),
abort
};
}, },
/** /**
@@ -97,195 +98,177 @@ const miaoFetchApi = {
* 正常上传文件请使用uploadQueue * 正常上传文件请使用uploadQueue
*/ */
upload(data: File, targetVDir: VirtualDirectory, option?: uploadOption) { upload(data: File, targetVDir: VirtualDirectory, option?: uploadOption) {
const { baseUrl, api } = Config const { baseUrl, api } = Config;
const url = `${baseUrl}${api.upload}${targetVDir.path}` const url = `${baseUrl}${api.upload}${targetVDir.path}`;
const retry = option?.retry ?? 0 const retry = option?.retry ?? 0;
const { miaoFetch, abortMiaoFetch } = useMiaoFetch({ const { miaoFetch, abortMiaoFetch } = useMiaoFetch({
retry retry
}) });
const sizeTotal = data.size const sizeTotal = data.size;
const opType = option?.type ?? 'write' const opType = option?.type ?? "write";
const formData = new FormData() const formData = new FormData();
formData.append('file', data) formData.append("file", data);
// 必须这样传,不然中文乱码 // 必须这样传,不然中文乱码
formData.append('fileName', data.name) formData.append("fileName", data.name);
formData.append('operateType', opType) formData.append("operateType", opType);
return { return {
abort: () => abortMiaoFetch(), abort: () => abortMiaoFetch(),
response: new Promise<{ response: new Promise<{
message: 'success' | 'failed' message: "success" | "failed";
error?: string error?: string;
}>(async (resolve) => { }>(async resolve => {
const response = await miaoFetch({ const response = await miaoFetch({
url, url,
method: 'post', method: "post",
data: formData, data: formData,
onUploadProgress(progressEvent) { onUploadProgress(progressEvent) {
option && option && option.onProcess && option.onProcess(progressEvent.loaded / sizeTotal);
option.onProcess &&
option.onProcess(progressEvent.loaded / sizeTotal)
} }
});
resolve(response.data);
}) })
resolve(response.data) };
})
}
}, },
/** /**
* 在目标文件夹下创建新的文件夹 * 在目标文件夹下创建新的文件夹
*/ */
mkdir( mkdir(targetVDir: VirtualDirectory, newFolderName: string, option?: mkdirOption) {
targetVDir: VirtualDirectory, const { baseUrl, api } = Config;
newFolderName: string, const url = `${baseUrl}${api.mkdir}${targetVDir.path}`;
option?: mkdirOption
) {
const { baseUrl, api } = Config
const url = `${baseUrl}${api.mkdir}${targetVDir.path}`
const { miaoFetch } = useMiaoFetch({ const { miaoFetch } = useMiaoFetch({
retry: option?.retry ?? 0 retry: option?.retry ?? 0
}) });
return new Promise<{ return new Promise<{
message: 'success' | 'failed' message: "success" | "failed";
error?: string error?: string;
}>(async (resolve) => { }>(async resolve => {
const response = await miaoFetch({ const response = await miaoFetch({
url, url,
method: 'post', method: "post",
data: { data: {
folderName: newFolderName folderName: newFolderName
} }
}) });
resolve(response.data) resolve(response.data);
}) });
}, },
/** /**
* 在目标文件夹下创建新的空文件,实际上是通过upload空文件实现 * 在目标文件夹下创建新的空文件,实际上是通过upload空文件实现
*/ */
mkFile( mkFile(targetVDir: VirtualDirectory, newFileName: string, option?: uploadOption) {
targetVDir: VirtualDirectory, const emptyBlob = new Blob([]);
newFileName: string,
option?: uploadOption
) {
const emptyBlob = new Blob([])
const emptyFile = new File([emptyBlob], newFileName, { const emptyFile = new File([emptyBlob], newFileName, {
type: 'text/plain' type: "text/plain"
}) });
return this.upload(emptyFile, targetVDir, option) return this.upload(emptyFile, targetVDir, option);
}, },
/** /**
* 删除对应的文件或文件夹,若为文件夹会递归删除 * 删除对应的文件或文件夹,若为文件夹会递归删除
*/ */
delete(target: VirtualFile | VirtualDirectory, option?: deleteOption) { delete(target: VirtualFile | VirtualDirectory, option?: deleteOption) {
const { baseUrl, api } = Config const { baseUrl, api } = Config;
const url = `${baseUrl}${api.delete}${target.path}` const url = `${baseUrl}${api.delete}${target.path}`;
const retry = option?.retry ?? 0 const retry = option?.retry ?? 0;
const { miaoFetch } = useMiaoFetch({ const { miaoFetch } = useMiaoFetch({
retry retry
}) });
return { return {
response: new Promise<{ response: new Promise<{
eventId: number eventId: number;
}>(async (resolve) => { }>(async resolve => {
const response = await miaoFetch({ const response = await miaoFetch({
url, url,
method: 'post' method: "post"
});
resolve(response.data);
}) })
resolve(response.data) };
})
}
}, },
/** /**
* 重命名对应的文件或文件夹 * 重命名对应的文件或文件夹
*/ */
rename( rename(target: VirtualFile | VirtualDirectory, newName: string, option?: renameOption) {
target: VirtualFile | VirtualDirectory, const { baseUrl, api } = Config;
newName: string, const url = `${baseUrl}${api.rename}${target.path}`;
option?: renameOption const retry = option?.retry ?? 0;
) {
const { baseUrl, api } = Config
const url = `${baseUrl}${api.rename}${target.path}`
const retry = option?.retry ?? 0
const { miaoFetch } = useMiaoFetch({ const { miaoFetch } = useMiaoFetch({
retry retry
}) });
return { return {
response: new Promise<{ response: new Promise<{
eventId: number eventId: number;
}>(async (resolve) => { }>(async resolve => {
const response = await miaoFetch({ const response = await miaoFetch({
url, url,
method: 'post', method: "post",
data: { newName } data: { newName }
});
resolve(response.data);
}) })
resolve(response.data) };
})
}
}, },
/** /**
* 剪切文件或文件夹 * 剪切文件或文件夹
*/ */
cut( cut(target: VirtualFile | VirtualDirectory, to: VirtualDirectory, option?: cutOption) {
target: VirtualFile | VirtualDirectory, const newPath = to.path;
to: VirtualDirectory, const { baseUrl, api } = Config;
option?: cutOption const url = `${baseUrl}${api.cut}${target.path}`;
) { const retry = option?.retry ?? 0;
const newPath = to.path
const { baseUrl, api } = Config
const url = `${baseUrl}${api.cut}${target.path}`
const retry = option?.retry ?? 0
const { miaoFetch } = useMiaoFetch({ const { miaoFetch } = useMiaoFetch({
retry retry
}) });
return { return {
response: new Promise<{ response: new Promise<{
eventId: number eventId: number;
}>(async (resolve) => { }>(async resolve => {
const response = await miaoFetch({ const response = await miaoFetch({
url, url,
method: 'post', method: "post",
data: { newPath } data: { newPath }
});
resolve(response.data);
}) })
resolve(response.data) };
})
}
}, },
/** /**
* 查询服务端操作事件的进展 * 查询服务端操作事件的进展
*/ */
query(eventId: number, option?: queryOption) { query(eventId: number, option?: queryOption) {
const { baseUrl, api } = Config const { baseUrl, api } = Config;
const url = `${baseUrl}${api.query}` const url = `${baseUrl}${api.query}`;
const retry = option?.retry ?? 0 const retry = option?.retry ?? 0;
const { miaoFetch } = useMiaoFetch({ const { miaoFetch } = useMiaoFetch({
retry retry
}) });
return { return {
response: new Promise<{ response: new Promise<{
status: 'process' | 'success' | 'failed' status: "process" | "success" | "failed";
error?: unknown error?: unknown;
}>(async (resolve) => { }>(async resolve => {
const response = await miaoFetch({ const response = await miaoFetch({
url, url,
method: 'get', method: "get",
params: { params: {
id: eventId id: eventId
} }
});
resolve(response.data);
}) })
resolve(response.data) };
})
}
}
} }
};
export default function useMiaoFetchApi() { export default function useMiaoFetchApi() {
return miaoFetchApi return miaoFetchApi;
} }
+6 -2
View File
@@ -1,11 +1,15 @@
import PluginCenter from "@/class/PluginCenter"; import PluginCenter from "@/class/PluginCenter";
import { alertTipType } from "@/types/type";
import { reactive } from "vue"; import { reactive } from "vue";
let pluginCenter: PluginCenter let pluginCenter: PluginCenter
export default function usePluginCenter(){ export const initPluginCenter = (globalAlertTip: alertTipType) => {
if(!pluginCenter) { if(!pluginCenter) {
pluginCenter = reactive(new PluginCenter()) pluginCenter = reactive(new PluginCenter(globalAlertTip))
} }
}
export default function usePluginCenter(){
return pluginCenter return pluginCenter
} }
+16
View File
@@ -0,0 +1,16 @@
import { VirtualFile } from "@/class/VirtualDirectory"
import config from "@/config"
import { usePluginHooksType } from "@/types/type"
import copy2Clipboard from 'copy-to-clipboard'
const copy = (__: any, vFiles: VirtualFile[], hooks: usePluginHooksType) => {
const { globalAlertTip } = hooks
const vFile = vFiles[0]
console.log(vFile.url)
copy2Clipboard(`${config.baseUrl}${vFile.url}`)
globalAlertTip(`${vFile.name}的链接已复制至剪切板`, {
type: 'info',
timeout: 2000
})
}
export default copy
@@ -0,0 +1,26 @@
import { Plugin, PluginGroup } from "@/class/PluginCenter";
import Copy from "./copy";
import { CopyOutline } from "@vicons/ionicons5";
import { PluginExportType } from "@/types/type";
import { shallowRef } from "vue";
const pluginConfig: Plugin = {
name: "复制链接",
icon: shallowRef(CopyOutline),
filter: (vDirs, vFiles) => {
if (vDirs.length !== 0) {
return false;
}
if (vFiles.length !== 1) {
return false;
}
return true;
},
disable: false,
group: [PluginGroup.default],
func: Copy
};
export default pluginConfig;
export const type = PluginExportType.default;
+3
View File
@@ -1,5 +1,6 @@
import { registerComponentOption } from '@/class/PluginCenter' import { registerComponentOption } from '@/class/PluginCenter'
import { CodeOutline } from '@vicons/ionicons5' import { CodeOutline } from '@vicons/ionicons5'
import { PluginExportType } from '@/types/type'
const pluginConfig: registerComponentOption = { const pluginConfig: registerComponentOption = {
key: 'miaoCodeMirror', key: 'miaoCodeMirror',
@@ -40,3 +41,5 @@ const pluginConfig: registerComponentOption = {
} }
export default pluginConfig export default pluginConfig
export const type = PluginExportType.component
+8 -15
View File
@@ -1,18 +1,11 @@
<template> <template>
<miao-message-provider ref="miaoMessage"> <miao-message-provider ref="miaoMessage">
<div <div class="codemirror-container" ref="rootRef" :class="`codemirror-container-${theme}`">
class="codemirror-container"
ref="rootRef"
:class="`codemirror-container-${theme}`">
<div class="codemirror-container-top"> <div class="codemirror-container-top">
<div <div class="codemirror-container-btn codemirror-container-btn-save" @click="handleSave">
class="codemirror-container-btn codemirror-container-btn-save"
@click="handleSave">
保存 保存
</div> </div>
<div <div class="codemirror-container-btn codemirror-container-btn-save" @click="handleReset">
class="codemirror-container-btn codemirror-container-btn-save"
@click="handleReset">
恢复到上次保存 恢复到上次保存
</div> </div>
</div> </div>
@@ -26,7 +19,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import miaoMessageProvider from '@/components/miaoMessageProvider.vue' import miaoMessageProvider from '@/components/miaoAlertTipProvider.vue'
import { VirtualFile } from '@/class/VirtualDirectory' import { VirtualFile } from '@/class/VirtualDirectory'
import useMiaoFetchApi from '@/hooks/useMiaoFetchApi' import useMiaoFetchApi from '@/hooks/useMiaoFetchApi'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
@@ -53,7 +46,7 @@ const theme = ref<'light' | 'dark'>('light')
const handleSave = async () => { const handleSave = async () => {
if (bakData.value === codeData.value || !vFile.value) { if (bakData.value === codeData.value || !vFile.value) {
miaoMessage.value?.message('内容未改动', { miaoMessage.value?.alertTip('内容未改动', {
timeout: 3000, timeout: 3000,
type: 'info' type: 'info'
}) })
@@ -69,7 +62,7 @@ const handleSave = async () => {
const { response } = miaoFetchApi.upload(file, parentDir) const { response } = miaoFetchApi.upload(file, parentDir)
false && miaoFetchApi.upload(file_bak, parentDir) false && miaoFetchApi.upload(file_bak, parentDir)
await response await response
miaoMessage.value?.message('保存成功', { miaoMessage.value?.alertTip('保存成功', {
timeout: 5000, timeout: 5000,
type: 'success' type: 'success'
}) })
@@ -152,13 +145,13 @@ onMounted(async () => {
vFile.value = currentFiles.value[0] vFile.value = currentFiles.value[0]
const fileName = currentFiles.value[0].name const fileName = currentFiles.value[0].name
codeData.value = decoder.decode( codeData.value = decoder.decode(
await miaoFetchApi.getFile(currentFiles.value[0], { await (miaoFetchApi.getFile(currentFiles.value[0], {
axiosOption: { axiosOption: {
headers: { headers: {
'Cache-Control': 'no-cache' 'Cache-Control': 'no-cache'
} }
} }
}) }).response)
) )
bakData.value = codeData.value bakData.value = codeData.value
extensions.value = [...(await getLangExtensions(fileName))] extensions.value = [...(await getLangExtensions(fileName))]
+4
View File
@@ -1,5 +1,6 @@
// @ts-nocheck // @ts-nocheck
import { registerComponentOption } from '@/class/PluginCenter' import { registerComponentOption } from '@/class/PluginCenter'
import { PluginExportType } from '@/types/type'
const pluginConfig: registerComponentOption = { const pluginConfig: registerComponentOption = {
key: 'miaoCrypto', key: 'miaoCrypto',
@@ -16,6 +17,9 @@ const pluginConfig: registerComponentOption = {
} }
return vFiles[0].name.endsWith('txt') return vFiles[0].name.endsWith('txt')
}, },
disable: true,
} }
export default pluginConfig export default pluginConfig
export const type = PluginExportType.component
+53 -21
View File
@@ -2,20 +2,15 @@
<miao-drop-handler @on-virtual-files="handleDropVirtualFiles"> <miao-drop-handler @on-virtual-files="handleDropVirtualFiles">
<div class="miaoCrypto-container"> <div class="miaoCrypto-container">
<div class="miaoCrypto-container-encrypt miaoCrypto-container-item"> <div class="miaoCrypto-container-encrypt miaoCrypto-container-item">
<n-scrollbar style="max-height: 100%"> <n-scrollbar style="max-height: 100%">
<div <div v-for="item of workItemList" :key="item.id" class="miaoCrypto-container-item-item">
v-for="item of workItemList" <div class="miaoCrypto-container-item-percentBar" :style="{
class="miaoCrypto-container-item-item">
<div
class="miaoCrypto-container-item-percentBar"
:style="{
zIndex: 0, zIndex: 0,
width: item.buffer ? `100%` : `0%`, width: item.getBufferProgress !== -1 ? `${item.getBufferProgress * 100}%` : `0%`,
backgroundColor: 'blue' backgroundColor: 'blue'
}"></div> }"></div>
<div <div class="miaoCrypto-container-item-percentBar" :style="{
class="miaoCrypto-container-item-percentBar"
:style="{
zIndex: 1, zIndex: 1,
width: item.status === 'finish' ? `100%` : `0%`, width: item.status === 'finish' ? `100%` : `0%`,
backgroundColor: 'green' backgroundColor: 'green'
@@ -24,15 +19,14 @@
</div> </div>
</n-scrollbar> </n-scrollbar>
</div> </div>
<div <!-- <div class="miaoCrypto-container-decrypt miaoCrypto-container-item"></div> -->
class="miaoCrypto-container-decrypt miaoCrypto-container-item"></div>
</div> </div>
</miao-drop-handler> </miao-drop-handler>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { NScrollbar } from 'naive-ui' import { NScrollbar } from 'naive-ui'
import { ref } from 'vue' import { onUnmounted, ref, shallowReactive } from 'vue'
import aes_ctr_worker from './hooks/AES' import aes_ctr_worker from './hooks/AES'
import { VirtualFile } from '@/class/VirtualDirectory' import { VirtualFile } from '@/class/VirtualDirectory'
import miaoDropHandler from '@/components/miaoDropHandler.vue' import miaoDropHandler from '@/components/miaoDropHandler.vue'
@@ -44,9 +38,12 @@ import { computed } from 'vue'
const miaoFetchApi = useMiaoFetchApi() const miaoFetchApi = useMiaoFetchApi()
interface workItem { interface workItem {
status: 'wait' | 'process' | 'finish' id: number
status: 'wait' | 'process' | 'finish' | 'error'
buffer?: Uint8Array buffer?: Uint8Array
getBuffer: () => Promise<Uint8Array> getBuffer: () => Promise<Uint8Array>
getBufferProgress: number
getBufferAbort?: () => void
isGettingBuffer: boolean isGettingBuffer: boolean
name: string name: string
} }
@@ -55,14 +52,16 @@ class Work {
constructor(func: (key: string, data: Uint8Array) => Promise<Uint8Array>) { constructor(func: (key: string, data: Uint8Array) => Promise<Uint8Array>) {
this.queue = [] this.queue = []
this.func = func this.func = func
this.abortLoop = false
} }
queue: workItem[] queue: workItem[]
func: (key: string, data: Uint8Array) => Promise<Uint8Array> func: (key: string, data: Uint8Array) => Promise<Uint8Array>
abortLoop: boolean
get isBusy() { get isBusy() {
return this.queue.filter((v) => v.status === 'process').length > 0 return this.queue.filter((v) => v.status === 'process').length > 0
} }
async funcLoop() { async funcLoop() {
if (this.isBusy) { if (this.isBusy || this.abortLoop) {
return return
} }
const currentItem = this.queue.filter((v) => v.status === 'wait')[0] const currentItem = this.queue.filter((v) => v.status === 'wait')[0]
@@ -70,20 +69,28 @@ class Work {
return return
} }
currentItem.status = 'process' currentItem.status = 'process'
try {
currentItem.buffer = await this.func(key.value, currentItem.buffer) currentItem.buffer = await this.func(key.value, currentItem.buffer)
currentItem.status = 'finish' currentItem.status = 'finish'
} catch (e) {
currentItem.status = 'error'
}
this.funcLoop() this.funcLoop()
} }
async getBufferLoop() { async getBufferLoop() {
const _list = this.queue.filter( const _list = this.queue.filter(
(v) => v.isGettingBuffer === false && v.buffer === undefined (v) => v.isGettingBuffer === false && v.buffer === undefined
) )
if (_list.length === 0) { if (_list.length === 0 || this.abortLoop) {
return return
} }
const item = _list[0] const item = _list[0]
item.isGettingBuffer = true item.isGettingBuffer = true
try {
item.buffer = await item.getBuffer() item.buffer = await item.getBuffer()
} catch(e) {
item.status = 'error'
}
item.isGettingBuffer = false item.isGettingBuffer = false
this.funcLoop() this.funcLoop()
this.getBufferLoop() this.getBufferLoop()
@@ -94,12 +101,25 @@ class Work {
} }
pushVFiles(itemList: VirtualFile[]) { pushVFiles(itemList: VirtualFile[]) {
this.push( this.push(
itemList.map((v) => ({ itemList.map((v) => {
name: v.name, const item: workItem = shallowReactive({
getBuffer: () => miaoFetchApi.getFile(v), id: Math.random(),
status: 'wait', status: 'wait',
isGettingBuffer: false name: v.name,
})) getBuffer: () => {
const { response, abort } = miaoFetchApi.getFile(v, {
onProcess(percent) {
item.getBufferProgress = percent
}
})
item.getBufferAbort = abort
return response
},
isGettingBuffer: false,
getBufferProgress: 0
})
return item
})
) )
} }
} }
@@ -125,6 +145,13 @@ const handleDropVirtualFiles = (vFiles: VirtualFile[]) => {
onMounted(() => { onMounted(() => {
encryptWork.pushVFiles(currentFiles.value) encryptWork.pushVFiles(currentFiles.value)
}) })
onUnmounted(() => {
encryptWork.abortLoop = true
encryptWork.queue.forEach(item => {
item.getBufferAbort && item.getBufferAbort()
})
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -133,12 +160,14 @@ onMounted(() => {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&-item { &-item {
max-height: 100%; max-height: 100%;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
&-item { &-item {
margin: 10px auto; margin: 10px auto;
width: 95%; width: 95%;
@@ -148,12 +177,15 @@ onMounted(() => {
0 0 3px 1px #43434359; 0 0 3px 1px #43434359;
border-radius: 5px; border-radius: 5px;
position: relative; position: relative;
.miaoCrypto-container-item-percentBar { .miaoCrypto-container-item-percentBar {
height: 100%; height: 100%;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
border-radius: 5px;
} }
.miaoCrypto-container-item-content { .miaoCrypto-container-item-content {
position: relative; position: relative;
z-index: 3 z-index: 3
@@ -3,7 +3,6 @@ import aesJs from 'aes-js'
self.onmessage = async function (event) { self.onmessage = async function (event) {
const data = event.data.data as Uint8Array const data = event.data.data as Uint8Array
const key = event.data.key as Uint8Array const key = event.data.key as Uint8Array
console.log(event)
var aesCtr = new aesJs.ModeOfOperation.ctr(key, new aesJs.Counter(7058)) var aesCtr = new aesJs.ModeOfOperation.ctr(key, new aesJs.Counter(7058))
const encryptData = aesCtr.encrypt(data) const encryptData = aesCtr.encrypt(data)
self.postMessage(encryptData) self.postMessage(encryptData)
+4
View File
@@ -17,3 +17,7 @@ const pluginConfig: registerComponentOption = {
} }
export default pluginConfig export default pluginConfig
import { PluginExportType } from '@/types/type'
export const type = PluginExportType.component
+91 -41
View File
@@ -1,42 +1,23 @@
<template> <template>
<miao-drop-handler <miao-drop-handler @on-virtual-directory="handleDropVDirectory" @on-files="handleDropFiles"
@on-virtual-directory="handleDropVDirectory"
@on-files="handleDropFiles"
@on-virtual-files="handleDropVirtualFiles"> @on-virtual-files="handleDropVirtualFiles">
<div class="miao-directory-item-container" ref="rootDomRef"> <div class="miao-directory-item-container" ref="rootDomRef">
<!-- 顶部面包屑导航和工具栏 --> <!-- 顶部面包屑导航和工具栏 -->
<miao-directory-top <miao-directory-top :color="props.color" :current-directory="currentDirectory" @back="handleBack"
:color="props.color" :open-menu-option="topBarMenuOption" @reload="handleReload" @exit="emit('exit')" :index="index"
:current-directory="currentDirectory" @set-current-directory="setCurrentDirectory" @search="handleSearch"
@back="handleBack"
:open-menu-option="topBarMenuOption"
@reload="handleReload"
@exit="emit('exit')"
:index="index"
@set-current-directory="setCurrentDirectory"
@search="handleSearch"
@menu-select="handleTopBarMenuSelect" /> @menu-select="handleTopBarMenuSelect" />
<!-- 文件和文件夹列表 --> <!-- 文件和文件夹列表 -->
<miao-directory-items <miao-directory-items ref="miaoDirectoryItemRef" :color="props.color"
ref="miaoDirectoryItemRef" :showData_directory="showData_directory" :showData_files="showData_files"
:color="props.color" :selected-item="selectedItem.value" :index="index" @item-click="handleItemClick"
:showData_directory="showData_directory" @item-download="handleItemDownload" @item-delete="handleItemDelete"
:showData_files="showData_files" @item-drag-start="handleItemDragStart" @item-rename="handleItemRename"
:selected-item="selectedItem.value"
:index="index"
@item-click="handleItemClick"
@item-download="handleItemDownload"
@item-delete="handleItemDelete"
@item-drag-start="handleItemDragStart"
@item-rename="handleItemRename"
@item-select="handleItemSelect" /> @item-select="handleItemSelect" />
<!-- 底部工具栏 --> <!-- 底部工具栏 -->
<miao-directory-bottom <miao-directory-bottom :index="index" @add-new-item="handleAddNewItem" v-if="false"
:index="index"
@add-new-item="handleAddNewItem"
v-if="false"
@pick-files-upload="handlePickFilesUpload" /> @pick-files-upload="handlePickFilesUpload" />
<miao-mask v-model:show="showModel"></miao-mask> <miao-mask v-model:show="showModel"></miao-mask>
@@ -106,13 +87,20 @@ class SelectedItem {
} }
_selectedItem: (VirtualDirectory | VirtualFile)[] _selectedItem: (VirtualDirectory | VirtualFile)[]
push(item: VirtualDirectory | VirtualFile) { push(item: VirtualDirectory | VirtualFile) {
this._selectedItem = [item, ...this._selectedItem] this._selectedItem = uniq([item, ...this._selectedItem])
}
pushArray(items: (VirtualDirectory | VirtualFile)[]) {
this._selectedItem = uniq([...items, ...this._selectedItem])
} }
remove(item: VirtualDirectory | VirtualFile) { remove(item: VirtualDirectory | VirtualFile) {
this._selectedItem = this._selectedItem.filter((v) => v !== item) this._selectedItem = this._selectedItem.filter((v) => v !== item)
} }
clear() { /**
this._selectedItem = [] * 仅保留filter为true的元素, filter默认为()=>false
*/
clear(filter?: (item: VirtualDirectory | VirtualFile) => boolean) {
filter || (filter = (__) => false)
this._selectedItem = this.value.filter(filter)
} }
// 保证内容仍然存在于dir中且各个元素不重复 // 保证内容仍然存在于dir中且各个元素不重复
get value() { get value() {
@@ -199,13 +187,13 @@ const topBarMenuOption = computed(() => {
icon: renderIcon(SearchOutline) icon: renderIcon(SearchOutline)
} }
: undefined, : undefined,
selectedItem.value.length !== 0 // selectedItem.value.length !== 0
? { // ? {
label: '重置多选', // label: '重置多选',
key: 'selectedItemReset', // key: 'selectedItemReset',
icon: renderIcon(CheckmarkDoneOutline) // icon: renderIcon(CheckmarkDoneOutline)
} // }
: undefined, // : undefined,
{ {
label: '筛选', label: '筛选',
key: 'filter', key: 'filter',
@@ -223,6 +211,30 @@ const topBarMenuOption = computed(() => {
], ],
icon: renderIcon(FilterOutline) icon: renderIcon(FilterOutline)
}, },
{
label: '多选',
key: 'multiSelect',
children: [
{
label: `全选`,
key: 'multiSelect:all'
},
{
label: `全不选`,
key: 'multiSelect:reset'
},
{
label: `反选`,
key: 'multiSelect:inversion'
},
selectedItem.value.length === 2 ?
{
label: `区间内全选`,
key: 'multiSelect:range'
} : undefined
].filter((v) => v !== undefined),
icon: renderIcon(CheckmarkDoneOutline),
},
{ {
label: '插件', label: '插件',
key: 'plugin', key: 'plugin',
@@ -299,15 +311,53 @@ const handleTopBarMenuSelect = (key: string) => {
handleAddNewItem() handleAddNewItem()
} else if (key === 'searchReset') { } else if (key === 'searchReset') {
handleSearchReset() handleSearchReset()
} else if (key === 'selectedItemReset') { // } else if (key === 'selectedItemReset') {
selectedItem.clear() // selectedItem.clear()
} else if (key === 'showDirs') { } else if (key === 'showDirs') {
showDirs.value = !showDirs.value showDirs.value = !showDirs.value
showDirs.value === false && selectedItem.clear(v => v.type !== 'directory')
} else if (key === 'showFiles') { } else if (key === 'showFiles') {
showFiles.value = !showFiles.value showFiles.value = !showFiles.value
showFiles.value === false && selectedItem.clear(v => v.type !== 'file')
} else if (key.startsWith('plugin:')) { } else if (key.startsWith('plugin:')) {
const pluginName = key.split(':')[1] const pluginName = key.split(':')[1]
pluginCenter.usePlugin(pluginName, [currentDirectory.value], []) pluginCenter.usePlugin(pluginName, [currentDirectory.value], [])
} else if (key.startsWith('multiSelect:')) {
const opName = key.split(':')[1]
switch (opName) {
case 'all':
selectedItem.pushArray([...showData_directory.value, ...showData_files.value])
break;
case 'reset':
selectedItem.clear()
break
case 'inversion':
const sArr = selectedItem.value
const arr = [...showData_directory.value, ...showData_files.value].filter(i => !sArr.includes(i))
selectedItem.clear()
selectedItem.pushArray(arr)
break
case "range":
const _sArr = selectedItem.value
const _arr = [...showData_directory.value, ...showData_files.value]
const _nArr = []
let _counter = 0
for (const i of _arr) {
if (_sArr.includes(i)) {
_counter++
}
if (_counter === 1) {
_nArr.push(i)
} else if (_counter === 2) {
_nArr.push(i)
break
}
}
selectedItem.clear()
selectedItem.pushArray(_nArr)
break
}
} }
} }
+4
View File
@@ -19,3 +19,7 @@ const pluginConfig: registerComponentOption = {
} }
export default pluginConfig export default pluginConfig
import { PluginExportType } from '@/types/type'
export const type = PluginExportType.component
+19 -2
View File
@@ -1,6 +1,7 @@
<template> <template>
<div class="miao-markdown-container-miaomiaomiao"> <div class="miao-markdown-container-miaomiaomiao">
<iframe <iframe
ref="iframeRef"
:src="iframeSrc" :src="iframeSrc"
frameborder="0" frameborder="0"
class="iframe-container-iframe"></iframe> class="iframe-container-iframe"></iframe>
@@ -8,17 +9,33 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { VirtualFile } from '@/class/VirtualDirectory' import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory'
import { onMounted, ref } from 'vue' import { cloneDeep } from 'lodash';
import { inject, onMounted, ref } from 'vue'
import buildFromTree from '../../hooks/buildFromTree';
const root = inject('rootDirectory') as VirtualDirectory
const currentFiles = defineModel<VirtualFile[]>('currentFiles', { const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
required: true required: true
}) })
const iframeSrc = ref<string>() const iframeSrc = ref<string>()
const iframeRef = ref<HTMLIFrameElement>();
const sendRootTree = () => {
iframeRef.value?.contentWindow && iframeRef.value.contentWindow.postMessage(cloneDeep(root.tree), "*");
}
const handleIframeMessage = (e: MessageEvent<any>) => {
// const data = e.data;
sendRootTree()
}
onMounted(() => { onMounted(() => {
iframeSrc.value = currentFiles.value[0].url iframeSrc.value = currentFiles.value[0].url
if (typeof window.addEventListener != "undefined") {
window.addEventListener("message", handleIframeMessage, false);
}
console.log(buildFromTree(cloneDeep(root.tree)))
}) })
</script> </script>
+4
View File
@@ -43,3 +43,7 @@ const pluginConfig: registerComponentOption = {
} }
export default pluginConfig export default pluginConfig
import { PluginExportType } from '@/types/type'
export const type = PluginExportType.component
+5 -5
View File
@@ -4,7 +4,7 @@
@on-virtual-files="handleDrop"> @on-virtual-files="handleDrop">
<miao-message-provider ref="miaoMessageRef"> <miao-message-provider ref="miaoMessageRef">
<div class="miaoImage" ref="rootRef"> <div class="miaoImage" ref="rootRef">
<n-scrollbar ref="NScrollbarRef"> <n-scrollbar ref="NScrollbarRef" style="max-height: 100%;">
<div <div
class="miaoImage-container" class="miaoImage-container"
v-for="imageGroup in imageGroupList"> v-for="imageGroup in imageGroupList">
@@ -62,7 +62,7 @@
<script setup lang="ts"> <script setup lang="ts">
import miaoMask from '@/components/miaoMask.vue' import miaoMask from '@/components/miaoMask.vue'
import miaoDropHandler from '@/components/miaoDropHandler.vue' import miaoDropHandler from '@/components/miaoDropHandler.vue'
import miaoMessageProvider from '@/components/miaoMessageProvider.vue' import miaoMessageProvider from '@/components/miaoAlertTipProvider.vue'
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory' import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory'
import { uniq } from 'lodash' import { uniq } from 'lodash'
import { computed, nextTick, onMounted, ref, watch } from 'vue' import { computed, nextTick, onMounted, ref, watch } from 'vue'
@@ -152,7 +152,7 @@ const scrollToActiveImage = () => {
const dom = rootRef.value?.querySelector( const dom = rootRef.value?.querySelector(
'.miaoImage-container-row-image-active' '.miaoImage-container-row-image-active'
) )
NScrollbarRef.value.scrollTo({ dom && NScrollbarRef.value.scrollTo({
top: dom.offsetTop - 30, top: dom.offsetTop - 30,
behavior: 'smooth' behavior: 'smooth'
}) })
@@ -199,7 +199,7 @@ const handleDrop = async (vItems: (VirtualFile | VirtualDirectory)[]) => {
// @ts-ignore // @ts-ignore
await updateImageSrcList(vItems) await updateImageSrcList(vItems)
} }
miaoMessageRef.value?.message( miaoMessageRef.value?.alertTip(
length_before === imageFileList.value.length length_before === imageFileList.value.length
? '未发现任何可添加图片' ? '未发现任何可添加图片'
: `导入${imageFileList.value.length - length_before}张图片`, : `导入${imageFileList.value.length - length_before}张图片`,
@@ -247,7 +247,7 @@ onMounted(async () => {
for (const i of [currentFiles.value, ...currentDirectories.value]) { for (const i of [currentFiles.value, ...currentDirectories.value]) {
await updateImageSrcList(i) await updateImageSrcList(i)
} }
miaoMessageRef.value?.message( miaoMessageRef.value?.alertTip(
length_before === imageFileList.value.length length_before === imageFileList.value.length
? '未发现任何新增图片' ? '未发现任何新增图片'
: `导入${imageFileList.value.length - length_before}张图片`, : `导入${imageFileList.value.length - length_before}张图片`,
+4
View File
@@ -19,3 +19,7 @@ const pluginConfig: registerComponentOption = {
} }
export default pluginConfig export default pluginConfig
import { PluginExportType } from '@/types/type'
export const type = PluginExportType.component
+1 -1
View File
@@ -28,7 +28,7 @@ const md = new MarkdownIt()
onMounted(async () => { onMounted(async () => {
markdownSrc.value = currentFiles.value[0].url markdownSrc.value = currentFiles.value[0].url
const text = decoder.decode(await miaoFetchApi.getFile(currentFiles.value[0])) const text = decoder.decode(await(miaoFetchApi.getFile(currentFiles.value[0])).response)
renderedMarkdown.value = md.render(text) renderedMarkdown.value = md.render(text)
}) })
</script> </script>
+4
View File
@@ -19,3 +19,7 @@ const pluginConfig: registerComponentOption = {
} }
export default pluginConfig export default pluginConfig
import { PluginExportType } from '@/types/type'
export const type = PluginExportType.component
+2 -2
View File
@@ -52,7 +52,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import miaoMessageProvider from '@/components/miaoMessageProvider.vue' import miaoMessageProvider from '@/components/miaoAlertTipProvider.vue'
import { onMounted, ref, shallowReactive } from 'vue' import { onMounted, ref, shallowReactive } from 'vue'
import PdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs?worker' import PdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs?worker'
import { VirtualFile } from '@/class/VirtualDirectory' import { VirtualFile } from '@/class/VirtualDirectory'
@@ -89,7 +89,7 @@ const handleResetMargin = () => {
} }
onMounted(async () => { onMounted(async () => {
const setLoadingMessage = miaoMessageRef.value?.message('加载PDF插件中', { const setLoadingMessage = miaoMessageRef.value?.alertTip('加载PDF插件中', {
type: 'info', type: 'info',
timeout: -1 timeout: -1
}) })
@@ -20,3 +20,7 @@ const pluginConfig: registerComponentOption = {
} }
export default pluginConfig export default pluginConfig
import { PluginExportType } from '@/types/type'
export const type = PluginExportType.component
+4
View File
@@ -26,3 +26,7 @@ const pluginConfig: registerComponentOption = {
} }
export default pluginConfig export default pluginConfig
import { PluginExportType } from '@/types/type'
export const type = PluginExportType.component
+9 -1
View File
@@ -8,12 +8,20 @@ export type miaoFetchConfig = {
export type getOption = {} & miaoFetchConfig; export type getOption = {} & miaoFetchConfig;
export type getFileOption = {
/**
* 下载进度回调函数
* @param {any} percent 进度百分比,float类型, 0-1
*/
onProcess?: (percent: number) => void;
} & miaoFetchConfig;
export type getResponse = (file | directory)[]; export type getResponse = (file | directory)[];
export type uploadOption = { export type uploadOption = {
/** /**
* 上传进度回调函数 * 上传进度回调函数
* @param {any} percent 进度百分比,float类型 * @param {any} percent 进度百分比,float类型, 0-1
*/ */
onProcess?: (percent: number) => void; onProcess?: (percent: number) => void;
/** /**
+17
View File
@@ -20,3 +20,20 @@ export type directory = {
stats: stats; stats: stats;
parent?: directory; parent?: directory;
}; };
export type alertTipType = ((content: string, options?: AlertTipMessageOptions) => (newContent: string, newOptions?: AlertTipMessageOptions) => void)
export interface AlertTipMessageOptions {
type?: 'info' | 'success' | 'error'
timeout?: number
id?: number
}
export enum PluginExportType {
default = 'default',
component = 'component'
}
export interface usePluginHooksType {
globalAlertTip: alertTipType;
}