提交 04d40262 authored 作者: 刘宇琪's avatar 刘宇琪

Merge remote-tracking branch 'origin/master' into liuyuqi

......@@ -7,6 +7,10 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
*.rar
*.zip
*.7z
# Dependencies
node_modules
.pnpm
......@@ -14,6 +18,7 @@ node_modules
# Build outputs
dist
dist.rar
dist-ssr
*.local
......
......@@ -2,85 +2,11 @@
<div id="app">
<div class="pro-wrapper">
<div class="home-page">
<!-- <div class="navbar">
<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/>
<ModuleHeader/>
<div class="main-container">
<router-view />
</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="item">
<div class="icon">
......@@ -127,10 +53,8 @@
<script setup>
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 { useRoute } from "vue-router";
import Breadcrumb from "@/components/BreadCrumb/index.vue";
import AiBox from "./components/AiBox.vue";
import { getPersonType } from "@/api/common/index";
// import { useDraggable } from "@vueuse/core";
......
......@@ -10,7 +10,7 @@
</div>
</div>
<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">
<img
:src="getProxyUrl(news.newsImage) || defaultImg"
......
<template>
<div class="box3">
<div class="box3-header">
<div class="box3-header-left">
<div class="box3-header-icon">
<img src="./image1.png" alt="" />
</div>
<div class="box3-header-title">{{ "新闻资讯" }}</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div>
</div>
<div class="box3-main">
<div class="box3-item" v-for="(news, index) in newsList" :key="index" @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>
</div>
</div>
</template>
<script setup>
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
const props = defineProps({
// 新闻列表数据
newsList: {
type: Array,
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 handleToMoreNews = () => {
emit('more-click')
};
const handleToNewsAnalysis = (item, index) => {
emit('item-click', item, index)
};
</script>
<style lang="scss" scoped>
.box3 {
width: 792px !important;
height: 450px !important;
border-radius: 10px !important;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1) !important;
background: rgba(255, 255, 255, 1) !important;
display: flex !important;
flex-direction: column;
gap: 0 !important;
overflow: hidden;
.box3-header {
height: 48px !important;
border-bottom: 1px solid rgba(234, 236, 238, 1) !important;
margin: 0 !important;
display: flex !important;
justify-content: space-between !important;
position: relative !important;
width: 100%;
box-sizing: border-box;
.box3-header-left {
display: flex !important;
.box3-header-icon {
margin-left: 19px !important;
margin-top: 14px !important;
width: 24px !important;
height: 24px !important;
img {
width: 100% !important;
height: 100% !important;
}
}
.box3-header-title {
margin-top: 11px !important;
margin-left: 17px !important;
height: 26px !important;
color: var(--color-main-active) !important;
font-family: 'Source Han Sans CN' !important;
font-size: 20px !important;
font-weight: 700 !important;
line-height: 26px !important;
}
}
.more {
width: 45px;
height: 24px;
position: absolute;
top: 12px;
right: 27px;
color: rgba(20, 89, 187, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.box3-main {
height: 401px;
overflow-y: auto;
overflow-x: hidden;
padding: 6px 0;
.box3-item {
display: flex;
height: 78px;
width: 749px;
margin-left: 21px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
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: 72px;
height: 48px;
margin-top: 15px;
img {
width: 100%;
height: 100%;
}
}
.right {
width: 657px;
margin-left: 20px;
.right-top {
width: 657px;
display: flex;
justify-content: space-between;
.title {
margin-top: 14px;
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 {
width: 157px;
text-align: right;
height: 22px;
margin-top: 14px;
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 {
width: 657px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
</style>
\ No newline at end of file
差异被折叠。
差异被折叠。
<template>
<div class="search-container" v-show="!isShow">
<div class="search-main">
<input v-model="store.searchBillText" :placeholder="placeholder" @keyup.enter="handleSearch" class="search-input" />
<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"
class="search-input" />
<div class="search-btn" @click="handleSearch">
<img src="@/assets/icons/search-icon.png" alt />
搜索
......@@ -43,19 +54,26 @@
</template>
<script setup>
import { ref, nextTick, watchEffect, onMounted } from "vue";
import { ref, nextTick, watchEffect } from "vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import { useRouter } from "vue-router";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
const store = useWrittingAsstaintStore();
const router = useRouter();
let { countInfo, containerRef, placeholder, areaName } = defineProps({
const {
countInfo,
containerRef,
placeholder,
areaName,
enableBillTypeSwitch,
defaultBillSearchType
} = defineProps({
countInfo: {
type: Array,
default: () => []
},
containerRef: {
type: Object,
default: {}
......@@ -67,18 +85,48 @@ let { countInfo, containerRef, placeholder, areaName } = defineProps({
areaName: {
type: String,
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 = () => {
window.sessionStorage.setItem("curTabName", `搜索-${store.searchBillText}`);
if (!areaName) return;
const curRoute = router.resolve({
path: "/searchResults",
query: {
const query = {
searchText: store.searchBillText,
areaName: areaName
};
// 法案页附带搜索类型参数,便于搜索结果页后续按类型处理
if (enableBillTypeSwitch) {
query.billSearchType = billSearchType.value;
}
const curRoute = router.resolve({
path: "/searchResults",
query
});
window.open(curRoute.href, "_blank");
};
......@@ -100,7 +148,7 @@ watchEffect(() => {
store.changeIsShowSearchBar(isShow.value);
});
store.setSearchData({ placeholder, areaName,containerRef:homeMainRef });
store.setSearchData({ placeholder, areaName, containerRef: homeMainRef });
// 锚点跳转
const handleToPosi = id => {
const element = document.getElementById(id);
......@@ -131,6 +179,43 @@ const handleToPosi = id => {
width: 960px;
height: 168px;
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 {
width: 688px;
height: 48px;
......@@ -170,6 +255,7 @@ const handleToPosi = id => {
}
}
}
.search-main {
display: flex;
padding-right: 3px;
......@@ -181,9 +267,11 @@ const handleToPosi = id => {
background-color: rgba(255, 255, 255, 0.65);
border-radius: 10px;
border: 1px solid #fff;
&:hover {
border: 1px solid var(--color-main-active);
}
.search-input {
border: none;
outline: none;
......@@ -201,6 +289,7 @@ const handleToPosi = id => {
color: #a8abb2;
}
}
.search-btn {
cursor: pointer;
display: flex;
......@@ -216,6 +305,7 @@ const handleToPosi = id => {
font-family: "Microsoft YaHei";
line-height: 22px;
color: #fff;
img {
width: 18px;
height: 18px;
......@@ -223,6 +313,7 @@ const handleToPosi = id => {
}
}
}
.search-bottom {
width: 688px;
height: 48px;
......@@ -230,6 +321,7 @@ const handleToPosi = id => {
margin-top: 36px;
display: flex;
justify-content: space-between;
// gap: 16px;
.btn {
display: flex;
......@@ -243,9 +335,11 @@ const handleToPosi = id => {
background: #e7f3ff;
cursor: pointer;
position: relative;
&:hover {
background: #cae3fc;
}
.btn-text {
width: 80px;
color: var(--color-main-active);
......@@ -256,12 +350,14 @@ const handleToPosi = id => {
margin-left: 36px;
text-align: center;
}
.btn-icon {
position: absolute;
top: 16px;
right: 19px;
width: 6px;
height: 12px;
img {
width: 100%;
height: 100%;
......
<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(() => ({
'tag13': props.tagName === '极地',
'tag14': props.tagName === '核',
'tag15': props.tagName === '其他',
}))
......@@ -50,6 +49,9 @@ const classObject = computed(() => ({
letter-spacing: 0px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
.tag1 {
......
<template>
<button class="main-button" :type="type">
</button>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'normal'
},
name: {
type: String,
default: ''
}
})
</script>
<style scoped>
.main-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;
}
.main-button[type="normal"] {
background-color: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
}
.main-button[type="active"] {
background-color: rgba(231, 243, 255, 1);
color: var(--color-main-active);
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 @@
<div class="wrapper-header">
<div class="header-icon"></div>
<div class="header-title">{{ title }}</div>
<div class="header-btn" v-if="!showAllBtn">
<slot name="headerBtn"></slot>
</div>
<div class="header-btn1" v-else>
<slot name="headerBtn"></slot>
</div>
<div class="header-right">
<div class="header-right-btn" @click="handleSave" v-if="showAllBtn">
<img src="@/assets/icons/box-header-icon1.png" alt="">
......@@ -22,6 +28,7 @@
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { ref, computed } from 'vue'
const props = defineProps({
......@@ -44,17 +51,23 @@ const props = defineProps({
})
const handleSave = () => {
alert('save')
ElMessage.success('保存当前内容')
// emit('save')
}
const handleDownload = () => {
alert('download')
ElMessage.success('下载当前内容')
// emit('download')
}
const handleCollect = () => {
alert('collect')
ElMessage.success('收藏当前内容')
// emit('collect')
}
const emit = defineEmits(['save','download','collect'])
</script>
......@@ -83,19 +96,39 @@ const handleCollect = () => {
margin-left: 14px;
margin-top: 14px;
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);
font-family: Source Han Sans CN;
font-style: Bold;
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
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 {
position: absolute;
top: 14px;
right: 12px;
right: 14px;
height: 28px;
display: flex;
justify-content: flex-end;
......@@ -117,9 +150,8 @@ const handleCollect = () => {
.wrapper-main {
height: calc(100% - 45px);
overflow: hidden;
overflow-y: auto;
box-sizing: border-box;
padding: 5px 10px;
// overflow-y: auto;
padding: 5px auto;
}
}
</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="headerIcon"></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>
<div class="overview-box-wrapper" :style="{ width: width ? width : '640px', height: height ? height : '415px' }">
<div class="wrapper-header">
<div class="header-icon"></div>
<div class="header-title">{{ title }}</div>
<div class="header-right">
<div class="header-right-btn" @click="handleSave">
<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 class="overview-normal-box-wrapper"
:style="{ width: width ? width : '1064px', height: height ? height : '460px' }">
<div class="overview-normal-box-header">
<div class="header-left">
<div class="header-icon">
<slot name="headerIcon"></slot>
</div>
<div class="header-right-btn" @click="handleCollect">
<img src="@/assets/icons/box-header-icon3.png" alt="">
<div class="header-title">{{ title }}</div>
</div>
<div class="header-right">
<slot name="headerRight"></slot>
</div>
</div>
<div class="wrapper-main">
......@@ -39,81 +37,54 @@ const props = defineProps({
}
})
const handleSave = () => {
alert('save')
}
const handleDownload = () => {
alert('download')
}
const handleCollect = () => {
alert('collect')
}
</script>
<style lang="scss" scoped>
.overview-box-wrapper {
.overview-normal-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;
.wrapper-header {
height: 45px;
.overview-normal-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 {
margin-top: 18px;
width: 8px;
height: 20px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
width: 24px;
height: 24px;
margin-top: 12px;
margin-left: 20px;
}
.header-title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
margin-left: 16px;
height: 48px;
color: var(--color-main-active);
font-family: Source Han Sans CN;
font-style: Bold;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
line-height: 48px;
text-align: center;
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
height: 28px;
display: flex;
justify-content: flex-end;
gap: 4px;
.header-right-btn {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
height: 48px;
margin-right: 28px;
}
}
.wrapper-main {
height: calc(100% - 45px);
height: calc(100% - 48px);
overflow: hidden;
overflow-y: auto;
box-sizing: border-box;
padding: 5px 10px;
}
}
</style>
<template>
<div class="box4">
<div class="box4-header">
<div class="header-icon">
<img src="./image1.png" alt="" />
</div>
<div class="header-title">{{ "社交媒体" }}</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div>
<div class="box4-main">
<div class="message-bubble" v-for="(item, index) in messageList" :key="index" @click="handleClickPerson(item)">
<div class="avatar-container">
<img :src="item[props.imageUrl] || avatarUser" :alt="item[props.name]" class="avatar" />
<div class="avatar-containerOne" v-if="isRepublicanParty"><img src="./image2.png" alt=""
class="avatar-imageOne" /></div>
<div class="avatar-containerTwo" v-if="isUnitedStatesSenate"><img src="./image3.png" alt=""
class="avatar-imageTwo" /></div>
</div>
<div class="bubble-container">
<div class="bubble">
<div class="bubble-header">
<span class="name">{{ item[props.name] }}</span>
<span class="meta">{{ item[props.time] }} · {{ item[props.source] }}</span>
</div>
<div class="bubble-content">
{{ item[props.content] }}
</div>
<div class="triangle"></div>
</div>
</div>
</div>
<!-- <MessageBubble v-for="(item, index) in messageList" @click="handleClickPsserson(item)"
@info-click="handleMediaClick(item)" :key="index" :avatar="item.img ? item.img : DefaultIcon1" :name="item.name"
:time="item.time" :source="item.source" :content="item.content" /> -->
<!-- <div class="box4-main-item" v-for="(item, index) in messageList" :key="index">
<div class="left" @click="handleClickPerson(item)">
<img :src="item.img ? item.img : DefaultIcon1" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="name">{{ item.name }}</div>
<div class="time">{{ item.time }}</div>
</div>
<div class="content">{{ item.content }}</div>
</div>
</div> -->
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import avatarUser from "@/assets/images/avatar_user.png";
const emit = defineEmits(["click", "info-click"]);
const props = defineProps({
isRepublicanParty: {
type: Boolean,
default: false
},
isUnitedStatesSenate: {
type: Boolean,
default: false
},
messageList: {
type: Array,
default: () => []
},
imageUrl: {
type: String,
default: "imageUrl"
},
name: {
type: String,
default: "name"
},
time: {
type: String,
default: "time"
},
source: {
type: String,
default: "source"
},
content: {
type: String,
default:
"content"
}
});
const formattedTime = computed((index) => {
const date = new Date(index);
if (isNaN(date.getTime())) {
return index; // 如果不是有效日期,返回原值
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate());
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
// return `${year}年${month}月${day}日 ${hours}:${minutes}:${seconds}`;
return `${month}${day}${hours}:${minutes}`;
});
const handleClickPerson = (item) => {
emit("person-click", item);
};
const handleInfoClick = (item) => {
emit("info-click", item);
};
const handleToMoreNews = (item) => {
emit("more-click", item);
};
</script>
<style scoped>
.box4 {
margin-left: 20px;
width: 792px;
height: 450px;
border-radius: 10px;
box-shadow: 0 0 20px 0 rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
border: 1px solid rgb(234, 236, 238);
.box4-header {
width: 792px;
height: 48px;
border-bottom: 1px solid rgb(234, 236, 238);
display: flex;
box-sizing: border-box;
position: relative;
.header-icon {
margin-left: 18px;
margin-top: 14px;
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
width: 80px;
margin-top: 11px;
margin-left: 18px;
height: 26px;
color: rgb(5, 95, 194);
font-family: "Source Han Sans CN";
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
}
.more {
width: 45px;
height: 24px;
position: absolute;
top: 12px;
right: 27px;
color: rgb(5, 95, 194);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
text-align: right;
}
}
.box4-main {
height: 402px;
overflow-y: auto;
box-sizing: border-box;
padding-bottom: 8px;
padding-left: 21px;
padding-top: 23px;
.message-bubble {
display: flex;
max-width: 740px;
margin-bottom: 15px;
.avatar-container {
flex-shrink: 0;
flex-grow: 0;
flex: 0;
width: 42px;
height: 42px;
margin-right: 14.5px;
cursor: pointer;
position: relative;
.avatar-containerOne,
.avatar-containerTwo {
display: inline-block;
position: absolute;
}
.avatar-containerOne {
left: 2px;
top: 29px;
.avatar-imageOne {
width: 20px;
height: 20px;
}
}
.avatar-containerTwo {
right: 2px;
top: 29px;
.avatar-imageTwo {
width: 20px;
height: 20px;
}
}
.avatar {
width: 42px;
height: 42px;
border-radius: 50%;
object-fit: cover;
}
}
.bubble-container {
flex: 1;
position: relative;
.bubble {
background-color: rgba(246, 250, 255, 1);
border-radius: 12px;
padding: 12px 12px;
position: relative;
border: 1px solid rgba(231, 243, 255, 1);
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
.bubble-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
.name {
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.meta {
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: right;
}
}
.bubble-content {
color: rgba(59, 65, 75, 1);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.triangle {
position: absolute;
left: -9px;
/* 向左偏移1px,给描边留出空间 */
top: 15px;
width: 0;
height: 0;
/* 外层:描边颜色的三角形(比内层大1px) */
border-top: 9px solid transparent;
border-bottom: 9px solid transparent;
border-right: 9px solid rgb(231, 243, 255);
}
/* 内层:原有颜色的三角形,覆盖在外层上面,模拟描边效果 */
.triangle::after {
content: '';
position: absolute;
top: -8px;
/* 向上偏移1px,对齐中心 */
left: 1px;
/* 向右偏移1px,露出外层的描边 */
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid rgba(246, 250, 255, 1);
}
}
}
}
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.message-bubble {
max-width: 100%;
}
.bubble-header {
flex-direction: column;
align-items: flex-start;
}
.meta {
margin-top: 4px;
}
}
</style>
<template>
<div class="navbarV2">
<div class="module-header-wrapper">
<div class="nav-content">
<div class="nav-left" :class="{ 'flex-start': isShowSearchBar }">
<div class="icon">
......@@ -222,7 +222,7 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.navbarV2 {
.module-header-wrapper {
width: 100%;
// height: 64px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
......@@ -365,7 +365,7 @@ onMounted(() => {
.menu-box {
position: absolute;
z-index: 999999;
z-index: 999999999;
width: 713px;
height: 413px;
top: 52px;
......
<template>
<div class="box3">
<div class="box3-header">
<div class="box3-header-left">
<div class="box3-header-icon">
<img src="./image1.png" alt="" />
</div>
<div class="box3-header-title">{{ "新闻资讯" }}</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div>
</div>
<div class="box3-main">
<div class="box3-item" v-for="(news, index) in newsList" :key="index" @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>
</div>
</div>
</template>
<script setup>
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
const props = defineProps({
// 新闻列表数据
newsList: {
type: Array,
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 handleToMoreNews = () => {
emit('more-click')
};
const handleToNewsAnalysis = (item, index) => {
emit('item-click', item, index)
};
</script>
<style lang="scss" scoped>
.box3 {
width: 792px !important;
height: 450px !important;
border-radius: 10px !important;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1) !important;
background: rgba(255, 255, 255, 1) !important;
display: flex !important;
flex-direction: column;
gap: 0 !important;
overflow: hidden;
.box3-header {
height: 48px !important;
border-bottom: 1px solid rgba(234, 236, 238, 1) !important;
margin: 0 !important;
display: flex !important;
justify-content: space-between !important;
position: relative !important;
width: 100%;
box-sizing: border-box;
.box3-header-left {
display: flex !important;
.box3-header-icon {
margin-left: 19px !important;
margin-top: 14px !important;
width: 24px !important;
height: 24px !important;
img {
width: 100% !important;
height: 100% !important;
}
}
.box3-header-title {
margin-top: 11px !important;
margin-left: 17px !important;
height: 26px !important;
color: var(--color-main-active) !important;
font-family: 'Source Han Sans CN' !important;
font-size: 20px !important;
font-weight: 700 !important;
line-height: 26px !important;
}
}
.more {
width: 45px;
height: 24px;
position: absolute;
top: 12px;
right: 27px;
color: rgba(20, 89, 187, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.box3-main {
height: 401px;
overflow-y: auto;
overflow-x: hidden;
padding: 6px 0;
.box3-item {
display: flex;
height: 78px;
width: 749px;
margin-left: 21px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
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: 72px;
height: 48px;
margin-top: 15px;
img {
width: 100%;
height: 100%;
}
}
.right {
width: 657px;
margin-left: 20px;
.right-top {
width: 657px;
display: flex;
justify-content: space-between;
.title {
margin-top: 14px;
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 {
width: 157px;
text-align: right;
height: 22px;
margin-top: 14px;
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 {
width: 657px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
</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="message-bubble">
<div class="avatar-container" @click="handleClick">
<img :src="avatar || avatarUser" :alt="name" class="avatar" />
</div>
<div class="bubble-container">
<div class="bubble">
<div class="bubble-header">
<span class="name">{{ name }}</span>
<span class="meta">{{ formattedTime }} · {{ source }}</span>
</div>
<div class="bubble-content" @click="handleInfoClick">
{{ content }}
</div>
<div class="triangle"></div>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import avatarUser from "@/assets/images/avatar_user.png";
const emit = defineEmits(["click", "info-click"]);
const props = defineProps({
avatar: {
type: String,
default: "https://via.placeholder.com/40x40/4A90E2/FFFFFF?text=T"
},
name: {
type: String,
default: "唐纳德·特朗普"
},
time: {
type: String,
default: "15:23"
},
source: {
type: String,
default: "发布于真实社交"
},
content: {
type: String,
default:
"埃隆·马斯克在强力支持我竞选总统之前,早就知道我强烈反对‘电动汽车强制令’。这太荒谬了,这一直是我竞选活动的主要部分。电动汽车没问题,但不应该强迫每个人都拥有一辆。埃隆获得的补贴可能远远超过历史上任何一个人。如果没有补贴,埃隆可能不得不关门大吉,回到南非老家。"
}
});
const formattedTime = computed(() => {
const date = new Date(props.time);
if (isNaN(date.getTime())) {
return props.time; // 如果不是有效日期,返回原值
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate());
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
// return `${year}年${month}月${day}日 ${hours}:${minutes}:${seconds}`;
return `${month}${day}${hours}:${minutes}`;
});
const handleClick = () => {
emit("click");
};
const handleInfoClick = () => {
emit("info-click");
};
</script>
<style scoped>
.message-bubble {
display: flex;
max-width: 750px;
margin-top: 5px;
margin-bottom: 12px;
}
.avatar-container {
flex-shrink: 0;
flex-grow: 0;
flex: 0;
margin-right: 12px;
cursor: pointer;
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
}
.bubble-container {
flex: 1;
position: relative;
}
.bubble {
background-color: rgba(246, 250, 255, 1);
border-radius: 12px;
padding: 12px 12px;
position: relative;
border: 1px solid rgba(231, 243, 255, 1);
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
}
.bubble-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
font-family: "微软雅黑";
}
.name {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.meta {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: right;
}
.bubble-content {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
font-family: "微软雅黑";
}
.triangle {
position: absolute;
left: -8px;
top: 15px;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid rgba(246, 250, 255, 1);
}
/* 响应式设计 */
@media (max-width: 768px) {
.message-bubble {
max-width: 100%;
}
.bubble-header {
flex-direction: column;
align-items: flex-start;
}
.meta {
margin-top: 4px;
}
}
</style>
......@@ -12,11 +12,18 @@ import "./styles/main.css";
import '@/assets/fonts/font.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import AreaTag from '@/components/areaTag.vue'
import leftBtn from "@/components/pageBtn/leftBtn.vue";
import rightBtn from "@/components/pageBtn/rightBtn.vue";
import OverviewBox from '@/components/BoxBackground/overviewBox.vue'
import AnalysisBox from '@/components/BoxBackground/analysisBox.vue'
import AreaTag from '@/components/base/AreaTag/index.vue'
import LeftBtn from "@/components/base/PageBtn/LeftBtn.vue";
import RightBtn from "@/components/base/PageBtn/RightBtn.vue";
import OverviewMainBox from "@/components/base/BoxBackground/OverviewMainBox.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 实例
import pinia from './stores'
......@@ -35,9 +42,18 @@ app.use(ElementPlus, {
})
app.use(pinia) // 挂载 Pinia
app.component("CardTitle", CardTitle);
app.component('AreaTag', AreaTag)
app.component('leftBtn', leftBtn)
app.component('rightBtn', rightBtn)
app.component('OverviewBox', OverviewBox)
app.component('AnalysisBox', AnalysisBox)
app.component('AreaTag', AreaTag) // 领域标签
app.component('LeftBtn', LeftBtn) // 向左按钮
app.component('RightBtn', RightBtn) // 向右按钮
app.component('OverviewMainBox', OverviewMainBox) // 概览页最新动态背景盒子
app.component('OverviewNormalBox', OverviewNormalBox) // 概览页一版模块背景盒子
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");
import { defineStore } from 'pinia'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
state: () => ({
......@@ -92,11 +92,43 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
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) {
this.tempActiveIndex = index;
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) {
this.routeQuery = { ...query };
......@@ -157,8 +189,7 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
}
} catch (error) {
console.error(`获取${typeName}数据异常:`, error);
ElMessage.error(`获取${typeName}数据失败: ${error.message}`);
this.resetGenerateState();
await this._handleGenerateError(`获取${typeName}数据失败: ${error.message}`);
}
},
......@@ -309,15 +340,20 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
});
}
break;
case 'error':
await this._handleGenerateError(jsonData.message);
break;
default:
console.debug('未处理的SSE事件类型', event.event);
break;
}
},
onerror: (error) => {
onerror: async (error) => {
console.error('SSE连接错误', error);
ElMessage.warning('写报生成报错!');
if (error.name !== 'AbortError') return true;
if (error.name !== 'AbortError') {
await this._handleGenerateError('写报生成报错!');
return true;
}
this.resetGenerateState();
},
onclose: () => {
......@@ -327,8 +363,9 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
});
} catch (error) {
if (error.name !== 'AbortError') {
ElMessage.error(`PDF解析请求失败:${error.message}`);
console.error('PDF SSE请求异常', error);
await this._handleGenerateError(`PDF解析请求失败:${error.message}`);
throw error;
}
this.resetGenerateState();
throw error;
......@@ -435,8 +472,7 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
} else if ((msgData.event_type || '').toLowerCase().includes('error')) {
// 优先从 data.error 获取详细错误描述
const errorMsg = msgData.data?.error || str || '生成失败';
ElMessage.error('生成失败:' + errorMsg);
this.resetGenerateState();
this._handleGenerateError('生成失败:' + errorMsg);
} else {
// 老版 --index.vue 行为:步骤栏直接追加服务端发来的完整步骤内容,不加时间戳、不强行换行
// 这样可以避免 SSE 分片导致的“步骤破碎”(一条步骤被拆成多条显示)
......@@ -446,15 +482,17 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
this.curAgentTool = msgData.tool || '无';
}
},
onerror: (error) => {
ElMessage.warning('写报生成报错!');
this.resetGenerateState();
onerror: async (error) => {
await this._handleGenerateError('写报生成报错!');
throw new Error(error);
}
});
} catch (error) {
ElMessage.warning('写报生成报错!');
if (error.name !== 'AbortError') {
await this._handleGenerateError(error.message || '写报生成报错!');
} else {
this.resetGenerateState();
}
throw error;
}
},
......
......@@ -75,12 +75,13 @@
</div>
</div>
<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="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 }}
</div>
</div> -->
<AreaTag v-for="tag,index in value.tags" :key="index" :tagName="tag"></AreaTag>
</div>
<div class="date">
......@@ -237,7 +238,8 @@
>
<div class="timeline-content-item">
<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 class="item-title">
<CommonPrompt :content="node.title" />
......@@ -1921,7 +1923,8 @@ watch(activeDate, () => {
gap: 8px;
.item-tags {
width: 350px;
width: 355px;
height: 30px;
display: flex;
gap: 8px;
overflow: auto;
......
......@@ -16,10 +16,11 @@
<div class="item-title">{{ item.name }}</div>
<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">
{{ ele.industryName }}
</div>
</div> -->
<AreaTag v-for="ele, idxx in item.industryList" :key="idxx" :tagName="ele.industryName"></AreaTag>
</div>
</div>
</div>
......@@ -101,10 +102,11 @@
</el-tooltip>
<div class="item-bottom">
<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">
<span>{{ ele.industryName }}</span>
</div>
</div> -->
<AreaTag v-for="ele, idx in item.industryList" :key="idx" :tagName="ele.industryName"></AreaTag>
</div>
<div class="bottom-right">{{ getTime(item.time) }}</div>
</div>
......@@ -131,8 +133,9 @@
</div>
<div class="item-content">
<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))">
<span>{{ getName(ele.industryList) }}</span>
<div class="list-left">
<!-- <span>{{ getName(ele.industryList) }}</span> -->
<AreaTag v-if="getName(ele.industryList)" :tagName="getName(ele.industryList)"></AreaTag>
</div>
<div class="list-content">{{ ele.summary }}</div>
<div class="list-time">{{ getTime(ele.stateTime) }}</div>
......@@ -1382,12 +1385,16 @@ watch(activeDate, async () => {
.type {
margin-left: 22px;
margin-top: 51px;
margin-top: 31px;
margin-bottom: 8px;
overflow-x: auto;
overflow-y: hidden;
width: calc(100% - 22px);
height: 25px;
height: 55px;
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
.type-item {
display: inline-block;
......@@ -1757,6 +1764,7 @@ watch(activeDate, async () => {
.bottom-left {
display: flex;
align-items: center;
gap: 8px;
.left-item {
/* 数据展示/Tag标签/亮色/蓝 */
......@@ -1922,11 +1930,8 @@ watch(activeDate, async () => {
/* 自动布局 */
display: flex;
flex-direction: row;
justify-content: center;
justify-content: right;
align-items: center;
padding: 2px 8px;
box-sizing: border-box;
border-radius: 4px;
span {
font-family: Microsoft YaHei;
......
......@@ -25,7 +25,7 @@
<div style="justify-content: space-between;display: flex;width: 300px;">
<div class="tag">{{ item.eventStrategy }}</div>
<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 class="title" :style="{
......
<!--ZM博弈概览页-->
<template>
<div class="home-wrapper">
<img :src="background" alt="" class="background-img" />
<!-- <div class="home-header">
<div class="header-left">
<HeaderMenu></HeaderMenu>
......@@ -23,6 +23,7 @@
</div>
</div> -->
<div class="content-box">
<div class="home-top-bg"></div>
<!-- 导航栏 -->
<div class="content-nav">
<div class="nav-title">中美科技博弈概览</div>
......@@ -41,13 +42,8 @@
<!-- 美对华“四全”打压 -->
<div id="us-pressure" class="us-pressure-section">
<div class="data-select">
<div
v-for="item in dateList"
:key="item.type"
class="date-item"
:class="{ active: activeDate === item.type }"
@click="handleDateClick(item.type)"
>
<div v-for="item in dateList" :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="" /> -->
<span>{{ item.name }}</span>
</div>
......@@ -168,6 +164,7 @@ const handleDateClick = type => {
margin: 0;
padding: 0;
}
.home-wrapper {
width: 100%;
height: 100vh;
......@@ -175,13 +172,13 @@ const handleDateClick = type => {
flex-direction: column;
overflow: hidden;
position: relative;
.background-img {
width: 100%;
height: 700px;
position: absolute;
left: 0;
z-index: -1;
}
// .background-img {
// width: 100%;
// height: 700px;
// position: absolute;
// left: 0;
// z-index: -1;
// }
// .home-header {
// height: 64px;
// flex-shrink: 0;
......@@ -268,11 +265,26 @@ const handleDateClick = type => {
.content-box {
flex: 1;
width: 100%;
height: 100%;
overflow-y: auto;
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 {
position: relative;
margin-top: 64px;
.data-select {
width: 108px;
height: 192px;
......@@ -297,6 +309,7 @@ const handleDateClick = type => {
cursor: pointer;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
&:hover {
background: var(--color-bg-hover);
// span{
......@@ -320,6 +333,7 @@ const handleDateClick = type => {
&.active {
background-color: rgba(231, 243, 255, 1);
span {
font-weight: 700;
color: rgb(5, 95, 194);
......@@ -328,6 +342,7 @@ const handleDateClick = type => {
}
}
}
.content-nav {
.nav-title {
font-size: 48px;
......@@ -377,11 +392,13 @@ const handleDateClick = type => {
}
}
}
.bottom-info {
width: 100%;
height: 176px;
margin-bottom: 80px;
background-color: rgb(247, 248, 249);
.info-item {
width: 1601px;
height: 176px;
......@@ -389,14 +406,17 @@ const handleDateClick = type => {
display: flex;
justify-content: space-between;
align-items: center;
.info-item-left {
display: flex;
align-items: center;
img {
width: 134px;
height: 91px;
margin-right: 27px;
}
.info-item-left-content {
font-size: 16px;
font-weight: 400;
......@@ -405,11 +425,13 @@ const handleDateClick = type => {
font-family: Microsoft YaHei;
}
}
.info-item-right {
display: flex;
align-items: center;
position: relative;
height: 60px;
.text2 {
left: 60px;
position: absolute;
......@@ -425,6 +447,7 @@ const handleDateClick = type => {
letter-spacing: 0px;
text-align: center;
}
.text3 {
left: 385px;
top: 12px;
......@@ -441,11 +464,13 @@ const handleDateClick = type => {
letter-spacing: 0px;
text-align: center;
}
.img1 {
width: 300px;
height: 48.8px;
margin-right: 24px;
}
.img2 {
width: 300px;
height: 43.5px;
......
......@@ -2,7 +2,7 @@
<div class="background-wrap">
<div class="background-wrap-left">
<div class="background-wrap-left-box1">
<div class="box-header">
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">立法背景</div>
<div class="header-btn-box">
......@@ -24,6 +24,38 @@
</div>
</div>
</div>
<div class="box1-main">
<div class="box1-main-center">
<div class="box1-main-item" v-for="(item, index) in backgroundList" :key="item.id">
<div class="id">{{ (currentPage - 1) * 10 + index + 1 }}</div>
<div class="title">{{ item.backgroundTitle }}</div>
<div class="share">
</div>
</div>
</div>
<div class="box1-main-footer">
<div class="info">
{{ `共 ${total} 项` }}
</div>
<div class="page-box">
<el-pagination background layout="prev, pager, next" :total="total" v-model:current-page="currentPage"
@current-change="handleGetBillBackground" />
</div>
</div>
</div> -->
<AnalysisBox title="立法背景" :showAllBtn="false">
<template #headerBtn>
<div class="header-btn-box">
<div class="btn" @click="handleClickBox1Btn(1)">
<el-button type="primary" plain v-if="box1BtnActive === 1">涉华背景</el-button>
<el-button type="info" plain v-else>涉华背景</el-button>
</div>
<div class="btn" @click="handleClickBox1Btn(2)">
<el-button type="primary" plain v-if="box1BtnActive === 2">全部背景</el-button>
<el-button type="info" plain v-else>全部背景</el-button>
</div>
</div>
</template>
<div class="box1-main">
<div class="box1-main-center">
<div class="box1-main-item" v-for="(item, index) in backgroundList" :key="item.id">
......@@ -44,9 +76,10 @@
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="background-wrap-left-box2">
<div class="box-header">
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">相关事件</div>
<div class="header-right">
......@@ -73,16 +106,29 @@
</div>
<div class="right">{{ item.sjsj }}</div>
</div>
</div> -->
<AnalysisBox title="相关事件" :showAllBtn="false">
<div class="box2-main">
<div class="box2-main-item" v-for="(item, index) in eventList" :key="index" @click="handleClickEvent(item)">
<div class="left">
<img :src="item.imageUrl || defaultNew" @error="e => (e.target.src = defaultNew)" alt="" />
</div>
<div class="center">
<CommonPrompt :content="item.sjbt">
<div class="title">{{ item.sjbt }}</div>
</CommonPrompt>
<CommonPrompt :content="item.sjnr">
<div class="content">{{ item.sjnr }}</div>
</CommonPrompt>
</div>
<!-- <div class="box2-footer">
<div class="btn-more">
<img src="../assets/images/btn-more.png" alt="" />
<div class="right">{{ item.sjsj }}</div>
</div>
</div> -->
</div>
</AnalysisBox>
</div>
</div>
<div class="background-wrap-right">
<div class="box-header">
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">议员相关性分析</div>
<div class="header-btn-box">
......@@ -106,12 +152,6 @@
</div>
<div class="background-wrap-right-main">
<div class="right-box1">
<!-- <div class="right-box1-header">
<div class="icon">
<img src="./assets/icons/right-icon1.png" alt="" />
</div>
<div class="title">提出议员相关性分析</div>
</div> -->
<div class="right-box1-main">
<div class="right-box1-main-top">
<el-icon style="margin-top: 20px; cursor: pointer" size="20"
......@@ -168,14 +208,84 @@
</div>
</div>
</div>
<!-- <div class="right-box2-footer">
<div class="btn-more">
<img src="../assets/images/btn-more.png" alt="" />
</div>
</div> -->
<AnalysisBox title="议员相关性分析" :showAllBtn="false">
<template #headerBtn>
<div class="header-btn-box">
<div class="btn" @click="handleClickBox2Btn(1)">
<el-button type="primary" plain v-if="box2BtnActive === 1">赞成议员</el-button>
<el-button type="info" plain v-else>赞成议员</el-button>
</div>
<div class="btn" @click="handleClickBox2Btn(2)">
<el-button type="primary" plain v-if="box2BtnActive === 2">反对议员</el-button>
<el-button type="info" plain v-else>反对议员</el-button>
</div>
</div>
</template>
<div class="background-wrap-right-main">
<div class="right-box1">
<div class="right-box1-main">
<div class="right-box1-main-top">
<el-icon style="margin-top: 20px; cursor: pointer" size="20"
:color="currentIndex > 0 ? '#5f656c' : '#ccc'" @click="handlePrev">
<CaretLeft />
</el-icon>
<div class="user-list-container">
<div class="user-list-wrapper" :style="{ transform: `translateX(-${currentIndex * 110}px)` }">
<div class="user-box" v-for="(item, index) in personList" :key="index"
@click="handleClickUser(item)">
<div class="img-box">
<img :src="item.image" alt="" />
<div class="icon1">
<img :src="item.icon" alt="" />
</div>
<div class="icon2">
<img :src="item.icon1" alt="" />
</div>
</div>
<CommonPrompt :content="item.name">
<div class="name">{{ item.name }}</div>
</CommonPrompt>
</div>
</div>
</div>
<el-icon style="margin-top: 20px; cursor: pointer" size="20"
:color="currentIndex < personList.length - 4 ? '#5f656c' : '#ccc'" @click="handleNext">
<CaretRight />
</el-icon>
</div>
<div class="right-box1-main-bottom">
<WordCloudMap :data="wordCloudData" :selectedName="selectedIndustryName" shape="circle"
@wordClick="handleWordClick" />
</div>
</div>
</div>
<div class="right-box2">
<div class="right-box2-header">
<div class="title">
<span class="title-active">"{{ selectedIndustryName }}"</span>涉及议员动态 >
</div>
</div>
<div class="right-box2-center">
<div class="user-box" v-for="(item, index) in aboutUserList" :key="index" @click="handleClickUser(item)">
<div class="user-left">
<div class="img-box">
<img :src="item.img" alt="" />
</div>
</div>
<div class="user-right">
<div class="name">{{ item.name }}</div>
<CommonPrompt :content="item.content">
<div class="content">{{ item.content }}</div>
</CommonPrompt>
</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
</template>
......@@ -400,6 +510,53 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.header-btn-box {
display: flex;
.btn {
margin-left: 8px;
}
:deep(.el-button) {
height: 28px;
padding: 2px 8px;
border-radius: 4px;
font-size: 16px;
font-weight: 400;
font-family: Microsoft YaHei;
line-height: 24px;
}
:deep(.el-button--primary.is-plain) {
background-color: #f0f7ff;
border-color: rgb(5, 95, 194);
color: rgb(5, 95, 194);
border-width: 1px;
&:hover,
&:focus {
background-color: #f0f7ff;
border-color: rgb(5, 95, 194);
color: rgb(5, 95, 194);
}
}
:deep(.el-button--info.is-plain) {
background-color: #fff;
border-color: rgb(230, 231, 232);
color: rgb(59, 65, 75);
&:hover,
&:focus {
background-color: #fff;
border-color: rgb(230, 231, 232);
color: rgb(59, 65, 75);
}
}
}
.background-wrap {
width: 100%;
height: 100%;
......@@ -507,12 +664,9 @@ onMounted(() => {
.background-wrap-left-box1 {
width: 1064px;
height: 415px;
background: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.box1-main {
.box1-main-center {
margin: 0 22px;
margin: 0 auto;
width: 1016px;
height: 280px;
display: flex;
......@@ -594,13 +748,9 @@ onMounted(() => {
margin-top: 15px;
width: 1064px;
height: 415px;
background: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.box2-main {
// margin-top: 9px;
width: 1010px;
margin-left: 23px;
margin: 0 auto;
height: 349px;
overflow-y: auto;
......@@ -611,6 +761,7 @@ onMounted(() => {
box-sizing: border-box;
padding: 6px 8px;
display: flex;
&:hover {
background: rgba(225, 225, 225, 0.3);
}
......@@ -699,16 +850,14 @@ onMounted(() => {
margin-top: 16px;
width: 520px;
height: 845px;
background: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.background-wrap-right-main {
.right-box1 {
height: 365px;
overflow: hidden;
.right-box1-header {
height: 22px;
margin-left: 22px;
margin: 0 auto;
display: flex;
.icon {
......@@ -835,19 +984,6 @@ onMounted(() => {
border-radius: 5px;
display: flex;
justify-content: space-between;
// .tag-box {
// flex: 1;
// .tag {
// height: 55px;
// line-height: 55px;
// color: rgba(95, 101, 108, 1);
// font-family: Microsoft YaHei;
// font-size: 14px;
// font-weight: 400;
// letter-spacing: 0px;
// text-align: center;
// }
// }
}
}
......
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论