Browse Source

test:共享文件接口对接

master
prr 7 months ago
parent
commit
6b4405d127
  1. 2
      frontend/components.d.ts
  2. 9
      frontend/src/components/builtin/FileIcon.vue
  3. 2
      frontend/src/components/builtin/FileList.vue
  4. 4
      frontend/src/components/builtin/FileTree.vue
  5. 239
      frontend/src/components/builtin/ShareFileTree.vue
  6. 84
      frontend/src/components/computer/Computer.vue
  7. 2
      frontend/src/components/oa/ShareFiles.vue
  8. 24
      frontend/src/hook/useComputer.ts
  9. 4
      frontend/src/hook/useContextMenu.ts
  10. 4
      frontend/src/i18n/index.ts
  11. 2
      frontend/src/i18n/lang/zh.json
  12. 82
      frontend/src/system/core/FileOs.ts
  13. 42
      frontend/src/system/index.ts

2
frontend/components.d.ts

@ -55,6 +55,7 @@ declare module 'vue' {
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCion: typeof import('element-plus/es')['ElCion']
ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElContainer: typeof import('element-plus/es')['ElContainer']
@ -134,6 +135,7 @@ declare module 'vue' {
Setting: typeof import('./src/components/setting/Setting.vue')['default']
SetUpdate: typeof import('./src/components/setting/SetUpdate.vue')['default']
ShareFiles: typeof import('./src/components/oa/ShareFiles.vue')['default']
ShareFileTree: typeof import('./src/components/builtin/ShareFileTree.vue')['default']
ShowNews: typeof import('./src/components/taskbar/ShowNews.vue')['default']
StartMenu: typeof import('./src/components/taskbar/StartMenu.vue')['default']
StartOption: typeof import('./src/components/taskbar/StartOption.vue')['default']

9
frontend/src/components/builtin/FileIcon.vue

@ -4,7 +4,7 @@
<FileIconImg v-if="isSvg === true" :file="file" :icon="icon" />
<FileIconIs v-else :file="file" :icon="icon" />
</Suspense>
<div v-if="extname(file?.path || '') === '.ln'" class="ln-img">
<div v-if="extname(file?.path || '') === '.ln' || file?.isShare === true" class="ln-img">
<img :src="lnicon" alt="ln" />
</div>
</div>
@ -24,7 +24,6 @@ if(props.icon && props.icon.indexOf('.') !== -1){
isSvg.value = false;
}
if(props.file && props.file.content) {
//console.log(props.file)
if(typeof props.file.content === 'string') {
const end = props.file.content.split("::").pop()
if(end && end.indexOf('.') !== -1){
@ -51,10 +50,10 @@ if(props.file && props.file.content) {
}
.ln-img {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
width: 80%;
height: 80%;
}
}
</style>

2
frontend/src/components/builtin/FileList.vue

@ -34,7 +34,7 @@
@dragleave="handleDragLeave()" @dragstart.stop="startDragApp($event, item)" @click="handleClick(index)"
@mousedown.stop :ref="(ref: any) => {
if (ref) {
appPositions[index] = markRaw(ref as Element);
appPositions[indeíx] = markRaw(ref as Element);
}
}
">

4
frontend/src/components/builtin/FileTree.vue

@ -52,8 +52,8 @@ import { useSystem,OsFileWithoutContent,basename } from '@/system/index.ts';
import { onMounted, ref } from 'vue';
import { dealSystemName } from '@/i18n';
//
import { getSystemConfig } from "@/system/config";
const config = ref(getSystemConfig())
// import { getSystemConfig } from "@/system/config";
// const config = ref(getSystemConfig())
const sys = useSystem();
type FileWithOpen = OsFileWithoutContent & {

239
frontend/src/components/builtin/ShareFileTree.vue

@ -0,0 +1,239 @@
<template>
<div
class="item-group"
v-for="(item, index) in fileList"
:key="item.path + item.isOpen"
>
<div
class="file-item"
:class="{
chosen: chosenPath === item.path,
// 'no-chosen': !chosenIndexs.includes(index),
'mode-icon': mode === 'icon',
'mode-list': mode === 'list',
}"
:style="{
'padding-left': level * 12 + 'px',
}"
@click="handleClick(item, index)"
@mousedown.stop
>
<div
class="iconfont"
:class="{
'open-arrow': item.isOpen,
'hide-arrow': item.isOpen && !item.subFileList?.length,
}"
@click.stop="onOpenArrow(item)"
>
<div class="icon-arrow-down"></div>
</div>
<div class="file-item_img">
<FileIcon :file="item" />
</div>
<span class="file-item_title">{{ getName(item) }}</span>
</div>
<div class="sub-tree">
<ShareFileTree
v-if="item.isOpen"
:level="level + 1"
mode="list"
:chosen-path="chosenPath"
:on-refresh="() => onSubRefresh(item)"
:on-open="onSubOpen"
:file-list="item.subFileList"
>
</ShareFileTree>
</div>
</div>
</template>
<script lang="ts" setup>
import { useSystem,OsFileWithoutContent,basename } from '@/system/index.ts';
import { onMounted, ref } from 'vue';
import { dealSystemName } from '@/i18n';
const sys = useSystem();
type FileWithOpen = OsFileWithoutContent & {
isOpen?: boolean;
subFileList?: FileWithOpen[];
};
const props = defineProps({
level: {
type: Number,
default: 0,
},
onOpen: {
type: Function,
default: () => {
//
},
},
onRefresh: {
type: Function,
default: () => {
//
},
},
chosenPath: {
type: String,
default: '',
},
fileList: {
type: Array<FileWithOpen>,
default: () => [],
},
mode: {
type: String,
default: 'icon',
},
});
const chosenIndexs = ref<Array<number>>([]);
function handleClick(item: FileWithOpen, index: number) {
chosenIndexs.value = [index];
props.onOpen(item.path);
}
function getName(item:any){
const name = dealSystemName(basename(item.path))
return name
}
onMounted(() => {
chosenIndexs.value = [];
props.fileList.forEach((item) => {
item.isOpen = false;
});
});
function onSubOpen(path: string) {
props.onOpen(path);
}
async function onSubRefresh(item: FileWithOpen) {
item.subFileList = (await sys.fs.sharedir(sys.getConfig('userInfo')?.id, item?.path)).filter((file:any) => {
return file.isDirectory;
});
}
async function onOpenArrow(item: FileWithOpen) {
if (item.isOpen && !item.subFileList?.length) {
return;
}
item.isOpen = !item.isOpen;
item.subFileList = (await sys.fs.sharedir(sys.getConfig('userInfo')?.id, item?.path)).filter((file:any) => {
return file.isDirectory;
});
}
</script>
<style lang="scss" scoped>
.icon-arrow-down {
display: block;
width: 4px;
height: 4px;
margin: 7px;
transform: translateY(0px) rotate(-45deg);
border: 2px solid rgba(0, 0, 0, 0.465);
border-left: none;
border-top: none;
transition: all 0.1s;
}
.hide-arrow {
border-color: transparent;
}
.open-arrow {
transform: translateY(-2px) translateX(2px) rotate(90deg);
}
.file-item {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
color: var(--icon-title-color);
font-size: var(--ui-font-size);
border: 1px solid transparent;
margin: 2px;
user-select: none;
}
.file-item:hover {
background-color: #b1f1ff4c;
}
.chosen {
border: 1px dashed #3bdbff3d;
background-color: #9595956b;
.file-item_title {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
// background-color: var(--theme-color);
}
.chosen:hover {
background-color: #9595956b;
}
.no-chosen {
.file-item_title {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
}
.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: calc(0.8 * var(--menulist-item-height));
width: 100%;
.file-item_img {
width: var(--menulist-item-height);
height: calc(0.6 * var(--menulist-item-height));
// margin: 0px auto;
user-select: none;
flex-shrink: 0;
svg {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.file-item_title {
height: min-content;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
}
</style>

84
frontend/src/components/computer/Computer.vue

@ -42,8 +42,9 @@
:key="random"
>
</FileTree>
<div class="showName">{{ t("share") }}</div>
<FileTree
<div class="showName" v-if="shareShow">{{ t("share") }}</div>
<ShareFileTree
v-if="shareShow"
:chosen-path="chosenTreePath"
mode="list"
:on-open="onTreeOpen"
@ -51,7 +52,7 @@
:file-list="shareFileList"
:key="random"
>
</FileTree>
</ShareFileTree>
<QuickLink :on-open="onTreeOpen"></QuickLink>
<div class="left-handle" @mousedown="leftHandleDown"></div>
</div>
@ -162,6 +163,10 @@ const { refersh, createFolder, backFolder, openFolder, onComputerMount } = useCo
readdir(path) {
return system.fs.readdir(path);
},
sharedir(path) {
const val:number = system.getConfig('userInfo')?.id
return system.fs.sharedir(val,path)
},
exists(path) {
return system.fs.exists(path);
},
@ -206,8 +211,44 @@ function leftHandleDown(e: MouseEvent) {
}
const rootFileList = ref<Array<OsFileWithoutContent>>([]);
const shareFileList = ref<Array<OsFileWithoutContent>>([]);
const shareFileList = ref<Array<OsFileWithoutContent>>([
{
ext: "",
isDirectory: true,
isFile: false,
isSymlink: false,
mode: 2147484141,
name: "myshare",
oldPath: "/F/myshare",
parentPath: "/F",
path: "/F/myshare",
title: "myshare",
size: 64,
mtime: "",
rdev: 0,
atime: "",
birthtime: ""
},
{
ext: "",
isDirectory: true,
isFile: false,
isSymlink: false,
mode: 2147484141,
name: "othershare",
oldPath: "/F/othershare",
parentPath: "/F",
path: "/F/othershare",
title: "othershare",
size: 64,
mtime: "",
rdev: 0,
atime: "",
birthtime: ""
}
]);
const random = ref(0);
const shareShow = ref(false)
onMounted(() => {
if (config) {
router_url.value = config.path;
@ -227,24 +268,7 @@ onMounted(() => {
random.value = random.value + 1;
}
});
shareFileList.value = [{
atime: "2024-10-21T18:25:12.936228381+08:00",
birthtime: "2024-10-21T18:25:12.936228381+08:00",
content: "",
ext: "",
isDirectory: true,
isFile: false,
isOpen: false,
isSymlink: false,
modTime: "2024-10-21T18:25:12.936228381+08:00",
mode: 2147484141,
name: "F",
oldPath: "/F",
parentPath: "/",
path: "/F",
size: 64,
title: "F"
}]
shareShow.value = system.getConfig('userType') === 'member' ? true : false
});
function handleOuterClick() {
@ -271,17 +295,17 @@ const chosenView = ref("icon");
/**------树状列表打开------ */
const chosenTreePath = ref("");
async function onTreeOpen(path: string) {
chosenTreePath.value = path;
const file = await system.fs.stat(path);
let file:any
// const file = await system.fs.stat(path);
if (path.substring(0,2) == '/F') {
file = path.indexOf('/F/myshare') !== -1 ? shareFileList.value[0] : shareFileList.value[1]
} else {
file = await system.fs.stat(path)
}
if (file) {
console.log('此电脑:',file,path);
const temp = path.substr(0,2) == '/F' ? shareFileList.value[0] : file
openFolder(temp)
// openFolder(file);
openFolder(file);
}
//console.log(path)
router_url.value = path;
}

2
frontend/src/components/oa/ShareFiles.vue

@ -62,6 +62,8 @@ const onSubmit = async () => {
temp.senderid = temp.senderid.toString()
temp.receverid = temp.receverid.map((item:any) => item.toString())
const res = await fetchPost(apiUrl, new URLSearchParams(temp))
console.log('分享文件:',res)
}
</script>
<style scoped>

24
frontend/src/hook/useComputer.ts

@ -10,6 +10,7 @@ export const useComputer = (adpater: {
rmdir: (path: RouterPath) => Promise<void>;
mkdir: (path: RouterPath) => Promise<void>;
readdir: (path: RouterPath) => Promise<OsFileWithoutContent[]>;
sharedir: (path: RouterPath) => Promise<OsFileWithoutContent[]>;
exists: (path: RouterPath) => Promise<boolean>;
isDirectory: (file: OsFileWithoutContent) => boolean;
notify: (title: string, content: string) => void;
@ -19,10 +20,12 @@ export const useComputer = (adpater: {
if (path === '') path = '/';
else if (path === '/') path = '/';
else if (path.endsWith('/')) path = path.substr(0, path.length - 1);
const isExist = await adpater.exists(path);
if (!isExist) {
adpater.notify('路径不存在', path);
return false;
if(path.substring(0,2) !== '/F') {
const isExist = await adpater.exists(path);
if (!isExist) {
adpater.notify('路径不存在', path);
return false;
}
}
return true;
};
@ -37,8 +40,13 @@ export const useComputer = (adpater: {
}
if (!(await isVia(currentPath))) return;
const result = await adpater.readdir(currentPath);
console.log('refersh result:',result)
console.log('use computer refresh:', currentPath);
let result
if (currentPath.substring(0,2) == '/F') {
result = await adpater.sharedir(currentPath)
} else {
result = await adpater.readdir(currentPath);
}
if (result) adpater.setFileList(result);
};
const createFolder = (path: RouterPath) => {
@ -59,11 +67,9 @@ export const useComputer = (adpater: {
refersh();
};
const openFolder = (file: OsFileWithoutContent) => {
console.log('打开:', file);
console.log('use computer openFolder 打开:', file);
if (adpater.isDirectory(file)) {
console.log('走refresh');
adpater.setRouter(file.path);
refersh();
} else {

4
frontend/src/hook/useContextMenu.ts

@ -268,6 +268,8 @@ function useContextMenu() {
if (file.isDirectory) {
return system?.fs.rmdir(file.path);
} else {
console.log('删除文件:',file);
return system?.fs.unlink(file.path);
}
}
@ -275,6 +277,8 @@ function useContextMenu() {
if (file.isDirectory) {
return system?.fs.rmdir(file.path);
} else {
console.log('删除文件:',file);
return system?.fs.unlink(file.path);
}
}

4
frontend/src/i18n/index.ts

@ -59,7 +59,9 @@ export function dealSystemName(name: string) {
"D": t('document'),
"E": t('office'),
"B": t('recycle'),
"F": t('share')
"F": t('share'),
"myshare": t('myshare'),
"othershare": t('othershare')
}
if (sysNames[name]) {
return sysNames[name];

2
frontend/src/i18n/lang/zh.json

@ -8,6 +8,8 @@
"office": "办公",
"recycle": "回收站",
"share": "分享",
"myshare": "我的分享",
"othershare": "接收的分享",
"favorite": "我的收藏",
"desktop": "桌面",
"personalization": "个性化",

82
frontend/src/system/core/FileOs.ts

@ -1,9 +1,9 @@
import { getFileUrl,fetchGet,fetchPost } from "../config.ts";
const API_BASE_URL = getFileUrl()
import { OsFileMode } from '../core/FileMode';
import { getSystemConfig } from "@/system/config";
// import { getSystemConfig } from "@/system/config";
export async function handleReadDir(path: string): Promise<any> {
export async function handleReadDir(path: any): Promise<any> {
const res = await fetchGet(`${API_BASE_URL}/read?path=${encodeURIComponent(path)}`);
if (!res.ok) {
return false;
@ -11,15 +11,41 @@ export async function handleReadDir(path: string): Promise<any> {
return await res.json();
}
// 查看分享文件
export async function handleReadShareDir(val: number): Promise<any> {
console.log('共享-用户ID:', val)
const res = await fetchGet(`${API_BASE_URL}/sharelist?id=${val}`);
export async function handleReadShareDir(val: number,path: string): Promise<any> {
const url = path.indexOf('/F/myshare') !== -1 ? 'sharemylist' : 'sharelist'
const res = await fetchGet(`${API_BASE_URL}/${url}?id=${val}`);
if (!res.ok) {
return false;
}
return await res.json();
}
// 查看分享文件
export async function handleReadShareFile(path: string): Promise<any> {
const res = await fetchGet(`${API_BASE_URL}/shareread?path=${path}`);
if (!res.ok) {
return false;
}
return await res.json();
}
// 删除分享文件
export async function handleShareUnlink(path: string): Promise<any> {
const file = await handleShareDetail(path)
const res = await fetchGet(`${API_BASE_URL}/sharedelete?path=${path}`);
if (!res.ok) {
return false;
}
return await res.json();
}
// 查看文件信息
export async function handleShareDetail(path: string): Promise<any> {
const res = await fetchGet(`${API_BASE_URL}/shareinfo?path=${path}`);
console.log('文件详情:', res);
if (!res.ok) {
return false;
}
return await res.json();
}
export async function handleStat(path: string): Promise<any> {
const res = await fetchGet(`${API_BASE_URL}/stat?path=${encodeURIComponent(path)}`);
if (!res.ok) {
@ -204,29 +230,39 @@ export async function handleUnZip(path: string): Promise<any> {
export const useOsFile = () => {
return {
// 分享
async sharedir(val: number) {
const response = await handleReadShareDir(val);
async sharedir(val: number, path: string) {
const response = await handleReadShareDir(val, path);
if (response && response.data) {
// return response.data;
console.log('文件列表:',response.data.map(item => item.fi));
return response.data.map(item => {
item.fi.isShare = true
return item.fi
})
}
return [];
},
async readShareFile(path: string) {
const response = await handleReadShareFile(path);
if (response && response.data) {
return response.data;
}
return [];
},
async readdir(path: string) {console.log('raeddir path:',path,path.substring(0,2))
const api = path.substring(0,2) == '/F' ? handleReadShareDir : handleReadDir
const temp = path.substring(0,2) == '/F' ? getSystemConfig().userInfo.id : path
const response = await api(temp);
// const response = await handleReadDir(path);
async readdir(path: string) {
// console.log('raeddir path:',path,path.substring(0,2))
// const api = path.substring(0,2) == '/F' ? handleReadShareDir : handleReadDir
// const temp = path.substring(0,2) == '/F' ? getSystemConfig().userInfo.id : path
// const response = await api(temp);
const response = await handleReadDir(path);
if (response && response.data) {
return response.data;
}
return [];
},
async stat(path: string) {
console.log('stat path:',path,path.substring(0,2))
const api = path.substring(0,2) == '/F' ? handleReadShareDir : handleStat
const temp = path.substring(0,2) == '/F' ? getSystemConfig().userInfo.id : path
const response = await api(temp);
// const response = await handleStat(path);
const response = await handleStat(path);
if (response && response.data) {
return response.data;
}
@ -245,11 +281,13 @@ export const useOsFile = () => {
if (response && response.data) {
return response.data;
}
// return false;
return false;
//分享
return true
// return true
},
async readFile(path: string) {
console.log('读文件:',path);
// 注意:handleReadFile返回的是JSON,但通常readFile期望返回Buffer或字符串
const response = await handleReadFile(path);
if (response && response.data) {
@ -258,7 +296,9 @@ export const useOsFile = () => {
return false;
},
async unlink(path: string) {
const response = await handleUnlink(path);
const fun = path.indexOf('data/userData') === 0 ? handleShareUnlink : handleUnlink
console.log('删除路径:',path,fun);
const response = await fun(path);
if (response) {
return response;
}

42
frontend/src/system/index.ts

@ -426,25 +426,37 @@ export class System {
}
/**打开os 文件系统的文件 */
async openFile(path: string) {
const fileStat = await this.fs.stat(path);
if (!fileStat) {
throw new Error('文件不存在');
}
// 如果fileStat为目录
if (fileStat?.isDirectory) {
// 从_fileOpenerMap中获取'link'对应的函数并调用
this._flieOpenerMap.get('dir')?.func.call(this, path, '');
return;
} else {
// 读取文件内容
const fileContent = await this.fs.readFile(path);
// 从_fileOpenerMap中获取文件扩展名对应的函数并调用
const fileName = extname(fileStat?.name || '') || 'link'
//console.log(fileName)
//判断是否是共享文件
const pos = path.indexOf('data/userData')
if (pos !== -1) {
const arr = path.split('/')
const fileContent = await this.fs.readShareFile(path)
const fileName = extname(arr[arr.length-1] || '') || 'link'
this._flieOpenerMap
.get(fileName)
?.func.call(this, path, fileContent || '');
} else {
const fileStat = await this.fs.stat(path)
if (!fileStat) {
throw new Error('文件不存在');
}
// 如果fileStat为目录
if (fileStat?.isDirectory) {
// 从_fileOpenerMap中获取'link'对应的函数并调用
this._flieOpenerMap.get('dir')?.func.call(this, path, '');
return;
} else {
// 读取文件内容
const fileContent = await this.fs.readFile(path);
// 从_fileOpenerMap中获取文件扩展名对应的函数并调用
const fileName = extname(fileStat?.name || '') || 'link'
//console.log(fileName)
this._flieOpenerMap
.get(fileName)
?.func.call(this, path, fileContent || '');
}
}
}
// 插件系统
use(func: OsPlugin): void {

Loading…
Cancel
Save