统一分类图标风格,目录结构调整

This commit is contained in:
fofolee
2025-01-06 10:24:13 +08:00
parent b8d48cb102
commit 4293f095ae
41 changed files with 53 additions and 53 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>