提交 a3bb0583 authored 作者: yanpeng's avatar yanpeng

Merge branch 'master' into yp-dev

<template>
<div class="layout-container">
<div class="layout-main">
<div class="layout-main-center">
<div class="report-header">
<div class="report-title">政令原文</div>
<el-switch v-model="isHighlight" />
<div class="switch-label switch-label-left">高亮实体</div>
<el-switch v-model="isTranslate" />
<div class="switch-label">原文显示</div>
<div
v-for="action in headerActions"
:key="action.key"
class="btn"
@click="action.onClick"
>
<div :class="['icon', action.iconGapClass]">
<img :src="action.icon" alt="" />
</div>
<div class="text">{{ action.text }}</div>
</div>
<div class="find-word-box" v-if="findWordBox">
<div class="find-word-input">
<el-input
v-model="findWordTxt"
placeholder="查找原文内容"
@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>
</div>
<div class="report-main">
<div v-if="!displayReportData.length" class="no-content">暂无数据</div>
<el-scrollbar v-else height="100%">
<div
v-for="item in displayReportData"
:key="item.num"
class="content-row"
:class="{ 'high-light': isHighlight }"
>
<div class="content-cn" :class="{ 'translate-cn': !isTranslate }" v-html="item.content" />
<div v-if="isTranslate" class="content-en" v-html="item.contentEn" />
</div>
</el-scrollbar>
</div>
</div>
</div>
</div>
</template>
<script setup>
import defaultDownloadIcon from "./assets/icons/download.png";
import defaultSearchIcon from "./assets/icons/search.png";
import { nextTick, ref, watch } from "vue";
import { debounce } from "lodash";
const props = defineProps({
reportData: { type: Array, default: () => [] },
});
const emits = defineEmits(["download"]);
const isHighlight = ref(false);
const isTranslate = ref(true);
const findWordTxt = ref("");
const findWordBox = ref(false);
const findWordNum = ref(0);
const findWordMax = ref(0);
const originReportData = ref([]);
const displayReportData = ref([]);
const headerActions = [
{
key: "download",
text: "下载",
icon: defaultDownloadIcon,
iconGapClass: "icon-gap-4",
onClick: () => emits("download"),
},
{
key: "search",
text: "查找",
icon: defaultSearchIcon,
iconGapClass: "icon-gap-6",
onClick: () => handleFindWord("open"),
},
];
function escapeRegExp(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function applyHighlightToText(text, searchTerm) {
if (!text || !searchTerm) {
return { html: text || "", count: 0 };
}
const escapedTerm = escapeRegExp(searchTerm);
let count = 0;
const html = String(text).replace(new RegExp(escapedTerm, "g"), (match) => {
count += 1;
return `<span class="highlight">${match}</span>`;
});
return { html, count };
}
function setDisplayFromOrigin() {
displayReportData.value = originReportData.value.map((item) => ({
...item,
content: item.content || "",
contentEn: item.contentEn || "",
}));
}
function updateActiveHighlight() {
const spans = document.querySelectorAll("span.highlight");
spans.forEach((span, index) => {
if (index + 1 === findWordNum.value) {
span.scrollIntoView({});
span.style.backgroundColor = "#ff9632";
} else {
span.style.backgroundColor = "#ffff00";
}
});
}
const doUpdateWord = () => {
findWordNum.value = 0;
findWordMax.value = 0;
const term = findWordTxt.value?.trim();
if (!term) {
setDisplayFromOrigin();
return;
}
displayReportData.value = originReportData.value.map((item) => {
const cn = applyHighlightToText(item.content, term);
const en = isTranslate.value
? applyHighlightToText(item.contentEn, term)
: { html: item.contentEn, count: 0 };
findWordMax.value += cn.count + en.count;
return {
...item,
content: cn.html,
contentEn: en.html,
};
});
if (findWordMax.value > 0) {
nextTick(() => {
findWordNum.value = 1;
updateActiveHighlight();
});
}
};
const handleUpdateWord = debounce(() => {
doUpdateWord();
}, 300);
function handleFindWord(event) {
switch (event) {
case "open":
findWordBox.value = true;
break;
case "last":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value === 1 ? findWordMax.value : findWordNum.value - 1;
updateActiveHighlight();
}
break;
case "next":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value === findWordMax.value ? 1 : findWordNum.value + 1;
updateActiveHighlight();
}
break;
case "close":
findWordBox.value = false;
findWordTxt.value = "";
doUpdateWord();
break;
}
}
watch(
() => props.reportData,
(val) => {
originReportData.value = (val || []).map((item) => ({
content: item?.content || "",
contentEn: item?.contentEn || "",
num: item?.num,
}));
setDisplayFromOrigin();
doUpdateWord();
},
{ deep: true, immediate: true },
);
watch(isTranslate, () => {
doUpdateWord();
});
</script>
<style lang="scss" scoped>
.high-light {
:deep(span.highlight) {
background-color: #ffff00;
}
}
.layout-container {
width: 100%;
height: 100%;
background-color: #f7f8f9;
.layout-main {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
.layout-main-center {
width: 1600px;
background-color: white;
padding: 0 60px;
flex: auto;
display: flex;
flex-direction: column;
.report-header {
height: 80px;
display: flex;
align-items: center;
border-bottom: solid 1px rgba(234, 236, 238, 1);
margin: 0 20px 10px;
position: relative;
.find-word-box {
position: absolute;
top: -50px;
right: 0px;
width: 430px;
height: 60px;
border: 1px solid rgba(230, 231, 232, 1);
background-color: white;
border-radius: 6px;
display: flex;
align-items: center;
.find-word-input {
flex: auto;
}
.find-word-limit {
border-right: solid 1px rgba(230, 231, 232, 1);
color: #5f656c;
padding-right: 16px;
}
.find-word-icon {
padding: 10px 12px;
margin: 0 2px;
cursor: pointer;
}
}
.report-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
line-height: 20px;
font-weight: 700;
flex: 1;
}
.btn {
margin-left: 10px;
width: 88px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 6px;
background: rgba(255, 255, 255, 1);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.text {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.icon {
width: 16px;
height: 16px;
font-size: 0px;
img {
width: 100%;
height: 100%;
}
}
.icon-gap-4 {
margin-right: 4px;
}
.icon-gap-6 {
margin-right: 6px;
}
}
}
.report-main {
flex: auto;
box-sizing: border-box;
padding-top: 10px;
.no-content {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 20px;
font-weight: 400;
}
.content-row {
display: flex;
width: 100%;
padding: 0 20px;
min-height: 100px;
gap: 80px;
.content-en,
.content-cn {
width: 50%;
flex: auto;
padding-bottom: 40px;
box-sizing: border-box;
font-size: 16px;
line-height: 1.8;
color: #3b414b;
font-family: Microsoft YaHei;
text-align: justify;
white-space: pre-wrap;
}
.translate-cn {
padding-bottom: 10px;
}
}
}
}
}
}
.switch-label {
margin-left: 6px;
}
.switch-label-left {
margin-right: 10px;
}
:deep(.el-scrollbar__bar.is-vertical) {
right: 0px;
width: 4px;
background: transparent;
border-radius: 2px;
& > div {
background: #c5c7c9;
opacity: 1;
}
& > div:hover {
background: #505357;
}
}
</style>
import 'echarts-wordcloud';
import { MUTICHARTCOLORS } from '@/common/constant';
const getWordCloudChart = (data) => {
const option = {
const getWordCloudChart = data => {
const option = {
grid: {
left: 5,
top: 5,
......@@ -12,9 +12,9 @@ const getWordCloudChart = (data) => {
series: [
{
type: "wordCloud",
shape: 'circle',
width: '100%',
height: '100%',
shape: "circle",
width: "100%",
height: "100%",
// 其他形状你可以使用形状路径
// shape: 'circle', // 示例
// 或者自定义路径
......@@ -22,43 +22,29 @@ const getWordCloudChart = (data) => {
sizeRange: [16, 36], // 定义词云中文字大小的范围
rotationRange: [0, 0],
rotationStep: 0,
drawOutOfBound: false, // 是否超出画布
shrinkToFit: true, // 是否自动缩小以适应容器
drawOutOfBound: false, // 是否超出画布
shrinkToFit: true, // 是否自动缩小以适应容器
// 字体
textStyle: {
// normal: {
// color: function () {
// return 'rgb(' + [
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160)
// ].join(',') + ')';
// }
// },
color: function () {
// let colors = [
// "rgba(189, 33, 33, 1)",
// "rgba(232, 151, 21, 1)",
// "rgba(220, 190, 68, 1)",
// "rgba(96, 58, 186, 1)",
// "rgba(32, 121, 69, 1)",
// "rgba(22, 119, 255, 1)",
// ];
// let colors = MUTICHARTCOLORS
return MUTICHARTCOLORS[parseInt(Math.random() * MUTICHARTCOLORS.length)];
color: function (params) {
const colors = MUTICHARTCOLORS || [];
if (!colors.length) {
return "#69B1FF";
}
return colors[params.dataIndex % colors.length];
},
emphasis: {
shadowBlur: 5,
shadowColor: "#333",
},
shadowColor: "#333"
}
},
// 设置词云数据
data: data,
},
],
data
}
]
};
return option
}
return option;
};
export default getWordCloudChart
\ No newline at end of file
export default getWordCloudChart;
\ No newline at end of file
<template>
<div v-if="visible" ref="dialogRef" class="dialog-wrapper" :style="position">
<div class="dialog-box1" @mousedown="handleMouseDown">
<div class="icon">
<img v-if="detailItem.orgName === '参议院'" :src="logoSenate" alt="" />
<img v-else :src="logoHouse" alt="" />
</div>
<div class="title">
<div class="date">{{ detailItem.actionDate }}</div>
<div class="text">
{{ detailItem.actionTitle }}
</div>
</div>
<div class="close" @click="handleClose">
<img :src="closeIcon" alt="" />
</div>
</div>
<div class="dialog-box2" v-if="detailItem.agreeVote !== null || detailItem.disagreeVote !== null">
<div class="vote-bar">
<div class="agree-bar" :style="{ flex: detailItem.agreeVote || 1 }"></div>
<div class="disagree-bar" :style="{ flex: detailItem.disagreeVote || 1 }"></div>
</div>
<div class="vote-text">
<div class="agree-text">{{ (detailItem.agreeVote || 0) + "赞成" }}</div>
<div class="disagree-text">{{ (detailItem.disagreeVote || 0) + "反对" }}</div>
</div>
</div>
<template v-if="detailItem.fynrList && detailItem.fynrList.length">
<div class="dialog-box4">
<div class="box4-left">
<div class="icon">
<img :src="changeIcon" alt="" />
</div>
<div class="text">{{ "变更条款" }}</div>
</div>
</div>
<div class="dialog-box5">
<div class="box5-item" v-for="(sub, subIndex) in detailItem.fynrList" :key="subIndex + '-' + sub">
<div class="icon"></div>
<div class="text">{{ sub }}</div>
</div>
</div>
</template>
</div>
</template>
<script setup>
import { ref } from "vue";
defineProps({
visible: {
type: Boolean,
default: false,
},
detailItem: {
type: Object,
default: () => ({}),
},
position: {
type: Object,
default: () => ({ left: "0px", top: "0px" }),
},
});
const emit = defineEmits(["close"]);
const logoSenate = new URL("@/views/bill/deepDig/processOverview/assets/images/logo1.png", import.meta.url).href;
const logoHouse = new URL("@/views/bill/deepDig/processOverview/assets/images/logo2.png", import.meta.url).href;
const closeIcon = new URL("@/views/bill/deepDig/processOverview/assets/images/close.png", import.meta.url).href;
const changeIcon = new URL("@/views/bill/deepDig/processOverview/assets/images/dialog-box4-icon.png", import.meta.url).href;
const dialogRef = ref(null);
const handleClose = () => {
emit("close");
};
const handleMouseDown = (e) => {
const dialog = dialogRef.value;
if (!dialog) return;
const startX = e.clientX;
const startY = e.clientY;
const initialLeft = dialog.offsetLeft;
const initialTop = dialog.offsetTop;
const move = (moveEvent) => {
const deltaX = moveEvent.clientX - startX;
const deltaY = moveEvent.clientY - startY;
dialog.style.right = "auto";
dialog.style.left = `${initialLeft + deltaX}px`;
dialog.style.top = `${initialTop + deltaY}px`;
};
const stop = () => {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", stop);
};
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", stop);
};
</script>
<style lang="scss" scoped>
.dialog-wrapper {
position: absolute;
width: 480px;
padding-bottom: 20px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
z-index: 10000;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);
.dialog-box1 {
display: flex;
min-height: 90px;
height: auto;
padding-bottom: 15px;
position: relative;
border-bottom: 1px solid rgba(240, 242, 244, 1);
cursor: move;
.icon {
width: 48px;
height: 48px;
margin-left: 18px;
margin-top: 20px;
img {
width: 100%;
height: 100%;
}
}
.title {
margin-top: 20px;
margin-left: 16px;
width: 350px;
.date {
height: 22px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
}
.text {
margin-top: 4px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
span {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
.close {
position: absolute;
top: 14px;
right: 15px;
width: 16px;
height: 16px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
.dialog-box2 {
height: 59px;
padding: 0 23px;
.vote-bar {
display: flex;
height: 4px;
width: 100%;
margin-top: 20px;
border-radius: 2px;
overflow: hidden;
.agree-bar {
background: #52c41a;
margin-right: 2px;
}
.disagree-bar {
background: #f5222d;
}
}
.vote-text {
display: flex;
justify-content: space-between;
margin-top: 8px;
.agree-text {
color: #52c41a;
font-size: 14px;
}
.disagree-text {
color: #f5222d;
font-size: 14px;
}
}
}
.dialog-box4 {
width: 438px;
margin-left: 23px;
margin-top: 20px;
display: flex;
justify-content: space-between;
height: 30px;
.box4-left {
display: flex;
.icon {
margin-top: 6px;
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 64px;
height: 30px;
margin-left: 6px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
}
}
.dialog-box5 {
margin-top: 3px;
margin-left: 23px;
width: 438px;
.box5-item {
min-height: 30px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 1.5;
display: flex;
margin-bottom: 8px;
.icon {
flex-shrink: 0;
margin-left: 15px;
width: 6px;
height: 6px;
margin-top: 9px;
border-radius: 3px;
background: #84888e;
}
.text {
margin-left: 10px;
word-break: break-all;
}
}
}
}
</style>
......@@ -647,7 +647,7 @@ onMounted(() => {
.icon1 {
position: absolute;
left: 5px;
left: 8px;
bottom: -8px;
width: 16px;
height: 16px;
......@@ -663,7 +663,7 @@ onMounted(() => {
.icon2 {
position: absolute;
right: 5px;
right: 8px;
bottom: -8px;
width: 16px;
height: 16px;
......
......@@ -180,7 +180,8 @@
<OverviewCard class="overview-card--single box9" title="涉华法案关键条款" :icon="box7HeaderIcon">
<div class="overview-card-body box9-main">
<div class="overview-chart-wrap">
<div id="wordCloudChart" class="overview-chart"></div>
<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>
......@@ -197,8 +198,8 @@
</template>
<script setup>
import RiskSignal from "@/components/base/RiskSignal/index.vue";
import { onMounted, ref, onUnmounted, nextTick, watch } from "vue";
import RiskSignal from "@/components/base/riskSignal/index.vue";
import { onMounted, ref, onUnmounted, nextTick, watch, computed } from "vue";
import router from "@/router/index";
import setChart from "@/utils/setChart";
import {
......@@ -220,9 +221,9 @@ import OverviewCard from "./OverviewCard.vue";
import ResourceLibrarySection from "./ResourceLibrarySection.vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import TipTab from "@/components/base/TipTab/index.vue";
import WordCloundChart from "@/components/base/WordCloundChart/index.vue";
import getMultiLineChart from "./utils/multiLineChart";
import getWordCloudChart from "./utils/worldCloudChart";
import getPieChart from "./utils/piechart";
import getDoublePieChart from "./utils/doublePieChart";
......@@ -637,6 +638,7 @@ const handleToSocialDetail = item => {
};
// 关键条款
const wordCloudData = ref([]);
const wordCloudHasData = computed(() => Array.isArray(wordCloudData.value) && wordCloudData.value.length > 0);
const handleGetKeyTK = async () => {
try {
const res = await getBillOverviewKeyTK();
......@@ -655,8 +657,6 @@ const handleGetKeyTK = async () => {
};
const handleBox6 = async () => {
await handleGetKeyTK();
const wordCloudChart = getWordCloudChart(wordCloudData.value);
setChart(wordCloudChart, "wordCloudChart");
};
// 涉华领域分布
......
<template>
<div class="bill-original-text-page">
<div class="page-header">
<div class="page-title">法案原文</div>
<div class="page-actions">
<div class="action-btn" @click="handleBack">返回</div>
</div>
</div>
<div class="page-content">
<iframe v-if="billFullText" :src="billFullText" width="100%" height="100%" frameborder="0"></iframe>
<div v-else class="empty-state">暂无原文</div>
</div>
<DecreeOriginal :report-data="reportData" @download="handleDownload" />
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { getBillFullText } from "@/api/bill";
const route = useRoute();
const router = useRouter();
const billFullText = ref("");
import { ref } from "vue";
import DecreeOriginal from "@/components/base/DecreeOriginal/index.vue";
const getBillFullTextFn = async () => {
const res = await getBillFullText({
id: route.query.billId
});
if (res.code === 200 && res.data) {
billFullText.value = typeof res.data === "string" ? res.data.trim() : res.data;
}
};
// 旧“法案原文”功能/按钮全部废弃:页面仅承载通用原文组件,后续接入新接口再赋值。
const reportData = ref([]);
const handleBack = () => {
router.back();
const handleDownload = () => {
// 后续接入新接口/下载逻辑
};
onMounted(() => {
getBillFullTextFn();
});
</script>
<style lang="scss" scoped>
.bill-original-text-page {
width: 100%;
box-sizing: border-box;
background: rgba(248, 249, 250, 1);
padding: 0 0 20px;
.page-header {
width: 100%;
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
.page-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
}
.page-actions {
display: flex;
justify-content: flex-end;
.action-btn {
cursor: pointer;
height: 32px;
line-height: 32px;
padding: 0 12px;
border-radius: 6px;
border: 1px solid rgba(230, 231, 232, 1);
background: rgba(255, 255, 255, 1);
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
.page-content {
width: 100%;
height: calc(100vh - 320px);
min-height: 600px;
background: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
overflow: hidden;
iframe {
display: block;
}
.empty-state {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
}
}
height: 100%;
}
</style>
......@@ -111,48 +111,6 @@
</AnalysisBox>
</div>
<div class="box3">
<!-- <div class="box-header">
<div class="icon"></div>
<div class="title">{{ "政治献金领域分布" }}</div>
<div class="header-right">
<div class="right-icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box3-main">
<div class="box3-main-left" id="chart2"></div>
<div class="box3-main-right">
<el-empty v-if="!areaList.length" description="暂无数据" :image-size="100" />
<div class="box3-main-right-item" v-for="(item, index) in areaList" :key="index">
<div class="id">{{ index + 1 }}</div>
<div class="name">{{ item.name }}</div>
<div class="line">
<div class="inner-line" :style="{ width: (item.num / areaList[0].num) * 100 + '%' }"></div>
</div>
<div class="num">{{ item.numtext }}</div>
<div class="more">{{ `${item.insNum}家机构 >` }}</div>
</div>
</div>
</div>
<div class="box-footer">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ currentPersonName }}的政治资金主要依赖于一个由亿万富翁、特定行业利益集团及通过​“超级政治行动委员会”​​
运作的大额捐款网络。
</div>
<div class="box-footer-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
</div> -->
<AnalysisBox title="政治献金领域分布">
<div class="box3-main" :class="{ 'box3-main-no-footer': !showHardcodedTips }">
<div class="box3-main-left" id="chart2"></div>
......@@ -196,6 +154,7 @@ import setChart from "@/utils/setChart";
import getPieChart from "./utils/piechart";
import getSankeyChart from "./utils/sankey";
import { MUTICHARTCOLORS } from "@/common/constant";
import Img1 from "./assets/images/1.png";
import Img2 from "./assets/images/2.png";
......@@ -434,23 +393,9 @@ const topAreaList = computed(() => {
return areaList.value.slice(0, 5);
});
const chart2ColorList = ref(["#4096FF", "#FFA39E", "#ADC6FF", "#FFC069", "#B5F5EC", "#B37FEB", "#D6E4FF"]);
const sankeyColors = [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc",
"#a2c0f1",
"#f596aa",
"#e6b422",
"#4b2c20"
];
const chart2ColorList = ref([...MUTICHARTCOLORS]);
const sankeyColors = [...MUTICHARTCOLORS];
const partyContributionList = ref([
{
......
......@@ -220,101 +220,12 @@
</div> -->
</AnalysisBox>
<div class="dialog-wrapper" v-if="isShowDetailDialog" ref="dialogRef" :style="dialogPos">
<div class="dialog-box1" @mousedown="handleMouseDown">
<div class="icon">
<img v-if="currentDetailItem.orgName === '参议院'" src="./assets/images/logo1.png" alt="" />
<img v-else src="./assets/images/logo2.png" alt="" />
</div>
<div class="title">
<div class="date">{{ currentDetailItem.actionDate }}</div>
<div class="text">
{{ currentDetailItem.actionTitle }}
</div>
</div>
<div class="close" @click="handleClickDetail(false)">
<img src="./assets/images/close.png" alt="" />
</div>
</div>
<div class="dialog-box2" v-if="currentDetailItem.agreeVote !== null || currentDetailItem.disagreeVote !== null">
<div class="vote-bar">
<div class="agree-bar" :style="{ flex: currentDetailItem.agreeVote || 1 }"></div>
<div class="disagree-bar" :style="{ flex: currentDetailItem.disagreeVote || 1 }"></div>
</div>
<div class="vote-text">
<div class="agree-text">{{ (currentDetailItem.agreeVote || 0) + "赞成" }}</div>
<div class="disagree-text">{{ (currentDetailItem.disagreeVote || 0) + "反对" }}</div>
</div>
</div>
<!-- <div class="dialog-box3">
<div class="box3-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="box3-center">
{{ "法案在众议院惊险通过,凸显两党分歧严重。" }}
</div>
<div class="box3-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div> -->
<template v-if="currentDetailItem.fynrList && currentDetailItem.fynrList.length">
<div class="dialog-box4">
<div class="box4-left">
<div class="icon">
<img src="./assets/images/dialog-box4-icon.png" alt="" />
</div>
<div class="text">{{ "变更条款" }}</div>
</div>
<!-- <div class="box4-right" @click="handleSwitchCompareDialog(true)">
{{ "条款对比 >" }}
</div> -->
</div>
<div class="dialog-box5">
<div class="box5-item" v-for="(sub, subIndex) in currentDetailItem.fynrList" :key="subIndex">
<div class="icon"></div>
<div class="text">{{ sub }}</div>
</div>
</div>
</template>
<!-- <div class="dialog-box6">
<div class="icon">
<img src="./assets/images/dialog-box6-icon.png" alt="" />
</div>
<div class="text">{{ "支持意见领袖" }}</div>
</div>
<div class="dialog-box7">
<div class="box7-item">
<div class="icon">
<img src="./assets/images/dialog-box7-icon1.png" alt="" />
<div class="inner-icon">
<img src="./assets/images/user-inner-icon.png" alt="" />
</div>
</div>
<div class="right">
<div class="name">{{ "迈克·约翰逊" }}</div>
<div class="content">
{{
"协调党内分歧:修改条款争取支持(如调整SALT扣除上限),推动程序性投票"
}}
</div>
</div>
</div>
<div class="box7-item">
<div class="icon">
<img src="./assets/images/dialog-box7-icon2.png" alt="" />
<div class="inner-icon">
<img src="./assets/images/user-inner-icon.png" alt="" />
</div>
</div>
<div class="right">
<div class="name">{{ "詹姆斯·戴维·万斯" }}</div>
<div class="content">
{{ "框架谈判主导:与约翰逊合作确立调和程序方向" }}
</div>
</div>
</div>
</div> -->
</div>
<ProcessOverviewDetailDialog
:visible="isShowDetailDialog"
:detailItem="currentDetailItem"
:position="dialogPos"
@close="handleClickDetail(false)"
/>
<div class="dialog-wrapper1" v-if="isShowCompareDialog">
<div class="dialog-header">
<div class="header-left">
......@@ -530,6 +441,7 @@
import { ref, onMounted, computed } from "vue";
import { getBillDyqkSummary } from "@/api/bill";
import CommonPrompt from "../../commonPrompt/index.vue";
import ProcessOverviewDetailDialog from "../../ProcessOverviewDetailDialog.vue";
// 获取法案流程
......@@ -669,39 +581,6 @@ const handleClickDetail = (isShow, item = {}, event = null) => {
}
};
const dialogRef = ref(null);
const handleMouseDown = (e) => {
const dialog = dialogRef.value;
if (!dialog) return;
const startX = e.clientX;
const startY = e.clientY;
// 获取当前的 offsetLeft 和 offsetTop
// 注意:如果是第一次拖动,且原本是 right 定位的,offsetLeft 也是可以直接获取到的
const initialLeft = dialog.offsetLeft;
const initialTop = dialog.offsetTop;
const move = (moveEvent) => {
const deltaX = moveEvent.clientX - startX;
const deltaY = moveEvent.clientY - startY;
// 清除 right 定位,改用 left
dialog.style.right = 'auto';
dialog.style.left = `${initialLeft + deltaX}px`;
dialog.style.top = `${initialTop + deltaY}px`;
};
const stop = () => {
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', stop);
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', stop);
};
// 挂载阶段调用
onMounted(() => {
getBillDyqkSummaryList();
......@@ -1382,337 +1261,6 @@ onMounted(() => {
}
}
.dialog-wrapper {
position: absolute;
width: 480px;
// height: 692px; // 去掉固定高度,由内容撑开
padding-bottom: 20px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
z-index: 10000; // 提高层级,超过节点的 9999
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15); // 加个阴影更好看
.dialog-box1 {
display: flex;
min-height: 90px; // 改为最小高度,允许撑开
height: auto; // 高度自动
padding-bottom: 15px; // 底部留白
position: relative;
border-bottom: 1px solid rgba(240, 242, 244, 1);
cursor: move; // 鼠标变为移动手势
.icon {
width: 48px;
height: 48px;
margin-left: 18px;
margin-top: 20px;
img {
width: 100%;
height: 100%;
}
}
.title {
margin-top: 20px;
margin-left: 16px;
width: 350px;
.date {
height: 22px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
}
.text {
margin-top: 4px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 22px;
span {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
.close {
position: absolute;
top: 14px;
right: 15px;
width: 16px;
height: 16px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
.dialog-box2 {
height: 59px;
padding: 0 23px;
// border-bottom: 1px solid rgba(240, 242, 244, 1);
.vote-bar {
display: flex;
height: 4px;
width: 100%;
margin-top: 20px;
border-radius: 2px;
overflow: hidden;
.agree-bar {
background: #52C41A;
margin-right: 2px;
}
.disagree-bar {
background: #F5222D;
}
}
.vote-text {
display: flex;
justify-content: space-between;
margin-top: 8px;
.agree-text {
color: #52C41A;
font-size: 14px;
}
.disagree-text {
color: #F5222D;
font-size: 14px;
}
}
}
.dialog-box3 {
width: 438px;
height: 46px;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(231, 241, 255, 1);
border-radius: 4px;
background: rgba(246, 251, 255, 1);
margin-left: 23px;
margin-top: 15px;
.box3-left {
width: 19px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.box3-center {
width: 345px;
margin-left: 25px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 46px;
}
.box3-right {
width: 24px;
height: 24px;
background: rgba(231, 241, 255, 1);
border-radius: 12px;
img {
width: 100%;
height: 100%;
}
}
}
.dialog-box4 {
width: 438px;
margin-left: 23px;
margin-top: 20px;
display: flex;
justify-content: space-between;
height: 30px;
.box4-left {
display: flex;
.icon {
margin-top: 6px;
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 64px;
height: 30px;
margin-left: 6px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
}
.box4-right {
height: 30px;
line-height: 30px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
cursor: pointer;
}
}
.dialog-box5 {
margin-top: 3px;
margin-left: 23px;
width: 438px;
.box5-item {
min-height: 30px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 1.5;
display: flex;
margin-bottom: 8px;
.icon {
flex-shrink: 0;
margin-left: 15px;
width: 6px;
height: 6px;
margin-top: 9px;
border-radius: 3px;
background: #84888e;
}
.text {
margin-left: 10px;
word-break: break-all;
}
}
}
.dialog-box6 {
margin-top: 30px;
margin-left: 20px;
display: flex;
height: 30px;
.icon {
margin-top: 6px;
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
margin-left: 6px;
}
}
.dialog-box7 {
margin-left: 21px;
margin-top: 3px;
height: 190px;
width: 440px;
overflow: hidden;
.box7-item {
display: flex;
.icon {
width: 42px;
height: 42px;
position: relative;
img {
width: 42px;
height: 42px;
}
.inner-icon {
position: absolute;
top: 0;
right: -8px;
width: 24px;
height: 24px;
box-sizing: border-box;
padding: 2px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.8);
img {
width: 20px;
height: 20px;
}
}
}
.right {
margin-left: 30px;
.name {
height: 30px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
.content {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
}
}
}
}
.dialog-wrapper1 {
position: absolute;
z-index: 9999;
......
......@@ -100,11 +100,11 @@
<div class="term-main">
<div class="term-row term-row-cn">
<div class="term-no-cn">第{{ term.tkxh }}条.</div>
<div class="term-content-cn">{{ term.fynr }}</div>
<div class="term-content-cn" v-html="getTermContentHtml(term, 'cn')"></div>
</div>
<div class="term-row term-row-en" v-if="termsShowOriginal">
<div class="term-no-en">Sec.{{ term.tkxh }}</div>
<div class="term-content-en">{{ term.ywnr }}</div>
<div class="term-content-en" v-html="getTermContentHtml(term, 'en')"></div>
</div>
</div>
</div>
......@@ -136,6 +136,8 @@ import * as echarts from "echarts";
import { Search } from "@element-plus/icons-vue";
import getPieChart from "./utils/piechart";
import { getBillContentId, getBillContentTk, getBillContentXzfs, getBillHyly } from "@/api/bill";
import { MUTICHARTCOLORS } from "@/common/constant";
import { extractTextEntity } from "@/api/intelligent/index";
const route = useRoute();
......@@ -161,6 +163,134 @@ const domainLoading = ref(false);
const termsHighlight = ref(true);
const termsShowOriginal = ref(true);
const entityRequestToken = ref(0);
const termEntityCache = ref(new Map());
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 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) => {
const rawText = String(text ?? "");
if (!rawText) return "";
const safeText = escapeHtml(rawText).replace(/\n/g, "<br />");
if (!enableHighlight) return safeText;
const ranges = getEntityRanges(rawText, entities);
if (!ranges.length) return safeText;
let html = "";
let cursor = 0;
for (const r of ranges) {
if (cursor < r.start) {
html += escapeHtml(rawText.slice(cursor, r.start));
}
const spanText = rawText.slice(r.start, r.end);
const type = escapeHtml(r.ent?.type ?? "");
html += `<span class="term-entity" data-entity-type="${type}">${escapeHtml(spanText)}</span>`;
cursor = r.end;
}
if (cursor < rawText.length) {
html += escapeHtml(rawText.slice(cursor));
}
return html.replace(/\n/g, "<br />");
};
const getTermEntityKey = (term, lang) => {
const baseKey = getTermKey(term, -1);
return `${baseKey}__${lang}`;
};
const ensureEntitiesForTerms = async terms => {
if (!termsHighlight.value) return;
const list = Array.isArray(terms) ? terms : [];
if (!list.length) return;
const currentToken = ++entityRequestToken.value;
const tasks = [];
for (const term of list) {
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) 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);
};
const tkRequestToken = ref(0);
const xzfsRequestToken = ref(0);
const hylyRequestToken = ref(0);
......@@ -182,9 +312,9 @@ const getTermSerial = index => {
};
const chart1Data = ref([]);
const chart1ColorList = ref(["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"]);
const chart1ColorList = ref([...MUTICHARTCOLORS]);
const chart2ColorList = ref(["#ff7875", "#85a5ff", "#95de64", "#ffc069", "#85e5db"]);
const chart2ColorList = ref([...MUTICHARTCOLORS]);
const chart2Data = ref([]);
......@@ -218,6 +348,14 @@ watch([selectedDomain, selectedLimit], () => {
handleGetBillContentTk(checkedValue.value ? "Y" : "N");
});
watch(
[termsHighlight, termsShowOriginal],
() => {
ensureEntitiesForTerms(displayTermsList.value);
},
{ immediate: true }
);
const handleSearchSubmit = () => {
searchKeyword.value = searchValue.value;
currentPage.value = 1;
......@@ -362,6 +500,7 @@ const handleGetBillContentTk = async cRelated => {
return item;
});
total.value = res.data.totalElements || 0;
ensureEntitiesForTerms(mainTermsList.value);
} else {
mainTermsList.value = [];
total.value = 0;
......@@ -805,6 +944,14 @@ onMounted(async () => {
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 {
......@@ -813,6 +960,14 @@ onMounted(async () => {
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);
}
}
.open {
......
<template>
<div class="layout-container">
<!-- 导航菜单 -->
<div class="layout-main">
<div class="layout-main-box">
<div class="header-main">
<div class="layout-main-header">
<div class="icon">
<img :src="summaryInfo.imageUrl" alt="" />
</div>
<div class="info">
<div class="info-box1 one-line-ellipsis">{{ summaryInfo.name }}</div>
<div class="info-box1 one-line-ellipsis">{{ summaryInfo.name || "--" }}</div>
<div class="info-box2">
<div class="info-box2-item">{{ summaryInfo.postDate }}</div>
<div class="info-box2-item">{{ summaryInfo.postDate || "--" }}</div>
|
<div class="info-box2-item">{{ summaryInfo.orgName }}</div>
<div class="info-box2-item">{{ summaryInfo.orgName || "--" }}</div>
|
<div class="info-box2-item one-line-ellipsis">{{ summaryInfo.ename }}</div>
<div class="info-box2-item one-line-ellipsis">{{ summaryInfo.ename || "--" }}</div>
</div>
</div>
<div class="layout-main-header-right-box">
<div class="right-box-top">
<div class="time">{{ summaryInfo.postDate }}</div>
<div class="name">{{ summaryInfo.orgName }}</div>
<div class="time">{{ summaryInfo.postDate || "--" }}</div>
<div class="name">{{ summaryInfo.orgName || "--" }}</div>
</div>
</div>
</div>
</div>
<div class="layout-main-center">
<div class="report-header">
<div class="report-title">政令原文</div>
<!-- <el-switch v-model="isHighlight" />
<div style="margin-left: 6px; margin-right: 10px;">高亮实体</div> -->
<el-switch v-model="isTranslate" />
<div style="margin-left: 6px;">原文显示</div>
<!-- <div class="btn" @click="handleDownload">
<el-icon><Document /></el-icon>
<div class="text">下载</div>
</div> -->
<div class="btn" @click="handleFindWord('open')">
<el-icon><Search /></el-icon>
<div class="text">查找</div>
</div>
<div class="find-word-box" v-if="findWordBox">
<div class="find-word-input">
<el-input v-model="findWordTxt" placeholder="查找原文内容" @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>
</div>
<div class="report-main">
<div v-if="!reportData.length" class="noContent">{{ "暂无数据" }}</div>
<el-scrollbar height="100%" v-else>
<div v-for="(item, index) in reportData" :key="index" :class="['content-row', {'high-light':isHighlight}]">
<!-- 右侧:中文 -->
<div :class="['content-cn', {'translate-cn':!isTranslate}]" v-html="item.content"></div>
<!-- 左侧:英文 -->
<div class="content-en" v-html="item.contentEn" v-if="isTranslate"></div>
</div>
</el-scrollbar>
</div>
<BaseDecreeOriginal :report-data="reportData" @download="handleDownload" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { debounce } from "lodash";
import { getDecreeSummary } from "@/api/decree/introduction";
import { getDecreeReport } from "@/api/decree/introduction";
import BaseDecreeOriginal from "@/components/base/DecreeOriginal/index.vue";
const route = useRoute();
// 政令原文操作
const isHighlight = ref(false);
const isTranslate = ref(true);
const findWordTxt = ref("")
const findWordBox = ref(false);
const findWordNum = ref(0);
const findWordMax = ref(0);
const handleDownload = async () => {
if (summaryInfo.value?.url) {
try {
......@@ -128,73 +80,6 @@ const handleDownload = async () => {
ElMessage.warning("暂无下载链接!");
}
}
const handleHighlight = () => {
let spans = document.querySelectorAll(`span.highlight`);
spans.forEach((span, index) => {
if (index+1 === findWordNum.value) {
// 平滑滚动 behavior: 'smooth'
span.scrollIntoView({})
span.style.backgroundColor = "#ff9632";
} else {
span.style.backgroundColor = "#ffff00";
}
})
}
const handleFindWord = (event) => {
switch (event) {
case "open":
findWordBox.value = true;
break;
case "last":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value==1 ? findWordMax.value : findWordNum.value-1;
handleHighlight()
}
break;
case "next":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value==findWordMax.value ? 1 : findWordNum.value+1;
handleHighlight()
}
break;
case "close":
findWordBox.value = false;
findWordTxt.value = "";
handleUpdateWord()
break;
}
}
const handleUpdateWord = debounce(() => {
console.log("更新查找词", findWordTxt.value);
findWordNum.value = 0;
findWordMax.value = 0;
if (findWordTxt.value) {
originData.forEach((item, index) => {
if (item.content) {
reportData.value[index].content = highlightText(item.content, findWordTxt.value);
}
if (isTranslate.value && item.contentEn) {
reportData.value[index].contentEn = highlightText(item.contentEn, findWordTxt.value);
}
});
if (findWordMax.value > 0) {
nextTick(() => { handleFindWord('next') })
}
} else {
originData.forEach((item, index) => {
reportData.value[index].content = item.content;
reportData.value[index].contentEn = item.contentEn;
});
}
}, 300)
const highlightText = (text, searchTerm) => {
const escapedTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return text.replace(new RegExp(escapedTerm, 'g'), (match) => {
findWordMax.value++;
return `<span class="highlight">${match}</span>`;
});
}
// 获取全局信息
const summaryInfo = ref({});
......@@ -213,13 +98,12 @@ const handleGetSummary = async () => {
// 获取报告原文 - 修改为获取分段数组
const reportData = ref([]);
let originData = [];
const handleGetReport = async () => {
try {
const res = await getDecreeReport({id: route.query.id});
console.log("报告原文", res);
if (res.code === 200 && res.data) {
originData = [];
const originData = [];
let num = Math.max(res.data.content.length, res.data.contentEn.length)
for (let i = 0; i < num; i++) {
let obj = {
......@@ -241,30 +125,25 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.high-light {
:deep(span.highlight) {
// color: #055FC2;
background-color: #ffff00;
}
}
.layout-container {
width: 100%;
height: 100%;
background-color: #F7F8F9;
background-color: #f7f8f9;
.layout-main {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
.layout-main-box {
padding: 16px 0;
.header-main {
padding: 17px 0;
width: 100%;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-top: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
}
.layout-main-header {
width: 1600px;
display: flex;
......@@ -287,6 +166,7 @@ onMounted(() => {
margin-right: 40px;
width: 20px;
flex: auto;
.info-box1 {
width: 100%;
color: rgba(59, 65, 75, 1);
......@@ -316,6 +196,7 @@ onMounted(() => {
white-space: nowrap;
padding: 0 10px;
}
.info-box2-item:first-child {
padding-left: 0px;
}
......@@ -325,6 +206,7 @@ onMounted(() => {
.layout-main-header-right-box {
.right-box-top {
white-space: nowrap;
.time {
height: 24px;
line-height: 24px;
......@@ -360,152 +242,7 @@ onMounted(() => {
flex: auto;
display: flex;
flex-direction: column;
.report-header {
height: 80px;
display: flex;
align-items: center;
border-bottom: solid 1px rgba(234, 236, 238, 1);
margin: 0 20px 10px;
position: relative;
.find-word-box {
position: absolute;
top: -50px;
right: 0px;
width: 430px;
height: 60px;
border: 1px solid rgba(230, 231, 232, 1);
background-color: white;
border-radius: 6px;
display: flex;
align-items: center;
.find-word-input {
width: 20px;
flex: auto;
}
.find-word-limit {
border-right: solid 1px rgba(230, 231, 232, 1);
color: #5F656C;
padding-right: 16px;
}
.find-word-icon {
padding: 10px 12px;
margin: 0 2px;
cursor: pointer;
}
}
.report-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
line-height: 20px;
font-weight: 700;
width: 20px;
flex: auto;
}
.btn {
margin-left: 10px;
width: 88px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 6px;
background: rgba(255, 255, 255, 1);
display: flex;
justify-content: center;
gap: 8px;
align-items: center;
cursor: pointer;
.text {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
}
.report-main {
height: 20px;
flex: auto;
box-sizing: border-box;
padding-top: 10px;
// 滚动条样式
&::-webkit-scrollbar {
display: none;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
.noContent {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 20px;
font-weight: 400;
}
.content-row {
display: flex;
width: 100%;
padding: 0 20px;
min-height: 100px;
gap: 80px;
&:last-child {
border-bottom: none;
}
.content-en,
.content-cn {
width: 50%;
flex: auto;
padding-bottom: 40px;
box-sizing: border-box;
font-size: 16px;
line-height: 1.8;
color: #3B414B;
font-family: Microsoft YaHei;
text-align: justify;
white-space: pre-wrap; // 保留换行格式
}
.translate-cn {
padding-bottom: 10px;
}
}
}
}
}
}
// 修改element-plus滚动条样式
:deep(.el-scrollbar__bar.is-vertical) {
right: 0px;
width: 4px;
background: transparent;
border-radius: 2px;
&>div {
background: #c5c7c9;
opacity: 1;
}
&>div:hover {
background: #505357;
}
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论