🔨 sftp 文件列表.
This commit is contained in:
@@ -26,7 +26,7 @@ export function readFileText(e: File, encoding = 'UTF-8'): Promise<string> {
|
||||
/**
|
||||
* 解析路径类型
|
||||
*/
|
||||
type PathAnalysis = {
|
||||
export interface PathAnalysis {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
@@ -34,23 +34,19 @@ type PathAnalysis = {
|
||||
/**
|
||||
* 获取解析路径
|
||||
*/
|
||||
export function getPathAnalysis(analysisPath: string, paths: PathAnalysis[] = []): PathAnalysis[] {
|
||||
const lastSymbol = analysisPath.lastIndexOf('/');
|
||||
if (lastSymbol === -1) {
|
||||
paths.unshift({
|
||||
name: '/',
|
||||
path: '/'
|
||||
});
|
||||
export function getPathAnalysis(path: string, paths: PathAnalysis[] = []): PathAnalysis[] {
|
||||
const lastSeparatorIndex = path.lastIndexOf('/');
|
||||
if (lastSeparatorIndex === -1) {
|
||||
return paths;
|
||||
}
|
||||
const name = analysisPath.substring(lastSymbol, analysisPath.length);
|
||||
const name = path.substring(lastSeparatorIndex, path.length);
|
||||
if (!isEmptyStr(name) && name !== '/') {
|
||||
paths.unshift({
|
||||
name: name.substring(1, name.length),
|
||||
path: analysisPath
|
||||
path: path
|
||||
});
|
||||
}
|
||||
return getPathAnalysis(analysisPath.substring(0, lastSymbol), paths);
|
||||
return getPathAnalysis(path.substring(0, lastSeparatorIndex), paths);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +54,7 @@ export function getPathAnalysis(analysisPath: string, paths: PathAnalysis[] = []
|
||||
*/
|
||||
export function getPath(path: string) {
|
||||
return path.replace(new RegExp('\\\\+', 'g'), '/')
|
||||
.replace(new RegExp('/+', 'g'), '/');
|
||||
.replace(new RegExp('/+', 'g'), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,27 +11,68 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="返回上级">
|
||||
<span class="click-icon-wrapper header-action-icon mr4">
|
||||
<span class="click-icon-wrapper header-action-icon mr4"
|
||||
@click="backParentPath">
|
||||
<icon-left />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 当前路径 -->
|
||||
<div class="sftp-path-wrapper">
|
||||
<a-breadcrumb>
|
||||
<!-- 分隔符 -->
|
||||
<template #separator>
|
||||
<icon-right />
|
||||
</template>
|
||||
<a-breadcrumb-item class="sftp-path-unit">/</a-breadcrumb-item>
|
||||
<a-breadcrumb-item class="sftp-path-unit">root</a-breadcrumb-item>
|
||||
<a-breadcrumb-item class="sftp-path-unit">orion</a-breadcrumb-item>
|
||||
<a-breadcrumb-item class="sftp-path-unit">space</a-breadcrumb-item>
|
||||
<a-breadcrumb-item class="sftp-path-unit">logs</a-breadcrumb-item>
|
||||
<div class="sftp-path-wrapper"
|
||||
@click="setPathEditable(true)">
|
||||
<!-- 路径输入框 -->
|
||||
<div v-if="pathEditable">
|
||||
<a-input v-model="pathInput"
|
||||
placeholder="文件夹路径"
|
||||
allow-clear
|
||||
@press-enter="doChangePath" />
|
||||
</div>
|
||||
<!-- 路径视图 -->
|
||||
<a-breadcrumb v-else>
|
||||
<!-- 根目录 -->
|
||||
<a-breadcrumb-item class="sftp-path-unit"
|
||||
@click.stop="loadFileList('/')">
|
||||
<icon-home />
|
||||
</a-breadcrumb-item>
|
||||
<!-- 子目录 -->
|
||||
<a-breadcrumb-item class="sftp-path-unit"
|
||||
v-for="path in analysisPaths"
|
||||
@click.stop="loadFileList(path.path)">
|
||||
{{ path.name }}
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧操作 -->
|
||||
<a-space class="sftp-table-header-right">
|
||||
<!-- 路径编辑模式-右侧操作 -->
|
||||
<a-space v-if="pathEditable" class="sftp-table-header-right">
|
||||
<!-- 进入 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:overlay-inverse="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="进入">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="doChangePath">
|
||||
<icon-right />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 取消 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:overlay-inverse="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="取消">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="setPathEditable(false)">
|
||||
<icon-stop />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<!-- 非路径编辑模式-右侧操作 -->
|
||||
<a-space v-else class="sftp-table-header-right">
|
||||
<!-- 刷新 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
@@ -40,7 +81,8 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="刷新">
|
||||
<span class="click-icon-wrapper header-action-icon">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="loadFileList">
|
||||
<icon-refresh />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
@@ -51,9 +93,11 @@
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="显示隐藏文件">
|
||||
<span class="click-icon-wrapper header-action-icon">
|
||||
<icon-eye />
|
||||
:content="showHiddenFile ? '不显示隐藏文件' : '显示隐藏文件'">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="toggleShowHiddenFile">
|
||||
<icon-eye-invisible v-if="showHiddenFile" />
|
||||
<icon-eye v-else />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 创建文件 -->
|
||||
@@ -64,7 +108,8 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="创建文件">
|
||||
<span class="click-icon-wrapper header-action-icon">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="createFile">
|
||||
<icon-drive-file />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
@@ -76,23 +121,25 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="创建文件夹">
|
||||
<span class="click-icon-wrapper header-action-icon">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="createDir">
|
||||
<icon-folder-add />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 删除 -->
|
||||
<!-- 删除选中文件 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:overlay-inverse="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="删除">
|
||||
<span class="click-icon-wrapper header-action-icon">
|
||||
content="删除选中文件">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="deleteSelectFiles">
|
||||
<icon-delete />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 复制 -->
|
||||
<!-- 复制 FIXME 不行就删除 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:overlay-inverse="true"
|
||||
@@ -100,11 +147,11 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="复制">
|
||||
<span class="click-icon-wrapper header-action-icon">
|
||||
<span v-if="false" class="click-icon-wrapper header-action-icon">
|
||||
<icon-copy />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 移动 -->
|
||||
<!-- 移动 FIXME 不行就删除 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:overlay-inverse="true"
|
||||
@@ -124,7 +171,8 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="上传">
|
||||
<span class="click-icon-wrapper header-action-icon">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="uploadFile">
|
||||
<icon-upload />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
@@ -136,7 +184,8 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="下载">
|
||||
<span class="click-icon-wrapper header-action-icon">
|
||||
<span class="click-icon-wrapper header-action-icon"
|
||||
@click="downloadFile">
|
||||
<icon-download />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
@@ -151,6 +200,91 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PathAnalysis } from '@/utils/file';
|
||||
import type { ISftpSession } from '../../types/terminal.type';
|
||||
import { ref, watch } from 'vue';
|
||||
import { getParentPath, getPathAnalysis } from '@/utils/file';
|
||||
|
||||
const props = defineProps<{
|
||||
currentPath: string;
|
||||
session: ISftpSession | undefined,
|
||||
selectedFiles: Array<string>
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(['loadFile']);
|
||||
|
||||
const showHiddenFile = ref(false);
|
||||
const analysisPaths = ref<Array<PathAnalysis>>([]);
|
||||
const pathEditable = ref(false);
|
||||
const pathInput = ref('');
|
||||
|
||||
// 监听路径变化
|
||||
watch(() => props.currentPath, (path) => {
|
||||
if (path) {
|
||||
analysisPaths.value = getPathAnalysis(path);
|
||||
} else {
|
||||
analysisPaths.value = [];
|
||||
}
|
||||
});
|
||||
|
||||
// 返回上级目录
|
||||
const backParentPath = () => {
|
||||
loadFileList(getParentPath(props.currentPath));
|
||||
};
|
||||
|
||||
// 设置命令编辑模式
|
||||
const setPathEditable = (editable: boolean) => {
|
||||
pathEditable.value = editable;
|
||||
pathInput.value = editable ? props.currentPath : '';
|
||||
};
|
||||
|
||||
// 执行修改目录
|
||||
const doChangePath = () => {
|
||||
loadFileList(pathInput.value);
|
||||
setPathEditable(false);
|
||||
};
|
||||
|
||||
// 加载文件列表
|
||||
const loadFileList = (path: string = props.currentPath) => {
|
||||
emits('loadFile', path);
|
||||
};
|
||||
|
||||
// 设置是否显示隐藏文件
|
||||
const toggleShowHiddenFile = () => {
|
||||
showHiddenFile.value = !showHiddenFile.value;
|
||||
// 设置显示状态并且刷新
|
||||
if (props.session) {
|
||||
props.session.setShowHiddenFile(showHiddenFile.value);
|
||||
loadFileList();
|
||||
}
|
||||
};
|
||||
|
||||
// 创建文件
|
||||
const createFile = () => {
|
||||
// openModal(true, "props.currentPath")
|
||||
};
|
||||
|
||||
// 创建文件夹
|
||||
const createDir = () => {
|
||||
// openModal(false, "props.currentPath")
|
||||
};
|
||||
|
||||
// 删除选中文件
|
||||
const deleteSelectFiles = () => {
|
||||
// confirm
|
||||
|
||||
};
|
||||
|
||||
// 上传文件
|
||||
const uploadFile = () => {
|
||||
// openModal("props.currentPath")
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
const downloadFile = () => {
|
||||
};
|
||||
|
||||
// FIXME 图标宽度提成变量
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-table row-key="name"
|
||||
<a-table row-key="path"
|
||||
class="sftp-table"
|
||||
label-align="left"
|
||||
:columns="columns"
|
||||
@@ -44,7 +44,7 @@
|
||||
<!-- 修改时间/操作 -->
|
||||
<template #modifyTime="{ record }">
|
||||
<!-- 修改时间 -->
|
||||
<span v-if="editName !== record.name">{{ dateFormat(new Date(record.modifyTime)) }}</span>
|
||||
<span v-if="editRowName !== record.name">{{ dateFormat(new Date(record.modifyTime)) }}</span>
|
||||
<!-- 操作 -->
|
||||
<a-space v-else>
|
||||
<!-- 复制路径 -->
|
||||
@@ -67,10 +67,10 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="删除">
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="deleteFile(record.path)">
|
||||
<icon-delete />
|
||||
</span>
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="deleteFile(record.path)">
|
||||
<icon-delete />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 下载 -->
|
||||
<a-tooltip position="top"
|
||||
@@ -79,10 +79,10 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="下载">
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="downloadFile(record.path)">
|
||||
<icon-download />
|
||||
</span>
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="downloadFile(record.path)">
|
||||
<icon-download />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 移动 -->
|
||||
<a-tooltip position="top"
|
||||
@@ -91,10 +91,10 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="移动">
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="moveFile(record.path)">
|
||||
<icon-paste />
|
||||
</span>
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="moveFile(record.path)">
|
||||
<icon-paste />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 提权 -->
|
||||
<a-tooltip position="top"
|
||||
@@ -103,10 +103,10 @@
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="提权">
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="chmodFile(record.path)">
|
||||
<icon-user-group />
|
||||
</span>
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="chmodFile(record.path, record.attr)">
|
||||
<icon-user-group />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
@@ -129,20 +129,37 @@
|
||||
import useCopy from '@/hooks/copy';
|
||||
import { FILE_TYPE } from '../../types/terminal.const';
|
||||
|
||||
import type { ISftpSession } from '../../types/terminal.type';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { computed } from 'vue/dist/vue';
|
||||
|
||||
const props = defineProps<{
|
||||
session: ISftpSession | undefined;
|
||||
list: Array<SftpFile>;
|
||||
loading: boolean;
|
||||
selectedFiles: Array<string>;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(['update:selectedFiles']);
|
||||
|
||||
const rowSelection = useRowSelection({ width: 40 });
|
||||
const { copy } = useCopy();
|
||||
|
||||
const selectedKeys = ref<Array<string>>([]);
|
||||
const editName = ref<string>('');
|
||||
const selectedKeys = computed({
|
||||
get() {
|
||||
return props.selectedFiles;
|
||||
},
|
||||
set(e) {
|
||||
emits('update:selectedFiles', e);
|
||||
}
|
||||
});
|
||||
|
||||
const editRowName = ref<string>('');
|
||||
|
||||
// 设置选中状态
|
||||
const setEditable = (record: TableData) => {
|
||||
editName.value = record.name;
|
||||
editRowName.value = record.name;
|
||||
record.hover = true;
|
||||
};
|
||||
|
||||
@@ -150,8 +167,8 @@
|
||||
const unsetEditable = (record: TableData) => {
|
||||
setTimeout(() => {
|
||||
// 等待后如果还是当前行 但是未被选中则代表已经被失焦
|
||||
if (record.name === editName.value && !record.hover) {
|
||||
editName.value = '';
|
||||
if (record.name === editRowName.value && !record.hover) {
|
||||
editRowName.value = '';
|
||||
}
|
||||
}, 20);
|
||||
record.hover = false;
|
||||
@@ -159,6 +176,7 @@
|
||||
|
||||
// 删除文件
|
||||
const deleteFile = (path: string) => {
|
||||
// confirm
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
@@ -167,10 +185,13 @@
|
||||
|
||||
// 移动文件
|
||||
const moveFile = (path: string) => {
|
||||
// openModal('path')
|
||||
};
|
||||
|
||||
// 文件提权
|
||||
const chmodFile = (path: string) => {
|
||||
const chmodFile = (path: string, attr: string) => {
|
||||
// openModal('path','mod')
|
||||
|
||||
};
|
||||
|
||||
// 格式化文件类型
|
||||
|
||||
@@ -6,11 +6,18 @@
|
||||
:disabled="!editView">
|
||||
<!-- 左侧面板表格 -->
|
||||
<template #first>
|
||||
<!-- FIXME spin -->
|
||||
<div class="sftp-table-container">
|
||||
<!-- 表头 -->
|
||||
<sftp-table-header class="sftp-table-header" />
|
||||
<sftp-table-header class="sftp-table-header"
|
||||
:current-path="currentPath"
|
||||
:session="session"
|
||||
:selected-files="selectFiles"
|
||||
@load-file="loadFiles" />
|
||||
<!-- 表格 -->
|
||||
<sftp-table class="sftp-table-wrapper"
|
||||
v-model:selected-files="selectFiles"
|
||||
:session="session"
|
||||
:list="fileList"
|
||||
:loading="tableLoading" />
|
||||
</div>
|
||||
@@ -48,19 +55,25 @@
|
||||
const session = ref<ISftpSession>();
|
||||
const currentPath = ref<string>('');
|
||||
const fileList = ref<Array<SftpFile>>(mockData);
|
||||
const selectFiles = ref<Array<string>>([]);
|
||||
const splitSize = ref(1);
|
||||
const editView = ref(true);
|
||||
|
||||
// 连接成功回调
|
||||
const connectCallback = () => {
|
||||
loadFiles(undefined);
|
||||
};
|
||||
|
||||
// 加载文件列表
|
||||
const loadFiles = (path: string | undefined) => {
|
||||
setTableLoading(true);
|
||||
session.value?.list(undefined);
|
||||
session.value?.list(path);
|
||||
};
|
||||
|
||||
// 接收列表回调
|
||||
const resolveList = (result: string, path: string, list: Array<SftpFile>) => {
|
||||
const success = !!Number.parseInt(result);
|
||||
setLoading(false);
|
||||
setTableLoading(false);
|
||||
if (!success) {
|
||||
Message.error('查询失败');
|
||||
return;
|
||||
|
||||
@@ -12,6 +12,8 @@ export default class SftpSession implements ISftpSession {
|
||||
|
||||
public resolver: ISftpSessionResolver;
|
||||
|
||||
private showHiddenFile: boolean;
|
||||
|
||||
private readonly channel: ITerminalChannel;
|
||||
|
||||
constructor(hostId: number,
|
||||
@@ -21,6 +23,7 @@ export default class SftpSession implements ISftpSession {
|
||||
this.sessionId = sessionId;
|
||||
this.channel = channel;
|
||||
this.connected = false;
|
||||
this.showHiddenFile = false;
|
||||
this.resolver = undefined as unknown as ISftpSessionResolver;
|
||||
}
|
||||
|
||||
@@ -36,10 +39,16 @@ export default class SftpSession implements ISftpSession {
|
||||
this.resolver.connectCallback();
|
||||
}
|
||||
|
||||
// 设置显示隐藏文件
|
||||
setShowHiddenFile(show: boolean): void {
|
||||
this.showHiddenFile = show;
|
||||
}
|
||||
|
||||
// 查询文件列表
|
||||
list(path: string | undefined) {
|
||||
this.channel.send(InputProtocol.SFTP_LIST, {
|
||||
sessionId: this.sessionId,
|
||||
showHiddenFile: ~~this.showHiddenFile,
|
||||
path
|
||||
});
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ export const InputProtocol = {
|
||||
// SFTP 文件列表
|
||||
SFTP_LIST: {
|
||||
type: 'ls',
|
||||
template: ['type', 'sessionId', 'path']
|
||||
template: ['type', 'sessionId', 'showHiddenFile', 'path']
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -295,6 +295,8 @@ export interface ISftpSession extends ITerminalSession {
|
||||
|
||||
// 初始化
|
||||
init: (resolver: ISftpSessionResolver) => void;
|
||||
// 设置显示隐藏文件
|
||||
setShowHiddenFile: (show: boolean) => void;
|
||||
// 查询文件列表
|
||||
list: (path: string | undefined) => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user