mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-12-20 19:20:37 +08:00
统一分类图标风格,目录结构调整
This commit is contained in:
@@ -1,320 +0,0 @@
|
||||
<template>
|
||||
<div class="array-editor">
|
||||
<div v-for="(item, index) in items" :key="index" class="row items-center">
|
||||
<template v-if="optionsKeys">
|
||||
<div
|
||||
v-for="key in optionsKeys"
|
||||
:key="key"
|
||||
:class="['col', optionsKeys.length > 1 ? 'q-pr-sm' : '']"
|
||||
>
|
||||
<VariableInput
|
||||
:model-value="item[key]"
|
||||
:label="key"
|
||||
:icon="icon || 'code'"
|
||||
@update:model-value="(val) => updateItemKeyValue(index, key, val)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="col">
|
||||
<VariableInput
|
||||
:model-value="item"
|
||||
:label="`${label || '项目'} ${index + 1}`"
|
||||
:icon="icon || 'code'"
|
||||
:options="options"
|
||||
@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>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 数组编辑器组件
|
||||
* @description 支持单值数组和多键对象数组的编辑
|
||||
*
|
||||
* @property {Array} modelValue - 绑定的数组值
|
||||
* @property {String} label - 输入框标签
|
||||
* @property {String} icon - 输入框图标
|
||||
* @property {Object} options - 配置选项
|
||||
* @property {String[]} [optionsKeys] - 多键对象模式的键名列表
|
||||
* @property {String[]} [options] - 下拉选择模式的选项列表
|
||||
*
|
||||
* @example
|
||||
* // 基础数组
|
||||
* [
|
||||
* {
|
||||
* value: "张三",
|
||||
* isString: true,
|
||||
* __varInputVal__: true
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* // 多键对象数组
|
||||
* optionsKeys = ['name', 'age', 'email']
|
||||
* [
|
||||
* {
|
||||
* name: { value: "张三", isString: true, __varInputVal__: true },
|
||||
* age: { value: "18", isString: false, __varInputVal__: true },
|
||||
* email: { value: "zhangsan@example.com", isString: true, __varInputVal__: true }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* // 下拉选择模式
|
||||
* options = ['选项1', '选项2', '选项3']
|
||||
* [
|
||||
* {
|
||||
* value: "选项1",
|
||||
* isString: true,
|
||||
* __varInputVal__: true
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
import { defineComponent } from "vue";
|
||||
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ArrayEditor",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
optionsKeys: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
// 本地维护的数组数据
|
||||
localItems: this.initializeItems(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
items: {
|
||||
get() {
|
||||
return this.localItems;
|
||||
},
|
||||
set(newItems) {
|
||||
this.localItems = newItems;
|
||||
this.$emit("update:modelValue", newItems);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initializeItems() {
|
||||
if (this.modelValue.length) {
|
||||
return this.modelValue;
|
||||
}
|
||||
|
||||
if (this.options?.keys) {
|
||||
const item = {};
|
||||
this.optionsKeys.forEach((key) => {
|
||||
item[key] = {
|
||||
value: "",
|
||||
isString: false,
|
||||
__varInputVal__: true,
|
||||
};
|
||||
});
|
||||
return [item];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
value: "",
|
||||
isString: false,
|
||||
__varInputVal__: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
/**
|
||||
* 添加新的数组项
|
||||
* 根据配置创建相应的数据结构
|
||||
*/
|
||||
addItem() {
|
||||
if (this.optionsKeys) {
|
||||
const newItem = {};
|
||||
this.optionsKeys.forEach((key) => {
|
||||
newItem[key] = {
|
||||
value: "",
|
||||
isString: false,
|
||||
__varInputVal__: true,
|
||||
};
|
||||
});
|
||||
this.items = [...this.items, newItem];
|
||||
} else {
|
||||
this.items = [
|
||||
...this.items,
|
||||
{
|
||||
value: "",
|
||||
isString: false,
|
||||
__varInputVal__: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 移除指定索引的数组项
|
||||
* 如果移除后数组为空,则创建一个新的空项
|
||||
*/
|
||||
removeItem(index) {
|
||||
const newItems = [...this.items];
|
||||
newItems.splice(index, 1);
|
||||
if (newItems.length === 0) {
|
||||
if (this.optionsKeys) {
|
||||
const newItem = {};
|
||||
this.optionsKeys.forEach((key) => {
|
||||
newItem[key] = {
|
||||
value: "",
|
||||
isString: false,
|
||||
__varInputVal__: true,
|
||||
};
|
||||
});
|
||||
newItems.push(newItem);
|
||||
} else {
|
||||
newItems.push({
|
||||
value: "",
|
||||
isString: false,
|
||||
__varInputVal__: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.items = newItems;
|
||||
},
|
||||
/**
|
||||
* 更新单值模式下的值
|
||||
*/
|
||||
updateItemValue(index, value) {
|
||||
const newItems = [...this.items];
|
||||
newItems[index] = value;
|
||||
this.items = newItems;
|
||||
},
|
||||
/**
|
||||
* 更新多键模式下指定键的值
|
||||
*/
|
||||
updateItemKeyValue(index, key, value) {
|
||||
const newItems = [...this.items];
|
||||
newItems[index] = {
|
||||
...newItems[index],
|
||||
[key]: value,
|
||||
};
|
||||
this.items = newItems;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.array-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>
|
||||
@@ -1,159 +0,0 @@
|
||||
<template>
|
||||
<div class="border-label" :class="{ collapsed }" :data-label="label">
|
||||
<div class="label-header" @click="toggleCollapse">
|
||||
<q-icon
|
||||
:name="collapsed ? 'expand_more' : 'expand_less'"
|
||||
size="16px"
|
||||
class="collapse-icon"
|
||||
/>
|
||||
<span class="label-text">{{ label }}</span>
|
||||
</div>
|
||||
<div class="content" :class="{ collapsed }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 边框标签组件
|
||||
* @description 对指定元素进行包裹,显示一个定位在左上角的标签,点击标签可以折叠/展开内容
|
||||
*
|
||||
* @property {Boolean} modelValue - 控制内容的展开/折叠状态
|
||||
* @property {String} label - 标签文本
|
||||
*/
|
||||
export default {
|
||||
name: "BorderLabel",
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
collapsed: this.modelValue,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.collapsed = val;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse() {
|
||||
this.collapsed = !this.collapsed;
|
||||
this.$emit("update:modelValue", this.collapsed);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.border-label {
|
||||
width: 100%;
|
||||
border: 1px solid var(--border-color, rgba(0, 0, 0, 0.1));
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
margin-top: 8px;
|
||||
transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
border-radius 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.label-header {
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
left: 16px;
|
||||
background: #fff;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
padding: 0 8px;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
margin-left: -12px;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
opacity: 0.6;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.label-header:hover .label-text {
|
||||
color: var(--q-primary);
|
||||
}
|
||||
|
||||
.label-header:hover .collapse-icon {
|
||||
opacity: 1;
|
||||
color: var(--q-primary);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content.collapsed {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.border-label.collapsed {
|
||||
border-width: 0;
|
||||
border-top-width: 1px;
|
||||
border-radius: 0;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.border-label::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
/* 暗黑模式适配 */
|
||||
.body--dark .border-label {
|
||||
--border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.body--dark .label-header {
|
||||
background: #303133;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/* 动画优化 */
|
||||
.border-label,
|
||||
.content {
|
||||
will-change: max-height, padding, opacity, border-width;
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
will-change: transform, opacity, color;
|
||||
}
|
||||
</style>
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<div class="control-input-wrapper">
|
||||
<q-input
|
||||
dense
|
||||
borderless
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
class="control-input"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-badge class="control-input-prepend" color="primary">{{
|
||||
label
|
||||
}}</q-badge>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
/**
|
||||
* 控制流程输入框组件
|
||||
* @description 调整了样式的输入框组件
|
||||
*
|
||||
* @property {String|Number} modelValue - 输入框的值
|
||||
* @property {String} label - 输入框前置标签文本
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "ControlInput",
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.control-input :deep(.q-field__control),
|
||||
.control-input :deep(.q-field__native),
|
||||
.control-input :deep(.q-field__marginal) {
|
||||
height: 21px !important;
|
||||
min-height: 21px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.control-input-prepend {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,298 +0,0 @@
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||
|
||||
/**
|
||||
* 字典编辑器组件
|
||||
* @description 支持键值对编辑,键支持变量选择和字符串输入,值为VariableInput特有对象
|
||||
*
|
||||
* @property {Object} modelValue - 绑定的字典对象
|
||||
* @property {Object} options - 配置选项
|
||||
* @property {String[]} [options.items] - 键名的下拉选择选项
|
||||
*
|
||||
* @example
|
||||
* // 基础字典对象
|
||||
* {
|
||||
* key: {
|
||||
* value: "", // 输入框的值
|
||||
* isString: true, // 是否是字符串
|
||||
* __varInputVal__: true // 用于标识是变量输入框
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // 下拉选择模式
|
||||
* options.items = ['User-Agent', 'Content-Type', 'Accept']
|
||||
* {
|
||||
* "User-Agent": {
|
||||
* value: "Mozilla/5.0",
|
||||
* isString: true,
|
||||
* __varInputVal__: true
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "DictEditor",
|
||||
components: {
|
||||
VariableInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
const modelEntries = Object.entries(this.modelValue || {});
|
||||
return {
|
||||
localItems: modelEntries.length
|
||||
? modelEntries.map(([key, value]) => ({ key, value }))
|
||||
: [
|
||||
{
|
||||
key: "",
|
||||
value: {
|
||||
value: "",
|
||||
isString: true,
|
||||
__varInputVal__: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
filterOptions: this.options?.items || [],
|
||||
inputValue: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
items: {
|
||||
get() {
|
||||
return this.localItems;
|
||||
},
|
||||
set(newItems) {
|
||||
this.localItems = newItems;
|
||||
const dict = {};
|
||||
newItems.forEach((item) => {
|
||||
if (item.key) {
|
||||
dict[item.key] = item.value;
|
||||
}
|
||||
});
|
||||
this.$emit("update:modelValue", dict);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addItem() {
|
||||
this.items = [
|
||||
...this.items,
|
||||
{
|
||||
key: "",
|
||||
value: { value: "", isString: true, __varInputVal__: true },
|
||||
},
|
||||
];
|
||||
},
|
||||
removeItem(index) {
|
||||
const newItems = [...this.items];
|
||||
newItems.splice(index, 1);
|
||||
if (newItems.length === 0) {
|
||||
newItems.push({
|
||||
key: "",
|
||||
value: { value: "", isString: true, __varInputVal__: true },
|
||||
});
|
||||
}
|
||||
this.items = newItems;
|
||||
},
|
||||
updateItemKey(val, index) {
|
||||
const newItems = [...this.items];
|
||||
newItems[index].key = val;
|
||||
this.items = newItems;
|
||||
},
|
||||
updateItemValue(val, index) {
|
||||
const newItems = [...this.items];
|
||||
newItems[index].value = val;
|
||||
this.items = newItems;
|
||||
},
|
||||
handleInput(val, index) {
|
||||
this.inputValue = val;
|
||||
if (val && !this.filterOptions.includes(val)) {
|
||||
const newItems = [...this.items];
|
||||
newItems[index].key = val;
|
||||
this.items = newItems;
|
||||
}
|
||||
},
|
||||
handleSelect(val, index) {
|
||||
this.inputValue = "";
|
||||
const newItems = [...this.items];
|
||||
newItems[index].key = val;
|
||||
this.items = newItems;
|
||||
},
|
||||
handleBlur() {
|
||||
this.inputValue = "";
|
||||
},
|
||||
filterFn(val, update) {
|
||||
if (!this.options?.items) return;
|
||||
|
||||
update(() => {
|
||||
if (val === "") {
|
||||
this.filterOptions = this.options.items;
|
||||
} else {
|
||||
const needle = val.toLowerCase();
|
||||
this.filterOptions = this.options.items.filter(
|
||||
(v) => v.toLowerCase().indexOf(needle) > -1
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</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>
|
||||
@@ -1,126 +0,0 @@
|
||||
<template>
|
||||
<q-input
|
||||
type="number"
|
||||
v-model.number="localValue"
|
||||
dense
|
||||
filled
|
||||
:label="label"
|
||||
class="number-input"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon v-if="icon" :name="icon" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<!-- <q-icon name="pin" size="xs" /> -->
|
||||
<div class="column items-center number-controls">
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
icon="keyboard_arrow_up"
|
||||
size="xs"
|
||||
class="number-btn"
|
||||
@click="updateNumber(100)"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
icon="keyboard_arrow_down"
|
||||
size="xs"
|
||||
class="number-btn"
|
||||
@click="updateNumber(-100)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
/**
|
||||
* 数字输入框组件
|
||||
* @description 对加减按钮进行了美化的数字输入组件
|
||||
*
|
||||
* @property {Number} modelValue - 输入框的数值
|
||||
* @property {String} label - 输入框标签
|
||||
* @property {String} icon - 输入框图标
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "NumberInput",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Number,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
computed: {
|
||||
localValue: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateNumber(delta) {
|
||||
this.$emit("update:modelValue", this.localValue + delta);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 数字输入框样式 */
|
||||
.number-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 隐藏默认的数字输入框箭头 - Chrome, Safari, Edge, Opera */
|
||||
.number-input :deep(input[type="number"]::-webkit-outer-spin-button),
|
||||
.number-input :deep(input[type="number"]::-webkit-inner-spin-button) {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.number-input :deep(.q-field__control) {
|
||||
padding-left: 8px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.number-controls {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
width: 32px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.number-btn {
|
||||
opacity: 0.7;
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: 16px;
|
||||
height: 16px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.number-btn :deep(.q-icon) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.number-btn:hover {
|
||||
opacity: 1;
|
||||
color: var(--q-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -1,393 +0,0 @@
|
||||
<template>
|
||||
<q-input
|
||||
v-model="inputValue"
|
||||
dense
|
||||
filled
|
||||
:label="label"
|
||||
class="variable-input"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-btn
|
||||
v-if="hasSelectedVariable"
|
||||
flat
|
||||
dense
|
||||
round
|
||||
icon="close"
|
||||
size="sm"
|
||||
class="clear-btn q-mr-xs"
|
||||
@click="clearVariable"
|
||||
>
|
||||
<q-tooltip>清除选中的变量</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
round
|
||||
:icon="isString ? 'format_quote' : 'data_object'"
|
||||
size="sm"
|
||||
class="string-toggle"
|
||||
@click="toggleType"
|
||||
v-if="!hasSelectedVariable"
|
||||
>
|
||||
<q-tooltip>{{
|
||||
isString
|
||||
? "当前类型是:字符串,点击切换"
|
||||
: "当前类型是:变量、数字、表达式等,点击切换"
|
||||
}}</q-tooltip>
|
||||
</q-btn>
|
||||
<!-- 选项下拉按钮 -->
|
||||
<q-btn-dropdown
|
||||
v-if="options && !hasSelectedVariable"
|
||||
flat
|
||||
dense
|
||||
size="sm"
|
||||
dropdown-icon="menu"
|
||||
no-icon-animation
|
||||
class="options-dropdown"
|
||||
>
|
||||
<q-list class="options-list">
|
||||
<q-item
|
||||
v-for="option in normalizedOptions"
|
||||
:key="getOptionValue(option)"
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="selectOption(option)"
|
||||
class="option-item"
|
||||
>
|
||||
<q-item-section>
|
||||
{{ getOptionLabel(option) }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<!-- 变量选择下拉 -->
|
||||
<q-btn-dropdown
|
||||
flat
|
||||
dense
|
||||
:class="{
|
||||
'text-primary': hasSelectedVariable,
|
||||
'text-grey-6': !hasSelectedVariable,
|
||||
}"
|
||||
class="variable-dropdown"
|
||||
size="sm"
|
||||
v-if="variables.length"
|
||||
>
|
||||
<q-list class="variable-list">
|
||||
<q-item-label header class="variable-label">
|
||||
<q-icon name="functions" size="15px" />
|
||||
选择变量
|
||||
</q-item-label>
|
||||
|
||||
<q-separator class="q-my-xs" />
|
||||
|
||||
<template v-if="variables.length">
|
||||
<q-item
|
||||
v-for="variable in variables"
|
||||
:key="variable.name"
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="insertVariable(variable)"
|
||||
class="variable-item"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label class="variable-name">
|
||||
{{ variable.name }}
|
||||
</q-item-label>
|
||||
<q-item-label caption class="variable-source">
|
||||
来自: {{ variable.sourceCommand.label }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</template>
|
||||
<template v-slot:prepend>
|
||||
<q-icon :name="icon || 'code'" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, inject } from "vue";
|
||||
|
||||
/**
|
||||
* 变量输入框组件
|
||||
* @description 支持变量选择和字符串输入的输入框组件
|
||||
*
|
||||
* @property {Object} modelValue - 输入框的值对象
|
||||
* @property {String} label - 输入框标签
|
||||
* @property {String} icon - 输入框图标
|
||||
* @property {String[]} [options] - 可选的下拉选项列表
|
||||
*
|
||||
* @example
|
||||
* // modelValue 对象格式
|
||||
* {
|
||||
* value: "", // 输入框的值
|
||||
* isString: true, // 是否是字符串
|
||||
* __varInputVal__: true // 用于标识是变量输入框
|
||||
* }
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "VariableInput",
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
value: "",
|
||||
isString: true,
|
||||
__varInputVal__: true,
|
||||
}),
|
||||
},
|
||||
label: String,
|
||||
icon: String,
|
||||
options: {
|
||||
type: Array,
|
||||
default: null,
|
||||
validator(value) {
|
||||
if (!value) return true;
|
||||
// 检查数组中的每一项是否符合格式要求
|
||||
return value.every((item) => {
|
||||
return (
|
||||
typeof item === "string" || // ["xxx", "yyy"]
|
||||
(typeof item === "object" && "label" in item && "value" in item) // [{label: "xxx", value: "yyy"}]
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
emits: ["update:modelValue"],
|
||||
setup() {
|
||||
const variables = inject("composerVariables", []);
|
||||
return { variables };
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedVariable: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 判断是否有选中的变量,用于控制UI显示和行为
|
||||
hasSelectedVariable() {
|
||||
return this.selectedVariable !== null;
|
||||
},
|
||||
|
||||
isString: {
|
||||
get() {
|
||||
return this.modelValue.isString;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", {
|
||||
...this.modelValue,
|
||||
isString: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
inputValue: {
|
||||
get() {
|
||||
return this.modelValue.value;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", {
|
||||
...this.modelValue,
|
||||
value,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
// 标准化选项格式
|
||||
normalizedOptions() {
|
||||
console.log(this.options);
|
||||
if (!this.options) return [];
|
||||
return this.options.map((option) => {
|
||||
if (typeof option === "string") {
|
||||
return { label: option, value: option };
|
||||
}
|
||||
return option;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 插入变量时的处理
|
||||
insertVariable(variable) {
|
||||
this.selectedVariable = variable;
|
||||
this.isString = false; // 变量模式下不需要字符串处理
|
||||
this.$emit("update:modelValue", {
|
||||
isString: false,
|
||||
value: variable.name,
|
||||
__varInputVal__: true,
|
||||
});
|
||||
},
|
||||
|
||||
// 清除变量时的处理
|
||||
clearVariable() {
|
||||
this.selectedVariable = null;
|
||||
this.isString = true; // 恢复到字符串模式
|
||||
this.$emit("update:modelValue", {
|
||||
isString: true,
|
||||
value: "",
|
||||
__varInputVal__: true,
|
||||
});
|
||||
},
|
||||
|
||||
// 切换类型
|
||||
toggleType() {
|
||||
this.$emit("update:modelValue", {
|
||||
...this.modelValue,
|
||||
isString: !this.modelValue.isString,
|
||||
});
|
||||
},
|
||||
|
||||
getOptionLabel(option) {
|
||||
return typeof option === "string" ? option : option.label;
|
||||
},
|
||||
|
||||
getOptionValue(option) {
|
||||
return typeof option === "string" ? option : option.value;
|
||||
},
|
||||
|
||||
selectOption(option) {
|
||||
const value = this.getOptionValue(option);
|
||||
this.$emit("update:modelValue", {
|
||||
value,
|
||||
isString: true,
|
||||
__varInputVal__: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.variable-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.variable-input :deep(.q-field__control) {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
/* 字符串切换按钮样式 */
|
||||
.string-toggle {
|
||||
min-width: 24px;
|
||||
padding: 4px;
|
||||
opacity: 0.6;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.string-toggle:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 变量下拉框样式 */
|
||||
.variable-dropdown {
|
||||
min-width: 32px;
|
||||
padding: 4px;
|
||||
opacity: 0.8;
|
||||
transition: all 0.3s ease;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.variable-dropdown:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 变量列表样式 */
|
||||
.variable-list {
|
||||
min-width: 200px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.variable-item {
|
||||
border-radius: 4px;
|
||||
padding: 0px 16px;
|
||||
transition: all 0.3s ease;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.variable-item:hover {
|
||||
background: var(--q-primary-opacity-10);
|
||||
}
|
||||
|
||||
.variable-label {
|
||||
padding: 4px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.variable-name {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.variable-source {
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
.body--dark .variable-item:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 清空按钮样式 */
|
||||
.clear-btn {
|
||||
opacity: 0.6;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.clear-btn:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
color: var(--q-negative);
|
||||
}
|
||||
|
||||
/* 选项下拉框样式 */
|
||||
.options-dropdown {
|
||||
min-width: 32px;
|
||||
padding: 4px;
|
||||
opacity: 0.8;
|
||||
transition: all 0.3s ease;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.options-dropdown:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.options-list {
|
||||
min-width: 120px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
border-radius: 4px;
|
||||
padding: 0px 16px;
|
||||
transition: all 0.3s ease;
|
||||
min-height: 40px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.option-item:hover {
|
||||
background: var(--q-primary-opacity-10);
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
.body--dark .option-item:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user