提交 02341b24 authored 作者: 张伊明's avatar 张伊明

feat 新增法案对比页面

feat 新增首页图标ai总结功能(未接入接口)
上级 b21e37b1
......@@ -192,3 +192,17 @@ export function getBillFullText(params) {
params,
})
}
// 条款对比-根据两版版本与筛选条件获取配对条款列表
/**
* @param {billId,oldVersionId,newVersionId,diffType,cRelated,keyword}
* @header token
* @returns { list: Array<{ oldTerm: object|null, newTerm: object|null }> }
*/
export function getBillTermsCompare(params) {
return request({
method: "GET",
url: "/api/billInfoBean/content/compare",
params,
});
}
......@@ -13,6 +13,7 @@ const BillInfluenceLayout = () => import('@/views/bill/influence/index.vue')
const BillInfluenceIndustry = () => import('@/views/bill/influence/industry/index.vue')
const BillInfluenceScientificResearch = () => import('@/views/bill/influence/scientificResearch/index.vue')
const BillRelevantCircumstance = () => import('@/views/bill/relevantCircumstance/index.vue')
const BillVersionCompare = () => import('@/views/bill/versionCompare/index.vue')
const billRoutes = [
......@@ -129,6 +130,14 @@ const billRoutes = [
// meta: {
// title: "相关情况"
// }
},
{
path: "versionCompare",
name: "BillVersionCompare",
component: BillVersionCompare,
meta: {
title: "版本对比"
}
}
]
},
......
......@@ -127,7 +127,13 @@
<el-empty v-if="!box5HasData" description="暂无数据" :image-size="100" />
<div v-else id="box5Chart" class="overview-chart"></div>
</div>
<TipTab class="overview-tip" />
<div class="overview-tip-row">
<TipTab class="overview-tip" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box5')" />
</div>
<div v-if="aiPaneVisible.box5" class="overview-ai-pane" @mouseleave="handleHideAiPane('box5')">
<AiPane :aiContent="overviewAiContent.box5" />
</div>
</div>
</OverviewCard>
<OverviewCard class="overview-card--single box6" title="涉华法案领域分布" :icon="box6HeaderIcon">
......@@ -141,7 +147,13 @@
<el-empty v-if="!box9HasData" description="暂无数据" :image-size="100" />
<div v-else id="box9Chart" class="overview-chart"></div>
</div>
<TipTab class="overview-tip" />
<div class="overview-tip-row">
<TipTab class="overview-tip" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box6')" />
</div>
<div v-if="aiPaneVisible.box6" class="overview-ai-pane" @mouseleave="handleHideAiPane('box6')">
<AiPane :aiContent="overviewAiContent.box6" />
</div>
</div>
</OverviewCard>
</div>
......@@ -157,7 +169,13 @@
<el-empty v-if="!box7HasData" description="暂无数据" :image-size="100" />
<div v-else id="box7Chart" class="overview-chart"></div>
</div>
<TipTab class="overview-tip" />
<div class="overview-tip-row">
<TipTab class="overview-tip" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box7')" />
</div>
<div v-if="aiPaneVisible.box7" class="overview-ai-pane" @mouseleave="handleHideAiPane('box7')">
<AiPane :aiContent="overviewAiContent.box7" />
</div>
</div>
</OverviewCard>
<OverviewCard class="overview-card--single box8" title="涉华法案进展分布" :icon="box7HeaderIcon">
......@@ -174,7 +192,13 @@
<div id="box8Chart" class="overview-chart box8-chart"></div>
</template>
</div>
<TipTab class="overview-tip" />
<div class="overview-tip-row">
<TipTab class="overview-tip" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box8')" />
</div>
<div v-if="aiPaneVisible.box8" class="overview-ai-pane" @mouseleave="handleHideAiPane('box8')">
<AiPane :aiContent="overviewAiContent.box8" />
</div>
</div>
</OverviewCard>
<OverviewCard class="overview-card--single box9" title="涉华法案关键条款" :icon="box7HeaderIcon">
......@@ -183,7 +207,13 @@
<el-empty v-if="!wordCloudHasData" description="暂无数据" :image-size="100" />
<WordCloundChart v-else class="overview-chart" width="100%" height="100%" :data="wordCloudData" />
</div>
<TipTab class="overview-tip" />
<div class="overview-tip-row">
<TipTab class="overview-tip" />
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('box9')" />
</div>
<div v-if="aiPaneVisible.box9" class="overview-ai-pane" @mouseleave="handleHideAiPane('box9')" >
<AiPane :aiContent="overviewAiContent.box9" />
</div>
</div>
</OverviewCard>
</div>
......@@ -221,6 +251,8 @@ import OverviewCard from "./OverviewCard.vue";
import ResourceLibrarySection from "./ResourceLibrarySection.vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import TipTab from "@/components/base/TipTab/index.vue";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import WordCloundChart from "@/components/base/WordCloundChart/index.vue";
import getMultiLineChart from "./utils/multiLineChart";
......@@ -370,6 +402,36 @@ const box7YearList = ref([
}
]);
const aiPaneVisible = ref({
box5: false,
box6: false,
box7: false,
box8: false,
box9: false
});
const overviewAiContent = ref({
box5: "智能总结生成中...",
box6: "智能总结生成中...",
box7: "智能总结生成中...",
box8: "智能总结生成中...",
box9: "智能总结生成中..."
});
const handleShowAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: true
};
};
const handleHideAiPane = key => {
aiPaneVisible.value = {
...aiPaneVisible.value,
[key]: false
};
};
const box8selectetedTime = ref("2025");
const box8YearList = ref([
{
......@@ -2109,6 +2171,20 @@ onUnmounted(() => {
.overview-card-body {
display: flex;
flex-direction: column;
position: relative;
}
.overview-ai-pane {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
z-index: 3;
pointer-events: none;
.ai-pane-wrapper {
pointer-events: auto;
}
}
.overview-chart-wrap {
......@@ -2131,8 +2207,17 @@ onUnmounted(() => {
min-height: 0;
}
.overview-tip {
.overview-tip-row {
margin-top: 10px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.overview-tip-action {
position: absolute;
right: -30px;
}
}
}
......
......@@ -7,6 +7,8 @@
:defaultLogo="USALogo"
:tabs="mainHeaderBtnList"
:activeTitle="activeTitle"
:showTabs="showHeaderTabs"
:showActions="showHeaderActions"
@tab-click="handleClickMainHeaderBtn"
@open-analysis="handleAnalysisClick"
/>
......@@ -79,6 +81,8 @@ const mainHeaderBtnList = ref([
]);
const activeTitle = ref("法案概况");
const showHeaderTabs = ref(true);
const showHeaderActions = ref(true);
const getActiveTitleByRoutePath = path => {
if (path.startsWith("/billLayout/deepDig")) return "深度挖掘";
......@@ -88,8 +92,12 @@ const getActiveTitleByRoutePath = path => {
return "法案概况";
};
const syncActiveTitleFromRoute = () => {
const syncHeaderStateFromRoute = () => {
const currentPath = route.path || "";
activeTitle.value = getActiveTitleByRoutePath(route.path);
const isVersionCompare = currentPath.startsWith("/billLayout/versionCompare");
showHeaderTabs.value = !isVersionCompare;
showHeaderActions.value = !isVersionCompare;
};
const handleClickMainHeaderBtn = item => {
......@@ -120,7 +128,7 @@ const handleAnalysisClick = () => {
onMounted(() => {
getBillInfoGlobalFn();
// 以当前路由为准,避免 sessionStorage 造成高亮错乱
syncActiveTitleFromRoute();
syncHeaderStateFromRoute();
// 兜底:如果未来出现未知路由且有缓存,再用缓存
const cachedTitle = window.sessionStorage.getItem("activeTitle");
if (!activeTitle.value && cachedTitle) activeTitle.value = cachedTitle;
......@@ -129,7 +137,7 @@ onMounted(() => {
watch(
() => route.path,
() => {
syncActiveTitleFromRoute();
syncHeaderStateFromRoute();
},
{ immediate: true }
);
......
......@@ -387,7 +387,7 @@ const handleChangeBill = val => {
};
const handleOpenVersionCompare = () => {
const targetUrl = `/billLayout/deepDig/processOverview?billId=${route.query.billId}`;
const targetUrl = `/billLayout/versionCompare?billId=${route.query.billId}`;
window.open(targetUrl, "_blank");
};
......
<template>
<div class="version-compare-wrap">
<div class="compare-top">
<div class="compare-top-col">
<div class="compare-top-label">原版本:</div>
<el-select v-model="oldVersionId" placeholder="请选择版本" class="compare-top-select" clearable>
<el-option v-for="item in versionOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
<div class="compare-top-col">
<div class="compare-top-label">现版本:</div>
<el-select v-model="newVersionId" placeholder="请选择版本" class="compare-top-select" clearable>
<el-option v-for="item in versionOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
<div class="compare-tools">
<el-tabs v-model="diffType" class="compare-tools-tabs">
<el-tab-pane label="变更" name="CHANGE" />
<el-tab-pane label="新增" name="ADD" />
<el-tab-pane label="删除" name="DELETE" />
</el-tabs>
<div class="compare-tools-actions">
<el-checkbox v-model="onlyChinaRelated" label="只看涉华条款" size="large" />
<div class="compare-tools-switches">
<div class="compare-tools-switch">
<span class="label">高亮实体</span>
<el-switch v-model="termsHighlight" inline-prompt active-text="开" inactive-text="关" />
</div>
<div class="compare-tools-switch">
<span class="label">显示原文</span>
<el-switch v-model="termsShowOriginal" inline-prompt active-text="开" inactive-text="关" />
</div>
</div>
<div class="find-word-wrap">
<div class="find-word-box" v-if="findWordBox">
<div class="find-word-input">
<el-input ref="findWordInputRef" v-model="findWordTxt" placeholder="查找条款内容" clearable
@input="handleUpdateWord" />
</div>
<div class="find-word-limit">{{ findWordNum }}/{{ findWordMax }}</div>
<div class="find-word-icon" @click="handleFindWord('last')">
<el-icon><ArrowUp /></el-icon>
</div>
<div class="find-word-icon" @click="handleFindWord('next')">
<el-icon><ArrowDown /></el-icon>
</div>
<div class="find-word-icon" @click="handleFindWord('close')">
<el-icon><Close /></el-icon>
</div>
</div>
<el-button type="primary" plain class="find-word-open-btn"
@click="handleFindWord('open')">查找</el-button>
</div>
</div>
</div>
<div class="compare-columns" v-loading="isLoading">
<div class="compare-col">
<div class="compare-col-body">
<div class="term-card" v-for="(pair, index) in comparePairs" :key="getPairKey(pair, index)">
<template v-if="pair?.oldTerm">
<div class="term-body">
<div class="term-main">
<div class="term-row term-row-cn">
<div class="term-no-cn">{{ pair.oldTerm.tkxh }}条.</div>
<div class="term-content-cn"
v-html="getTermContentHtml(pair.oldTerm, 'cn')"></div>
</div>
<div class="term-row term-row-en" v-if="termsShowOriginal">
<div class="term-no-en">Sec.{{ pair.oldTerm.tkxh }}</div>
<div class="term-content-en"
v-html="getTermContentHtml(pair.oldTerm, 'en')"></div>
</div>
</div>
</div>
</template>
<div v-else class="term-empty"></div>
</div>
</div>
</div>
<div class="compare-col">
<div class="compare-col-body">
<div class="term-card" v-for="(pair, index) in comparePairs" :key="getPairKey(pair, index)">
<template v-if="pair?.newTerm">
<div class="term-body">
<div class="term-main">
<div class="term-row term-row-cn">
<div class="term-no-cn">{{ pair.newTerm.tkxh }}条.</div>
<div class="term-content-cn"
v-html="getTermContentHtml(pair.newTerm, 'cn')"></div>
</div>
<div class="term-row term-row-en" v-if="termsShowOriginal">
<div class="term-no-en">Sec.{{ pair.newTerm.tkxh }}</div>
<div class="term-content-en"
v-html="getTermContentHtml(pair.newTerm, 'en')"></div>
</div>
</div>
</div>
</template>
<div v-else class="term-empty"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed, nextTick, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { getBillContentId, getBillTermsCompare } from "@/api/bill";
import { extractTextEntity } from "@/api/intelligent/index";
import { ArrowDown, ArrowUp, Close } from "@element-plus/icons-vue";
const route = useRoute();
const billId = computed(() => route.query.billId);
const versionOptions = ref([]);
const oldVersionId = ref("");
const newVersionId = ref("");
const diffType = ref("CHANGE");
const onlyChinaRelated = ref(false);
const keywordInput = ref("");
const keyword = ref("");
const termsHighlight = ref(true);
const termsShowOriginal = ref(true);
const findWordBox = ref(true);
const findWordInputRef = ref();
const findWordTxt = ref("");
const findWordKeyword = ref("");
const findWordNum = ref(0);
const findWordMax = ref(0);
const findWordTimer = ref(null);
const isLoading = ref(false);
const comparePairs = ref([]);
const compareRequestToken = ref(0);
const handleLoadVersionOptions = async () => {
if (!billId.value) {
versionOptions.value = [];
return;
}
const res = await getBillContentId({ id: billId.value });
const rawList = Array.isArray(res?.data) ? res.data : [];
const seen = new Set();
versionOptions.value = rawList
.map(item => {
return {
label: item?.bbmc,
value: item?.bbmc
};
})
.filter(item => item.value)
.filter(item => {
if (seen.has(item.value)) return false;
seen.add(item.value);
return true;
});
if (!versionOptions.value.length) {
oldVersionId.value = "";
newVersionId.value = "";
return;
}
if (!oldVersionId.value) {
oldVersionId.value = versionOptions.value[0].value;
}
if (!newVersionId.value) {
newVersionId.value = versionOptions.value[versionOptions.value.length - 1].value;
}
};
const handleSearchSubmit = () => {
keyword.value = (keywordInput.value || "").trim();
};
const normalizeDiffType = value => {
if (value === "ADD") return "ADD";
if (value === "DELETE") return "DELETE";
return "CHANGE";
};
const loadComparePairs = async () => {
if (!billId.value || !oldVersionId.value || !newVersionId.value) {
comparePairs.value = [];
return;
}
const currentToken = ++compareRequestToken.value;
isLoading.value = true;
try {
const params = {
billId: billId.value,
oldVersionId: oldVersionId.value,
newVersionId: newVersionId.value,
diffType: normalizeDiffType(diffType.value),
cRelated: onlyChinaRelated.value ? "Y" : "N",
keyword: keyword.value
};
const res = await getBillTermsCompare(params);
if (currentToken !== compareRequestToken.value) return;
const list = Array.isArray(res?.data?.list) ? res.data.list : Array.isArray(res?.list) ? res.list : [];
comparePairs.value = list;
await ensureEntitiesForPairs(comparePairs.value);
} catch (error) {
if (currentToken !== compareRequestToken.value) return;
comparePairs.value = [];
} finally {
if (currentToken === compareRequestToken.value) {
isLoading.value = false;
}
}
};
const getPairKey = (pair, index) => {
const oldKey = pair?.oldTerm?.ywid ?? pair?.oldTerm?.id ?? pair?.oldTerm?.tkxh ?? "";
const newKey = pair?.newTerm?.ywid ?? pair?.newTerm?.id ?? pair?.newTerm?.tkxh ?? "";
return `${oldKey || "old"}__${newKey || "new"}__${index}`;
};
const escapeHtml = value => {
const str = String(value ?? "");
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
};
const escapeRegExp = text => {
return String(text || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
const getSearchRanges = (text, searchTerm) => {
const rawText = String(text ?? "");
const term = String(searchTerm ?? "").trim();
if (!rawText || !term) return [];
const ranges = [];
const reg = new RegExp(escapeRegExp(term), "g");
let match;
while ((match = reg.exec(rawText)) !== null) {
ranges.push({ start: match.index, end: match.index + match[0].length });
if (match[0].length === 0) reg.lastIndex += 1;
}
return ranges;
};
const normalizeEntities = entities => {
const list = Array.isArray(entities) ? entities : [];
return list
.map(item => {
return {
text_span: String(item?.text_span ?? "").trim(),
type: String(item?.type ?? "").trim()
};
})
.filter(item => item.text_span);
};
const getEntityRanges = (text, entities) => {
const ranges = [];
const rawText = String(text ?? "");
if (!rawText) return ranges;
const list = normalizeEntities(entities).sort((a, b) => b.text_span.length - a.text_span.length);
for (const ent of list) {
let startIndex = 0;
while (startIndex < rawText.length) {
const idx = rawText.indexOf(ent.text_span, startIndex);
if (idx === -1) break;
ranges.push({ start: idx, end: idx + ent.text_span.length, ent });
startIndex = idx + ent.text_span.length;
}
}
ranges.sort((a, b) => a.start - b.start || b.end - a.end);
const merged = [];
let lastEnd = 0;
for (const r of ranges) {
if (r.start < lastEnd) continue;
merged.push(r);
lastEnd = r.end;
}
return merged;
};
const buildHighlightedHtml = (text, entities, enableHighlight, searchTerm) => {
const rawText = String(text ?? "");
if (!rawText) return "";
const safeText = escapeHtml(rawText).replace(/\n/g, "<br />");
const term = String(searchTerm ?? "").trim();
const enableSearch = Boolean(term);
if (!enableHighlight && !enableSearch) return safeText;
const ranges = getEntityRanges(rawText, entities);
const searchRanges = enableSearch ? getSearchRanges(rawText, term) : [];
if (!ranges.length && !searchRanges.length) return safeText;
const merged = [];
for (const r of ranges) {
merged.push({ start: r.start, end: r.end, type: "entity", ent: r.ent });
}
for (const r of searchRanges) {
const overlapsEntity = ranges.some(er => r.start < er.end && r.end > er.start);
if (overlapsEntity) continue;
merged.push({ start: r.start, end: r.end, type: "search" });
}
merged.sort((a, b) => a.start - b.start || b.end - a.end);
let html = "";
let cursor = 0;
for (const r of merged) {
if (cursor < r.start) {
html += escapeHtml(rawText.slice(cursor, r.start));
}
const spanText = rawText.slice(r.start, r.end);
if (r.type === "entity") {
const type = escapeHtml(r.ent?.type ?? "");
html += `<span class="term-entity" data-entity-type="${type}">${escapeHtml(spanText)}</span>`;
} else {
html += `<span class="term-find-highlight">${escapeHtml(spanText)}</span>`;
}
cursor = r.end;
}
if (cursor < rawText.length) {
html += escapeHtml(rawText.slice(cursor));
}
return html.replace(/\n/g, "<br />");
};
const termEntityCache = ref(new Map());
const entityRequestToken = ref(0);
const getTermEntityKey = (term, lang) => {
const baseKey = term?.ywid ?? term?.id ?? term?.tkxh ?? "";
return `${baseKey}__${lang}`;
};
const ensureEntitiesForPairs = async pairs => {
if (!termsHighlight.value) return;
const list = Array.isArray(pairs) ? pairs : [];
if (!list.length) return;
const currentToken = ++entityRequestToken.value;
const tasks = [];
const maxTasks = 40;
for (const pair of list) {
const terms = [pair?.oldTerm, pair?.newTerm].filter(Boolean);
for (const term of terms) {
const cnKey = getTermEntityKey(term, "cn");
const enKey = getTermEntityKey(term, "en");
if (!termEntityCache.value.has(cnKey) && String(term?.fynr ?? "").trim()) {
tasks.push({ key: cnKey, text: term.fynr });
}
if (!termEntityCache.value.has(enKey) && String(term?.ywnr ?? "").trim()) {
tasks.push({ key: enKey, text: term.ywnr });
}
if (tasks.length >= maxTasks) break;
}
if (tasks.length >= maxTasks) break;
}
if (!tasks.length) return;
try {
const results = await Promise.all(
tasks.map(async item => {
const res = await extractTextEntity(item.text);
const entities = normalizeEntities(res?.result ?? res?.data?.result ?? res?.data ?? res);
return { key: item.key, entities };
})
);
if (currentToken !== entityRequestToken.value) return;
for (const r of results) {
termEntityCache.value.set(r.key, r.entities);
}
} catch (error) {
if (currentToken !== entityRequestToken.value) return;
}
};
const getTermContentHtml = (term, lang) => {
const raw = lang === "en" ? term?.ywnr : term?.fynr;
const key = getTermEntityKey(term, lang);
const entities = termEntityCache.value.get(key) || [];
return buildHighlightedHtml(raw, entities, termsHighlight.value, findWordKeyword.value);
};
const countOccurrences = (text, searchTerm) => {
const rawText = String(text ?? "");
const term = String(searchTerm ?? "").trim();
if (!rawText || !term) return 0;
const reg = new RegExp(escapeRegExp(term), "g");
const matches = rawText.match(reg);
return matches ? matches.length : 0;
};
const updateActiveFindHighlight = () => {
const spans = document.querySelectorAll("span.term-find-highlight");
spans.forEach((span, index) => {
if (index + 1 === findWordNum.value) {
span.classList.add("is-active");
span.scrollIntoView({ block: "center" });
} else {
span.classList.remove("is-active");
}
});
};
const doUpdateFindWord = async () => {
findWordNum.value = 0;
findWordMax.value = 0;
const term = String(findWordTxt.value || "").trim();
findWordKeyword.value = term;
if (!term) {
await nextTick();
return;
}
const list = Array.isArray(comparePairs.value) ? comparePairs.value : [];
for (const pair of list) {
const terms = [pair?.oldTerm, pair?.newTerm].filter(Boolean);
for (const t of terms) {
findWordMax.value += countOccurrences(t?.fynr, term);
if (termsShowOriginal.value) {
findWordMax.value += countOccurrences(t?.ywnr, term);
}
}
}
if (findWordMax.value > 0) {
await nextTick();
findWordNum.value = 1;
updateActiveFindHighlight();
}
};
const handleUpdateWord = () => {
if (findWordTimer.value) {
clearTimeout(findWordTimer.value);
findWordTimer.value = null;
}
findWordTimer.value = setTimeout(() => {
doUpdateFindWord();
}, 300);
};
const handleFindWord = event => {
switch (event) {
case "open":
findWordBox.value = true;
nextTick(() => {
findWordInputRef.value?.focus?.();
});
break;
case "last":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value === 1 ? findWordMax.value : findWordNum.value - 1;
updateActiveFindHighlight();
}
break;
case "next":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value === findWordMax.value ? 1 : findWordNum.value + 1;
updateActiveFindHighlight();
}
break;
case "close":
findWordBox.value = false;
findWordTxt.value = "";
findWordKeyword.value = "";
findWordNum.value = 0;
findWordMax.value = 0;
break;
}
};
watch(
[billId, oldVersionId, newVersionId, diffType, onlyChinaRelated, keyword],
() => {
loadComparePairs();
},
{ immediate: true }
);
watch(termsHighlight, () => {
ensureEntitiesForPairs(comparePairs.value);
});
watch(
[comparePairs, termsShowOriginal],
() => {
if (!findWordBox.value) return;
doUpdateFindWord();
},
{ deep: true }
);
onMounted(async () => {
await handleLoadVersionOptions();
});
</script>
<style lang="scss" scoped>
.version-compare-wrap {
display: flex;
flex-direction: column;
row-gap: 16px;
width: 100%;
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
padding: 16px 75px;
box-sizing: border-box;
}
.compare-top {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 37px;
margin-top: 0;
}
.compare-top-col {
display: flex;
align-items: center;
gap: 22px;
background: transparent;
border-radius: 0;
padding: 0;
box-shadow: none;
}
.compare-top-label {
font-size: 18px;
font-weight: 700;
color: var(--text-primary-80-color);
white-space: nowrap;
}
.compare-top-select {
flex: 1;
:deep(.el-select__wrapper) {
background-color: rgb(246, 250, 255);
box-shadow: 0 0 0 1px var(--color-primary-35) inset;
}
:deep(.el-select__wrapper.is-hovering) {
box-shadow: 0 0 0 1px var(--color-primary-35) inset;
}
:deep(.el-select__wrapper.is-focused) {
box-shadow: 0 0 0 1px var(--color-primary-35) inset;
}
:deep(.el-select__selected-item),
:deep(.el-select__placeholder),
:deep(.el-select__input),
:deep(.el-select__caret),
:deep(.el-select__suffix),
:deep(.el-select__icon) {
color: var(--color-primary-100);
}
}
.compare-tools {
height: 66px;
border-bottom: 1px solid var(--border-black-5);
border-top: 1px solid var(--border-black-5);
background: transparent;
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.compare-tools-tabs {
flex: 0 0 auto;
:deep(.el-tabs__item) {
font-size: 18px;
font-weight: 700;
}
}
.compare-tools-actions {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
margin-left: auto;
justify-content: flex-end;
}
.compare-tools-switches {
display: flex;
align-items: center;
gap: 16px;
}
.compare-tools-switch {
display: inline-flex;
align-items: center;
gap: 8px;
.label {
font-size: 14px;
color: rgba(95, 101, 108, 1);
}
}
.compare-tools-search {
display: inline-flex;
align-items: center;
gap: 8px;
}
.find-word-box {
width: 430px;
height: 60px;
border: 1px solid rgba(230, 231, 232, 1);
background-color: #ffffff;
border-radius: 6px;
display: flex;
align-items: center;
position: absolute;
right: 0;
top: -68px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.06);
.find-word-input {
flex: 1;
min-width: 0;
padding-left: 8px;
}
.find-word-limit {
border-right: solid 1px rgba(230, 231, 232, 1);
color: #5f656c;
padding: 0 16px 0 8px;
white-space: nowrap;
}
.find-word-icon {
padding: 6px 10px;
margin: 0 2px;
cursor: pointer;
display: inline-flex;
align-items: center;
}
}
.find-word-wrap {
position: relative;
display: inline-flex;
align-items: center;
}
.find-word-open-btn {
height: 32px;
}
:deep(span.term-find-highlight) {
background-color: #ffff00;
}
:deep(span.term-find-highlight.is-active) {
background-color: #ff9632;
}
.compare-columns {
margin-top: 16px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.compare-col {
background: transparent;
border-radius: 0;
box-shadow: none;
display: flex;
flex-direction: column;
min-height: 520px;
}
.compare-col-title {
padding: 0 0 12px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
}
.compare-col-body {
flex: 1;
min-height: 0;
overflow: auto;
padding: 16px 0 0;
display: flex;
flex-direction: column;
gap: 12px;
}
.term-card {
width: 100%;
box-sizing: border-box;
border-radius: 2px;
background: rgb(247, 248, 249);
display: flex;
align-items: flex-start;
position: relative;
padding: 16px;
}
.term-card:nth-child(2n-1) {
background: rgba(249, 250, 252, 1);
}
.term-empty {
width: 100%;
min-height: 48px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(132, 136, 142, 1);
}
.term-body {
display: flex;
column-gap: 18px;
flex: 1;
min-width: 0;
width: 100%;
}
.term-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
row-gap: 6px;
}
.term-row {
display: flex;
align-items: flex-start;
column-gap: 18px;
}
.term-no-cn {
font-size: 16px;
font-weight: 700;
line-height: 24px;
color: var(--color-primary-100);
white-space: nowrap;
width: 90px;
text-align: center;
flex: 0 0 90px;
}
.term-no-en {
font-size: 14px;
font-weight: 400;
line-height: 24px;
color: var(--color-primary-100);
white-space: nowrap;
width: 90px;
text-align: center;
flex: 0 0 90px;
}
.term-content-cn {
flex: 1;
font-size: 16px;
font-weight: 700;
line-height: 24px;
color: var(--text-primary-80-color);
:deep(.term-entity) {
display: inline;
padding: 0 2px;
border-radius: 4px;
background: rgba(255, 213, 79, 0.35);
box-shadow: inset 0 0 0 1px rgba(255, 193, 7, 0.25);
}
}
.term-content-en {
flex: 1;
font-size: 14px;
font-weight: 400;
line-height: 24px;
color: var(--text-primary-65-color);
:deep(.term-entity) {
display: inline;
padding: 0 2px;
border-radius: 4px;
background: rgba(255, 213, 79, 0.28);
box-shadow: inset 0 0 0 1px rgba(255, 193, 7, 0.2);
}
}
@media (max-width: 1680px) {
.compare-columns,
.compare-top {
grid-template-columns: 1fr;
}
.compare-tools-actions {
width: 100%;
}
.compare-tools-search {
width: 100%;
}
.find-word-box {
width: 100%;
}
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论