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 = [
command,
{
code: `console.log(${command.outputVariable})`,
//
code: `${command.outputVariable} && console.log(${command.outputVariable})`,
},
];
//

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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