mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-30 04:42:45 +08:00
298 lines
7.8 KiB
Vue
298 lines
7.8 KiB
Vue
<template>
|
||
<div class="flex-container">
|
||
<div
|
||
v-if="hasFunctionSelector"
|
||
class="flex-item"
|
||
:style="{ flex: localCommand.functionSelector.width || 3 }"
|
||
>
|
||
<div class="operation-cards">
|
||
<div
|
||
v-for="option in localCommand.functionSelector?.options"
|
||
:key="option.value"
|
||
:class="['operation-card', { active: funcName === option.value }]"
|
||
:data-value="option.value"
|
||
@click="funcName = option.value"
|
||
>
|
||
<q-icon
|
||
:name="option.icon || localCommand.icon || 'functions'"
|
||
size="16px"
|
||
:color="funcName === option.value ? 'primary' : 'grey'"
|
||
/>
|
||
<div class="text-caption">{{ option.label }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex-container">
|
||
<div
|
||
v-for="(item, index) in localConfig"
|
||
:key="index"
|
||
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>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { defineComponent } from "vue";
|
||
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||
import NumberInput from "components/composer/ui/NumberInput.vue";
|
||
import { stringifyWithType, parseToHasType } from "js/composer/formatString";
|
||
|
||
export default defineComponent({
|
||
name: "MultiParams",
|
||
components: {
|
||
VariableInput,
|
||
NumberInput,
|
||
},
|
||
props: {
|
||
modelValue: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
required: true,
|
||
},
|
||
},
|
||
emits: ["update:modelValue"],
|
||
computed: {
|
||
localCommand() {
|
||
return this.modelValue;
|
||
},
|
||
localConfig() {
|
||
return (this.modelValue.config || []).map((item) => {
|
||
return {
|
||
...item,
|
||
value: item.defaultValue,
|
||
};
|
||
});
|
||
},
|
||
defaultArgvs() {
|
||
return this.localConfig.map((item) => item.value);
|
||
},
|
||
funcName: {
|
||
get() {
|
||
return this.modelValue.value;
|
||
},
|
||
set(value) {
|
||
this.updateModelValue(value, this.defaultArgvs);
|
||
},
|
||
},
|
||
argvs() {
|
||
return (
|
||
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
|
||
);
|
||
},
|
||
hasFunctionSelector() {
|
||
return !!this.localCommand.functionSelector;
|
||
},
|
||
},
|
||
methods: {
|
||
updateArgv(index, value) {
|
||
const newArgvs = [...this.argvs];
|
||
newArgvs[index] = value;
|
||
|
||
this.updateModelValue(this.funcName, newArgvs);
|
||
},
|
||
generateCode(funcName, argvs) {
|
||
const newArgvs = argvs
|
||
.map((argv) => stringifyWithType(argv))
|
||
.filter((item) => item != null && item !== "");
|
||
console.log(newArgvs);
|
||
return `${funcName}(${newArgvs.join(",")})`;
|
||
},
|
||
parseCodeToArgvs(code) {
|
||
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
|
||
if (!code) return argvs;
|
||
|
||
// 匹配函数名和参数
|
||
const pattern = new RegExp(`^${this.funcName}\\((.*?)\\)$`);
|
||
const match = code.match(pattern);
|
||
if (match) {
|
||
try {
|
||
const paramStr = match[1].trim();
|
||
if (!paramStr) return argvs;
|
||
|
||
// 分割参数,考虑括号嵌套
|
||
let params = [];
|
||
let bracketCount = 0;
|
||
let currentParam = "";
|
||
|
||
for (let i = 0; i < paramStr.length; i++) {
|
||
const char = paramStr[i];
|
||
if (char === "," && bracketCount === 0) {
|
||
params.push(currentParam.trim());
|
||
currentParam = "";
|
||
continue;
|
||
}
|
||
if (char === "{") bracketCount++;
|
||
if (char === "}") bracketCount--;
|
||
currentParam += char;
|
||
}
|
||
if (currentParam) {
|
||
params.push(currentParam.trim());
|
||
}
|
||
|
||
// 根据配置处理每个参数
|
||
params.forEach((param, index) => {
|
||
if (index >= this.localConfig.length) return;
|
||
|
||
const config = this.localConfig[index];
|
||
if (config.type === "varInput") {
|
||
// 对于 VariableInput 类型,解析为带有 __varInputVal__ 标记的对象
|
||
argvs[index] = parseToHasType(param);
|
||
} else if (config.type === "numInput") {
|
||
// 对于 NumberInput 类型,转换为数字
|
||
argvs[index] = Number(param) || 0;
|
||
} else {
|
||
// 其他类型直接使用值
|
||
argvs[index] = param;
|
||
}
|
||
});
|
||
|
||
return argvs;
|
||
} catch (e) {
|
||
console.error("解析参数失败:", e);
|
||
}
|
||
}
|
||
return argvs;
|
||
},
|
||
getSummary(argvs) {
|
||
// 虽然header里对溢出做了处理,但是这里截断主要是为了节省存储空间
|
||
const funcNameLabel = this.localCommand.functionSelector?.options.find(
|
||
(option) => option.value === this.funcName
|
||
)?.label;
|
||
const subFeature = funcNameLabel ? `${funcNameLabel} ` : "";
|
||
const allArgvs = argvs
|
||
.map((item) =>
|
||
item?.hasOwnProperty("__varInputVal__")
|
||
? window.lodashM.truncate(item.value, {
|
||
length: 30,
|
||
omission: "...",
|
||
})
|
||
: item
|
||
)
|
||
.filter((item) => item != null && item != "");
|
||
return `${subFeature}${allArgvs.join(",")}`;
|
||
},
|
||
updateModelValue(funcName, argvs) {
|
||
this.$emit("update:modelValue", {
|
||
...this.modelValue,
|
||
value: funcName,
|
||
argvs,
|
||
summary: this.getSummary(argvs),
|
||
code: this.generateCode(funcName, argvs),
|
||
});
|
||
},
|
||
},
|
||
mounted() {
|
||
if (!this.modelValue.argvs && !this.modelValue.code) {
|
||
this.updateModelValue(this.funcName, this.defaultArgvs);
|
||
}
|
||
},
|
||
watch: {
|
||
funcName: {
|
||
immediate: true,
|
||
handler(newVal) {
|
||
// 当操作卡片改变时,确保它在视图中可见
|
||
this.$nextTick(() => {
|
||
document
|
||
.querySelector(`.operation-card[data-value="${newVal}"]`)
|
||
?.scrollIntoView({
|
||
behavior: "smooth",
|
||
block: "nearest",
|
||
inline: "nearest",
|
||
});
|
||
});
|
||
},
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.flex-container {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
.flex-item {
|
||
min-width: 100px;
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.flex-item {
|
||
flex: 1 1 100% !important;
|
||
}
|
||
}
|
||
|
||
.operation-cards {
|
||
display: flex;
|
||
align-items: center;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
white-space: nowrap;
|
||
padding: 1px;
|
||
gap: 8px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.operation-cards::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.operation-card {
|
||
cursor: pointer;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
border: 1px solid transparent;
|
||
border-radius: 6px;
|
||
min-width: 72px;
|
||
padding: 2px 0;
|
||
background: rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.body--dark .operation-card {
|
||
background: rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.operation-card:hover {
|
||
background: var(--q-primary-opacity-5);
|
||
transform: translateY(-1px);
|
||
border: 1px solid var(--q-primary-opacity-10);
|
||
}
|
||
|
||
.operation-card.active {
|
||
border-color: var(--q-primary);
|
||
background: var(--q-primary-opacity-5);
|
||
}
|
||
|
||
.body--dark .operation-card.active {
|
||
border-color: var(--q-primary-opacity-50);
|
||
}
|
||
</style>
|