完善编排的文件操作模块

This commit is contained in:
fofolee 2025-03-23 13:49:17 +08:00
parent 3cec2f94f2
commit 9bcea1e575
5 changed files with 277 additions and 445 deletions

View File

@ -126,6 +126,4 @@ async function archive(
}); });
} }
module.exports = { module.exports = archive;
archive,
};

View File

@ -7,23 +7,22 @@ const path = require("path");
* @param {string} config.filePath 文件路径 * @param {string} config.filePath 文件路径
* @param {string} config.encoding 编码方式 * @param {string} config.encoding 编码方式
* @param {string} config.readMode 读取模式 * @param {string} config.readMode 读取模式
* @param {string} config.flag 读取标志
* @param {number} [config.start] 起始位置 * @param {number} [config.start] 起始位置
* @param {number} [config.length] 读取长度 * @param {number} [config.length] 读取长度
* @returns {Promise<string|Buffer>} 文件内容 * @returns {Promise<string|Buffer>} 文件内容
*/ */
async function read(config) { async function read(config) {
const { filePath, encoding, readMode, flag, start, length } = config; const { filePath, encoding, readMode, start, length } = config;
if (readMode === "all") { if (readMode === "all") {
return await fs.readFile(filePath, { encoding, flag }); return await fs.readFile(filePath, { encoding });
} else if ( } else if (
readMode === "start" && readMode === "start" &&
typeof start === "number" && typeof start === "number" &&
typeof length === "number" typeof length === "number"
) { ) {
// 指定位置读取 // 指定位置读取
const fileHandle = await fs.open(filePath, flag || "r"); const fileHandle = await fs.open(filePath, "r");
try { try {
const buffer = Buffer.alloc(length); const buffer = Buffer.alloc(length);
const { bytesRead } = await fileHandle.read(buffer, 0, length, start); const { bytesRead } = await fileHandle.read(buffer, 0, length, start);
@ -39,12 +38,11 @@ async function read(config) {
// 按行读取,暂时使用全部读取然后分行的方式 // 按行读取,暂时使用全部读取然后分行的方式
const content = await fs.readFile(filePath, { const content = await fs.readFile(filePath, {
encoding: encoding || "utf8", encoding: encoding || "utf8",
flag,
}); });
return content.split(/\r?\n/); return content.split(/\r?\n/);
} else { } else {
// 默认使用全部读取 // 默认使用全部读取
return await fs.readFile(filePath, { encoding, flag }); return await fs.readFile(filePath, { encoding });
} }
} }
@ -85,20 +83,12 @@ async function write(config) {
* 文件删除操作 * 文件删除操作
*/ */
async function remove(config) { async function remove(config) {
const { filePath, recursive, force, targetType } = config; const { filePath, recursive, force } = config;
// 检查文件是否存在 // 检查文件是否存在
try { try {
const stats = await fs.lstat(filePath); const stats = await fs.lstat(filePath);
// 检查目标类型
if (targetType === "file" && !stats.isFile()) {
throw new Error("目标不是文件");
}
if (targetType === "directory" && !stats.isDirectory()) {
throw new Error("目标不是目录");
}
// 执行删除操作 // 执行删除操作
if (stats.isDirectory()) { if (stats.isDirectory()) {
await fs.rm(filePath, { recursive, force }); await fs.rm(filePath, { recursive, force });
@ -115,39 +105,16 @@ async function remove(config) {
} }
/** /**
* 文件管理操作 * 文件权限操作
*/ */
async function manage(config) { async function permission(config) {
const { const { filePath, operationType, mode, uid, gid, recursive } = config;
filePath,
manageOperation,
newPath,
mode,
uid,
gid,
recursive,
targetType,
} = config;
try {
// 检查文件是否存在 // 检查文件是否存在
const stats = await fs.lstat(filePath); const stats = await fs.lstat(filePath);
// 检查目标类型 if (operationType === "chmod") {
if (targetType === "file" && !stats.isFile()) {
throw new Error("目标不是文件");
}
if (targetType === "directory" && !stats.isDirectory()) {
throw new Error("目标不是目录");
}
switch (manageOperation) {
case "rename":
// 确保目标目录存在
await fs.mkdir(path.dirname(newPath), { recursive: true });
await fs.rename(filePath, newPath);
break;
case "chmod":
if (recursive && stats.isDirectory()) { if (recursive && stats.isDirectory()) {
const walk = async (dir) => { const walk = async (dir) => {
const files = await fs.readdir(dir); const files = await fs.readdir(dir);
@ -166,9 +133,7 @@ async function manage(config) {
} else { } else {
await fs.chmod(filePath, parseInt(mode, 8)); await fs.chmod(filePath, parseInt(mode, 8));
} }
break; } else if (operationType === "chown") {
case "chown":
if (recursive && stats.isDirectory()) { if (recursive && stats.isDirectory()) {
await fs.chown(filePath, uid, gid); await fs.chown(filePath, uid, gid);
const walk = async (dir) => { const walk = async (dir) => {
@ -188,10 +153,71 @@ async function manage(config) {
} else { } else {
await fs.chown(filePath, uid, gid); await fs.chown(filePath, uid, gid);
} }
break; } else {
throw new Error(`不支持的操作类型: ${operationType}`);
}
} catch (error) {
if (error.code === "ENOENT") {
throw new Error("文件或目录不存在");
}
throw error;
}
}
default: /**
throw new Error(`不支持的操作类型: ${manageOperation}`); * 文件复制移动操作
*/
async function transfer(config) {
const { filePath, transferOperation, newPath } = config;
// 检查文件是否存在
try {
const stats = await fs.lstat(filePath);
// 确保目标目录存在
await fs.mkdir(path.dirname(newPath), { recursive: true });
if (transferOperation === "copy") {
const processBar = await quickcommand.showProcessBar({
text: "复制中...",
});
if (stats.isDirectory()) {
// 复制目录
const copyDir = async (src, dest) => {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(src);
for (const entry of entries) {
const srcPath = path.join(src, entry);
const destPath = path.join(dest, entry);
const entryStat = await fs.lstat(srcPath);
if (entryStat.isDirectory()) {
await copyDir(srcPath, destPath);
} else {
await fs.copyFile(srcPath, destPath);
}
quickcommand.updateProcessBar({ text: entry }, processBar);
}
};
await copyDir(filePath, newPath);
} else {
// 复制文件
await fs.copyFile(filePath, newPath);
}
processBar.close();
} else if (transferOperation === "rename") {
const processBar = await quickcommand.showProcessBar({
text: "处理中...",
});
await fs.rename(filePath, newPath);
processBar.close();
} else {
throw new Error(`不支持的操作类型: ${transferOperation}`);
}
} catch (error) {
processBar?.close();
if (error.code === "ENOENT") {
throw new Error("文件或目录不存在");
}
throw error;
} }
} }
@ -246,69 +272,49 @@ async function list(config) {
} }
} }
/**
* 格式化文件大小
* @param {number} bytes 字节数
* @returns {string} 格式化后的文件大小
*/
function formatBytes(bytes) {
const units = ["B", "KB", "MB", "GB", "TB"];
let unitIndex = 0;
while (bytes >= 1024 && unitIndex < units.length - 1) {
bytes /= 1024;
unitIndex++;
}
return `${bytes.toFixed(2)} ${units[unitIndex]}`;
}
/** /**
* 获取文件或目录状态 * 获取文件或目录状态
* @param {Object} config 配置对象 * @param {Object} config 配置对象
* @param {string} config.filePath 路径 * @param {string} config.filePath 路径
* @param {string} config.targetType 目标类型
* @param {string} config.statMode 检查类型
* @param {boolean} [config.followSymlinks] 是否跟随符号链接 * @param {boolean} [config.followSymlinks] 是否跟随符号链接
* @returns {Promise<Object>} 状态信息 * @returns {Promise<Object>} 状态信息
*/ */
async function stat(config) { async function stat(config) {
const { filePath, targetType, statMode, followSymlinks } = config; const { filePath, followSymlinks } = config;
try { try {
const statFn = followSymlinks ? fs.stat : fs.lstat; const statFn = followSymlinks ? fs.stat : fs.lstat;
const stats = await statFn(filePath); const stats = await statFn(filePath);
// 检查目标类型是否匹配
if (targetType === "file" && !stats.isFile()) {
throw new Error("目标不是文件");
}
if (targetType === "directory" && !stats.isDirectory()) {
throw new Error("目标不是目录");
}
// 根据检查类型返回不同的信息
if (statMode === "exists") {
return {
exists: true,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
};
} else if (statMode === "status") {
return { return {
exists: true, exists: true,
isFile: stats.isFile(), isFile: stats.isFile(),
isDirectory: stats.isDirectory(), isDirectory: stats.isDirectory(),
isSymbolicLink: stats.isSymbolicLink(), isSymbolicLink: stats.isSymbolicLink(),
size: stats.size, humanReadSize: formatBytes(stats.size),
mode: stats.mode, ...stats,
uid: stats.uid,
gid: stats.gid,
accessTime: stats.atime,
modifyTime: stats.mtime,
changeTime: stats.ctime,
birthTime: stats.birthtime,
}; };
} else {
// 默认返回基本信息
return {
exists: true,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
isSymbolicLink: stats.isSymbolicLink(),
};
}
} catch (error) { } catch (error) {
if (error.code === "ENOENT") { if (error.code === "ENOENT") {
return { return {
exists: false, exists: false,
...(statMode === "exists" && {
isFile: false, isFile: false,
isDirectory: false, isDirectory: false,
}),
}; };
} }
throw error; throw error;
@ -319,39 +325,28 @@ async function stat(config) {
* 统一的文件操作入口 * 统一的文件操作入口
*/ */
async function operation(config) { async function operation(config) {
if (!config || typeof config !== "object") { const { operation: op } = config;
throw new Error("配置参数必须是一个对象");
}
const { operation } = config; switch (op) {
if (!operation) {
throw new Error("缺少必要的 operation 参数");
}
switch (operation) {
case "read": case "read":
return await read(config); return await read(config);
case "write": case "write":
return await write(config); return await write(config);
case "list": case "list":
return await list(config); return await list(config);
case "stat":
return await stat(config);
case "delete": case "delete":
return await remove(config); return await remove(config);
case "manage": case "stat":
return await manage(config); return await stat(config);
case "permission":
return await permission(config);
case "transfer":
return await transfer(config);
default: default:
throw new Error(`不支持的操作类型: ${operation}`); throw new Error(`不支持的操作类型: ${op}`);
} }
} }
module.exports = { module.exports = {
read,
write,
list,
stat,
remove,
manage,
operation, operation,
}; };

View File

@ -43,10 +43,16 @@
<div class="q-ml-xs">删除</div> <div class="q-ml-xs">删除</div>
</div> </div>
</q-tab> </q-tab>
<q-tab name="manage" no-caps> <q-tab name="transfer" no-caps>
<div class="row items-center no-wrap"> <div class="row items-center no-wrap">
<q-icon name="settings" size="16px" /> <q-icon name="drive_file_move" size="16px" />
<div class="q-ml-xs">管理</div> <div class="q-ml-xs">复制移动</div>
</div>
</q-tab>
<q-tab name="permission" no-caps>
<div class="row items-center no-wrap">
<q-icon name="security" size="16px" />
<div class="q-ml-xs">权限</div>
</div> </div>
</q-tab> </q-tab>
</q-tabs> </q-tabs>
@ -64,10 +70,7 @@
dialog: { dialog: {
options: { options: {
title: '选择文件', title: '选择文件',
properties: [ properties: ['openDirectory', 'openFile', 'showHiddenFiles'],
shouldSelectDirectory ? 'openDirectory' : 'openFile',
'showHiddenFiles',
],
}, },
}, },
}" }"
@ -100,25 +103,13 @@
map-options map-options
@update:model-value="updateArgvs('readMode', $event)" @update:model-value="updateArgvs('readMode', $event)"
/> />
<q-select
v-model="argvs.readFlag"
:options="readFlagOptions"
label="读取标志"
dense
options-dense
filled
class="col-grow"
emit-value
map-options
@update:model-value="updateArgvs('readFlag', $event)"
/>
<NumberInput <NumberInput
v-if="argvs.readMode === 'start'" v-if="argvs.readMode === 'start'"
:model-value="argvs.start" :model-value="argvs.start"
@update:model-value="updateArgvs('start', $event)" @update:model-value="updateArgvs('start', $event)"
label="起始位置" label="起始位置"
icon="first_page" icon="first_page"
class="col-grow" class="col"
/> />
<NumberInput <NumberInput
v-if="argvs.readMode === 'start'" v-if="argvs.readMode === 'start'"
@ -126,7 +117,7 @@
@update:model-value="updateArgvs('length', $event)" @update:model-value="updateArgvs('length', $event)"
label="读取长度" label="读取长度"
icon="last_page" icon="last_page"
class="col-grow" class="col"
/> />
</div> </div>
</template> </template>
@ -186,7 +177,7 @@
@update:model-value="updateArgvs('content', $event)" @update:model-value="updateArgvs('content', $event)"
label="写入内容" label="写入内容"
icon="edit" icon="edit"
class="col-12" class="col-grow"
/> />
</div> </div>
</template> </template>
@ -194,80 +185,87 @@
<!-- 删除操作配置 --> <!-- 删除操作配置 -->
<template v-if="argvs.operation === 'delete'"> <template v-if="argvs.operation === 'delete'">
<div class="row q-gutter-sm"> <div class="row q-gutter-sm">
<q-select <CheckButton
v-model="argvs.targetType"
:options="targetTypeOptions"
label="目标类型"
dense
filled
options-dense
class="col-grow"
emit-value
map-options
@update:model-value="updateArgvs('targetType', $event)"
/>
<q-checkbox
v-model="argvs.recursive" v-model="argvs.recursive"
label="递归删除" label="递归删除"
v-if="argvs.targetType === 'directory'" class="col"
dense
class="col-grow"
@update:model-value="updateArgvs('recursive', $event)" @update:model-value="updateArgvs('recursive', $event)"
/> />
<q-checkbox <CheckButton
v-model="argvs.force" v-model="argvs.force"
label="强制删除" label="强制删除"
dense class="col"
class="col-grow"
@update:model-value="updateArgvs('force', $event)" @update:model-value="updateArgvs('force', $event)"
/> />
</div> </div>
</template> </template>
<!-- 管理操作配置 --> <!-- 列目录操作配置 -->
<template v-if="argvs.operation === 'manage'"> <template v-if="argvs.operation === 'list'">
<div class="row q-gutter-sm"> <div class="row q-gutter-sm">
<q-select <CheckButton
v-model="argvs.targetType" v-model="argvs.recursive"
:options="targetTypeOptions" label="递归列出子目录"
label="目标类型" class="col"
dense @update:model-value="updateArgvs('recursive', $event)"
options-dense
filled
class="col-grow"
emit-value
map-options
@update:model-value="updateArgvs('targetType', $event)"
/> />
<q-select <CheckButton
v-model="argvs.manageOperation" v-model="argvs.showHidden"
:options="manageOperationOptions" label="显示隐藏文件"
label="管理操作" class="col"
dense @update:model-value="updateArgvs('showHidden', $event)"
options-dense
filled
class="col-grow"
emit-value
map-options
@update:model-value="updateArgvs('manageOperation', $event)"
/> />
</div> </div>
</template>
<!-- 重命名操作 --> <!-- 状态操作配置 -->
<template v-if="argvs.manageOperation === 'rename'"> <template v-if="argvs.operation === 'stat'">
<div class="row q-gutter-sm"> <div class="row q-gutter-sm">
<VariableInput <CheckButton
:model-value="argvs.newPath" v-model="argvs.followSymlinks"
@update:model-value="updateArgvs('newPath', $event)" label="跟随符号链接"
label="新路径" @update:model-value="updateArgvs('followSymlinks', $event)"
icon="drive_file_rename_outline"
class="col-grow" class="col-grow"
/> />
</div> </div>
</template> </template>
<!-- 复制移动操作配置 -->
<template v-if="argvs.operation === 'transfer'">
<div class="row q-gutter-sm">
<div class="col-6">
<VariableInput
:model-value="argvs.newPath"
@update:model-value="updateArgvs('newPath', $event)"
label="目标路径"
icon="drive_file_rename_outline"
class="col-6"
/>
</div>
<div class="col">
<ButtonGroup
v-model="argvs.transferOperation"
:options="transferOperationOptions"
height="36px"
@update:model-value="updateArgvs('transferOperation', $event)"
/>
</div>
</div>
</template>
<!-- 权限操作配置 -->
<template v-if="argvs.operation === 'permission'">
<div class="row q-gutter-sm">
<ButtonGroup
v-model="argvs.operationType"
class="col"
:options="operationTypeOptions"
@update:model-value="updateArgvs('operationType', $event)"
/>
</div>
<!-- 修改权限操作 --> <!-- 修改权限操作 -->
<template v-if="argvs.manageOperation === 'chmod'"> <template v-if="argvs.operationType === 'chmod'">
<div class="row q-gutter-sm"> <div class="row q-gutter-sm">
<q-select <q-select
v-model="argvs.mode" v-model="argvs.mode"
@ -290,11 +288,9 @@
</q-item> </q-item>
</template> </template>
</q-select> </q-select>
<q-checkbox <CheckButton
v-model="argvs.recursive" v-model="argvs.recursive"
label="递归修改" label="递归修改"
v-if="argvs.targetType === 'directory'"
dense
class="col-grow" class="col-grow"
@update:model-value="updateArgvs('recursive', $event)" @update:model-value="updateArgvs('recursive', $event)"
/> />
@ -302,7 +298,7 @@
</template> </template>
<!-- 修改所有者操作 --> <!-- 修改所有者操作 -->
<template v-if="argvs.manageOperation === 'chown'"> <template v-if="argvs.operationType === 'chown'">
<div class="row q-gutter-sm"> <div class="row q-gutter-sm">
<NumberInput <NumberInput
:model-value="argvs.uid" :model-value="argvs.uid"
@ -318,75 +314,15 @@
icon="group" icon="group"
class="col-grow" class="col-grow"
/> />
<q-checkbox <CheckButton
v-model="argvs.recursive" v-model="argvs.recursive"
label="递归修改" label="递归修改"
v-if="argvs.targetType === 'directory'"
dense
class="col-grow" class="col-grow"
@update:model-value="updateArgvs('recursive', $event)" @update:model-value="updateArgvs('recursive', $event)"
/> />
</div> </div>
</template> </template>
</template> </template>
<!-- 列目录操作配置 -->
<template v-if="argvs.operation === 'list'">
<div class="row q-gutter-sm q-px-xs">
<q-checkbox
v-model="argvs.recursive"
label="递归列出子目录"
dense
class="col-grow"
@update:model-value="updateArgvs('recursive', $event)"
/>
<q-checkbox
v-model="argvs.showHidden"
label="显示隐藏文件"
dense
class="col-grow"
@update:model-value="updateArgvs('showHidden', $event)"
/>
</div>
</template>
<!-- 状态操作配置 -->
<template v-if="argvs.operation === 'stat'">
<div class="row q-gutter-sm">
<q-select
v-model="argvs.targetType"
:options="targetTypeOptions"
label="目标类型"
dense
filled
options-dense
class="col-grow"
emit-value
map-options
@update:model-value="updateArgvs('targetType', $event)"
/>
<q-select
v-model="argvs.statMode"
:options="statModeOptions"
label="检查类型"
dense
filled
options-dense
class="col-grow"
emit-value
map-options
@update:model-value="updateArgvs('statMode', $event)"
/>
<q-checkbox
v-model="argvs.followSymlinks"
label="跟随符号链接"
v-if="argvs.statMode === 'status'"
@update:model-value="updateArgvs('followSymlinks', $event)"
dense
class="col-grow"
/>
</div>
</template>
</div> </div>
</template> </template>
@ -394,7 +330,9 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import VariableInput from "components/composer/common/VariableInput.vue"; import VariableInput from "components/composer/common/VariableInput.vue";
import NumberInput from "components/composer/common/NumberInput.vue"; import NumberInput from "components/composer/common/NumberInput.vue";
import { stringifyArgv, parseFunction } from "js/composer/formatString"; import ButtonGroup from "components/composer/common/ButtonGroup.vue";
import CheckButton from "components/composer/common/CheckButton.vue";
import { stringifyArgv } from "js/composer/formatString";
import { newVarInputVal } from "js/composer/varInputValManager"; import { newVarInputVal } from "js/composer/varInputValManager";
// //
@ -418,11 +356,6 @@ const READ_MODE_OPTIONS = [
{ label: "按行读取", value: "line" }, { label: "按行读取", value: "line" },
]; ];
const READ_FLAG_OPTIONS = [
{ label: "同步读取", value: "sync" },
{ label: "异步读取", value: "async" },
];
const WRITE_MODE_OPTIONS = [ const WRITE_MODE_OPTIONS = [
{ label: "覆盖写入", value: "write" }, { label: "覆盖写入", value: "write" },
{ label: "追加写入", value: "append" }, { label: "追加写入", value: "append" },
@ -435,20 +368,19 @@ const WRITE_FLAG_OPTIONS = [
{ label: "777", value: "777", hint: "所有人读写执行" }, { label: "777", value: "777", hint: "所有人读写执行" },
]; ];
const STAT_MODE_OPTIONS = [
{ label: "检查存在", value: "exists" },
{ label: "完整状态", value: "status" },
];
const TARGET_TYPE_OPTIONS = [ const TARGET_TYPE_OPTIONS = [
{ label: "文件", value: "file" }, { label: "文件", value: "file" },
{ label: "目录", value: "directory" }, { label: "目录", value: "directory" },
]; ];
const MANAGE_OPERATION_OPTIONS = [ const OPERATION_TYPE_OPTIONS = [
{ label: "重命名", value: "rename" }, { label: "修改权限", value: "chmod", icon: "lock" },
{ label: "修改权限", value: "chmod" }, { label: "修改所有者", value: "chown", icon: "person" },
{ label: "修改所有者", value: "chown" }, ];
const TRANSFER_OPERATION_OPTIONS = [
{ label: "移动/重命名", value: "rename", icon: "drive_file_rename_outline" },
{ label: "复制", value: "copy", icon: "content_copy" },
]; ];
export default defineComponent({ export default defineComponent({
@ -456,6 +388,8 @@ export default defineComponent({
components: { components: {
VariableInput, VariableInput,
NumberInput, NumberInput,
ButtonGroup,
CheckButton,
}, },
props: { props: {
modelValue: { modelValue: {
@ -468,44 +402,34 @@ export default defineComponent({
return { return {
encodingOptions: ENCODING_OPTIONS, encodingOptions: ENCODING_OPTIONS,
readModeOptions: READ_MODE_OPTIONS, readModeOptions: READ_MODE_OPTIONS,
readFlagOptions: READ_FLAG_OPTIONS,
writeModeOptions: WRITE_MODE_OPTIONS, writeModeOptions: WRITE_MODE_OPTIONS,
writeFlagOptions: WRITE_FLAG_OPTIONS, writeFlagOptions: WRITE_FLAG_OPTIONS,
statModeOptions: STAT_MODE_OPTIONS,
targetTypeOptions: TARGET_TYPE_OPTIONS, targetTypeOptions: TARGET_TYPE_OPTIONS,
manageOperationOptions: MANAGE_OPERATION_OPTIONS, operationTypeOptions: OPERATION_TYPE_OPTIONS,
transferOperationOptions: TRANSFER_OPERATION_OPTIONS,
defaultArgvs: { defaultArgvs: {
operation: "read", operation: "read",
filePath: newVarInputVal("str"), filePath: newVarInputVal("str"),
encoding: "utf8", encoding: "utf8",
readMode: "all", readMode: "all",
readFlag: "async",
start: 0, start: 0,
length: 100, length: 100,
targetType: "file", targetType: "file",
writeMode: "write", writeMode: "write",
writeFlag: "644", writeFlag: "644",
statMode: "exists", followSymlinks: true,
followSymlinks: false, mode: "644",
recursive: false, recursive: false,
force: false, force: false,
showHidden: false, showHidden: false,
manageOperation: "rename", operationType: "chmod",
transferOperation: "rename",
}, },
}; };
}, },
computed: { computed: {
argvs() { argvs() {
return ( return this.modelValue.argvs || this.defaultArgvs;
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
shouldSelectDirectory() {
return (
this.argvs.operation === "list" ||
(this.argvs.targetType === "directory" &&
["delete", "manage", "stat"].includes(this.argvs.operation))
);
}, },
}, },
methods: { methods: {
@ -519,7 +443,6 @@ export default defineComponent({
switch (argvs.operation) { switch (argvs.operation) {
case "read": case "read":
params.encoding = argvs.encoding; params.encoding = argvs.encoding;
params.readFlag = argvs.readFlag;
params.readMode = argvs.readMode; params.readMode = argvs.readMode;
if (argvs.readMode === "start") { if (argvs.readMode === "start") {
params.start = Number(argvs.start) || 0; params.start = Number(argvs.start) || 0;
@ -537,32 +460,33 @@ export default defineComponent({
case "list": case "list":
params.targetType = "directory"; params.targetType = "directory";
params.recursive = argvs.recursive; params.recursive = argvs.recursive;
params.showHidden = argvs.showHidden;
break; break;
case "delete": case "delete":
params.targetType = argvs.targetType;
params.force = argvs.force; params.force = argvs.force;
params.recursive = params.recursive = argvs.recursive;
argvs.recursive && argvs.targetType === "directory";
break;
case "manage":
params.targetType = argvs.targetType;
params.manageOperation = argvs.manageOperation;
if (argvs.manageOperation === "rename") {
params.newPath = argvs.newPath;
} else {
if (argvs.mode) params.mode = argvs.mode;
if (argvs.uid) params.uid = argvs.uid;
if (argvs.gid) params.gid = argvs.gid;
}
break; break;
case "stat": case "stat":
params.targetType = argvs.targetType;
params.statMode = argvs.statMode;
params.followSymlinks = argvs.followSymlinks; params.followSymlinks = argvs.followSymlinks;
break; break;
case "permission":
params.operationType = argvs.operationType;
if (argvs.operationType === "chmod") {
params.mode = argvs.mode;
} else {
params.uid = argvs.uid;
params.gid = argvs.gid;
}
params.recursive = argvs.recursive;
break;
case "transfer":
params.transferOperation = argvs.transferOperation;
params.newPath = argvs.newPath;
break;
} }
return `${this.modelValue.value}(${stringifyArgv(params)})`; return `${this.modelValue.value}(${stringifyArgv(params)})`;
@ -610,52 +534,6 @@ export default defineComponent({
this.argvs.mode = `${ownerValue}${groupValue}${otherValue}`; this.argvs.mode = `${ownerValue}${groupValue}${otherValue}`;
this.updateArgvs(); this.updateArgvs();
}, },
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
const variableFormatPaths = [
"arg0.filePath",
"arg0.content",
"arg0.newPath",
];
const result = parseFunction(code, { variableFormatPaths });
let params = result.argvs[0];
//
switch (params.operation) {
case "read":
if (params.readMode === "start") {
params.start = Number(params.start) || 0;
params.length = Number(params.length) || 100;
}
break;
case "write":
// flag writeMode
params.writeMode = params.flag === "a" ? "append" : "write";
// mode writeFlag
params.writeFlag = params.mode;
break;
case "list":
params.targetType = "directory";
break;
case "delete":
case "manage":
case "stat":
// 使
break;
}
return params;
} catch (e) {
console.error("解析文件操作参数失败:", e);
}
return argvs;
},
updateModelValue(argvs) { updateModelValue(argvs) {
this.$emit("update:modelValue", { this.$emit("update:modelValue", {
...this.modelValue, ...this.modelValue,
@ -670,19 +548,11 @@ export default defineComponent({
write: "写入", write: "写入",
list: "列目录", list: "列目录",
delete: "删除", delete: "删除",
manage: "管理", stat: "获取状态",
stat: "状态", transfer: "复制移动",
permission: "设置权限",
}; };
const findOptionsLabel = (options, value) => { let operationInfo = operationDict[argvs.operation] + " ";
return options.find((option) => option.value === value)?.label || value;
};
let operationInfo =
argvs.operation === "manage"
? findOptionsLabel(MANAGE_OPERATION_OPTIONS, argvs.manageOperation) +
" "
: argvs.operation === "stat"
? findOptionsLabel(STAT_MODE_OPTIONS, argvs.statMode) + " "
: operationDict[argvs.operation] + " ";
return operationInfo + argvs.filePath.value; return operationInfo + argvs.filePath.value;
}, },
}, },

View File

@ -382,7 +382,7 @@ export default defineComponent({
}, },
computed: { computed: {
argvs() { argvs() {
return this.modelValue.argvs; return this.modelValue.argvs || this.defaultArgvs;
}, },
hasRequestData() { hasRequestData() {
return ["PUT", "POST", "PATCH"].includes(this.argvs.method); return ["PUT", "POST", "PATCH"].includes(this.argvs.method);

View File

@ -507,89 +507,58 @@ interface quickcomposerApi {
file: { file: {
/** /**
* *
*/
operation: {
/**
*
* @param config * @param config
*/ */
operation(config: { operation(config: {
/** 操作类型read-读取write-写入list-列表stat-状态delete-删除manage-管理 */ /** 操作类型read-读取write-写入list-列表stat-状态delete-删除permission-权限transfer-复制移动 */
operation: "read" | "write" | "list" | "stat" | "delete" | "manage"; operation:
| "read"
| "write"
| "list"
| "stat"
| "delete"
| "permission"
| "transfer";
/** 文件路径 */ /** 文件路径 */
filePath: string; filePath: string;
/** 读取操作配置 */ /** 读取操作配置 */
readOptions?: {
/** 编码方式 */
encoding?: BufferEncoding; encoding?: BufferEncoding;
/** 起始位置 */ readMode?: "all" | "start" | "line";
start?: number; start?: number;
/** 结束位置 */ length?: number;
end?: number;
};
/** 写入操作配置 */ /** 写入操作配置 */
writeOptions?: { content?: string;
/** 写入内容 */ flag?: "w" | "a";
content: string;
/** 编码方式 */
encoding?: BufferEncoding;
/** 写入模式overwrite-覆盖append-追加 */
writeMode?: "overwrite" | "append";
/** 文件权限 */
writeFlag?: string;
};
/** 列表操作配置 */
listOptions?: {
/** 是否递归 */
recursive?: boolean;
/** 是否包含隐藏文件 */
includeHidden?: boolean;
};
/** 管理操作配置 */
manageOptions?: {
/** 目标类型file-文件directory-目录 */
targetType?: "file" | "directory";
/** 管理操作类型rename-重命名chmod-修改权限chown-修改所有者 */
manageOperation?: "rename" | "chmod" | "chown";
/** 新路径(重命名时使用) */
newPath?: string;
/** 权限模式(chmod时使用) */
mode?: string | number; mode?: string | number;
/** 用户ID(chown时使用) */ /** 列表操作配置 */
uid?: number;
/** 组ID(chown时使用) */
gid?: number;
/** 是否递归操作 */
recursive?: boolean; recursive?: boolean;
}; showHidden?: boolean;
/** 权限操作配置 */
operationType?: "chmod" | "chown";
uid?: number;
gid?: number;
/** 复制移动操作配置 */
transferOperation?: "copy" | "rename";
newPath?: string;
/** 删除操作配置 */
force?: boolean;
/** 状态操作配置 */
followSymlinks?: boolean;
}): Promise<any>; }): Promise<any>;
};
/**
*
*/
archive: {
/** /**
* *
* @param config * @param operation compress-extract-
* @param format zip, tar, gzip
* @param source /
* @param destination
*/ */
archive(config: { archive(
/** 操作类型compress-压缩extract-解压 */ operation: "compress" | "extract",
operation: "compress" | "extract"; format: "zip" | "tar" | "gzip",
/** 归档格式zip, tar, gzip */ source: string | string[],
format: "zip" | "tar" | "gzip"; destination: string
/** 源文件/文件夹路径 */ ): Promise<void>;
sourcePath: string | string[];
/** 目标路径 */
targetPath: string;
/** 压缩级别(1-9) */
level?: number;
/** 是否保持目录结构 */
preserveStructure?: boolean;
/** 密码保护(仅zip格式支持) */
password?: string;
}): Promise<void>;
};
}; };
system: { system: {
/** /**