编排新增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 iconv = require("iconv-lite");
const path = require("path");
const axios = require("axios");
const getQuickcommandTempFile = require("./getQuickcommandTempFile");
const getCommandToLaunchTerminal = require("./getCommandToLaunchTerminal");
@ -72,9 +73,9 @@ const quickcommand = {
},
// 下载文件
downloadFile: function (url, file = {}) {
downloadFile: function (url, file) {
return new Promise((reslove, reject) => {
if (file instanceof Object)
if (!file || file instanceof Object)
file = window.utools.showSaveDialog(JSON.parse(JSON.stringify(file)));
axios({
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) => {
var objfile;
if (file instanceof File) {
objfile = file;
} else {
if (file instanceof Object)
if (!file || file instanceof Object)
file = window.utools.showOpenDialog(
JSON.parse(JSON.stringify(file))
)[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 buffer = require("./buffer");
const zlib = require("./zlib");
const htmlParser = require("./htmlParser");
module.exports = {
...string,
...htmlParser,
buffer,
zlib,
};

View File

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

View File

@ -1,12 +1,20 @@
<template>
<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
:name="collapsed ? 'expand_more' : 'expand_less'"
size="16px"
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 class="content" :class="{ collapsed }">
<slot></slot>
@ -33,6 +41,10 @@ export default {
type: Boolean,
default: true,
},
icon: {
type: String,
default: "",
},
},
emits: ["update:modelValue"],
data() {

View File

@ -1,102 +1,110 @@
<template>
<div class="dict-editor">
<div
v-for="(item, index) in items"
:key="index"
class="row q-col-gutter-sm items-center"
>
<div class="col-4">
<q-select
v-if="options?.items"
:model-value="item.key"
:options="options.items"
label="名称"
dense
filled
use-input
input-debounce="0"
:hide-selected="!!inputValue"
@filter="filterFn"
@update:model-value="(val) => handleSelect(val, index)"
@input-value="(val) => handleInput(val, index)"
@blur="handleBlur"
>
<template v-slot:prepend>
<q-icon name="code" />
</template>
</q-select>
<q-input
v-else
:model-value="item.key"
label="名称"
dense
filled
@update:model-value="(val) => updateItemKey(val, index)"
>
<template v-slot:prepend>
<q-icon name="code" />
</template>
</q-input>
</div>
<div class="col">
<VariableInput
:model-value="item.value"
label="值"
icon="code"
class="col-grow"
@update:model-value="(val) => updateItemValue(val, index)"
/>
</div>
<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>
<template v-else-if="index === items.length - 1">
<q-btn
flat
dense
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>
<component
:is="!!label ? 'BorderLabel' : 'div'"
:label="label"
:icon="icon"
:model-value="isCollapse"
>
<div class="dict-editor">
<div
v-for="(item, index) in items"
:key="index"
class="row q-col-gutter-sm items-center"
>
<div class="col-4">
<q-select
v-if="options?.items"
:model-value="item.key"
:options="options.items"
label="名称"
dense
filled
use-input
input-debounce="0"
:hide-selected="!!inputValue"
@filter="filterFn"
@update:model-value="(val) => handleSelect(val, index)"
@input-value="(val) => handleInput(val, index)"
@blur="handleBlur"
>
<template v-slot:prepend>
<q-icon name="code" />
</template>
</q-select>
<q-input
v-else
:model-value="item.key"
label="名称"
dense
filled
@update:model-value="(val) => updateItemKey(val, index)"
>
<template v-slot:prepend>
<q-icon name="code" />
</template>
</q-input>
</div>
<div class="col">
<VariableInput
:model-value="item.value"
label="值"
icon="code"
class="col-grow"
@update:model-value="(val) => updateItemValue(val, index)"
/>
</div>
<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>
<template v-else-if="index === items.length - 1">
<q-btn
flat
dense
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>
</component>
</template>
<script>
import { defineComponent } from "vue";
import { newVarInputVal } from "js/composer/varInputValManager";
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",
components: {
VariableInput,
BorderLabel,
},
props: {
modelValue: {
type: Object,
required: true,
default: () => ({}),
},
options: {
type: Object,
default: () => ({}),
},
label: {
type: String,
default: "",
},
icon: {
type: String,
default: "",
},
isCollapse: {
type: Boolean,
default: true,
},
},
emits: ["update:modelValue"],
data() {

View File

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

View File

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

View File

@ -440,5 +440,43 @@ export const dataCommands = {
icon: "compress",
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 = {
label: "网络操作",
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,
},
],
},
],
};