mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-08 06:16:27 +08:00
339 lines
8.3 KiB
Vue
339 lines
8.3 KiB
Vue
<template>
|
|
<component
|
|
:is="!!label ? 'BorderLabel' : 'div'"
|
|
:label="label"
|
|
:icon="icon"
|
|
:model-value="isCollapse"
|
|
>
|
|
<div class="dict-editor">
|
|
<div
|
|
v-for="(item, index) in allItems"
|
|
:key="`${item.type}-${index}`"
|
|
class="row q-col-gutter-sm items-center"
|
|
>
|
|
<div class="col-4">
|
|
<template v-if="item.type === 'fixed'">
|
|
<q-input
|
|
:model-value="item.key"
|
|
:label="getKeyLabel(item.key)"
|
|
dense
|
|
filled
|
|
readonly
|
|
disable
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="lock" />
|
|
</template>
|
|
</q-input>
|
|
</template>
|
|
<template v-else>
|
|
<q-input
|
|
v-if="options?.optionKeys"
|
|
:model-value="item.key"
|
|
label="名称"
|
|
dense
|
|
filled
|
|
@update:model-value="(val) => handleInput(val, index)"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="code" />
|
|
</template>
|
|
<template v-slot:append>
|
|
<q-btn dense flat icon="menu">
|
|
<q-menu>
|
|
<q-list dense>
|
|
<q-item
|
|
v-for="opt in normalizedOptionKeys"
|
|
:key="opt.value"
|
|
clickable
|
|
v-close-popup
|
|
@click="handleSelect(opt, index)"
|
|
>
|
|
<q-item-section>
|
|
{{ getKeyLabel(opt) }}
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
</template>
|
|
</q-input>
|
|
<q-input
|
|
v-else
|
|
:model-value="item.key"
|
|
label="名称"
|
|
dense
|
|
filled
|
|
@update:model-value="(val) => handleInput(val, index)"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="code" />
|
|
</template>
|
|
</q-input>
|
|
</template>
|
|
</div>
|
|
<div class="col">
|
|
<VariableInput
|
|
:model-value="item.value"
|
|
label="值"
|
|
icon="code"
|
|
class="col-grow"
|
|
@update:model-value="(val) => updateItemValue(val, index)"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-if="item.type !== 'fixed' && !options?.disableAdd"
|
|
class="col-auto"
|
|
>
|
|
<div class="btn-container">
|
|
<template v-if="localItems.length === 1">
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="sm"
|
|
icon="add"
|
|
class="center-btn"
|
|
@click="addItem"
|
|
/>
|
|
</template>
|
|
<template v-else-if="index === allItems.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>
|
|
</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";
|
|
|
|
export default defineComponent({
|
|
name: "DictEditor",
|
|
components: {
|
|
VariableInput,
|
|
BorderLabel,
|
|
},
|
|
props: {
|
|
modelValue: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
options: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
label: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
icon: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
isCollapse: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
},
|
|
emits: ["update:modelValue"],
|
|
created() {
|
|
if (!this.modelValue || Object.keys(this.modelValue).length === 0) {
|
|
this.$emit("update:modelValue", { "": newVarInputVal("str") });
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
inputValue: "",
|
|
};
|
|
},
|
|
computed: {
|
|
modelEntries() {
|
|
return Object.entries(this.modelValue || {});
|
|
},
|
|
fixedItems() {
|
|
const fixedKeys = this.normalizeKeys(this.options?.fixedKeys || []);
|
|
return fixedKeys.map((key) => ({
|
|
type: "fixed",
|
|
key: key.value,
|
|
value:
|
|
this.modelEntries.find(([k]) => k === key.value)?.[1] ||
|
|
newVarInputVal("str"),
|
|
}));
|
|
},
|
|
localItems() {
|
|
return this.modelEntries
|
|
.filter(
|
|
([key]) =>
|
|
!this.normalizeKeys(this.options?.fixedKeys || []).some(
|
|
(k) => k.value === key
|
|
)
|
|
)
|
|
.map(([key, value]) => ({
|
|
type: "editable",
|
|
key,
|
|
value,
|
|
}));
|
|
},
|
|
allItems() {
|
|
return [...this.fixedItems, ...this.localItems];
|
|
},
|
|
normalizedOptionKeys() {
|
|
return this.normalizeKeys(this.options?.optionKeys || []);
|
|
},
|
|
},
|
|
methods: {
|
|
normalizeKeys(keys) {
|
|
return keys.map((key) =>
|
|
typeof key === "string" ? { value: key, label: key } : key
|
|
);
|
|
},
|
|
getKeyLabel(key) {
|
|
if (typeof key === "object") return key.label;
|
|
const allKeys = [
|
|
...this.normalizeKeys(this.options?.fixedKeys || []),
|
|
...this.normalizedOptionKeys,
|
|
];
|
|
return allKeys.find((k) => k.value === key)?.label || key;
|
|
},
|
|
updateItemValue(val, index) {
|
|
const item = this.allItems[index];
|
|
const dict = { ...this.modelValue };
|
|
dict[item.key] = val;
|
|
this.$emit("update:modelValue", dict);
|
|
},
|
|
addItem() {
|
|
if (this.options?.disableAdd) return;
|
|
const dict = { ...this.modelValue };
|
|
dict[""] = newVarInputVal("str");
|
|
this.$emit("update:modelValue", dict);
|
|
},
|
|
removeItem(index) {
|
|
const item = this.localItems[index];
|
|
const dict = { ...this.modelValue };
|
|
delete dict[item.key];
|
|
|
|
// 如果删除后没有条目,添加一个空行
|
|
if (Object.keys(dict).length === 0) {
|
|
dict[""] = newVarInputVal("str");
|
|
}
|
|
|
|
this.$emit("update:modelValue", dict);
|
|
},
|
|
handleInput(val, index) {
|
|
this.inputValue = val;
|
|
const item = this.allItems[index];
|
|
if (val in this.modelValue && val !== item.key) {
|
|
return;
|
|
}
|
|
const dict = { ...this.modelValue };
|
|
delete dict[item.key];
|
|
dict[val] = item.value;
|
|
this.$emit("update:modelValue", dict);
|
|
},
|
|
handleSelect(val, index) {
|
|
this.inputValue = "";
|
|
const value = val?.value || val;
|
|
const item = this.allItems[index];
|
|
if (value in this.modelValue && value !== item.key) {
|
|
return;
|
|
}
|
|
const dict = { ...this.modelValue };
|
|
delete dict[item.key];
|
|
dict[value] = item.value;
|
|
this.$emit("update:modelValue", dict);
|
|
},
|
|
handleBlur() {
|
|
this.inputValue = "";
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.dict-editor {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* 防止输入框换行 */
|
|
:deep(.q-field__native) {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.btn-container {
|
|
position: relative;
|
|
width: 32px;
|
|
height: 32px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.btn-container .q-btn {
|
|
position: absolute;
|
|
width: 16px;
|
|
height: 16px;
|
|
min-height: 16px;
|
|
padding: 0;
|
|
}
|
|
|
|
.btn-container .center-btn {
|
|
position: relative;
|
|
}
|
|
|
|
.btn-container .top-btn {
|
|
top: 0;
|
|
}
|
|
|
|
.btn-container .bottom-btn {
|
|
bottom: 0;
|
|
}
|
|
|
|
:deep(.q-btn .q-icon) {
|
|
font-size: 14px;
|
|
}
|
|
|
|
:deep(.q-btn.q-btn--dense) {
|
|
padding: 0;
|
|
min-height: 16px;
|
|
}
|
|
</style>
|