You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

756 lines
16 KiB

<template>
<template v-if="mode === 'detail'">
<div class="file-item file-bar mode-detail">
<div class="file-item_img"></div>
<div class="file-item_title"></div>
<div class="file-item_type">
<span>{{ t("size") }}</span>
</div>
<div class="file-item_type">
<span>{{ t("creation.time") }}</span>
</div>
<div class="file-item_type">
<span>{{ t("modification.time") }}</span>
</div>
<div class="file-item_type">
<span>{{ t("permission") }}</span>
</div>
</div>
</template>
<div draggable="true" class="file-item" :class="{
chosen: chosenIndexs.includes(index),
'no-chosen': !chosenIndexs.includes(index),
'mode-icon': mode === 'icon',
'mode-list': mode === 'list',
'mode-big': mode === 'big',
'mode-middle': mode === 'middle',
'mode-detail': mode === 'detail',
'drag-over': hoverIndex === index,
}" :style="{
'--theme-color': theme === 'light' ? '#ffffff6b' : '#3bdbff3d',
}" v-for="(item, index) in fileList" :key="item.path" @dblclick="handleOnOpen(item)"
@touchstart.passive="doubleTouch($event, item)"
@contextmenu.stop.prevent="handleRightClick($event, item, index)" @drop="hadnleDrop($event, item.path)"
@dragenter.prevent="handleDragEnter(index)" @dragover.prevent @dragleave="handleDragLeave()"
@dragstart.stop="startDragApp($event, item)" @click="handleClick(index, item)" @mousedown.stop :ref="(ref: any) => {
if (ref) {
appPositions[index] = markRaw(ref as Element);
}
}
">
<div class="file-item_img">
<FileIcon :file="item" />
</div>
<span v-if="editIndex !== index" class="file-item_title">
{{ getName(item) }}
</span>
<textarea autofocus draggable="false" @dragover.stop @dragstart.stop @dragenter.stop @mousedown.stop
@dblclick.stop @click.stop @blur="onEditNameEnd" v-if="editIndex === index"
class="file-item_title file-item_editing" v-model="editName"></textarea>
<template v-if="mode === 'detail'">
<div class="file-item_type">
<span>{{ item.isDirectory ? "-" : dealSize(item.size) }}</span>
</div>
<div class="file-item_type">
<span>{{ item.birthtime.toLocaleString() }}</span>
</div>
<div class="file-item_type">
<span>{{ item.mtime.toLocaleString() }}</span>
</div>
<div class="file-item_type">
<span>{{ item.mode?.toString?.(8) || "unknow" }}</span>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { useAppMenu } from "@/hook/useAppMenu";
import { useContextMenu } from "@/hook/useContextMenu.ts";
import { useFileDrag } from "@/hook/useFileDrag";
import { Rect } from "@/hook/useRectChosen";
import { dealSystemName, t } from "@/i18n";
import { useChooseStore } from "@/stores/choose";
import { getSystemKey } from "@/system/config";
import { emitEvent, mountEvent } from "@/system/event";
import { addKnowledge } from "@/hook/useAi";
import {
basename,
BrowserWindow,
Notify,
OsFileWithoutContent,
useSystem,
} from "@/system/index.ts";
import { Menu } from "@/system/menu/Menu";
import { throttle } from "@/util/debounce";
import { dealSize } from "@/util/file";
import { markRaw, onMounted, ref } from "vue";
const { openPropsWindow, copyFile, createLink, deleteFile } =
useContextMenu();
const sys = useSystem();
const { startDrag, folderDrop } = useFileDrag(sys);
const choose = useChooseStore();
const props = defineProps({
onChosen: {
type: Function,
required: true,
},
onOpen: {
type: Function,
default: () => {
//
},
},
onRefresh: {
type: Function,
default: () => {
//
},
},
fileList: {
type: Array<OsFileWithoutContent>,
default: () => [],
},
theme: {
type: String || Object,
default: "light",
},
mode: {
type: String,
default: "icon",
},
});
function getName(item: any) {
const name = dealSystemName(basename(item.path));
// console.log(name)
// console.log(item.path)
if (name.endsWith(".exe")) {
return t(name.replace(".exe", ""));
} else {
return name;
}
}
function handleOnOpen(item: OsFileWithoutContent) {
// props.onOpen(item);
// emitEvent('desktop.app.open');
chosenIndexs.value = [];
if (choose.ifShow && !item.isDirectory) {
choose.path.push(item.path);
choose.close();
} else {
// console.log(' file list:',props.fileList);
props.onOpen(item);
emitEvent("desktop.app.open");
}
}
function hadnleDrop(mouse: DragEvent, path: string) {
hoverIndex.value = -1;
folderDrop(mouse, path);
chosenIndexs.value = [];
}
let expired: number | null = null;
function doubleTouch(e: TouchEvent, item: OsFileWithoutContent) {
if (e.touches.length === 1) {
if (!expired) {
expired = e.timeStamp + 400;
} else if (e.timeStamp <= expired) {
// remove the default of this event ( Zoom )
handleOnOpen(item);
e.preventDefault();
// then reset the variable for other "double Touches" event
expired = null;
} else {
// if the second touch was expired, make it as it's the first
expired = e.timeStamp + 400;
}
}
}
const editIndex = ref<number>(-1);
const editName = ref<string>("");
async function onEditNameEnd() {
const editEndName = editName.value.trim();
if (editEndName && editIndex.value >= 0) {
const editpath: any =
props.fileList[editIndex.value].path.toString();
let newPath: any;
let sp = "/";
if (editpath.indexOf("/") === -1) {
sp = "\\";
}
newPath = editpath?.split(sp);
newPath.pop();
newPath.push(editEndName);
newPath = newPath.join(sp);
await sys?.fs.rename(editpath, newPath);
props.onRefresh();
if (newPath.indexOf("Desktop") !== -1) {
sys.refershAppList();
}
}
editIndex.value = -1;
}
mountEvent("edit.end", () => {
onEditNameEnd();
});
const hoverIndex = ref<number>(-1);
const appPositions = ref<Array<Element>>([]);
const chosenIndexs = ref<Array<number>>([]);
import { isMobileDevice } from "@/util/device";
const isMobile = ref<boolean>(false);
function handleClick(index: number, item: OsFileWithoutContent) {
chosenIndexs.value = [index];
if (isMobile.value) {
handleOnOpen(item)
}
}
onMounted(() => {
isMobile.value = isMobileDevice();
chosenIndexs.value = [];
props.onChosen(
throttle((rect: Rect) => {
const tempChosen: number[] = [];
appPositions.value.forEach((el, index) => {
const rect2 = el.getBoundingClientRect();
const rect2Center = {
x: rect2.left + rect2.width / 2,
y: rect2.top + rect2.height / 2,
};
if (
rect2Center.x > rect.left &&
rect2Center.x < rect.left + rect.width &&
rect2Center.y > rect.top &&
rect2Center.y < rect.top + rect.height
) {
tempChosen.push(index);
}
});
chosenIndexs.value = tempChosen;
}, 100)
);
});
function startDragApp(mouse: DragEvent, item: OsFileWithoutContent) {
if (chosenIndexs.value.length) {
startDrag(
mouse,
chosenIndexs.value.map((index) => {
return props.fileList[index];
}),
() => {
chosenIndexs.value = [];
}
);
} else {
startDrag(mouse, [item], () => {
chosenIndexs.value = [];
});
}
}
document.addEventListener('copy', function () {
const files = chosenIndexs.value.map(
(index) => props.fileList[index]
)
if (files.length > 0) {
copyFile(files);
chosenIndexs.value = [];
}
});
// document.addEventListener('keydown', function (event) {
// // 检测 Control + C
// if (event.ctrlKey && event.key === 'c') {
// //console.log('Control + C 被按下');
// copyFile(
// chosenIndexs.value.map(
// (index) => props.fileList[index]
// )
// );
// chosenIndexs.value = [];
// }
// });
function handleRightClick(
mouse: MouseEvent,
item: OsFileWithoutContent,
index: number
) {
if (chosenIndexs.value.length <= 1) {
chosenIndexs.value = [
props.fileList.findIndex((app) => app.path === item.path),
];
}
const ext = item.name.split(".").pop();
const zipSucess = (res: any) => {
if (!res || res.code < 0) {
new Notify({
title: t("tips"),
content: t("error"),
});
} else {
props.onRefresh();
new Notify({
title: t("tips"),
content: t("file.zip.success"),
});
if (item.parentPath == "/C/Users/Desktop") {
sys.refershAppList();
}
}
};
// eslint-disable-next-line prefer-const
let menuArr: any = [
{
label: t("open"),
click: () => {
chosenIndexs.value = [];
props.onOpen(item);
},
},
// {
// label: t('open.with'),
// click: () => {
// chosenIndexs.value = [];
// openWith(item);
// },
// },
];
if (item.isDirectory) {
if (getSystemKey("storeType") == "local") {
menuArr.push({
label: t("zip"),
submenu: [
{
label: "zip",
click: () => {
sys.fs
.zip(item.path, "zip")
.then((res: any) => {
zipSucess(res);
});
},
},
{
label: "tar",
click: () => {
sys.fs
.zip(item.path, "tar")
.then((res: any) => {
zipSucess(res);
});
},
},
{
label: "gz",
click: () => {
sys.fs.zip(item.path, "gz").then((res: any) => {
zipSucess(res);
});
},
},
],
});
} else {
menuArr.push({
label: t("zip"),
click: () => {
sys.fs.zip(item.path, "zip").then((res: any) => {
zipSucess(res);
});
},
});
}
if (!item.knowledgeId) {
menuArr.push({
label: "加入知识库",
click: () => {
console.log(item)
addKnowledge(item.path).then((res: any) => {
console.log(res)
if (res.code != 0) {
new Notify({
title: t("tips"),
content: res.message,
});
} else {
new Notify({
title: t("tips"),
content: "添加成功",
});
props.onRefresh();
}
})
},
});
} else {
menuArr.push({
label: "对话知识库",
click: () => {
const win = new BrowserWindow({
title: "对话知识库",
content: "AiChatMain",
config: {
path: item.path,
knowledgeId: item.knowledgeId,
},
width: 700,
height: 600,
center: true,
});
win.show();
},
});
}
}
if (choose.ifShow) {
menuArr.push({
label: "选中发送",
click: () => {
const paths: any = [];
chosenIndexs.value.forEach((index) => {
const item = props.fileList[index];
paths.push(item.path);
});
if (paths.length > 0) {
choose.path = paths;
choose.close();
}
chosenIndexs.value = [];
},
});
}
// eslint-disable-next-line prefer-const
let extMenus = useAppMenu(item, sys, props);
if (extMenus && extMenus.length > 0) {
// eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, extMenus);
}
if (ext != "exe") {
const fileMenus = [
{
label: t("rename"),
click: () => {
editIndex.value = index;
editName.value = basename(item.path);
chosenIndexs.value = [];
},
},
{
label: t("copy"),
click: () => {
//if(["/","/B"].includes(item.path)) return;
copyFile(
chosenIndexs.value.map(
(index) => props.fileList[index]
)
);
chosenIndexs.value = [];
},
},
];
if (item.path.indexOf("/F") < 0) {
fileMenus.push({
label: t("delete"),
click: async () => {
for (let i = 0; i < chosenIndexs.value.length; i++) {
await deleteFile(
props.fileList[chosenIndexs.value[i]]
);
}
chosenIndexs.value = [];
props.onRefresh();
if (item.path.indexOf("Desktop") > -1) {
sys.refershAppList();
}
},
});
fileMenus.push({
label: t("create.shortcut"),
click: () => {
createLink(item.path)?.then(() => {
chosenIndexs.value = [];
props.onRefresh();
});
},
});
}
const userType = sys.getConfig("userType");
if (userType == "member") {
menuArr.push({
label: "分享给...",
click: () => {
const win = new BrowserWindow({
title: "分享",
content: "ShareFiles",
config: {
path: item.path,
},
width: 450,
height: 280,
center: true,
});
win.show();
},
});
}
if (!item.isDirectory) {
menuArr.push({
label: "文件加密",
click: () => {
const win = new BrowserWindow({
title: "文件加密",
content: "FilePwd",
config: {
path: item.path,
},
width: 400,
height: 200,
center: true,
});
win.show();
},
});
}
menuArr.push.apply(menuArr, fileMenus);
}
const sysEndMenu = [
{
label: t("props"),
click: () => {
chosenIndexs.value.forEach((index) => {
openPropsWindow(props.fileList[index].path);
chosenIndexs.value = [];
});
},
},
];
// eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, sysEndMenu);
//console.log(item)
//console.log(ext)
Menu.buildFromTemplate(menuArr).popup(mouse);
}
function handleDragEnter(index: number) {
hoverIndex.value = index;
}
function handleDragLeave() {
hoverIndex.value = -1;
}
// function dealtName(name: string) {
// return name;
// }
</script>
<style lang="scss" scoped>
.file-item {
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
width: var(--desk-item-size);
height: var(--desk-item-size);
font-size: var(--ui-font-size);
color: var(--icon-title-color);
padding-top: 4px;
border: 1px solid transparent;
margin: 6px;
.file-item_img {
width: 60%;
height: 60%;
pointer-events: none;
}
.file-item_type {
display: none;
}
.file-item_title {
pointer-events: none;
}
.file-item_editing {
display: inline-block !important;
outline: none;
pointer-events: all;
padding: 0;
margin: 0;
min-width: 0;
height: min-content !important;
width: min-content !important;
resize: none;
border-radius: 0;
}
}
.file-item:hover {
background-color: #b1f1ff4c;
}
.chosen {
border: 1px dashed #3bdbff3d;
// background-color: #ffffff6b;
background-color: var(--theme-color);
.file-item_title {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
.no-chosen {
.file-item_title {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
.drag-over {
border: 1px dashed #3bdbff3d;
// background-color: #ffffff6b;
background-color: var(--theme-color);
}
.mode-icon {
.file-item_img {
width: 60%;
height: calc(0.6 * var(--desk-item-size));
margin: 0px auto;
user-select: none;
flex-shrink: 0;
}
.file-item_title {
// color: var(--color-ui-desk-item-title);
// height: calc(0.4 * var(--desk-item-size));
// display: block;
text-align: center;
word-break: break-all;
flex-grow: 0;
}
}
.mode-list {
display: flex;
flex-direction: row;
justify-content: flex-start;
height: var(--menulist-item-height);
width: var(--menulist-width);
.file-item_img {
width: var(--menulist-item-height);
height: calc(0.6 * var(--menulist-item-height));
flex-shrink: 0;
user-select: none;
}
.file-item_title {
height: min-content;
word-break: break-all;
}
}
.mode-icon {
width: var(--desk-item-size);
height: var(--desk-item-size);
}
.mode-big {
width: calc(var(--desk-item-size) * 2.5);
height: calc(var(--desk-item-size) * 2.5);
}
.mode-middle {
width: calc(var(--desk-item-size) * 1.5);
height: calc(var(--desk-item-size) * 1.5);
}
.mode-detail {
display: flex;
flex-direction: row;
justify-content: flex-start;
height: var(--menulist-item-height);
width: 100%;
margin: 2px;
.file-item_img {
width: 30px;
}
.file-item_title {
width: 40%;
display: flex;
align-items: center;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-item_type {
display: block;
color: var(--color-dark-hover);
width: 20%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.file-bar:hover {
background-color: unset;
user-select: none;
}
@media screen and (max-width: 768px) {
.file-item {
height: vh(70);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
&:hover {
background-color: transparent;
}
.icon {
width: vw(40);
height: vh(40);
}
.title {
font-size: vw(12);
text-align: center;
}
}
.chosen {
background-color: transparent;
border: 1px solid transparent;
}
}
</style>