提交 10eacdc2 authored 作者: 朱政's avatar 朱政

Merge branch 'master' into zz-dev

---
alwaysApply: true
---
# Overview
Insert overview text here. The agent will only see this should they choose to apply the rule.
```markdown
---
alwaysApply: true
---
# 前端开发(Cursor 专用执行要点)
## 1. HTML / 模板
- 属性命名:统一用小写-中线分割。动态 class 也用这种格式,使用单引号:`:class="{ 'xxx-xxx': isXxx }"`
- v-for 必须有唯一 key,`v-for + :key`(如`:key="item.id"`),禁止仅凭索引。示例:`<li v-for="item in list" :key="item.id"></li>`
- 禁止 v-if 与 v-for 同时在同一节点;如需筛选请用计算属性/方法过滤后再 v-for
- 无内容组件或标签必须自闭合,如 `<MyComp />` `<img />` `<input />`
- HTML 属性用双引号,动态属性外已双引号则用单引号
- 模板表达式保持极简,只写取值或很简单的显示;任何有数据处理(如数组/字符串操作、三元、函数执行等)必须抽到方法或计算属性完成
## 2. CSS
- 样式模块优先通用组件,确需定制严格按设计来写
- 全部样式变量统一来源于全局(如 `:root { --color-main-primary: #055fC2; ... }`)
- `<style scoped>` 默认加 scoped
- 修改子组件样式用 `:deep()`(Vue3 推荐写法),如:`.xxx :deep(.el-xxx) { ... }`
- 严禁页面大量内联 style,样式尽量写到 class 或 :style 绑定表达式
## 3. 文件系统
- 文件夹采用小驼峰,无数字/无关字符
- 每个业务模块 API 独立放模块对应文件夹
- 静态资源分全局 assets 与模块 assets:全局图片 assets/images,icon 建议 SVG,模块区分子文件夹
## 4. 组件
- 组件名大驼峰(PascalCase)。组件类型目录小驼峰,具体组件目录(如 AreaTag)大驼峰
- 单文件组件名 index.vue,多组件文件按具体功能如 LeftBtn.vue
- 优先用通用组件,业务组件只能放业务模块,不得放全局
## 5. JS/TS
- 命名统一规范
- 变量:小驼峰 userName、isVisible
- 常量:大写+下划线,如 MAX_COUNT
- 枚举:枚举名大驼峰,枚举值全大写+下划线 enum Status { SUCCESS = 'SUCCESS' }
- 普通函数:小驼峰+动词前缀 getUser/formatTime
- 事件函数:小驼峰+handle/on handleSubmit/onClose
- 布尔型函数:is/has/should + 大驼峰,如 isValid()
- 异步强制 async/await,catch 错误。禁止 Promise.then 链式嵌套(如遇回调 hell 必须拆分/抽象重写)
- 一律用 const/let 替换 var,优先 const
- 注释:
- 单行注释:后加空格,如 `// 注释内容`
- 方法/复杂模块加 /** JSDoc 注释 */
## 6. 其它
- 所有复杂渲染逻辑逻辑一律抽到计算属性或方法,模板结构保持简洁
...@@ -7,6 +7,10 @@ yarn-error.log* ...@@ -7,6 +7,10 @@ yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
*.rar
*.zip
*.7z
# Dependencies # Dependencies
node_modules node_modules
.pnpm .pnpm
...@@ -14,6 +18,7 @@ node_modules ...@@ -14,6 +18,7 @@ node_modules
# Build outputs # Build outputs
dist dist
dist.rar
dist-ssr dist-ssr
*.local *.local
......
...@@ -2,85 +2,11 @@ ...@@ -2,85 +2,11 @@
<div id="app"> <div id="app">
<div class="pro-wrapper"> <div class="pro-wrapper">
<div class="home-page"> <div class="home-page">
<!-- <div class="navbar"> <ModuleHeader/>
<div class="nav-content">
<div class="nav-left">
<div class="icon">
<img src="@/assets/icons/overview/logo.png" alt="" />
</div>
<div class="title-box">
<div class="title" v-for="(item, index) in homeTitleList" :key="index"
@mouseenter="handleShowMenu(index, true)" @mouseleave="handleShowMenu(index, false)"
@click="handleClickTitle(item)">
<div class="text" :class="{ textActive: homeActiveTitleIndex === index }">
{{ item.name }}
</div>
<div class="bottom-line" v-if="homeActiveTitleIndex === index"></div>
</div>
</div>
</div>
<div class="nav-right">
<div class="search-box">
<div class="input"><el-input type="text" v-model="searchText" @keyup.enter="handleSearch" /></div>
<div class="icon" @click="handleSearch">
<img src="@/assets/icons/overview/search.png" alt="" />
</div>
</div>
<div class="info-box" @click="handleClickToolBox">
<div class="mail">
<img src="@/assets/icons/overview/mail.png" alt="" />
</div>
<div class="user">
<img src="@/assets/icons/overview/user.png" alt="" />
</div>
<div class="name">{{ "管理员" }}</div>
</div>
</div>
<div class="menu-box" v-if="isShowMenu" @mouseenter="handleHoverMenu(true)"
@mouseleave="handleHoverMenu(false)">
<div class="menu-content">
<div class="menu-item" v-for="(item, index) in menuList" :key="index" @click="handleToModule(item)">
<div class="icon">
<img :src="item.icon" alt="" />
</div>
<div class="title">{{ item.title }}</div>
</div>
</div>
</div>
</div>
</div> -->
<NavBarV2/>
<div class="main-container"> <div class="main-container">
<router-view /> <router-view />
</div> </div>
</div> </div>
<!-- <div class="content-page" v-if="!isCurrentOverview">
<div class="navbar">
<div class="nav-brand">
<div class="brand-icon">
<img src="@/assets/icons/header-logo.png" alt="" />
</div>
<div class="brand-text" @click="handleToHome">
<div class="text-ch">某方向风险监测预警系统</div>
</div>
</div>
<div class="user-info">
<div class="email">
<img src="@/assets/icons/header-icon.png" alt="" />
</div>
<div class="avator">
<img src="@/assets/icons/header-avator.png" alt="" />
</div>
<span class="user">管理员</span>
</div>
</div>
<div class="main-container">
<router-view />
</div>
</div> -->
<div class="right-btn" @click="handleClickToolBox"> <div class="right-btn" @click="handleClickToolBox">
<div class="item"> <div class="item">
<div class="icon"> <div class="icon">
...@@ -127,10 +53,8 @@ ...@@ -127,10 +53,8 @@
<script setup> <script setup>
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
import { Monitor, House, User, Location, Document, Bell, Message, ArrowDown } from "@element-plus/icons-vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import Breadcrumb from "@/components/BreadCrumb/index.vue";
import AiBox from "./components/AiBox.vue"; import AiBox from "./components/AiBox.vue";
import { getPersonType } from "@/api/common/index"; import { getPersonType } from "@/api/common/index";
// import { useDraggable } from "@vueuse/core"; // import { useDraggable } from "@vueuse/core";
......
...@@ -104,22 +104,32 @@ export function getBillPostOrg(params) { ...@@ -104,22 +104,32 @@ export function getBillPostOrg(params) {
}) })
} }
// 获取关键议员提案 // 获取涉华法案进展分布
/** /**
* @param {year} * @param {year}
*/ */
export function getMemberProposal(params) { export function getBillProcess(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/BillOverview/memberProposal/${params.year}`, url: `/bill/BillOverview/billsProcess/${params.year}`,
}) })
} }
// 获取资源库 // 获取资源库法案
export function getBills(params, signal) { export function getBills(params, signal) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/BillOverview/bills`, url: `/bill/BillOverview/bills`,
params,
signal
})
}
// 获取资源库国会议员
export function getBillsPerson(params, signal) {
return request({
method: 'GET',
url: `/bill/BillOverview/billsPerson`,
params, params,
signal signal
}) })
......
<template>
<div
class="card-title-custom"
:style="{ '--left-offset': leftOffset }"
>
{{ props.title }}
</div>
</template>
<script setup>
const props = defineProps({
title: {
type: String,
required: true
},
leftOffset: {
type: String,
default: '-22px'
}
})
</script>
<style scoped>
.card-title-custom {
font-size: 16px;
font-weight: 600;
color: #333;
position: relative;
}
.card-title-custom::before {
content: '';
position: absolute;
left: var(--left-offset, -22px);
top: 50%;
transform: translateY(-50%);
width: 8px;
height: 16px;
background-color: #1459BB;
}
</style>
\ No newline at end of file
<template>
<div class="hello-world">
<el-card>
<template #header>
<div class="card-header">
<el-icon><Star /></el-icon>
<span>Hello World 组件</span>
</div>
</template>
<div class="content">
<h3>{{ title }}</h3>
<p>{{ description }}</p>
<el-divider />
<div class="counter-section">
<h4>计数器示例</h4>
<div class="counter">
<el-button type="primary" :icon="Minus" @click="decrement" :disabled="count <= 0"></el-button>
<el-input-number v-model="count" :min="0" :max="100" style="margin: 0 10px;"></el-input-number>
<el-button type="primary" :icon="Plus" @click="increment" :disabled="count >= 100"></el-button>
</div>
<el-progress :percentage="count" :color="progressColor" style="margin-top: 20px;"></el-progress>
</div>
<el-divider />
<div class="theme-section">
<h4>主题切换</h4>
<el-radio-group v-model="theme" @change="handleThemeChange">
<el-radio-button label="light">浅色主题</el-radio-button>
<el-radio-button label="dark">深色主题</el-radio-button>
</el-radio-group>
</div>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Star, Plus, Minus } from '@element-plus/icons-vue'
// Props
const props = defineProps({
title: {
type: String,
default: '欢迎使用 Vue 3 + Element Plus'
},
description: {
type: String,
default: '这是一个可复用的组件示例,展示了 Vue 3 Composition API 的强大功能。'
}
})
// 响应式数据
const count = ref(0)
const theme = ref('light')
// 计算属性
const progressColor = computed(() => {
if (count.value < 30) return '#f56c6c'
if (count.value < 70) return '#e6a23c'
return '#67c23a'
})
// 方法
const increment = () => {
if (count.value < 100) {
count.value++
if (count.value === 100) {
ElMessage.success('恭喜!达到最大值!')
}
}
}
const decrement = () => {
if (count.value > 0) {
count.value--
if (count.value === 0) {
ElMessage.info('已归零')
}
}
}
const handleThemeChange = (value) => {
ElMessage.success(`已切换到${value === 'light' ? '浅色' : '深色'}主题`)
}
// 暴露给父组件的方法
defineExpose({
reset: () => {
count.value = 0
theme.value = 'light'
ElMessage.success('组件已重置')
}
})
</script>
<style scoped>
.hello-world {
margin: 20px 0;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: bold;
}
.content h3 {
color: #409eff;
margin-bottom: 12px;
}
.content p {
color: #606266;
line-height: 1.6;
}
.counter-section,
.theme-section {
text-align: center;
}
.counter-section h4,
.theme-section h4 {
color: #303133;
margin-bottom: 16px;
}
.counter {
display: flex;
align-items: center;
justify-content: center;
}
</style>
\ No newline at end of file
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
</div> </div>
</div> </div>
<div class="box3-main"> <div class="box3-main">
<div class="box3-item" v-for="(news, index) in list.slice(0,5)" :key="index" @click="handleClickToNewsDetail(news)"> <div class="box3-item" v-for="(news, index) in list" :key="index" @click="handleClickToNewsDetail(news)">
<div class="left"> <div class="left">
<img <img
:src="getProxyUrl(news.newsImage) || defaultImg" :src="getProxyUrl(news.newsImage) || defaultImg"
......
<template>
<div class="policy-list">
<div
v-for="(item, index) in policyList"
:key="index"
class="policy-item"
>
<div class="item-left">
<div class="report-cover">
<img :src="$withFallbackImage(item.imageUrl, index)" alt="Report Cover" />
</div>
</div>
<div class="item-right">
<h3 class="item-title">
{{ item.content }}
</h3>
<div class="item-meta">
<span class="meta-date">{{ formatDate(item.times) }}</span>
<span class="meta-divider">·</span>
<span class="meta-source">
{{ item.name }}
<el-icon class="link-icon"><TopRight /></el-icon>
</span>
</div>
<div class="item-tags" v-if="item.tags && item.tags.length">
<span v-for="(tag, tIndex) in item.tags" :key="tIndex" class="tag-pill">
{{ tag }}
</span>
</div>
<div class="item-actions" v-if="item.statusRaw">
<div
v-for="(statusItem, sIndex) in parseStatus(item.statusRaw)"
:key="sIndex"
class="status-link"
>
<span class="status-type">{{ statusItem.type }}</span>
<span class="status-year">{{ statusItem.year }}</span>
<span class="status-name">{{ statusItem.name }}</span>
<el-icon class="arrow-icon"><Right /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { TopRight, Right } from '@element-plus/icons-vue'
interface PolicyItem {
content: string;
statusRaw: string; // 原始的长字符串
name: string;
times: string;
tags?: string[];
coverUrl?: string;
}
const props = defineProps<{
policyList: PolicyItem[]
}>()
// 格式化日期:2025-06-26 -> 2025年6月26日
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
}
// 解析状态字符串
// 输入: "法案 2024 《芯片科学法案》; 政令 2025 《推动美国...》"
// 输出: 数组对象
const parseStatus = (raw: string) => {
if (!raw) return [];
// 按分号分割多个条目
const items = raw.split(/[;;]/).map(s => s.trim()).filter(s => s);
return items.map(itemStr => {
// 简单正则匹配: "类型 年份 《名称》"
// 注意:这里假设数据格式比较规范,实际需根据后端数据调整
// 尝试移除书名号进行提取
const cleanStr = itemStr.replace(/[《》]/g, '');
const parts = cleanStr.split(' ');
return {
type: parts[0] || '政策',
year: parts[1] || '',
name: parts.slice(2).join(' ') || cleanStr // 剩余部分作为名称
}
});
}
</script>
<style scoped>
.policy-list {
display: flex;
flex-direction: column;
}
.policy-item {
display: flex;
padding: 20px 0;
border-bottom: 1px solid #ebeef5;
gap: 16px;
transition: background-color 0.2s;
}
.policy-item:last-child {
border-bottom: none;
}
/* 左侧封面 */
.item-left {
flex-shrink: 0;
}
.report-cover {
width: 60px;
height: 80px;
background-color: #f2f3f5;
border: 1px solid #e4e7ed;
border-radius: 2px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.report-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 右侧内容 */
.item-right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 1. 标题 */
.item-title {
margin: 0 0 6px 0;
font-size: 16px;
font-weight: 700;
color: #1a1a1a;
line-height: 1.4;
cursor: pointer;
}
.item-title:hover {
color: #409EFF;
}
/* 2. 元数据 */
.item-meta {
display: flex;
align-items: center;
font-size: 13px;
color: #606266;
margin-bottom: 8px;
}
.meta-divider {
margin: 0 8px;
font-weight: bold;
}
.meta-source {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
}
.meta-source:hover {
color: #409EFF;
}
.link-icon {
font-size: 12px;
}
/* 3. 标签 */
.item-tags {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.tag-pill {
background-color: #f2f3f5;
color: #5e6d82;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
}
/* 4. 底部状态链接 */
.item-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.status-link {
display: inline-flex;
align-items: center;
background-color: #ecf5ff; /* 浅蓝色背景 */
color: #409EFF; /* 蓝色文字 */
padding: 4px 12px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.status-link:hover {
background-color: #d9ecff;
}
.status-type {
font-weight: bold;
margin-right: 4px;
}
.status-year {
margin-right: 4px;
}
.status-name {
margin-right: 4px;
}
.arrow-icon {
margin-left: 4px;
font-size: 12px;
}
</style>
\ No newline at end of file
<template>
<div class="policy-tracker-container">
<PolicyOverview />
<!-- 顶部搜索栏 -->
<div class="top-search-bar" v-if="props.showSearch">
<div class="search-left">
<el-input
v-model="searchQuery"
placeholder="搜索政策建议"
class="search-input"
size="default"
>
<template #suffix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
<div class="search-right">
<el-select v-model="dateRange" class="select-item" size="default">
<el-option label="近一年发布" value="last_year" />
<el-option label="近三年发布" value="last_three_years" />
</el-select>
<el-select v-model="sortOrder" class="select-item" size="default">
<el-option label="发布时间" value="time" />
<el-option label="热度" value="popularity" />
</el-select>
</div>
</div>
<el-row :gutter="24">
<el-col :span="6">
<aside class="filter-sidebar">
<div class="filter-group">
<CardTitle title="实施状态" style="margin-bottom: 10px"/>
<div class="checkbox-group">
<el-checkbox-group v-model="activeStatus">
<el-checkbox
v-for="status in statusFilters"
:key="status.id"
:label="status.id"
class="filter-checkbox"
>
<span
class="status-dot"
:style="`background-color: ${status.color}; border-color: ${status.borderColor || status.color || '#dcdfe6'}`"
></span>
{{ status.label }} ({{ status.count }})
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="filter-group">
<CardTitle title="科技领域" style="margin-bottom: 10px"/>
<div class="checkbox-group">
<el-checkbox-group v-model="activeTechField">
<el-checkbox
v-for="field in techFieldFilters"
:key="field.id"
:label="field.id"
class="filter-checkbox tech-checkbox"
>
{{ field.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</aside>
</el-col>
<el-col :span="18">
<div class="main-content">
<PolicyList :policyList="policies" />
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Search } from '@element-plus/icons-vue';
import PolicyList from './PolicyList.vue';
import CardTitle from './CardTitle.vue';
import { getOverviewPolicy } from '@/api'
import PolicyOverview from '@/views/thinkTank/components/PolicyOverview.vue'
import { mockPolicyList } from '@/views/thinkTank/mockData';
const props = defineProps({
showSearch: {
type: Boolean,
default: true
}
})
// --- Sidebar Filters State & Data ---
const activeStatus = ref(['implemented']); // 改为数组支持多选
const activeTechField = ref(['ai']); // 改为数组支持多选
const statusFilters = ref([
{ id: 'implemented', label: '已实施', count: 18, color: '#e66657' },
{ id: 'partial', label: '部分实施', count: 12, color: '#e6a23c' },
{ id: 'unimplemented', label: '未实施', count: 12, color: '#909399' },
{ id: 'unknown', label: '未知状态', count: 4, color: 'transparent', borderColor: '#909399' },
]);
const techFieldFilters = ref([
{ id: 'ai', label: '人工智能' },
{ id: 'semiconductor', label: '半导体/芯片' },
{ id: 'energy', label: '能源与气候' },
{ id: 'international', label: '国际关系' },
{ id: 'economy', label: '经济决策' },
{ id: 'national_security', label: '国防与安全' },
]);
// --- Main Content State ---
const searchQuery = ref('');
const dateRange = ref('last_year');
const sortOrder = ref('time');
const policies = ref([]);
const getPolicies = async () => {
const { data } = await getOverviewPolicy({
researchTypeIds: activeTechField.value,
statusList: activeStatus.value,
})
// policies.value = data
policies.value = mockPolicyList
}
onMounted(() => {
getPolicies()
})
</script>
<style scoped>
.policy-tracker-container {
padding-top: 0;
}
/* 顶部搜索栏样式 */
.top-search-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.search-left {
flex: 1;
max-width: 400px;
}
.search-input {
width: 100%;
}
.search-input :deep(.el-input__wrapper) {
border-radius: 6px;
box-shadow: 0 0 0 1px #dcdfe6;
transition: box-shadow 0.3s ease;
}
.search-input :deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #409EFF;
}
.search-input :deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #409EFF;
}
.search-right {
display: flex;
align-items: center;
gap: 12px;
}
.select-item {
width: 140px;
}
.select-item :deep(.el-input__wrapper) {
border-radius: 6px;
box-shadow: 0 0 0 1px #dcdfe6;
transition: box-shadow 0.3s ease;
}
.select-item :deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #409EFF;
}
.main-content {
background-color: #fff;
padding: 8px;
}
/* --- Sidebar Styles --- */
.filter-sidebar {
background-color: #fff;
padding: 20px;
border-radius: 4px;
}
.filter-group {
margin-bottom: 25px;
}
.filter-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin: 0 0 15px 0;
display: flex;
align-items: center;
}
.implementation-title {
padding-left: 12px;
position: relative;
}
.implementation-title::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background-color: #409EFF;
border-radius: 2px;
}
.icon-square {
display: inline-block;
width: 4px;
height: 16px;
background-color: #409EFF;
margin-right: 8px;
border-radius: 2px;
}
.filter-sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
/* 复选框组样式 */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.filter-checkbox {
margin: 0 !important;
padding: 8px 12px;
border-radius: 4px;
transition: all 0.3s ease;
width: 100%;
display: flex;
align-items: center;
}
.filter-checkbox:hover {
background-color: #f0f2f5;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 0;
border: 1px solid transparent;
display: inline-block;
flex-shrink: 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.top-search-bar {
flex-direction: column;
gap: 16px;
padding: 16px;
}
.search-left {
max-width: none;
width: 100%;
}
.search-right {
width: 100%;
justify-content: center;
gap: 8px;
}
.select-item {
width: 120px;
}
}
@media (max-width: 480px) {
.search-right {
flex-direction: column;
gap: 8px;
}
.select-item {
width: 100%;
max-width: 200px;
}
}
</style>
\ No newline at end of file
差异被折叠。
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
<div class="num">{{ list.length }}</div> <div class="num">{{ list.length }}</div>
</div> </div>
</div> </div>
<div class="box2-main"> <div class="box2-main" v-infinite-scroll="loadMore" :infinite-scroll-disabled="allLoaded || loading"
:infinite-scroll-distance="10" v-loading="loading">
<div class="box2-main-item" v-for="(item, index) in list" :key="index" @click="handleItemClick(item, index)"> <div class="box2-main-item" v-for="(item, index) in list" :key="index" @click="handleItemClick(item, index)">
<div :class="{ <div :class="{
...@@ -37,6 +38,7 @@ ...@@ -37,6 +38,7 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
// 接收父组件传递的参数 // 接收父组件传递的参数
const props = defineProps({ const props = defineProps({
...@@ -72,11 +74,19 @@ const props = defineProps({ ...@@ -72,11 +74,19 @@ const props = defineProps({
type: String, type: String,
default: "riskLevel" default: "riskLevel"
}, },
allLoaded: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
},
}); });
// 定义自定义事件,把点击事件传递给父组件 // 定义自定义事件,把点击事件传递给父组件
const emit = defineEmits(['item-click', 'more-click']); const emit = defineEmits(['item-click', 'more-click', 'loadMore']);
// 点击单条风险信号 // 点击单条风险信号
const handleItemClick = (item, index) => { const handleItemClick = (item, index) => {
...@@ -85,10 +95,84 @@ const handleItemClick = (item, index) => { ...@@ -85,10 +95,84 @@ const handleItemClick = (item, index) => {
// 点击“查看更多” // 点击“查看更多”
const handleMoreClick = () => { const handleMoreClick = () => {
emit('more-click') emit('more-click')
}; };
// const allLoaded = ref(false)
// const loading = ref(false)
// const currentPage = ref(0)
// const pageSize = ref(7)
// const loadMore = async () => {
// if (allLoaded.value || loading.value) return;
// // await fetchListData(true); // 传入 true 表示追加数据
// ElMessage.success('追加数据')
// }
// // 添加初始化函数来获取数据
// const fetchListData = async (append = false) => {
// console.log("加载状态 =>", loading.value, allLoaded.value);
// if (loading.value || allLoaded.value) return;
// try {
// loading.value = true;
// // 调用接口获取数据
// const response = await getStrategiesTopN(currentPage.value, pageSize.value, countryName.value);
// const apiData = response.data || [];
// // 转换数据格式
// const newData =
// apiData.content?.map(item => ({
// ...item,
// img: item.countryImageUrl, // 使用默认图片或根据需要调整
// title: item.titleZh,
// content: item.contentZh,
// time: formatTime(item.date), // 转换时间格式
// tagList: item.domains
// ? item.domains.slice(0, 2).map(field => ({
// name: field,
// status: Math.floor(Math.random() * 3) + 1 // 随机分配 1-3 的状态值
// }))
// : []
// })) || [];
// if (newData.length > 0) {
// if (append) {
// // 追加数据
// list.value = [...list.value, ...newData];
// } else {
// // 替换数据
// list.value = [...newData];
// }
// // 检查是否还有更多数据(根据实际 API 响应调整判断逻辑)
// if (newData.length < pageSize.value) {
// allLoaded.value = true;
// } else {
// currentPage.value++;
// }
// } else {
// allLoaded.value = true;
// }
// console.log("获取策略数据成功:", list.value);
// } catch (error) {
// console.error("获取策略数据失败:", error);
// // 错误处理,停止加载状态但不重置数据
// if (!append && list.value.length === 0) {
// list.value = [];
// }
// } finally {
// loading.value = false;
// }
// };
const loadMore = () => {
emit('loadMore')
}
</script> </script>
......
<template> <template>
<div class="search-container" v-show="!isShow"> <div class="search-container" v-show="!isShow">
<div class="search-main"> <div class="search-type-tabs" v-if="enableBillTypeSwitch">
<div class="search-type-tab" :class="{ active: billSearchType === 'federal' }"
@click="handleChangeBillSearchType('federal')">
联邦议会
</div>
<div class="search-type-tab" :class="{ active: billSearchType === 'state' }"
@click="handleChangeBillSearchType('state')">
州议会
</div>
</div>
<div class="search-main" :class="{ 'search-main-with-tabs': enableBillTypeSwitch }">
<input v-model="store.searchBillText" :placeholder="placeholder" @keyup.enter="handleSearch" <input v-model="store.searchBillText" :placeholder="placeholder" @keyup.enter="handleSearch"
class="search-input" /> class="search-input" />
<div class="search-btn" @click="handleSearch"> <div class="search-btn" @click="handleSearch">
...@@ -44,19 +54,26 @@ ...@@ -44,19 +54,26 @@
</template> </template>
<script setup> <script setup>
import { ref, nextTick, watchEffect, onMounted } from "vue"; import { ref, nextTick, watchEffect } from "vue";
import { useContainerScroll } from "@/hooks/useScrollShow"; import { useContainerScroll } from "@/hooks/useScrollShow";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore"; import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
const store = useWrittingAsstaintStore(); const store = useWrittingAsstaintStore();
const router = useRouter(); const router = useRouter();
let { countInfo, containerRef, placeholder, areaName } = defineProps({
const {
countInfo,
containerRef,
placeholder,
areaName,
enableBillTypeSwitch,
defaultBillSearchType
} = defineProps({
countInfo: { countInfo: {
type: Array, type: Array,
default: () => [] default: () => []
}, },
containerRef: { containerRef: {
type: Object, type: Object,
default: {} default: {}
...@@ -68,18 +85,48 @@ let { countInfo, containerRef, placeholder, areaName } = defineProps({ ...@@ -68,18 +85,48 @@ let { countInfo, containerRef, placeholder, areaName } = defineProps({
areaName: { areaName: {
type: String, type: String,
default: "法案" default: "法案"
},
// 法案页专用:是否展示“联邦议会/州议会”搜索类型切换
// 其他页面默认 false,不受影响
enableBillTypeSwitch: {
type: Boolean,
default: false
},
// 法案页专用:默认搜索类型
// 可选值:'federal'(联邦议会)| 'state'(州议会)
defaultBillSearchType: {
type: String,
default: "federal"
} }
}); });
// 法案搜索类型状态(仅在 enableBillTypeSwitch=true 时生效)
// 维护说明:
// - federal: 联邦议会
// - state: 州议会
const billSearchType = ref(defaultBillSearchType === "state" ? "state" : "federal");
const handleChangeBillSearchType = type => {
billSearchType.value = type;
};
const handleSearch = () => { const handleSearch = () => {
window.sessionStorage.setItem("curTabName", `搜索-${store.searchBillText}`); window.sessionStorage.setItem("curTabName", `搜索-${store.searchBillText}`);
if (!areaName) return; if (!areaName) return;
const curRoute = router.resolve({ const query = {
path: "/searchResults",
query: {
searchText: store.searchBillText, searchText: store.searchBillText,
areaName: areaName areaName: areaName
};
// 法案页附带搜索类型参数,便于搜索结果页后续按类型处理
if (enableBillTypeSwitch) {
query.billSearchType = billSearchType.value;
} }
const curRoute = router.resolve({
path: "/searchResults",
query
}); });
window.open(curRoute.href, "_blank"); window.open(curRoute.href, "_blank");
}; };
...@@ -133,6 +180,42 @@ const handleToPosi = id => { ...@@ -133,6 +180,42 @@ const handleToPosi = id => {
height: 168px; height: 168px;
margin: 0 auto 68px auto; margin: 0 auto 68px auto;
.search-type-tabs {
display: flex;
align-items: flex-end;
height: 41px;
gap: 2px;
.search-type-tab {
width: 176px;
height: 41px;
line-height: 48px;
text-align: center;
border-radius: 10px 10px 0 0;
border: 1px solid rgb(255, 255, 255);
border-bottom: none;
background: rgba(255, 255, 255, 0.65);
color: rgb(95, 101, 108);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 41px;
cursor: pointer;
padding: 0 16px;
box-sizing: border-box;
}
.search-type-tab.active {
background: rgba(231, 243, 255, 1);
color: rgb(5, 95, 194);
border-color: rgb(255, 255, 255);
}
}
.search-main-with-tabs {
border-top-left-radius: 0 !important;
}
.search-center { .search-center {
width: 688px; width: 688px;
height: 48px; height: 48px;
......
<template>
<button class="action-button" :type="type">
{{ name }}
</button>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'normal'
},
name: {
type: String,
default: ''
}
})
</script>
<style scoped>
.action-button {
height: 28px;
padding: 0 8px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
cursor: pointer;
line-height: 26px;
font-family: Source Han Sans CN;
font-size: 16px;
font-weight: 400;
transition: all 0.3s;
}
.action-button[type="normal"] {
background-color: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
}
.action-button[type="active"] {
background-color: rgba(231, 243, 255, 1);
color: var(--color-main-active);
border: 1px solid var(--color-main-active);
}
/* 悬停效果
.action-button[type="normal"]:hover {
background-color: #d9d9d9;
}
.action-button[type="active"]:hover {
background-color: #40a9ff;
} */
</style>
\ No newline at end of file
...@@ -30,7 +30,6 @@ const classObject = computed(() => ({ ...@@ -30,7 +30,6 @@ const classObject = computed(() => ({
'tag13': props.tagName === '极地', 'tag13': props.tagName === '极地',
'tag14': props.tagName === '核', 'tag14': props.tagName === '核',
'tag15': props.tagName === '其他', 'tag15': props.tagName === '其他',
})) }))
...@@ -50,6 +49,9 @@ const classObject = computed(() => ({ ...@@ -50,6 +49,9 @@ const classObject = computed(() => ({
letter-spacing: 0px; letter-spacing: 0px;
box-sizing: border-box; box-sizing: border-box;
border-radius: 4px; border-radius: 4px;
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
} }
.tag1 { .tag1 {
......
<template>
<button class="main-button" :type="type">
<div class="icon">
<slot name="button-icon"></slot>
</div>
<div class="button-text">{{ name }}</div>
</button>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'normal'
},
name: {
type: String,
default: ''
}
})
</script>
<style scoped>
.main-button {
height: 36px;
padding: 0 16px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
cursor: pointer;
line-height: 34px;
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: 400;
transition: all 0.3s;
}
.main-button[type="normal"] {
background-color: rgba(255, 255, 255, 1);
color: var(--color-main-primary);
}
.main-button[type="active"] {
background-color: var(--color-main-active);
color: #fff;
border: 1px solid var(--color-main-active);
}
/* 悬停效果
.main-button[type="normal"]:hover {
background-color: #d9d9d9;
}
.main-button[type="active"]:hover {
background-color: #40a9ff;
} */
</style>
\ No newline at end of file
<template>
<div class="source-tab-list-wrapper" :style="{ width: width }">
<div class="tab-item" :class="{ tabItemActive: activeSouceTabId === item.id }" v-for="item, index in sourceTabList"
:key="index" @click="handleClcikTab(item)">
{{ item.name }}
</div>
</div>
</template>
<script setup>
const props = defineProps({
width: {
type: String,
default: '1000px'
},
sourceTabList: {
type: Array,
default: [
]
},
activeSouceTabId: {
type: [String, Number],
default: ''
}
})
const emit = defineEmits('clickTab')
const handleClcikTab = (tab) => {
emit('clickTab', tab)
}
</script>
<style lang="scss" scoped>
.source-tab-list-wrapper {
height: 42px;
display: flex;
justify-content: flex-start;
gap: 12px;
.tab-item {
height: 42px;
line-height: 42px;
padding: 0 16px;
font-size: 20px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
font-family: Source Han Sans CN;
text-align: center;
cursor: pointer;
&:hover {
color: var(--color-main-active);
}
}
.tabItemActive {
background: var(--color-main-active);
color: #fff;
font-weight: 700;
border-radius: 21px;
&:hover {
color: #fff;
}
}
}
</style>
\ No newline at end of file
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
<div class="wrapper-header"> <div class="wrapper-header">
<div class="header-icon"></div> <div class="header-icon"></div>
<div class="header-title">{{ title }}</div> <div class="header-title">{{ title }}</div>
<div class="header-btn" v-if="!showAllBtn">
<slot name="header-btn"></slot>
</div>
<div class="header-btn1" v-else>
<slot name="header-btn"></slot>
</div>
<div class="header-right"> <div class="header-right">
<div class="header-right-btn" @click="handleSave" v-if="showAllBtn"> <div class="header-right-btn" @click="handleSave" v-if="showAllBtn">
<img src="@/assets/icons/box-header-icon1.png" alt=""> <img src="@/assets/icons/box-header-icon1.png" alt="">
...@@ -22,6 +28,7 @@ ...@@ -22,6 +28,7 @@
</template> </template>
<script setup> <script setup>
import { ElMessage } from 'element-plus'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
const props = defineProps({ const props = defineProps({
...@@ -44,17 +51,23 @@ const props = defineProps({ ...@@ -44,17 +51,23 @@ const props = defineProps({
}) })
const handleSave = () => { const handleSave = () => {
alert('save') ElMessage.success('保存当前内容')
// emit('save')
} }
const handleDownload = () => { const handleDownload = () => {
alert('download') ElMessage.success('下载当前内容')
// emit('download')
} }
const handleCollect = () => { const handleCollect = () => {
alert('collect') ElMessage.success('收藏当前内容')
// emit('collect')
} }
const emit = defineEmits(['save','download','collect'])
</script> </script>
...@@ -83,19 +96,39 @@ const handleCollect = () => { ...@@ -83,19 +96,39 @@ const handleCollect = () => {
margin-left: 14px; margin-left: 14px;
margin-top: 14px; margin-top: 14px;
height: 26px; height: 26px;
// color: var(--color-main-active);
// font-family: Source Han Sans CN;
// font-size: 20px;
// font-weight: 700;
// line-height: 26px;
// letter-spacing: 0px;
height: 26px;
color: var(--color-main-active); color: var(--color-main-active);
font-family: Source Han Sans CN; font-family: Microsoft YaHei;
font-style: Bold;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
line-height: 26px; line-height: 26px;
letter-spacing: 0px; }
.header-btn {
position: absolute;
top: 14px;
right: 84px;
// display: flex;
// justify-content: flex-end;
// gap: 8px;
}
.header-btn1 {
position: absolute;
top: 14px;
right: 104px;
} }
.header-right { .header-right {
position: absolute; position: absolute;
top: 14px; top: 14px;
right: 12px; right: 14px;
height: 28px; height: 28px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
...@@ -117,9 +150,8 @@ const handleCollect = () => { ...@@ -117,9 +150,8 @@ const handleCollect = () => {
.wrapper-main { .wrapper-main {
height: calc(100% - 45px); height: calc(100% - 45px);
overflow: hidden; overflow: hidden;
overflow-y: auto; // overflow-y: auto;
box-sizing: border-box; padding: 5px auto;
padding: 5px 10px;
} }
} }
</style> </style>
<template>
<div class="overview-main-box-wrapper"
:style="{ width: width ? width : '1064px', height: height ? height : '450px' }">
<div class="overview-main-box-header">
<div class="header-left">
<div class="header-icon">
<slot name="header-icon"></slot>
</div>
<div class="header-title">{{ title }}</div>
</div>
<div class="header-right" @click="handleClickToDetail()">
{{ "查看详情 >" }}
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const emit = defineEmits(['toDetail'])
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
}
})
const handleClickToDetail = () => {
emit('toDetail')
}
</script>
<style lang="scss" scoped>
.overview-main-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.overview-main-box-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
box-sizing: border-box;
.header-left {
display: flex;
.header-icon {
width: 18px;
height: 18px;
margin-top: 13.7px;
margin-left: 18.7px;
}
.header-title {
margin-left: 21px;
height: 48px;
padding: 0 16px;
background: var(--color-main-active);
color: #fff;
font-family: Source Han Sans CN;
font-size: 20px;
font-weight: 700;
line-height: 48px;
text-align: center;
}
}
.header-right {
margin-right: 27px;
margin-top: 12px;
height: 24px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.wrapper-main {
height: calc(100% - 48px);
overflow: hidden;
// position: relative;
}
}
</style>
<template> <template>
<div class="overview-box-wrapper" :style="{ width: width ? width : '640px', height: height ? height : '415px' }"> <div class="overview-normal-box-wrapper"
<div class="wrapper-header"> :style="{ width: width ? width : '1064px', height: height ? height : '460px' }">
<div class="header-icon"></div> <div class="overview-normal-box-header">
<div class="header-title">{{ title }}</div> <div class="header-left">
<div class="header-right"> <div class="header-icon">
<div class="header-right-btn" @click="handleSave"> <slot name="header-icon"></slot>
<img src="@/assets/icons/box-header-icon1.png" alt="">
</div>
<div class="header-right-btn" @click="handleDownload">
<img src="@/assets/icons/box-header-icon2.png" alt="">
</div> </div>
<div class="header-right-btn" @click="handleCollect"> <div class="header-title">{{ title }}</div>
<img src="@/assets/icons/box-header-icon3.png" alt="">
</div> </div>
<div class="header-right">
<slot name="header-right"></slot>
</div> </div>
</div> </div>
<div class="wrapper-main"> <div class="wrapper-main">
...@@ -39,81 +37,54 @@ const props = defineProps({ ...@@ -39,81 +37,54 @@ const props = defineProps({
} }
}) })
const handleSave = () => {
alert('save')
}
const handleDownload = () => {
alert('download')
}
const handleCollect = () => {
alert('collect')
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.overview-box-wrapper { .overview-normal-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1); border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
position: relative;
.wrapper-header { .overview-normal-box-header {
height: 45px; height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex; display: flex;
justify-content: space-between;
box-sizing: border-box; box-sizing: border-box;
.header-left {
display: flex;
.header-icon { .header-icon {
margin-top: 18px; width: 24px;
width: 8px; height: 24px;
height: 20px; margin-top: 12px;
background: var(--color-main-active); margin-left: 20px;
border-radius: 0 4px 4px 0;
} }
.header-title { .header-title {
margin-left: 14px; margin-left: 16px;
margin-top: 14px; height: 48px;
height: 26px;
color: var(--color-main-active); color: var(--color-main-active);
font-family: Source Han Sans CN; font-family: Source Han Sans CN;
font-style: Bold;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
line-height: 26px; line-height: 48px;
letter-spacing: 0px; text-align: center;
}
} }
.header-right { .header-right {
position: absolute; height: 48px;
top: 14px; margin-right: 28px;
right: 12px;
height: 28px;
display: flex;
justify-content: flex-end;
gap: 4px;
.header-right-btn {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
} }
} }
.wrapper-main { .wrapper-main {
height: calc(100% - 45px); height: calc(100% - 48px);
overflow: hidden; overflow: hidden;
overflow-y: auto;
box-sizing: border-box;
padding: 5px 10px;
} }
} }
</style> </style>
...@@ -120,9 +120,8 @@ const handleToMoreNews = (item) => { ...@@ -120,9 +120,8 @@ const handleToMoreNews = (item) => {
}; };
</script> </script>
<style scoped> <style lang="scss" scoped>
.box4 { .box4 {
margin-left: 20px;
width: 792px; width: 792px;
height: 450px; height: 450px;
border-radius: 10px; border-radius: 10px;
......
<template> <template>
<div class="navbarV2"> <div class="module-header-wrapper">
<div class="nav-content"> <div class="nav-content">
<div class="nav-left" :class="{ 'flex-start': isShowSearchBar }"> <div class="nav-left" :class="{ 'flex-start': isShowSearchBar }">
<div class="icon"> <div class="icon">
...@@ -55,6 +55,7 @@ import { ref, computed, onMounted, watchEffect } from "vue"; ...@@ -55,6 +55,7 @@ import { ref, computed, onMounted, watchEffect } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { getPersonType } from "@/api/common/index"; import { getPersonType } from "@/api/common/index";
import SearchBar from "@/components/layout/SearchBar.vue";
import Menu1 from "@/assets/icons/overview/menu1.png"; import Menu1 from "@/assets/icons/overview/menu1.png";
import Menu2 from "@/assets/icons/overview/menu2.png"; import Menu2 from "@/assets/icons/overview/menu2.png";
...@@ -222,7 +223,7 @@ onMounted(() => { ...@@ -222,7 +223,7 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.navbarV2 { .module-header-wrapper {
width: 100%; width: 100%;
// height: 64px; // height: 64px;
border-bottom: 1px solid rgba(234, 236, 238, 1); border-bottom: 1px solid rgba(234, 236, 238, 1);
...@@ -365,7 +366,7 @@ onMounted(() => { ...@@ -365,7 +366,7 @@ onMounted(() => {
.menu-box { .menu-box {
position: absolute; position: absolute;
z-index: 999999; z-index: 999999999;
width: 713px; width: 713px;
height: 413px; height: 413px;
top: 52px; top: 52px;
......
<template>
<div class="box3-item" @click="handleToNewsAnalysis(news)">
<div class="left">
<img :src="news[props.img] ? news[props.img] : DefaultIconNews" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="title"><span class="text-inner">{{ news[props.title] }}</span></div>
<div class="time">{{ news[props.from] }}</div>
</div>
<div class="right-footer">{{ news[props.content] }}</div>
</div>
</div>
</template>
<script setup>
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
const props = defineProps({
// 新闻列表数据
news: {
type: Object,
default: () => { }
},
img: {
type: String,
default: 'img'
},
title: {
type: String,
default: "title"
},
from: {
type: String,
default: "from"
},
content: {
type: String,
default: "content"
},
});
const emit = defineEmits(['item-click', 'more-click']);
const handleToNewsAnalysis = (item, index) => {
emit('item-click', item, index)
};
</script>
<style lang="scss" scoped>
.box3-item {
display: flex;
align-items: center;
height: 78px;
margin: 0px 21px;
cursor: pointer;
&:hover {
.right-top .title {
color: rgb(5, 95, 194) !important;
font-weight: 700;
}
.right-top .text-inner {
border-bottom-color: rgb(5, 95, 194) !important;
}
}
}
.left {
width: 97px;
// flex-shrink: 0;
height: 72px;
img {
width: 100%;
height: 100%;
border-radius: 4px;
}
}
.right {
flex: 1;
min-width: 0;
margin-left: 20px;
.right-top {
display: flex;
justify-content: space-between;
.title {
// width: 500px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 700;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.text-inner {
border-bottom: 1px solid transparent;
}
}
.time {
text-align: right;
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: 'Source Han Sans CN';
font-size: 14px;
font-weight: 400;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.right-footer {
height: 48px;
/* 调整为2行的高度:24px × 2 = 48px */
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
overflow: hidden;
display: -webkit-box;
/* 关键 */
-webkit-line-clamp: 2;
/* 显示2行 */
-webkit-box-orient: vertical;
/* 垂直方向排列 */
text-overflow: ellipsis;
/* 第二行省略号 */
white-space: normal;
/* 改为 normal */
word-break: break-word;
/* 允许单词换行 */
}
}
</style>
\ No newline at end of file
<template>
<el-space @click="onClick(person)">
<ElAvatar :size="48" :src="person[img]" alignment="center" />
<el-space :size="0" direction="vertical" alignment="flex-start">
<div class="text-header">{{ person[name] }}</div>
<div class="person-position">{{ person[position] }}</div>
</el-space>
</el-space>
</template>
<script setup>
import '@/styles/common.scss'
import { ElSpace } from 'element-plus';
const props = defineProps({
// 新闻列表数据
person: {
type: Object,
default: () => { }
},
img: {
type: String,
default: 'avatarUrl'
},
name: {
type: String,
default: "name"
},
position: {
type: String,
default: "position"
},
introduction: {
type: String,
default: "introduction"
},
});
const emit = defineEmits(['item-click', 'more-click']);
const onClick = (item) => {
emit('item-click', item)
console.log(item)
};
</script>
<style lang="scss" scoped>
.person-position {
font-size: 16px;
line-height: 24px;
color: var(--text-primary-65-color);
}
</style>
\ No newline at end of file
<template>
<div class="box2">
<div class="box2-header">
<div class="icon">
<img src="./image1.png" alt="" />
</div>
<div class="title">
<div class="text">{{ title }}</div>
<div class="num">{{ list.length }}</div>
</div>
</div>
<div class="box2-main">
<div class="box2-main-item" v-for="(item, index) in list" :key="index" @click="handleItemClick(item, index)">
<div :class="{
itemLeftStatus1: item[props.riskLevel] === '特别重大',
itemLeftStatus2: item[props.riskLevel] === '重大风险',
itemLeftStatus3: item[props.riskLevel] === '较大风险',
itemLeftStatus4: item[props.riskLevel] === '一般风险' || !item[props.riskLevel],
itemLeftStatus5: item[props.riskLevel] === '低风险',
}">
{{ item[props.riskLevel] || "暂无数据" }}
</div>
<div class="item-right">
<div class="text"> <span class="text-inner">{{ item[props.name] }}</span></div>
<div class="time">{{ item[props.postDate] }}</div>
</div>
</div>
</div>
<div class="box2-footer" @click="handleMoreClick" v-if="showMore">
<div class="icon">
<img src="./image2.png" alt="" />
</div>
<div class="text">{{ moreText }}</div>
</div>
</div>
</template>
<script setup>
import { ElMessage } from "element-plus";
// 接收父组件传递的参数
const props = defineProps({
// 标题(默认“风险信号”)
title: {
type: String,
default: "风险信号"
},
// 风险信号列表数据
list: {
type: Array,
default: () => []
},
// “查看更多”文本(默认“查看更多”)
moreText: {
type: String,
default: "查看更多"
},
//控制“查看更多”是否显示,默认显示
showMore: {
type: Boolean,
default: true
},
name: {
type: String,
default: "name"
},
postDate: {
type: String,
default: "postDate"
},
riskLevel: {
type: String,
default: "riskLevel"
},
});
// 定义自定义事件,把点击事件传递给父组件
const emit = defineEmits(['item-click', 'more-click']);
// 点击单条风险信号
const handleItemClick = (item, index) => {
emit('item-click', item, index)
};
// 点击“查看更多”
const handleMoreClick = () => {
emit('more-click')
};
</script>
<style scoped lang="scss">
.risk-status-base {
width: 40px;
height: 40px;
border-radius: 20px;
font-size: 12px;
font-weight: 400;
line-height: 14px;
box-sizing: border-box;
padding: 6px 4px;
text-align: center;
}
.itemLeftStatus1 {
color: rgb(206, 79, 81) !important;
background: rgba(255, 241, 240, 1) !important;
@extend .risk-status-base
}
.itemLeftStatus2 {
color: rgba(250, 140, 22, 1) !important;
background: rgba(255, 247, 230, 1) !important;
@extend .risk-status-base
}
.itemLeftStatus3 {
color: rgba(212, 177, 6, 1) !important;
background: rgba(254, 255, 230, 1) !important;
@extend .risk-status-base
}
.itemLeftStatus4 {
color: rgba(82, 196, 26, 1) !important;
background: rgba(246, 255, 237, 1) !important;
@extend .risk-status-base
}
.itemLeftStatus5 {
color: rgba(22, 119, 255, 1) !important;
background: rgba(230, 244, 255, 1) !important;
@extend .risk-status-base
}
.box2 {
width: 520px;
height: 450px;
border-radius: 10px;
position: relative;
background: rgba(255, 255, 255, 1);
padding: 0;
box-shadow: 0 0 20px 0 rgba(25, 69, 130, 0.1);
border: 1px solid rgba(234, 236, 238, 1);
box-sizing: border-box;
overflow: hidden;
.box2-header {
height: 48px;
display: flex;
border-bottom: 1px solid rgba(240, 242, 244, 1);
.icon {
width: 24px;
height: 24px;
margin-left: 18px;
margin-top: 14px;
margin-bottom: 10px;
img {
width: 100%;
height: 100%;
}
}
.title {
display: flex;
width: 148px;
background: rgb(206, 79, 81);
margin-left: 18px;
.text {
margin-left: 16px;
height: 48px;
color: rgba(255, 255, 255, 1);
font-family: 'Source Han Sans CN';
font-size: 20px;
font-weight: 700;
line-height: 48px;
}
.num {
width: 24px;
height: 20px;
line-height: 20px;
text-align: center;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 12px;
margin-left: 15px;
margin-top: 15px;
border-radius: 100px;
background: rgba(255, 255, 255, 0.3);
}
}
}
.box2-main {
box-sizing: border-box;
padding-left: 23px;
padding-right: 30px;
overflow-y: auto;
width: 520px;
height: calc(100% - 160px);
border-radius: 4px;
.box2-main-item {
width: 463px;
height: 48px;
border-radius: 2px;
position: relative;
display: flex;
align-items: center;
cursor: pointer;
&:hover {
.item-right .text {
color: rgb(5, 95, 194) !important;
font-weight: 700;
}
.item-right .text-inner {
border-bottom-color: rgb(5, 95, 194) !important;
}
}
.item-left {
margin-top: 4px;
margin-left: 0px;
margin-bottom: 4px;
width: 40px;
height: 40px;
border-radius: 20px;
color: rgba(82, 196, 26, 1);
background: rgba(246, 255, 237, 1);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 14px;
box-sizing: border-box;
padding: 6px 4px;
text-align: center;
flex-shrink: 0;
}
.item-right {
margin-left: 12px;
height: 46px;
display: flex;
align-items: center;
flex: 1;
background: transparent;
padding: 0;
border-bottom: 1px solid #EAECEE;
box-sizing: border-box;
overflow: hidden; // 保证右侧不会溢出
.text {
padding-top: 8px;
padding-bottom: 8px;
flex: 1 1 auto;
min-width: 0;
height: 100%;
background: transparent;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
letter-spacing: 0px;
text-align: left;
color: rgb(59, 65, 75);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
flex-shrink: 1;
.text-inner {
border-bottom: 1px solid transparent;
}
}
.time {
flex: 0 0 auto;
margin-left: 12px;
padding-top: 11px;
padding-bottom: 11px;
height: 100%;
flex-shrink: 0;
background: transparent;
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: right;
box-sizing: border-box;
color: rgb(132, 136, 142);
white-space: nowrap;
}
}
}
}
.box2-footer {
position: absolute;
left: 26px;
right: 20px;
bottom: 20px;
width: 460px;
height: 42px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border-radius: 6px;
background: var(--color-main-active);
cursor: pointer;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
color: rgba(255, 255, 255, 1);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="header-wrapper">
<div class="icon"></div>
<div class="title"></div>
<div class="btn-box"></div>
</div>
</template>
\ No newline at end of file
<script setup>
import { ElSpace, ElButton } from 'element-plus';
import "@/styles/main.css"
import { copyToClipboardThenTip } from '@/utils/systemUtils'
const colors = [
{ title: "黑色90% / 主标题文字颜色", name: "--text-primary-90-color" },
{ title: "黑色80% / 小标题文字颜色", name: "--text-primary-80-color" },
{ title: "黑色65% / 正文颜色", name: "--text-primary-65-color" },
{ title: "黑色50%", name: "--text-primary-50-color" },
{ title: "黑色10%", name: "--bg-black-10" },
{ title: "黑色5% / 分割线颜色", name: "--bg-black-5" },
{ title: "黑色2% / 灰色背景色", name: "--bg-black-2" },
{ title: "白色主色", name: "--bg-white-100" },
{ title: "主色", name: "--color-primary-100" },
]
function copyColorVar(item) {
const color = `var(${item.name})`
copyToClipboardThenTip(color)
}
</script>
<template>
<el-space direction="vertical" alignment="flex-start">
<div class="text-title-2">颜色</div>
<el-space>
<el-button v-for="(item, index) in colors" :key="index" :color="`var(${item.name})`"
v-on:click="copyColorVar(item)">
{{ item.title }}
</el-button>
</el-space>
</el-space>
</template>
<style scoped></style>
\ No newline at end of file
<script setup lang="ts">
import "@/styles/container.scss"
import TextStyle from './textStyle.vue';
import ConstStyle from './constStyle.vue';
import { ElScrollbar, ElSpace } from "element-plus";
</script>
<template>
<el-scrollbar>
<div class="common-page">
<el-space direction="vertical" alignment="flex-start">
<div class="text-title-0-show">开发样式</div>
<div class="text-title-1-show">样式变量</div>
<const-style></const-style>
<div class="text-title-1-show">文字样式</div>
<text-style></text-style>
</el-space>
</div>
</el-scrollbar>
</template>
\ No newline at end of file
<template>
<table style="width: 100%; border-collapse: collapse; border: 1px solid #ebeef5;">
<!-- 表头 -->
<thead>
<tr class="text-title-2">
<th> 名称</th>
<th> 类型名称</th>
<th> 操作</th>
</tr>
</thead>
<!-- 表格内容 -->
<tbody>
<template v-for="(row, index) in tableData" :key="index">
<!-- 隔行变色效果 -->
<tr :style="{
height: '60px',
backgroundColor: index % 2 === 1 ? '#fafafa' : 'transparent'
}">
<!-- 名称列 -->
<td style="padding: 12px; border: 1px solid #ebeef5;">
<div :class="row.className">
{{ row.name }}
</div>
</td>
<!-- 类型名称列(带复制功能) -->
<td style="padding: 12px; border: 1px solid #ebeef5;">
<div @click="copyToClipboardThenTip(row.className)" style="cursor: pointer;">
<span>{{ row.className }}</span>
</div>
</td>
<!-- 操作列 -->
<td style="padding: 12px; border: 1px solid #ebeef5; text-align: center;">
<el-button type="primary" link @click="copyToClipboardThenTip(row.className)">
<el-icon>
<DocumentCopy />
</el-icon>
复制
</el-button>
</td>
</tr>
</template>
</tbody>
</table>
</template>
<script setup>
import { ref } from 'vue'
import { DocumentCopy } from '@element-plus/icons-vue'
import "@/styles/common.scss"
import { copyToClipboardThenTip } from '@/utils/systemUtils'
// 表格数据
const tableData = ref([
// { name: '0级标题', className: 'text-title-0' },
{ name: '0级标题-加粗', className: 'text-title-0-bold' },
{ name: '0级标题-综艺', className: 'text-title-0-show' },
// { name: '1级标题', className: 'text-title-1' },
{ name: '1级标题-加粗', className: 'text-title-1-bold' },
{ name: '1级标题-综艺', className: 'text-title-1-show' },
{ name: '2级标题', className: 'text-title-2' },
{ name: '2级标题-加粗', className: 'text-title-2-bold' },
{ name: '2级标题-综艺', className: 'text-title-2-show' },
{ name: '3级标题', className: 'text-title-3' },
{ name: '3级标题-加粗', className: 'text-title-3-bold' },
{ name: '3级标题-综艺', className: 'text-title-3-show' },
{ name: '正文', className: 'text-regular' },
{ name: '正文-加粗', className: 'text-bold' },
{ name: '正文-紧凑', className: 'text-compact' },
{ name: '正文-紧凑-加粗', className: 'text-compact-bold' },
{ name: '1级提示文字', className: 'text-tip-1' },
{ name: '1级提示文字-加粗', className: 'text-tip-1-bold' },
{ name: '2级提示文字', className: 'text-tip-2' },
{ name: '2级提示文字-加粗', className: 'text-tip-2-bold' },
{ name: '3级提示文字', className: 'text-tip-3' },
])
</script>
<style scoped></style>
\ No newline at end of file
<template>
<div class="info-wrapper">
<!-- <div class="header-item">国家科技安全</div>
<div class="header-item">></div> -->
<div class="header-item back-item" @click="handleBackHome">中美博弈概览</div>
<div class="header-item">></div>
<div class="header-item">{{ curTitleName }}</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import router from "@/router";
const props = defineProps({
curTitleName: {
type: String,
required: true
}
});
// 返回首页
const handleBackHome = () => {
router.push({
path: "/overview"
});
};
</script>
<style lang="scss" scoped>
.info-wrapper {
height: 64px;
line-height: 64px;
display: flex;
justify-content: flex-end;
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
color: #fff;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="menu-wrapper">
<div class="menu-item" @click="handleToOverview">
<div class="menu-item-icon">
<img src="@/assets/icons/home-header-icon1.png" alt="" />
</div>
<div class="menu-item-text">{{ "首页" }}</div>
</div>
<div
class="menu-item"
@click="handleToGjOverview"
@mouseenter="handleIsShowCountryMore(true)"
@mouseleave="handleIsShowCountryMore(false)"
>
<div class="menu-item-icon1">
<img src="@/assets/icons/home-header-icon2.png" alt="" />
</div>
<div class="menu-item-text">{{ "国家" }}</div>
</div>
<div class="menu-item">
<div class="menu-item-icon2">
<img src="@/assets/icons/home-header-icon3.png" alt="" />
</div>
<div class="menu-item-text">{{ "领域" }}</div>
</div>
<div class="menu-item">
<div class="menu-item-icon3">
<img src="@/assets/icons/home-header-icon4.png" alt="" />
</div>
<div class="menu-item-text">{{ "要素" }}</div>
</div>
<div class="menu-item">
<div class="menu-item-icon4">
<img src="@/assets/icons/home-header-icon5.png" alt="" />
</div>
<div class="menu-item-text">{{ "事件" }}</div>
</div>
</div>
<div
class="more-wrapper"
v-if="isShowCountryMore"
@mouseenter="handleIsShowCountryMore(true)"
@mouseleave="handleIsShowCountryMore(false)"
>
<div class="left">
<div class="left-header">
<div class="title">{{ "中美科技博弈概览" }}</div>
<div class="icon">
<img src="@/assets/icons/more.png" alt="" />
</div>
</div>
<div class="left-main">
<div class="item" v-for="(item, index) in leftList" :key="index" @click="handleClickItem(item)">
<div class="icon"></div>
<div class="text">{{ item.name }}</div>
</div>
</div>
</div>
<!-- <div class="right">
<div class="right-header">
<div class="title">{{ "风险监测" }}</div>
<div class="icon">
<img src="@/assets/icons/more.png" alt="" />
</div>
</div>
<div class="right-main">
<div class="item" v-for="(item, index) in rightList" :key="index">
<div class="icon"></div>
<div class="text">{{ item.name }}</div>
</div>
</div>
</div> -->
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import router from "@/router";
const isShowCountryMore = ref(false);
const handleIsShowCountryMore = isShow => {
isShowCountryMore.value = isShow;
};
const handleToOverview = () => {
router.push({
path: "/overview"
});
};
const handleToGjOverview = () => {
router.push({
path: "/gjOverview"
});
};
const leftList = ref([
{
name: "科技法案",
path: "/billHome"
},
{
name: "科技政令",
path: "/decree"
},
{
name: "美国科技智库",
path: "/thinkTank"
},
{
name: "出口管制",
path: "/exportControl"
},
{
name: "科研合作限制",
path: "/cooperationRestrictions"
},
{
name: "投融资限制",
path: "/finance"
},
{
name: "市场准入限制",
path: "/marketAccessRestrictions"
},
{
name: "规则限制",
path: "/ruleRestrictions"
},
{
name: "美国科技人物观点",
path: "/technologyFigures"
},
{
name: "美国主要创新主体动向",
path: "/innovationSubject"
},
{
name: "美国科研资助体系分析",
path: "/scientificFunding"
}
]);
const handleClickItem = item => {
const curRoute = router.resolve({
path: item.path
});
window.open(curRoute.href, "_blank");
};
const rightList = ref([
{
name: "科技战略布局",
path: ""
},
{
name: "创新体系位势分析",
path: ""
}
]);
</script>
<style lang="scss" scoped>
.menu-wrapper {
width: 644px;
height: 64px;
display: flex;
justify-content: space-between;
.menu-item {
display: flex;
gap: 11px;
width: 112px;
height: 64px;
justify-content: center;
align-items: center;
cursor: pointer;
&:hover {
background: var(--color-main-active);
}
.menu-item-icon {
// margin-top: 2px;
width: 22px;
height: 22px;
img {
width: 100%;
height: 100%;
}
}
.menu-item-icon1 {
// margin-top: 4px;
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
.menu-item-icon2 {
// margin-top: 4px;
width: 24px;
height: 22px;
img {
width: 100%;
height: 100%;
}
}
.menu-item-icon3 {
// margin-top: 2px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.menu-item-icon4 {
width: 22px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.menu-item-text {
// margin-top: 16px;
height: 32px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 24px;
font-weight: 700;
line-height: 32px;
letter-spacing: 0px;
text-align: left;
}
}
}
.more-wrapper {
position: absolute;
z-index: 99999;
top: 64px;
left: 0;
width: 100%;
height: 299px;
background: rgb(249, 249, 249);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.3);
display: flex;
.left {
margin-top: 35px;
margin-left: 179px;
width: 769px;
height: 218px;
.left-header {
display: flex;
height: 26px;
align-items: center;
.title {
width: 160px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
.icon {
margin-top: -8px;
margin-left: 8px;
width: 10px;
height: 10px;
img {
width: 100%;
height: 100%;
}
}
}
.left-main {
height: 192px;
display: flex;
flex-wrap: wrap;
.item {
margin-top: 18px;
display: flex;
width: 256px;
height: 36px;
align-items: center;
border-radius: 4px;
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
.icon {
background: var(--color-main-active) !important;
}
.text {
color: var(--color-main-active) !important;
font-weight: 700;
font-size: 20px;
}
}
.icon {
width: 6px;
height: 6px;
border-radius: 3px;
background: rgba(95, 101, 108, 1);
}
.text {
margin-left: 10px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 18px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
}
}
.right {
margin-top: 35px;
margin-left: 53px;
width: 192px;
height: 116px;
.right-header {
display: flex;
height: 26px;
align-items: center;
.title {
width: 80px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
.icon {
margin-top: -8px;
margin-left: 8px;
width: 10px;
height: 10px;
img {
width: 100%;
height: 100%;
}
}
}
.right-main {
.item {
margin-top: 18px;
display: flex;
width: 256px;
height: 36px;
align-items: center;
border-radius: 4px;
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
.icon {
background: var(--color-main-active) !important;
}
.text {
color: var(--color-main-active) !important;
font-weight: 700;
font-size: 20px;
}
}
.icon {
width: 6px;
height: 6px;
border-radius: 3px;
background: rgba(95, 101, 108, 1);
}
.text {
margin-left: 10px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 18px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
}
}
}
</style>
\ No newline at end of file
...@@ -4,7 +4,6 @@ import router from "./router"; ...@@ -4,7 +4,6 @@ import router from "./router";
import ElementPlus from "element-plus"; import ElementPlus from "element-plus";
import "element-plus/dist/index.css"; import "element-plus/dist/index.css";
import * as ElementPlusIconsVue from "@element-plus/icons-vue"; import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import CardTitle from "./components/CardTitle.vue";
import { withFallbackImage } from "./utils"; import { withFallbackImage } from "./utils";
import "./styles/scrollbar.css"; import "./styles/scrollbar.css";
import "./styles/elui.css"; import "./styles/elui.css";
...@@ -12,11 +11,18 @@ import "./styles/main.css"; ...@@ -12,11 +11,18 @@ import "./styles/main.css";
import '@/assets/fonts/font.css' import '@/assets/fonts/font.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import AreaTag from '@/components/areaTag.vue' // import AreaTag from '@/components/base/AreaTag/index.vue'
import leftBtn from "@/components/pageBtn/leftBtn.vue"; // import LeftBtn from "@/components/base/PageBtn/LeftBtn.vue";
import rightBtn from "@/components/pageBtn/rightBtn.vue"; // import RightBtn from "@/components/base/PageBtn/RightBtn.vue";
import OverviewBox from '@/components/BoxBackground/overviewBox.vue' // import OverviewMainBox from "@/components/base/BoxBackground/OverviewMainBox.vue";
import AnalysisBox from '@/components/BoxBackground/analysisBox.vue' // import OverviewNormalBox from "@/components/base/BoxBackground/OverviewNormalBox.vue";
// import AnalysisBox from '@/components/base/BoxBackground/AnalysisBox.vue'
// import NewsList from '@/components/base/NewsList/index.vue'
// import ModuleHeader from '@/components/base/ModuleHeader/index.vue'
// import RiskSignal from "@/components/base/RiskSignal/index.vue";
// import MessageBubble from "@/components/base/MessageBubble/index.vue";
// import SourceTabLsit from '@/components/base/SourceTabList/index.vue'
// import ActionButton from "@/components/base/ActionButton/index.vue"
// 引入 Pinia 实例 // 引入 Pinia 实例
import pinia from './stores' import pinia from './stores'
...@@ -34,10 +40,18 @@ app.use(ElementPlus, { ...@@ -34,10 +40,18 @@ app.use(ElementPlus, {
locale: zhCn, locale: zhCn,
}) })
app.use(pinia) // 挂载 Pinia app.use(pinia) // 挂载 Pinia
app.component("CardTitle", CardTitle); // app.component('AreaTag', AreaTag) // 领域标签
app.component('AreaTag', AreaTag) // app.component('LeftBtn', LeftBtn) // 向左按钮
app.component('leftBtn', leftBtn) // app.component('RightBtn', RightBtn) // 向右按钮
app.component('rightBtn', rightBtn) // app.component('OverviewMainBox', OverviewMainBox) // 概览页最新动态背景盒子
app.component('OverviewBox', OverviewBox) // app.component('OverviewNormalBox', OverviewNormalBox) // 概览页一版模块背景盒子
app.component('AnalysisBox', AnalysisBox) // app.component('AnalysisBox', AnalysisBox) // 分析页模块背景
// app.component('ModuleHeader', ModuleHeader) // 模块头部
// app.component('RiskSignal', RiskSignal) // 风险信号
// app.component('NewsList', NewsList) // 新闻资讯
// app.component('MessageBubble', MessageBubble) // 社交媒体
// app.component('SourceTabLsit', SourceTabLsit) // 资源库tab列表
// app.component('ActionButton', ActionButton) // 普通按钮和激活按钮
app.mount("#app"); app.mount("#app");
//创新主体 //创新主体
import InnovationSubject from "@/views/innovationSubject/index.vue"; const InnovationSubject = () => import('@/views/innovationSubject/index.vue')
import InnovationInstitution from "@/views/innovationSubject/innovativeInstitutions/index.vue"; const InnovationInstitution = () => import('@/views/innovationSubject/innovativeInstitutions/index.vue')
const innovationSubjectRoutes = [ const innovationSubjectRoutes = [
//创新主体 //创新主体
......
//ZM博弈概览 //ZM博弈概览
import ZMGame from "@/views/ZMGame/index.vue"; const ZMGame = () => import('@/views/ZMGame/index.vue')
const ZMGameRoutes = [ const ZMGameRoutes = [
{ {
......
//ZM博弈概览 //ZM博弈概览
import ZMOverview from "@/views/ZMOverView/index.vue"; const ZMOverview = () => import('@/views/ZMOverView/index.vue')
const ZMOverviewRoutes = [ const ZMOverviewRoutes = [
{ {
......
// 法案相关 // 法案相关
import BillHome from "@/views/bill/billHome/index.vue"; const BillHome = () => import('@/views/bill/billHome/index.vue')
import BillLayoutContainer from "@/views/bill/billLayout/index.vue"; const BillLayoutContainer = () => import('@/views/bill/billLayout/index.vue')
import BillLayout from "@/views/bill/index.vue"; const BillLayout = () => import('@/views/bill/index.vue')
import BillIntroduction from "@/views/bill/introdoction/index.vue"; const BillIntroduction = () => import('@/views/bill/introdoction/index.vue')
import BillBackground from "@/views/bill/background/index.vue"; const BillBackground = () => import('@/views/bill/background/index.vue')
import BillTemplate from "@/views/bill/template/index.vue"; const BillTemplate = () => import('@/views/bill/template/index.vue')
import BillDeepDigLayout from "@/views/bill/deepDig/index.vue"; const BillDeepDigLayout = () => import('@/views/bill/deepDig/index.vue')
import BillDeepDigProcessOverview from "@/views/bill/deepDig/processOverview/index.vue"; const BillDeepDigProcessOverview = () => import('@/views/bill/deepDig/processOverview/index.vue')
import BillDeepDigProcessAnalysis from "@/views/bill/deepDig/processAnalysis/index.vue"; const BillDeepDigProcessAnalysis = () => import('@/views/bill/deepDig/processAnalysis/index.vue')
import BillDeepDigPoliContribution from "@/views/bill/deepDig/poliContribution/index.vue"; const BillDeepDigPoliContribution = () => import('@/views/bill/deepDig/poliContribution/index.vue')
import BillInfluenceLayout from "@/views/bill/influence/index.vue"; const BillInfluenceLayout = () => import('@/views/bill/influence/index.vue')
import BillInfluenceIndustry from "@/views/bill/influence/industry/index.vue"; const BillInfluenceIndustry = () => import('@/views/bill/influence/industry/index.vue')
import BillInfluenceScientificResearch from "@/views/bill/influence/scientificResearch/index.vue"; const BillInfluenceScientificResearch = () => import('@/views/bill/influence/scientificResearch/index.vue')
import BillRelevantCircumstance from "@/views/bill/relevantCircumstance/index.vue"; const BillRelevantCircumstance = () => import('@/views/bill/relevantCircumstance/index.vue')
const billRoutes = [ const billRoutes = [
......
// 人物主页 // 人物主页
import CharacterPage from "@/views/characterPage/index.vue"; const CharacterPage = () => import('@/views/characterPage/index.vue')
const characterPageRoutes = [ const characterPageRoutes = [
// 人物主页 // 人物主页
......
//企业主页 //企业主页
import companyPages from "@/views/companyPages/index.vue";
const companyPages = () => import('@/views/companyPages/index.vue')
const companyPagesRoutes = [ const companyPagesRoutes = [
// 智库系统的主要路由 // 智库系统的主要路由
......
// 综合搜索 // 综合搜索
import ComprehensiveSearch from '@/views/comprehensiveSearch/index.vue' const ComprehensiveSearch = () => import('@/views/comprehensiveSearch/index.vue')
import SearchResults from '@/views/comprehensiveSearch/searchResults/index.vue' const SearchResults = () => import('@/views/comprehensiveSearch/searchResults/index.vue')
import Chat from '@/views/comprehensiveSearch/chat/index.vue' const Chat = () => import('@/views/comprehensiveSearch/chat/index.vue')
const comprehensiveSearchRoutes = [ const comprehensiveSearchRoutes = [
// 综合搜索 // 综合搜索
......
// 合作限制 // 合作限制
import CooperationRestrictions from "@/views/coopRestriction/index.vue"; const CooperationRestrictions = () => import('@/views/coopRestriction/index.vue')
import CooperationRestrictionsDetail from "@/views/coopRestriction/detail/index.vue"; const CooperationRestrictionsDetail = () => import('@/views/coopRestriction/detail/index.vue')
const cooperationRestrictionsRoutes = [ const cooperationRestrictionsRoutes = [
// 合作限制 // 合作限制
......
// 政令 // 政令
import Decree from "@/views/decree/decreeHome/index.vue"; const Decree = () => import('@/views/decree/decreeHome/index.vue')
import DecreeLayoutContainer from "@/views/decree/decreeLayout/index.vue"; const DecreeLayoutContainer = () => import('@/views/decree/decreeLayout/index.vue')
import DecreeOverviewLayout from "@/views/decree/decreeLayout/overview/index.vue"; const DecreeOverviewLayout = () => import('@/views/decree/decreeLayout/overview/index.vue')
import DecreeIntroduction from "@/views/decree/decreeLayout/overview/introduction/index.vue"; const DecreeIntroduction = () => import('@/views/decree/decreeLayout/overview/introduction/index.vue')
import DecreeBackground from "@/views/decree/decreeLayout/overview/background/index.vue"; const DecreeBackground = () => import('@/views/decree/decreeLayout/overview/background/index.vue')
import DecreeDeepDig from "@/views/decree/decreeLayout/deepdig/index.vue"; const DecreeDeepDig = () => import('@/views/decree/decreeLayout/deepdig/index.vue')
import DecreeInfluence from "@/views/decree/decreeLayout/influence/index.vue"; const DecreeInfluence = () => import('@/views/decree/decreeLayout/influence/index.vue')
import Institution from "@/views/decree/institution/index.vue" const Institution = () => import('@/views/decree/institution/index.vue')
import DecreeOriginal from "@/views/decree/decreeOriginal/index.vue" const DecreeOriginal = () => import('@/views/decree/decreeOriginal/index.vue')
const decreeRoutes = [ const decreeRoutes = [
// 政令首页 // 政令首页
......
//样式主页
const StylePages = () => import("@/components/devStyle/components/index.vue");
const stylePagesRoutes = [
// 智库系统的主要路由
{
path: "/devStylePages",
name: "devStylePages",
component: StylePages,
meta: {
title: "开发样式",
dynamicTitle: true
}
},
]
export default stylePagesRoutes
\ No newline at end of file
// 出口管制 // 出口管制
import ExportControl from "@/views/exportControl/index.vue"; const ExportControl = () => import('@/views/exportControl/index.vue')
const exportControlRoutes = [ const exportControlRoutes = [
// 出口管制首页 // 出口管制首页
......
// 投融资限制 // 投融资限制
import Finance from "@/views/finance/index.vue"; const Finance = () => import('@/views/finance/index.vue')
const financeRoutes = [ const financeRoutes = [
// 投融资限制 // 投融资限制
......
// 市场准入限制 // 市场准入限制
import MarketAccessRestrictions from "@/views/marketAccessRestrictions/marketAccessHome/index.vue"; const MarketAccessRestrictions = () => import('@/views/marketAccessRestrictions/marketAccessHome/index.vue')
import MarketAccessLayout from "@/views/marketAccessRestrictions/marketAccessLayout/index.vue"; const MarketAccessLayout = () => import('@/views/marketAccessRestrictions/marketAccessLayout/index.vue')
import MarketAccessOverview from "@/views/marketAccessRestrictions/marketAccessLayout/overview/index.vue"; const MarketAccessOverview = () => import('@/views/marketAccessRestrictions/marketAccessLayout/overview/index.vue')
import MarketAccessCase from "@/views/marketAccessRestrictions/marketAccessLayout/case/index.vue"; const MarketAccessCase = () => import('@/views/marketAccessRestrictions/marketAccessLayout/case/index.vue')
import MarketSingleCaseLayout from "@/views/marketAccessRestrictions/singleCaseLayout/index.vue"; const MarketSingleCaseLayout = () => import('@/views/marketAccessRestrictions/singleCaseLayout/index.vue')
import MarketSingleCaseOverview from "@/views/marketAccessRestrictions/singleCaseLayout/overview/index.vue"; const MarketSingleCaseOverview = () => import('@/views/marketAccessRestrictions/singleCaseLayout/overview/index.vue')
import MarketSingleCaseDeepdig from "@/views/marketAccessRestrictions/singleCaseLayout/deepdig/index.vue"; const MarketSingleCaseDeepdig = () => import('@/views/marketAccessRestrictions/singleCaseLayout/deepdig/index.vue')
const marketAccessRestrictionsRoutes = [ const marketAccessRestrictionsRoutes = [
// 市场准入限制首页 // 市场准入限制首页
{ {
path: "/marketAccessRestrictions", path: "/marketAccessRestrictions",
......
//新闻速览 const newsBrief = () => import('@/views/newsBrief/index.vue')
import newsBrief from "@/views/newsBrief/index.vue"; const NewsAnalysis = () => import('@/views/newsAnalysis/index.vue')
// 新闻事件分析
import NewsAnalysis from "@/views/newsAnalysis/index.vue";
const newsRoutes = [ const newsRoutes = [
//新闻速览页面路由 //新闻速览页面路由
......
import Portal from "@/views/portals/portal1/index.vue"; const Portal = () => import('@/views/portals/portal1/index.vue')
import Portal2 from "@/views/portals/portal2/index.vue"; const Portal2 = () => import('@/views/portals/portal2/index.vue')
const portalRoutes = [ const portalRoutes = [
// 门户 // 门户
......
// 风险信号 // 风险信号
import RiskSignal from "@/views/riskSignal/index.vue" const RiskSignal = () => import('@/views/riskSignal/index.vue')
const riskSignalRoutes = [ const riskSignalRoutes = [
//风险信号页面路由 //风险信号页面路由
...@@ -10,8 +10,7 @@ const riskSignalRoutes = [ ...@@ -10,8 +10,7 @@ const riskSignalRoutes = [
meta: { meta: {
title: "风险信号" title: "风险信号"
} }
}, }
] ]
export default riskSignalRoutes export default riskSignalRoutes
\ No newline at end of file
// 规则限制 // 规则限制
import RuleRestriction from "@/views/ruleRestriction/index.vue"; const RuleRestriction = () => import('@/views/ruleRestriction/index.vue')
import RuleRestrictionDetail from "@/views/ruleRestriction/detail/index.vue"; const RuleRestrictionDetail = () => import('@/views/ruleRestriction/detail/index.vue')
const ruleRestrictionsRoutes = [ const ruleRestrictionsRoutes = [
// 规则限制 // 规则限制
......
// 科研资助体系 // 科研资助体系
import ScientificFunding from "@/views/scientificFunding/index.vue"; const ScientificFunding = () => import('@/views/scientificFunding/index.vue')
const scientificFundingRoutes = [ const scientificFundingRoutes = [
// 科研资助体系 // 科研资助体系
......
//科技人物观点 //科技人物观点
import TechnologyFigures from "@/views/technologyFigures/index.vue"; const TechnologyFigures = () => import('@/views/technologyFigures/index.vue')
const technologyFiguresRoutes = [ const technologyFiguresRoutes = [
{ {
......
// 智库相关 // 智库相关
import thinkTank from "@/views/thinkTank/index.vue"; const thinkTank = () => import('@/views/thinkTank/index.vue')
import ThinkTankDetail from "@/views/thinkTank/ThinkTankDetail/index.vue"; const ThinkTankDetail = () => import('@/views/thinkTank/ThinkTankDetail/index.vue')
import ReportDetail from "@/views/thinkTank/ReportDetail/index.vue"; const ReportDetail = () => import('@/views/thinkTank/ReportDetail/index.vue')
import ReportOriginal from "@/views/thinkTank/reportOriginal/index.vue" const ReportOriginal = () => import('@/views/thinkTank/reportOriginal/index.vue')
const thinktankRoutes = [ const thinktankRoutes = [
// 智库系统的主要路由 // 智库系统的主要路由
{ {
......
// 智能写报 // 智能写报
import WrittingAsstaint from "@/views/writtingAsstaint/index.vue"; const WrittingAsstaint = () => import('@/views/writtingAsstaint/index.vue')
const writtingRoutes = [ const writtingRoutes = [
// 智能写报路由 // 智能写报路由
......
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ElMessage } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
export const useWrittingAsstaintStore = defineStore('writtingAsstaint', { export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
state: () => ({ state: () => ({
...@@ -92,11 +92,43 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', { ...@@ -92,11 +92,43 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
this.abortController = null; this.abortController = null;
}, },
backToInputAndClear() {
this.resetGenerateState();
this.reportContent = '';
this.writtingTitle = '';
this.descText = '';
this.uploadFileList = [];
this.routeQuery = {};
this._isDisableTemplate = false;
this.curTempTitle = '政令';
this.tempActiveIndex = 0;
},
updateTempActiveIndex(index, title) { updateTempActiveIndex(index, title) {
this.tempActiveIndex = index; this.tempActiveIndex = index;
this.curTempTitle = title; this.curTempTitle = title;
}, },
_keepStepsViewOnError() {
this.isGenerating = false;
this.isShowProcess = true;
this.isShowSteps = true;
this.abortController?.abort();
this.abortController = null;
},
async _showErrorDialog(message) {
await ElMessageBox.alert(message || '写报生成失败', '提示', {
confirmButtonText: '确认',
type: 'error'
});
},
async _handleGenerateError(message) {
this._keepStepsViewOnError();
await this._showErrorDialog(message);
},
// ========== 路由参数处理 ========== // ========== 路由参数处理 ==========
async setRouteParams(query) { async setRouteParams(query) {
this.routeQuery = { ...query }; this.routeQuery = { ...query };
...@@ -157,8 +189,7 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', { ...@@ -157,8 +189,7 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
} }
} catch (error) { } catch (error) {
console.error(`获取${typeName}数据异常:`, error); console.error(`获取${typeName}数据异常:`, error);
ElMessage.error(`获取${typeName}数据失败: ${error.message}`); await this._handleGenerateError(`获取${typeName}数据失败: ${error.message}`);
this.resetGenerateState();
} }
}, },
...@@ -309,15 +340,20 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', { ...@@ -309,15 +340,20 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
}); });
} }
break; break;
case 'error':
await this._handleGenerateError(jsonData.message);
break;
default: default:
console.debug('未处理的SSE事件类型', event.event); console.debug('未处理的SSE事件类型', event.event);
break; break;
} }
}, },
onerror: (error) => { onerror: async (error) => {
console.error('SSE连接错误', error); console.error('SSE连接错误', error);
ElMessage.warning('写报生成报错!'); if (error.name !== 'AbortError') {
if (error.name !== 'AbortError') return true; await this._handleGenerateError('写报生成报错!');
return true;
}
this.resetGenerateState(); this.resetGenerateState();
}, },
onclose: () => { onclose: () => {
...@@ -327,8 +363,9 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', { ...@@ -327,8 +363,9 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
}); });
} catch (error) { } catch (error) {
if (error.name !== 'AbortError') { if (error.name !== 'AbortError') {
ElMessage.error(`PDF解析请求失败:${error.message}`);
console.error('PDF SSE请求异常', error); console.error('PDF SSE请求异常', error);
await this._handleGenerateError(`PDF解析请求失败:${error.message}`);
throw error;
} }
this.resetGenerateState(); this.resetGenerateState();
throw error; throw error;
...@@ -435,8 +472,7 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', { ...@@ -435,8 +472,7 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
} else if ((msgData.event_type || '').toLowerCase().includes('error')) { } else if ((msgData.event_type || '').toLowerCase().includes('error')) {
// 优先从 data.error 获取详细错误描述 // 优先从 data.error 获取详细错误描述
const errorMsg = msgData.data?.error || str || '生成失败'; const errorMsg = msgData.data?.error || str || '生成失败';
ElMessage.error('生成失败:' + errorMsg); this._handleGenerateError('生成失败:' + errorMsg);
this.resetGenerateState();
} else { } else {
// 老版 --index.vue 行为:步骤栏直接追加服务端发来的完整步骤内容,不加时间戳、不强行换行 // 老版 --index.vue 行为:步骤栏直接追加服务端发来的完整步骤内容,不加时间戳、不强行换行
// 这样可以避免 SSE 分片导致的“步骤破碎”(一条步骤被拆成多条显示) // 这样可以避免 SSE 分片导致的“步骤破碎”(一条步骤被拆成多条显示)
...@@ -446,15 +482,17 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', { ...@@ -446,15 +482,17 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
this.curAgentTool = msgData.tool || '无'; this.curAgentTool = msgData.tool || '无';
} }
}, },
onerror: (error) => { onerror: async (error) => {
ElMessage.warning('写报生成报错!'); await this._handleGenerateError('写报生成报错!');
this.resetGenerateState();
throw new Error(error); throw new Error(error);
} }
}); });
} catch (error) { } catch (error) {
ElMessage.warning('写报生成报错!'); if (error.name !== 'AbortError') {
await this._handleGenerateError(error.message || '写报生成报错!');
} else {
this.resetGenerateState(); this.resetGenerateState();
}
throw error; throw error;
} }
}, },
......
/***背景作为卡片***/
.background-as-card {
background-color: var(--color-primary-65);
border-radius: 10px;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
}
.flex-display{
display: flex;
}
/***文本样式***/
.text-base{
color: var(--color-primary-90);
}
//0级标题
.text-title-0{
@extend .text-base;
color: var(--color-primary-90);
font-size: 32px;
}
.text-title-0-bold{
@extend .text-title-0;
font-weight: Bold;
}
.text-title-0-show{
@extend .text-title-0;
font-size: 48px;
font-family: "YouSheBiaoTiHei";
}
//1级标题
.text-title-1{
@extend .text-base;
color: var(--color-primary-90);
font-size: 24px;
}
.text-title-1-bold{
@extend .text-title-1;
font-weight: Bold;
}
.text-title-1-show{
@extend .text-title-1;
font-size: 30px;
font-family: "YouSheBiaoTiHei";
}
//2级标题
.text-title-2{
@extend .text-base;
color: var(--color-primary-90);
font-size: 20px;
line-height:26px;
}
.text-title-2-bold{
@extend .text-title-2;
font-weight: Bold;
}
.text-title-2-show{
@extend .text-title-2;
font-size: 24px;
font-family: "YouSheBiaoTiHei";
line-height: normal !important; // 取消继承的行高
}
//3级标题
.text-title-3{
@extend .text-base;
color: var(--color-primary-90);
font-size: 18px;
line-height:24px;
}
.text-title-3-bold{
@extend .text-title-3;
font-weight: Bold;
}
.text-title-3-show{
@extend .text-title-3;
font-size: 20px;
font-family: "YouSheBiaoTiHei";
line-height: normal !important; // 取消继承的行高
}
//正文
.text-regular{
@extend .text-base;
font-size: 16px;
line-height:30px;
}
//正文-加粗
.text-bold{
@extend .text-base;
font-weight: Bold;
}
//正文-紧凑
.text-compact{
@extend .text-base;
font-size: 16px;
line-height:24px;
}
.text-compact-bold{
@extend .text-base;
font-size: 16px;
line-height:24px;
font-weight: Bold;
}
//1级提示文字
.text-tip-1{
@extend .text-base;
color: var(--color-primary-90);
font-size: 16px;
line-height:24px;
}
.text-tip-1-bold{
@extend .text-tip-1;
font-weight: Bold;
}
//2级提示文字
.text-tip-2{
@extend .text-base;
color: var(--color-primary-90);
font-size: 14px;
line-height:22px;
}
.text-tip-2-bold{
@extend .text-tip-2;
font-weight: Bold;
}
//3级提示文字
.text-tip-3{
@extend .text-base;
color: var(--color-primary-90);
font-size: 12px;
}
@use "common";
/***通用页面***/
.common-page {
font-family: "Microsoft Yahei", "PingFang SC", sans-serif;
margin: 16px auto;
width: 1600px;
align-items: center;
}
\ No newline at end of file
@use "common";
/***没有nav下划线***/
.common-descriptions .el-descriptions__label{
@extend .text-tip-1-bold;
}
.common-descriptions .el-descriptions__content{
@extend .text-tip-1;
color: var(--text-primary-80-color);
}
\ No newline at end of file
:root { :root {
--color-main-active: rgba(5, 95, 194, 1); --color-main-active: rgba(5, 95, 194, 1);
--color-main-primay: rgba(59, 65, 75, 1); --color-main-primay: rgba(59, 65, 75, 1);
--font-family-base: "Source Han Sans CN";
--font-size-base: 16px;
--color-bg-hover: #e7f3ff;
--color-bg-hover: #f6faff;
/* 普通按钮颜色 */ /* 普通按钮颜色 */
--btn-plain-border-color: rgba(230, 231, 232, 1); --btn-plain-border-color: rgba(230, 231, 232, 1);
...@@ -13,27 +17,8 @@ ...@@ -13,27 +17,8 @@
--btn-active-bg-color: rgba(231, 243, 255, 1); --btn-active-bg-color: rgba(231, 243, 255, 1);
--btn-active-text-color: var(--color-main-active); --btn-active-text-color: var(--color-main-active);
/* 标签按钮颜色 */
--tag-btn1-bg-color: rgba(255, 241, 240, 1);
--tag-btn1-border-color: rgba(255, 204, 199, 1);
--tag-btn1-text-color: rgba(255, 77, 79, 1);
--tag-btn2-bg-color: rgba(255, 251, 230, 1);
--tag-btn2-border-color: rgba(255, 241, 184, 1);
--tag-btn2-text-color: rgba(250, 173, 20, 1);
--tag-btn2-bg-color: rgba(230, 244, 255, 1);
--tag-btn2-border-color: rgba(186, 224, 255, 1);
--tag-btn2-text-color: rgba(22, 119, 255, 1);
--tag-btn3-bg-color: rgba(246, 255, 237, 1);
--tag-btn3-border-color: rgba(217, 247, 190, 1);
--tag-btn3-text-color: rgba(82, 196, 26, 1);
/* 文字颜色 设计定义*/ /* 文字颜色 设计定义*/
--text-primary-color: #0a121e;
--text-primary-90-color: #222934; --text-primary-90-color: #222934;
--text-primary-80-color: #3b414b; --text-primary-80-color: #3b414b;
--text-primary-65-color: #5f656c; --text-primary-65-color: #5f656c;
......
@use "common";
/***没有nav下划线***/
.tabs-nav-no-wrap .el-tabs__nav-wrap::after{
height: 0px !important;
width: 0px !important;
}
/***nav as card***/
.tabs-header-as-card .el-tabs__header:not(.disinheritance .el-tabs__header) {
@extend .background-as-card;
padding: 2px;
}
/***作为按钮样式的tabs-bar***/
/*选中无下划线*/
.tabs-bar-as-btn .el-tabs__active-bar:not(.disinheritance .el-tabs__active-bar) {
height: 0px;
}
/*定义字体*/
.tabs-bar-as-btn .el-tabs__item:not(.disinheritance .el-tabs__item) {
font-size: 20px;
font-family: "Source Han Sans CN-Regular";
font-weight: Regular;
line-height: 26px;
height: 50px;
}
/*激活时按钮样式*/
.tabs-bar-as-btn .el-tabs__item.is-active:not(.disinheritance .el-tabs__item){
font-size: 24px;
font-family: "Source Han Sans CN-Bold";
font-weight: Bold;
color: var(--color-primary-100);
border-width: 1px;
border-style: solid;
border-color: var(--color-primary-35);
border-radius: 10px;
background-color: var(--color-primary-2);
}
/***tabs-bar左边悬浮***/
.left-float-nav-tabs .el-tabs__item{
position:relative;
}
.left-float-nav-tabs .el-tabs__item.is-active{
background-color: aquamarine;
}
\ No newline at end of file
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
$primary-color: var(--el-color-primary); $primary-color: var(--el-color-primary);
$base-color: rgba(5, 95, 194, 1); $base-color: rgba(5, 95, 194, 1);
$base-font-size: 16px; $base-font-size: 16px;
$base-font-family: "微软雅黑"; // $base-font-family: "微软雅黑";
$base-font-family: "Source Han Sans CN";
// :root { // :root {
// --base-color: #{$base-color}; // --base-color: #{$base-color};
......
import { ElMessage } from "element-plus";
interface BaseReturn {
status: boolean;
message: string;
}
export async function copyToClipboard(txet: string): Promise<BaseReturn> {
try {
// 使用现代 Clipboard API
await navigator.clipboard.writeText(txet);
console.log("已复制:", txet);
return { status: true, message: "已复制:" + txet };
} catch (err) {
console.error("复制失败:", err);
// 降级方案:使用document.execCommand
const textArea = document.createElement("textarea");
textArea.value = txet;
document.body.appendChild(textArea);
textArea.select();
try {
const successful = document.execCommand("copy");
if (successful) {
return { status: true, message: "已复制:" + txet };
}
} catch (err) {}
document.body.removeChild(textArea);
return { status: false, message: "复制失败,请手动复制" };
}
}
// 复制类型名称到剪贴板
export async function copyToClipboardThenTip(text: string): Promise<void> {
const { status, message } = await copyToClipboard(text);
if (status) {
ElMessage.success(message);
} else {
ElMessage.error(message);
}
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="home-wrapper"> <div class="home-wrapper">
<div class="home-header"> <div class="home-header">
<div class="header-left"> <div class="header-left">
<HeaderMenu></HeaderMenu> <!-- <HeaderMenu></HeaderMenu> -->
</div> </div>
<div class="header-right"> <div class="header-right">
<input :value="input"> <input :value="input">
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
<script setup> <script setup>
import { onMounted, ref, computed } from "vue"; import { onMounted, ref, computed } from "vue";
import HeaderMenu from "@/components/headerMenu.vue"; // import HeaderMenu from "@/components/headerMenu.vue";
import RiskToday from './component/riskToday/index.vue' import RiskToday from './component/riskToday/index.vue'
import ScienceNews from './component/scienceNews/index.vue' import ScienceNews from './component/scienceNews/index.vue'
const input = ref('') const input = ref('')
......
...@@ -75,12 +75,13 @@ ...@@ -75,12 +75,13 @@
</div> </div>
</div> </div>
<div class="news-content"> <div class="news-content">
<div v-for="value in newsList" class="news-item"> <div v-for="value,idx in newsList" :key="idx" class="news-item">
<div class="news-item-title"> <div class="news-item-title">
<div class="tag-container"> <div class="tag-container">
<div v-for="tag in value.tags" :key="tag" :class="getTagClass(tag)"> <!-- <div v-for="tag in value.tags" :key="tag" :class="getTagClass(tag)">
{{ tag }} {{ tag }}
</div> </div> -->
<AreaTag v-for="tag,index in value.tags" :key="index" :tagName="tag"></AreaTag>
</div> </div>
<div class="date"> <div class="date">
...@@ -237,7 +238,8 @@ ...@@ -237,7 +238,8 @@
> >
<div class="timeline-content-item"> <div class="timeline-content-item">
<div class="item-tags"> <div class="item-tags">
<span v-for="tag in node.tags" :key="tag" :class="getTagClass(tag)">{{ tag }}</span> <!-- <span v-for="tag in node.tags" :key="tag" :class="getTagClass(tag)">{{ tag }}</span> -->
<AreaTag v-for="tag,idx in node.tags" :key="idx" :tagName="tag"></AreaTag>
</div> </div>
<div class="item-title"> <div class="item-title">
<CommonPrompt :content="node.title" /> <CommonPrompt :content="node.title" />
...@@ -1921,7 +1923,8 @@ watch(activeDate, () => { ...@@ -1921,7 +1923,8 @@ watch(activeDate, () => {
gap: 8px; gap: 8px;
.item-tags { .item-tags {
width: 350px; width: 355px;
height: 30px;
display: flex; display: flex;
gap: 8px; gap: 8px;
overflow: auto; overflow: auto;
......
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
<div class="item-title">{{ item.name }}</div> <div class="item-title">{{ item.name }}</div>
<div class="type"> <div class="type">
<div class="type-item" :class="getTagClass(ele.industryName)" v-for="ele, idxx in item.industryList" <!-- <div class="type-item" :class="getTagClass(ele.industryName)" v-for="ele, idxx in item.industryList"
:key="idxx"> :key="idxx">
{{ ele.industryName }} {{ ele.industryName }}
</div> </div> -->
<AreaTag v-for="ele, idxx in item.industryList" :key="idxx" :tagName="ele.industryName"></AreaTag>
</div> </div>
</div> </div>
</div> </div>
...@@ -101,10 +102,11 @@ ...@@ -101,10 +102,11 @@
</el-tooltip> </el-tooltip>
<div class="item-bottom"> <div class="item-bottom">
<div class="bottom-left"> <div class="bottom-left">
<div class="left-item" :class="getTagClass(ele.industryName)" v-for="ele, idx in item.industryList" <!-- <div class="left-item" :class="getTagClass(ele.industryName)" v-for="ele, idx in item.industryList"
:key="idx"> :key="idx">
<span>{{ ele.industryName }}</span> <span>{{ ele.industryName }}</span>
</div> </div> -->
<AreaTag v-for="ele, idx in item.industryList" :key="idx" :tagName="ele.industryName"></AreaTag>
</div> </div>
<div class="bottom-right">{{ getTime(item.time) }}</div> <div class="bottom-right">{{ getTime(item.time) }}</div>
</div> </div>
...@@ -131,8 +133,9 @@ ...@@ -131,8 +133,9 @@
</div> </div>
<div class="item-content"> <div class="item-content">
<div class="content-list" v-for="(ele, idx) in item.statementList" :key="idx" @click="handleClick(ele)"> <div class="content-list" v-for="(ele, idx) in item.statementList" :key="idx" @click="handleClick(ele)">
<div class="list-left" :class="getTagClass(getName(ele.industryList))"> <div class="list-left">
<span>{{ getName(ele.industryList) }}</span> <!-- <span>{{ getName(ele.industryList) }}</span> -->
<AreaTag v-if="getName(ele.industryList)" :tagName="getName(ele.industryList)"></AreaTag>
</div> </div>
<div class="list-content">{{ ele.summary }}</div> <div class="list-content">{{ ele.summary }}</div>
<div class="list-time">{{ getTime(ele.stateTime) }}</div> <div class="list-time">{{ getTime(ele.stateTime) }}</div>
...@@ -1382,12 +1385,16 @@ watch(activeDate, async () => { ...@@ -1382,12 +1385,16 @@ watch(activeDate, async () => {
.type { .type {
margin-left: 22px; margin-left: 22px;
margin-top: 51px; margin-top: 31px;
margin-bottom: 8px; margin-bottom: 8px;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
width: calc(100% - 22px); width: calc(100% - 22px);
height: 25px; height: 55px;
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
.type-item { .type-item {
display: inline-block; display: inline-block;
...@@ -1757,6 +1764,7 @@ watch(activeDate, async () => { ...@@ -1757,6 +1764,7 @@ watch(activeDate, async () => {
.bottom-left { .bottom-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px;
.left-item { .left-item {
/* 数据展示/Tag标签/亮色/蓝 */ /* 数据展示/Tag标签/亮色/蓝 */
...@@ -1922,11 +1930,8 @@ watch(activeDate, async () => { ...@@ -1922,11 +1930,8 @@ watch(activeDate, async () => {
/* 自动布局 */ /* 自动布局 */
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: right;
align-items: center; align-items: center;
padding: 2px 8px;
box-sizing: border-box;
border-radius: 4px;
span { span {
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<div style="justify-content: space-between;display: flex;width: 300px;"> <div style="justify-content: space-between;display: flex;width: 300px;">
<div class="tag">{{ item.eventStrategy }}</div> <div class="tag">{{ item.eventStrategy }}</div>
<img :src="item.eventCountryImg" class="icon" <img :src="item.eventCountryImg" class="icon"
style="border-radius: 14px;height: 28px;width: 28px;;"></img> style="border-radius: 14px;height: 28px;width: 28px;" />
</div> </div>
<div class="title" :style="{ <div class="title" :style="{
......
<!--ZM博弈概览页--> <!--ZM博弈概览页-->
<template> <template>
<div class="home-wrapper"> <div class="home-wrapper">
<img :src="background" alt="" class="background-img" />
<!-- <div class="home-header">
<div class="header-left">
<HeaderMenu></HeaderMenu>
</div>
<div class="header-right">
<div class="header-search-box">
<div class="header-search-left">
<div class="input-box">
<el-input v-model="searchText" @keyup.enter="handleSearch" />
</div>
<div class="icon" @click="handleSearch">
<img src="./assets/images/search-icon.png" alt="" />
</div>
</div>
<div class="header-search-right" @click="handleToSearch">
<div class="header-img-box"><img src="./assets/images/search-btn.png" alt="" /></div>
</div>
</div>
</div>
</div> -->
<div class="content-box"> <div class="content-box">
<div class="home-top-bg"></div>
<!-- 导航栏 --> <!-- 导航栏 -->
<div class="content-nav"> <div class="content-nav">
<div class="nav-title">中美科技博弈概览</div> <div class="nav-title">中美科技博弈概览</div>
...@@ -41,13 +21,8 @@ ...@@ -41,13 +21,8 @@
<!-- 美对华“四全”打压 --> <!-- 美对华“四全”打压 -->
<div id="us-pressure" class="us-pressure-section"> <div id="us-pressure" class="us-pressure-section">
<div class="data-select"> <div class="data-select">
<div <div v-for="item in dateList" :key="item.type" class="date-item" :class="{ active: activeDate === item.type }"
v-for="item in dateList" @click="handleDateClick(item.type)">
:key="item.type"
class="date-item"
:class="{ active: activeDate === item.type }"
@click="handleDateClick(item.type)"
>
<!-- <img :src="activeDate === item.type ? item.activeIcon : item.icon" alt="" /> --> <!-- <img :src="activeDate === item.type ? item.activeIcon : item.icon" alt="" /> -->
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
</div> </div>
...@@ -85,7 +60,6 @@ import { onMounted, onUnmounted, ref, computed, provide } from "vue"; ...@@ -85,7 +60,6 @@ import { onMounted, onUnmounted, ref, computed, provide } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import background from "./assets/images/backgroundBT.png"; import background from "./assets/images/backgroundBT.png";
import HeaderMenu from "@/components/headerMenu.vue";
import right from "./assets/icons/right.png"; import right from "./assets/icons/right.png";
import commonTitle from "./commonTitle/comTitle.vue"; import commonTitle from "./commonTitle/comTitle.vue";
import week from "./assets/icons/week.png"; import week from "./assets/icons/week.png";
...@@ -168,6 +142,7 @@ const handleDateClick = type => { ...@@ -168,6 +142,7 @@ const handleDateClick = type => {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.home-wrapper { .home-wrapper {
width: 100%; width: 100%;
height: 100vh; height: 100vh;
...@@ -175,13 +150,13 @@ const handleDateClick = type => { ...@@ -175,13 +150,13 @@ const handleDateClick = type => {
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
.background-img { // .background-img {
width: 100%; // width: 100%;
height: 700px; // height: 700px;
position: absolute; // position: absolute;
left: 0; // left: 0;
z-index: -1; // z-index: -1;
} // }
// .home-header { // .home-header {
// height: 64px; // height: 64px;
// flex-shrink: 0; // flex-shrink: 0;
...@@ -268,11 +243,26 @@ const handleDateClick = type => { ...@@ -268,11 +243,26 @@ const handleDateClick = type => {
.content-box { .content-box {
flex: 1; flex: 1;
width: 100%; width: 100%;
height: 100%;
overflow-y: auto; overflow-y: auto;
padding-top: 48px; padding-top: 48px;
position: relative;
.home-top-bg {
background:
url("./assets/images/background.png"), linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
background-size: 100% 100%;
position: absolute;
width: 100%;
height: 100%;
z-index: -1;
top: -64px;
}
.us-pressure-section { .us-pressure-section {
position: relative; position: relative;
margin-top: 64px; margin-top: 64px;
.data-select { .data-select {
width: 108px; width: 108px;
height: 192px; height: 192px;
...@@ -297,6 +287,7 @@ const handleDateClick = type => { ...@@ -297,6 +287,7 @@ const handleDateClick = type => {
cursor: pointer; cursor: pointer;
border-top-right-radius: 10px; border-top-right-radius: 10px;
border-bottom-right-radius: 10px; border-bottom-right-radius: 10px;
&:hover { &:hover {
background: var(--color-bg-hover); background: var(--color-bg-hover);
// span{ // span{
...@@ -320,6 +311,7 @@ const handleDateClick = type => { ...@@ -320,6 +311,7 @@ const handleDateClick = type => {
&.active { &.active {
background-color: rgba(231, 243, 255, 1); background-color: rgba(231, 243, 255, 1);
span { span {
font-weight: 700; font-weight: 700;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
...@@ -328,6 +320,7 @@ const handleDateClick = type => { ...@@ -328,6 +320,7 @@ const handleDateClick = type => {
} }
} }
} }
.content-nav { .content-nav {
.nav-title { .nav-title {
font-size: 48px; font-size: 48px;
...@@ -377,11 +370,13 @@ const handleDateClick = type => { ...@@ -377,11 +370,13 @@ const handleDateClick = type => {
} }
} }
} }
.bottom-info { .bottom-info {
width: 100%; width: 100%;
height: 176px; height: 176px;
margin-bottom: 80px; margin-bottom: 80px;
background-color: rgb(247, 248, 249); background-color: rgb(247, 248, 249);
.info-item { .info-item {
width: 1601px; width: 1601px;
height: 176px; height: 176px;
...@@ -389,14 +384,17 @@ const handleDateClick = type => { ...@@ -389,14 +384,17 @@ const handleDateClick = type => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.info-item-left { .info-item-left {
display: flex; display: flex;
align-items: center; align-items: center;
img { img {
width: 134px; width: 134px;
height: 91px; height: 91px;
margin-right: 27px; margin-right: 27px;
} }
.info-item-left-content { .info-item-left-content {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -405,11 +403,13 @@ const handleDateClick = type => { ...@@ -405,11 +403,13 @@ const handleDateClick = type => {
font-family: Microsoft YaHei; font-family: Microsoft YaHei;
} }
} }
.info-item-right { .info-item-right {
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; position: relative;
height: 60px; height: 60px;
.text2 { .text2 {
left: 60px; left: 60px;
position: absolute; position: absolute;
...@@ -425,6 +425,7 @@ const handleDateClick = type => { ...@@ -425,6 +425,7 @@ const handleDateClick = type => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: center; text-align: center;
} }
.text3 { .text3 {
left: 385px; left: 385px;
top: 12px; top: 12px;
...@@ -441,11 +442,13 @@ const handleDateClick = type => { ...@@ -441,11 +442,13 @@ const handleDateClick = type => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: center; text-align: center;
} }
.img1 { .img1 {
width: 300px; width: 300px;
height: 48.8px; height: 48.8px;
margin-right: 24px; margin-right: 24px;
} }
.img2 { .img2 {
width: 300px; width: 300px;
height: 43.5px; height: 43.5px;
......
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论