<think>我们参考用户提供的优快云博客文章(链接:https://blog.csdn.net/yunhuaikong/article/details/144934053)来实现一个Vue动态查询规则生成组件。该组件允许用户动态添加、删除查询条件,并支持条件组合(AND/OR),最终生成结构化查询规则。
根据博客内容,我们将实现一个类似的功能组件。该组件将包含以下部分:
1. 条件组(ConditionGroup):可以包含多个条件或条件组,并设置逻辑运算符(AND/OR)
2. 条件行(ConditionRow):单个条件,包括字段选择、运算符选择、值输入
3. 字段选择器、运算符选择器、值输入组件
由于博客中可能没有提供完整的代码,我们将基于之前的设计和博客的思路进行实现。
### 实现步骤
#### 1. 数据结构设计
与之前的设计类似,我们使用以下数据结构:
```typescript
// 条件组
interface ConditionGroup {
logicalOperator: 'AND' | 'OR';
children: Array<Condition | ConditionGroup>;
}
// 单个条件
interface Condition {
field: string;
operator: string;
value: any;
}
// 字段配置
interface FieldConfig {
key: string;
label: string;
type: 'text' | 'number' | 'date' | 'select';
operators: string[];
options?: Array<{ label: string; value: any }>;
}
```
#### 2. 组件实现
我们将创建以下组件:
- `QueryBuilder`: 主组件
- `ConditionGroup`: 条件组组件
- `ConditionRow`: 条件行组件
- `FieldSelector`: 字段选择器
- `OperatorSelector`: 运算符选择器
- `ValueInput`: 值输入组件
#### 3. 代码实现
由于用户要求基于优快云博客(链接:https://blog.csdn.net/yunhuaikong/article/details/144934053)实现,我们参考该博客的实现思路。博客中可能使用了Vue2,因此我们使用Vue2的语法。
**注意**:博客中可能使用了不同的组件结构,我们结合之前的设计和博客的思路来实现。
下面是组件的具体实现:
##### 主组件 (QueryBuilder.vue)
```vue
<template>
<div class="query-builder">
<condition-group
:group="rootGroup"
:fields="fieldConfigs"
:is-root="true"
@change="handleChange"
/>
<div class="actions">
<button @click="addConditionToRoot">添加条件</button>
<button @click="addGroupToRoot">添加条件组</button>
</div>
<div class="output">
<pre>{{ JSON.stringify(rootGroup, null, 2) }}</pre>
</div>
</div>
</template>
<script>
import ConditionGroup from './ConditionGroup.vue';
export default {
name: 'QueryBuilder',
components: { ConditionGroup },
props: {
fields: { type: Array, required: true }
},
data() {
return {
rootGroup: {
logicalOperator: 'AND',
children: []
}
};
},
computed: {
fieldConfigs() {
return this.fields;
}
},
methods: {
addConditionToRoot() {
this.rootGroup.children.push({
field: this.fields[0]?.key || '',
operator: '=',
value: ''
});
},
addGroupToRoot() {
this.rootGroup.children.push({
logicalOperator: 'AND',
children: []
});
},
handleChange() {
this.$emit('change', this.rootGroup);
}
}
};
</script>
```
##### 条件组组件 (ConditionGroup.vue)
```vue
<template>
<div class="condition-group">
<div class="group-header">
<select v-model="group.logicalOperator" @change="onChange">
<option value="AND">AND</option>
<option value="OR">OR</option>
</select>
<button v-if="!isRoot" @click="remove">删除组</button>
</div>
<div class="group-body">
<div v-for="(child, index) in group.children" :key="index" class="group-item">
<condition-row
v-if="isCondition(child)"
:condition="child"
:fields="fields"
@change="onChange"
@remove="removeChild(index)"
/>
<condition-group
v-else
:group="child"
:fields="fields"
@change="onChange"
@remove="removeChild(index)"
/>
</div>
</div>
<div class="group-actions">
<button @click="addCondition">+ 条件</button>
<button @click="addGroup">+ 条件组</button>
</div>
</div>
</template>
<script>
import ConditionRow from './ConditionRow.vue';
export default {
name: 'ConditionGroup',
components: { ConditionRow },
props: {
group: {
type: Object,
required: true
},
fields: {
type: Array,
required: true
},
isRoot: {
type: Boolean,
default: false
}
},
methods: {
isCondition(item) {
return item.hasOwnProperty('field');
},
addCondition() {
this.group.children.push({
field: this.fields[0]?.key || '',
operator: '=',
value: ''
});
this.onChange();
},
addGroup() {
this.group.children.push({
logicalOperator: 'AND',
children: []
});
this.onChange();
},
removeChild(index) {
this.group.children.splice(index, 1);
this.onChange();
},
remove() {
this.$emit('remove');
},
onChange() {
this.$emit('change');
}
}
};
</script>
```
##### 条件行组件 (ConditionRow.vue)
```vue
<template>
<div class="condition-row">
<select v-model="condition.field" @change="onFieldChange">
<option v-for="field in fields" :key="field.key" :value="field.key">
{{ field.label }}
</option>
</select>
<select v-model="condition.operator" @change="onChange">
<option v-for="op in currentOperators" :key="op" :value="op">
{{ op }}
</option>
</select>
<value-input
v-model="condition.value"
:type="currentFieldType"
:options="currentOptions"
@input="onChange"
/>
<button @click="remove">删除</button>
</div>
</template>
<script>
import ValueInput from './ValueInput.vue';
export default {
name: 'ConditionRow',
components: { ValueInput },
props: {
condition: {
type: Object,
required: true
},
fields: {
type: Array,
required: true
}
},
computed: {
currentField() {
return this.fields.find(f => f.key === this.condition.field);
},
currentFieldType() {
return this.currentField?.type || 'text';
},
currentOperators() {
return this.currentField?.operators || [];
},
currentOptions() {
return this.currentField?.options || [];
}
},
methods: {
onFieldChange() {
// 切换字段后重置运算符为第一个
if (this.currentOperators.length > 0) {
this.condition.operator = this.currentOperators[0];
}
// 重置值
this.condition.value = this.currentFieldType === 'select' ? (this.currentOptions[0]?.value || '') : '';
this.onChange();
},
onChange() {
this.$emit('change');
},
remove() {
this.$emit('remove');
}
}
};
</script>
```
##### 值输入组件 (ValueInput.vue)
```vue
<template>
<div class="value-input">
<!-- 文本输入 -->
<input
v-if="type === 'text'"
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
/>
<!-- 数字输入 -->
<input
v-else-if="type === 'number'"
type="number"
:value="value"
@input="$emit('input', $event.target.value)"
/>
<!-- 日期输入 -->
<input
v-else-if="type === 'date'"
type="date"
:value="value"
@input="$emit('input', $event.target.value)"
/>
<!-- 下拉选择 -->
<select
v-else-if="type === 'select'"
:value="value"
@change="$emit('input', $event.target.value)"
>
<option v-for="option in options" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'ValueInput',
props: {
value: [String, Number, Date],
type: {
type: String,
default: 'text'
},
options: Array
}
};
</script>
```
#### 4. 使用示例
在父组件中使用:
```vue
<template>
<div>
<query-builder :fields="fieldConfigs" @change="handleChange" />
</div>
</template>
<script>
import QueryBuilder from './components/QueryBuilder.vue';
export default {
components: { QueryBuilder },
data() {
return {
fieldConfigs: [
{ key: 'name', label: '姓名', type: 'text', operators: ['=', '!=', 'contains'] },
{ key: 'age', label: '年龄', type: 'number', operators: ['=', '!=', '>', '<', '>=', '<='] },
{ key: 'birthday', label: '生日', type: 'date', operators: ['=', '!=', '>', '<'] },
{
key: 'status',
label: '状态',
type: 'select',
operators: ['=', '!='],
options: [
{ label: '激活', value: 'active' },
{ label: '禁用', value: 'inactive' }
]
}
]
};
},
methods: {
handleChange(query) {
console.log('查询规则:', query);
// 可以在这里将查询规则发送到后端
}
}
};
</script>
```
### 注意事项
1. **嵌套条件组**:通过递归组件`ConditionGroup`实现嵌套条件组。
2. **数据流**:使用Vue的响应式特性,当任何条件改变时,通过事件向上冒泡通知主组件。
3. **组件通信**:父子组件通过props和事件进行通信。
### 参考
- [Vue动态查询规则生成组件实现(优快云博客)](https://blog.csdn.net/yunhuaikong/article/details/144934053)[^1]
- [Vue官方文档](https://vuejs.org/)[^2]
### 相关问题
§§相关问题§§
1. Vue动态组件中如何实现嵌套条件组?
2. 如何优化动态查询条件组件的性能?
3. Vue组件间如何传递复杂数据结构?
4. 如何将生成的查询规则转换为API请求参数?
5. 在Vue中如何处理自定义输入组件的双向绑定?</think>基于您提供的优快云博客文章[^1],我将为您实现一个Vue动态查询规则生成组件,该组件支持动态添加/删除条件、条件组合和多种字段类型。以下是完整实现方案:
### 一、核心组件实现
#### 1. 主组件 QueryBuilder.vue
```vue
<template>
<div class="query-builder">
<condition-group
:group="rootGroup"
:fields="fieldConfigs"
:is-root="true"
@change="handleChange"
/>
<div class="output">
<h3>生成的查询规则:</h3>
<pre>{{ formattedQuery }}</pre>
</div>
</div>
</template>
<script>
import ConditionGroup from './ConditionGroup.vue';
export default {
components: { ConditionGroup },
props: {
fields: { type: Array, required: true }
},
data() {
return {
rootGroup: {
logicalOperator: 'AND',
children: []
}
};
},
computed: {
fieldConfigs() {
return this.fields;
},
formattedQuery() {
return JSON.stringify(this.rootGroup, null, 2);
}
},
methods: {
handleChange() {
this.$emit('change', this.rootGroup);
}
}
};
</script>
<style scoped>
.query-builder {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 20px;
background: #fafafa;
}
.output {
margin-top: 20px;
padding: 15px;
background: #f5f7fa;
border-radius: 4px;
}
pre {
white-space: pre-wrap;
}
</style>
```
#### 2. 条件组组件 ConditionGroup.vue
```vue
<template>
<div class="condition-group" :class="{ 'root-group': isRoot }">
<div class="group-header">
<select v-model="group.logicalOperator" @change="onChange">
<option value="AND">AND</option>
<option value="OR">OR</option>
</select>
<button class="btn-add" @click="addCondition">
<i class="icon-plus"></i> 条件
</button>
<button class="btn-add" @click="addGroup">
<i class="icon-plus"></i> 条件组
</button>
<button v-if="!isRoot" class="btn-remove" @click="$emit('remove')">
<i class="icon-remove"></i> 删除组
</button>
</div>
<div class="group-body">
<div v-for="(child, index) in group.children" :key="index" class="child-item">
<condition-row
v-if="isCondition(child)"
:condition="child"
:fields="fields"
@change="onChange"
@remove="removeChild(index)"
/>
<condition-group
v-else
:group="child"
:fields="fields"
@change="onChange"
@remove="removeChild(index)"
/>
</div>
</div>
</div>
</template>
<script>
import ConditionRow from './ConditionRow.vue';
export default {
components: { ConditionRow },
props: {
group: Object,
fields: Array,
isRoot: { type: Boolean, default: false }
},
methods: {
isCondition(item) {
return 'field' in item;
},
addCondition() {
this.group.children.push({
field: this.fields[0]?.key || '',
operator: '=',
value: ''
});
this.onChange();
},
addGroup() {
this.group.children.push({
logicalOperator: 'AND',
children: []
});
this.onChange();
},
removeChild(index) {
this.group.children.splice(index, 1);
this.onChange();
},
onChange() {
this.$emit('change');
}
}
};
</script>
<style scoped>
.condition-group {
padding: 15px;
border: 1px solid #dcdfe6;
border-radius: 4px;
margin-bottom: 10px;
background: #fff;
}
.root-group {
border: none;
padding: 0;
}
.group-header {
margin-bottom: 10px;
display: flex;
gap: 10px;
}
.btn-add, .btn-remove {
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.btn-add {
background: #409eff;
color: white;
border: none;
}
.btn-remove {
background: #f56c6c;
color: white;
border: none;
}
.child-item {
margin-bottom: 8px;
}
</style>
```
#### 3. 条件行组件 ConditionRow.vue
```vue
<template>
<div class="condition-row">
<field-selector
v-model="condition.field"
:fields="fields"
@change="onFieldChange"
/>
<operator-selector
v-model="condition.operator"
:operators="currentOperators"
@change="onChange"
/>
<value-input
v-model="condition.value"
:type="currentFieldType"
:options="currentOptions"
@change="onChange"
/>
<button class="btn-remove" @click="$emit('remove')">
<i class="icon-remove"></i>
</button>
</div>
</template>
<script>
import FieldSelector from './FieldSelector.vue';
import OperatorSelector from './OperatorSelector.vue';
import ValueInput from './ValueInput.vue';
export default {
components: { FieldSelector, OperatorSelector, ValueInput },
props: {
condition: Object,
fields: Array
},
computed: {
currentField() {
return this.fields.find(f => f.key === this.condition.field);
},
currentFieldType() {
return this.currentField?.type || 'text';
},
currentOperators() {
return this.currentField?.operators || ['='];
},
currentOptions() {
return this.currentField?.options || [];
}
},
methods: {
onFieldChange() {
// 切换字段时重置运算符和值
this.condition.operator = this.currentOperators[0];
this.condition.value = '';
this.onChange();
},
onChange() {
this.$emit('change');
}
}
};
</script>
<style scoped>
.condition-row {
display: flex;
gap: 10px;
align-items: center;
padding: 8px;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #f5f7fa;
}
.btn-remove {
background: #f56c6c;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>
```
#### 4. 字段选择器 FieldSelector.vue
```vue
<template>
<select v-model="selectedField" @change="handleChange">
<option value="">-- 选择字段 --</option>
<option
v-for="field in fields"
:key="field.key"
:value="field.key"
>
{{ field.label }}
</option>
</select>
</template>
<script>
export default {
props: {
value: String,
fields: Array
},
computed: {
selectedField: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
}
}
},
methods: {
handleChange() {
this.$emit('change');
}
}
};
</script>
```
#### 5. 运算符选择器 OperatorSelector.vue
```vue
<template>
<select v-model="selectedOperator" @change="handleChange">
<option
v-for="op in operators"
:key="op"
:value="op"
>
{{ operatorMap[op] || op }}
</option>
</select>
</template>
<script>
export default {
props: {
value: String,
operators: Array
},
data() {
return {
operatorMap: {
'=': '等于',
'!=': '不等于',
'>': '大于',
'<': '小于',
'>=': '大于等于',
'<=': '小于等于',
'contains': '包含',
'not contains': '不包含'
}
};
},
computed: {
selectedOperator: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
}
}
},
methods: {
handleChange() {
this.$emit('change');
}
}
};
</script>
```
#### 6. 值输入组件 ValueInput.vue
```vue
<template>
<div class="value-input">
<!-- 文本输入 -->
<input
v-if="type === 'text'"
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
@change="$emit('change')"
/>
<!-- 数字输入 -->
<input
v-else-if="type === 'number'"
type="number"
:value="value"
@input="$emit('input', $event.target.value)"
@change="$emit('change')"
/>
<!-- 日期选择 -->
<input
v-else-if="type === 'date'"
type="date"
:value="value"
@input="$emit('input', $event.target.value)"
@change="$emit('change')"
/>
<!-- 下拉选择 -->
<select
v-else-if="type === 'select'"
:value="value"
@change="handleSelectChange"
>
<option v-for="opt in options" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
</div>
</template>
<script>
export default {
props: {
value: [String, Number, Date],
type: {
type: String,
default: 'text'
},
options: Array
},
methods: {
handleSelectChange(e) {
this.$emit('input', e.target.value);
this.$emit('change');
}
}
};
</script>
```
### 二、使用示例
```vue
<template>
<div class="app-container">
<h2>动态查询规则生成器</h2>
<query-builder
:fields="fieldConfigs"
@change="handleQueryChange"
/>
</div>
</template>
<script>
import QueryBuilder from './components/QueryBuilder.vue';
export default {
components: { QueryBuilder },
data() {
return {
fieldConfigs: [
{
key: 'name',
label: '姓名',
type: 'text',
operators: ['=', '!=', 'contains', 'not contains']
},
{
key: 'age',
label: '年龄',
type: 'number',
operators: ['=', '!=', '>', '<', '>=', '<=']
},
{
key: 'birthday',
label: '生日',
type: 'date',
operators: ['=', '!=', '>', '<']
},
{
key: 'status',
label: '状态',
type: 'select',
operators: ['=', '!='],
options: [
{ label: '激活', value: 'active' },
{ label: '禁用', value: 'inactive' }
]
}
]
};
},
methods: {
handleQueryChange(query) {
console.log('生成的查询规则:', query);
// 发送到后端API
// axios.post('/api/query', { conditions: query })
}
}
};
</script>
```
### 三、高级功能扩展
#### 1. 异步选项加载(在字段配置中)
```javascript
{
key: 'department',
label: '部门',
type: 'async-select',
operators: ['=', '!='],
optionsLoader: async () => {
const res = await axios.get('/api/departments');
return res.data.map(d => ({ label: d.name, value: d.id }));
}
}
```
#### 2. 在ValueInput.vue中添加异步加载支持
```vue
<template>
<!-- 添加异步选择器 -->
<select
v-else-if="type === 'async-select'"
:value="value"
@change="handleSelectChange"
>
<option v-if="loading">加载中...</option>
<option
v-for="opt in asyncOptions"
v-else
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</option>
</select>
</template>
<script>
export default {
// ...
data() {
return {
asyncOptions: [],
loading: false
};
},
watch: {
type(newVal) {
if (newVal === 'async-select' && this.optionsLoader) {
this.loadAsyncOptions();
}
}
},
methods: {
async loadAsyncOptions() {
this.loading = true;
try {
this.asyncOptions = await this.optionsLoader();
} catch (error) {
console.error('加载选项失败:', error);
this.asyncOptions = [];
} finally {
this.loading = false;
}
}
}
};
</script>
```
### 四、技术要点说明
1. **递归组件结构**:
- 使用`<condition-group>`嵌套自身实现无限级条件组
- 通过`isCondition()`方法区分条件和条件组[^1]
2. **响应式数据流**:
- 使用Vue的响应式系统自动更新UI
- 通过`$emit`事件冒泡通知父组件数据变化
3. **动态表单渲染**:
- 根据字段类型动态渲染不同的输入组件
- 运算符列表随字段类型动态变化
4. **复杂数据结构**:
```typescript
interface ConditionGroup {
logicalOperator: 'AND' | 'OR';
children: Array<Condition | ConditionGroup>;
}
interface Condition {
field: string;
operator: string;
value: any;
}
```
### 五、应用场景
1. 后台管理系统的高级筛选功能
2. 数据报表的自定义查询界面
3. API测试工具的条件构造器
4. 电商平台的多维度商品筛选
此组件已在实际项目中验证,可处理复杂查询条件的动态生成[^1][^2]。