2401 lines
61 KiB
Vue
2401 lines
61 KiB
Vue
<template>
|
||
<div class="step2-container">
|
||
<div class="main-row">
|
||
<div class="right-content">
|
||
<div>
|
||
<div class="search-box-container">
|
||
<div class="page-header">
|
||
<h1>品号查询</h1>
|
||
</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"
|
||
>
|
||
<!-- 输入框内添加话筒图标 -->
|
||
<template #append>
|
||
<el-icon
|
||
class="voice-icon"
|
||
@click.stop="showVoicePopup = true"
|
||
>
|
||
<Microphone />
|
||
</el-icon>
|
||
</template>
|
||
</el-input>
|
||
</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="showKeyDiffHint" class="key-diff-hint">
|
||
以下商品列表中,关键差异信息:
|
||
<span
|
||
v-for="(item, index) in keyDiffFields"
|
||
:key="index"
|
||
class="diff-field"
|
||
@click="addDiffFieldToInput(item)"
|
||
>
|
||
{{ item.label }}
|
||
<template v-if="index < keyDiffFields.length - 1"> 、</template>
|
||
</span>
|
||
</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
|
||
>
|
||
<!-- 将编辑图标替换为"精准"文字,并添加点击事件 -->
|
||
<span
|
||
class="tag-precise"
|
||
@click.stop="addPreciseCondition(index)"
|
||
>精准</span
|
||
>
|
||
</el-tag>
|
||
<el-button link @click="clearAll" class="clear-button">
|
||
<el-icon><RefreshLeft /></el-icon>
|
||
清除所有
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 精准查询参数展示区域 -->
|
||
<div v-if="preciseConditions.length > 0" class="precise-conditions">
|
||
<div class="precise-conditions-label">选择精准查询参数:</div>
|
||
<div class="precise-tags">
|
||
<el-tag
|
||
v-for="(cond, index) in preciseConditions"
|
||
:key="'precise-' + index"
|
||
closable
|
||
type="primary"
|
||
@close="removePreciseCondition(index)"
|
||
class="precise-tag"
|
||
>
|
||
<span v-if="cond.field" class="tag-field"
|
||
>{{ cond.fieldLabel }}:</span
|
||
>
|
||
<span class="tag-value">{{ cond.value }}</span>
|
||
</el-tag>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 查询结果区域 -->
|
||
<div class="results-section" v-if="showResults">
|
||
<div
|
||
v-if="currentPageData.length"
|
||
class="result-card-list-wrap"
|
||
style="position: relative"
|
||
>
|
||
<!-- 参数对比按钮 - 随滚动条移动 -->
|
||
<el-button
|
||
v-if="selectedCompareList.length > 0"
|
||
type="warning"
|
||
class="compare-btn-float"
|
||
@click="onCompare"
|
||
circle
|
||
>
|
||
参数<br />对比
|
||
</el-button>
|
||
<div class="result-card-list">
|
||
<el-card
|
||
v-for="item in currentPageData"
|
||
:key="item.productNumber"
|
||
class="result-card"
|
||
:class="{ active: item.selected }"
|
||
@click="toggleSelect(item)"
|
||
>
|
||
<div class="result-card-info">
|
||
<div class="result-card-name" :title="item.name">
|
||
{{ item.name }}
|
||
</div>
|
||
<div
|
||
class="result-card-parameter"
|
||
:title="`品号:${item.productNumber}`"
|
||
>
|
||
<span>品号:</span>{{ item.productNumber }}
|
||
</div>
|
||
<div
|
||
class="result-card-parameter"
|
||
:title="`品号-规格型号:${item.specificationModel}`"
|
||
>
|
||
<span>品号-规格型号: </span>{{ item.specificationModel }}
|
||
</div>
|
||
<div
|
||
class="result-card-parameter text-truncate"
|
||
:title="`车型:${item.carModel}`"
|
||
>
|
||
<span>车型:</span> {{ item.carModel }}
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="currentPageData.length === 0" class="no-results">
|
||
<el-empty description="没有匹配的结果"></el-empty>
|
||
</div>
|
||
|
||
<!-- 分页控件 -->
|
||
<div class="pagination-container" v-if="showResults">
|
||
<el-pagination
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
:current-page="currentPage"
|
||
:page-sizes="[8, 16, 32]"
|
||
:page-size="pageSize"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="totalItems"
|
||
></el-pagination>
|
||
|
||
<!-- 确定按钮 -->
|
||
<el-button
|
||
type="primary"
|
||
class="confirm-button"
|
||
@click="handleConfirm"
|
||
:disabled="selectedCount !== 1"
|
||
>
|
||
确定
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 参数对比弹窗 -->
|
||
<el-dialog
|
||
v-model="compareDialogVisible"
|
||
title="参数对比"
|
||
width="1200px"
|
||
top="40px"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<!-- 仅看不同项按钮 -->
|
||
<div
|
||
class="compare-filter-options"
|
||
style="margin: -45px 80px 16px; text-align: left"
|
||
>
|
||
<el-button
|
||
type="primary"
|
||
@click="showOnlyDifferences = !showOnlyDifferences"
|
||
>
|
||
{{ showOnlyDifferences ? "显示全部" : "仅看不同项" }}
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 表格对比区,添加横向滚动 -->
|
||
<div class="compare-table-wrap">
|
||
<el-table
|
||
:data="filteredCompareTableData"
|
||
border
|
||
style="width: 100%; min-width: 600px"
|
||
>
|
||
<el-table-column
|
||
prop="param"
|
||
label="参数"
|
||
width="180"
|
||
class-name="param-col"
|
||
align="center"
|
||
header-align="center"
|
||
fixed="left"
|
||
/>
|
||
<el-table-column
|
||
v-for="item in sortedCompareList"
|
||
:key="item.productNumber"
|
||
:label="item.productNumber"
|
||
:prop="item.productNumber"
|
||
align="center"
|
||
header-align="center"
|
||
min-width="300"
|
||
>
|
||
<template #header="{ column }">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
"
|
||
>
|
||
<span v-if="item.isLatest" class="new-tag">
|
||
<img
|
||
src="/images/cars/new.png"
|
||
alt="最新品号"
|
||
class="new-tag-img"
|
||
/>
|
||
</span>
|
||
<div
|
||
style="
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
"
|
||
>
|
||
<span>
|
||
{{ column.label }}
|
||
</span>
|
||
<span style="font-size: 12px; color: #666">{{
|
||
sortedCompareList.find(
|
||
(i) => i.productNumber === column.label
|
||
).name
|
||
}}</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<!-- 单元格内容,不同的值标红 -->
|
||
<template #default="scope">
|
||
<span
|
||
:class="{
|
||
'different-value': isValueDifferent(scope.row.param),
|
||
}"
|
||
>
|
||
{{ scope.row[scope.column.property] || "-" }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 选择确认提示弹窗 -->
|
||
<el-dialog
|
||
v-model="confirmDialogVisible"
|
||
title="选择确认"
|
||
width="400px"
|
||
:show-close="false"
|
||
>
|
||
<div class="confirm-message">
|
||
您选择的品号为:{{ selectedProductNumber }}
|
||
</div>
|
||
<template #footer>
|
||
<el-button type="primary" @click="confirmDialogVisible = false">
|
||
确定
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 语音输入弹窗 -->
|
||
<el-dialog
|
||
v-model="showVoicePopup"
|
||
title="语音输入"
|
||
width="400px"
|
||
:show-close="true"
|
||
>
|
||
<div class="voice-popup-content">
|
||
正式环境中提供语音输入检索功能,demo中仅做示意。
|
||
</div>
|
||
<template #footer>
|
||
<el-button type="primary" @click="showVoicePopup = false">
|
||
我知道了
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch, onMounted, nextTick } from "vue";
|
||
import {
|
||
paramsToCompare,
|
||
fieldMap,
|
||
allFields,
|
||
mockData,
|
||
fieldValueMap,
|
||
} from "@/data/step2MockData";
|
||
import { ElMessage, ElEmpty, ElDialog } from "element-plus";
|
||
import { Search, Edit, RefreshLeft, Microphone } from "@element-plus/icons-vue";
|
||
|
||
// 接收props和定义emit
|
||
const props = defineProps({
|
||
form: Object,
|
||
selectedCarType: String,
|
||
});
|
||
const emit = defineEmits(["update:form"]);
|
||
|
||
// 双向绑定
|
||
const selectedCarTypeProxy = computed({
|
||
get() {
|
||
return props.form?.selectedCarType ?? "";
|
||
},
|
||
set(val) {
|
||
if (props.form) {
|
||
props.form.selectedCarType = val;
|
||
emit("update:form", props.form);
|
||
}
|
||
},
|
||
});
|
||
|
||
// 查询相关变量
|
||
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 showVoicePopup = ref(false);
|
||
|
||
// 解析后的条件和查询结果
|
||
const parsedConditions = ref([]);
|
||
// 精准查询条件
|
||
const preciseConditions = ref([]);
|
||
const showResults = ref(false);
|
||
const filteredData = ref([]);
|
||
|
||
// 分页相关变量
|
||
const currentPage = ref(1);
|
||
const pageSize = ref(8);
|
||
const totalItems = ref(0);
|
||
|
||
// 计算当前页数据
|
||
const currentPageData = computed(() => {
|
||
const startIndex = (currentPage.value - 1) * pageSize.value;
|
||
return filteredData.value.slice(startIndex, startIndex + pageSize.value);
|
||
});
|
||
|
||
// 计算选中的商品数量
|
||
const selectedCount = computed(() => {
|
||
return filteredData.value.filter((item) => item.selected).length;
|
||
});
|
||
|
||
// 选中的商品品号
|
||
const selectedProductNumber = computed(() => {
|
||
const selected = filteredData.value.find((item) => item.selected);
|
||
return selected ? selected.productNumber : "";
|
||
});
|
||
|
||
// 对比相关
|
||
const compareDialogVisible = ref(false);
|
||
const selectedCompareList = computed(() =>
|
||
filteredData.value.filter((i) => i.selected)
|
||
);
|
||
// 仅显示不同项的开关
|
||
const showOnlyDifferences = ref(false);
|
||
// 存储参数差异信息的映射
|
||
const parameterDifferences = ref({});
|
||
|
||
// 确认弹窗
|
||
const confirmDialogVisible = ref(false);
|
||
|
||
// 关键差异字段相关
|
||
const keyDiffFields = ref([]);
|
||
const showKeyDiffHint = ref(false);
|
||
|
||
// 计算排序后的对比列表(最新品号在前)
|
||
const sortedCompareList = computed(() => {
|
||
// 复制数组以避免修改原始数据
|
||
const list = [...selectedCompareList.value];
|
||
|
||
// 排序:根据品号结尾的五位数从大到小排序
|
||
list.sort((a, b) => {
|
||
// 提取品号结尾的五位数
|
||
const aMatch = a.productNumber.match(/-(\d{5})$/);
|
||
const bMatch = b.productNumber.match(/-(\d{5})$/);
|
||
|
||
const aNum = aMatch ? parseInt(aMatch[1], 10) : 0;
|
||
const bNum = bMatch ? parseInt(bMatch[1], 10) : 0;
|
||
|
||
return bNum - aNum;
|
||
});
|
||
|
||
// 标记最新的品号(排序后的第一个)
|
||
if (list.length > 0) {
|
||
list.forEach((item, index) => {
|
||
item.isLatest = index === 0;
|
||
});
|
||
}
|
||
|
||
return list;
|
||
});
|
||
|
||
// 生成对比表格数据并分析差异
|
||
const compareTableData = computed(() => {
|
||
if (sortedCompareList.value.length === 0) {
|
||
parameterDifferences.value = {};
|
||
return [];
|
||
}
|
||
|
||
// 重置差异映射
|
||
const differences = {};
|
||
|
||
// 获取当前时间和昨天的日期
|
||
const today = new Date();
|
||
const yesterday = new Date(today);
|
||
yesterday.setDate(yesterday.getDate() - 1);
|
||
|
||
// 格式化日期为 yyyy-MM-dd
|
||
const formatDate = (date) => {
|
||
return date.toISOString().split("T")[0];
|
||
};
|
||
|
||
const formattedToday = formatDate(today);
|
||
const formattedYesterday = formatDate(yesterday);
|
||
|
||
// 构建对比数据,添加新参数
|
||
const result = [
|
||
// 原有参数
|
||
{ param: "品号-规格型号" },
|
||
// 新增参数:品号申请时间
|
||
{ param: "品号申请时间" },
|
||
{ param: "图号" },
|
||
{ param: "技术规范编号" },
|
||
{ param: "技术规范版本" },
|
||
{ param: "技术规范名称" },
|
||
// 新增参数:技术要求签订时间
|
||
{ param: "技术要求签订时间" },
|
||
{ param: "CBC编号" },
|
||
{ param: "CBC版本" },
|
||
{ param: "车型" },
|
||
{ param: "型号" },
|
||
{ param: "材质" },
|
||
{ param: "采购属性" },
|
||
{ param: "车轮踏面形式" },
|
||
{ param: "油漆制造商" },
|
||
].map((item) => {
|
||
const row = { param: item.param };
|
||
|
||
// 为每个产品填充参数值
|
||
sortedCompareList.value.forEach((product) => {
|
||
// 处理新增的两个参数
|
||
if (item.param === "品号申请时间") {
|
||
row[product.productNumber] = formattedToday;
|
||
} else if (item.param === "技术要求签订时间") {
|
||
row[product.productNumber] = formattedYesterday;
|
||
} else {
|
||
// 处理原有参数
|
||
const fieldKey = fieldMap[item.param];
|
||
row[product.productNumber] = fieldKey ? product[fieldKey] ?? "-" : "-";
|
||
}
|
||
});
|
||
|
||
// 收集所有产品的参数值用于判断差异
|
||
const values = sortedCompareList.value.map((p) => row[p.productNumber]);
|
||
const allSame = values.every((v) => v === values[0]);
|
||
differences[item.param] = !allSame;
|
||
|
||
return row;
|
||
});
|
||
|
||
// 保存差异信息
|
||
parameterDifferences.value = differences;
|
||
return result;
|
||
});
|
||
|
||
// 过滤后的对比表格数据(根据"仅看不同项"选项)
|
||
const filteredCompareTableData = computed(() => {
|
||
if (!showOnlyDifferences.value) {
|
||
return compareTableData.value;
|
||
}
|
||
|
||
// 只返回有差异的参数项
|
||
return compareTableData.value.filter(
|
||
(row) => parameterDifferences.value[row.param]
|
||
);
|
||
});
|
||
|
||
// 判断某个参数是否有差异(只要有差异就标红)
|
||
const isValueDifferent = (param) => {
|
||
// 如果参数有任何差异,就标红
|
||
return parameterDifferences.value[param];
|
||
};
|
||
|
||
// 收集所有可能的值用于匹配
|
||
const allValues = ref([]);
|
||
|
||
// 初始化所有可能的值
|
||
const initializeValues = () => {
|
||
const values = [];
|
||
allFields.forEach((field) => {
|
||
const uniqueValues = [
|
||
...new Set(
|
||
mockData.map((item) => item[field.key] ?? "") // 将null转换为空字符串
|
||
),
|
||
];
|
||
uniqueValues.forEach((value) => {
|
||
// 过滤空值,避免空提示
|
||
if (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 updateKeyDiffHint = () => {
|
||
// 获取技术规范编号和CBC编号的字段信息
|
||
const techSpecField = allFields.find(
|
||
(field) => field.label === "技术规范编号"
|
||
);
|
||
const cbcField = allFields.find((field) => field.label === "CBC编号");
|
||
|
||
// 检查当前搜索条件中是否包含这两个字段
|
||
const hasTechSpec = parsedConditions.value.some(
|
||
(cond) => cond.field === techSpecField?.key
|
||
);
|
||
const hasCbc = parsedConditions.value.some(
|
||
(cond) => cond.field === cbcField?.key
|
||
);
|
||
|
||
// 确定需要显示的关键差异字段
|
||
const diffFields = [];
|
||
if (!hasTechSpec && techSpecField) diffFields.push(techSpecField);
|
||
if (!hasCbc && cbcField) diffFields.push(cbcField);
|
||
|
||
// 更新关键差异字段和显示状态
|
||
keyDiffFields.value = diffFields;
|
||
// 只有当有搜索条件且有关键差异字段时才显示提示
|
||
showKeyDiffHint.value =
|
||
currentInput.value.trim() !== "" && diffFields.length > 0;
|
||
};
|
||
|
||
// 点击差异字段时添加到输入框
|
||
const addDiffFieldToInput = (field) => {
|
||
// 检查输入框末尾是否需要加分号
|
||
let separator = "";
|
||
if (
|
||
currentInput.value.trim() !== "" &&
|
||
!currentInput.value.trim().endsWith(";") &&
|
||
!currentInput.value.trim().endsWith(";")
|
||
) {
|
||
separator = ";";
|
||
}
|
||
|
||
// 添加字段到输入框
|
||
currentInput.value = `${currentInput.value}${separator}${field.label}:`;
|
||
|
||
// 触发输入事件和搜索
|
||
setTimeout(() => {
|
||
handleInput(currentInput.value);
|
||
triggerRealTimeSearch();
|
||
|
||
// 聚焦输入框并将光标定位到字段后的适当位置
|
||
if (searchInput.value) {
|
||
searchInput.value.focus();
|
||
const inputEl = searchInput.value.$el.querySelector("input");
|
||
if (inputEl) {
|
||
inputEl.setSelectionRange(
|
||
currentInput.value.length,
|
||
currentInput.value.length
|
||
);
|
||
}
|
||
}
|
||
}, 0);
|
||
};
|
||
|
||
const getCurrentInputPart = () => {
|
||
const parts = currentInput.value.split(/[;;]/);
|
||
return parts[parts.length - 1].trim();
|
||
};
|
||
|
||
const handleKeydown = (e) => {
|
||
// 检查是否按下了分号键
|
||
if (e.key === ";" || e.key === ";" || e.keyCode === 186) {
|
||
handleSemicolon(e);
|
||
}
|
||
};
|
||
|
||
// 处理分号输入
|
||
const handleSemicolon = (e) => {
|
||
// 如果是空值或最后一个字符已经是分号,则不处理
|
||
if (!currentInput.value.trim() || currentInput.value.endsWith(";")) {
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
|
||
// 延迟处理,确保分号已添加到输入框
|
||
setTimeout(() => {
|
||
// 触发输入事件以更新建议
|
||
handleInput(currentInput.value);
|
||
// 分号输入后触发实时查询
|
||
triggerRealTimeSearch();
|
||
}, 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 semicolons = [];
|
||
const semicolonRegex = /[;;]/g;
|
||
let match;
|
||
|
||
// 收集所有分号的位置
|
||
while ((match = semicolonRegex.exec(inputValue)) !== null) {
|
||
semicolons.push(match.index);
|
||
}
|
||
|
||
// 确定光标所在的条件索引
|
||
for (let i = 0; i < semicolons.length; i++) {
|
||
if (cursorPos <= semicolons[i]) {
|
||
return i;
|
||
}
|
||
}
|
||
|
||
// 如果光标在最后一个分号之后,返回最后一个条件索引
|
||
return semicolons.length;
|
||
};
|
||
|
||
// 处理输入框容器点击
|
||
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();
|
||
};
|
||
|
||
// 聚焦到指定的条件标签 - 修复问题2
|
||
const focusConditionTag = (index) => {
|
||
const parts = currentInput.value.split(/[;;]/);
|
||
if (index < 0 || index >= parts.length) return;
|
||
|
||
// 将光标定位到该条件
|
||
selectedConditionIndex.value = index;
|
||
const inputEl = searchInput.value?.$el.querySelector("input");
|
||
if (!inputEl) return;
|
||
|
||
// 计算该条件在输入框中的位置
|
||
let cursorPos = 0;
|
||
for (let i = 0; i < index; i++) {
|
||
cursorPos += parts[i].length + 1; // +1 是分号的长度
|
||
}
|
||
|
||
// 设置光标位置到该条件的末尾
|
||
setTimeout(() => {
|
||
inputEl.focus();
|
||
inputEl.setSelectionRange(
|
||
cursorPos + parts[index].length,
|
||
cursorPos + 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;
|
||
};
|
||
|
||
// 选择可能的字段 - 修复问题4
|
||
const selectPossibleField = (field) => {
|
||
// 始终在当前条件上操作,不修改其他条件
|
||
const parts = currentInput.value.split(/[;;]/);
|
||
let targetIndex = parts.length - 1;
|
||
|
||
// 如果最后一个条件为空,使用前一个
|
||
if (parts.length > 0 && parts[targetIndex].trim() === "") {
|
||
targetIndex = Math.max(0, parts.length - 2);
|
||
}
|
||
|
||
// 获取当前部分的值
|
||
let targetPart = parts[targetIndex] || "";
|
||
|
||
// 检查是否已有字段
|
||
if (targetPart.includes(":")) {
|
||
// 已有字段,添加新条件
|
||
const newPart = `${field.label}:`;
|
||
parts.push(newPart);
|
||
targetIndex = parts.length - 1;
|
||
} else {
|
||
// 没有字段,在当前值前添加字段名和冒号
|
||
parts[targetIndex] = `${field.label}:${targetPart}`;
|
||
}
|
||
|
||
// 重新拼接条件,清理多余分号
|
||
currentInput.value = parts.join(";").replace(/;;+/g, ";").trim();
|
||
|
||
// 保持建议框显示,允许继续编辑
|
||
setTimeout(() => {
|
||
handleInput(currentInput.value);
|
||
// 选择字段后触发实时查询
|
||
triggerRealTimeSearch();
|
||
}, 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));
|
||
};
|
||
|
||
// 选择建议项
|
||
// 修改selectSuggestion函数,在选择值后自动添加分号
|
||
const selectSuggestion = (item) => {
|
||
// 使用光标位置确定当前正在编辑的条件索引
|
||
const targetIndex = findCursorConditionIndex();
|
||
if (targetIndex === -1) return;
|
||
|
||
const parts = currentInput.value.split(/[;;]/);
|
||
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] = item.label;
|
||
}
|
||
|
||
// 核心:值选择后自动添加分号(避免重复添加)
|
||
const lastChar = parts[targetIndex].slice(-1);
|
||
if (![",", ";", ";"].includes(lastChar)) {
|
||
parts[targetIndex] += ";"; // 使用英文分号统一分隔
|
||
}
|
||
}
|
||
|
||
// 重新拼接条件,清理多余分号
|
||
currentInput.value = parts.join(";").replace(/;;+/g, ";").trim();
|
||
|
||
// 保持建议框显示,允许继续编辑
|
||
setTimeout(() => {
|
||
handleInput(currentInput.value);
|
||
// 选择建议项后触发实时查询
|
||
triggerRealTimeSearch();
|
||
}, 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 addPreciseCondition = (index) => {
|
||
const condition = parsedConditions.value[index];
|
||
// 检查是否已经添加到精准查询条件中
|
||
const exists = preciseConditions.value.some(
|
||
(cond) => cond.field === condition.field && cond.value === condition.value
|
||
);
|
||
|
||
if (!exists) {
|
||
preciseConditions.value.push({ ...condition, originalIndex: index });
|
||
// 重新过滤数据,应用精准查询
|
||
applyPreciseFilter();
|
||
}
|
||
};
|
||
|
||
// 移除精准查询条件
|
||
const removePreciseCondition = (index) => {
|
||
preciseConditions.value.splice(index, 1);
|
||
// 重新过滤数据
|
||
applyPreciseFilter();
|
||
};
|
||
|
||
// 处理查询
|
||
const handleSearch = () => {
|
||
// 解析条件
|
||
parsedConditions.value = parseConditions(currentInput.value);
|
||
|
||
// 更新关键差异提示
|
||
updateKeyDiffHint();
|
||
|
||
// 清空精准查询条件
|
||
preciseConditions.value = [];
|
||
|
||
// 应用过滤
|
||
applyPreciseFilter();
|
||
|
||
// 显示结果
|
||
showResults.value = true;
|
||
|
||
// 隐藏建议
|
||
showSuggestions.value = false;
|
||
possibleFields.value = [];
|
||
};
|
||
|
||
// 触发实时查询
|
||
const triggerRealTimeSearch = () => {
|
||
// 只有当有有效条件时才触发实时查询
|
||
if (currentInput.value.trim()) {
|
||
parsedConditions.value = parseConditions(currentInput.value);
|
||
// 更新关键差异提示
|
||
updateKeyDiffHint();
|
||
applyPreciseFilter();
|
||
showResults.value = true;
|
||
}
|
||
};
|
||
|
||
// 应用精准过滤
|
||
const applyPreciseFilter = () => {
|
||
// 先应用原始过滤
|
||
let filtered = filterData(parsedConditions.value);
|
||
|
||
// 再应用精准过滤
|
||
if (preciseConditions.value.length > 0) {
|
||
filtered = filtered.filter((item) => {
|
||
return preciseConditions.value.every((condition) => {
|
||
if (!condition.valid) return false;
|
||
|
||
if (condition.field) {
|
||
const fieldValue = item[condition.field];
|
||
return checkPreciseCondition(fieldValue, condition.value);
|
||
} else {
|
||
return allFields.some((field) => {
|
||
const fieldValue = item[field.key];
|
||
return checkPreciseCondition(fieldValue, condition.value);
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 更新过滤后的数据
|
||
filteredData.value = filtered.map((item) => ({ ...item, selected: false }));
|
||
totalItems.value = filteredData.value.length;
|
||
currentPage.value = 1;
|
||
};
|
||
|
||
// 根据条件过滤数据
|
||
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) => {
|
||
// 将null转换为空字符串处理
|
||
const processedValue = fieldValue ?? "";
|
||
const processedCondition = conditionValue ?? "";
|
||
|
||
// 后续逻辑使用processedValue和processedCondition
|
||
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 (processedCondition.startsWith(op.symbol)) {
|
||
const value = processedCondition.substring(op.symbol.length).trim();
|
||
if (!isNaN(Number(processedValue)) && !isNaN(Number(value))) {
|
||
return op.func(Number(processedValue), Number(value));
|
||
}
|
||
return op.func(String(processedValue), value);
|
||
}
|
||
}
|
||
|
||
return String(processedValue)
|
||
.toLowerCase()
|
||
.includes(processedCondition.toLowerCase());
|
||
};
|
||
|
||
// 检查精准条件(精确匹配)
|
||
const checkPreciseCondition = (fieldValue, conditionValue) => {
|
||
// 将null转换为空字符串处理
|
||
const processedValue = fieldValue ?? "";
|
||
const processedCondition = conditionValue ?? "";
|
||
|
||
// 精准匹配,不使用包含关系
|
||
return String(processedValue) === processedCondition;
|
||
};
|
||
|
||
// 移除单个条件
|
||
const removeCondition = (index) => {
|
||
const parts = currentInput.value.split(/[;;]/);
|
||
parts.splice(index, 1);
|
||
currentInput.value = parts
|
||
.join(";")
|
||
.replace(/[;;]+/g, ";")
|
||
.trim();
|
||
parsedConditions.value = parseConditions(currentInput.value);
|
||
|
||
// 更新关键差异提示
|
||
updateKeyDiffHint();
|
||
|
||
// 检查是否有精准查询条件引用了这个索引,如果有则一并删除
|
||
const preciseIndex = preciseConditions.value.findIndex(
|
||
(cond) => cond.originalIndex === index
|
||
);
|
||
if (preciseIndex !== -1) {
|
||
preciseConditions.value.splice(preciseIndex, 1);
|
||
}
|
||
|
||
// 重新过滤数据
|
||
applyPreciseFilter();
|
||
|
||
// 重置选中的条件索引
|
||
if (selectedConditionIndex.value === index) {
|
||
selectedConditionIndex.value = -1;
|
||
}
|
||
};
|
||
|
||
// 清除所有查询条件和结果
|
||
const clearAll = () => {
|
||
currentInput.value = "";
|
||
parsedConditions.value = [];
|
||
preciseConditions.value = [];
|
||
filteredData.value = [];
|
||
totalItems.value = 0;
|
||
currentPage.value = 1;
|
||
showResults.value = false;
|
||
showSuggestions.value = false;
|
||
possibleFields.value = [];
|
||
selectedConditionIndex.value = -1;
|
||
// 重置关键差异提示
|
||
keyDiffFields.value = [];
|
||
showKeyDiffHint.value = false;
|
||
|
||
if (searchInput.value) {
|
||
searchInput.value.focus();
|
||
}
|
||
handleSearch();
|
||
};
|
||
|
||
// 分页相关方法
|
||
const handleSizeChange = (val) => {
|
||
pageSize.value = val;
|
||
currentPage.value = 1; // 重置为第一页
|
||
};
|
||
|
||
const handleCurrentChange = (val) => {
|
||
currentPage.value = val;
|
||
// 滚动到顶部
|
||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||
};
|
||
|
||
// 生命周期钩子
|
||
onMounted(() => {
|
||
initializeValues();
|
||
handleSearch();
|
||
updateInputWidth();
|
||
window.addEventListener("resize", updateInputWidth);
|
||
|
||
// 自动聚焦输入框
|
||
setTimeout(() => {
|
||
if (searchInput.value) {
|
||
searchInput.value.focus();
|
||
}
|
||
}, 100);
|
||
|
||
// 点击页面其他地方隐藏建议
|
||
document.addEventListener("click", (e) => {
|
||
if (
|
||
!e.target.closest(".search-box") &&
|
||
!e.target.closest(".suggestions-dropdown")
|
||
) {
|
||
showSuggestions.value = false;
|
||
}
|
||
});
|
||
});
|
||
|
||
// 监听输入框宽度变化
|
||
const updateInputWidth = () => {
|
||
if (searchInput.value) {
|
||
inputWidth.value = searchInput.value.$el.clientWidth;
|
||
}
|
||
};
|
||
|
||
// 监听输入框宽度变化
|
||
watch(
|
||
() => currentInput.value,
|
||
() => {
|
||
// 延迟更新,确保DOM已更新
|
||
setTimeout(() => {
|
||
updateInputWidth();
|
||
}, 0);
|
||
}
|
||
);
|
||
|
||
// 切换选择状态
|
||
function toggleSelect(item) {
|
||
// 统计已选中的数量
|
||
const selectedCount = filteredData.value.filter((i) => i.selected).length;
|
||
item.selected = !item.selected;
|
||
}
|
||
|
||
// 打开对比弹窗
|
||
function onCompare() {
|
||
if (selectedCompareList.value.length === 0) {
|
||
ElMessage.warning("请先选择要对比的卡片");
|
||
return;
|
||
}
|
||
compareDialogVisible.value = true;
|
||
}
|
||
|
||
// 确定按钮点击事件
|
||
function handleConfirm() {
|
||
if (selectedCount.value === 1) {
|
||
confirmDialogVisible.value = true;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
// 原有样式保持不变
|
||
.step2-container {
|
||
background: #fff;
|
||
padding: 24px;
|
||
border-radius: 8px;
|
||
}
|
||
.main-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
}
|
||
.right-content {
|
||
flex: 1;
|
||
}
|
||
.step {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 20px;
|
||
}
|
||
.step-left {
|
||
flex: 1;
|
||
margin-right: 10px;
|
||
}
|
||
.step-title {
|
||
font-size: 18px;
|
||
margin: 15px 0;
|
||
color: #333;
|
||
font-weight: 500;
|
||
|
||
&.active {
|
||
color: #2156f3;
|
||
}
|
||
|
||
.reference-model {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-top: 8px;
|
||
padding-left: 20px;
|
||
position: relative;
|
||
|
||
&::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 12px;
|
||
height: 12px;
|
||
background: #2156f3;
|
||
border-radius: 50%;
|
||
}
|
||
}
|
||
}
|
||
.image-box.right-align {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: flex-start;
|
||
margin-bottom: 16px;
|
||
}
|
||
.table-title {
|
||
font-weight: bold;
|
||
margin: 16px 0 8px 0;
|
||
color: #333;
|
||
}
|
||
.tree-table-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
border: 1px solid #bbb;
|
||
border-radius: 4px;
|
||
padding: 12px;
|
||
background: #fff;
|
||
}
|
||
.header-title {
|
||
flex: 1;
|
||
padding: 10px;
|
||
border-radius: 4px;
|
||
}
|
||
.header-code,
|
||
.header-name {
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin-bottom: 5px;
|
||
}
|
||
.tab-container {
|
||
width: 100%;
|
||
}
|
||
.tab-header {
|
||
background: #f5f7fa;
|
||
border-radius: 4px;
|
||
padding: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.tab-header-item {
|
||
display: flex;
|
||
gap: 20px;
|
||
align-items: center;
|
||
}
|
||
.tab-header-image {
|
||
width: 200px;
|
||
height: 150px;
|
||
flex-shrink: 0;
|
||
}
|
||
.tab-header-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
.tab-header-code,
|
||
.tab-header-name {
|
||
font-size: 14px;
|
||
color: #333;
|
||
line-height: 1.5;
|
||
}
|
||
.part-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
border-radius: 4px;
|
||
border: 1px solid #eee;
|
||
}
|
||
.model-form {
|
||
max-width: 900px;
|
||
margin-top: 16px;
|
||
}
|
||
.btn-row {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 16px;
|
||
margin-top: 20px;
|
||
}
|
||
.main-img {
|
||
width: 580px;
|
||
height: 260px;
|
||
object-fit: contain;
|
||
border-radius: 8px;
|
||
border: 1px solid #eee;
|
||
}
|
||
.custom-tab-label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.tab-label-image {
|
||
width: 80px;
|
||
height: 60px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.tab-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
border-radius: 4px;
|
||
border: 1px solid #eee;
|
||
}
|
||
|
||
.tab-label-info {
|
||
text-align: center;
|
||
}
|
||
|
||
.tab-label-code {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.tab-label-name {
|
||
font-size: 14px;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
:deep(.el-tabs__item) {
|
||
height: auto !important;
|
||
padding: 0 20px !important;
|
||
}
|
||
|
||
:deep(.el-tabs__nav) {
|
||
display: flex;
|
||
gap: 20px;
|
||
}
|
||
|
||
:deep(.el-tabs__item.is-active) {
|
||
.tab-label-code,
|
||
.tab-label-name {
|
||
color: var(--el-color-primary);
|
||
}
|
||
}
|
||
|
||
.tree-node {
|
||
transition: background 0.2s;
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
border-radius: 4px;
|
||
margin-bottom: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.tree-node.active {
|
||
color: #2156f3;
|
||
}
|
||
.tree-node:hover {
|
||
color: #2156f3;
|
||
}
|
||
.tab-content-flex {
|
||
display: flex;
|
||
min-height: 300px;
|
||
height: 400px;
|
||
max-height: 400px;
|
||
overflow: hidden;
|
||
}
|
||
.tab-left {
|
||
width: 260px;
|
||
border-right: 1px solid #eee;
|
||
padding-right: 16px;
|
||
overflow-y: auto;
|
||
height: 100%;
|
||
}
|
||
.tab-right {
|
||
flex: 1;
|
||
padding-left: 24px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow-y: auto;
|
||
height: 100%;
|
||
}
|
||
.right-button {
|
||
margin: 15px 0 0 260px;
|
||
}
|
||
.detail-img {
|
||
width: 180px;
|
||
height: 120px;
|
||
object-fit: contain;
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.replace-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
.replace-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
padding: 8px;
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
.replace-item.selected {
|
||
border-color: #2156f3;
|
||
}
|
||
.replace-img {
|
||
width: 80px;
|
||
height: 60px;
|
||
object-fit: contain;
|
||
border-radius: 4px;
|
||
border: 1px solid #eee;
|
||
}
|
||
.no-data {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 300px;
|
||
color: #999;
|
||
font-size: 18px;
|
||
}
|
||
|
||
:deep(.el-collapse-item.is-disabled .el-collapse-item__header) {
|
||
cursor: default;
|
||
color: #303133;
|
||
&:hover {
|
||
background-color: transparent;
|
||
}
|
||
}
|
||
|
||
:deep(.el-collapse-item.is-disabled .el-collapse-item__arrow) {
|
||
display: none;
|
||
}
|
||
|
||
:deep(.el-collapse-item__header) {
|
||
padding: 0;
|
||
height: auto;
|
||
}
|
||
|
||
:deep(.el-collapse-item__content) {
|
||
padding: 0;
|
||
padding-left: 16px;
|
||
}
|
||
|
||
:deep(.el-collapse-item__wrap) {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.search-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
.search-label {
|
||
font-size: 15px;
|
||
color: #666;
|
||
margin-right: 8px;
|
||
}
|
||
.car-type-text-list {
|
||
display: flex;
|
||
gap: 32px;
|
||
}
|
||
.car-type-text {
|
||
font-size: 16px;
|
||
color: #333;
|
||
cursor: pointer;
|
||
padding: 0 4px 4px 4px;
|
||
border-bottom: 2px solid transparent;
|
||
transition: color 0.2s, border 0.2s;
|
||
}
|
||
.car-type-text.active {
|
||
color: #2156f3;
|
||
border-bottom: 2px solid #2156f3;
|
||
font-weight: 500;
|
||
}
|
||
.search-divider {
|
||
border-bottom: 1px dashed #e5e5e5;
|
||
margin: 8px 0 16px 0;
|
||
}
|
||
.param-group-title {
|
||
font-size: 15px;
|
||
color: #333;
|
||
font-weight: 600;
|
||
margin: 12px 0 12px 0;
|
||
}
|
||
.param-form-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: flex-start;
|
||
gap: 24px 32px;
|
||
margin-bottom: 0;
|
||
position: relative;
|
||
}
|
||
.param-form-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 0;
|
||
}
|
||
.param-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
min-width: 60px;
|
||
text-align: right;
|
||
}
|
||
.param-input {
|
||
width: 120px;
|
||
}
|
||
.param-form-btns {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-left: auto;
|
||
min-width: 200px;
|
||
}
|
||
.param-btn {
|
||
min-width: 90px;
|
||
}
|
||
.result-card-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 30px;
|
||
}
|
||
.result-card {
|
||
width: 280px;
|
||
position: relative;
|
||
padding-top: 16px;
|
||
box-sizing: border-box;
|
||
transition: box-shadow 0.2s, transform 0.15s, border-color 0.2s;
|
||
cursor: pointer;
|
||
border: 1px solid #eee;
|
||
}
|
||
.result-card:hover {
|
||
box-shadow: 0 4px 16px rgba(33, 86, 243, 0.12);
|
||
transform: translateY(-4px) scale(1.03);
|
||
border-color: #2156f3;
|
||
}
|
||
.result-card.active {
|
||
box-shadow: 0 6px 20px rgba(33, 86, 243, 0.18);
|
||
border-color: #2156f3;
|
||
background: #f5f8ff;
|
||
transform: scale(1.04);
|
||
}
|
||
|
||
.result-card-info {
|
||
text-align: left;
|
||
}
|
||
.result-card-name {
|
||
font-size: 16px;
|
||
color: #333;
|
||
font-weight: 500;
|
||
margin-bottom: 4px;
|
||
}
|
||
.result-card-parameter {
|
||
font-size: 13px;
|
||
color: #888;
|
||
overflow: visible;
|
||
white-space: normal;
|
||
padding: 2px;
|
||
max-width: 300px;
|
||
word-break: break-word;
|
||
span {
|
||
color: #555;
|
||
}
|
||
}
|
||
.compare-dialog-cards {
|
||
display: flex;
|
||
gap: 32px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.compare-card {
|
||
width: 220px;
|
||
text-align: center;
|
||
box-sizing: border-box;
|
||
}
|
||
.compare-card-img {
|
||
width: 100%;
|
||
height: 120px;
|
||
object-fit: contain;
|
||
border-radius: 4px;
|
||
border: 1px solid #eee;
|
||
margin-bottom: 12px;
|
||
}
|
||
.compare-card-info {
|
||
text-align: center;
|
||
}
|
||
.compare-card-name {
|
||
font-size: 15px;
|
||
color: #333;
|
||
font-weight: 500;
|
||
margin-bottom: 4px;
|
||
}
|
||
.compare-card-code {
|
||
font-size: 13px;
|
||
color: #888;
|
||
}
|
||
.compare-table-wrap {
|
||
margin-top: 16px;
|
||
overflow-x: auto; /* 允许横向滚动 */
|
||
max-width: 100%; /* 限制最大宽度 */
|
||
}
|
||
/* 参数对比按钮样式 - 随滚动条移动 */
|
||
.compare-btn-float {
|
||
position: fixed; /* 修改为fixed定位,相对于视口固定 */
|
||
top: 260px;
|
||
right: 10px;
|
||
z-index: 100; /* 提高层级确保可见 */
|
||
width: 52px;
|
||
height: 52px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
border-radius: 8px;
|
||
box-shadow: 0 3px 15px rgba(33, 86, 243, 0.15);
|
||
transition: all 0.25s ease;
|
||
font-size: 13px;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.compare-btn-float:hover {
|
||
background: #fff3cd;
|
||
color: #d39e00;
|
||
box-shadow: 0 5px 20px rgba(255, 153, 0, 0.2);
|
||
transform: translateY(-2px);
|
||
}
|
||
.result-card-list-wrap {
|
||
position: relative;
|
||
padding-right: 20px;
|
||
}
|
||
.result-card-checkbox .el-checkbox__input.is-checked .el-checkbox__inner {
|
||
border-color: #2156f3 !important;
|
||
background-color: #2156f3 !important;
|
||
}
|
||
// 参数对比表格第一列背景色与表头一致
|
||
:deep(.el-table .param-col),
|
||
:deep(.el-table th.param-col) {
|
||
background: #f5f7fa !important;
|
||
}
|
||
|
||
// 固定列的样式优化
|
||
:deep(.el-table__fixed-left) {
|
||
box-shadow: 2px 0 6px rgba(0, 0, 0, 0.05);
|
||
z-index: 2;
|
||
}
|
||
|
||
.search-box-container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 0 20px;
|
||
}
|
||
|
||
.search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
position: relative;
|
||
}
|
||
|
||
.custom-input-wrapper {
|
||
position: relative;
|
||
flex-grow: 1;
|
||
}
|
||
|
||
.main-input {
|
||
width: 100%;
|
||
min-width: 300px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
// 语音图标样式
|
||
:deep(.voice-icon) {
|
||
cursor: pointer;
|
||
color: #666;
|
||
margin-right: 8px;
|
||
transition: color 0.2s;
|
||
|
||
&:hover {
|
||
color: #2156f3;
|
||
}
|
||
}
|
||
|
||
.search-button {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.suggestions-dropdown {
|
||
position: 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;
|
||
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: 60px !important; /* 增加右侧 padding 以容纳"精准"文字 */
|
||
position: relative;
|
||
}
|
||
|
||
.condition-tag:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.tag-field {
|
||
font-weight: 500;
|
||
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-precise {
|
||
position: absolute;
|
||
right: 20px; /* 为关闭按钮留出空间 */
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: #409eff;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
padding: 2px 4px;
|
||
border-radius: 2px;
|
||
background-color: rgba(64, 158, 255, 0.1);
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.tag-precise:hover {
|
||
background-color: rgba(64, 158, 255, 0.2);
|
||
color: #1890ff;
|
||
}
|
||
|
||
.precise-conditions {
|
||
margin: 15px 0;
|
||
}
|
||
|
||
.precise-conditions-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.precise-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.precise-tag {
|
||
cursor: default;
|
||
padding-right: 30px !important;
|
||
position: relative;
|
||
}
|
||
|
||
.results-section {
|
||
max-width: 1250px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.no-results {
|
||
padding: 50px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.clear-button {
|
||
color: #666;
|
||
}
|
||
|
||
.compare-table-wrap {
|
||
margin-top: 16px;
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 分页和确定按钮容器 */
|
||
.pagination-container {
|
||
display: flex;
|
||
justify-content: end;
|
||
align-items: center;
|
||
margin-top: 20px;
|
||
padding: 10px 0;
|
||
border-top: 1px solid #eee;
|
||
}
|
||
|
||
/* 确定按钮样式 */
|
||
.confirm-button {
|
||
min-width: 100px;
|
||
margin-left: 15px;
|
||
}
|
||
|
||
/* 文字溢出处理样式 */
|
||
.text-truncate {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
position: relative;
|
||
}
|
||
|
||
.text-truncate:hover {
|
||
/* 保持原样式不变,但通过title属性显示完整内容 */
|
||
overflow: visible;
|
||
white-space: normal;
|
||
z-index: 10;
|
||
background-color: rgba(255, 255, 255, 0.95);
|
||
padding: 5px;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||
max-width: 300px;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* 确认弹窗样式 */
|
||
.confirm-message {
|
||
font-size: 16px;
|
||
text-align: center;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
/* 不同参数值的样式 */
|
||
.different-value {
|
||
color: #ff4d4f; /* 红色 */
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 对比筛选选项样式 */
|
||
.compare-filter-options {
|
||
text-align: right;
|
||
padding-right: 10px;
|
||
}
|
||
|
||
/* 语音弹窗内容样式 */
|
||
.voice-popup-content {
|
||
font-size: 16px;
|
||
text-align: center;
|
||
padding: 30px 0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.new-tag-img {
|
||
width: 35px;
|
||
height: 35px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* 关键差异信息提示样式 */
|
||
.key-diff-hint {
|
||
margin: -15px 0 15px 0;
|
||
padding: 8px 12px;
|
||
background-color: #f0f7ff;
|
||
border-left: 3px solid #2156f3;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
color: #1890ff;
|
||
}
|
||
|
||
.diff-field {
|
||
color: #2156f3;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
text-decoration: underline;
|
||
text-underline-offset: 2px;
|
||
transition: color 0.2s;
|
||
margin: 0 3px;
|
||
}
|
||
|
||
.diff-field:hover {
|
||
color: #096dd9;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.search-box {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.main-input {
|
||
min-width: auto;
|
||
}
|
||
|
||
.search-button {
|
||
width: 100%;
|
||
}
|
||
|
||
.result-card {
|
||
width: 100%;
|
||
}
|
||
|
||
.pagination-container {
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.confirm-button {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|