提交 47bc38c9 authored 作者: 张伊明's avatar 张伊明

feat 新增法案原文页面

feat 完成法案概况-内容概要页面 fix 修复法案首页某些bug
上级 fd608e1e
......@@ -117,7 +117,7 @@ export function getBillContentId(params) {
// 主要条款-根据原文ID获取条款内容
/**
* @param {billid,id,cRelated,currentPage,pageSize}
* @param {billId,id,cRelated,currentPage,pageSize,domainNameList,measuresNameList,content}
* @header token
*/
export function getBillContentTk(params) {
......
......@@ -145,6 +145,16 @@ export function getBillsPersonRel(params, signal) {
})
}
// 获取涉华委员会及其法案
export function getBillsIsCnCommittee(params, signal) {
return request({
method: 'GET',
url: `/api/BillOverview/billsIsCnCommittee`,
params,
signal
})
}
// 获取提出部门列表
export function getPostOrgList() {
return request({
......
......@@ -72,6 +72,15 @@ service.interceptors.response.use(
},
error => {
console.log('err' + error)
const isCanceledError =
error?.code === 'ERR_CANCELED' ||
error?.name === 'CanceledError' ||
error?.name === 'AbortError' ||
(typeof error?.message === 'string' && /canceled/i.test(error.message))
// 重复请求触发的取消不提示错误
if (isCanceledError) return Promise.reject(error)
// 处理token过期或无效的情况
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
......
......@@ -2,14 +2,14 @@
<div class="sider-tabs-wrapper">
<div class="sider-item"
:class="{'sider-item-active': sider.active}"
v-for="sider, index in siderList" :key="index"
v-for="(sider, index) in siderList" :key="sider.name || index"
@click="handleClickSiderItem(sider)"
>
<div
class="sider-item-text text-primary-65-clor text-tip-1"
:class="{'sider-item-text-active': sider.active}">{{ sider.name }}</div>
<div class="sider-item-icon" v-show="sider.active">
<img src="./active-icon.svg" alt="">
<img src="./active-icon.svg" alt="" />
</div>
</div>
</div>
......@@ -20,7 +20,7 @@
const props = defineProps({
siderList: {
type: Array,
default: [
default: () => [
{
name: '分析内容1',
active: true
......
......@@ -47,21 +47,38 @@ const props = defineProps({
showAllBtn: {
type: Boolean,
default: true
},
// 当业务功能尚未实现时,点击右上角图标仅弹出统一提示
devTip: {
type: Boolean,
default: false
}
})
const handleSave = () => {
if (props.devTip) {
ElMessage.warning('当前功能正在开发中,敬请期待!')
return
}
ElMessage.success('保存当前内容')
// emit('save')
}
const handleDownload = () => {
if (props.devTip) {
ElMessage.warning('当前功能正在开发中,敬请期待!')
return
}
ElMessage.success('下载当前内容')
// emit('download')
}
const handleCollect = () => {
if (props.devTip) {
ElMessage.warning('当前功能正在开发中,敬请期待!')
return
}
ElMessage.success('收藏当前内容')
// emit('collect')
......
......@@ -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 BillOriginalText = () => import('@/views/bill/billOriginalText/index.vue')
const billRoutes = [
......@@ -35,6 +36,14 @@ const billRoutes = [
dynamicTitle: true // 标记需要动态设置标题
},
children: [
{
path: "originalText",
name: "BillOriginalText",
component: BillOriginalText,
meta: {
title: "法案原文"
}
},
// 法案分析路由
{
path: "bill",
......
......@@ -51,7 +51,7 @@ const initChart = () => {
color: "#fff",
backgroundColor: "rgba(189, 33, 33, 0.9)",
borderRadius: 20,
padding: [8, 16] // 适当增加 padding 以确保背景块足够大
padding: [10, 16] // 适当增加 padding 以确保背景块足够大
}
};
}
......
<template>
<div class="background-wrap">
<div class="background-wrap-left">
<AnalysisBox class="left-box left-box--background" title="立法背景" :showAllBtn="false">
<AnalysisBox class="left-box left-box--background" title="立法背景" :showAllBtn="false" :devTip="true">
<template #header-btn>
<div class="header-btn-box">
<el-button :type="box1Btn1Type" plain @click="handleClickBox1Btn(1)">涉华背景</el-button>
......@@ -28,7 +28,7 @@
</div>
</AnalysisBox>
<AnalysisBox class="left-box left-box--event" title="相关事件" :showAllBtn="false">
<AnalysisBox class="left-box left-box--event" title="相关事件" :showAllBtn="false" :devTip="true">
<div class="box2-main">
<div class="box2-main-item" v-for="item in eventDisplayList" :key="item.id" @click="handleClickEvent(item)">
<div class="left">
......@@ -47,7 +47,7 @@
</div>
</AnalysisBox>
</div>
<AnalysisBox class="right-panel" title="议员相关性分析" :showAllBtn="false">
<AnalysisBox class="right-panel" title="议员相关性分析" :showAllBtn="false" :devTip="true">
<template #header-btn>
<div class="header-btn-box">
<el-button :type="box2Btn1Type" plain @click="handleClickBox2Btn(1)">赞成议员</el-button>
......@@ -266,7 +266,7 @@ const handleGetRelatedEvent = async () => {
};
try {
const res = await getBillInfoEvent(params);
eventList.value = res.data;
eventList.value = res.data.slice(0, 5);
} catch (error) { }
};
......
......@@ -61,6 +61,17 @@
</div>
</template>
</div>
<div class="right-header" v-else>
<div class="right-header-box right-header-sort" style="margin-left: auto">
<el-checkbox
v-model="committeeIsInvolveCn"
class="involve-checkbox"
@change="handleCommitteeInvolveCnChange"
>
只看涉华委员会
</el-checkbox>
</div>
</div>
<div class="right-main" v-loading="loading">
<template v-if="activeTabName === '国会法案'">
<div class="right-main-box" v-for="item in bills" :key="item.billId">
......@@ -100,7 +111,7 @@
<div class="member-card" v-for="item in memberList" :key="item.id">
<div class="member-card-top">
<div class="member-avatar-wrap">
<img class="member-avatar" :src="item.avatar || defaultAvatar" alt="avatar" />
<img class="member-avatar" :src="item.avatar || defaultAvatar" alt="avatar" @click="handleClickAvatar(item)" />
<div class="member-icon-row">
<img v-if="item.partyIcon" class="member-mini-icon-img" :src="item.partyIcon" alt="party" />
<img v-if="item.chamberIcon" class="member-mini-icon-img" :src="item.chamberIcon" alt="chamber" />
......@@ -137,12 +148,12 @@
<div class="coop-card-header">
<div class="coop-members">
<div class="coop-member">
<img class="coop-avatar" :src="item.left.avatar || defaultAvatar" alt="avatar-left" />
<img class="coop-avatar" :src="item.left.avatar || defaultAvatar" alt="avatar-left" @click="handleClickAvatar(item.left)" />
<div class="coop-member-name" :title="item.left.name">{{ item.left.name }}</div>
</div>
<div class="coop-dot">·</div>
<div class="coop-member">
<img class="coop-avatar" :src="item.right.avatar || defaultAvatar" alt="avatar-right" />
<img class="coop-avatar" :src="item.right.avatar || defaultAvatar" alt="avatar-right" @click="handleClickAvatar(item.right)" />
<div class="coop-member-name" :title="item.right.name">{{ item.right.name }}</div>
</div>
</div>
......@@ -200,15 +211,20 @@
{{ item.desc }}
</div>
<div class="coop-count">
{{ `${(item.bills || []).length}项重点法案` }}
{{ `${item.proposalSize ?? (item.bills || []).length}项重点法案` }}
</div>
<slot name="committee-extra" :committee="item" />
</div>
<div class="coop-proposals">
<div class="coop-proposal-item" v-for="bill in item.bills" :key="`${item.id}-${bill}`">
<div
class="coop-proposal-item"
v-for="bill in item.bills"
:key="`${item.id}-${bill.billId || bill.title}`"
@click="handleClickCommitteeBill(bill)"
>
<div class="coop-proposal-main">
<div class="coop-proposal-subtitle" :title="bill">
{{ bill }}
<div class="coop-proposal-subtitle" :title="bill.title">
{{ bill.title }}
</div>
</div>
<div class="coop-proposal-arrow">></div>
......@@ -216,6 +232,19 @@
</div>
</div>
</div>
<div class="right-footer">
<div class="footer-left">{{ `共 ${committeeTotal} 项` }}</div>
<div class="footer-right">
<el-pagination
@current-change="handleCommitteeCurrentChange"
:page-size="committeePageSize"
:current-page="committeeCurrentPage"
background
layout="prev, pager, next"
:total="committeeTotal"
/>
</div>
</div>
</div>
</div>
</div>
......@@ -225,7 +254,8 @@
<script setup>
import { computed, onMounted, ref } from "vue";
import { getHylyList, getPostOrgList, getPostMemberList, getBills, getBillsPerson, getBillsPersonRel } from "@/api/bill/billHome";
import { useRouter } from "vue-router";
import { getHylyList, getPostOrgList, getPostMemberList, getBills, getBillsPerson, getBillsPersonRel, getBillsIsCnCommittee } from "@/api/bill/billHome";
import CommonPrompt from "../commonPrompt/index.vue";
import desc from "./assets/icons/icon-desc.png";
import defaultAvatar from "@/assets/icons/default-icon1.png";
......@@ -235,6 +265,8 @@ import cyyIcon from "@/assets/icons/cyy.png";
import ghdIcon from "@/assets/icons/ghd.png";
import mzdIcon from "@/assets/icons/mzd.png";
const router = useRouter();
const props = defineProps({
onClickToDetail: { type: Function, required: true },
onAfterPageChange: { type: Function, default: null }
......@@ -276,6 +308,7 @@ const releaseTimeList = ref([
{ label: "发布时间倒序", value: false }
]);
const isInvolveCn = ref("Y");
const committeeIsInvolveCn = ref(false);
const cateKuList = ref([]);
const activeAreaList = ref(["全部领域"]);
......@@ -310,7 +343,7 @@ const postOrgList = ref([{ departmentName: "全部委员会", departmentId: "全
const postMemberList = ref([{ memberName: "全部提出议员", memberId: "全部提出议员" }]);
const memberList = ref([]);
// sortFun: boolean(议员接口字段),当前接口表现与“正序/倒序”相反,发参时取反
const memberSortFun = ref(true);
const memberSortFun = ref(false);
const memberSortFunList = ref([
{ label: "提案数量正序", value: true },
{ label: "提案数量倒序", value: false }
......@@ -326,29 +359,10 @@ const coopPageSize = ref(6);
const coopCurrentPage = ref(1);
const coopPagedList = computed(() => coopList.value || []);
const committeeList = ref([
{
id: "committee-1",
avatar: "",
name: "美中经济与安全审查委员会",
desc: "聚焦中美经贸、科技与国家安全议题,跟踪涉华政策与立法动态。",
bills: ["2025年涉华关键技术审查法案", "供应链安全与关键矿产保障法案", "对华投资限制与透明度加强法案", "联邦科研合作安全审查法案"]
},
{
id: "committee-2",
avatar: "",
name: "众议院外交事务委员会",
desc: "负责对外政策与国际关系立法,持续推进对华议题相关审议与听证。",
bills: ["印太战略资源配置法案", "对外援助合规与问责法案", "跨境数据安全与执法协作法案", "关键基础设施网络韧性法案"]
},
{
id: "committee-3",
avatar: "",
name: "参议院军事委员会",
desc: "围绕国防授权、军费及安全合作展开审查,关注涉华国防与技术议题。",
bills: ["国防授权法涉华条款(示例)", "先进半导体供应安全法案", "军民两用技术出口管制法案", "海外关键设施安全评估法案"]
}
]);
const committeeList = ref([]);
const committeeTotal = ref(0);
const committeePageSize = ref(6);
const committeeCurrentPage = ref(1);
const bills = ref([]);
const total = ref(0);
......@@ -387,19 +401,71 @@ const handleBillImageError = e => {
img.src = defaultBill;
};
const handleClickAvatar = member => {
if (!member?.id) return;
window.sessionStorage.setItem("curTabName", member.name || "");
const routeData = router.resolve({
path: "/characterPage",
query: {
personId: member.id
}
});
window.open(routeData.href, "_blank");
};
const getReversedProgress = progress => (Array.isArray(progress) ? [...progress].reverse() : []);
// 获取委员会列表(占位)
const handleClickCommitteeBill = bill => {
if (!bill?.billId) return;
props.onClickToDetail({
billId: bill.billId,
name: bill.title || ""
});
};
// 获取委员会列表
const handleGetCommitteeList = async () => {
loading.value = true;
const { token, signal } = beginAbortableRequest();
const params = {
currentPage: committeeCurrentPage.value,
pageSize: committeePageSize.value,
isInvolveCn: committeeIsInvolveCn.value
};
if (!activeYyList.value.includes("全部议院")) params.congressIds = activeYyList.value.join(",");
try {
// TODO: 接入委员会列表接口后,在此替换为真实请求并赋值 committeeList
// const res = await getCommitteeList(params);
// committeeList.value = res?.data?.content || [];
const res = await getBillsIsCnCommittee(params, signal);
if (res.code === 200 && res.data && Array.isArray(res.data.content)) {
committeeList.value = res.data.content.map(item => {
const billInfoPage = item.billInfoPage || [];
const bills = billInfoPage.slice(0, 4).map(bill => ({
billId: bill.billId,
title: bill.billName || "-"
}));
const descText = billInfoPage[0]?.originDepart || "";
return {
id: item.id,
avatar: "",
name: item.name || "-",
desc: descText,
proposalSize: Number(item.proposalSize || 0),
bills
};
});
committeeTotal.value = res.data.totalElements || 0;
} else {
committeeList.value = [];
committeeTotal.value = 0;
}
} catch (error) {
committeeList.value = [];
if (error.name !== "AbortError") {
committeeList.value = [];
committeeTotal.value = 0;
}
} finally {
loading.value = false;
endAbortableRequest(token);
}
};
......@@ -441,10 +507,12 @@ const handleGetCoopList = async () => {
return {
id: id || Math.random().toString(36).slice(2),
left: {
id: left.id,
name: leftName,
avatar: left.imageUrl || ""
},
right: {
id: right.id,
name: rightName,
avatar: right.imageUrl || ""
},
......@@ -623,6 +691,7 @@ const resetPages = () => {
currentPage.value = 1;
memberCurrentPage.value = 1;
coopCurrentPage.value = 1;
committeeCurrentPage.value = 1;
};
const refreshByActiveTab = () => {
......@@ -679,6 +748,12 @@ const handleInvolveCnChange = val => {
handleGetBills();
};
const handleCommitteeInvolveCnChange = val => {
committeeIsInvolveCn.value = val;
committeeCurrentPage.value = 1;
handleGetCommitteeList();
};
const handleCurrentChange = page => {
currentPage.value = page;
handleGetBills();
......@@ -719,6 +794,12 @@ const handleMemberCurrentChange = page => {
props.onAfterPageChange && props.onAfterPageChange();
};
const handleCommitteeCurrentChange = page => {
committeeCurrentPage.value = page;
handleGetCommitteeList();
props.onAfterPageChange && props.onAfterPageChange();
};
onMounted(() => {
handleGetHylyList();
handleGetPostOrgList();
......@@ -987,6 +1068,7 @@ onMounted(() => {
height: 88px;
border-radius: 50%;
object-fit: cover;
cursor: pointer;
}
.member-icon-row {
......@@ -1156,18 +1238,20 @@ onMounted(() => {
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
cursor: pointer;
}
.coop-member-name {
max-width: 130px;
max-width: none;
color: var(--text-primary-80-color);
font-family: "Source Han Sans CN";
font-size: var(--font-size-base);
font-weight: 700;
line-height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
overflow: visible;
text-overflow: clip;
word-break: break-word;
}
.coop-dot {
......
......@@ -101,7 +101,13 @@
<DivideHeader id="position2" class="divide2" :titleText="'资讯要闻'"></DivideHeader>
<div class="center-center">
<NewsList :list="newsList" />
<NewsList
:newsList="newsList"
img="newsImage"
title="newsTitle"
from="from"
content="newsContent"
/>
<MessageBubble :messageList="messageList" imageUrl="personImage"
@more-click="handleToSocialDetail" @person-click="handleClickToCharacter" name="personName"
content="remarks" source="orgName" />
......@@ -116,9 +122,12 @@
<el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</template>
<div class="box5-main" :style="getEmptyStateStyle(box5HasData)">
<el-empty v-if="!box5HasData" description="暂无数据" :image-size="100" />
<div v-else id="box5Chart" style="width: 100%; height: 100%"></div>
<div class="overview-card-body box5-main">
<div class="overview-chart-wrap" :class="{ 'is-empty': !box5HasData }">
<el-empty v-if="!box5HasData" description="暂无数据" :image-size="100" />
<div v-else id="box5Chart" class="overview-chart"></div>
</div>
<TipTab class="overview-tip" />
</div>
</OverviewCard>
<OverviewCard class="overview-card--single box6" title="涉华法案领域分布" :icon="box6HeaderIcon">
......@@ -127,9 +136,12 @@
<el-option v-for="item in box9YearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box6-main" :style="getEmptyStateStyle(box9HasData)">
<el-empty v-if="!box9HasData" description="暂无数据" :image-size="100" />
<div v-else id="box9Chart" style="width: 100%; height: 100%"></div>
<div class="overview-card-body box6-main">
<div class="overview-chart-wrap" :class="{ 'is-empty': !box9HasData }">
<el-empty v-if="!box9HasData" description="暂无数据" :image-size="100" />
<div v-else id="box9Chart" class="overview-chart"></div>
</div>
<TipTab class="overview-tip" />
</div>
</OverviewCard>
</div>
......@@ -140,9 +152,12 @@
<el-option v-for="item in box7YearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box7-main" :style="getEmptyStateStyle(box7HasData)">
<el-empty v-if="!box7HasData" description="暂无数据" :image-size="100" />
<div v-else id="box7Chart" style="width: 100%; height: 100%"></div>
<div class="overview-card-body box7-main">
<div class="overview-chart-wrap" :class="{ 'is-empty': !box7HasData }">
<el-empty v-if="!box7HasData" description="暂无数据" :image-size="100" />
<div v-else id="box7Chart" class="overview-chart"></div>
</div>
<TipTab class="overview-tip" />
</div>
</OverviewCard>
<OverviewCard class="overview-card--single box8" title="涉华法案进展分布" :icon="box7HeaderIcon">
......@@ -151,16 +166,24 @@
<el-option v-for="item in box8YearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<div class="box8-main" :style="getEmptyStateStyle(box8HasData)">
<el-empty v-if="!box8HasData" description="暂无数据" :image-size="100" />
<template v-else>
<div class="box8-desc">• 通过涉华法案{{ box8Summary }}</div>
<div id="box8Chart" class="box8-chart"></div>
</template>
<div class="overview-card-body box8-main">
<div class="overview-chart-wrap" :class="{ 'is-empty': !box8HasData }">
<el-empty v-if="!box8HasData" description="暂无数据" :image-size="100" />
<template v-else>
<div class="box8-desc">• 通过涉华法案{{ box8Summary }}</div>
<div id="box8Chart" class="overview-chart box8-chart"></div>
</template>
</div>
<TipTab class="overview-tip" />
</div>
</OverviewCard>
<OverviewCard class="overview-card--single box9" title="涉华法案关键条款" :icon="box7HeaderIcon">
<div class="box9-main" id="wordCloudChart"></div>
<div class="overview-card-body box9-main">
<div class="overview-chart-wrap">
<div id="wordCloudChart" class="overview-chart"></div>
</div>
<TipTab class="overview-tip" />
</div>
</OverviewCard>
</div>
</div>
......@@ -196,6 +219,7 @@ import overviewMainBox from "@/components/base/boxBackground/overviewMainBox.vue
import OverviewCard from "./OverviewCard.vue";
import ResourceLibrarySection from "./ResourceLibrarySection.vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import TipTab from "@/components/base/TipTab/index.vue";
import getMultiLineChart from "./utils/multiLineChart";
import getWordCloudChart from "./utils/worldCloudChart";
......@@ -325,12 +349,6 @@ const handleToMoreRiskSignal = () => {
// 风险信号
const warningList = ref([]);
const getEmptyStateStyle = hasData => ({
display: hasData ? "block" : "flex",
justifyContent: "center",
alignItems: "center"
});
const box7selectetedTime = ref("2025");
const box7YearList = ref([
{
......@@ -420,15 +438,15 @@ const handleGetNews = async () => {
try {
const res = await getNews(params);
console.log("新闻资讯", res);
if (res.code === 200) {
newsList.value =
res.data ||
[].map(item => {
return {
...item,
from: `${item.newsOrg} · ${item.newsDate ? item.newsDate.slice(5) : ""}`
};
});
if (res.code === 200 && Array.isArray(res.data)) {
newsList.value = res.data.map(item => {
return {
...item,
from: `${item.newsOrg} · ${item.newsDate ? item.newsDate.slice(5) : ""}`
};
});
} else {
newsList.value = [];
}
} catch (error) { }
};
......@@ -568,17 +586,28 @@ const handleBox7Data = async () => {
await nextTick();
const data1 = [];
const houseTotal = Number(orgBillNumMap?.H || 0);
const senateTotal = Number(orgBillNumMap?.S || 0);
const houseTotal = Number(orgBillNumMap?.House || 0);
const senateTotal = Number(orgBillNumMap?.Senate || 0);
if (houseTotal > 0) data1.push({ name: "众议院", value: houseTotal });
if (senateTotal > 0) data1.push({ name: "参议院", value: senateTotal });
const data2 = orgBillNumList.map(item => ({
name: item.orgName,
value: Number(item.count || 0),
percent: typeof item.percent === "number" ? item.percent : Number(item.percent || 0),
type: item.orgType === "S" ? "参议院" : "众议院"
}));
const getOrgTypeLabel = orgType => (orgType === "Senate" ? "参议院" : "众议院");
const typeOrderMap = { 众议院: 0, 参议院: 1 };
const data2 = orgBillNumList
.map(item => ({
name: item.orgName,
value: Number(item.count || 0),
percent: typeof item.percent === "number" ? item.percent : Number(item.percent || 0),
type: getOrgTypeLabel(item.orgType)
}))
// 关键:外环顺序必须按内环(众→参)分组,否则扇区角度会交错导致“不对应”
.sort((a, b) => {
const t1 = typeOrderMap[a.type] ?? 99;
const t2 = typeOrderMap[b.type] ?? 99;
if (t1 !== t2) return t1 - t2;
return (b.value ?? 0) - (a.value ?? 0);
});
const box7Chart = getDoublePieChart(data1, data2);
setChart(box7Chart, "box7Chart");
......@@ -2051,13 +2080,13 @@ onUnmounted(() => {
.box9-main {
height: 100%;
box-sizing: border-box;
padding: 8px 30px;
padding: 8px 30px 20px;
}
.box8-main {
height: 100%;
box-sizing: border-box;
padding: 12px 30px 18px;
padding: 12px 30px 20px;
.box8-desc {
height: 24px;
......@@ -2069,14 +2098,41 @@ onUnmounted(() => {
}
.box8-chart {
width: 100%;
height: calc(100% - 30px);
cursor: pointer;
}
}
.box9-main {
padding: 10px 30px;
padding: 10px 30px 20px;
}
.overview-card-body {
display: flex;
flex-direction: column;
}
.overview-chart-wrap {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
&.is-empty {
align-items: center;
justify-content: center;
}
}
.overview-chart {
width: 100%;
height: 100%;
min-height: 0;
}
.overview-tip {
margin-top: 10px;
}
}
}
......
......@@ -7,9 +7,22 @@ const truncateLabel = (value, maxLen = 6) => {
return `${chars.slice(0, maxLen).join('')}...`
}
const getCssVar = (varName, fallback) => {
try {
if (typeof window === 'undefined') return fallback
const val = window.getComputedStyle(document.documentElement).getPropertyValue(varName)
return (val || '').trim() || fallback
} catch (e) {
return fallback
}
}
const getDoublePieChart = (data1, data2) => {
const colorList = ['#8AC4FF', '#FFD591']
const colorList1 = ['#055FC2', '#FFA940']
const senateInnerColor = getCssVar('--color-primary-100', '#055FC2')
const senateDeptColor = getCssVar('--color-primary-50', '#89C1FF')
const houseInnerColor = 'rgba(255, 169, 64, 1)'
const houseDeptColor = 'rgba(255, 213, 145, 1)'
const innerLabelColor = getCssVar('--text-primary-80-color', '#3b414b')
let option = {
series: [
{
......@@ -26,14 +39,16 @@ const getDoublePieChart = (data1, data2) => {
position: 'inside',
fontSize: '16px',
fontWeight: 700,
// color: '#333'
color: innerLabelColor,
textBorderColor: '#fff',
textBorderWidth: 1
},
data: data1.map(item => {
return {
name: item.name,
value: item.value,
itemStyle: {
color: item.name === '参议院' ? '#055FC2' : '#FFA940'
color: item.name === '参议院' ? senateInnerColor : houseInnerColor
}
}
})
......@@ -70,7 +85,7 @@ const getDoublePieChart = (data1, data2) => {
time: {
fontSize: 14,
fontFamily: 'Microsoft YaHei',
color: '#rgba(95, 101, 108, 1)',
color: 'rgba(95, 101, 108, 1)',
padding: [10,0,10,0]
}
}
......@@ -97,7 +112,7 @@ const getDoublePieChart = (data1, data2) => {
value: item.value,
percent: item.percent,
itemStyle: {
color: item.type === '参议院' ? '#8AC4FF' : '#FFD591'
color: item.type === '参议院' ? senateDeptColor : houseDeptColor
}
}
......
<template>
<div class="header-main">
<div class="layout-main-header">
<div class="layout-main-header-left-box">
<div class="left-box-top">
<el-skeleton :loading="isLoading" animated>
<template #template>
<div class="icon">
<el-skeleton-item class="skeleton-avatar" variant="image" />
</div>
<div class="info">
<el-skeleton-item class="skeleton-title" variant="h1" />
<el-skeleton-item class="skeleton-subtitle" variant="text" />
</div>
</template>
<template #default>
<div class="icon">
<img :src="billInfo?.imageUrl || defaultLogo" alt="" />
</div>
<div class="info">
<div class="info-box1">{{ billInfo?.billName }}</div>
<div class="info-box2">{{ billInfo?.description }} {{ billInfo?.billNameEn }}</div>
</div>
</template>
</el-skeleton>
</div>
<div class="left-box-bottom" v-if="showTabs">
<template v-if="isLoading">
<div class="left-box-bottom-item is-skeleton" v-for="n in 4" :key="n">
<div class="icon">
<el-skeleton-item class="skeleton-tab-icon" variant="text" />
</div>
<div class="name">
<el-skeleton-item class="skeleton-tab-text" variant="text" />
</div>
</div>
</template>
<template v-else>
<div
class="left-box-bottom-item"
:class="{ leftBoxBottomItemActive: activeTitle === item.name }"
v-for="item in tabs"
:key="item.path"
@click="emit('tab-click', item)"
>
<div class="icon">
<img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<img v-else :src="item.icon" alt="" />
</div>
<div class="name" :class="{ nameActive: activeTitle === item.name }">
{{ item.name }}
</div>
</div>
</template>
</div>
</div>
<div class="layout-main-header-right-box">
<div class="right-box-top">
<el-skeleton :loading="isLoading" animated>
<template #template>
<div class="time">
<el-skeleton-item class="skeleton-right-line" variant="text" />
</div>
<div class="name">
<el-skeleton-item class="skeleton-right-line" variant="text" />
</div>
</template>
<template #default>
<div class="time">{{ billInfo?.introductionDate }}</div>
<div class="name">{{ billInfo?.tarName }}</div>
</template>
</el-skeleton>
</div>
<div class="right-box-bottom" v-if="showActions">
<template v-if="isLoading">
<div class="btn1 is-skeleton">
<div class="icon">
<el-skeleton-item class="skeleton-action-icon" variant="text" />
</div>
<div class="text">
<el-skeleton-item class="skeleton-action-text" variant="text" />
</div>
</div>
<div class="btn3 is-skeleton">
<div class="icon">
<el-skeleton-item class="skeleton-action-icon" variant="text" />
</div>
<div class="text">
<el-skeleton-item class="skeleton-action-text" variant="text" />
</div>
</div>
</template>
<template v-else>
<div class="btn1" @click="emit('open-original-text')">
<div class="icon">
<img :src="btnIconOriginalText" alt="" />
</div>
<div class="text">{{ "法案原文" }}</div>
</div>
<div class="btn3" @click="emit('open-analysis')">
<div class="icon">
<img :src="btnIconAnalysis" alt="" />
</div>
<div class="text">{{ "分析报告" }}</div>
</div>
</template>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from "vue";
import btnIconOriginalText from "@/views/thinkTank/ReportDetail/images/btn-icon1.png";
import btnIconAnalysis from "@/views/thinkTank/ReportDetail/images/btn-icon3.png";
const props = defineProps({
billInfo: {
type: Object,
default: () => ({})
},
defaultLogo: {
type: String,
default: ""
},
tabs: {
type: Array,
default: () => []
},
activeTitle: {
type: String,
default: ""
},
showTabs: {
type: Boolean,
default: true
},
showActions: {
type: Boolean,
default: true
}
});
const isLoading = computed(() => !props.billInfo || !props.billInfo.billName);
const emit = defineEmits(["tab-click", "open-original-text", "open-analysis"]);
</script>
<style lang="scss" scoped>
.skeleton-avatar {
width: 64px;
height: 64px;
border-radius: 50%;
}
.skeleton-title {
width: 360px;
height: 20px;
}
.skeleton-subtitle {
width: 520px;
height: 14px;
margin-top: 8px;
}
.skeleton-right-line {
width: 140px;
height: 14px;
}
.skeleton-tab-icon,
.skeleton-action-icon {
width: 16px;
height: 16px;
border-radius: 4px;
}
.skeleton-tab-text,
.skeleton-action-text {
width: 80px;
height: 14px;
border-radius: 6px;
}
.header-main {
position: sticky;
top: 0;
z-index: 1000;
width: 100%;
background-color: #fff;
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.layout-main-header {
width: 1600px;
background: #fff;
display: flex;
justify-content: space-between;
margin: 0 auto;
padding-top: 10px;
.layout-main-header-left-box {
width: 2600px;
.left-box-top {
height: 64px;
display: flex;
align-items: center;
justify-content: left;
margin-bottom: 21px;
:deep(.el-skeleton) {
width: 100%;
display: flex;
align-items: center;
}
.icon {
width: 64px;
height: 64px;
flex: 0 0 64px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
display: block;
object-fit: contain;
}
}
.info {
margin-left: 8px;
display: flex;
align-items: left;
flex-direction: column;
.info-box1 {
color: rgba(59, 65, 75, 1);
font-family: "Microsoft YaHei";
font-size: 20px;
font-weight: 600;
letter-spacing: 0px;
text-align: left;
}
.info-box2 {
margin-top: 4px;
color: rgba(132, 136, 142, 1);
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
letter-spacing: 0px;
text-align: left;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.left-box-bottom {
display: flex;
height: 40px;
// margin-top: 21px;
.left-box-bottom-item {
display: flex;
margin-right: 32px;
margin-top: 3px;
height: 36px;
cursor: pointer;
.icon {
margin-top: 4px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.name {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
margin-left: 3px;
}
.nameActive {
color: rgba(20, 89, 187, 1);
font-weight: 700;
}
}
.leftBoxBottomItemActive {
border-bottom: 3px solid rgba(20, 89, 187, 1);
}
}
}
.layout-main-header-right-box {
width: 600px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: right;
.right-box-top {
height: 64px;
display: flex;
align-items: right;
flex-direction: column;
justify-content: center;
:deep(.el-skeleton) {
width: 100%;
}
.time {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: right;
}
.name {
margin-top: 4px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: right;
}
}
.right-box-bottom {
margin-bottom: 8px;
display: flex;
justify-content: flex-end;
gap: 8px;
.btn1 {
cursor: pointer;
width: 120px;
height: 36px;
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;
gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
.btn3 {
cursor: pointer;
width: 120px;
height: 36px;
border-radius: 6px;
background: rgba(5, 95, 194, 1);
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
.is-skeleton {
pointer-events: none;
}
}
}
}
</style>
......@@ -2,141 +2,34 @@
<div class="layout-container">
<!-- 导航菜单 -->
<div class="layout-main">
<div class="header-main">
<div class="layout-main-header">
<div class="layout-main-header-left-box">
<div class="left-box-top">
<div class="icon">
<img :src="billInfoGlobal.imageUrl || USALogo" alt="" />
</div>
<div class="info">
<div class="info-box1">{{ billInfoGlobal.billName }}</div>
<div class="info-box2">{{ billInfoGlobal.description }} {{ billInfoGlobal.billNameEn }}</div>
</div>
</div>
<div class="left-box-bottom">
<div
class="left-box-bottom-item"
:class="{ leftBoxBottomItemActive: activeTitle === item.name }"
v-for="item in mainHeaderBtnList"
:key="item.path"
@click="handleClickMainHeaderBtn(item)"
>
<div class="icon">
<img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<img v-else :src="item.icon" alt="" />
</div>
<div class="name" :class="{ nameActive: activeTitle === item.name }">
{{ item.name }}
</div>
</div>
</div>
</div>
<div class="layout-main-header-right-box">
<div class="right-box-top">
<div class="time">{{ billInfoGlobal.introductionDate }}</div>
<div class="name">{{ billInfoGlobal.tarName }}</div>
</div>
<div class="right-box-bottom">
<div class="btn1" @click="handleSwitchActiveName('法案原文')">
<div class="icon">
<img src="./assets/icons/btn-icon1.png" alt="" />
</div>
<div class="text">{{ "法案原文" }}</div>
</div>
<!-- <div class="btn2">
<div class="icon">
<img src="./assets/icons/btn-icon2.png" alt="" />
</div>
<div class="text">{{ "查看官网" }}</div>
</div> -->
<div class="btn3" @click="handleAnalysisClick">
<div class="icon">
<img src="./assets/icons/btn-icon3.png" alt="" />
</div>
<div class="text">{{ "分析报告" }}</div>
</div>
<!-- <div class="btn4">
<div class="icon">
<img src="./assets/icons/btn-icon4.png" alt="" />
</div>
<div class="text">{{ "特别重大风险" }}</div>
<div class="icon1">
<img src="./assets/icons/btn-icon5.png" alt="" />
</div>
</div> -->
</div>
</div>
</div>
</div>
<BillHeader
:billInfo="billInfoGlobal"
:defaultLogo="USALogo"
:tabs="mainHeaderBtnList"
:activeTitle="activeTitle"
:showTabs="!isBillOriginalTextPage"
:showActions="!isBillOriginalTextPage"
@tab-click="handleClickMainHeaderBtn"
@open-original-text="handleOpenBillOriginalText"
@open-analysis="handleAnalysisClick"
/>
<div class="layout-main-center">
<router-view />
</div>
</div>
<div class="layout-report-box" v-if="activeName === '法案原文'">
<div class="report-main">
<div class="report-close" @click="handleSwitchActiveName('分析报告')">
<img src="./assets/images/report-close-icon.png" alt="" />
</div>
<div class="report-header">
<div class="report-header-left">
<!-- <div class="text">法案版本:</div>
<div class="select-box">
<el-select v-model="curBill" placeholder="选择法案" style="width: 240px">
<el-option v-for="item in billList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div> -->
</div>
<div class="report-header-right">
<!-- <div class="btn">
<div class="icon">
<img src="./assets/images/report-header-icon1.png" alt="" />
</div>
<div class="text">翻译</div>
</div> -->
<!-- <div class="btn">
<div class="icon">
<img src="./assets/images/report-header-icon2.png" alt="" />
</div>
<div class="text">下载</div>
</div>
<div class="btn">
<div class="icon">
<img src="./assets/images/report-header-icon3.png" alt="" />
</div>
<div class="text">对比</div>
</div>
<div class="btn">
<div class="icon">
<img src="./assets/images/report-header-icon4.png" alt="" />
</div>
<div class="text">查找</div>
</div> -->
</div>
</div>
<div class="report-content">
<div class="content-left">
<iframe v-if="billFullText" :src="billFullText" width="100%" height="100%" frameborder="0"></iframe>
<!-- <img v-else src="./assets/images/report1.png" alt="" /> -->
</div>
<!-- <div class="content-right">
<img src="./assets/images/report2.png" alt="" />
</div> -->
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { ref, onMounted, computed, watch } from "vue";
import router from "@/router";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { getBillInfoGlobal, getBillFullText } from "@/api/bill";
import { getBillInfoGlobal } from "@/api/bill";
import BillHeader from "./components/BillHeader.vue";
const route = useRoute();
const isBillOriginalTextPage = computed(() => route.path === "/billLayout/originalText");
import icon1 from "./assets/icons/icon1.svg";
import icon1Active from "./assets/icons/icon1_active.svg";
......@@ -148,22 +41,6 @@ import icon4 from "./assets/icons/icon4.svg";
import icon4Active from "./assets/icons/icon4_active.svg";
import USALogo from "./assets/images/USA-logo.png";
// 法案原文
const billFullText = ref("");
const getBillFullTextFn = async () => {
const res = await getBillFullText({
id: route.query.billId
});
if (res.code === 200) {
console.log("法案全文", res);
if (res.data) {
billFullText.value = typeof res.data === "string" ? res.data.trim() : res.data;
}
}
};
const activeName = ref("分析报告");
// 获取法案全局信息
const billInfoGlobal = ref({});
const getBillInfoGlobalFn = async () => {
......@@ -178,26 +55,16 @@ const getBillInfoGlobalFn = async () => {
}
};
const handleSwitchActiveName = name => {
activeName.value = name;
if (name === "法案原文") {
getBillFullTextFn();
}
const handleOpenBillOriginalText = () => {
const targetRoute = router.resolve({
path: "/billLayout/originalText",
query: {
billId: route.query.billId
}
});
window.open(targetRoute.href, "_blank");
};
const curBill = ref("公法(2025年7月4日)");
const billList = ref([
{
label: "公法(2025年7月4日)",
value: "公法(2025年7月4日)"
},
{
label: "公法(2025年7月2日)",
value: "公法(2025年7月2日)"
}
]);
const mainHeaderBtnList = ref([
{
icon: icon1,
......@@ -227,6 +94,18 @@ const mainHeaderBtnList = ref([
const activeTitle = ref("法案概况");
const getActiveTitleByRoutePath = path => {
if (path.startsWith("/billLayout/deepDig")) return "深度挖掘";
if (path.startsWith("/billLayout/influence")) return "影响分析";
if (path.startsWith("/billLayout/relevantCircumstance")) return "相关情况";
// /billLayout/bill 及其子路由(introduction/background/template)都归为“法案概况”
return "法案概况";
};
const syncActiveTitleFromRoute = () => {
activeTitle.value = getActiveTitleByRoutePath(route.path);
};
const handleClickMainHeaderBtn = item => {
// if (["影响分析", "相关情况"].includes(item.name)) {
// ElMessage.warning("当前功能正在开发中,敬请期待!");
......@@ -254,10 +133,20 @@ const handleAnalysisClick = () => {
onMounted(() => {
getBillInfoGlobalFn();
if (window.sessionStorage.getItem("activeTitle")) {
activeTitle.value = window.sessionStorage.getItem("activeTitle");
}
// 以当前路由为准,避免 sessionStorage 造成高亮错乱
syncActiveTitleFromRoute();
// 兜底:如果未来出现未知路由且有缓存,再用缓存
const cachedTitle = window.sessionStorage.getItem("activeTitle");
if (!activeTitle.value && cachedTitle) activeTitle.value = cachedTitle;
});
watch(
() => route.path,
() => {
syncActiveTitleFromRoute();
},
{ immediate: true }
);
</script>
<style lang="scss" scoped>
......@@ -271,285 +160,6 @@ onMounted(() => {
width: 100%;
height: calc(100vh - 72px);
overflow-y: auto;
.header-main {
position: sticky;
top: 0;
z-index: 1000;
width: 100%;
background-color: #fff;
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.layout-main-header {
width: 1600px;
background: #fff;
display: flex;
justify-content: space-between;
margin: 0 auto;
padding-top: 10px;
.layout-main-header-left-box {
width: 2600px;
.left-box-top {
height: 64px;
display: flex;
align-items: center;
justify-content: left;
.icon {
width: 64px;
height: 64px;
}
.info {
margin-left: 8px;
display: flex;
align-items: left;
flex-direction: column;
.info-box1 {
color: rgba(59, 65, 75, 1);
font-family: "Microsoft YaHei";
font-size: 20px;
font-weight: 600;
// line-height: 22px;
letter-spacing: 0px;
text-align: left;
// margin-top: 5px;
}
.info-box2 {
margin-top: 4px;
// height: 22px;
// line-height: 22px;
color: rgba(132, 136, 142, 1);
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
// line-height: 22px;
letter-spacing: 0px;
text-align: left;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.left-box-bottom {
display: flex;
height: 40px;
margin-top: 21px;
.left-box-bottom-item {
display: flex;
margin-right: 32px;
margin-top: 3px;
height: 36px;
cursor: pointer;
// &:hover{
// .name {
// color: rgba(22, 119, 255, 1);
// }
// }
.icon {
margin-top: 4px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.name {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
margin-left: 3px;
}
.nameActive {
color: rgba(20, 89, 187, 1);
font-weight: 700;
}
}
.leftBoxBottomItemActive {
border-bottom: 3px solid rgba(20, 89, 187, 1);
}
}
}
.layout-main-header-right-box {
width: 600px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: right;
// margin-right: 150px;
// margin-top: 19px;
.right-box-top {
height: 64px;
display: flex;
align-items: right;
flex-direction: column;
justify-content: center;
.time {
// height: 24px;
// line-height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: right;
}
.name {
// height: 24px;
// line-height: 24px;
margin-top: 4px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: right;
}
}
.right-box-bottom {
margin-bottom: 8px;
display: flex;
justify-content: flex-end;
gap: 8px;
.btn1 {
cursor: pointer;
width: 120px;
height: 36px;
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;
gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
.btn2 {
width: 120px;
height: 36px;
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;
gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
.btn3 {
cursor: pointer;
width: 120px;
height: 36px;
border-radius: 6px;
background: rgba(5, 95, 194, 1);
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
.btn4 {
width: 180px;
height: 36px;
box-sizing: border-box;
border: 1px solid rgba(255, 163, 158, 1);
border-radius: 4px;
background: rgba(255, 241, 240, 1);
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
.icon {
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
}
.icon1 {
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
}
.layout-main-center {
// height: calc(100% - 137px);
width: 1600px;
......@@ -557,121 +167,5 @@ onMounted(() => {
padding-bottom: 20px;
}
}
.layout-report-box {
position: absolute;
z-index: 9999;
top: 154px;
left: 0;
width: 100%;
height: 926px;
background: rgba(248, 249, 250, 1);
.report-main {
position: relative;
width: 1744px;
height: 926px;
margin: 0 auto;
background: #fff;
box-sizing: border-box;
padding: 0 69px;
// border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.report-close {
position: absolute;
top: 24px;
right: 24px;
width: 32px;
height: 32px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.report-header {
height: 77px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
.report-header-left {
display: flex;
.text {
margin-top: 32px;
width: 70px;
height: 14px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 14px;
}
.select-box {
margin-left: 8px;
margin-top: 23px;
}
}
.report-header-right {
display: flex;
margin-top: 24px;
.btn {
display: flex;
width: 88px;
height: 32px;
margin-left: 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
display: flex;
justify-content: center;
align-items: center;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
height: 32px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 32px;
}
}
}
}
.report-content {
width: 100%;
display: flex;
margin-top: 35px;
.content-left {
// width: 680px;
width: 100%;
height: 786px;
// background: #eee;
// overflow-y: auto;
img {
width: 100%;
height: 100%;
}
}
.content-right {
margin-left: 89px;
width: 680px;
height: 786px;
// background: #eee;
// overflow-y: auto;
img {
width: 100%;
height: 100%;
}
}
}
}
}
}
</style>
\ No newline at end of file
<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>
</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("");
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 handleBack = () => {
router.back();
};
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;
}
}
}
</style>
......@@ -2,20 +2,7 @@
<div class="home-container">
<div class="home-center">
<div class="home-sider">
<div
class="sider-btn"
:class="{ siderBtnActive: siderBtnActive === item.name }"
@click="handleClickLeftSiderBtn(item)"
v-for="item,index in siderBtnList"
:key="index"
>
<div class="btn-text">{{item.name}}</div>
<div class="btn-icon">
<el-icon v-if="siderBtnActive === item.name" color="#fff"
><CaretRight
/></el-icon>
</div>
</div>
<SiderTabs :siderList="siderTabs" @clickSiderItem="handleClickLeftSiderBtn" />
</div>
<div class="home-main">
<router-view />
......@@ -25,9 +12,10 @@
</template>
<script setup>
import { onMounted, ref } from "vue";
import { onMounted, ref, computed, watch } from "vue";
import { useRoute } from "vue-router";
import router from '@/router'
import SiderTabs from "@/components/base/SiderTabs/index.vue";
const route = useRoute();
......@@ -47,6 +35,20 @@ const siderBtnList = ref([
])
const siderBtnActive = ref("法案简介");
const getSiderActiveByRoutePath = path => {
if (path.includes("/billLayout/bill/background")) return "法案背景";
if (path.includes("/billLayout/bill/template")) return "内容概要";
return "法案简介";
};
const siderTabs = computed(() =>
siderBtnList.value.map(item => ({
...item,
active: siderBtnActive.value === item.name
}))
);
const handleClickLeftSiderBtn = (item) => {
siderBtnActive.value = item.name
router.push({
......@@ -58,6 +60,14 @@ const handleClickLeftSiderBtn = (item) => {
}
onMounted(() => {});
watch(
() => route.path,
(newPath) => {
siderBtnActive.value = getSiderActiveByRoutePath(newPath);
},
{ immediate: true }
);
</script>
<style lang="scss" scoped>
......@@ -168,10 +178,10 @@ onMounted(() => {});
height: 879px;
position: relative;
.home-sider {
width: 160px;
width: 120px;
position: absolute;
top: 8px;
left: -160px;
top: 16px;
left: -140px;
.sider-btn {
margin-top: 20px;
margin-left: 20px;
......
......@@ -17,7 +17,7 @@
<div class="item-title">{{ item.actionTitle }}</div>
</el-tooltip>
<div class="right">
<div v-if="item.riskText" class="risk-tag" :class="item.riskClass">{{ item.riskText }}</div>
<!-- <div v-if="item.riskText" class="risk-tag" :class="item.riskClass">{{ item.riskText }}</div> -->
<div class="arrow">></div>
</div>
</div>
......
<svg viewBox="0 0 12.375 13.5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.375000" height="13.500000" fill="none" customFrame="#000000">
<path id="矢量 359" d="M10.125 1L10.125 5.0625L11.375 5.0625C11.9273 5.0625 12.375 5.51022 12.375 6.0625L12.375 12.5C12.375 13.0523 11.9273 13.5 11.375 13.5L3.8125 13.5C3.26022 13.5 2.8125 13.0523 2.8125 12.5L2.8125 11.25L1 11.25C0.447715 11.25 0 10.8023 0 10.25L0 1C0 0.447715 0.447715 0 1 0L9.125 0C9.67728 0 10.125 0.447715 10.125 1ZM11.25 6.1875L10.125 6.1875L10.125 10.25C10.125 10.8023 9.67729 11.25 9.125 11.25L3.9375 11.25L3.9375 12.375L11.25 12.375L11.25 6.1875ZM9 1.125L1.125 1.125L1.125 10.125L9 10.125L9 1.125ZM6.1875 7.3125L6.1875 8.4375L2.8125 8.4375L2.8125 7.3125L6.1875 7.3125ZM7.3125 5.0625L7.3125 6.1875L2.8125 6.1875L2.8125 5.0625L7.3125 5.0625ZM7.3125 2.8125L7.3125 3.9375L2.8125 3.9375L2.8125 2.8125L7.3125 2.8125Z" fill="rgb(5,95,194)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none">
<rect id="翻译 1" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="矢量 455" d="M3.33398 10.0007L3.33398 11.334C3.33397 11.3756 3.3359 11.4171 3.33977 11.4585C3.34364 11.4999 3.34943 11.541 3.35715 11.5819C3.36487 11.6227 3.37448 11.6632 3.38598 11.7031C3.39748 11.7431 3.41082 11.7824 3.426 11.8211C3.44119 11.8599 3.45814 11.8978 3.47687 11.9349C3.4956 11.9721 3.51602 12.0082 3.53814 12.0434C3.56026 12.0787 3.58397 12.1128 3.60927 12.1458C3.63458 12.1788 3.66137 12.2105 3.68965 12.241C3.71793 12.2715 3.74757 12.3006 3.77857 12.3283C3.80957 12.3561 3.8418 12.3823 3.87525 12.407C3.9087 12.4317 3.94324 12.4548 3.97885 12.4762C4.01447 12.4977 4.051 12.5175 4.08847 12.5355C4.12593 12.5536 4.16416 12.5699 4.20314 12.5843C4.24213 12.5988 4.2817 12.6114 4.32187 12.6222C4.36204 12.633 4.40262 12.6419 4.44362 12.6488C4.48461 12.6558 4.52585 12.6609 4.56732 12.664L4.66732 12.6673L6.66732 12.6673L6.66732 14.0007L4.66732 14.0007C4.57998 14.0007 4.49285 13.9964 4.40594 13.9878C4.31902 13.9792 4.23273 13.9665 4.14708 13.9494C4.06142 13.9324 3.9768 13.9112 3.89322 13.8858C3.80965 13.8605 3.72752 13.8311 3.64683 13.7977C3.56614 13.7642 3.48728 13.7269 3.41026 13.6858C3.33324 13.6446 3.25841 13.5998 3.1858 13.5512C3.11318 13.5027 3.04311 13.4508 2.9756 13.3953C2.90809 13.3399 2.84346 13.2814 2.7817 13.2196C2.71994 13.1578 2.66136 13.0932 2.60596 13.0257C2.55055 12.9582 2.49859 12.8881 2.45007 12.8155C2.40154 12.7429 2.3567 12.6681 2.31553 12.591C2.27436 12.514 2.23706 12.4352 2.20364 12.3545C2.17022 12.2738 2.14083 12.1917 2.11548 12.1081C2.09012 12.0245 2.06893 11.9399 2.05189 11.8542C2.03485 11.7686 2.02205 11.6823 2.01349 11.5954C2.00493 11.5084 2.00065 11.4213 2.00065 11.334L2.00065 10.0007L3.33398 10.0007L3.33398 10.0007ZM12.0007 6.66732L14.934 14.0007L13.4973 14.0007L12.6967 12.0007L9.96999 12.0007L9.17065 14.0007L7.73465 14.0007L10.6673 6.66732L12.0007 6.66732L12.0007 6.66732ZM11.334 8.59065L10.5027 10.6673L12.164 10.6673L11.334 8.59065ZM5.33398 1.33398L5.33398 2.66732L8.00065 2.66732L8.00065 7.33398L5.33398 7.33398L5.33398 9.33398L4.00065 9.33398L4.00065 7.33398L1.33398 7.33398L1.33398 2.66732L4.00065 2.66732L4.00065 1.33398L5.33398 1.33398ZM11.334 2.00065C11.4213 2.00065 11.5084 2.00493 11.5954 2.01349C11.6823 2.02205 11.7686 2.03485 11.8542 2.05189C11.9399 2.06893 12.0245 2.09012 12.1081 2.11548C12.1917 2.14083 12.2738 2.17022 12.3545 2.20364C12.4352 2.23706 12.514 2.27436 12.591 2.31553C12.6681 2.3567 12.7429 2.40154 12.8155 2.45007C12.8881 2.49859 12.9582 2.55055 13.0257 2.60596C13.0932 2.66136 13.1578 2.71994 13.2196 2.7817C13.2814 2.84346 13.3399 2.90809 13.3953 2.9756C13.4508 3.04311 13.5027 3.11318 13.5512 3.1858C13.5998 3.25841 13.6446 3.33324 13.6858 3.41026C13.7269 3.48728 13.7642 3.56614 13.7977 3.64683C13.8311 3.72752 13.8605 3.80965 13.8858 3.89323C13.9112 3.9768 13.9324 4.06142 13.9494 4.14708C13.9665 4.23274 13.9793 4.31902 13.9878 4.40594C13.9964 4.49286 14.0007 4.57998 14.0007 4.66732L14.0007 6.00065L12.6673 6.00065L12.6673 4.66732C12.6673 4.62365 12.6652 4.58009 12.6609 4.53663C12.6566 4.49317 12.6502 4.45003 12.6417 4.4072C12.6332 4.36437 12.6226 4.32206 12.6099 4.28027C12.5972 4.23848 12.5825 4.19742 12.5658 4.15707C12.5491 4.11673 12.5305 4.0773 12.5099 4.03879C12.4893 4.00028 12.4669 3.96287 12.4426 3.92656C12.4184 3.89025 12.3924 3.85522 12.3647 3.82146C12.337 3.7877 12.3077 3.75539 12.2768 3.72451C12.2459 3.69363 12.2136 3.66434 12.1798 3.63664C12.1461 3.60893 12.1111 3.58295 12.0747 3.55869C12.0384 3.53443 12.001 3.51201 11.9625 3.49142C11.924 3.47084 11.8846 3.45219 11.8442 3.43548C11.8039 3.41877 11.7628 3.40407 11.721 3.3914C11.6792 3.37872 11.6369 3.36812 11.5941 3.3596C11.5513 3.35108 11.5081 3.34468 11.4647 3.3404C11.4212 3.33612 11.3777 3.33398 11.334 3.33398L9.33399 3.33398L9.33399 2.00065L11.334 2.00065L11.334 2.00065ZM4.00065 4.00065L2.66732 4.00065L2.66732 6.00065L4.00065 6.00065L4.00065 4.00065ZM6.66732 4.00065L5.33398 4.00065L5.33398 6.00065L6.66732 6.00065L6.66732 4.00065Z" fill="rgb(95,101,108)" fill-rule="nonzero" />
</svg>
<template>
<div class="temp-wrap">
<div class="side">
<div class="side-box side-box-domain">
<AnalysisBox title="涉及领域" width="520px" height="415px">
<div :class="['right-box2-main', { 'right-box-main--full': !domainFooterText }]" id="chart2"></div>
<div v-if="domainFooterText" class="right-box2-footer">
<div class="right-box2-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box2-footer-center">
{{ domainFooterText }}
</div>
<div class="right-box2-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div>
</AnalysisBox>
</div>
<div class="side-box side-box-limit">
<AnalysisBox title="限制手段" width="520px" height="415px">
<div :class="['right-box1-main', { 'right-box-main--full': !limitFooterText }]" id="chart1"></div>
<div v-if="limitFooterText" class="right-box1-footer">
<div class="right-box1-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box1-footer-center">
{{ limitFooterText }}
</div>
<div class="right-box1-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="terms">
<!-- <div class="box-header">
<div class="box-header-left"></div>
<div class="box-header-title">主要条款</div>
<div class="box-header-btn-box"></div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="left-top">
<el-select v-model="curBill" placeholder="请选择" @change="handleChangeBill">
<el-option v-for="item in billList" :key="item.id" :label="item.label" :value="item.value" />
<div class="tools-row">
<div class="tools-row-left">
<el-select v-model="curBill" placeholder="请选择版本" @change="handleChangeBill"
class="tools-row-control tools-row-control--primary tools-row-control--bill">
<el-option v-for="item in billList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-checkbox
style="margin-left: 30px"
v-model="checkedValue"
label="只看涉华条款"
size="large"
@change="handleChangeCheckbox"
/>
<div class="search" style="width: 240px; margin-left: 475px">
<el-input v-model="searchValue" placeholder="搜索条款" :suffix-icon="Search" clearable/>
<el-button class="tools-row-control tools-row-control--primary tools-row-compare-btn"
@click="handleOpenVersionCompare">
<img class="tools-row-compare-icon" src="./assets/icons/compare-icon.svg" alt="" />
<span>版本对比</span>
</el-button>
</div>
<div class="tools-row-right">
<el-input v-model="searchValue" class="tools-row-control--search" placeholder="搜索条款" clearable
@keyup.enter="handleSearchSubmit">
<template #suffix>
<el-icon class="tools-row-search-icon" @click="handleSearchSubmit">
<Search />
</el-icon>
</template>
</el-input>
<div class="tools-row-right-actions">
<el-checkbox class="tools-row-control" v-model="checkedValue" label="只看涉华条款" size="large"
@change="handleChangeCheckbox" />
<el-select v-model="selectedDomain" class="tools-row-control tools-row-control--domain"
placeholder="全部领域">
<el-option label="全部领域" value="" />
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
<el-select v-model="selectedLimit" class="tools-row-control tools-row-control--limit"
placeholder="全部限制手段">
<el-option label="全部限制手段" value="" />
<el-option v-for="item in limitOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
<div class="left-main">
<div class="left-main-item" v-for="(term, index) in mainTermsList" :key="index">
<div class="id">{{ (currentPage - 1) * pageSize + index + 1 }}</div>
<div class="info">
<div class="title">
<span class="title-active">{{ term.tkxh }}条.</span>
{{ term.fynr }}
</div>
<div class="content">
<span class="content-active">Sec.{{ term.tkxh }}</span>
{{ term.ywnr }}
</div>
<div class="content-row">
<div class="side">
<div class="side-box side-box-domain">
<AnalysisBox title="涉及领域" width="520px" height="415px" v-loading="domainLoading">
<div :class="['right-box2-main', { 'right-box-main--full': !domainFooterText }]" id="chart2"></div>
<div v-if="domainFooterText" class="right-box2-footer">
<div class="right-box2-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box2-footer-center">
{{ domainFooterText }}
</div>
<div class="right-box2-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div>
</div>
<div class="tags-box">
<div
class="tag"
v-for="(val, idx) in (term.hylyList || []).slice(0, 2)"
:key="idx"
:class="{
tag1: val === '人工智能',
tag2: val === '新一代信息技术' || !['人工智能', '政治', '经济', '军事', '科技'].includes(val),
tag3: val === '政治',
tag4: val === '经济',
tag5: val === '军事',
tag6: val === '科技'
}"
>
{{ val }}
</AnalysisBox>
</div>
<div class="side-box side-box-limit">
<AnalysisBox title="限制手段" width="520px" height="415px" v-loading="limitLoading">
<div :class="['right-box1-main', { 'right-box-main--full': !limitFooterText }]" id="chart1"></div>
<div v-if="limitFooterText" class="right-box1-footer">
<div class="right-box1-footer-left">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="right-box1-footer-center">
{{ limitFooterText }}
</div>
<div class="right-box1-footer-right">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="left-footer">
<div class="left-footer-text">
{{ `共 ${total} 项` }}
</div>
<div class="left-footer-right">
<el-pagination
background
layout="prev, pager, next"
:total="total"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
@current-change="handleCurrentChange"
<div class="terms">
<div class="terms-switch">
<span class="terms-switch-label">高亮实体</span>
<el-switch
v-model="termsHighlight"
inline-prompt
active-text="开"
inactive-text="关"
/>
<span class="terms-switch-divider"></span>
<span class="terms-switch-label">显示原文</span>
<el-switch
v-model="termsShowOriginal"
inline-prompt
active-text="开"
inactive-text="关"
/>
</div>
</div> -->
<AnalysisBox title="主要条款" :showAllBtn="false">
<div class="left-top">
<el-select v-model="curBill" placeholder="请选择" @change="handleChangeBill">
<el-option v-for="item in billList" :key="item.id" :label="item.label" :value="item.value" />
</el-select>
<el-checkbox style="margin-left: 30px" v-model="checkedValue" label="只看涉华条款" size="large"
@change="handleChangeCheckbox" />
<div class="search" style="width: 240px; margin-left: 475px">
<el-input v-model="searchValue" placeholder="搜索条款" :suffix-icon="Search" clearable />
<!-- <div class="icon"> -->
<!-- <img src="./assets/icons/search-icon.png" alt="" /> -->
<!-- </div> -->
</div>
</div>
<div class="left-main">
<div class="left-main-item" v-for="(term, index) in mainTermsList" :key="getTermKey(term, index)">
<div class="id">{{ (currentPage - 1) * pageSize + index + 1 }}</div>
<div class="info">
<div class="title">
<span class="title-active">{{ term.tkxh }}.</span>
{{ term.fynr }}
</div>
<div class="content">
<span class="content-active">Sec.{{ term.tkxh }}</span>
{{ term.ywnr }}
<AnalysisBox title="主要条款" :showAllBtn="false" v-loading="termsLoading">
<div class="left-main">
<div class="left-main-item" v-for="(term, index) in displayTermsList" :key="getTermKey(term, index)">
<div class="term-body">
<div class="term-index">{{ getTermSerial(index) }}</div>
<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>
<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>
</div>
</div>
<!-- <div class="open">
<img src="./assets/icons/open-icon.png" alt="" />
</div> -->
</div>
<div class="tags-box">
<div class="tag" v-for="(val, idx) in (term.hylyList || []).slice(0, 2)" :key="getTagKey(val, idx)" :class="{
tag1: val === '人工智能',
tag2: val === '新一代信息技术' || !['人工智能', '政治', '经济', '军事', '科技'].includes(val),
tag3: val === '政治',
tag4: val === '经济',
tag5: val === '军事',
tag6: val === '科技'
}">
{{ val }}
</div>
</div>
<!-- <div class="open">
<img src="./assets/icons/open-icon.png" alt="" />
</div> -->
</div>
</div>
<div class="left-footer">
<div class="left-footer-text">
{{ `共 ${total} 项` }}
</div>
<div class="left-footer-right">
<el-pagination background layout="prev, pager, next" :total="total" v-model:current-page="currentPage"
v-model:page-size="pageSize" @current-change="handleCurrentChange" />
<div class="left-footer">
<div class="left-footer-text">
{{ `共 ${total} 项` }}
</div>
<div class="left-footer-right">
<el-pagination background layout="prev, pager, next" :total="total"
v-model:current-page="currentPage" v-model:page-size="pageSize"
@current-change="handleCurrentChange" />
</div>
</div>
</div>
</AnalysisBox>
</AnalysisBox>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { ref, onMounted, computed, watch } from "vue";
import { useRoute } from "vue-router";
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";
......@@ -182,11 +144,27 @@ const curBill = ref("");
const curBillId = ref(null);
const checkedValue = ref(false);
const searchValue = ref("");
const searchKeyword = ref("");
const selectedDomain = ref("");
const selectedLimit = ref("");
const domainOptions = ref([]);
const limitOptions = ref([]);
const billList = ref([]);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const termsLoading = ref(false);
const limitLoading = ref(false);
const domainLoading = ref(false);
const termsHighlight = ref(true);
const termsShowOriginal = ref(true);
const tkRequestToken = ref(0);
const xzfsRequestToken = ref(0);
const hylyRequestToken = ref(0);
const mainTermsList = ref([]);
const domainFooterText = ref("");
const limitFooterText = ref("");
......@@ -199,8 +177,8 @@ const getTermKey = (term, index) => {
return term?.ywid ?? term?.id ?? term?.tkxh ?? index;
};
const getTagKey = (val, idx) => {
return `${val}-${idx}`;
const getTermSerial = index => {
return (currentPage.value - 1) * pageSize.value + index + 1;
};
const chart1Data = ref([]);
......@@ -210,6 +188,42 @@ const chart2ColorList = ref(["#ff7875", "#85a5ff", "#95de64", "#ffc069", "#85e5d
const chart2Data = ref([]);
const displayTermsList = computed(() => {
const keyword = (searchKeyword.value || "").trim().toLowerCase();
const domain = selectedDomain.value;
const limit = selectedLimit.value;
return (mainTermsList.value || []).filter(term => {
if (keyword) {
const cn = String(term?.fynr ?? "").toLowerCase();
const en = String(term?.ywnr ?? "").toLowerCase();
const no = String(term?.tkxh ?? "").toLowerCase();
if (!cn.includes(keyword) && !en.includes(keyword) && !no.includes(keyword)) return false;
}
if (domain) {
const list = term?.hylyList || term?.hyly || [];
const arr = Array.isArray(list) ? list : [];
if (!arr.includes(domain)) return false;
}
if (limit) {
const list = term?.xzfsList || term?.xzfs || [];
const arr = Array.isArray(list) ? list : [];
if (!arr.includes(limit)) return false;
}
return true;
});
});
watch([selectedDomain, selectedLimit], () => {
currentPage.value = 1;
handleGetBillContentTk(checkedValue.value ? "Y" : "N");
});
const handleSearchSubmit = () => {
searchKeyword.value = searchValue.value;
currentPage.value = 1;
handleGetBillContentTk(checkedValue.value ? "Y" : "N");
};
// 绘制echarts图表
const setChart = (option, chartId) => {
let chartDom = document.getElementById(chartId);
......@@ -224,7 +238,9 @@ const handleChangeBill = val => {
curBill.value = val;
const item = billList.value.find(item => item.value === val);
if (item) {
curBillId.value = item.id;
curBillId.value = item.value;
selectedDomain.value = "";
selectedLimit.value = "";
currentPage.value = 1;
handleGetBillContentTk(checkedValue.value ? "Y" : "N");
handleGetBillContentXzfs();
......@@ -232,6 +248,11 @@ const handleChangeBill = val => {
}
};
const handleOpenVersionCompare = () => {
const targetUrl = `/billLayout/deepDig/processOverview?billId=${route.query.billId}`;
window.open(targetUrl, "_blank");
};
// 获取法案id列表
const handleGetBillList = async () => {
const params = {
......@@ -240,16 +261,24 @@ const handleGetBillList = async () => {
try {
const res = await getBillContentId(params);
console.log("法案id列表", res);
billList.value = res.data.map(item => {
return {
label: item.bbmc,
value: item.bbmc,
id: item.ywid
};
});
const rawList = Array.isArray(res?.data) ? res.data : [];
const seen = new Set();
billList.value = rawList
.map(item => {
return {
label: item.bbmc,
value: item.bbmc
};
})
.filter(item => {
const uniqKey = `${item.value ?? ""}`;
if (seen.has(uniqKey)) return false;
seen.add(uniqKey);
return true;
});
if (billList.value.length > 0) {
curBill.value = billList.value[0].value;
curBillId.value = billList.value[0].id;
curBillId.value = billList.value[0].value;
}
} catch (error) { }
};
......@@ -268,6 +297,9 @@ const handleCurrentChange = val => {
// 根据原文ID获取条款列表
const handleGetBillContentTk = async cRelated => {
const currentToken = ++tkRequestToken.value;
termsLoading.value = true;
const params = {
billId: route.query.billId,
id: curBill.value,
......@@ -275,8 +307,23 @@ const handleGetBillContentTk = async cRelated => {
pageSize: pageSize.value,
currentPage: currentPage.value
};
if (selectedDomain.value) {
params.domainNameList = selectedDomain.value;
}
if (selectedLimit.value) {
params.measuresNameList = selectedLimit.value;
}
if (searchKeyword.value && searchKeyword.value.trim()) {
params.content = searchKeyword.value.trim();
}
try {
const res = await getBillContentTk(params);
if (currentToken !== tkRequestToken.value) {
return;
}
console.log("条款内容", res);
if (res && res.data) {
mainTermsList.value = (res.data.content || []).map(item => {
......@@ -320,14 +367,24 @@ const handleGetBillContentTk = async cRelated => {
total.value = 0;
}
} catch (error) {
if (currentToken !== tkRequestToken.value) {
return;
}
console.error("获取条款内容失败", error);
mainTermsList.value = [];
total.value = 0;
} finally {
if (currentToken === tkRequestToken.value) {
termsLoading.value = false;
}
}
};
// 获取限制方式列表
const handleGetBillContentXzfs = async () => {
const currentToken = ++xzfsRequestToken.value;
limitLoading.value = true;
const params = {
billId: route.query.billId,
versionId: curBill.value,
......@@ -336,7 +393,22 @@ const handleGetBillContentXzfs = async () => {
try {
const res = await getBillContentXzfs(params);
if (currentToken !== xzfsRequestToken.value) {
return;
}
console.log("限制方式", res);
const limitSeen = new Set();
limitOptions.value = (res?.data || [])
.map(item => item?.xzfsmc)
.filter(Boolean)
.filter(name => {
if (limitSeen.has(name)) return false;
limitSeen.add(name);
return true;
})
.map(name => {
return { label: name, value: name };
});
chart1Data.value = res.data.map(item => {
return {
name: item.xzfsmc,
......@@ -345,11 +417,22 @@ const handleGetBillContentXzfs = async () => {
});
let chart1 = getPieChart(chart1Data.value, chart1ColorList.value);
setChart(chart1, "chart1");
} catch (error) { }
} catch (error) {
if (currentToken !== xzfsRequestToken.value) {
return;
}
} finally {
if (currentToken === xzfsRequestToken.value) {
limitLoading.value = false;
}
}
};
// 行业领域
const handleGetBillHyly = async () => {
const currentToken = ++hylyRequestToken.value;
domainLoading.value = true;
const params = {
billId: route.query.billId,
versionId: curBill.value,
......@@ -358,7 +441,22 @@ const handleGetBillHyly = async () => {
try {
const res = await getBillHyly(params);
if (currentToken !== hylyRequestToken.value) {
return;
}
console.log("行业领域", res.data);
const domainSeen = new Set();
domainOptions.value = (res?.data || [])
.map(item => item?.hylymc)
.filter(Boolean)
.filter(name => {
if (domainSeen.has(name)) return false;
domainSeen.add(name);
return true;
})
.map(name => {
return { label: name, value: name };
});
chart2Data.value = res.data.map(item => {
return {
name: item.hylymc,
......@@ -368,7 +466,15 @@ const handleGetBillHyly = async () => {
let chart2 = getPieChart(chart2Data.value, chart2ColorList.value);
setChart(chart2, "chart2");
} catch (error) { }
} catch (error) {
if (currentToken !== hylyRequestToken.value) {
return;
}
} finally {
if (currentToken === hylyRequestToken.value) {
domainLoading.value = false;
}
}
};
onMounted(async () => {
......@@ -382,6 +488,138 @@ onMounted(async () => {
<style lang="scss" scoped>
.temp-wrap {
display: flex;
flex-direction: column;
.tools-row {
margin-top: 16px;
display: flex;
align-items: center;
.tools-row-left {
width: 520px;
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 16px;
}
.tools-row-right {
width: 1064px;
margin-left: 16px;
display: flex;
align-items: center;
column-gap: 16px;
}
.tools-row-right-actions {
margin-left: auto;
display: flex;
align-items: center;
column-gap: 16px;
}
.tools-row-control {
height: 32px;
font-size: 16px;
font-weight: 400;
:deep(.el-select__wrapper) {
height: 32px;
font-size: 16px;
font-weight: 400;
}
:deep(.el-checkbox__label) {
font-size: 16px;
font-weight: 400;
}
}
.tools-row-control--primary {
:deep(.el-select__wrapper) {
background: rgb(246, 250, 255);
border: 1px solid var(--color-primary-35);
box-shadow: none;
color: var(--color-primary-100);
}
:deep(.el-select__selected-item) {
color: var(--color-primary-100);
font-size: 16px;
font-weight: 400;
}
:deep(.el-select__caret) {
color: var(--color-primary-100);
}
}
:deep(.el-button.tools-row-control--primary) {
height: 32px;
padding: 0 12px;
border: 1px solid var(--color-primary-35);
background: rgb(246, 250, 255);
color: var(--color-primary-100);
font-size: 16px;
font-weight: 400;
width: 111px;
}
.tools-row-compare-btn {
display: inline-flex;
align-items: center;
}
.tools-row-compare-icon {
width: 14px;
height: 14px;
flex: 0 0 14px;
margin-right: 6px;
}
.tools-row-control--bill {
width: 392px;
}
.tools-row-control--search {
width: 200px;
margin-right: auto;
:deep(.el-input__wrapper) {
height: 32px;
background: var(--el-fill-color-blank);
border-radius: 4px;
box-shadow: 0 0 0 1px var(--el-border-color) inset;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px var(--el-color-primary) inset !important;
border-color: var(--el-color-primary) !important;
}
:deep(.el-input__wrapper:not(.is-focus):hover) {
box-shadow: 0 0 0 1px var(--el-border-color) inset !important;
border-color: var(--el-border-color) !important;
}
:deep(.el-input__inner) {
font-size: 16px;
}
}
.tools-row-search-icon {
cursor: pointer;
}
.tools-row-control--domain,
.tools-row-control--limit {
width: 150px;
}
}
.content-row {
display: flex;
}
.box-header {
display: flex;
......@@ -446,152 +684,135 @@ onMounted(async () => {
margin-top: 16px;
margin-left: 16px;
width: 1064px;
height: 845px;
.left-top {
// height: 45px;
height: 1232px;
position: relative;
margin: 0px 24px 8px 24px;
.terms-switch {
position: absolute;
top: 13px;
right: 85px;
z-index: 5;
display: flex;
column-gap: 6px;
align-items: center;
:deep(.el-input) {
width: 240px;
border-radius: 4px;
border: 1px solid var(--border-black-10);
.terms-switch-label {
font-size: 16px;
color: var(--text-primary-65-color);
}
:deep(.el-select) {
width: 240px;
border-radius: 4px;
// border: 1px solid var(--border-black-10);
.terms-switch-divider {
display: inline-block;
width: 1px;
height: 14px;
background: var(--el-border-color);
margin: 0 2px;
}
:deep(.el-button--small) {
height: 26px;
padding: 0 10px;
font-size: 12px;
}
}
:deep(.wrapper-main) {
display: flex;
flex-direction: column;
height: calc(100% - 45px);
gap:16px;
}
.left-main {
height: 660px;
overflow: hidden;
flex: 1;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 12px;
padding: 0 16px;
.left-main-item {
margin-left: 22px;
.left-main-item {
width: 1016px;
// height: 65px;
box-sizing: border-box;
border-radius: 2px;
background: rgba(255, 255, 255, 1);
background: rgb(247, 248, 249);
display: flex;
align-items: flex-start;
position: relative;
padding: 16px;
.id {
margin-top: 20px;
margin-left: 15px;
.term-body {
display: flex;
column-gap: 18px;
flex: 1;
min-width: 0;
width: 100%;
}
.term-index {
width: 24px;
height: 24px;
border-radius: 12px;
background: rgba(231, 241, 255);
color: var(--color-main-active);
border-radius: 50%;
background: var(--color-primary-10);
color: var(--color-primary-100);
font-size: 12px;
text-align: center;
font-weight: 400;
line-height: 24px;
text-align: center;
flex: 0 0 24px;
}
.info {
margin-left: 13px;
margin-top: 15px;
width: 920px;
.title {
// height: 14px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 600;
line-height: 22px;
text-align: left;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
.title-active {
color: var(--color-main-active);
}
}
.content {
margin-top: 6px;
// height: 14px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 24px;
text-align: left;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
.content-active {
color: var(--color-main-active);
}
}
.term-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
row-gap: 6px;
}
.tags-box {
.term-row {
display: flex;
justify-content: right;
align-items: center;
flex: 1;
margin-right: 50px;
.tag {
text-align: right;
line-height: 18px;
padding: 1px 8px;
border-radius: 4px;
margin-left: 5px;
font-size: 12px;
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
}
.tag1 {
border: 1px solid rgba(217, 247, 190, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
.tag2 {
border: 1px solid rgba(186, 224, 255, 1);
background: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
}
align-items: flex-start;
column-gap: 18px;
}
.tag3 {
border: 1px solid rgba(135, 232, 222, 1);
background: rgba(230, 255, 251, 1);
color: rgba(19, 168, 168, 1);
}
.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;
}
.tag4 {
border: 1px solid rgba(211, 173, 247, 1);
background: rgba(249, 240, 255, 1);
color: rgba(114, 46, 209, 1);
}
.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;
}
.tag5 {
border: 1px solid rgba(255, 229, 143, 1);
background: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
}
.term-content-cn {
flex: 1;
font-size: 16px;
font-weight: 700;
line-height: 24px;
color: var(--text-primary-80-color);
}
.tag6 {
border: 1px solid rgba(255, 163, 158, 1);
background: rgba(255, 241, 240, 1);
color: rgba(245, 34, 45, 1);
}
.term-content-en {
flex: 1;
font-size: 14px;
font-weight: 400;
line-height: 24px;
color: var(--text-primary-65-color);
}
.open {
......@@ -614,8 +835,8 @@ onMounted(async () => {
}
.left-footer {
margin: 30px;
height: 30px;
margin: 0 16px 16px;
height: 40px;
display: flex;
justify-content: space-between;
......@@ -641,19 +862,20 @@ onMounted(async () => {
width: 520px;
}
.side-box-limit {
margin-top: 15px;
.side-box-limit {
margin-top: 15px;
width: 520px;
height: 415px;
.right-box1-main {
width: 520px;
height: 415px;
.right-box1-main {
width: 520px;
height: 315px;
padding: 16px;
}
height: 315px;
padding: 16px;
}
.right-box-main--full {
height: 375px;
}
.right-box-main--full {
height: 375px;
}
.right-box1-footer {
width: 493px;
......@@ -708,18 +930,19 @@ onMounted(async () => {
}
}
.side-box-domain {
.side-box-domain {
width: 520px;
height: 415px;
.right-box2-main {
width: 520px;
height: 415px;
.right-box2-main {
width: 520px;
height: 315px;
padding: 16px;
}
height: 315px;
padding: 16px;
}
.right-box-main--full {
height: 375px;
}
.right-box-main--full {
height: 375px;
}
.right-box2-footer {
width: 493px;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论