提交 467910f8 authored 作者: 闫鹏's avatar 闫鹏

合并分支 'yp-dev' 到 'pre'

Yp dev 查看合并请求 !304
流水线 #323 已通过 于阶段
in 3 分 20 秒
import request from "@/api/request.js"; // import request from "@/api/request.js";
\ No newline at end of file // import { ElMessage } from "element-plus";
// const request200 = requestP => {
// return requestP.then(data => {
// if (data.code === 200) {
// console.log('返回的数据结构 =>', data.data)
// return data.data;
// }
// ElMessage({
// message: data.message,
// type: "error",
// duration: 3 * 1000
// });
// return null;
// });
// };
// export function getDataCount() {
// return request200(
// request({
// method: "GET",
// // url: "/api/entitiesDataCount/countData",
// url: "/api/sanctionList/invFin/getTotalInfo"
// })
// );
// }
import { http } from "./service.js";
/**
* 总次数统计
*/
export function getDataCount() {
return http.get("/api/sanctionList/invFin/getTotalInfo");
}
/**
* 最新出口管制政策
* url:/sanctionList/invFin/getLatestEntityListInfo
*/
export function getLatestEntityListInfo() {
return http.get("/api/sanctionList/invFin/getLatestEntityListInfo");
}
/**
* 风险信号
* url:/commonFeature/riskSignal/{moduleId}
*/
export function getRiskSignal(moduleId='0104') {
return http.get(`/api/commonFeature/riskSignal/${moduleId}`);
}
/**
* 新闻资讯
* url:/commonFeature/news/{moduleId}
*/
export function getNewsInfo(moduleId='0104') {
return http.get(`/api/commonFeature/news/${moduleId}`);
}
/**
* 社交媒体信息
* url:/commonFeature/remarks/{moduleId}
*/
export function getSocialMediaInfo(moduleId='0104') {
return http.get(`/api/commonFeature/remarks/${moduleId}`);
}
/**
* 发布频度
* url:/entitiesDataCount/getAnnualCount
*/
export function getReleaseCount(id) {
return http.get(`/api/entitiesDataCount/getAnnualCount?sanTypeId=${id}`);
}
/**
* 制裁领域分析
* url:/entitiesDataCount/getSanDomainCount
*/
export function getSanDomainCount(sanTypeIds) {
return http.get(`/api/entitiesDataCount/getSanDomainCount?sanTypeIds=${sanTypeIds}`);
}
/**
* 制裁清单增长趋势
* url:/entitiesDataCount/getAnnualSanDomain
*/
export function getAnnualSanDomain(params) {
return http.post("/api/entitiesDataCount/getAnnualSanDomain", params);
}
/**
* 全部制裁(历史制裁过程)
* url:/entitiesDataCount/getSanctionProcess
*/
export function getSanctionProcess(sanTypeIds = "1", pageNum = 1, pageSize = 10, isCn = false) {
return http.post("/api/entitiesDataCount/getSanctionProcess",{
sanTypeIds,
// typeName: tabMap[sanTypeId],
pageNum,
pageSize,
isCn
});
}
// 引入 axios 请求
import axios from 'axios'
// 引入 element-plus 里面的消息提示
import { ElMessage } from 'element-plus'
// Token 管理
const TOKEN_KEY = 'auth_token'
// 获取token
const getToken = () => {
return 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw'
// return localStorage.getItem(TOKEN_KEY)
}
// 设置 token
const setToken = (token) => {
localStorage.setItem(TOKEN_KEY, token)
}
// 移除 token
const removeToken = () => {
localStorage.removeItem(TOKEN_KEY)
}
// 导出 token 管理方法
export { getToken, setToken, removeToken }
// 创建 axios 实例
const service = axios.create({
timeout: 300 * 1000 // 请求超时时间
})
// request 拦截器
service.interceptors.request.use(config => {
// 获取 token 并添加到请求头
const token = getToken()
if (token) {
config.headers['token'] = token
}
// 图表解读等 AI 分析服务(Vite 代理 /aiAnalysis)需要 X-API-Key
const reqUrl = String(config.url || '')
if (reqUrl.includes('aiAnalysis')) {
const aiApiKey = import.meta.env.VITE_AI_ANALYSIS_API_KEY
if (aiApiKey) {
config.headers['X-API-Key'] = aiApiKey
}
}
return config
}, error => {
console.log(error)
return Promise.reject(error)
})
// response 拦截器
service.interceptors.response.use(
response => {
const res = response.data
// 根据需求:接口返回 code 不等于 200 的时候报错
if (res.code !== 200) {
ElMessage({
message: res.message || '请求失败',
type: 'error',
duration: 3 * 1000
})
return Promise.reject(res)
}
return res.data
},
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 过期或无效的情况
const errUrl = String(error.config?.url || '')
const isAiAnalysisRequest = errUrl.includes('aiAnalysis')
if (
error.response &&
(error.response.status === 401 || error.response.status === 403) &&
!isAiAnalysisRequest
) {
ElMessage({
message: 'Token 已过期,请重新登录',
type: 'error',
duration: 3 * 1000
})
removeToken()
} else {
ElMessage({
message: error.message,
type: 'error',
duration: 3 * 1000
})
}
return Promise.reject(error)
}
)
// 封装通用请求函数(支持 http(config) 和 http.get/post 等调用方式)
function http(config) {
return service(config)
}
// 为 http 函数添加快捷方法
http.get = function(url, params) {
return service({ url, method: 'get', params })
}
http.post = function(url, data) {
return service({ url, method: 'post', data })
}
http.put = function(url, data) {
return service({ url, method: 'put', data })
}
http.delete = function(url, params) {
return service({ url, method: 'delete', params })
}
export { http }
export default service
\ No newline at end of file
...@@ -58,7 +58,7 @@ const routes = [ ...@@ -58,7 +58,7 @@ const routes = [
]; ];
console.log('路由', routes)
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes routes
......
...@@ -5,7 +5,7 @@ const financeRoutes = [ ...@@ -5,7 +5,7 @@ const financeRoutes = [
// 投融资限制 // 投融资限制
{ {
path: "/finance", path: "/finance",
name: "finance", name: "Finance",
component: Finance, component: Finance,
meta: { meta: {
title: "投融资限制概览", title: "投融资限制概览",
......
...@@ -73,13 +73,18 @@ ...@@ -73,13 +73,18 @@
</div> </div>
<div class="box1-top-content-item"> <div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 涉及领域:</span> <span class="box1-top-content-item-title">· 涉及领域:</span>
<div <!-- <div
class="box1-top-content-item-tags" class="box1-top-content-item-tags"
v-for="(domainItem, index) in item.domains" v-for="(domainItem, index) in item.domains"
:key="index" :key="index"
> >
<el-tag :type="getTagType(domainItem)">{{ domainItem }}</el-tag> <el-tag :type="getTagType(domainItem)">{{ domainItem }}</el-tag>
</div> </div> -->
<AreaTag
v-for="(domainItem, index) in item.domains"
:key="index"
:tagName="domainItem"
/>
</div> </div>
</div> </div>
</div> </div>
......
{
"code": 200,
"message": "操作成功",
"success": true,
"data": {
"content": [
{
"createTime": null,
"updateTime": null,
"id": "3357",
"entityName": "Arrow Electronics (Hong Kong) Co., Ltd.",
"sanTypeId": 1,
"entityType": 2,
"entityId": null,
"entityNameZh": "艾睿电子(香港)有限公司",
"countryId": "0101",
"sanReason": "对于所有受《出口管理条例》(EAR)管制的项目,协助为伊朗代理人(包括哈马斯(Hamas))购买美国原产的电子元件,用于武器化无人机(UAV),违反美国国家安全;协助为伊朗代理人操作的武器化无人驾驶航空系统(UAS)购买美国原产的电子元件。",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [],
"ruleOrgCount": 39
},
{
"createTime": null,
"updateTime": null,
"id": "3ef6b0b3-4f6a-4",
"entityName": "Shanghai Langqing Electronic Technology Co.",
"sanTypeId": 1,
"entityType": 2,
"entityId": null,
"entityNameZh": "上海朗晴电子科技有限公司",
"countryId": "0101",
"sanReason": "对于所有受《出口管理条例》(EAR)管辖的物品",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"经济"
],
"ruleOrgCount": 93
},
{
"createTime": null,
"updateTime": null,
"id": "81fc1a85-a2be-4",
"entityName": "Easy Fly Intelligent Technology Co., Ltd.",
"sanTypeId": 1,
"entityType": 2,
"entityId": null,
"entityNameZh": "易飞智能科技有限公司",
"countryId": "0101",
"sanReason": "对于所有受《出口管理条例》(EAR)管辖的物品",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"科技"
],
"ruleOrgCount": 18
},
{
"createTime": null,
"updateTime": null,
"id": "2813d17d-8edd-4",
"entityName": "Beijing Rageflight Technology Co., Ltd.",
"sanTypeId": 1,
"entityType": 2,
"entityId": null,
"entityNameZh": "北京锐飞科技有限公司",
"countryId": "0101",
"sanReason": "对于所有受《出口管理条例》(EAR)管辖的物品,为伊朗代理人操作的武器化无人驾驶航空系统(UAS)采购美国原产电子组件提供了便利。",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"科技",
"军事"
],
"ruleOrgCount": 2
},
{
"createTime": null,
"updateTime": null,
"id": "f7bf81c6-9f5c-4",
"entityName": "Beijing Kevins Technology Development Co., Ltd.",
"sanTypeId": 1,
"entityType": 2,
"entityId": null,
"entityNameZh": "北京凯文斯科技发展有限公司",
"countryId": "0101",
"sanReason": "对于所有受《出口管理条例》(EAR)管辖的项目,协助为伊朗代理人操作的武器化无人飞行系统(UAS)采购美国原产电子元件;协助为伊朗代理人,包括哈马斯(Hamas),采购用于武器化无人飞行器(UAV)的美国原产电子元件,此举违背了美国国家安全。",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"经济",
"安全"
],
"ruleOrgCount": 51
},
{
"createTime": null,
"updateTime": null,
"id": "3add08b1-3a8a-4",
"entityName": "China Address 17",
"sanTypeId": 1,
"entityType": 7,
"entityId": null,
"entityNameZh": "中国地址17",
"countryId": "0101",
"sanReason": "与艾米丽·刘(Emily Liu)的采购网络相关联,此人先前被海外资产控制办公室(OFAC)指定为支持伊朗设拉子电子工业(SEI)",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"安全"
],
"ruleOrgCount": 66
},
{
"createTime": null,
"updateTime": null,
"id": "2b660bc7-d770-4",
"entityName": "Address 18",
"sanTypeId": 1,
"entityType": 7,
"entityId": null,
"entityNameZh": "地址18",
"countryId": "0101",
"sanReason": "对于商业管制清单(CCL)上的物品以及《出口管理条例》(EAR)第746部分第7号补编中列出的EAR 99物品",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"经济"
],
"ruleOrgCount": 1
},
{
"createTime": null,
"updateTime": null,
"id": "ccaa8cd6-2b78-4",
"entityName": "Shanghai Bitconn Electronics Co., Ltd.",
"sanTypeId": 1,
"entityType": 2,
"entityId": null,
"entityNameZh": "上海比通电子有限公司",
"countryId": "0101",
"sanReason": "对于所有受《出口管理条例》(EAR)约束的项目,为伊朗代理人操作的武器化无人驾驶航空系统(UAS)购买美国原产电子元件提供了便利。",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"经济",
"军事",
"科技"
],
"ruleOrgCount": 15
},
{
"createTime": null,
"updateTime": null,
"id": "8aa1686a-33e8-4",
"entityName": "Feng Bao Electronic Information Technology (Shanghai) Co., Ltd.",
"sanTypeId": 1,
"entityType": 2,
"entityId": null,
"entityNameZh": "丰宝电子信息科技(上海)有限公司",
"countryId": "0101",
"sanReason": "对于所有受《出口管理条例》(EAR)管辖的项目",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"科技"
],
"ruleOrgCount": 42
},
{
"createTime": null,
"updateTime": null,
"id": "d9016b50-dee1-4",
"entityName": "Royal Impact Trading L.L.C.",
"sanTypeId": 1,
"entityType": 2,
"entityId": null,
"entityNameZh": "皇家影响力贸易有限责任公司",
"countryId": "134",
"sanReason": "对于所有受《出口管理条例》(EAR)管辖的项目,根据EAR第744.11节的规定,推定为拒绝批准,将美国原产项目转移至伊朗,包括归类于ECCN 2B350的项目。",
"sanIntensity": "\u0000",
"startTime": "2025-10-09",
"endTime": null,
"isKey": "\u0000",
"techDomainList": null,
"techDomains": [
"经济"
],
"ruleOrgCount": 39
}
],
"pageable": {
"sort": {
"unsorted": false,
"sorted": true,
"empty": false
},
"pageNumber": 0,
"pageSize": 10,
"offset": 0,
"unpaged": false,
"paged": true
},
"totalPages": 347,
"last": false,
"totalElements": 3461,
"sort": {
"unsorted": false,
"sorted": true,
"empty": false
},
"first": true,
"numberOfElements": 10,
"size": 10,
"number": 0,
"empty": false
}
}
\ No newline at end of file
...@@ -4,22 +4,29 @@ ...@@ -4,22 +4,29 @@
<div class="header-title"> <div class="header-title">
<img :src="headerTitle.img" alt="" /> <img :src="headerTitle.img" alt="" />
<div> <div>
<div class="title"> <div class="title">{{ headerTitle.title }}</div>
{{ headerTitle.title }} <div class="department">{{ headerTitle.department }}</div>
</div>
<div class="department">
{{ headerTitle.department }}
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="main"> <div class="main">
<div class="main-header"> <div class="main-header">
<div>实体清单制裁文件</div> <div class="header-left">实体清单制裁文件</div>
<div class="header-right">
<!-- 中英文切换开关 -->
<div class="toggle-group">
<!-- <span :class="{ active: !showChinese }">英文</span> -->
<el-switch v-model="showChinese" @change="handleToggleChange" />
<img :src="transIcon" alt="" />
<span :class="{ active: showChinese }">显示原文</span>
</div>
<!-- 下载按钮 -->
<el-button plain :icon="Download" @click="handleDownload"> 下载 </el-button>
</div>
</div> </div>
<!-- 外层滚动容器,统一控制两侧滚动 --> <!-- 外层滚动容器,统一控制两侧滚动 -->
<div class="report-box" ref="reportBoxRef"> <div class="report-box" ref="reportBoxRef">
<div class="pdf-pane-wrap"> <div class="pdf-pane-wrap" v-if="showChinese" :class="{ 'center-mode': !showChinese }">
<pdf ref="leftPdfRef" :pdfUrl="headerTitle.srcUrl" class="pdf-pane-inner" /> <pdf ref="leftPdfRef" :pdfUrl="headerTitle.srcUrl" class="pdf-pane-inner" />
</div> </div>
<div class="pdf-pane-wrap"> <div class="pdf-pane-wrap">
...@@ -31,9 +38,11 @@ ...@@ -31,9 +38,11 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch } from "vue"; import { ref, onMounted, watch, computed } from "vue";
import { Download } from "@element-plus/icons-vue";
import { getSingleSanctionOverview } from "@/api/exportControlV2.0.js"; import { getSingleSanctionOverview } from "@/api/exportControlV2.0.js";
import title from "../assets/title.png"; import title from "../assets/title.png";
import transIcon from "../assets/icon-translation.png";
import pdf from "./pdf.vue"; import pdf from "./pdf.vue";
const leftPdfRef = ref(null); const leftPdfRef = ref(null);
...@@ -51,6 +60,46 @@ const headerTitle = ref({ ...@@ -51,6 +60,46 @@ const headerTitle = ref({
const sanRecordId = ref(""); const sanRecordId = ref("");
const isSyncing = ref(false); const isSyncing = ref(false);
// ✅ 控制中文 PDF 显示
const showChinese = ref(true);
// ✅ 计算当前显示模式
const showMode = computed(() => {
return showChinese.value ? "both" : "en";
});
// ✅ 切换中英文显示
const handleToggleChange = value => {
console.log("切换中英文显示:", value ? "中英双栏" : "仅英文");
showChinese.value = value;
};
// ✅ 下载功能
const handleDownload = async () => {
const files = [
{ url: headerTitle.value.srcUrl, name: "英文原版.pdf" },
{ url: headerTitle.value.transUrl, name: "中文翻译.pdf" }
];
for (const file of files) {
if (file.url) {
try {
const response = await fetch(file.url);
const blob = await response.blob();
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = file.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
} catch (error) {
console.error(`下载${file.name}失败:`, error);
}
}
}
};
const getUrlParams = () => { const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""; sanRecordId.value = urlParams.get("id") || "";
...@@ -112,7 +161,6 @@ const setupScrollSync = () => { ...@@ -112,7 +161,6 @@ const setupScrollSync = () => {
watch( watch(
() => [headerTitle.value.srcUrl, headerTitle.value.transUrl], () => [headerTitle.value.srcUrl, headerTitle.value.transUrl],
() => { () => {
// PDF URL 变化时,等待渲染完成后设置滚动监听
setTimeout(() => { setTimeout(() => {
setupScrollSync(); setupScrollSync();
}, 1000); }, 1000);
...@@ -124,18 +172,17 @@ onMounted(() => { ...@@ -124,18 +172,17 @@ onMounted(() => {
getUrlParams(); getUrlParams();
getSingleSanctionOverviewData(); getSingleSanctionOverviewData();
// 等待 DOM 渲染完成后设置滚动监听
setTimeout(() => { setTimeout(() => {
setupScrollSync(); setupScrollSync();
}, 500); }, 500);
}); });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
* { // * {
margin: 0; // margin: 0;
padding: 0; // padding: 0;
} // }
.entity-list { .entity-list {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -207,9 +254,57 @@ onMounted(() => { ...@@ -207,9 +254,57 @@ onMounted(() => {
font-weight: 700; font-weight: 700;
line-height: 26px; line-height: 26px;
width: 1456px; width: 1456px;
text-align: left;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
.header-right {
display: flex;
align-items: center;
gap: 24px;
.toggle-group {
display: flex;
align-items: center;
gap: 10px;
span {
font-size: 14px;
color: rgb(150, 150, 150);
transition: color 0.3s;
&.active {
color: rgb(5, 95, 194);
font-weight: 600;
}
}
:deep(.el-switch) {
--el-switch-on-color: #055fc2;
--el-switch-off-color: #e6e7e8;
.el-switch__label {
color: #fff;
font-size: 12px;
font-weight: 600;
&.is-active {
color: #fff;
}
}
}
}
// :deep(.el-button) {
// --el-button-bg-color: #055fc2;
// --el-button-border-color: #055fc2;
// --el-button-hover-bg-color: #044c9b;
// --el-button-hover-border-color: #044c9b;
// font-size: 14px;
// padding: 10px 20px;
// }
}
} }
.report-box { .report-box {
...@@ -217,8 +312,10 @@ onMounted(() => { ...@@ -217,8 +312,10 @@ onMounted(() => {
width: 1456px; width: 1456px;
height: calc(100% - 64px); height: calc(100% - 64px);
display: flex; display: flex;
overflow-y: auto; /* 统一滚动条,控制两侧一起滚动 */ overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
// ✅ 添加居中对齐
justify-content: center;
} }
.pdf-pane-wrap { .pdf-pane-wrap {
...@@ -226,6 +323,15 @@ onMounted(() => { ...@@ -226,6 +323,15 @@ onMounted(() => {
max-width: 50%; max-width: 50%;
height: 100%; height: 100%;
min-width: 0; min-width: 0;
transition: all 0.3s;
&.center-mode {
flex: 0 0 100%;
max-width: 100%;
// ✅ 添加居中样式
width: 728px; // 约一半宽度,保持单栏时美观
margin: 0 auto;
}
} }
.pdf-pane-inner { .pdf-pane-inner {
......
<template>
<div class="sanction-container">
<!-- 页面标题区域 -->
<div class="page-header">
<!-- <h1 class="main-title">制裁分析</h1> -->
<div class="bill-info">
<div class="bill-details">
<div class="main-title">制裁分析</div>
<div class="bill-name-en">第119届美国国会众议院第1号法案One Big Beautiful Bill Act</div>
</div>
<div class="date-author">
<div class="date">2025年7月</div>
<div class="author">乔迪·阿灵顿(Jodey Arrington)</div>
</div>
</div>
</div>
<!-- 导航标签区域 -->
<!-- <div class="nav-section">
<div class="tabs">
<div class="tab-item active">制裁概况</div>
<div class="tab-item">深度挖掘</div>
<div class="tab-item">影响分析</div>
</div>
<div class="action-buttons">
<el-button>法案原文</el-button>
<el-button type="primary">分析报告</el-button>
</div>
</div> -->
<!-- 内容区域 -->
<!-- <div class="content-section">
<div class="content-placeholder">
<el-icon><document /></el-icon>
<p>这里是制裁概况的内容区域</p>
<p>请选择上方标签查看不同分析内容</p>
</div>
</div> -->
</div>
</template>
<script setup></script>
<style scoped>
.sanction-container {
width: 100%;
/* max-width: 1200px; */
background-color: #fff;
/* border-radius: 8px; */
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
box-sizing: border-box;
padding-left: 5%;
padding-right: 5%;
}
.page-header {
padding: 24px;
padding-top: 0;
/* border-bottom: 1px solid #ebeef5; */
}
.main-title {
font-size: 20px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
/* color: var(--base-color); */
margin-bottom: 8px;
}
.bill-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
}
.bill-details {
flex: 1;
}
.bill-name {
font-size: 16px;
color: #606266;
margin-bottom: 4px;
}
.bill-name-en {
font-size: 14px;
color: #909399;
font-style: italic;
}
.date-author {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.date {
font-size: 14px;
color: #606266;
margin-bottom: 4px;
}
.author {
font-size: 14px;
color: #606266;
}
.nav-section {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
background-color: #f8fafc;
border-bottom: 1px solid #ebeef5;
}
.tabs {
display: flex;
gap: 0;
}
.tab-item {
padding: 12px 24px;
font-size: 14px;
color: #606266;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.3s;
}
.tab-item.active {
color: #409eff;
border-bottom-color: #409eff;
background-color: rgba(64, 158, 255, 0.1);
}
.tab-item:hover {
color: #409eff;
}
.action-buttons {
display: flex;
gap: 12px;
}
.content-section {
padding: 24px;
min-height: 400px;
}
.content-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
color: #909399;
}
.content-placeholder .el-icon {
font-size: 48px;
margin-bottom: 16px;
color: #dcdfe6;
}
.content-placeholder p {
font-size: 16px;
margin-top: 8px;
}
@media (max-width: 768px) {
.bill-info {
flex-direction: column;
align-items: flex-start;
}
.date-author {
align-items: flex-start;
margin-top: 12px;
}
.nav-section {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.tabs {
width: 100%;
overflow-x: auto;
}
}
</style>
<template>
<div class="page-container">
<Header />
<!-- 导航标签区域 -->
<div class="nav-section">
<div class="tabs">
<div :class="['tab-item', { active: activeTab === '制裁概况' }]" @click="setActiveTab('制裁概况')">制裁概况</div>
<div :class="['tab-item', { active: activeTab === '深度挖掘' }]" @click="setActiveTab('深度挖掘')">深度挖掘</div>
<div :class="['tab-item', { active: activeTab === '影响分析' }]" @click="setActiveTab('影响分析')">影响分析</div>
</div>
<div class="action-buttons">
<el-button>法案原文</el-button>
<el-button type="primary">分析报告</el-button>
</div>
</div>
<!-- 内容区域 -->
<div class="content-section">
<!-- <div class="content-placeholder">
<el-icon><document /></el-icon>
<p>这里是制裁概况的内容区域</p>
<p>请选择上方标签查看不同分析内容</p>
</div> -->
<Survey v-if="activeTab === '制裁概况'" />
<DepthMine v-if="activeTab === '深度挖掘'" />
<ImpactAnalysis v-if="activeTab === '影响分析'" />
</div>
<div class="left-absolute">
<el-image :src="Left1" alt="" />
<el-image :src="Left2" alt="" />
<el-image :src="Left3" alt="" />
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import Header from "./header.vue";
import Survey from "./content/survey.vue";
import DepthMine from "./content/depthMine.vue";
import ImpactAnalysis from "./content/impactAnalysis.vue";
import Left1 from "@/assets/images/left-1.png";
import Left2 from "@/assets/images/left-2.png";
import Left3 from "@/assets/images/left-3.png";
const activeTab = ref("制裁概况");
const setActiveTab = tabName => {
activeTab.value = tabName;
};
</script>
<style scoped lang="scss">
.page-container {
position: relative;
margin: 0px auto;
background-color: rgba(247, 248, 249, 1);
}
.nav-section {
display: flex;
justify-content: space-between;
align-items: center;
/* padding: 16px 24px; */
padding-left: 6%;
padding-right: 6%;
background-color: #fff;
border-bottom: 1px solid #ebeef5;
}
.tabs {
display: flex;
gap: 0;
}
.tab-item {
width: 120px;
height: 40px;
text-align: center;
line-height: 30px;
// padding: 12px 24px;
font-size: 18px;
color: #606266;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s;
box-sizing: border-box;
}
// .tab-item.active {
// color: #409eff;
// border-bottom-color: #409eff;
// background-color: rgba(64, 158, 255, 0.1);
// }
.tab-item.active {
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 30px;
border-bottom: 3px solid var(--color-main-active);
}
.tab-item:hover {
color: var(--color-main-active);
}
.action-buttons {
display: flex;
gap: 12px;
}
.content-section {
padding: 24px;
min-height: 400px;
}
.content-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
color: #909399;
}
.content-placeholder .el-icon {
font-size: 48px;
margin-bottom: 16px;
color: #dcdfe6;
}
.content-placeholder p {
font-size: 16px;
margin-top: 8px;
}
@media (max-width: 768px) {
.bill-info {
flex-direction: column;
align-items: flex-start;
}
.date-author {
align-items: flex-start;
margin-top: 12px;
}
.nav-section {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.tabs {
width: 100%;
overflow-x: auto;
}
}
.left-absolute {
position: fixed;
left: 0;
top: 60%;
// width: 48px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
background-color: #fff;
border-radius: 10px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
padding: 16px 12px;
box-shadow: 0 0 15px 0 rgba(60, 87, 126, 0.2);
div {
width: 17px;
height: 18px;
}
}
</style>
<template>
<div class="message-bubble">
<div class="avatar-container">
<img :src="avatar" :alt="name" class="avatar" />
</div>
<div class="bubble-container">
<div class="bubble">
<div class="bubble-header">
<span class="name">{{ name }}</span>
<span class="meta">{{ time }} · {{ source }}</span>
</div>
<div class="bubble-content">
{{ content }}
</div>
<div class="triangle"></div>
</div>
</div>
</div>
</template>
<script setup>
defineProps({
avatar: {
type: String,
default: "https://via.placeholder.com/40x40/4A90E2/FFFFFF?text=T"
},
name: {
type: String,
default: "唐纳德·特朗普"
},
time: {
type: String,
default: "15:23"
},
source: {
type: String,
default: "发布于真实社交"
},
content: {
type: String,
default:
"埃隆·马斯克在强力支持我竞选总统之前,早就知道我强烈反对‘电动汽车强制令’。这太荒谬了,这一直是我竞选活动的主要部分。电动汽车没问题,但不应该强迫每个人都拥有一辆。埃隆获得的补贴可能远远超过历史上任何一个人。如果没有补贴,埃隆可能不得不关门大吉,回到南非老家。"
}
});
</script>
<style scoped>
.message-bubble {
display: flex;
max-width: 700px;
margin: 20px 0;
}
.avatar-container {
flex-shrink: 0;
margin-right: 12px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.bubble-container {
flex: 1;
position: relative;
}
.bubble {
background-color: rgba(246, 250, 255, 1);
border-radius: 12px;
padding: 12px 16px;
position: relative;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.bubble-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.name {
font-weight: bold;
font-size: 16px;
color: #333;
}
.meta {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: right;
}
.bubble-content {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.triangle {
position: absolute;
left: -8px;
top: 15px;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid rgba(246, 250, 255, 1);
}
/* 响应式设计 */
@media (max-width: 768px) {
.message-bubble {
max-width: 100%;
}
.bubble-header {
flex-direction: column;
align-items: flex-start;
}
.meta {
margin-top: 4px;
}
}
</style>
<template>
<div class="info-card">
<div class="color-bar" :style="{ backgroundColor: color }"></div>
<div class="card-content">
<div class="title-section">
<div class="main-title">{{ title }}</div>
<div class="sub-title">{{ subtitle }}</div>
</div>
<div class="description">{{ description }}</div>
<div v-if="quantity > 0" class="quantity" :style="{ color: color }">{{ quantity }}</div>
</div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: "标题"
},
subtitle: {
type: String,
default: "Subtitle"
},
description: {
type: String,
default: "描述信息"
},
quantity: {
type: [Number, String],
default: 0
},
color: {
type: String,
default: "#409EFF"
}
});
</script>
<style scoped>
.info-card {
max-width: 388px;
min-width: 300px;
height: 150px;
border-radius: 12px;
background-color: white;
display: flex;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.info-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.color-bar {
width: 6px;
height: 120px;
flex-shrink: 0;
margin-top: 15px;
}
.card-content {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
position: relative;
}
.title-section {
margin-bottom: 12px;
}
.main-title {
font-size: 24px;
font-weight: 700;
color: #1f2937;
line-height: 1.2;
}
.sub-title {
font-size: 16px;
font-weight: 700;
line-height: 1.2;
color: #6b7280;
margin-top: 2px;
}
.description {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
line-height: 1.4;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.quantity {
position: absolute;
top: 20px;
right: 20px;
font-size: 32px;
font-weight: 700;
}
</style>
<template>
<div :class="['clickable-card', { disabled: disabled }]" @click="handleClick">
<span class="card-text">{{ text }}</span>
<!-- <span class="arrow-icon"></span> -->
<div class="icon">
<el-icon><ArrowRightBold /></el-icon>
</div>
</div>
</template>
<script setup>
import { ArrowRight, ArrowRightBold } from "@element-plus/icons-vue";
import { useRouter } from "vue-router";
const router = useRouter();
const props = defineProps({
text: {
type: String,
default: "点击跳转"
},
link: {
type: String,
default: ""
},
target: {
type: String,
default: "_self"
},
disabled: {
type: Boolean,
default: false
}
});
const emit = defineEmits(["click"]);
const handleClick = () => {
if (props.disabled) return;
emit("click");
if (props.link) {
if (props.link.startsWith("http") || props.link.startsWith("//")) {
// 外部链接
window.open(props.link, props.target);
} else {
router.push(props.link);
console.log(`跳转到: ${props.link}`);
}
}
};
</script>
<style lang="scss" scoped>
.clickable-card {
width: 160px;
height: 48px;
border-radius: 32px;
border: 1px solid rgba(174, 214, 255, 1);
background-color: rgba(231, 243, 255, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
/* padding: 0 16px; */
cursor: pointer;
transition: all 0.3s ease;
color: rgba(5, 95, 194, 1);
font-size: 20px;
font-weight: 500;
box-sizing: border-box;
position: relative;
.icon{
position: absolute;
top: 14px;
right: 12px;
}
}
.clickable-card:hover {
background-color: rgba(231, 243, 255, 0.5);
box-shadow: 0 2px 8px rgba(5, 95, 194, 0.1);
transform: translateY(-2px);
}
.clickable-card:active {
transform: translateY(0);
box-shadow: 0 1px 4px rgba(5, 95, 194, 0.1);
}
.clickable-card.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.clickable-card.disabled:hover {
background-color: white;
box-shadow: none;
transform: none;
}
.arrow-icon {
font-weight: bold;
font-size: 16px;
}
</style>
<template>
<div class="news-list">
<div v-for="(item, index) in listData" :key="index" class="news-item" @click="handleItemClick(item)">
<div class="news-image">
<img :src="item.image" :alt="item.title" />
</div>
<div class="news-content">
<div class="news-header">
<div class="news-title">{{ item.title }}</div>
<div class="news-meta">
<span class="news-time">{{ item.time }}</span> ·
<span class="news-source">{{ item.source }}</span>
</div>
</div>
<div class="news-description">{{ item.description }}</div>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
listData: {
type: Array,
default: () => [
{
image: "https://via.placeholder.com/72x48/4A90E2/FFFFFF?text=News",
title: "美国智库激辩人工智能监管路径",
time: "11-4",
source: "华盛顿邮报",
description: "各方就AI监管模式展开讨论。有观点认为碎片化州级监管比全面联邦法规更灵活,也有分析指出..."
}
]
}
});
const emit = defineEmits(["item-click"]);
const handleItemClick = item => {
emit("item-click", item);
};
</script>
<style scoped>
.news-list {
width: 100%;
max-width: 800px;
}
.news-item {
display: flex;
padding: 10px 0;
align-items: center;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.3s;
}
.news-item:hover {
background-color: #f9f9f9;
}
.news-item:last-child {
border-bottom: none;
}
.news-image {
width: 72px;
height: 48px;
flex-shrink: 0;
margin-right: 16px;
border-radius: 4px;
overflow: hidden;
}
.news-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.news-content {
flex: 1;
display: flex;
flex-direction: column;
}
.news-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.news-title {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
line-height: 24px;
flex: 1;
margin-right: 12px;
}
.news-meta {
display: flex;
align-items: center;
height: 24px;
line-height: 24px;
flex-shrink: 0;
font-size: 14px;
color: #999;
}
.news-time {
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: right;
}
.news-source {
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: right;
}
.news-description {
font-size: 16px;
color: rgba(59, 65, 75, 1);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<template>
<div class="custom-title">
<div class="color-block block1"></div>
<div class="color-block block2"></div>
<div class="title-text">{{ title }}</div>
<div class="color-line"></div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: "最新动态"
}
});
</script>
<style scoped>
.custom-title {
display: flex;
align-items: center;
width: 100%;
margin-bottom: 20px;
padding: 10px 15px;
}
.color-block {
background-color: rgba(174, 214, 255, 1);
height: 30px;
flex-shrink: 0;
}
.block1 {
width: 24px;
}
.block2 {
width: 8px;
margin-left: 10px;
}
.title-text {
font-size: 32px;
font-weight: 700;
margin-left: 20px;
white-space: nowrap;
}
.color-line {
background-color: rgba(174, 214, 255, 1);
height: 2px;
flex-grow: 1;
margin-left: 20px;
}
</style>
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论