486 lines
13 KiB
Vue

<template>
<div class="url-editor">
<!-- 操作类型选择 -->
<div class="operation-cards">
<div
v-for="op in operations"
:key="op.name"
:class="['operation-card', { active: argvs.operation === op.name }]"
@click="updateArgvs('operation', op.name)"
:data-value="op.name"
>
<q-icon
:name="op.icon"
size="16px"
:color="argvs.operation === op.name ? 'primary' : 'grey'"
/>
<div class="text-caption">{{ op.label }}</div>
</div>
</div>
<!-- 操作配置 -->
<div class="operation-options q-mt-sm">
<div class="options-container">
<!-- URL 输入 -->
<template v-if="needsUrlInput">
<VariableInput
:model-value="argvs.url"
@update:model-value="(val) => updateArgvs('url', val)"
label="URL"
icon="link"
/>
</template>
<!-- 查询字符串输入 -->
<template
v-if="
argvs.operation === 'parseQuery' ||
argvs.operation === 'formatQuery'
"
>
<template v-if="argvs.operation === 'parseQuery'">
<VariableInput
:model-value="argvs.query"
@update:model-value="(val) => updateArgvs('query', val)"
label="查询字符串"
icon="search"
/>
</template>
<template v-else>
<DictEditor
:model-value="argvs.queryParams"
@update:model-value="(val) => updateArgvs('queryParams', val)"
/>
</template>
</template>
<!-- 参数操作的参数输入 -->
<template
v-if="
['getQueryParam', 'addQueryParam', 'removeQueryParam'].includes(
argvs.operation
)
"
>
<VariableInput
:model-value="argvs.param"
@update:model-value="(val) => updateArgvs('param', val)"
label="参数名"
icon="key"
class="q-mt-sm"
/>
<template v-if="argvs.operation === 'addQueryParam'">
<div class="q-mt-sm">
<VariableInput
:model-value="argvs.value"
@update:model-value="(val) => updateArgvs('value', val)"
label="参数值"
icon="text_fields"
/>
</div>
</template>
</template>
<!-- 主机名输入 -->
<template v-if="argvs.operation === 'parseHost'">
<VariableInput
:model-value="argvs.host"
@update:model-value="(val) => updateArgvs('host', val)"
label="主机名"
icon="dns"
/>
</template>
<!-- 路径输入 -->
<template v-if="argvs.operation === 'parsePath'">
<VariableInput
:model-value="argvs.path"
@update:model-value="(val) => updateArgvs('path', val)"
label="路径"
icon="folder"
/>
</template>
<!-- URL 对象输入 -->
<template v-if="argvs.operation === 'format'">
<div class="row q-col-gutter-sm">
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.urlObject.protocol"
@update:model-value="(val) => updateUrlObject('protocol', val)"
label="协议"
icon="security"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.urlObject.auth"
@update:model-value="(val) => updateUrlObject('auth', val)"
label="认证信息"
icon="person"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.urlObject.hostname"
@update:model-value="(val) => updateUrlObject('hostname', val)"
label="主机名"
icon="dns"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.urlObject.port"
@update:model-value="(val) => updateUrlObject('port', val)"
label="端口"
icon="settings_ethernet"
/>
</div>
<div class="col-12">
<VariableInput
:model-value="argvs.urlObject.pathname"
@update:model-value="(val) => updateUrlObject('pathname', val)"
label="路径"
icon="folder"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.urlObject.search"
@update:model-value="(val) => updateUrlObject('search', val)"
label="查询字符串"
icon="search"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.urlObject.hash"
@update:model-value="(val) => updateUrlObject('hash', val)"
label="锚点"
icon="tag"
/>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import { stringifyArgv, parseFunction } from "js/composer/formatString";
import VariableInput from "components/composer/common/VariableInput.vue";
import DictEditor from "components/composer/common/DictEditor.vue";
export default defineComponent({
name: "UrlEditor",
components: {
VariableInput,
DictEditor,
},
props: {
modelValue: {
type: Object,
required: true,
},
},
emits: ["update:modelValue"],
data() {
return {
operations: [
{ name: "parse", label: "解析URL", icon: "link_off" },
{ name: "format", label: "格式化URL", icon: "link" },
{ name: "parseQuery", label: "解析查询字符串", icon: "search" },
{ name: "formatQuery", label: "格式化查询字符串", icon: "edit" },
{ name: "parsePath", label: "解析路径", icon: "folder_open" },
{ name: "parseHost", label: "解析主机名", icon: "dns" },
{ name: "getQueryParam", label: "获取参数", icon: "find_in_page" },
{ name: "addQueryParam", label: "添加参数", icon: "add_circle" },
{ name: "removeQueryParam", label: "移除参数", icon: "remove_circle" },
{ name: "isAbsolute", label: "检查绝对URL", icon: "check_circle" },
{ name: "parseComponents", label: "解析组成部分", icon: "category" },
],
defaultArgvs: {
operation: "parse",
url: {
value: "",
isString: true,
__varInputVal__: true,
},
query: {
value: "",
isString: true,
__varInputVal__: true,
},
queryParams: {},
param: {
value: "",
isString: true,
__varInputVal__: true,
},
value: {
value: "",
isString: true,
__varInputVal__: true,
},
host: {
value: "",
isString: true,
__varInputVal__: true,
},
path: {
value: "",
isString: true,
__varInputVal__: true,
},
urlObject: {
protocol: {
value: "",
isString: true,
__varInputVal__: true,
},
auth: {
value: "",
isString: true,
__varInputVal__: true,
},
hostname: {
value: "",
isString: true,
__varInputVal__: true,
},
port: {
value: "",
isString: true,
__varInputVal__: true,
},
pathname: {
value: "",
isString: true,
__varInputVal__: true,
},
search: {
value: "",
isString: true,
__varInputVal__: true,
},
hash: {
value: "",
isString: true,
__varInputVal__: true,
},
},
},
};
},
computed: {
argvs: {
get() {
return (
this.modelValue.argvs ||
this.parseCodeToArgvs(this.modelValue.code) || {
...this.defaultArgvs,
}
);
},
set(value) {
this.updateModelValue(value);
},
},
needsUrlInput() {
return [
"parse",
"getQueryParam",
"addQueryParam",
"removeQueryParam",
"isAbsolute",
"parseComponents",
].includes(this.argvs.operation);
},
},
methods: {
generateCode(argvs = this.argvs) {
switch (argvs.operation) {
case "parse":
case "isAbsolute":
case "parseComponents":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.url
)})`;
case "format":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.urlObject
)})`;
case "parseQuery":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.query
)})`;
case "formatQuery":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.queryParams
)})`;
case "parsePath":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.path
)})`;
case "parseHost":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.host
)})`;
case "getQueryParam":
case "removeQueryParam":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.url
)}, ${stringifyArgv(argvs.param)})`;
case "addQueryParam":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.url
)}, ${stringifyArgv(argvs.param)}, ${stringifyArgv(argvs.value)})`;
default:
return `${this.modelValue.value}.${argvs.operation}()`;
}
},
parseCodeToArgvs(code) {
if (!code) return null;
try {
const variableFormatPaths = ["arg*", "arg*.**"];
const result = parseFunction(code, { variableFormatPaths });
if (!result) return this.defaultArgvs;
const operation = result.name.split(".").pop();
const [firstArg, secondArg, thirdArg] = result.args;
const newArgvs = {
...this.defaultArgvs,
operation,
};
switch (operation) {
case "parse":
case "isAbsolute":
case "parseComponents":
newArgvs.url = firstArg;
break;
case "format":
newArgvs.urlObject = firstArg || this.defaultArgvs.urlObject;
break;
case "parseQuery":
newArgvs.query = firstArg;
break;
case "formatQuery":
if (firstArg) {
newArgvs.queryParams = firstArg;
}
break;
case "parsePath":
newArgvs.path = firstArg;
break;
case "parseHost":
newArgvs.host = firstArg;
break;
case "getQueryParam":
case "removeQueryParam":
newArgvs.url = firstArg;
newArgvs.param = secondArg;
break;
case "addQueryParam":
newArgvs.url = firstArg;
newArgvs.param = secondArg;
newArgvs.value = thirdArg;
break;
}
return newArgvs;
} catch (e) {
console.error("解析URL参数失败:", e);
return this.defaultArgvs;
}
},
updateArgvs(key, value) {
this.argvs = {
...this.argvs,
[key]: value,
};
},
updateUrlObject(key, value) {
this.updateArgvs("urlObject", {
...this.argvs.urlObject,
[key]: value,
});
},
getSummary(argvs) {
const op = this.operations.find(
(op) => op.name === argvs.operation
)?.label;
return op + " " + argvs.url.value;
},
updateModelValue(argvs) {
this.$emit("update:modelValue", {
...this.modelValue,
summary: this.getSummary(argvs),
argvs,
code: this.generateCode(argvs),
});
},
},
mounted() {
if (!this.modelValue.argvs && !this.modelValue.code) {
this.updateModelValue(this.defaultArgvs);
}
},
watch: {
"argvs.operation": {
immediate: true,
handler(newVal) {
this.$nextTick(() => {
document
.querySelector(`.operation-card[data-value="${newVal}"]`)
?.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "nearest",
});
});
},
},
},
});
</script>
<style scoped>
.url-editor {
display: flex;
flex-direction: column;
}
.options-container {
min-height: 32px;
display: flex;
flex-direction: column;
justify-content: center;
}
/* 覆盖command-composer的样式 */
.command-composer .operation-card {
min-width: 100px;
}
</style>