提交 36503d51 authored 作者: yanpeng's avatar yanpeng

merge1

......@@ -14,6 +14,8 @@
"@microsoft/fetch-event-source": "^2.0.1",
"@traptitech/markdown-it-katex": "^3.6.0",
"axios": "^1.12.2",
"d3": "^7.9.0",
"d3-cloud": "^1.2.7",
"echarts": "^5.4.3",
"echarts-liquidfill": "^3.1.0",
"echarts-wordcloud": "^2.1.0",
......@@ -2364,6 +2366,21 @@
"node": ">=12"
}
},
"node_modules/d3-cloud": {
"version": "1.2.7",
"resolved": "https://registry.npmmirror.com/d3-cloud/-/d3-cloud-1.2.7.tgz",
"integrity": "sha512-8TrgcgwRIpoZYQp7s3fGB7tATWfhckRb8KcVd1bOgqkNdkJRDGWfdSf4HkHHzZxSczwQJdSxvfPudwir5IAJ3w==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-dispatch": "^1.0.3"
}
},
"node_modules/d3-cloud/node_modules/d3-dispatch": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz",
"integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==",
"license": "BSD-3-Clause"
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
......
......@@ -23,6 +23,8 @@
"@microsoft/fetch-event-source": "^2.0.1",
"@traptitech/markdown-it-katex": "^3.6.0",
"axios": "^1.12.2",
"d3": "^7.9.0",
"d3-cloud": "^1.2.7",
"echarts": "^5.4.3",
"echarts-liquidfill": "^3.1.0",
"echarts-wordcloud": "^2.1.0",
......
......@@ -7,11 +7,11 @@
<div class="brand-icon">
<img src="@/assets/icons/header-logo.png" alt="" />
</div>
<div class="brand-text">
<div class="brand-text" @click="handleToHome">
<div class="text-ch">某方向风险监测预警系统</div>
<div class="text-en">
<!-- <div class="text-en">
National Science and Technology Security Risk Monitoring and Early Warning System
</div>
</div> -->
</div>
</div>
<!-- <div class="nav-menu">
......@@ -101,6 +101,12 @@ import AiBox from "./components/AiBox.vue";
const router = useRouter();
const handleToHome = () => {
router.push({
path: '/overview'
})
}
const isShowAiBox = ref(false);
const closeAiBox = () => {
......@@ -193,6 +199,7 @@ body {
}
.brand-text {
cursor: pointer;
.text-ch {
height: 37px;
color: rgba(10, 18, 30, 1);
......@@ -298,7 +305,7 @@ body {
.ai-btn {
position: absolute;
top: 50%;
bottom: 20%;
right: 46px;
cursor: pointer;
......@@ -313,6 +320,7 @@ body {
}
.text {
margin-top: -15px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
......@@ -326,7 +334,7 @@ body {
.ai-dialog {
position: absolute;
right: 100px;
top: 50px;
top: 100px;
z-index: 9999;
}
}
......
......@@ -269,7 +269,7 @@ onUnmounted(() => {
<style lang="scss" scoped>
.ai-wrapper {
width: 548px;
height: 1048px;
height: 800px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
......@@ -326,7 +326,7 @@ onUnmounted(() => {
}
.main {
height: 830px;
height: 580px;
// background: rgba(225, 225, 225, 0.5);
width: 520px;
margin: 10px auto;
......
......@@ -10,6 +10,11 @@
<div v-if="block" class="blue-title-block"></div>
<el-image v-else :src="titleIcon" class="header-icon" fit="contain" />
<div :class="headerTitleClasses">{{ title }}</div>
<div v-if="props.headerNum > 0" class="num-box">
<div class="num">
{{ headerNum }}
</div>
</div>
</slot>
</div>
<div class="header-right">
......@@ -61,6 +66,10 @@ const props = defineProps({
block: {
type: Boolean,
default: false
},
headerNum: {
type: Number,
default: 0
}
});
......@@ -99,6 +108,26 @@ const headerTitleClasses = computed(() => [
display: flex;
align-items: center;
padding-left: 14px;
.num-box {
height: 48px;
width: 35px;
background: rgba(206, 79, 81, 1);
display: flex;
justify-content: flex-start;
align-items: center;
.num {
width: 24px;
height: 20px;
line-height: 20px;
text-align: center;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 12px;
// border: 1px solid rgba(255, 255, 255, 1);
border-radius: 100px;
background: rgba(255, 255, 255, 0.3);
}
}
}
.header-icon {
......@@ -131,7 +160,7 @@ const headerTitleClasses = computed(() => [
}
.header-title-danger {
background: red;
background: rgba(206, 79, 81, 1);
color: white;
}
......
<template>
<div class="policy-list">
<div v-for="item in props.policyList" :key="item.id" class="policy-item">
<el-image :src="$withFallbackImage(item.imageUrl, item.content) " class="item-cover" fit="cover"/>
<div class="item-details">
<h3 class="item-title">{{ item.name }}</h3>
<div class="item-content"> {{ item.times }} · {{ item.content }} <el-icon><Link /></el-icon></div>
<div class="item-tags">
<el-tag v-for="tag in item.tags" :key="tag" class="custom-tag">{{ tag }}</el-tag>
<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
v-if="item.relatedBill"
class="related-bill-box"
:class="`status-bg-${item.status}`"
>
<span>{{ item.relatedBill.text }}</span>
<div class="status-badge" :class="`status-color-${item.status}`">
<span class="badge-dot"></span>
{{ getStatusInfo(item.status).text }}
</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 v-else class="related-bill-box status-bg-unimplemented">
<span>不存在相关提案。</span>
<div class="status-badge status-color-unimplemented">
<span class="badge-dot"></span>
{{ item.status }}
<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>
......@@ -33,35 +48,53 @@
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Link } from '@element-plus/icons-vue'
const props = defineProps({
policyList: {
type: Array,
default: () => []
}
})
// --- Status Styling Helper ---
const getStatusInfo = (status) => {
switch (status) {
case 'implemented':
return { text: '已实施', color: '#e66657', bgColor: '#fdeeed' };
case 'partial':
return { text: '部分实施', color: '#d38f24', bgColor: '#fcf3e4' };
case 'unimplemented':
return { text: '未实施', color: '#409eff', bgColor: '#ecf5ff' };
default:
return { text: '未知', color: '#909399', bgColor: '#f4f4f5' };
}
};
<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 Styles --- */
.policy-list {
display: flex;
flex-direction: column;
......@@ -70,139 +103,143 @@ const getStatusInfo = (status) => {
.policy-item {
display: flex;
padding: 20px 0;
border-bottom: 1px solid #e4e7ed;
border-bottom: 1px solid #ebeef5;
gap: 16px;
transition: background-color 0.2s;
}
.policy-item:last-child {
border-bottom: none;
}
.item-cover {
width: 80px;
height: 80px;
margin-right: 20px;
/* 左侧封面 */
.item-left {
flex-shrink: 0;
border-radius: 4px;
border: 1px solid #ebeef5;
}
.item-details {
flex-grow: 1;
.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: 600;
color: #303133;
margin: 0 0 8px;
cursor: pointer;
font-weight: 700;
color: #1a1a1a;
line-height: 1.4;
cursor: pointer;
}
.item-title:hover {
color: #409eff;
color: #409EFF;
}
.item-content {
color: #909399;
font-size: 14px;
/* 2. 元数据 */
.item-meta {
display: flex;
align-items: center;
font-size: 13px;
color: #606266;
margin-bottom: 8px;
}
.item-tags {
margin-bottom: 12px;
}
.custom-tag {
margin-right: 8px;
background-color: #f0f2f5;
color: #606266;
border-color: #e4e7ed;
font-size: 12px;
.meta-divider {
margin: 0 8px;
font-weight: bold;
}
.related-bill-box {
.meta-source {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
line-height: 1.4;
gap: 4px;
cursor: pointer;
}
.meta-source:hover {
color: #409EFF;
}
.status-badge {
.link-icon {
font-size: 12px;
}
/* 3. 标签 */
.item-tags {
display: flex;
align-items: center;
font-size: 13px;
padding: 4px 8px;
border-radius: 4px;
white-space: nowrap;
font-weight: 500;
gap: 8px;
margin-bottom: 10px;
}
.badge-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background-color: currentColor;
margin-right: 6px;
.tag-pill {
background-color: #f2f3f5;
color: #5e6d82;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
}
/* Dynamic status colors */
.status-bg-implemented {
background-color: #fdeeed;
/* 4. 底部状态链接 */
.item-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.status-color-implemented {
color: #e66657;
.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-bg-partial {
background-color: #fcf3e4;
.status-link:hover {
background-color: #d9ecff;
}
.status-color-partial {
color: #d38f24;
.status-type {
font-weight: bold;
margin-right: 4px;
}
.status-bg-unimplemented {
background-color: #ecf5ff;
.status-year {
margin-right: 4px;
}
.status-color-unimplemented {
color: #409eff;
.status-name {
margin-right: 4px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.policy-item {
flex-direction: column;
gap: 12px;
padding: 16px 0;
}
.item-cover {
width: 100%;
height: 120px;
margin-right: 0;
margin-bottom: 12px;
}
.item-title {
font-size: 15px;
}
.related-bill-box {
flex-direction: column;
align-items: flex-start;
gap: 8px;
padding: 12px;
}
.status-badge {
align-self: flex-end;
}
.arrow-icon {
margin-left: 4px;
font-size: 12px;
}
</style>
</style>
\ No newline at end of file
......@@ -82,7 +82,7 @@ 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,
......@@ -120,7 +120,8 @@ const getPolicies = async () => {
researchTypeIds: activeTechField.value,
statusList: activeStatus.value,
})
policies.value = data
// policies.value = data
policies.value = mockPolicyList
}
......
:root {
--color-main-active: rgba(10, 87, 166, 1);
--color-main-active: rgba(5, 95, 194, 1);
--color-main-primay: rgba(59, 65, 75, 1);
--color-bg-hover: #e7f3ff;
/* 普通按钮颜色 */
--btn-plain-border-color: rgba(230, 231, 232, 1);
--btn-plain-bg-color: rgba(255, 255, 255, 1);
......
// 平滑滚动到指定元素
const scrollToCenter = (elementId) => {
const element = document.getElementById(elementId);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
}
export default scrollToCenter
\ No newline at end of file
// 平滑滚动到指定元素
const scrollToTop = (elementId) => {
const element = document.getElementById(elementId);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
export default scrollToTop
\ No newline at end of file
......@@ -2,13 +2,14 @@
<div :class="['clickable-card', { disabled: disabled }]" @click="handleClick">
<span class="card-text">{{ text }}</span>
<!-- <span class="arrow-icon"></span> -->
<el-icon><ArrowRight /></el-icon>
<div class="icon">
<el-icon><ArrowRightBold /></el-icon>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
import { ArrowRight } from "@element-plus/icons-vue";
import { ArrowRight, ArrowRightBold } from "@element-plus/icons-vue";
import { useRouter } from "vue-router";
const router = useRouter();
......@@ -51,7 +52,7 @@ const handleClick = () => {
};
</script>
<style scoped>
<style lang="scss" scoped>
.clickable-card {
width: 160px;
height: 48px;
......@@ -69,6 +70,12 @@ const handleClick = () => {
font-size: 20px;
font-weight: 500;
box-sizing: border-box;
position: relative;
.icon{
position: absolute;
top: 14px;
right: 12px;
}
}
.clickable-card:hover {
......
......@@ -19,7 +19,6 @@
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
listData: {
......
......@@ -2,12 +2,14 @@
<div :class="['clickable-card', { disabled: disabled }]" @click="handleClick">
<span class="card-text">{{ text }}</span>
<!-- <span class="arrow-icon"></span> -->
<el-icon><ArrowRight /></el-icon>
<div class="icon">
<el-icon><ArrowRightBold /></el-icon>
</div>
</div>
</template>
<script setup>
import { ArrowRight } from "@element-plus/icons-vue";
import { ArrowRight, ArrowRightBold } from "@element-plus/icons-vue";
import { useRouter } from "vue-router";
const router = useRouter();
......@@ -50,7 +52,7 @@ const handleClick = () => {
};
</script>
<style scoped>
<style lang="scss" scoped>
.clickable-card {
width: 160px;
height: 48px;
......@@ -68,6 +70,12 @@ const handleClick = () => {
font-size: 20px;
font-weight: 500;
box-sizing: border-box;
position: relative;
.icon{
position: absolute;
top: 14px;
right: 12px;
}
}
.clickable-card:hover {
......
......@@ -19,7 +19,6 @@
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
listData: {
......
......@@ -41,8 +41,8 @@ defineProps({
}
.title-text {
font-size: 20px;
font-weight: bold;
font-size: 32px;
font-weight: 700;
margin-left: 20px;
white-space: nowrap;
}
......
......@@ -127,6 +127,8 @@
<script setup>
import { ref, onMounted, watch } from "vue";
import router from '@/router'
import { useRoute } from "vue-router";
const route = useRoute();
const releaseTime = ref("近一年发布");
......@@ -350,7 +352,14 @@ watch(
);
const handleToSingleCase = (item) => {
router.push('/marketSingleCaseLayout/overview')
// router.push('/marketSingleCaseLayout/overview')
const curRoute = router.resolve({
path: "/marketSingleCaseLayout/overview",
query: {
id: route.query.id
}
});
window.open(curRoute.href, "_blank");
}
onMounted(() => {
......
<template>
<div class="wrap">
<div class="header">
<div class="header-top">
<div class="left">
<div class="icon">
<img src="./assets/images/img1.png" alt="" />
</div>
<div class="info">
<div class="title">{{ "337调查" }}</div>
<div class="content">
{{ '依据《1974年贸易法》第301条针对"不合理或不公正贸易做法"' }}
</div>
</div>
</div>
<div class="right">
<div class="icon">
<img src="./assets/images/button-icon.png" alt="" />
</div>
<div class="text">{{ "查看官网" }}</div>
</div>
</div>
<div class="header-footer">
<div
class="btn-item"
:class="{ btnItemActive: activeBtnName === item.name }"
v-for="(item, index) in btnList"
:key="index"
@click="handleClickBtn(item)"
>
<div class="icon">
<img
:src="item.acitveIcon"
alt=""
v-if="activeBtnName === item.name"
/>
<img :src="item.icon" alt="" v-else />
</div>
<div
class="text"
:class="{ textActive: activeBtnName === item.name }"
>
{{ item.name }}
</div>
</div>
</div>
</div>
<div class="main">
<router-view />
</div>
</div>
<div class="wrap">
<div class="header">
<div class="header-top">
<div class="left">
<div class="icon">
<img :src="curSurvey.image" alt="" />
</div>
<div class="info">
<div class="title">{{ curSurvey.title }}</div>
<div class="content">
{{ curSurvey.desc }}
</div>
</div>
</div>
<div class="right">
<div class="icon">
<img src="./assets/images/button-icon.png" alt="" />
</div>
<div class="text">{{ "查看官网" }}</div>
</div>
</div>
<div class="header-footer">
<div
class="btn-item"
:class="{ btnItemActive: activeBtnName === item.name }"
v-for="(item, index) in btnList"
:key="index"
@click="handleClickBtn(item)"
>
<div class="icon">
<img :src="item.acitveIcon" alt="" v-if="activeBtnName === item.name" />
<img :src="item.icon" alt="" v-else />
</div>
<div class="text" :class="{ textActive: activeBtnName === item.name }">
{{ item.name }}
</div>
</div>
</div>
</div>
<div class="main">
<router-view />
</div>
</div>
</template>
<script setup>
......@@ -58,154 +51,189 @@ import icon1 from "./assets/images/icon1.png";
import icon1Active from "./assets/images/icon1_active.png";
import icon2 from "./assets/images/icon2.png";
import icon2Active from "./assets/images/icon2_active.png";
import Img337 from "./assets/images/337.png";
import Img232 from "./assets/images/232.png";
import { useRoute } from "vue-router";
const route = useRoute();
const btnList = ref([
{
name: "调查概况",
icon: icon1,
acitveIcon: icon1Active,
path: "/marketAccessLayout/overview",
},
{
name: "调查案件",
icon: icon2,
acitveIcon: icon2Active,
path: "/marketAccessLayout/case",
},
{
name: "调查概况",
icon: icon1,
acitveIcon: icon1Active,
path: "/marketAccessLayout/overview"
},
{
name: "调查案件",
icon: icon2,
acitveIcon: icon2Active,
path: "/marketAccessLayout/case"
}
]);
const surveyList = ref([
{
title: "337调查",
desc: '依据《1974年贸易法》第301条针对"不合理或不公正贸易做法"',
image: Img337
},
{
title: "232调查",
desc: "依据《1962年贸易扩展法》第232条款,授权美国商务部对“特定进口产品是否威胁或损害美国国家安全”而开展的全面调查。",
image: Img232
}
]);
const curSurvey = ref({
title: "337调查",
desc: '依据《1974年贸易法》第301条针对"不合理或不公正贸易做法"',
image: Img337
});
const activeBtnName = ref("调查概况");
const handleClickBtn = (item) => {
activeBtnName.value = item.name;
router.push(item.path);
const handleClickBtn = item => {
activeBtnName.value = item.name;
router.push({
path: item.path,
query: {
id: route.query.id
}
});
};
onMounted(() => {
if (route.query.id === "232") {
curSurvey.value = surveyList.value[1];
}
});
</script>
<style lang="scss" scoped>
.wrap {
width: 1920px;
height: 1016px;
.header {
width: 1920px;
height: 148px;
box-sizing: border-box;
border-bottom: 1px solid rgba(230, 231, 232, 1);
background: rgba(255, 255, 255, 1);
.header-top {
display: flex;
height: 100px;
justify-content: space-between;
.left {
margin-left: 160px;
margin-top: 24px;
display: flex;
width: 1920px;
height: 1016px;
.header {
width: 1920px;
height: 148px;
box-sizing: border-box;
border-bottom: 1px solid rgba(230, 231, 232, 1);
background: rgba(255, 255, 255, 1);
.header-top {
display: flex;
height: 100px;
justify-content: space-between;
.left {
margin-left: 160px;
margin-top: 24px;
display: flex;
.icon {
width: 54px;
height: 54px;
img {
width: 100%;
height: 100%;
}
}
.info {
margin-left: 14px;
margin-top: -1px;
.title {
height: 26px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.content {
margin-top: 1px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.right {
margin-top: 24px;
margin-right: 160px;
width: 120px;
height: 36px;
border-radius: 6px;
background: rgba(22, 119, 255, 1);
display: flex;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
margin-left: 16px;
margin-top: 10px;
position: relative;
z-index: 99;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
margin-top: 7px;
height: 22px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
}
.header-footer {
height: 48px;
margin-left: 160px;
display: flex;
.btn-item {
display: flex;
width: 92px;
margin-right: 30px;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
margin-top: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 2px;
margin-top: 12px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 24px;
}
.textActive {
color: rgba(10, 87, 166, 1);
font-weight: 700;
}
}
.btnItemActive {
border-bottom: 3px solid rgba(10, 87, 166, 1);
}
}
}
.main {
width: 1920px;
height: 868px;
background: #f7f8f9;
}
.icon {
width: 54px;
height: 54px;
img {
width: 100%;
height: 100%;
}
}
.info {
margin-left: 14px;
margin-top: -1px;
.title {
height: 26px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.content {
margin-top: 1px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.right {
margin-top: 24px;
margin-right: 160px;
width: 120px;
height: 36px;
border-radius: 6px;
background: var(--color-main-active);
display: flex;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
margin-left: 16px;
margin-top: 10px;
position: relative;
z-index: 99;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
margin-top: 7px;
height: 22px;
color: rgba(255, 255, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
}
.header-footer {
height: 48px;
margin-left: 160px;
display: flex;
.btn-item {
display: flex;
width: 92px;
margin-right: 30px;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
margin-top: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 2px;
margin-top: 12px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 24px;
}
.textActive {
color: rgba(10, 87, 166, 1);
font-weight: 700;
}
}
.btnItemActive {
border-bottom: 3px solid rgba(10, 87, 166, 1);
}
}
}
.main {
width: 1920px;
height: 868px;
background: #f7f8f9;
}
}
</style>
\ No newline at end of file
import * as echarts from "echarts";
const getBarChart = (nameList, valueList) => {
const option = {
tooltip: {},
grid: {
top: '3%',
right: '3%',
bottom: '1%',
left: '1%',
containLabel: true
},
xAxis: {
type: 'value',
splitLine: {
show: false
},
show: false
},
yAxis: {
type: 'category',
data: nameList,
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: false
},
axisLabel: {
show: true
}
},
series: [{
type: 'bar',
data: valueList,
label: {
show: true,
position: [610, 0],
formatter: function (params) {
return params.value + ' 次'
}
},
barWidth: 8,
itemStyle: {
color: function (params) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0,
[{
offset: 0,
color: 'rgba(255,255,255,0)'
},
{
offset: 1,
color: '#1778ff'
}
]);
},
barBorderRadius: 4,
}
}]
}
return option
}
export default getBarChart
\ No newline at end of file
import * as echarts from 'echarts'
import chinaJson from '../../../assets/json/China.json'
const getMapChart = (mapData) => {
echarts.registerMap('china', chinaJson);
// 定义颜色范围
const colorRanges = [
{ range: [0, 4], color: '#8ECFC9', label: '0-4' },
{ range: [5, 9], color: '#FFBE7A', label: '5-9' },
{ range: [10, 19], color: '#FA7F6F', label: '10-19' },
{ range: [20, 30], color: '#AA6FC9', label: '20-30' }
];
const getColorByValue = (value, colorRanges) => {
const range = colorRanges.find(range =>
value >= range.range[0] && value <= range.range[1]
);
return range ? range.color : '#CCCCCC';
}
const option = {
title: {
// text: '全国数据分布图',
// subtext: '数据范围颜色标识',
left: 'center',
textStyle: {
fontSize: 18,
color: '#333'
}
},
tooltip: {
trigger: 'item',
formatter: (params) => {
if (params.componentType === 'series') {
if (params.seriesType === 'scatter') {
return `${params.data.name}<br/>数值: ${params.data.value[2]}`;
} else {
return `${params.name}<br/>数值: ${params.value[2] || 0}`;
}
}
return params.name;
}
},
visualMap: {
type: 'piecewise',
pieces: colorRanges.map(range => ({
min: range.range[0],
max: range.range[1],
label: range.label,
color: range.color
})),
left: 'right',
top: 'bottom',
textStyle: {
color: '#000',
fontSize: 12
},
itemWidth: 20,
itemHeight: 14
},
geo: {
map: 'china',
roam: true,
zoom: 1.2,
label: {
emphasis: {
show: true,
color: '#fff'
}
},
itemStyle: {
areaColor: '#F9FAFC',
borderColor: '#E7F1FF',
borderWidth: 1
},
emphasis: {
itemStyle: {
areaColor: '#ffd700'
},
label: {
show: true,
color: '#fff',
fontSize: 12
}
}
},
series: [
// 地图系列 - 用于省份高亮
{
name: '数据分布',
type: 'map',
map: 'china',
geoIndex: 0,
data: mapData.map(item => ({
name: item.name,
value: item.value,
itemStyle: {
// color: getColorByValue(item.value, colorRanges),
areaColor: '#E7F1FF'
}
})),
emphasis: {
itemStyle: {
areaColor: '#ffd700',
borderColor: '#333',
borderWidth: 2
},
label: {
show: true,
fontSize: 14,
fontWeight: 'bold',
color: '#333'
}
}
},
// 散点系列 - 用于显示圆点数字标记
{
name: '数据标记',
type: 'scatter',
coordinateSystem: 'geo',
symbol: 'circle',
symbolSize: (val) => {
// 根据数值大小调整圆点大小
const size = Math.max(20, val[2] * 2);
return Math.min(size, 30);
},
// symbolSize: 30,
label: {
show: true,
formatter: (params) => {
return params.data.value[2];
},
position: 'inside',
color: '#fff',
fontSize: 12,
fontWeight: 'bold'
},
itemStyle: {
color: (params) => {
return getColorByValue(params.data.value, colorRanges);
},
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
},
data: mapData.map(item => ({
name: item.name,
value: [item.coord[0], item.coord[1], item.value],
itemStyle: {
color: getColorByValue(item.value, colorRanges)
}
})),
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold'
},
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(0, 0, 0, 0.8)'
}
}
}
]
};
return option
}
export default getMapChart
\ No newline at end of file
import * as echarts from 'echarts'
const getMultiLineChart = (dataX, dataY1, dataY2) => {
return {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
grid: {
top: '8%',
right: '5%',
bottom: '5%',
left: '5%',
containLabel: true
},
legend: {
data: ['提出法案', '通过法案'],
show: false
},
color: ['#1459bb', '#fa8c16'],
xAxis: [
{
type: 'category',
boundaryGap: false,
data: dataX
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '提出法案',
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(22, 119, 255, 0.5)' // 起始颜色
}, {
offset: 1,
color: 'rgba(22, 119, 255, 0)' // 结束颜色
}])
},
emphasis: {
focus: 'series'
},
data: dataY1
},
// {
// name: '通过法案',
// type: 'line',
// areaStyle: {
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
// offset: 0,
// color: 'rgba(255, 172, 77, 1)' // 起始颜色
// }, {
// offset: 1,
// color: 'rgba(255, 172, 77, 0)' // 结束颜色
// }])
// },
// emphasis: {
// focus: 'series'
// },
// data: dataY2
// }
]
}
}
export default getMultiLineChart
\ No newline at end of file
const getPieChart = (data,colorList) => {
let option = {
color: colorList,
series: [
{
type: 'pie',
radius: [80, 100],
height: '100%',
left: 'center',
width: '100%',
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
label: {
alignTo: 'edge',
formatter: '{name|{b}} {time|{c} 项 {d}%}\n',
minMargin: 5,
edgeDistance: 10,
lineHeight: 15,
rich: {
time: {
fontSize: 12,
color: '#999'
}
}
},
labelLine: {
length: 15,
length2: 0,
maxSurfaceAngle: 80
},
labelLayout: function (params) {
const isLeft = params.labelRect.x < 556 / 2;
const points = params.labelLinePoints;
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
data: data
}]
}
return option
}
export default getPieChart;
\ No newline at end of file
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论