VarInput支持选择文件

This commit is contained in:
fofolee 2025-01-06 17:13:17 +08:00
parent 5ea6850bb9
commit 516e6c2d16
8 changed files with 222 additions and 171 deletions

View File

@ -325,7 +325,8 @@ export default defineComponent({
const tempFlow = [ const tempFlow = [
command, command,
{ {
code: `console.log(${command.outputVariable})`, //
code: `${command.outputVariable} && console.log(${command.outputVariable})`,
}, },
]; ];
// //

View File

@ -29,36 +29,68 @@
class="flex-item" class="flex-item"
:style="{ flex: item.width || 12 }" :style="{ flex: item.width || 12 }"
> >
<div v-if="item.type === 'varInput'"> <VariableInput
<VariableInput v-if="item.type === 'varInput'"
:model-value="argvs[index]" :model-value="argvs[index]"
@update:model-value="updateArgv(index, $event)" @update:model-value="updateArgv(index, $event)"
:label="item.label" :label="item.label"
:icon="item.icon" :icon="item.icon"
/> :options="item.options"
</div> />
<div v-else-if="item.type === 'numInput'"> <NumberInput
<NumberInput v-else-if="item.type === 'numInput'"
:model-value="argvs[index]" :model-value="argvs[index]"
@update:model-value="updateArgv(index, $event)" @update:model-value="updateArgv(index, $event)"
:label="item.label" :label="item.label"
:icon="item.icon" :icon="item.icon"
/> />
</div> <ArrayEditor
<div v-else-if="item.type === 'switch'"> v-else-if="item.type === 'arrayEditor'"
<q-toggle :model-value="argvs[index]"
:model-value="argvs[index]" @update:model-value="updateArgv(index, $event)"
@update:model-value="updateArgv(index, $event)" :options="item.options"
:label="item.label" />
:icon="item.icon" <DictEditor
/> v-else-if="item.type === 'dictEditor'"
</div> :model-value="argvs[index]"
<div v-else-if="item.type === 'arrayEditor'"> @update:model-value="updateArgv(index, $event)"
<ArrayEditor :options="item.options"
:model-value="argvs[index]" />
@update:model-value="updateArgv(index, $event)" <q-toggle
/> v-else-if="item.type === 'switch'"
</div> :model-value="argvs[index]"
@update:model-value="updateArgv(index, $event)"
:label="item.label"
:icon="item.icon"
/>
<q-select
v-else-if="item.type === 'select'"
:model-value="argvs[index]"
@update:model-value="updateArgv(index, $event)"
:options="item.options"
>
<template v-slot:prepend>
<q-icon :name="item.icon" />
</template>
</q-select>
<q-input
v-else-if="item.type === 'input'"
:model-value="argvs[index]"
@update:model-value="updateArgv(index, $event)"
:label="item.label"
:icon="item.icon"
>
<template v-slot:prepend>
<q-icon :name="item.icon" />
</template>
</q-input>
<q-checkbox
v-else-if="item.type === 'checkbox'"
:model-value="argvs[index]"
@update:model-value="updateArgv(index, $event)"
:label="item.label"
:icon="item.icon"
/>
</div> </div>
</div> </div>
</div> </div>
@ -69,6 +101,7 @@ 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 ArrayEditor from "components/composer/common/ArrayEditor.vue"; import ArrayEditor from "components/composer/common/ArrayEditor.vue";
import DictEditor from "components/composer/common/DictEditor.vue";
import { stringifyArgv, parseFunction } from "js/composer/formatString"; import { stringifyArgv, parseFunction } from "js/composer/formatString";
export default defineComponent({ export default defineComponent({
@ -77,6 +110,7 @@ export default defineComponent({
VariableInput, VariableInput,
NumberInput, NumberInput,
ArrayEditor, ArrayEditor,
DictEditor,
}, },
props: { props: {
modelValue: { modelValue: {
@ -141,6 +175,8 @@ export default defineComponent({
variableFormatPaths.push(`arg${index}`); variableFormatPaths.push(`arg${index}`);
} else if (item.type === "arrayEditor") { } else if (item.type === "arrayEditor") {
variableFormatPaths.push(`arg${index}[*]`); variableFormatPaths.push(`arg${index}[*]`);
} else if (item.type === "dictEditor") {
variableFormatPaths.push(`arg${index}.**`);
} }
}); });
try { try {

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="array-editor"> <div class="array-editor">
<div v-for="(item, index) in items" :key="index" class="row items-center"> <div v-for="(item, index) in items" :key="index" class="row items-center">
<template v-if="optionsKeys"> <template v-if="options.keys">
<div <div
v-for="key in optionsKeys" v-for="key in options.keys"
:key="key" :key="key"
:class="['col', optionsKeys.length > 1 ? 'q-pr-sm' : '']" :class="['col', options.keys.length > 1 ? 'q-pr-sm' : '']"
> >
<VariableInput <VariableInput
:model-value="item[key]" :model-value="item[key]"
@ -21,7 +21,9 @@
:model-value="item" :model-value="item"
:label="`${label || '项目'} ${index + 1}`" :label="`${label || '项目'} ${index + 1}`"
:icon="icon || 'code'" :icon="icon || 'code'"
:options="options" :options="{
items: options.items,
}"
@update:model-value="(val) => updateItemValue(index, val)" @update:model-value="(val) => updateItemValue(index, val)"
/> />
</div> </div>
@ -81,8 +83,8 @@
* @property {String} label - 输入框标签 * @property {String} label - 输入框标签
* @property {String} icon - 输入框图标 * @property {String} icon - 输入框图标
* @property {Object} options - 配置选项 * @property {Object} options - 配置选项
* @property {String[]} [optionsKeys] - 多键对象模式的键名列表 * @property {String[]} [options.keys] - 多键对象模式的键名列表
* @property {String[]} [options] - 下拉选择模式的选项列表 * @property {String[]} [options.items] - 下拉选择模式的选项列表
* *
* @example * @example
* // * //
@ -95,7 +97,7 @@
* ] * ]
* *
* // * //
* optionsKeys = ['name', 'age', 'email'] * options.keys = ['name', 'age', 'email']
* [ * [
* { * {
* name: { value: "张三", isString: true, __varInputVal__: true }, * name: { value: "张三", isString: true, __varInputVal__: true },
@ -105,7 +107,7 @@
* ] * ]
* *
* // * //
* options = ['选项1', '选项2', '选项3'] * options.items = ['选项1', '选项2', '选项3']
* [ * [
* { * {
* value: "选项1", * value: "选项1",
@ -136,12 +138,8 @@ export default defineComponent({
default: "", default: "",
}, },
options: { options: {
type: Array, type: Object,
default: null, default: () => ({}),
},
optionsKeys: {
type: Array,
default: null,
}, },
}, },
emits: ["update:modelValue"], emits: ["update:modelValue"],
@ -152,9 +150,9 @@ export default defineComponent({
}, },
methods: { methods: {
initializeItems() { initializeItems() {
if (this.optionsKeys) { if (this.options.keys) {
const item = {}; const item = {};
this.optionsKeys.forEach((key) => { this.options.keys.forEach((key) => {
item[key] = { item[key] = {
value: "", value: "",
isString: false, isString: false,
@ -178,9 +176,9 @@ export default defineComponent({
*/ */
addItem() { addItem() {
let newItems = []; let newItems = [];
if (this.optionsKeys) { if (this.options.keys) {
const newItem = {}; const newItem = {};
this.optionsKeys.forEach((key) => { this.options.keys.forEach((key) => {
newItem[key] = { newItem[key] = {
value: "", value: "",
isString: false, isString: false,
@ -208,9 +206,9 @@ export default defineComponent({
const newItems = [...this.items]; const newItems = [...this.items];
newItems.splice(index, 1); newItems.splice(index, 1);
if (newItems.length === 0) { if (newItems.length === 0) {
if (this.optionsKeys) { if (this.options.keys) {
const newItem = {}; const newItem = {};
this.optionsKeys.forEach((key) => { this.options.keys.forEach((key) => {
newItem[key] = { newItem[key] = {
value: "", value: "",
isString: false, isString: false,

View File

@ -137,7 +137,7 @@ export default defineComponent({
}, },
options: { options: {
type: Object, type: Object,
default: null, default: () => ({}),
}, },
}, },
emits: ["update:modelValue"], emits: ["update:modelValue"],

View File

@ -11,10 +11,9 @@
v-if="hasSelectedVariable" v-if="hasSelectedVariable"
flat flat
dense dense
round
icon="close" icon="close"
size="sm" size="sm"
class="clear-btn q-mr-xs" class="clear-btn prepend-btn"
@click="clearVariable" @click="clearVariable"
> >
<q-tooltip>清除选中的变量</q-tooltip> <q-tooltip>清除选中的变量</q-tooltip>
@ -22,10 +21,9 @@
<q-btn <q-btn
flat flat
dense dense
round
:icon="isString ? 'format_quote' : 'data_object'" :icon="isString ? 'format_quote' : 'data_object'"
size="sm" size="sm"
class="string-toggle" class="string-toggle prepend-btn"
@click="toggleType" @click="toggleType"
v-if="!hasSelectedVariable" v-if="!hasSelectedVariable"
> >
@ -37,29 +35,40 @@
</q-btn> </q-btn>
<!-- 选项下拉按钮 --> <!-- 选项下拉按钮 -->
<q-btn-dropdown <q-btn-dropdown
v-if="options && !hasSelectedVariable" v-if="options.items && !hasSelectedVariable"
flat flat
dense dense
size="sm" size="sm"
dropdown-icon="menu" dropdown-icon="menu"
no-icon-animation no-icon-animation
class="options-dropdown" class="options-dropdown prepend-btn"
> >
<q-list class="options-list"> <q-list class="options-item-list">
<q-item <q-item
v-for="option in normalizedOptions" v-for="item in normalizedItems"
:key="getOptionValue(option)" :key="getItemValue(item)"
clickable clickable
v-close-popup v-close-popup
@click="selectOption(option)" @click="selectItem(item)"
class="option-item" class="option-item"
> >
<q-item-section> <q-item-section>
{{ getOptionLabel(option) }} {{ getItemLabel(item) }}
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
</q-btn-dropdown> </q-btn-dropdown>
<q-btn
v-if="!hasSelectedVariable && options.dialog"
flat
dense
icon="file_open"
size="sm"
class="prepend-btn"
@click="handleFileOpen(options.dialog)"
>
<q-tooltip>选择文件</q-tooltip>
</q-btn>
<!-- 变量选择下拉 --> <!-- 变量选择下拉 -->
<q-btn-dropdown <q-btn-dropdown
flat flat
@ -68,7 +77,7 @@
'text-primary': hasSelectedVariable, 'text-primary': hasSelectedVariable,
'text-grey-6': !hasSelectedVariable, 'text-grey-6': !hasSelectedVariable,
}" }"
class="variable-dropdown" class="variable-dropdown prepend-btn"
size="sm" size="sm"
v-if="variables.length" v-if="variables.length"
> >
@ -118,8 +127,12 @@ import { defineComponent, inject } from "vue";
* @property {Object} modelValue - 输入框的值对象 * @property {Object} modelValue - 输入框的值对象
* @property {String} label - 输入框标签 * @property {String} label - 输入框标签
* @property {String} icon - 输入框图标 * @property {String} icon - 输入框图标
* @property {String[]} [options] - 可选的下拉选项列表 * @property {Object} [options] - 可选的配置对象
* * @property {Array} [options.items] - 选项列表
* @property {Boolean} [options.dialog] - 是否显示文件选择对话框
* @property {Object} [options.dialog] - 文件选择对话框配置
* @property {String} [options.dialog.type] - 对话框类型open save
* @property {Object} [options.dialog.options] - 对话框选项对应 utools.showOpenDialog utools.showSaveDialog options
* @example * @example
* // modelValue * // modelValue
* { * {
@ -127,6 +140,18 @@ import { defineComponent, inject } from "vue";
* isString: true, // * isString: true, //
* __varInputVal__: true // * __varInputVal__: true //
* } * }
* @example
* // options
* {
* items: ["item1", "item2", "item3"], //
* dialog: {
* type: "open", // open save
* options: {
* title: "选择文件",
* defaultPath: "/",
* },
* },
* }
*/ */
export default defineComponent({ export default defineComponent({
name: "VariableInput", name: "VariableInput",
@ -144,18 +169,8 @@ export default defineComponent({
label: String, label: String,
icon: String, icon: String,
options: { options: {
type: Array, type: Object,
default: null, default: () => ({}),
validator(value) {
if (!value) return true;
//
return value.every((item) => {
return (
typeof item === "string" || // ["xxx", "yyy"]
(typeof item === "object" && "label" in item && "value" in item) // [{label: "xxx", value: "yyy"}]
);
});
},
}, },
}, },
@ -202,14 +217,13 @@ export default defineComponent({
}, },
// //
normalizedOptions() { normalizedItems() {
console.log(this.options); if (!this.options.items) return [];
if (!this.options) return []; return this.options.items.map((item) => {
return this.options.map((option) => { if (typeof item === "string") {
if (typeof option === "string") { return { label: item, value: item };
return { label: option, value: option };
} }
return option; return item;
}); });
}, },
}, },
@ -245,22 +259,56 @@ export default defineComponent({
}); });
}, },
getOptionLabel(option) { getItemLabel(option) {
return typeof option === "string" ? option : option.label; return typeof option === "string" ? option : option.label;
}, },
getOptionValue(option) { getItemValue(option) {
return typeof option === "string" ? option : option.value; return typeof option === "string" ? option : option.value;
}, },
selectOption(option) { selectItem(option) {
const value = this.getOptionValue(option); const value = this.getItemValue(option);
this.$emit("update:modelValue", { this.$emit("update:modelValue", {
value, value,
isString: true, isString: true,
__varInputVal__: true, __varInputVal__: true,
}); });
}, },
escapePath(paths) {
if (!paths) return null;
if (typeof paths === "string") return paths.replace(/\\/g, "\\\\");
return paths.map((path) => path.replace(/\\/g, "\\\\"));
},
handleFileOpen(dialog) {
let { type, options } = window.lodashM.cloneDeep(dialog);
if (!type) type = "open";
if (type === "open") {
const files = this.escapePath(utools.showOpenDialog(options));
if (!files) return;
if (files.length > 1) {
this.$emit("update:modelValue", {
value: files,
isString: false,
__varInputVal__: true,
});
} else if (files.length === 1) {
this.$emit("update:modelValue", {
value: files[0],
isString: true,
__varInputVal__: true,
});
}
} else {
const file = this.escapePath(utools.showSaveDialog(options));
if (!file) return;
this.$emit("update:modelValue", {
value: file,
isString: true,
__varInputVal__: true,
});
}
},
}, },
}); });
</script> </script>
@ -275,31 +323,20 @@ export default defineComponent({
padding-right: 8px; padding-right: 8px;
} }
/* 字符串切换按钮样式 */ .prepend-btn {
.string-toggle {
min-width: 24px; min-width: 24px;
padding: 4px; padding: 4px;
opacity: 0.6; opacity: 0.6;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.string-toggle:hover { .prepend-btn:hover {
opacity: 1; opacity: 1;
transform: scale(1.05); transform: scale(1.05);
} }
/* 变量下拉框样式 */ .clear-btn:hover {
.variable-dropdown { color: var(--q-negative);
min-width: 32px;
padding: 4px;
opacity: 0.8;
transition: all 0.3s ease;
margin-left: 4px;
}
.variable-dropdown:hover {
opacity: 1;
transform: scale(1.05);
} }
/* 变量列表样式 */ /* 变量列表样式 */
@ -316,7 +353,7 @@ export default defineComponent({
} }
.variable-item:hover { .variable-item:hover {
background: var(--q-primary-opacity-10); backg: var(--q-primary-opacity-10);
} }
.variable-label { .variable-label {
@ -341,33 +378,7 @@ export default defineComponent({
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
} }
/* 清空按钮样式 */ .options-item-list {
.clear-btn {
opacity: 0.6;
transition: all 0.3s ease;
}
.clear-btn:hover {
opacity: 1;
transform: scale(1.1);
color: var(--q-negative);
}
/* 选项下拉框样式 */
.options-dropdown {
min-width: 32px;
padding: 4px;
opacity: 0.8;
transition: all 0.3s ease;
margin-left: 4px;
}
.options-dropdown:hover {
opacity: 1;
transform: scale(1.05);
}
.options-list {
min-width: 120px; min-width: 120px;
padding: 4px; padding: 4px;
} }
@ -383,11 +394,11 @@ export default defineComponent({
} }
.option-item:hover { .option-item:hover {
background: var(--q-primary-opacity-10); backg: var(--q-primary-opacity-10);
} }
/* 暗色模式适配 */ /* 暗色模式适配 */
.body--dark .option-item:hover { .body--dark .option-item:hover {
background: rgba(255, 255, 255, 0.1); backg: rgba(255, 255, 255, 0.1);
} }
</style> </style>

View File

@ -54,25 +54,24 @@
</div> </div>
<!-- 文件路径输入 --> <!-- 文件路径输入 -->
<div class="row q-gutter-sm"> <VariableInput
<VariableInput :model-value="argvs.filePath"
:model-value="argvs.filePath" @update:model-value="updateArgvs('filePath', $event)"
@update:model-value="updateArgvs('filePath', $event)" label="文件路径"
label="文件路径" icon="folder"
icon="folder" class="col-grow"
class="col-grow" :options="{
/> dialog: {
<q-btn options: {
flat title: '选择文件',
dense properties: [
round shouldSelectDirectory ? 'openDirectory' : 'openFile',
icon="folder_open" 'showHiddenFiles',
class="self-center" ],
@click="selectFile" },
> },
<q-tooltip>选择文件</q-tooltip> }"
</q-btn> />
</div>
<!-- 读取操作配置 --> <!-- 读取操作配置 -->
<template v-if="argvs.operation === 'read'"> <template v-if="argvs.operation === 'read'">
@ -584,23 +583,6 @@ export default defineComponent({
this.updateModelValue(argvs); this.updateModelValue(argvs);
}, },
async selectFile() {
const result = window.utools.showOpenDialog({
title: "选择文件",
properties: [
this.shouldSelectDirectory ? "openDirectory" : "openFile",
"showHiddenFiles",
],
buttonLabel: "选择",
});
if (result && result[0]) {
this.updateArgvs("filePath", {
value: result[0],
isString: true,
__varInputVal__: true,
});
}
},
updateMode() { updateMode() {
const modeMap = { const modeMap = {
read: 4, read: 4,

View File

@ -90,7 +90,9 @@
:model-value="argvs.headers['User-Agent']" :model-value="argvs.headers['User-Agent']"
@update:model-value="updateArgvs('headers.User-Agent', $event)" @update:model-value="updateArgvs('headers.User-Agent', $event)"
label="User Agent" label="User Agent"
:options="userAgentOptions" :options="{
items: userAgentOptions,
}"
icon="devices" icon="devices"
class="col-grow" class="col-grow"
/> />

View File

@ -19,6 +19,12 @@ export const fileCommands = {
label: "文件、文件夹或软件的绝对路径", label: "文件、文件夹或软件的绝对路径",
type: "varInput", type: "varInput",
icon: "folder_open", icon: "folder_open",
options: {
dialog: {
type: "open",
options: {},
},
},
}, },
], ],
}, },
@ -31,6 +37,12 @@ export const fileCommands = {
label: "文件、文件夹或软件的绝对路径", label: "文件、文件夹或软件的绝对路径",
type: "varInput", type: "varInput",
icon: "location_on", icon: "location_on",
options: {
dialog: {
type: "open",
options: {},
},
},
}, },
], ],
}, },
@ -43,6 +55,15 @@ export const fileCommands = {
label: "文件或软件的绝对路径", label: "文件或软件的绝对路径",
type: "varInput", type: "varInput",
icon: "folder_open", icon: "folder_open",
options: {
dialog: {
type: "open",
options: {
filters: [{ extensions: ["exe", "app"] }],
properties: ["openFile", "openDirectory"],
},
},
},
}, },
], ],
}, },