编排新增HTML解析、上传、下载

This commit is contained in:
fofolee 2025-01-08 17:35:27 +08:00
parent ebbc7b7661
commit 01183d06e8
10 changed files with 346 additions and 180 deletions

View File

@ -4,6 +4,7 @@ const fs = require("fs");
const kill = require("tree-kill"); const kill = require("tree-kill");
const iconv = require("iconv-lite"); const iconv = require("iconv-lite");
const path = require("path"); const path = require("path");
const axios = require("axios");
const getQuickcommandTempFile = require("./getQuickcommandTempFile"); const getQuickcommandTempFile = require("./getQuickcommandTempFile");
const getCommandToLaunchTerminal = require("./getCommandToLaunchTerminal"); const getCommandToLaunchTerminal = require("./getCommandToLaunchTerminal");
@ -72,9 +73,9 @@ const quickcommand = {
}, },
// 下载文件 // 下载文件
downloadFile: function (url, file = {}) { downloadFile: function (url, file) {
return new Promise((reslove, reject) => { return new Promise((reslove, reject) => {
if (file instanceof Object) if (!file || file instanceof Object)
file = window.utools.showSaveDialog(JSON.parse(JSON.stringify(file))); file = window.utools.showSaveDialog(JSON.parse(JSON.stringify(file)));
axios({ axios({
method: "get", method: "get",
@ -95,13 +96,13 @@ const quickcommand = {
}, },
// 上传文件 // 上传文件
uploadFile: function (url, file = {}, name = "file", formData = {}) { uploadFile: function (url, file, name = "file", formData = {}) {
return new Promise((reslove, reject) => { return new Promise((reslove, reject) => {
var objfile; var objfile;
if (file instanceof File) { if (file instanceof File) {
objfile = file; objfile = file;
} else { } else {
if (file instanceof Object) if (!file || file instanceof Object)
file = window.utools.showOpenDialog( file = window.utools.showOpenDialog(
JSON.parse(JSON.stringify(file)) JSON.parse(JSON.stringify(file))
)[0]; )[0];

View File

@ -0,0 +1,14 @@
const htmlParser = (html, selector = "", attr = "") => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
if (!selector) return doc;
const elements = doc.querySelectorAll(selector);
if (!attr) return elements;
let result = Array.from(elements).map((element) => element[attr]);
if (result.length === 1) return result[0];
return result;
};
module.exports = {
htmlParser,
};

View File

@ -1,9 +1,11 @@
const string = require("./string"); const string = require("./string");
const buffer = require("./buffer"); const buffer = require("./buffer");
const zlib = require("./zlib"); const zlib = require("./zlib");
const htmlParser = require("./htmlParser");
module.exports = { module.exports = {
...string, ...string,
...htmlParser,
buffer, buffer,
zlib, zlib,
}; };

View File

@ -1,82 +1,89 @@
<template> <template>
<div class="array-editor"> <component
<div v-for="(item, index) in items" :key="index" class="row items-center"> :is="!!label ? 'BorderLabel' : 'div'"
<template v-if="optionsKeys.length"> :label="label"
<div :icon="icon"
v-for="key in optionsKeys" :model-value="isCollapse"
:key="key.value" >
:class="[ <div class="array-editor">
key.width ? `col-${key.width}` : 'col', <div v-for="(item, index) in items" :key="index" class="row items-center">
optionsKeys.length > 1 ? 'q-pr-sm' : '', <template v-if="optionsKeys.length">
]" <div
> v-for="key in optionsKeys"
<VariableInput :key="key.value"
:model-value="item[key.value]" :class="[
:label="key.label" key.width ? `col-${key.width}` : 'col',
:no-icon="true" optionsKeys.length > 1 ? 'q-pr-sm' : '',
@update:model-value=" ]"
(val) => updateItemKeyValue(index, key.value, val) >
" <VariableInput
/> :model-value="item[key.value]"
</div> :label="key.label"
</template> :no-icon="true"
<template v-else> @update:model-value="
<div class="col"> (val) => updateItemKeyValue(index, key.value, val)
<VariableInput "
:model-value="item"
:label="`${label || '项目'} ${index + 1}`"
:icon="icon || 'code'"
:options="{
items: options.items,
}"
@update:model-value="(val) => updateItemValue(index, val)"
/>
</div>
</template>
<div class="col-auto">
<div class="btn-container">
<template v-if="items.length === 1">
<q-btn
flat
dense
size="sm"
icon="add"
class="center-btn"
@click="addItem"
/> />
</template> </div>
<template v-else-if="index === items.length - 1"> </template>
<q-btn <template v-else>
flat <div class="col">
dense <VariableInput
size="sm" :model-value="item"
icon="remove" :label="`${label || '项目'} ${index + 1}`"
class="top-btn" :icon="icon || 'code'"
@click="removeItem(index)" :options="{
items: options.items,
}"
@update:model-value="(val) => updateItemValue(index, val)"
/> />
<q-btn </div>
flat </template>
dense <div class="col-auto">
size="sm" <div class="btn-container">
icon="add" <template v-if="items.length === 1">
class="bottom-btn" <q-btn
@click="addItem" flat
/> dense
</template> size="sm"
<template v-else> icon="add"
<q-btn class="center-btn"
flat @click="addItem"
dense />
size="sm" </template>
icon="remove" <template v-else-if="index === items.length - 1">
class="center-btn" <q-btn
@click="removeItem(index)" flat
/> dense
</template> size="sm"
icon="remove"
class="top-btn"
@click="removeItem(index)"
/>
<q-btn
flat
dense
size="sm"
icon="add"
class="bottom-btn"
@click="addItem"
/>
</template>
<template v-else>
<q-btn
flat
dense
size="sm"
icon="remove"
class="center-btn"
@click="removeItem(index)"
/>
</template>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </component>
</template> </template>
<script> <script>
@ -118,16 +125,19 @@
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 { newVarInputVal } from "js/composer/varInputValManager"; import { newVarInputVal } from "js/composer/varInputValManager";
import BorderLabel from "components/composer/common/BorderLabel.vue";
export default defineComponent({ export default defineComponent({
name: "ArrayEditor", name: "ArrayEditor",
components: { components: {
VariableInput, VariableInput,
BorderLabel,
}, },
props: { props: {
modelValue: { modelValue: {
type: Array, type: Array,
required: true, required: true,
default: () => [],
}, },
label: { label: {
type: String, type: String,
@ -137,6 +147,10 @@ export default defineComponent({
type: String, type: String,
default: "", default: "",
}, },
isCollapse: {
type: Boolean,
default: true,
},
options: { options: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),

View File

@ -1,12 +1,20 @@
<template> <template>
<div class="border-label" :class="{ collapsed }" :data-label="label"> <div class="border-label" :class="{ collapsed }" :data-label="label">
<div class="label-header" @click="toggleCollapse"> <div class="label-header row items-center" @click="toggleCollapse">
<q-icon <q-icon
:name="collapsed ? 'expand_more' : 'expand_less'" :name="collapsed ? 'expand_more' : 'expand_less'"
size="16px" size="16px"
class="collapse-icon" class="collapse-icon"
/> />
<span class="label-text">{{ label }}</span> <div class="label-text row items-center">
<q-icon
v-if="icon"
:name="icon"
size="16px"
class="collapse-icon q-pl-sm"
/>
<div class="label-text">{{ label }}</div>
</div>
</div> </div>
<div class="content" :class="{ collapsed }"> <div class="content" :class="{ collapsed }">
<slot></slot> <slot></slot>
@ -33,6 +41,10 @@ export default {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
icon: {
type: String,
default: "",
},
}, },
emits: ["update:modelValue"], emits: ["update:modelValue"],
data() { data() {

View File

@ -1,102 +1,110 @@
<template> <template>
<div class="dict-editor"> <component
<div :is="!!label ? 'BorderLabel' : 'div'"
v-for="(item, index) in items" :label="label"
:key="index" :icon="icon"
class="row q-col-gutter-sm items-center" :model-value="isCollapse"
> >
<div class="col-4"> <div class="dict-editor">
<q-select <div
v-if="options?.items" v-for="(item, index) in items"
:model-value="item.key" :key="index"
:options="options.items" class="row q-col-gutter-sm items-center"
label="名称" >
dense <div class="col-4">
filled <q-select
use-input v-if="options?.items"
input-debounce="0" :model-value="item.key"
:hide-selected="!!inputValue" :options="options.items"
@filter="filterFn" label="名称"
@update:model-value="(val) => handleSelect(val, index)" dense
@input-value="(val) => handleInput(val, index)" filled
@blur="handleBlur" use-input
> input-debounce="0"
<template v-slot:prepend> :hide-selected="!!inputValue"
<q-icon name="code" /> @filter="filterFn"
</template> @update:model-value="(val) => handleSelect(val, index)"
</q-select> @input-value="(val) => handleInput(val, index)"
<q-input @blur="handleBlur"
v-else >
:model-value="item.key" <template v-slot:prepend>
label="名称" <q-icon name="code" />
dense </template>
filled </q-select>
@update:model-value="(val) => updateItemKey(val, index)" <q-input
> v-else
<template v-slot:prepend> :model-value="item.key"
<q-icon name="code" /> label="名称"
</template> dense
</q-input> filled
</div> @update:model-value="(val) => updateItemKey(val, index)"
<div class="col"> >
<VariableInput <template v-slot:prepend>
:model-value="item.value" <q-icon name="code" />
label="值" </template>
icon="code" </q-input>
class="col-grow" </div>
@update:model-value="(val) => updateItemValue(val, index)" <div class="col">
/> <VariableInput
</div> :model-value="item.value"
<div class="col-auto"> label="值"
<div class="btn-container"> icon="code"
<template v-if="items.length === 1"> class="col-grow"
<q-btn @update:model-value="(val) => updateItemValue(val, index)"
flat />
dense </div>
size="sm" <div class="col-auto">
icon="add" <div class="btn-container">
class="center-btn" <template v-if="items.length === 1">
@click="addItem" <q-btn
/> flat
</template> dense
<template v-else-if="index === items.length - 1"> size="sm"
<q-btn icon="add"
flat class="center-btn"
dense @click="addItem"
size="sm" />
icon="remove" </template>
class="top-btn" <template v-else-if="index === items.length - 1">
@click="removeItem(index)" <q-btn
/> flat
<q-btn dense
flat size="sm"
dense icon="remove"
size="sm" class="top-btn"
icon="add" @click="removeItem(index)"
class="bottom-btn" />
@click="addItem" <q-btn
/> flat
</template> dense
<template v-else> size="sm"
<q-btn icon="add"
flat class="bottom-btn"
dense @click="addItem"
size="sm" />
icon="remove" </template>
class="center-btn" <template v-else>
@click="removeItem(index)" <q-btn
/> flat
</template> dense
size="sm"
icon="remove"
class="center-btn"
@click="removeItem(index)"
/>
</template>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </component>
</template> </template>
<script> <script>
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { newVarInputVal } from "js/composer/varInputValManager"; import { newVarInputVal } from "js/composer/varInputValManager";
import VariableInput from "components/composer/common/VariableInput.vue"; import VariableInput from "components/composer/common/VariableInput.vue";
import BorderLabel from "components/composer/common/BorderLabel.vue";
/** /**
* 字典编辑器组件 * 字典编辑器组件
@ -124,16 +132,30 @@ export default defineComponent({
name: "DictEditor", name: "DictEditor",
components: { components: {
VariableInput, VariableInput,
BorderLabel,
}, },
props: { props: {
modelValue: { modelValue: {
type: Object, type: Object,
required: true, required: true,
default: () => ({}),
}, },
options: { options: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
label: {
type: String,
default: "",
},
icon: {
type: String,
default: "",
},
isCollapse: {
type: Boolean,
default: true,
},
}, },
emits: ["update:modelValue"], emits: ["update:modelValue"],
data() { data() {

View File

@ -29,12 +29,17 @@
@update:model-value="$emit('update', index, $event)" @update:model-value="$emit('update', index, $event)"
:label="config.label" :label="config.label"
:options="config.options" :options="config.options"
:icon="config.icon"
:is-collapse="config.isCollapse"
/> />
<DictEditor <DictEditor
v-else-if="config.type === 'dictEditor'" v-else-if="config.type === 'dictEditor'"
:model-value="values[index]" :model-value="values[index]"
@update:model-value="$emit('update', index, $event)" @update:model-value="$emit('update', index, $event)"
:label="config.label"
:options="config.options" :options="config.options"
:icon="config.icon"
:is-collapse="config.isCollapse"
/> />
<q-toggle <q-toggle
v-else-if="config.type === 'switch'" v-else-if="config.type === 'switch'"

View File

@ -116,12 +116,12 @@
<!-- 环境变量 --> <!-- 环境变量 -->
<div class="col-12"> <div class="col-12">
<BorderLabel label="环境变量"> <DictEditor
<DictEditor :model-value="argvs.options.env"
:model-value="argvs.options.env" @update:model-value="(val) => updateArgvs('options.env', val)"
@update:model-value="(val) => updateArgvs('options.env', val)" label="环境变量"
/> icon="environment"
</BorderLabel> />
</div> </div>
</div> </div>
</q-slide-transition> </q-slide-transition>
@ -136,7 +136,6 @@ import { newVarInputVal } from "js/composer/varInputValManager";
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 DictEditor from "components/composer/common/DictEditor.vue"; import DictEditor from "components/composer/common/DictEditor.vue";
import BorderLabel from "components/composer/common/BorderLabel.vue";
export default defineComponent({ export default defineComponent({
name: "SystemCommandEditor", name: "SystemCommandEditor",
@ -144,7 +143,6 @@ export default defineComponent({
VariableInput, VariableInput,
NumberInput, NumberInput,
DictEditor, DictEditor,
BorderLabel,
}, },
props: { props: {
modelValue: { modelValue: {
@ -166,7 +164,6 @@ export default defineComponent({
], ],
defaultArgvs: { defaultArgvs: {
command: newVarInputVal("str"), command: newVarInputVal("str"),
},
options: { options: {
cwd: newVarInputVal("str"), cwd: newVarInputVal("str"),
env: {}, env: {},
@ -174,9 +171,10 @@ export default defineComponent({
encoding: "buffer", encoding: "buffer",
timeout: 0, timeout: 0,
maxBuffer: 1024 * 1024, // 1MB maxBuffer: 1024 * 1024, // 1MB
shell: newVarInputVal("str"), shell: newVarInputVal("str"),
windowsHide: true, windowsHide: true,
}, },
},
}; };
}, },
computed: { computed: {

View File

@ -440,5 +440,43 @@ export const dataCommands = {
icon: "compress", icon: "compress",
isAsync: true, isAsync: true,
}, },
{
value: "quickcomposer.data.htmlParser",
label: "HTML解析",
desc: "解析 HTML 字符串",
icon: "html",
config: [
{
label: "要解析的 HTML",
type: "varInput",
icon: "html",
placeholder: "例如:<div>Hello, World!</div>",
width: 12,
},
{
label: "CSS选择器",
type: "varInput",
icon: "css",
placeholder: "例如:.class",
width: 7,
},
{
label: "属性",
type: "varInput",
icon: "colorize",
options: {
items: [
{ label: "innerText", value: "innerText" },
{ label: "innerHTML", value: "innerHTML" },
{ label: "href", value: "href" },
{ label: "src", value: "src" },
{ label: "value", value: "value" },
],
},
defaultValue: newVarInputVal("str"),
width: 5,
},
],
},
], ],
}; };

View File

@ -1,3 +1,5 @@
import { newVarInputVal } from "../varInputValManager";
export const networkCommands = { export const networkCommands = {
label: "网络操作", label: "网络操作",
icon: "language", icon: "language",
@ -313,5 +315,63 @@ export const networkCommands = {
}, },
], ],
}, },
{
value: "quickcommand.downloadFile",
label: "下载文件",
desc: "下载文件",
icon: "file_download",
isAsync: true,
config: [
{
label: "文件URL",
type: "varInput",
icon: "link",
defaultValue: newVarInputVal("str", "https://"),
width: 12,
},
{
label: "保存路径",
type: "varInput",
icon: "folder",
width: 12,
placeholder: "留空则弹出对话框选择保存路径",
},
],
},
{
value: "quickcommand.uploadFile",
label: "上传文件",
desc: "上传文件",
icon: "file_upload",
isAsync: true,
config: [
{
label: "上传接口地址",
type: "varInput",
icon: "link",
defaultValue: newVarInputVal("str", "https://"),
width: 12,
},
{
label: "文件路径",
type: "varInput",
icon: "file_present",
width: 12,
placeholder: "留空则弹出对话框选择文件",
},
{
label: "文件名",
type: "varInput",
icon: "text_fields",
defaultValue: newVarInputVal("str", "file"),
width: 12,
},
{
label: "额外表单数据(可选)",
type: "dictEditor",
width: 12,
},
],
},
], ],
}; };