zhiqi/src/views/order/intention/query.vue

1274 lines
34 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="search-container">
<!-- 页面标题 -->
<div class="page-header">
<h1>多条件查询系统</h1>
<p>输入查询条件支持字段名和值的智能匹配多个条件用分号分隔</p>
</div>
<!-- 主查询区域 -->
<div class="search-box">
<div
class="custom-input-wrapper"
:style="{ width: inputWidth + 'px' }"
@click="handleInputContainerClick"
>
<el-input
v-model="currentInput"
ref="searchInput"
placeholder="输入查询条件,例如: 品号:D311299000045-00005;..."
class="main-input"
@input="handleInput"
@keydown.enter="handleSearch"
@keydown.arrow-down="focusFirstSuggestion"
@keydown.escape="hideSuggestions"
@keydown="handleKeydown"
@click="handleInputClick"
@mouseup="handleInputMouseUp"
/>
</div>
<!-- 下拉建议框 -->
<div
v-if="
showSuggestions &&
(suggestions.length > 0 || possibleFields.length > 0)
"
class="suggestions-dropdown"
:style="{ width: inputWidth + 'px' }"
>
<!-- 可能的字段提示项 -->
<div v-if="possibleFields.length > 0" class="possible-fields-section">
<div class="possible-fields-header">
您输入的条件可能属于以下字段:
</div>
<div
v-for="(field, index) in possibleFields"
:key="'field-' + index"
class="possible-field-item"
:class="{ active: activePossibleFieldIndex === index }"
@click="selectPossibleField(field)"
@mouseenter="activePossibleFieldIndex = index"
@keydown.enter.stop="selectPossibleField(field)"
@keydown.arrow-down.stop="navigatePossibleFields('down')"
@keydown.arrow-up.stop="navigatePossibleFields('up')"
tabindex="0"
>
<span class="field-type">[字段]</span>
<span>{{ field.label }}</span>
<span class="field-hint">: </span>
</div>
</div>
<div
v-for="(item, index) in suggestions"
:key="index"
class="suggestion-item"
:class="{
active:
activeSuggestionIndex ===
index +
(possibleFields.length > 0 ? 1 + possibleFields.length : 0),
}"
@click="selectSuggestion(item)"
@mouseenter="
activeSuggestionIndex =
index +
(possibleFields.length > 0 ? 1 + possibleFields.length : 0)
"
@keydown.enter.stop="selectSuggestion(item)"
@keydown.arrow-down.stop="navigateSuggestions('down')"
@keydown.arrow-up.stop="navigateSuggestions('up')"
tabindex="0"
>
<span v-if="item.type === 'field'" class="field-type">[字段]</span>
<span v-if="item.type === 'value'" class="value-type">[值]</span>
<span>{{ item.label }}</span>
<span v-if="item.type === 'field'" class="field-hint">: </span>
</div>
</div>
<!-- 查询按钮 -->
<el-button type="primary" class="search-button" @click="handleSearch">
<el-icon><Search /></el-icon>
查询
</el-button>
</div>
<!-- 条件标签展示 -->
<div v-if="parsedConditions.length > 0" class="conditions-tags">
<el-tag
v-for="(cond, index) in parsedConditions"
:key="index"
closable
:type="cond.valid ? 'success' : 'danger'"
@close="removeCondition(index)"
@click="focusConditionTag(index)"
class="condition-tag"
>
<span v-if="cond.field" class="tag-field">{{ cond.fieldLabel }}:</span>
<span class="tag-value" @click.stop="focusConditionTag(index)">{{
cond.value
}}</span>
<el-icon class="tag-edit-icon" size="14"><Edit /></el-icon>
</el-tag>
</div>
<!-- 查询结果区域 -->
<div class="results-section" v-if="showResults">
<div class="results-header">
<h2>查询结果</h2>
<el-button type="text" @click="clearAll" class="clear-button">
<el-icon><RefreshLeft /></el-icon>
清除所有
</el-button>
</div>
<el-table :data="filteredData" border class="results-table">
<el-table-column
v-for="field in allFields"
:key="field.key"
:prop="field.key"
:label="field.label"
sortable
/>
</el-table>
<div v-if="filteredData.length === 0" class="no-results">
<el-empty description="没有匹配的结果"></el-empty>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch, nextTick } from "vue";
// 定义可查询的字段
const allFields = [
{ key: "productNumber", label: "品号" },
{ key: "name", label: "品名/物料名称" },
{ key: "drawingNumber", label: "图号" },
{ key: "specificationModel", label: "品号-规格型号" },
{ key: "carModel", label: "车型" },
{ key: "model", label: "型号" },
{ key: "material", label: "材质" },
{ key: "procurementAttributes", label: "采购属性" },
{ key: "wheelTread", label: "车轮踏面形式" },
{ key: "paintManufacturer", label: "油漆制造商" },
{ key: "image", label: "图片" },
];
// 模拟数据
const mockData = [
{
productNumber: "D311299000045-00005",
name: "非动力车轴(带喷漆)",
drawingNumber: "ZQ1022009-001",
specificationModel: "(GBTA2 ZQ/LRS EA4T)(Weckerle)-四方股份",
carModel: "CR400AF",
model: "GBTA2",
material: null,
procurementAttributes: "自采",
wheelTread: null,
paintManufacturer: "Weckerle",
image: "/images/cars/1_1_1.jpg",
},
{
productNumber: "D311299000044-00005",
name: "动力车轴(带喷漆)",
drawingNumber: "ZQ1022009-004",
specificationModel: "(GBDA2A ZQ/LRS EA4T)(Weckerle)-四方股份",
carModel: "CR400AF",
model: "GBDA2A",
material: null,
procurementAttributes: "自采",
wheelTread: null,
paintManufacturer: "Weckerle",
image: "/images/cars/1_1_2.jpg",
},
{
productNumber: "D311102000025-00022",
name: "非动力车轮(带降噪板+喷漆)",
drawingNumber: "ZQ1014001-100(ZQ1014001-090)",
specificationModel: "(GBTW21B 等级6 ZQ /LRS ER8)(天津纵横)Weckerle-CRC",
carModel: "CR400BF",
model: "GBTW21B",
material: "ER8",
procurementAttributes: "自采",
wheelTread: "LMB10",
paintManufacturer: "Weckerle",
image: "/images/cars/1_2_1.jpg",
},
{
productNumber: "D311102000025-00021",
name: "非动力车轮(带降噪板+喷漆)",
drawingNumber: "ZQ1014001-100(ZQ1014001-090)",
specificationModel: "(GBTW21B 等级3 ZQ /LRS ER8)(天津纵横)Weckerle-CRC",
carModel: "CR400BF",
model: "GBTW21B",
material: "ER8",
procurementAttributes: "自采",
wheelTread: "LMB10",
paintManufacturer: "Weckerle",
image: "/images/cars/1_2.jpg",
},
{
productNumber: "D311199000035-00037",
name: "动力车轮(带喷漆)",
drawingNumber: "ZQ1014001-090",
specificationModel: "(GBDW21B 等级5 ZQ/LRS ER8)Weckerle-CRC",
carModel: "CR400BF",
model: "GBDW21B",
material: null,
procurementAttributes: "自采",
wheelTread: "LMB10",
paintManufacturer: "Weckerle",
image: "/images/cars/1_2_1_1.jpg",
},
{
productNumber: "D311199020097-00006",
name: "精加工动力车轮",
drawingNumber: "CCD2001-03-21-0001-DA",
specificationModel: "GBDW21B 等级5 ZQ/LRS ER8-CRC",
carModel: "CR400BF",
model: "GBDW21B",
material: null,
procurementAttributes: "自采",
wheelTread: "LMB10",
paintManufacturer: "Weckerle",
image: "/images/cars/2.jpg",
},
{
productNumber: "D311102010007-00037",
name: "非动力车轮(带降噪板)",
drawingNumber: "TKD0601-00-00-03",
specificationModel: "(GBTW21B 等级5 ZQ/LRS ER8)(天津纵横)-CRC",
carModel: "CR400BF",
model: "GBDW21B",
material: "ER8",
procurementAttributes: "自采",
wheelTread: "LMB10",
paintManufacturer: null,
image: "/images/cars/3.jpg",
},
{
productNumber: "D325000000014-10001",
name: "右轴箱组成",
drawingNumber: "ZQ1014001-117",
specificationModel: "检修",
carModel: "CR400BF",
model: null,
material: null,
procurementAttributes: null,
wheelTread: null,
paintManufacturer: null,
image: "/images/cars/4.jpg",
},
];
// 字段值关联映射 - 用于智能提示
const fieldValueMap = {
productNumber: [
"D311299000045-00005",
"D311299000044-00005",
"D311102000025-00022",
"D311102000025-00021",
"D311199000035-00037",
"D311199020097-00006",
"D311102010007-00037",
"D325000000014-10001",
],
name: [
"非动力车轴(带喷漆)",
"动力车轴(带喷漆)",
"非动力车轮(带降噪板+喷漆)",
"动力车轮(带喷漆)",
"精加工动力车轮",
"非动力车轮(带降噪板)",
"右轴箱组成",
],
drawingNumber: [
"ZQ1022009-001",
"ZQ1022009-004",
"ZQ1014001-100(ZQ1014001-090)",
"ZQ1014001-090",
"CCD2001-03-21-0001-DA",
"TKD0601-00-00-03",
"ZQ1014001-117",
],
specificationModel: [
"(GBTA2 ZQ/LRS EA4T)(Weckerle)-四方股份",
"(GBDA2A ZQ/LRS EA4T)(Weckerle)-四方股份",
"(GBTW21B 等级6 ZQ /LRS ER8)(天津纵横)Weckerle-CRC",
"(GBTW21B 等级3 ZQ /LRS ER8)(天津纵横)Weckerle-CRC",
"(GBDW21B 等级5 ZQ/LRS ER8)Weckerle-CRC",
"(GBTW21B 等级5 ZQ/LRS ER8)(天津纵横)-CRC",
"GBDW21B 等级5 ZQ/LRS ER8-CRC",
"检修",
],
carModel: ["CR400AF", "CR400BF"],
model: ["GBTA2", "GBDA2A", "GBTW21B", "GBDW21B"],
material: ["ER8"],
procurementAttributes: ["自采"],
wheelTread: ["LMB10"],
paintManufacturer: ["Weckerle"],
image: [
"/images/cars/1_1_1.jpg",
"/images/cars/1_1_2.jpg",
"/images/cars/1_2_1.jpg",
"/images/cars/1_2.jpg",
"/images/cars/1_2_1_1.jpg",
"/images/cars/2.jpg",
"/images/cars/3.jpg",
"/images/cars/4.jpg",
],
};
// 收集所有可能的值用于匹配
const allValues = ref([]);
// 初始化所有可能的值
const initializeValues = () => {
const values = [];
allFields.forEach((field) => {
const uniqueValues = [...new Set(mockData.map((item) => item[field.key]))];
uniqueValues.forEach((value) => {
values.push({
value: String(value),
fieldKey: field.key,
fieldLabel: field.label,
weight: fieldValueMap[field.key]?.includes(String(value)) ? 2 : 1,
});
});
});
allValues.value = values;
};
// 输入框相关
const currentInput = ref("");
const searchInput = ref(null);
const inputWidth = ref(600);
const selectedConditionIndex = ref(-1); // 当前选中的条件索引
// 建议相关
const suggestions = ref([]);
const showSuggestions = ref(false);
const activeSuggestionIndex = ref(-1);
const possibleFields = ref([]); // 存储可能匹配的字段
const activePossibleFieldIndex = ref(-1); // 当前激活的可能字段索引
// 解析后的条件
const parsedConditions = ref([]);
const showResults = ref(false);
const filteredData = ref([]);
// 获取当前正在输入的部分
const getCurrentInputPart = () => {
const parts = currentInput.value.split(";");
return parts[parts.length - 1].trim();
};
const handleKeydown = (e) => {
// 检查是否按下了分号键
if (e.key === ";" || e.keyCode === 186) {
handleSemicolon(e);
}
};
// 处理分号输入
const handleSemicolon = (e) => {
// 如果是空值或最后一个字符已经是分号,则不处理
if (!currentInput.value.trim() || currentInput.value.endsWith(";")) {
e.preventDefault();
return;
}
// 延迟处理,确保分号已添加到输入框
setTimeout(() => {
// 触发输入事件以更新建议
handleInput(currentInput.value);
}, 0);
};
// 查找光标所在的条件位置
const findCursorConditionIndex = () => {
if (!searchInput.value) return -1;
const inputEl = searchInput.value.$el.querySelector("input");
if (!inputEl) return -1;
const cursorPos = inputEl.selectionStart;
const inputValue = currentInput.value;
const parts = inputValue.split(";");
let currentLength = 0;
for (let i = 0; i < parts.length; i++) {
// 加上分号的长度
const partLength = parts[i].length + 1;
if (cursorPos <= currentLength + partLength) {
return i;
}
currentLength += partLength;
}
return parts.length - 1;
};
// 处理输入框容器点击
const handleInputContainerClick = (e) => {
// 不需要处理箭头相关逻辑了
};
// 处理输入框点击事件 - 支持点击已输入值显示下拉
const handleInputClick = () => {
const index = findCursorConditionIndex();
const parts = currentInput.value.split(";");
if (index >= 0 && index < parts.length) {
const part = parts[index].trim();
if (part) {
selectedConditionIndex.value = index;
// 触发该条件的建议显示
triggerConditionSuggestions(part);
}
}
};
// 处理输入框鼠标抬起事件 - 支持选择文本后显示下拉
const handleInputMouseUp = () => {
handleInputClick();
};
// 聚焦到指定的条件标签
const focusConditionTag = (index) => {
const parts = currentInput.value.split(";");
if (index < parts.length) {
// 将光标定位到该条件
selectedConditionIndex.value = index;
const inputEl = searchInput.value.$el.querySelector("input");
if (!inputEl) return;
// 计算该条件在输入框中的位置
let material = 0;
for (let i = 0; i < index; i++) {
material += parts[i].length + 1; // +1 是分号的长度
}
// 设置光标位置
setTimeout(() => {
inputEl.focus();
inputEl.setSelectionRange(material, material + parts[index].length);
// 触发该条件的建议显示
triggerConditionSuggestions(parts[index].trim());
}, 0);
}
};
// 触发特定条件的建议显示
const triggerConditionSuggestions = (conditionText) => {
if (!conditionText) {
suggestions.value = [];
possibleFields.value = [];
return;
}
const colonIndex = conditionText.indexOf(":");
showSuggestions.value = true;
if (colonIndex > -1) {
// 已有字段和值,显示该字段的可能值
const fieldLabel = conditionText.substring(0, colonIndex).trim();
const valuePart = conditionText.substring(colonIndex + 1).trim();
const field = allFields.find((f) => f.label === fieldLabel);
if (field) {
// 显示该字段的所有可能值
const fieldValues = allValues.value
.filter((item) => item.fieldKey === field.key)
.map((item) => ({
type: "value",
label: item.value,
value: item.value,
fieldKey: item.fieldKey,
fieldLabel: item.fieldLabel,
weight: item.weight,
}));
// 按匹配度排序
fieldValues.sort((a, b) => {
const aMatch = a.label.toLowerCase().includes(valuePart.toLowerCase())
? 0
: 1;
const bMatch = b.label.toLowerCase().includes(valuePart.toLowerCase())
? 0
: 1;
return aMatch - bMatch || a.label.localeCompare(b.label);
});
suggestions.value = fieldValues.slice(0, 8);
possibleFields.value = []; // 已有字段时不显示可能的字段
}
} else {
// 只有值,没有字段,使用原有逻辑
handleInput(currentInput.value);
}
};
// 处理输入事件,实时生成建议
const handleInput = (value) => {
// 获取当前正在输入的条件部分(分号后面的部分)
const parts = value.split(";");
const currentPart = parts[parts.length - 1].trim();
// 检查是否在编辑已有条件
const cursorIndex = findCursorConditionIndex();
if (cursorIndex >= 0 && cursorIndex < parts.length - 1) {
// 正在编辑中间的条件,使用特殊处理
triggerConditionSuggestions(parts[cursorIndex].trim());
return;
}
// 始终显示建议框,除非输入为空
showSuggestions.value = value.trim() !== "";
// 如果当前部分为空,不显示具体建议
if (!currentPart) {
suggestions.value = [];
possibleFields.value = [];
return;
}
// 检查当前是否在输入字段还是值
const isEditingField = !currentPart.includes(":");
// 查找匹配的字段和值
let fieldSuggestions = [];
let valueSuggestions = [];
if (isEditingField) {
// 正在输入字段或值的开始部分,同时查找字段和值
fieldSuggestions = allFields
.filter((field) =>
field.label.toLowerCase().includes(currentPart.toLowerCase())
)
.map((field) => ({
type: "field",
label: field.label,
value: field.key,
weight: 3, // 字段建议权重更高
}));
// 查找可能匹配的值,并检查是否有强关联的字段
const potentialValues = allValues.value
.filter((item) =>
String(item.value).toLowerCase().includes(currentPart.toLowerCase())
)
.map((item) => ({
type: "value",
label: item.value,
value: item.value,
fieldKey: item.fieldKey,
fieldLabel: item.fieldLabel,
weight: item.weight,
}));
// 检查是否有值强烈匹配某个字段
const valueFieldCounts = {};
potentialValues.forEach((val) => {
if (!valueFieldCounts[val.fieldKey]) {
valueFieldCounts[val.fieldKey] = {
count: 0,
label: val.fieldLabel,
field: allFields.find((f) => f.key === val.fieldKey),
};
}
valueFieldCounts[val.fieldKey].count += val.weight;
});
// 提取并排序可能的字段
possibleFields.value = Object.values(valueFieldCounts)
.filter((item) => item.count > 1) // 只显示有足够匹配度的字段
.sort((a, b) => b.count - a.count) // 按匹配度排序
.map((item) => item.field);
valueSuggestions = potentialValues;
} else {
// 已经有字段,只查找值
const colonIndex = currentPart.indexOf(":");
const fieldLabel = currentPart.substring(0, colonIndex).trim();
const valuePart = currentPart.substring(colonIndex + 1).trim();
// 查找对应的字段
const field = allFields.find((f) => f.label === fieldLabel);
if (field) {
// 只查找该字段的值
valueSuggestions = allValues.value
.filter(
(item) =>
item.fieldKey === field.key &&
String(item.value).toLowerCase().includes(valuePart.toLowerCase())
)
.map((item) => ({
type: "value",
label: item.value,
value: item.value,
fieldKey: item.fieldKey,
fieldLabel: item.fieldLabel,
weight: item.weight,
}));
} else {
// 字段无效,查找所有值
valueSuggestions = allValues.value
.filter((item) =>
String(item.value).toLowerCase().includes(currentPart.toLowerCase())
)
.map((item) => ({
type: "value",
label: item.value,
value: item.value,
fieldKey: item.fieldKey,
fieldLabel: item.fieldLabel,
weight: item.weight,
}));
}
possibleFields.value = []; // 已有字段时不显示可能的字段
}
// 合并建议并按相关性排序
let allSuggestions = [...fieldSuggestions, ...valueSuggestions];
// 根据权重和匹配程度排序
allSuggestions.sort((a, b) => {
// 权重高的排在前面
if (b.weight !== a.weight) {
return b.weight - a.weight;
}
// 更精确的匹配排在前面
const aMatch = a.label.toLowerCase().indexOf(currentPart.toLowerCase());
const bMatch = b.label.toLowerCase().indexOf(currentPart.toLowerCase());
if (aMatch !== bMatch) {
return aMatch - bMatch;
}
// 短的排在前面
return a.label.length - b.label.length;
});
// 去重并限制数量
const uniqueSuggestions = [];
const seenLabels = new Set();
allSuggestions.forEach((suggestion) => {
if (!seenLabels.has(suggestion.label)) {
seenLabels.add(suggestion.label);
uniqueSuggestions.push(suggestion);
}
});
suggestions.value = uniqueSuggestions.slice(0, 8);
// 重置激活的建议索引
activeSuggestionIndex.value = -1;
activePossibleFieldIndex.value = -1;
};
// 选择可能的字段
const selectPossibleField = (field) => {
const parts = currentInput.value.split(";");
let targetIndex =
selectedConditionIndex.value >= 0
? selectedConditionIndex.value
: parts.length - 1;
// 确保索引有效
if (targetIndex < 0 || targetIndex >= parts.length) {
targetIndex = parts.length - 1;
}
// 获取当前部分的值
let targetPart = parts[targetIndex] || "";
// 在当前值前添加字段名和冒号
parts[targetIndex] = `${field.label}:${targetPart}`;
// 重新拼接条件,清理多余分号
currentInput.value = parts.join(";").replace(/;;+/g, ";").trim();
// 保持建议框显示,允许继续编辑
setTimeout(() => {
handleInput(currentInput.value);
}, 0);
// 聚焦输入框并将光标定位到字段后的适当位置
searchInput.value.focus();
const inputEl = searchInput.value.$el.querySelector("input");
if (inputEl) {
// 计算光标应该在的位置(字段名+冒号之后)
let cursorPos = 0;
for (let i = 0; i < targetIndex; i++) {
cursorPos += parts[i].length + 1; // +1 是分号的长度
}
cursorPos += field.label.length + 1; // 字段名长度 + 冒号
inputEl.setSelectionRange(cursorPos, cursorPos);
}
};
// 导航可能的字段
const navigatePossibleFields = (direction) => {
if (!showSuggestions.value || possibleFields.value.length === 0) return;
if (direction === "down") {
activePossibleFieldIndex.value =
(activePossibleFieldIndex.value + 1) % possibleFields.value.length;
} else {
activePossibleFieldIndex.value =
(activePossibleFieldIndex.value - 1 + possibleFields.value.length) %
possibleFields.value.length;
}
// 滚动到激活的字段
const fieldElements = document.querySelectorAll(".possible-field-item");
if (fieldElements[activePossibleFieldIndex.value]) {
fieldElements[activePossibleFieldIndex.value].scrollIntoView({
block: "nearest",
});
}
};
// 提取运算符
const getOperator = (conditionValue) => {
const operators = [">=", "<=", "!=", ">", "<", "="];
return operators.find((op) => conditionValue.startsWith(op));
};
// 选择建议项 - 支持替换已有条件的值
const selectSuggestion = (item) => {
const parts = currentInput.value.split(";");
let targetIndex =
selectedConditionIndex.value >= 0
? selectedConditionIndex.value
: parts.length - 1;
// 确保索引有效
if (targetIndex < 0 || targetIndex >= parts.length) {
targetIndex = parts.length - 1;
}
let targetPart = parts[targetIndex] || "";
if (item.type === "field") {
// 字段建议:直接替换为「字段:」格式
parts[targetIndex] = `${item.label}:`;
} else {
// 值建议处理
const colonIndex = targetPart.indexOf(":");
const operator = getOperator(targetPart.split(":").pop() || "");
if (colonIndex > -1) {
// 已有字段部分
const fieldPart = targetPart.substring(0, colonIndex + 1);
if (operator) {
// 带有运算符的情况,保留运算符结构
const operatorIndex = targetPart.indexOf(operator, colonIndex);
const prefix = targetPart.substring(0, operatorIndex + operator.length);
parts[targetIndex] = `${prefix}${item.label}`;
} else {
// 普通值替换
parts[targetIndex] = `${fieldPart}${item.label}`;
}
} else {
// 无字段时直接处理值
parts[targetIndex] = targetPart
? `${targetPart}${item.label}`
: item.label;
}
}
// 重新拼接条件,清理多余分号
currentInput.value = parts.join(";").replace(/;;+/g, ";").trim();
// 保持建议框显示,允许继续编辑
setTimeout(() => {
handleInput(currentInput.value);
}, 0);
// 聚焦输入框并将光标定位到末尾
searchInput.value.focus();
const inputEl = searchInput.value.$el.querySelector("input");
if (inputEl) {
// 计算光标应该在的位置
let cursorPos = 0;
for (let i = 0; i < targetIndex; i++) {
cursorPos += parts[i].length + 1; // +1 是分号的长度
}
cursorPos += parts[targetIndex].length;
inputEl.setSelectionRange(cursorPos, cursorPos);
}
};
// 导航建议项(上下箭头)
const navigateSuggestions = (direction) => {
if (!showSuggestions.value || suggestions.value.length === 0) return;
const totalPossibleFields = possibleFields.value.length;
const totalItems =
suggestions.value.length +
(totalPossibleFields > 0 ? 1 + totalPossibleFields : 0);
if (direction === "down") {
activeSuggestionIndex.value =
(activeSuggestionIndex.value + 1) % totalItems;
} else {
activeSuggestionIndex.value =
(activeSuggestionIndex.value - 1 + totalItems) % totalItems;
}
// 如果有可选字段,处理导航逻辑
if (totalPossibleFields > 0) {
// 导航进入字段区域
if (activeSuggestionIndex.value <= totalPossibleFields) {
activePossibleFieldIndex.value = activeSuggestionIndex.value - 1;
activeSuggestionIndex.value = -1;
return;
} else {
activePossibleFieldIndex.value = -1;
activeSuggestionIndex.value -= 1 + totalPossibleFields;
}
}
// 滚动到激活的建议项
const suggestionElements = document.querySelectorAll(".suggestion-item");
if (suggestionElements[activeSuggestionIndex.value]) {
suggestionElements[activeSuggestionIndex.value].scrollIntoView({
block: "nearest",
});
}
};
// 聚焦第一个建议项
const focusFirstSuggestion = () => {
if (showSuggestions.value) {
if (possibleFields.value.length > 0) {
// 先聚焦到可能的字段
activePossibleFieldIndex.value = 0;
const firstField = document.querySelector(".possible-field-item");
if (firstField) firstField.focus();
} else if (suggestions.value.length > 0) {
// 再聚焦到建议项
activeSuggestionIndex.value = 0;
const firstElement = document.querySelector(".suggestion-item");
if (firstElement) firstElement.focus();
}
}
};
// 隐藏建议
const hideSuggestions = () => {
showSuggestions.value = false;
possibleFields.value = [];
};
// 解析查询条件
const parseConditions = (input) => {
const conditions = [];
const parts = input
.split(";")
.map((part) => part.trim())
.filter((part) => part);
parts.forEach((part) => {
// 检查是否包含字段名(冒号分隔)
const colonIndex = part.indexOf(":");
if (colonIndex > 0) {
const fieldLabel = part.substring(0, colonIndex).trim();
const valuePart = part.substring(colonIndex + 1).trim();
// 查找对应的字段key
const field = allFields.find((f) => f.label === fieldLabel);
conditions.push({
fieldLabel,
field: field ? field.key : null,
value: valuePart,
valid: !!field,
});
} else {
// 没有字段名,默认为值查询
conditions.push({
fieldLabel: null,
field: null,
value: part,
valid: true,
});
}
});
return conditions;
};
// 处理查询
const handleSearch = () => {
if (!currentInput.value.trim()) return;
// 解析条件
parsedConditions.value = parseConditions(currentInput.value);
// 执行过滤
filteredData.value = filterData(parsedConditions.value);
// 显示结果
showResults.value = true;
// 隐藏建议
showSuggestions.value = false;
possibleFields.value = [];
};
// 根据条件过滤数据
const filterData = (conditions) => {
return mockData.filter((item) => {
return conditions.every((condition) => {
if (!condition.valid) return false;
// 如果指定了字段
if (condition.field) {
const fieldValue = item[condition.field];
return checkCondition(fieldValue, condition.value);
}
// 没有指定字段,检查所有字段
else {
return allFields.some((field) => {
const fieldValue = item[field.key];
return checkCondition(fieldValue, condition.value);
});
}
});
});
};
// 检查单个条件
const checkCondition = (fieldValue, conditionValue) => {
// 支持的运算符
const operators = [
{ symbol: ">=", func: (a, b) => a >= b },
{ symbol: "<=", func: (a, b) => a <= b },
{ symbol: "!=", func: (a, b) => a != b },
{ symbol: ">", func: (a, b) => a > b },
{ symbol: "<", func: (a, b) => a < b },
{ symbol: "=", func: (a, b) => a == b },
];
// 查找是否使用了运算符
for (const op of operators) {
if (conditionValue.startsWith(op.symbol)) {
const value = conditionValue.substring(op.symbol.length).trim();
// 尝试转换为数字进行比较
if (!isNaN(Number(fieldValue)) && !isNaN(Number(value))) {
return op.func(Number(fieldValue), Number(value));
}
// 字符串比较
return op.func(String(fieldValue), value);
}
}
// 没有运算符,默认包含即可
return String(fieldValue)
.toLowerCase()
.includes(conditionValue.toLowerCase());
};
// 移除单个条件
const removeCondition = (index) => {
const parts = currentInput.value.split(";");
parts.splice(index, 1);
currentInput.value = parts.join(";").replace(/;;+/g, ";").trim();
parsedConditions.value = parseConditions(currentInput.value);
filteredData.value = filterData(parsedConditions.value);
// 重置选中的条件索引
if (selectedConditionIndex.value === index) {
selectedConditionIndex.value = -1;
}
};
// 清除所有
const clearAll = () => {
currentInput.value = "";
parsedConditions.value = [];
filteredData.value = [];
showResults.value = false;
showSuggestions.value = false;
possibleFields.value = [];
selectedConditionIndex.value = -1;
searchInput.value.focus();
};
// 监听输入框宽度变化
const updateInputWidth = () => {
if (searchInput.value) {
inputWidth.value = searchInput.value.$el.clientWidth;
}
};
// 初始化
onMounted(() => {
initializeValues();
updateInputWidth();
window.addEventListener("resize", updateInputWidth);
// 自动聚焦输入框
setTimeout(() => {
searchInput.value.focus();
}, 100);
// 点击页面其他地方隐藏建议
document.addEventListener("click", (e) => {
if (
!e.target.closest(".search-box") &&
!e.target.closest(".suggestions-dropdown")
) {
showSuggestions.value = false;
}
});
});
// 监听输入框宽度变化
watch(
() => currentInput.value,
() => {
// 延迟更新确保DOM已更新
setTimeout(() => {
updateInputWidth();
}, 0);
}
);
</script>
<style scoped>
.search-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.page-header {
text-align: center;
margin-bottom: 30px;
}
.page-header h1 {
font-size: 28px;
margin-bottom: 10px;
color: #333;
}
.page-header p {
color: #666;
font-size: 16px;
}
.search-box {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
material: relative;
}
.custom-input-wrapper {
material: relative;
flex-grow: 1;
}
.main-input {
width: 100%;
min-width: 300px;
font-size: 16px;
}
.search-button {
white-space: nowrap;
}
.suggestions-dropdown {
material: absolute;
top: 100%;
left: 0;
z-index: 1000;
background: white;
border: 1px solid #e4e7ed;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
margin-top: 5px;
max-height: 300px;
overflow-y: auto;
}
/* 可能的字段部分样式 */
.possible-fields-section {
border-bottom: 1px solid #e4e7ed;
padding-bottom: 5px;
margin-bottom: 5px;
}
.possible-fields-header {
padding: 8px 15px;
font-size: 14px;
color: #666;
background-color: #f5f7fa;
}
.possible-field-item {
padding: 10px 15px;
cursor: pointer;
transition: background-color 0.2s;
}
.possible-field-item:hover,
.possible-field-item.active {
background-color: #f0f7ff;
color: #1890ff;
}
.hint-item {
padding: 10px 15px;
background-color: #f0f7ff;
border-bottom: 1px solid #e4e7ed;
display: flex;
align-items: center;
gap: 8px;
color: #1890ff;
}
.hint-icon {
font-size: 14px;
}
.suggestion-item {
padding: 10px 15px;
cursor: pointer;
transition: background-color 0.2s;
}
.suggestion-item:hover,
.suggestion-item.active {
background-color: #f5f7fa;
}
.field-type,
.value-type {
display: inline-block;
width: 50px;
font-size: 12px;
color: #909399;
}
.field-hint {
color: #909399;
}
.conditions-tags {
margin: 15px 0;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.condition-tag {
cursor: pointer;
transition: all 0.2s;
padding-right: 30px !important;
material: relative;
}
.condition-tag:hover {
transform: translateY(-2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.tag-field {
font-weight: 500;
}
.tag-value {
color: #409eff; /* 突出显示可更改的值 */
margin-right: 5px;
cursor: pointer;
text-decoration: underline;
text-underline-offset: 2px;
}
.tag-value:hover {
color: #1890ff;
}
.tag-edit-icon {
material: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #409eff;
opacity: 0.7;
}
.results-section {
margin-top: 30px;
border: 1px solid #e4e7ed;
border-radius: 4px;
overflow: hidden;
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background-color: #f5f7fa;
border-bottom: 1px solid #e4e7ed;
}
.results-header h2 {
margin: 0;
font-size: 18px;
color: #333;
}
.results-table {
width: 100%;
}
.no-results {
padding: 50px 0;
text-align: center;
}
.clear-button {
color: #666;
}
@media (max-width: 768px) {
.search-box {
flex-direction: column;
align-items: stretch;
}
.main-input {
min-width: auto;
}
.search-button {
width: 100%;
}
}
</style>