first commit
This commit is contained in:
@@ -0,0 +1,593 @@
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "index",
|
||||
props: {
|
||||
group: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
field_options: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
operator_options: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ label: "等于", value: "=" },
|
||||
{ label: "不等于", value: "!=" },
|
||||
{ label: "大于", value: ">" },
|
||||
{ label: "小于", value: "<" },
|
||||
{ label: "大于等于", value: ">=" },
|
||||
{ label: "小于等于", value: "<=" },
|
||||
{ label: "包含", value: "LIKE" },
|
||||
{ label: "为空", value: "IS NULL" },
|
||||
{ label: "不为空", value: "IS NOT NULL" },
|
||||
{ label: "在列表中", value: "IN" },
|
||||
{ label: "不在列表中", value: "NOT IN" },
|
||||
{ label: "在范围内", value: "BETWEEN" }
|
||||
]
|
||||
},
|
||||
can_remove: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fieldTypes: {}, // 存储字段类型信息
|
||||
errors: [] // 存储验证错误
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化字段类型信息
|
||||
this.initFieldTypes()
|
||||
},
|
||||
methods: {
|
||||
initFieldTypes() {
|
||||
// 从field_options中提取字段类型信息
|
||||
this.field_options.forEach((field) => {
|
||||
if (field.type) {
|
||||
this.fieldTypes[field.value] = field.type
|
||||
} else {
|
||||
// 默认为字符串类型
|
||||
this.fieldTypes[field.value] = "string"
|
||||
}
|
||||
})
|
||||
},
|
||||
getFieldType(fieldName) {
|
||||
return this.fieldTypes[fieldName] || "string"
|
||||
},
|
||||
addCondition() {
|
||||
if (!this.group.conditions) {
|
||||
this.$set(this.group, "conditions", [])
|
||||
}
|
||||
this.group.conditions.push({
|
||||
field: "",
|
||||
operator: "=",
|
||||
value: "",
|
||||
valid: false
|
||||
})
|
||||
this.validateAndEmitChange()
|
||||
},
|
||||
removeCondition(index) {
|
||||
this.group.conditions.splice(index, 1)
|
||||
this.validateAndEmitChange()
|
||||
},
|
||||
addGroup() {
|
||||
if (!this.group.groups) {
|
||||
this.$set(this.group, "groups", [])
|
||||
}
|
||||
this.group.groups.push({
|
||||
logic: "AND",
|
||||
conditions: [],
|
||||
groups: []
|
||||
})
|
||||
this.validateAndEmitChange()
|
||||
},
|
||||
removeGroup(index) {
|
||||
this.group.groups.splice(index, 1)
|
||||
this.validateAndEmitChange()
|
||||
},
|
||||
isNullOperator(operator) {
|
||||
return operator === "IS NULL" || operator === "IS NOT NULL"
|
||||
},
|
||||
isInOperator(operator) {
|
||||
return operator === "IN" || operator === "NOT IN"
|
||||
},
|
||||
isBetweenOperator(operator) {
|
||||
return operator === "BETWEEN"
|
||||
},
|
||||
validateCondition(condition) {
|
||||
// 检查条件是否有效
|
||||
if (!condition.field || !condition.operator) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 对于IS NULL和IS NOT NULL操作符,不需要值
|
||||
if (this.isNullOperator(condition.operator)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 对于其他操作符,需要值
|
||||
return condition.value !== undefined && condition.value !== ""
|
||||
},
|
||||
validateGroup(group) {
|
||||
let isValid = true
|
||||
|
||||
// 验证条件
|
||||
if (group.conditions && group.conditions.length > 0) {
|
||||
group.conditions.forEach((condition) => {
|
||||
condition.valid = this.validateCondition(condition)
|
||||
isValid = isValid && condition.valid
|
||||
})
|
||||
}
|
||||
|
||||
// 验证嵌套组
|
||||
if (group.groups && group.groups.length > 0) {
|
||||
group.groups.forEach((nestedGroup) => {
|
||||
isValid = isValid && this.validateGroup(nestedGroup)
|
||||
})
|
||||
}
|
||||
|
||||
// 一个组至少需要一个条件或嵌套组才有效
|
||||
if ((group.conditions && group.conditions.length > 0) || (group.groups && group.groups.length > 0)) {
|
||||
return isValid
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
formatValue(value, type) {
|
||||
// 根据字段类型格式化值
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return "NULL"
|
||||
}
|
||||
|
||||
switch (type.toLowerCase()) {
|
||||
case "number":
|
||||
case "int":
|
||||
case "integer":
|
||||
case "float":
|
||||
case "double":
|
||||
case "decimal":
|
||||
return value
|
||||
case "date":
|
||||
case "datetime":
|
||||
case "time":
|
||||
case "string":
|
||||
default:
|
||||
// 转义单引号
|
||||
const escaped = String(value).replace(/'/g, "''")
|
||||
return `'${escaped}'`
|
||||
}
|
||||
},
|
||||
buildSql(group = this.group) {
|
||||
let sql = []
|
||||
|
||||
// 处理条件
|
||||
if (group.conditions && group.conditions.length > 0) {
|
||||
group.conditions.forEach((condition) => {
|
||||
if (condition.field && condition.operator) {
|
||||
const fieldType = this.getFieldType(condition.field)
|
||||
|
||||
if (this.isNullOperator(condition.operator)) {
|
||||
sql.push(`${condition.field} ${condition.operator}`)
|
||||
} else if (this.isInOperator(condition.operator) && condition.value) {
|
||||
// 处理IN操作符 - 假设值是以逗号分隔的列表
|
||||
const values = condition.value
|
||||
.split(",")
|
||||
.map((v) => v.trim())
|
||||
.filter((v) => v !== "")
|
||||
.map((v) => this.formatValue(v, fieldType))
|
||||
.join(", ")
|
||||
|
||||
if (values) {
|
||||
sql.push(`${condition.field} ${condition.operator} (${values})`)
|
||||
}
|
||||
} else if (this.isBetweenOperator(condition.operator) && condition.value) {
|
||||
// 处理BETWEEN操作符 - 假设值是以逗号分隔的两个值
|
||||
const parts = condition.value.split(",").map((v) => v.trim())
|
||||
if (parts.length === 2) {
|
||||
const from = this.formatValue(parts[0], fieldType)
|
||||
const to = this.formatValue(parts[1], fieldType)
|
||||
sql.push(`${condition.field} BETWEEN ${from} AND ${to}`)
|
||||
}
|
||||
} else if (condition.value !== undefined && condition.value !== "") {
|
||||
// 其他操作符
|
||||
const formattedValue = this.formatValue(condition.value, fieldType)
|
||||
sql.push(`${condition.field} ${condition.operator} ${formattedValue}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理嵌套组
|
||||
if (group.groups && group.groups.length > 0) {
|
||||
group.groups.forEach((nestedGroup) => {
|
||||
const nestedSql = this.buildSql(nestedGroup)
|
||||
if (nestedSql) {
|
||||
sql.push(`(${nestedSql})`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 用逻辑运算符连接所有条件
|
||||
return sql.length > 0 ? sql.join(` ${group.logic} `) : ""
|
||||
},
|
||||
validateAndEmitChange() {
|
||||
// 验证所有条件
|
||||
const isValid = this.validateGroup(this.group)
|
||||
|
||||
// 发出变更事件
|
||||
this.$emit("change", {
|
||||
sql: this.buildSql(),
|
||||
valid: isValid
|
||||
})
|
||||
},
|
||||
getOperatorsByFieldType(fieldType) {
|
||||
// 根据字段类型过滤可用的操作符
|
||||
if (!fieldType) return this.operator_options
|
||||
|
||||
const type = fieldType.toLowerCase()
|
||||
return this.operator_options.filter((op) => {
|
||||
// 数值类型
|
||||
if (['number', 'int', 'integer', 'float', 'double', 'decimal'].includes(type)) {
|
||||
// 数值类型不应该使用LIKE操作符
|
||||
return op.value !== 'LIKE'
|
||||
}
|
||||
// 日期类型
|
||||
else if (['date', 'datetime', 'time'].includes(type)) {
|
||||
// 日期类型不应该使用LIKE和IN操作符
|
||||
return !['LIKE', 'IN', 'NOT IN'].includes(op.value)
|
||||
}
|
||||
// 字符串类型,支持所有操作符
|
||||
return true
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
group: {
|
||||
handler: "validateAndEmitChange",
|
||||
deep: true
|
||||
},
|
||||
field_options: {
|
||||
handler: "initFieldTypes",
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="condition-builder">
|
||||
<!-- 主逻辑选择和组操作 -->
|
||||
<div class="condition-group-header">
|
||||
<div class="logic-selector">
|
||||
<span class="logic-label">匹配方式:</span>
|
||||
<el-radio-group v-model="group.logic" size="small" @change="validateAndEmitChange">
|
||||
<el-radio-button label="AND">满足所有条件</el-radio-button>
|
||||
<el-radio-button label="OR">满足任意条件</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-button v-if="can_remove" type="danger" size="mini" icon="el-icon-delete" @click="$emit('remove')" class="remove-group-btn">
|
||||
删除组
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 条件列表 -->
|
||||
<div class="conditions-container">
|
||||
<div
|
||||
v-for="(condition, index) in group.conditions"
|
||||
:key="'c-' + index"
|
||||
class="condition-row"
|
||||
:class="{ 'invalid-condition': condition.valid === false }"
|
||||
>
|
||||
<div class="condition-index">{{ index + 1 }}</div>
|
||||
|
||||
<el-select
|
||||
v-model="condition.field"
|
||||
filterable
|
||||
placeholder="请选择字段"
|
||||
size="small"
|
||||
class="field-select"
|
||||
@change="validateAndEmitChange"
|
||||
>
|
||||
<el-option v-for="field in field_options" :key="field.value" :label="field.label" :value="field.value">
|
||||
<span>{{ field.label }}</span>
|
||||
<span class="field-type-hint" v-if="field.type">({{ field.type }})</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="condition.operator" placeholder="操作符" size="small" class="operator-select" @change="validateAndEmitChange">
|
||||
<el-option
|
||||
v-for="op in getOperatorsByFieldType(getFieldType(condition.field))"
|
||||
:key="op.value"
|
||||
:label="op.label"
|
||||
:value="op.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
|
||||
<!-- 根据操作符类型和字段类型显示不同的输入控件 -->
|
||||
<template v-if="!isNullOperator(condition.operator)">
|
||||
<!-- 对于IN操作符,显示标签输入框 -->
|
||||
<el-input
|
||||
v-if="isInOperator(condition.operator)"
|
||||
v-model="condition.value"
|
||||
placeholder="多个值用逗号分隔"
|
||||
size="small"
|
||||
class="value-input"
|
||||
@change="validateAndEmitChange"
|
||||
></el-input>
|
||||
|
||||
<!-- 对于BETWEEN操作符,显示两个输入框 -->
|
||||
<div v-else-if="isBetweenOperator(condition.operator)" class="between-inputs">
|
||||
<el-input
|
||||
v-model="condition.value"
|
||||
placeholder="起始值,结束值"
|
||||
size="small"
|
||||
class="value-input"
|
||||
@change="validateAndEmitChange"
|
||||
></el-input>
|
||||
</div>
|
||||
|
||||
<!-- 对于其他操作符,根据字段类型显示不同的输入控件 -->
|
||||
<template v-else>
|
||||
<!-- 数字类型 -->
|
||||
<el-input
|
||||
v-if="['number', 'int', 'integer', 'float', 'double', 'decimal'].includes(getFieldType(condition.field))"
|
||||
v-model.number="condition.value"
|
||||
placeholder="请输入数值"
|
||||
size="small"
|
||||
class="value-input"
|
||||
type="number"
|
||||
@change="validateAndEmitChange"
|
||||
></el-input>
|
||||
|
||||
<!-- 日期类型 -->
|
||||
<el-date-picker
|
||||
v-else-if="getFieldType(condition.field) === 'date'"
|
||||
v-model="condition.value"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
size="small"
|
||||
class="value-input"
|
||||
value-format="yyyy-MM-dd"
|
||||
@change="validateAndEmitChange"
|
||||
></el-date-picker>
|
||||
|
||||
<!-- 日期时间类型 -->
|
||||
<el-date-picker
|
||||
v-else-if="getFieldType(condition.field) === 'datetime'"
|
||||
v-model="condition.value"
|
||||
type="datetime"
|
||||
placeholder="选择日期时间"
|
||||
size="small"
|
||||
class="value-input"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
@change="validateAndEmitChange"
|
||||
></el-date-picker>
|
||||
|
||||
<!-- 时间类型 -->
|
||||
<el-time-picker
|
||||
v-else-if="getFieldType(condition.field) === 'time'"
|
||||
v-model="condition.value"
|
||||
placeholder="选择时间"
|
||||
size="small"
|
||||
class="value-input"
|
||||
value-format="HH:mm:ss"
|
||||
@change="validateAndEmitChange"
|
||||
></el-time-picker>
|
||||
|
||||
<!-- 默认为字符串类型 -->
|
||||
<el-input
|
||||
v-else
|
||||
v-model="condition.value"
|
||||
placeholder="请输入值"
|
||||
size="small"
|
||||
class="value-input"
|
||||
@change="validateAndEmitChange"
|
||||
></el-input>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 空值占位,使布局保持一致 -->
|
||||
<div v-else class="value-placeholder"></div>
|
||||
|
||||
<el-button type="danger" size="mini" icon="el-icon-delete" @click="removeCondition(index)" class="remove-btn"></el-button>
|
||||
</div>
|
||||
|
||||
<!-- 空条件提示 -->
|
||||
<div class="empty-condition-hint" v-if="!group.conditions || group.conditions.length === 0">
|
||||
<i class="el-icon-info"></i>
|
||||
请添加筛选条件
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 嵌套条件组 -->
|
||||
<div v-for="(nestedGroup, index) in group.groups" :key="'g-' + index" class="nested-group">
|
||||
<div class="nested-group-header">
|
||||
<span class="nested-group-title">子条件组 {{ index + 1 }}</span>
|
||||
</div>
|
||||
<condition-group
|
||||
:group="nestedGroup"
|
||||
:field_options="field_options"
|
||||
:operator_options="operator_options"
|
||||
:can_remove="true"
|
||||
@remove="removeGroup(index)"
|
||||
@change="validateAndEmitChange"
|
||||
></condition-group>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<div class="condition-actions">
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" @click="addCondition">添加条件</el-button>
|
||||
<el-button type="success" size="small" icon="el-icon-folder-add" @click="addGroup">添加条件组</el-button>
|
||||
</div>
|
||||
|
||||
<!-- SQL预览 -->
|
||||
<div class="sql-preview" v-if="buildSql()">
|
||||
<div class="sql-preview-header">
|
||||
<div class="sql-preview-title">条件预览:</div>
|
||||
</div>
|
||||
<el-input type="textarea" :value="buildSql()" readonly :rows="2" class="sql-preview-content"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.condition-builder {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.condition-group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.logic-label {
|
||||
margin-right: 10px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.conditions-container {
|
||||
padding: 5px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.condition-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.condition-row:hover {
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
.invalid-condition {
|
||||
border: 1px dashed #f56c6c;
|
||||
}
|
||||
|
||||
.condition-index {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
background-color: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.field-select {
|
||||
width: 150px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.operator-select {
|
||||
width: 120px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.value-input {
|
||||
width: 200px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.between-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 200px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.value-placeholder {
|
||||
width: 200px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.field-type-hint {
|
||||
color: #909399;
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.nested-group {
|
||||
margin: 10px 0;
|
||||
padding: 10px 0 10px 20px;
|
||||
border-left: 2px solid #409eff;
|
||||
background-color: #f9fafc;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.nested-group-header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nested-group-title {
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.condition-actions {
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.remove-group-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.sql-preview {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.sql-preview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sql-preview-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.sql-preview-content {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.empty-condition-hint {
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user