1409 lines
42 KiB
Vue
1409 lines
42 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="index-module__NV_5cW__container">
|
|||
|
|
<!-- 头部区域:标题、副标题、武汉核心数据统计 -->
|
|||
|
|
<div class="index-module__NV_5cW__header">
|
|||
|
|
<div class="index-module__NV_5cW__floatingElements">
|
|||
|
|
<div class="index-module__NV_5cW__floatingElement"></div>
|
|||
|
|
<div class="index-module__NV_5cW__floatingElement"></div>
|
|||
|
|
<div class="index-module__NV_5cW__floatingElement"></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="index-module__NV_5cW__titleSection">
|
|||
|
|
|
|||
|
|
<div class="text-center max-w-4xl mx-auto">
|
|||
|
|
<div class="flex items-center justify-center mb-5">
|
|||
|
|
<div class="title-arrow-left"></div>
|
|||
|
|
|
|||
|
|
<h2 class="text-[32px] md:text-[40px] font-bold text-[#1a1a1a] px-5 tracking-[0.05em] leading-tight">
|
|||
|
|
开源 · 武汉
|
|||
|
|
</h2>
|
|||
|
|
|
|||
|
|
<div class="title-arrow-right"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p class="text-[#888888] text-base md:text-lg leading-relaxed max-w-3xl mx-auto">
|
|||
|
|
汇聚武汉开源力量,共建城市开源生态
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<StatsBar />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 内容区域:ECharts图表模块 + 项目列表 -->
|
|||
|
|
<div class="index-module__NV_5cW__content">
|
|||
|
|
<!-- ECharts图表区域 -->
|
|||
|
|
<div class="index-module__NV_5cW__chartsSection">
|
|||
|
|
<div class="index-module__NV_5cW__sectionHeader">
|
|||
|
|
<h2 class="index-module__NV_5cW__sectionTitle">
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
|||
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|||
|
|
class="lucide lucide-chart-pie" aria-hidden="true">
|
|||
|
|
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|||
|
|
<circle cx="12" cy="10" r="3"></circle>
|
|||
|
|
</svg>
|
|||
|
|
<span>武汉开源生态数据可视化</span>
|
|||
|
|
</h2>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 图表网格布局:新增多个模块 -->
|
|||
|
|
<div class="index-module__NV_5cW__chartsGrid">
|
|||
|
|
<!-- 1. 武汉优秀开源项目(列表展示)-->
|
|||
|
|
<div class="index-module__NV_5cW__chartCard list-card">
|
|||
|
|
<h3 class="chartCard__title">武汉优秀开源项目</h3>
|
|||
|
|
<!-- 新增:ref + 鼠标事件 -->
|
|||
|
|
<div class="chartCard__container list-container"
|
|||
|
|
ref="scrollContainer1"
|
|||
|
|
@mouseenter="pauseScroll(1)"
|
|||
|
|
@mouseleave="resumeScroll(1)">
|
|||
|
|
<div class="project-list">
|
|||
|
|
<div class="project-item" v-for="(item, index) in excellentProjects" :key="index">
|
|||
|
|
<span class="project-rank">{{ index + 1 }}</span>
|
|||
|
|
<div class="project-info">
|
|||
|
|
<span class="project-name">{{ item.name }}</span>
|
|||
|
|
<span class="project-desc">{{ item.desc }}</span>
|
|||
|
|
</div>
|
|||
|
|
<span class="project-org">{{ item.org }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 2. 高校开源参与分布(饼图)-->
|
|||
|
|
<div class="index-module__NV_5cW__chartCard">
|
|||
|
|
<h3 class="chartCard__title">高校开源参与分布</h3>
|
|||
|
|
<div class="chartCard__container">
|
|||
|
|
<div class="num-empty" v-if="isEmpty.university"></div>
|
|||
|
|
<div v-else id="universityChart" class="chart"></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 3. 开源社区区域分布(柱状图)-->
|
|||
|
|
<div class="index-module__NV_5cW__chartCard">
|
|||
|
|
<h3 class="chartCard__title">开源社区区域分布</h3>
|
|||
|
|
<div class="chartCard__container">
|
|||
|
|
<div class="num-empty" v-if="isEmpty.community"></div>
|
|||
|
|
<div v-else id="communityChart" class="chart"></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 4. OSS Compass开源项目排行(横向柱状图)-->
|
|||
|
|
<div class="index-module__NV_5cW__chartCard">
|
|||
|
|
<h3 class="chartCard__title">OSS Compass 开源项目排行</h3>
|
|||
|
|
<div class="chartCard__container">
|
|||
|
|
<div class="num-empty" v-if="isEmpty.ossCompass"></div>
|
|||
|
|
<div v-else id="ossCompassChart" class="chart"></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 5. 武汉开源开发者排行榜(横向柱状图)-->
|
|||
|
|
<div class="index-module__NV_5cW__chartCard">
|
|||
|
|
<h3 class="chartCard__title">武汉开源开发者排行榜</h3>
|
|||
|
|
<div class="chartCard__container">
|
|||
|
|
<div class="num-empty" v-if="isEmpty.developer"></div>
|
|||
|
|
<div v-else id="developerChart" class="chart"></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 6. 2025武汉市优秀开源软件项目(列表)-->
|
|||
|
|
<div class="index-module__NV_5cW__chartCard list-card">
|
|||
|
|
<h3 class="chartCard__title">2025武汉市优秀开源软件项目</h3>
|
|||
|
|
<!-- 新增:ref + 鼠标事件 -->
|
|||
|
|
<div class="chartCard__container list-container"
|
|||
|
|
ref="scrollContainer2"
|
|||
|
|
@mouseenter="pauseScroll(2)"
|
|||
|
|
@mouseleave="resumeScroll(2)">
|
|||
|
|
<div class="project-list">
|
|||
|
|
<div class="project-item" v-for="(item, index) in excellentProjects2025" :key="index">
|
|||
|
|
<span class="project-rank">{{ index + 1 }}</span>
|
|||
|
|
<div class="project-info">
|
|||
|
|
<span class="project-name">{{ item.name }}</span>
|
|||
|
|
<span class="project-desc">{{ item.desc }}</span>
|
|||
|
|
</div>
|
|||
|
|
<span class="project-org">{{ item.org }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 7. 武汉市语言与技术竞争趋势(折线图,2020-2024)-->
|
|||
|
|
<div class="index-module__NV_5cW__chartCard chartCard__wide">
|
|||
|
|
<h3 class="chartCard__title">武汉市编程语言占比趋势(2020-2024)</h3>
|
|||
|
|
<div class="chartCard__container">
|
|||
|
|
<div class="num-empty" v-if="isEmpty.languageTech"></div>
|
|||
|
|
<div v-else id="languageTechChart" class="chart"></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 加入武汉开源社区行动区 -->
|
|||
|
|
<div class="index-module__NV_5cW__actionSection">
|
|||
|
|
<div class="index-module__NV_5cW__actionBackground"></div>
|
|||
|
|
<div class="index-module__NV_5cW__actionContent">
|
|||
|
|
<div class="index-module__NV_5cW__actionIcon">
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none"
|
|||
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|||
|
|
class="lucide lucide-sparkles" aria-hidden="true">
|
|||
|
|
<path
|
|||
|
|
d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"></path>
|
|||
|
|
<path d="M20 3v4"></path>
|
|||
|
|
<path d="M22 5h-4"></path>
|
|||
|
|
<path d="M4 17v2"></path>
|
|||
|
|
<path d="M5 18H3"></path>
|
|||
|
|
</svg>
|
|||
|
|
</div>
|
|||
|
|
<h3 class="index-module__NV_5cW__actionTitle">加入武汉开源社区</h3>
|
|||
|
|
<p class="index-module__NV_5cW__actionDescription">
|
|||
|
|
与武汉本地开发者、企业、高校携手共建开源生态,共享开源成果,参与社区活动与项目协作
|
|||
|
|
</p>
|
|||
|
|
<div class="index-module__NV_5cW__actionFeatures">
|
|||
|
|
<div class="index-module__NV_5cW__feature">
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|||
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|||
|
|
class="lucide lucide-users" aria-hidden="true">
|
|||
|
|
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
|
|||
|
|
<path d="M16 3.128a4 4 0 0 1 0 7.744"></path>
|
|||
|
|
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
|
|||
|
|
<circle cx="9" cy="7" r="4"></circle>
|
|||
|
|
</svg>
|
|||
|
|
<span>本地技术交流</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="index-module__NV_5cW__feature">
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|||
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|||
|
|
class="lucide lucide-building2" aria-hidden="true">
|
|||
|
|
<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"></path>
|
|||
|
|
<path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"></path>
|
|||
|
|
<path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"></path>
|
|||
|
|
<path d="M10 6h4"></path>
|
|||
|
|
<path d="M10 10h4"></path>
|
|||
|
|
<path d="M10 14h4"></path>
|
|||
|
|
<path d="M10 18h4"></path>
|
|||
|
|
</svg>
|
|||
|
|
<span>企业项目协作</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="index-module__NV_5cW__feature">
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|||
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|||
|
|
class="lucide lucide-graduation-cap" aria-hidden="true">
|
|||
|
|
<path d="M22 10v6M2 10l10-5 10 5-10 5z"></path>
|
|||
|
|
<path d="M6 12v5c3 3 9 3 12 0v-5"></path>
|
|||
|
|
</svg>
|
|||
|
|
<span>高校产学研对接</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<button type="button"
|
|||
|
|
class="ant-btn css-aa1tt5 ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-lg index-module__NV_5cW__createButton">
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
|
|||
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|||
|
|
class="lucide lucide-user-plus" aria-hidden="true">
|
|||
|
|
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
|
|||
|
|
<circle cx="9" cy="7" r="4"></circle>
|
|||
|
|
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
|
|||
|
|
<path d="M16 3.12a4 4 0 0 1 0 7.76"></path>
|
|||
|
|
</svg>
|
|||
|
|
<span>加入武汉开源社区</span>
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
|
|||
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|||
|
|
class="lucide lucide-arrow-right" aria-hidden="true">
|
|||
|
|
<path d="M5 12h14"></path>
|
|||
|
|
<path d="m12 5 7 7-7 7"></path>
|
|||
|
|
</svg>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script lang="ts" setup>
|
|||
|
|
import { onMounted, ref, nextTick, watch, onUnmounted } from 'vue';
|
|||
|
|
import * as echarts from 'echarts';
|
|||
|
|
import { usePageResize } from '@/utils/hooks/usePageResize';
|
|||
|
|
import StatsBar from './StatsBar.vue'
|
|||
|
|
|
|||
|
|
// 页面尺寸响应式
|
|||
|
|
const { widthType } = usePageResize();
|
|||
|
|
|
|||
|
|
// 1. 武汉优秀开源项目数据
|
|||
|
|
const excellentProjects = ref([
|
|||
|
|
{ name: '调问(DWSurvey)', desc: '问卷表单', org: '极推科技(武汉)有限公司' },
|
|||
|
|
{ name: 'Eova', desc: '低代码开发平台', org: '武汉简码科技有限公司' },
|
|||
|
|
{ name: 'ClickVisual', desc: '轻量级日志查询、分析、报警可视化平台', org: '武汉初心科技有限公司' },
|
|||
|
|
{ name: 'meta2d.js', desc: '2D图元组成的可视化引擎', org: '武汉乐吾乐科技有限责任公司' },
|
|||
|
|
{ name: 'Juno', desc: '分布式微服务管理系统', org: '武汉斗鱼鱼乐网络科技有限公司' },
|
|||
|
|
{ name: 'Opentalent 开源北极星', desc: '开源人才评价系统', org: '湖北大学' },
|
|||
|
|
{ name: 'fountain', desc: '仓颉编程语言实现的JAVA Spring框架的国产替代', org: '江汉大学' },
|
|||
|
|
{ name: 'deepin', desc: '深度操作系统', org: '武汉深之度科技有限公司' },
|
|||
|
|
{ name: 'RustSBI', desc: '安全引导程序', org: '华中科技大学' },
|
|||
|
|
{ name: 'NextDenovo', desc: '高效且精确的长读长纠错与组装工具', org: '武汉希望组生物科技有限公司' }
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// 2. 2025武汉市优秀开源软件项目
|
|||
|
|
const excellentProjects2025 = ref([
|
|||
|
|
{ name: '调问(DWSurvey)', desc: '问卷表单', org: '极推科技(武汉)有限公司' },
|
|||
|
|
{ name: 'Eova', desc: '低代码开发平台', org: '武汉简码科技有限公司' },
|
|||
|
|
{ name: 'ClickVisual', desc: '轻量级日志查询、分析、报警可视化平台', org: '武汉初心科技有限公司' },
|
|||
|
|
{ name: 'meta2d.js', desc: '2D图元组成的可视化引擎', org: '武汉乐吾乐科技有限责任公司' },
|
|||
|
|
{ name: 'Juno', desc: '分布式微服务管理系统', org: '武汉斗鱼鱼乐网络科技有限公司' },
|
|||
|
|
{ name: 'Opentalent 开源北极星', desc: '开源人才评价系统', org: '湖北大学' },
|
|||
|
|
{ name: 'fountain', desc: '仓颉编程语言实现的JAVA Spring框架的国产替代', org: '江汉大学' }
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// 3. 图表数据定义(已更新为新数据)
|
|||
|
|
const chartData = ref({
|
|||
|
|
// 高校开源参与分布(新的占比数据)
|
|||
|
|
university: [
|
|||
|
|
{ value: 28, name: '华中科技大学' },
|
|||
|
|
{ value: 25, name: '武汉大学' },
|
|||
|
|
{ value: 13, name: '江汉大学' },
|
|||
|
|
{ value: 9, name: '湖北大学' },
|
|||
|
|
{ value: 7, name: '武汉理工大学' },
|
|||
|
|
{ value: 6, name: '华中师范大学' },
|
|||
|
|
{ value: 6, name: '武汉科技大学' },
|
|||
|
|
{ value: 5, name: '湖北工业大学' },
|
|||
|
|
{ value: 1, name: '其他' }
|
|||
|
|
],
|
|||
|
|
// 开源社区区域分布(行政区+社区数量)
|
|||
|
|
community: {
|
|||
|
|
xAxis: ['洪山区', '武昌区', '东湖高新区', '江夏区', '东西湖区', '青山区'],
|
|||
|
|
data: [22, 11, 9, 5, 2, 1]
|
|||
|
|
},
|
|||
|
|
// OSS Compass开源项目排行(保留原有数据)
|
|||
|
|
ossCompass: {
|
|||
|
|
yAxis: [
|
|||
|
|
'openharmony/docs',
|
|||
|
|
'mindspore/mindspore',
|
|||
|
|
'openharmony/arkui_ace_engine',
|
|||
|
|
'apache/doris',
|
|||
|
|
'openharmony/interface_sdk-js',
|
|||
|
|
'anolis-challenge/activity-school',
|
|||
|
|
'PaddlePaddle/Paddle',
|
|||
|
|
'StarRocks/starrocks',
|
|||
|
|
'openharmony/xts_acts',
|
|||
|
|
'ant-design/ant-design'
|
|||
|
|
],
|
|||
|
|
data: [91.55, 67.35, 62.07, 48.82, 44.06, 39.77, 39.55, 34.2, 30.39, 30.37],
|
|||
|
|
// 完整分数数据(用于tooltip)
|
|||
|
|
fullData: [
|
|||
|
|
{ openRank: '91.55 | 30.86', quality: '92.06 | 8.905', wxb: '70.69 | -14.8', score: '84.77 | 8.32' },
|
|||
|
|
{ openRank: '67.35 | 27.41', quality: '96.92 | 7.17', wxb: '98.915 | 7.1', score: '87.73 | 13.89' },
|
|||
|
|
{ openRank: '62.07 | 17.98', quality: '71.69 | 2.885', wxb: '76.06 | 10.9', score: '69.94 | 10.59' },
|
|||
|
|
{ openRank: '48.82 | 10.14', quality: '67.26 | 0.573', wxb: '30.5 | 7.3', score: '48.86 | 6.00' },
|
|||
|
|
{ openRank: '44.06 | 11.49', quality: '32.5 | 3.14', wxb: '67.55 | 16.7', score: '48.04 | 10.44' },
|
|||
|
|
{ openRank: '39.77 | 37.64', quality: '31.05 | 0.215', wxb: '24.798 | -18.9', score: '31.87 | 6.32' },
|
|||
|
|
{ openRank: '39.55 | 7.25', quality: '68.55 | -2.937', wxb: '68.26 | -17.3', score: '58.79 | -4.33' },
|
|||
|
|
{ openRank: '34.2 | 2.08', quality: '49.07 | -0.877', wxb: '47.07 | 9.9', score: '43.45 | 3.70' },
|
|||
|
|
{ openRank: '30.39 | 10.97', quality: '29.8 | 5.633', wxb: '27.045 | 4.3', score: '29.08 | 6.97' },
|
|||
|
|
{ openRank: '30.37 | 8.57', quality: '37.1 | 4.295', wxb: '35.097 | 6.8', score: '34.19 | 6.55' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
// 武汉开源开发者排行榜(新的15位开发者总分数据)
|
|||
|
|
developer: {
|
|||
|
|
yAxis: [
|
|||
|
|
'白纯骢', '陆红旭', '蔡冬亮', '张继德', '蒋周奇',
|
|||
|
|
'张天雄', '阮文俊', '李奇', '李成刚', '陈鹏轩',
|
|||
|
|
'陈天宇', '彭友顺', '杨帆', '江彬', '韩凡宇'
|
|||
|
|
],
|
|||
|
|
data: [92, 88, 85, 83, 81, 79, 77, 75, 74, 73, 72, 70, 68, 65, 63]
|
|||
|
|
},
|
|||
|
|
// 武汉市编程语言占比趋势(2020-2024)
|
|||
|
|
languageTech: {
|
|||
|
|
xAxis: ['2020', '2021', '2022', '2023', '2024'],
|
|||
|
|
series: [
|
|||
|
|
{ name: 'JavaScript', data: [15.50, 17.00, 18.50, 19.80, 20.50] },
|
|||
|
|
{ name: 'Python', data: [18.00, 18.50, 17.80, 16.50, 15.20] },
|
|||
|
|
{ name: 'Java', data: [10.00, 11.50, 12.80, 13.50, 14.00] },
|
|||
|
|
{ name: 'TypeScript', data: [16.50, 14.80, 12.50, 10.20, 8.50] },
|
|||
|
|
{ name: 'C#', data: [6.50, 6.80, 7.20, 7.50, 7.80] },
|
|||
|
|
{ name: 'PHP', data: [4.20, 5.00, 5.80, 6.50, 7.00] },
|
|||
|
|
{ name: 'C++', data: [5.50, 5.80, 6.00, 6.30, 6.50] },
|
|||
|
|
{ name: 'Go', data: [6.00, 5.60, 5.30, 4.80, 4.20] },
|
|||
|
|
{ name: 'Rust', data: [5.00, 4.50, 3.80, 3.20, 2.80] },
|
|||
|
|
{ name: 'Kotlin', data: [3.50, 3.20, 3.00, 2.80, 2.50] }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 空数据标识
|
|||
|
|
const isEmpty = ref({
|
|||
|
|
university: false,
|
|||
|
|
community: false,
|
|||
|
|
ossCompass: false,
|
|||
|
|
developer: false,
|
|||
|
|
languageTech: false
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 图表实例存储(用于resize时销毁重绘)
|
|||
|
|
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
|||
|
|
universityChart: null,
|
|||
|
|
communityChart: null,
|
|||
|
|
ossCompassChart: null,
|
|||
|
|
developerChart: null,
|
|||
|
|
languageTechChart: null
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ===================== 新增:列表自动滚动逻辑 =====================
|
|||
|
|
// 1. 定义滚动容器的ref
|
|||
|
|
const scrollContainer1 = ref<HTMLDivElement | null>(null); // 第一个列表容器
|
|||
|
|
const scrollContainer2 = ref<HTMLDivElement | null>(null); // 第二个列表容器
|
|||
|
|
|
|||
|
|
// 2. 存储定时器(键:1/2,值:定时器ID)
|
|||
|
|
const scrollTimers: { [key: number]: NodeJS.Timeout | null } = {
|
|||
|
|
1: null,
|
|||
|
|
2: null
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 3. 滚动配置
|
|||
|
|
const scrollStep = 1; // 每次滚动的步长(像素,越小越流畅)
|
|||
|
|
const scrollInterval = 20; // 滚动间隔(毫秒,越小越流畅)
|
|||
|
|
|
|||
|
|
// 4. 通用自动滚动函数
|
|||
|
|
const autoScroll = (containerKey: number) => {
|
|||
|
|
// 获取对应容器
|
|||
|
|
const container = containerKey === 1 ? scrollContainer1.value : scrollContainer2.value;
|
|||
|
|
if (!container) return;
|
|||
|
|
|
|||
|
|
// 获取列表内容元素(project-list)
|
|||
|
|
const projectList = container.querySelector('.project-list') as HTMLDivElement;
|
|||
|
|
if (!projectList) return;
|
|||
|
|
|
|||
|
|
// 若内容高度 ≤ 容器高度,不执行滚动
|
|||
|
|
if (projectList.scrollHeight <= container.clientHeight) {
|
|||
|
|
// 清除现有定时器
|
|||
|
|
if (scrollTimers[containerKey]) {
|
|||
|
|
clearInterval(scrollTimers[containerKey]!);
|
|||
|
|
scrollTimers[containerKey] = null;
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清除现有定时器(避免重复)
|
|||
|
|
if (scrollTimers[containerKey]) {
|
|||
|
|
clearInterval(scrollTimers[containerKey]!);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 启动定时器滚动
|
|||
|
|
scrollTimers[containerKey] = setInterval(() => {
|
|||
|
|
// 向下滚动步长
|
|||
|
|
container.scrollTop += scrollStep;
|
|||
|
|
|
|||
|
|
// 滚动到底部时,重置为0(循环滚动)
|
|||
|
|
// 加1是为了兼容像素精度问题
|
|||
|
|
if (projectList.scrollHeight - container.scrollTop <= container.clientHeight + 1) {
|
|||
|
|
container.scrollTop = 0; // 直接重置(也可以用平滑滚动:container.scrollTo({ top: 0, behavior: 'smooth' }))
|
|||
|
|
}
|
|||
|
|
}, scrollInterval);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 5. 暂停滚动
|
|||
|
|
const pauseScroll = (containerKey: number) => {
|
|||
|
|
if (scrollTimers[containerKey]) {
|
|||
|
|
clearInterval(scrollTimers[containerKey]!);
|
|||
|
|
scrollTimers[containerKey] = null;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 6. 恢复滚动
|
|||
|
|
const resumeScroll = (containerKey: number) => {
|
|||
|
|
autoScroll(containerKey);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 7. 组件卸载时清除所有定时器
|
|||
|
|
onUnmounted(() => {
|
|||
|
|
Object.keys(scrollTimers).forEach(key => {
|
|||
|
|
const k = Number(key);
|
|||
|
|
if (scrollTimers[k]) {
|
|||
|
|
clearInterval(scrollTimers[k]!);
|
|||
|
|
scrollTimers[k] = null;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
// ===================== 新增结束 =====================
|
|||
|
|
|
|||
|
|
// 初始化高校参与分布饼图
|
|||
|
|
const initUniversityChart = () => {
|
|||
|
|
const el = document.getElementById('universityChart');
|
|||
|
|
if (!el) return;
|
|||
|
|
|
|||
|
|
if (chartInstances.value.universityChart) {
|
|||
|
|
chartInstances.value.universityChart.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const myChart = echarts.init(el);
|
|||
|
|
chartInstances.value.universityChart = myChart;
|
|||
|
|
|
|||
|
|
myChart.setOption({
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'item',
|
|||
|
|
formatter: (params: any) => `${params.name}: ${params.value}%`
|
|||
|
|
},
|
|||
|
|
legend: {
|
|||
|
|
bottom: '0',
|
|||
|
|
textStyle: { fontSize: 10 }, // 缩小图例字体,适配更多高校
|
|||
|
|
itemWidth: 10,
|
|||
|
|
itemHeight: 10,
|
|||
|
|
type: 'scroll' // 开启图例滚动,避免溢出
|
|||
|
|
},
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: '参与度',
|
|||
|
|
type: 'pie',
|
|||
|
|
radius: ['40%', '70%'],
|
|||
|
|
data: chartData.value.university,
|
|||
|
|
label: {
|
|||
|
|
show: true,
|
|||
|
|
formatter: '{b}: {c}%',
|
|||
|
|
fontSize: 9 // 缩小标签字体,适配更多高校
|
|||
|
|
},
|
|||
|
|
color: [
|
|||
|
|
'#3b82f6', '#8b5cf6', '#ec4899', '#10b981', '#f59e0b',
|
|||
|
|
'#6366f1', '#14b8a6', '#f43f5e', '#94a3b8' // 新增颜色,匹配9个分类
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
grid: { left: 0, bottom: 0, top: 0, right: 0 }
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 初始化社区区域分布柱状图
|
|||
|
|
const initCommunityChart = () => {
|
|||
|
|
const el = document.getElementById('communityChart');
|
|||
|
|
if (!el) return;
|
|||
|
|
|
|||
|
|
if (chartInstances.value.communityChart) {
|
|||
|
|
chartInstances.value.communityChart.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const myChart = echarts.init(el);
|
|||
|
|
chartInstances.value.communityChart = myChart;
|
|||
|
|
|
|||
|
|
myChart.setOption({
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'axis',
|
|||
|
|
axisPointer: { type: 'shadow' },
|
|||
|
|
formatter: (params: any) => `${params[0].name}: ${params[0].value}个`
|
|||
|
|
},
|
|||
|
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
|||
|
|
xAxis: {
|
|||
|
|
type: 'category',
|
|||
|
|
data: chartData.value.community.xAxis,
|
|||
|
|
axisLabel: { fontSize: 12 }
|
|||
|
|
},
|
|||
|
|
yAxis: {
|
|||
|
|
type: 'value',
|
|||
|
|
name: '社区数量',
|
|||
|
|
nameTextStyle: { fontSize: 12 },
|
|||
|
|
axisLabel: { fontSize: 12 },
|
|||
|
|
min: 0 // 强制y轴从0开始,更直观
|
|||
|
|
},
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: '开源社区数量',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: chartData.value.community.data,
|
|||
|
|
itemStyle: {
|
|||
|
|
color: (params: any) => {
|
|||
|
|
const colorList = ['#3b82f6', '#8b5cf6', '#ec4899', '#10b981', '#f59e0b', '#6366f1'];
|
|||
|
|
return colorList[params.dataIndex];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 初始化OSS Compass开源项目排行(横向柱状图)
|
|||
|
|
const initOssCompassChart = () => {
|
|||
|
|
const el = document.getElementById('ossCompassChart');
|
|||
|
|
if (!el) return;
|
|||
|
|
|
|||
|
|
if (chartInstances.value.ossCompassChart) {
|
|||
|
|
chartInstances.value.ossCompassChart.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const myChart = echarts.init(el);
|
|||
|
|
chartInstances.value.ossCompassChart = myChart;
|
|||
|
|
|
|||
|
|
myChart.setOption({
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'axis',
|
|||
|
|
axisPointer: { type: 'shadow' },
|
|||
|
|
formatter: (params: any) => {
|
|||
|
|
const index = params[0].dataIndex;
|
|||
|
|
const data = chartData.value.ossCompass.fullData[index];
|
|||
|
|
return `
|
|||
|
|
<div>${params[0].name}</div>
|
|||
|
|
<div>OpenRank: ${data.openRank}</div>
|
|||
|
|
<div>代码内生质量: ${data.quality}</div>
|
|||
|
|
<div>WXB分数: ${data.wxb}</div>
|
|||
|
|
<div>综合分数: ${data.score}</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
grid: { left: '15%', right: '4%', bottom: '3%', top: '3%', containLabel: true },
|
|||
|
|
xAxis: {
|
|||
|
|
type: 'value',
|
|||
|
|
name: 'OpenRank总分',
|
|||
|
|
nameTextStyle: { fontSize: 12 },
|
|||
|
|
axisLabel: { fontSize: 12 }
|
|||
|
|
},
|
|||
|
|
yAxis: {
|
|||
|
|
type: 'category',
|
|||
|
|
data: chartData.value.ossCompass.yAxis,
|
|||
|
|
axisLabel: { fontSize: 10, overflow: 'truncate', width: 120 }
|
|||
|
|
},
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: 'OpenRank总分',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: chartData.value.ossCompass.data,
|
|||
|
|
itemStyle: { color: '#2951E0' },
|
|||
|
|
label: {
|
|||
|
|
show: true,
|
|||
|
|
position: 'right',
|
|||
|
|
formatter: '{c}',
|
|||
|
|
fontSize: 10
|
|||
|
|
},
|
|||
|
|
areaStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|||
|
|
{ offset: 0, color: 'rgba(41, 81, 224, 0.24)' },
|
|||
|
|
{ offset: 1, color: 'rgba(41, 81, 224, 0.04)' }
|
|||
|
|
])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 初始化武汉开源开发者排行榜(横向柱状图)
|
|||
|
|
const initDeveloperChart = () => {
|
|||
|
|
const el = document.getElementById('developerChart');
|
|||
|
|
if (!el) return;
|
|||
|
|
|
|||
|
|
if (chartInstances.value.developerChart) {
|
|||
|
|
chartInstances.value.developerChart.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const myChart = echarts.init(el);
|
|||
|
|
chartInstances.value.developerChart = myChart;
|
|||
|
|
|
|||
|
|
myChart.setOption({
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'axis',
|
|||
|
|
axisPointer: { type: 'shadow' },
|
|||
|
|
formatter: (params: any) => `${params[0].name}: ${params[0].value}贡献值`
|
|||
|
|
},
|
|||
|
|
grid: { left: '10%', right: '4%', bottom: '3%', top: '3%', containLabel: true },
|
|||
|
|
xAxis: {
|
|||
|
|
type: 'value',
|
|||
|
|
name: '贡献值',
|
|||
|
|
nameTextStyle: { fontSize: 12 },
|
|||
|
|
axisLabel: { fontSize: 12 },
|
|||
|
|
min: 0 // 强制y轴从0开始
|
|||
|
|
},
|
|||
|
|
yAxis: {
|
|||
|
|
type: 'category',
|
|||
|
|
data: chartData.value.developer.yAxis,
|
|||
|
|
axisLabel: { fontSize: 11 } // 缩小字体,适配15个开发者
|
|||
|
|
},
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: '贡献值',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: chartData.value.developer.data,
|
|||
|
|
itemStyle: { color: '#8b5cf6' },
|
|||
|
|
label: {
|
|||
|
|
show: true,
|
|||
|
|
position: 'right',
|
|||
|
|
formatter: '{c}',
|
|||
|
|
fontSize: 11 // 缩小标签字体
|
|||
|
|
},
|
|||
|
|
areaStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|||
|
|
{ offset: 0, color: 'rgba(139, 92, 246, 0.24)' },
|
|||
|
|
{ offset: 1, color: 'rgba(139, 92, 246, 0.04)' }
|
|||
|
|
])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 初始化武汉市编程语言占比趋势(折线图)
|
|||
|
|
const initLanguageTechChart = () => {
|
|||
|
|
const el = document.getElementById('languageTechChart');
|
|||
|
|
if (!el) return;
|
|||
|
|
|
|||
|
|
if (chartInstances.value.languageTechChart) {
|
|||
|
|
chartInstances.value.languageTechChart.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const myChart = echarts.init(el);
|
|||
|
|
chartInstances.value.languageTechChart = myChart;
|
|||
|
|
|
|||
|
|
// 定义系列颜色(10个颜色,匹配10种语言)
|
|||
|
|
const colorList = [
|
|||
|
|
'#3b82f6', '#10b981', '#f59e0b', '#ec4899', '#8b5cf6',
|
|||
|
|
'#6366f1', '#14b8a6', '#f43f5e', '#94a3b8', '#475569'
|
|||
|
|
];
|
|||
|
|
const series = chartData.value.languageTech.series.map((item, index) => ({
|
|||
|
|
name: item.name,
|
|||
|
|
type: 'line',
|
|||
|
|
data: item.data,
|
|||
|
|
symbol: 'circle',
|
|||
|
|
symbolSize: 6,
|
|||
|
|
lineStyle: { color: colorList[index], width: 2 },
|
|||
|
|
itemStyle: { color: colorList[index] },
|
|||
|
|
areaStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|||
|
|
{ offset: 0, color: `${colorList[index]}33` },
|
|||
|
|
{ offset: 1, color: `${colorList[index]}08` }
|
|||
|
|
])
|
|||
|
|
},
|
|||
|
|
// 隐藏部分系列的图例,避免拥挤(可选,根据需求调整)
|
|||
|
|
// legendHoverLink: index < 5 // 只显示前5个语言的图例交互
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
myChart.setOption({
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'axis',
|
|||
|
|
formatter: (params: any) => {
|
|||
|
|
let res = `<div>${params[0].axisValue}</div>`;
|
|||
|
|
params.forEach((p: any) => {
|
|||
|
|
res += `<div>${p.seriesName}: ${p.value}%</div>`;
|
|||
|
|
});
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
legend: {
|
|||
|
|
top: 0,
|
|||
|
|
textStyle: { fontSize: 10 },
|
|||
|
|
type: 'scroll' // 开启图例滚动,适配10种语言
|
|||
|
|
},
|
|||
|
|
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
|
|||
|
|
xAxis: {
|
|||
|
|
type: 'category',
|
|||
|
|
data: chartData.value.languageTech.xAxis,
|
|||
|
|
axisLabel: { fontSize: 12 }
|
|||
|
|
},
|
|||
|
|
yAxis: {
|
|||
|
|
type: 'value',
|
|||
|
|
name: '占比(%)',
|
|||
|
|
nameTextStyle: { fontSize: 12 },
|
|||
|
|
axisLabel: {
|
|||
|
|
fontSize: 12,
|
|||
|
|
formatter: '{value}%' // 显示百分比符号
|
|||
|
|
},
|
|||
|
|
min: 0,
|
|||
|
|
max: 25 // 限制y轴最大值,更直观
|
|||
|
|
},
|
|||
|
|
series
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 统一初始化图表
|
|||
|
|
const initCharts = () => {
|
|||
|
|
// 空数据判断
|
|||
|
|
isEmpty.value = {
|
|||
|
|
university: chartData.value.university.length === 0,
|
|||
|
|
community: chartData.value.community.data.every(item => item === 0),
|
|||
|
|
ossCompass: chartData.value.ossCompass.data.every(item => item === 0),
|
|||
|
|
developer: chartData.value.developer.data.every(item => item === 0),
|
|||
|
|
languageTech: chartData.value.languageTech.series.every(s => s.data.every(d => d === 0))
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 初始化各图表
|
|||
|
|
if (!isEmpty.value.university) nextTick(() => initUniversityChart());
|
|||
|
|
if (!isEmpty.value.community) nextTick(() => initCommunityChart());
|
|||
|
|
if (!isEmpty.value.ossCompass) nextTick(() => initOssCompassChart());
|
|||
|
|
if (!isEmpty.value.developer) nextTick(() => initDeveloperChart());
|
|||
|
|
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 页面resize时重绘图表
|
|||
|
|
watch(widthType, () => {
|
|||
|
|
initCharts();
|
|||
|
|
// 重新启动滚动(防止resize后滚动失效)
|
|||
|
|
autoScroll(1);
|
|||
|
|
autoScroll(2);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 挂载时初始化
|
|||
|
|
onMounted(() => {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
initCharts();
|
|||
|
|
// 启动列表自动滚动
|
|||
|
|
autoScroll(1);
|
|||
|
|
autoScroll(2);
|
|||
|
|
}, 100);
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
.index-module__NV_5cW__container {
|
|||
|
|
//background: linear-gradient(to bottom right, #faf5ff, #60a5fa);
|
|||
|
|
min-height: 100vh;
|
|||
|
|
padding: 2rem 1rem;
|
|||
|
|
position: relative;
|
|||
|
|
overflow-x: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__header {
|
|||
|
|
text-align: center;
|
|||
|
|
margin-bottom: 3rem;
|
|||
|
|
padding: 2rem 1rem 1rem;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__floatingElements {
|
|||
|
|
pointer-events: none;
|
|||
|
|
opacity: 0.3;
|
|||
|
|
position: absolute;
|
|||
|
|
inset: 0;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__floatingElement {
|
|||
|
|
background: linear-gradient(135deg, #3b82f60d, #9333ea0d);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
width: 120px;
|
|||
|
|
height: 120px;
|
|||
|
|
animation: 30s linear infinite index-module__NV_5cW__float;
|
|||
|
|
position: absolute;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__floatingElement:first-child {
|
|||
|
|
animation-delay: 0s;
|
|||
|
|
top: 10%;
|
|||
|
|
left: 10%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__floatingElement:nth-child(2) {
|
|||
|
|
animation-delay: -7s;
|
|||
|
|
top: 60%;
|
|||
|
|
right: 10%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__floatingElement:nth-child(3) {
|
|||
|
|
animation-delay: -14s;
|
|||
|
|
bottom: 20%;
|
|||
|
|
left: 50%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes index-module__NV_5cW__float {
|
|||
|
|
0% {
|
|||
|
|
transform: translateY(0) rotate(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
33% {
|
|||
|
|
transform: translateY(-20px) rotate(120deg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
66% {
|
|||
|
|
transform: translateY(10px) rotate(240deg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
to {
|
|||
|
|
transform: translateY(0) rotate(360deg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加宽标题区域:移除max-width限制,调整宽度为100%,增加内边距
|
|||
|
|
.index-module__NV_5cW__titleSection {
|
|||
|
|
color: #333;
|
|||
|
|
z-index: 2;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 1rem;
|
|||
|
|
display: flex;
|
|||
|
|
position: relative;
|
|||
|
|
width: 100%;
|
|||
|
|
max-width: 1200px; // 增加最大宽度,适配大屏
|
|||
|
|
margin: 0 auto; // 居中显示
|
|||
|
|
padding: 0 2rem; // 增加左右内边距
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__titleContainer {
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 1rem;
|
|||
|
|
display: flex;
|
|||
|
|
width: 100%; // 宽度100%
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__title {
|
|||
|
|
background: #333;
|
|||
|
|
-webkit-text-fill-color: transparent;
|
|||
|
|
letter-spacing: -0.02em;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
-webkit-background-clip: text;
|
|||
|
|
background-clip: text;
|
|||
|
|
margin: 0;
|
|||
|
|
font-size: 2.8rem;
|
|||
|
|
font-weight: 700;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__titleBadge {
|
|||
|
|
color: #fff;
|
|||
|
|
letter-spacing: 0.05em;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
background: #60a5fa;
|
|||
|
|
border-radius: 1.5rem;
|
|||
|
|
padding: 0.4rem 1rem;
|
|||
|
|
font-size: 1rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
box-shadow: 0 2px 10px #3b82f64d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__subtitle {
|
|||
|
|
color: #666;
|
|||
|
|
text-align: center;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 0.5rem;
|
|||
|
|
max-width: 800px; // 加宽副标题最大宽度
|
|||
|
|
font-size: 1.1rem;
|
|||
|
|
font-weight: 400;
|
|||
|
|
display: flex;
|
|||
|
|
width: 100%; // 宽度100%
|
|||
|
|
justify-content: center; // 居中
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加宽数据统计栏:移除max-width限制,调整为100%宽度,适配大屏
|
|||
|
|
.index-module__NV_5cW__statsBar {
|
|||
|
|
-webkit-backdrop-filter: blur(10px);
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
background: #ffffff1a;
|
|||
|
|
border: 1px solid #fff3;
|
|||
|
|
border-radius: 1.5rem;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 2rem; // 增加间距,更舒展
|
|||
|
|
width: 100%; // 宽度100%
|
|||
|
|
max-width: 100%; // 移除最大宽度限制
|
|||
|
|
margin-top: 0.75rem;
|
|||
|
|
padding: 1rem 2rem; // 增加内边距
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center; // 居中分布
|
|||
|
|
flex-wrap: wrap; // 移动端自动换行
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__stat {
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 0.25rem;
|
|||
|
|
display: flex;
|
|||
|
|
flex: 1; // 平均分配宽度
|
|||
|
|
min-width: 80px; // 最小宽度,防止挤压
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statNumber {
|
|||
|
|
color: #60a5fa;
|
|||
|
|
font-size: 1.5rem;
|
|||
|
|
font-weight: 700;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statLabel {
|
|||
|
|
color: #333;
|
|||
|
|
font-size: 0.875rem;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statDivider {
|
|||
|
|
background: #fff3;
|
|||
|
|
width: 1px;
|
|||
|
|
height: 2rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__content {
|
|||
|
|
max-width: 1400px; /* 加宽容器,适配更多图表 */
|
|||
|
|
margin: 0 auto;
|
|||
|
|
padding: 0 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 图表区域样式(适配示例的num-empty) */
|
|||
|
|
.index-module__NV_5cW__chartsSection {
|
|||
|
|
margin-bottom: 3rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__chartsGrid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); /* 调整列宽,适配列表 */
|
|||
|
|
gap: 1.5rem;
|
|||
|
|
padding: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__chartCard {
|
|||
|
|
-webkit-backdrop-filter: blur(10px);
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
background: #ffffffe6;
|
|||
|
|
border: 1px solid #ffffff4d;
|
|||
|
|
border-radius: 1rem;
|
|||
|
|
padding: 1.5rem;
|
|||
|
|
box-shadow: 0 4px 16px #00000014;
|
|||
|
|
transition: all 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 列表卡片特殊样式 */
|
|||
|
|
.index-module__NV_5cW__chartCard.list-card {
|
|||
|
|
padding: 1.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__chartCard:hover {
|
|||
|
|
border-color: #3b82f64d;
|
|||
|
|
transform: translateY(-4px);
|
|||
|
|
box-shadow: 0 8px 24px #0000001f;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.chartCard__wide {
|
|||
|
|
grid-column: 1 / -1; /* 跨列显示 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.chartCard__title {
|
|||
|
|
color: #333;
|
|||
|
|
font-size: 1.1rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin: 0 0 1rem 0;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.chartCard__container {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 300px; /* 图表高度 */
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 列表容器样式:隐藏原生滚动条(可选,视觉更美观) */
|
|||
|
|
.chartCard__container.list-container {
|
|||
|
|
height: auto;
|
|||
|
|
max-height: 300px; /* 限制列表高度,超出滚动 */
|
|||
|
|
overflow-y: auto;
|
|||
|
|
padding: 0 0.5rem;
|
|||
|
|
/* 隐藏滚动条 */
|
|||
|
|
scrollbar-width: none; /* Firefox */
|
|||
|
|
-ms-overflow-style: none; /* IE/Edge */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 隐藏webkit内核浏览器的滚动条 */
|
|||
|
|
.chartCard__container.list-container::-webkit-scrollbar {
|
|||
|
|
display: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 项目列表样式 */
|
|||
|
|
.project-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 0.8rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.project-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 1rem;
|
|||
|
|
padding: 0.8rem;
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
border-radius: 0.5rem;
|
|||
|
|
border-left: 3px solid #2951E0;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 鼠标悬停项目项时的样式(可选) */
|
|||
|
|
.project-item:hover {
|
|||
|
|
background: #f0f4ff;
|
|||
|
|
transform: translateX(2px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.project-rank {
|
|||
|
|
width: 24px;
|
|||
|
|
height: 24px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
background: #2951E0;
|
|||
|
|
color: #fff;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.project-info {
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 0.2rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.project-name {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.project-desc {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #666;
|
|||
|
|
font-style: italic;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.project-org {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #888;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
max-width: 150px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 与示例一致的空数据样式 */
|
|||
|
|
.num-empty {
|
|||
|
|
height: 10px;
|
|||
|
|
border-top: 2px solid #2951E0;
|
|||
|
|
width: 100%;
|
|||
|
|
background: linear-gradient(180deg, rgba(41, 81, 224, 0.12) 0%, rgba(41, 81, 224, 0.04) 100%);
|
|||
|
|
position: absolute;
|
|||
|
|
top: 50%;
|
|||
|
|
left: 0;
|
|||
|
|
transform: translateY(-50%);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.chart {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__sectionHeader {
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 1rem;
|
|||
|
|
margin-bottom: 2rem;
|
|||
|
|
display: flex;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__sectionTitle {
|
|||
|
|
color: #333;
|
|||
|
|
text-align: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 0.75rem;
|
|||
|
|
width: 100%;
|
|||
|
|
margin: 0;
|
|||
|
|
font-size: 1.5rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
display: flex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionSection {
|
|||
|
|
text-align: center;
|
|||
|
|
-webkit-backdrop-filter: blur(10px);
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
background: #ffffffe6;
|
|||
|
|
border: 1px solid #ffffff4d;
|
|||
|
|
border-radius: 1.5rem;
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 2.5rem 2rem;
|
|||
|
|
position: relative;
|
|||
|
|
overflow: hidden;
|
|||
|
|
box-shadow: 0 8px 24px #00000014;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionBackground {
|
|||
|
|
z-index: 1;
|
|||
|
|
background: linear-gradient(135deg, #3b82f60d 0%, #9333ea0d 100%);
|
|||
|
|
position: absolute;
|
|||
|
|
inset: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionContent {
|
|||
|
|
z-index: 2;
|
|||
|
|
max-width: 600px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionIcon {
|
|||
|
|
color: #fff;
|
|||
|
|
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
width: 3rem;
|
|||
|
|
height: 3rem;
|
|||
|
|
margin: 0 auto 1.5rem;
|
|||
|
|
display: flex;
|
|||
|
|
box-shadow: 0 4px 12px #3b82f633;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionTitle {
|
|||
|
|
background: linear-gradient(135deg, #1e293b 0%, #3b82f6 100%);
|
|||
|
|
-webkit-text-fill-color: transparent;
|
|||
|
|
-webkit-background-clip: text;
|
|||
|
|
background-clip: text;
|
|||
|
|
margin-bottom: 1rem;
|
|||
|
|
font-size: 2rem;
|
|||
|
|
font-weight: 700;
|
|||
|
|
line-height: 1.2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionDescription {
|
|||
|
|
color: #64748b;
|
|||
|
|
margin-bottom: 2rem;
|
|||
|
|
font-size: 1rem;
|
|||
|
|
font-weight: 400;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionFeatures {
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
justify-content: center;
|
|||
|
|
gap: 1.5rem;
|
|||
|
|
margin-bottom: 2rem;
|
|||
|
|
display: flex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__feature {
|
|||
|
|
color: #3b82f6;
|
|||
|
|
background: #3b82f614;
|
|||
|
|
border: 1px solid #3b82f626;
|
|||
|
|
border-radius: 1.5rem;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 0.4rem;
|
|||
|
|
padding: 0.5rem 1rem;
|
|||
|
|
font-size: 0.85rem;
|
|||
|
|
font-weight: 500;
|
|||
|
|
display: flex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__createButton {
|
|||
|
|
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 1.5rem;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 0.5rem;
|
|||
|
|
height: auto;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
padding: 0.75rem 2rem;
|
|||
|
|
font-size: 1rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
transition: all 0.3s;
|
|||
|
|
display: flex;
|
|||
|
|
box-shadow: 0 4px 12px #3b82f64d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__createButton:hover {
|
|||
|
|
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 6px 18px #3b82f666;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 响应式适配 */
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.index-module__NV_5cW__titleContainer {
|
|||
|
|
text-align: center;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 0.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__title {
|
|||
|
|
font-size: 2.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__titleBadge {
|
|||
|
|
padding: 0.4rem 1.2rem;
|
|||
|
|
font-size: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__container {
|
|||
|
|
padding: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__header {
|
|||
|
|
margin-bottom: 2rem;
|
|||
|
|
padding: 1.5rem 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 移动端数据统计栏调整
|
|||
|
|
.index-module__NV_5cW__statsBar {
|
|||
|
|
gap: 1rem;
|
|||
|
|
padding: 0.8rem 1rem;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__stat {
|
|||
|
|
flex: 1 1 60px;
|
|||
|
|
min-width: 60px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statNumber {
|
|||
|
|
font-size: 1.1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statLabel {
|
|||
|
|
white-space: nowrap;
|
|||
|
|
font-size: 0.7rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statDivider {
|
|||
|
|
width: 1px;
|
|||
|
|
height: 1.2rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__sectionTitle {
|
|||
|
|
text-align: left;
|
|||
|
|
flex: 1;
|
|||
|
|
justify-content: flex-start;
|
|||
|
|
gap: 0.5rem;
|
|||
|
|
font-size: 1.3rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionSection {
|
|||
|
|
border-radius: 1rem;
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 2rem 1.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionTitle {
|
|||
|
|
font-size: 1.6rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionFeatures {
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__createButton {
|
|||
|
|
padding: 0.65rem 1.5rem;
|
|||
|
|
font-size: 0.95rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.chartCard__container {
|
|||
|
|
height: 250px; /* 移动端图表高度调整 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 移动端列表样式调整 */
|
|||
|
|
.project-item {
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 0.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.project-org {
|
|||
|
|
width: 100%;
|
|||
|
|
max-width: 100%;
|
|||
|
|
margin-top: 0.2rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 移动端标题区域调整
|
|||
|
|
.index-module__NV_5cW__titleSection {
|
|||
|
|
padding: 0 1rem;
|
|||
|
|
max-width: 100%;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 480px) {
|
|||
|
|
.index-module__NV_5cW__title {
|
|||
|
|
font-size: 1.8rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__actionSection {
|
|||
|
|
padding: 1.5rem 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statsBar {
|
|||
|
|
gap: 0.6rem;
|
|||
|
|
padding: 0.6rem 0.8rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statNumber {
|
|||
|
|
font-size: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__statLabel {
|
|||
|
|
font-size: 0.65rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__sectionTitle {
|
|||
|
|
gap: 0.4rem;
|
|||
|
|
font-size: 1.2rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.chartCard__container {
|
|||
|
|
height: 200px; /* 小屏移动端图表高度调整 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-module__NV_5cW__chartsGrid {
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 小屏列表高度调整 */
|
|||
|
|
.chartCard__container.list-container {
|
|||
|
|
max-height: 300px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/* 绘制标题左侧红色箭头(尖端指向文字) */
|
|||
|
|
.title-arrow-left {
|
|||
|
|
width: 0;
|
|||
|
|
height: 0;
|
|||
|
|
border-top: 9px solid transparent;
|
|||
|
|
border-bottom: 9px solid transparent;
|
|||
|
|
border-left: 12px solid #C8161D; /* 使用标准的实验室红色值 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 绘制标题右侧红色箭头(尖端指向文字) */
|
|||
|
|
.title-arrow-right {
|
|||
|
|
width: 0;
|
|||
|
|
height: 0;
|
|||
|
|
border-top: 9px solid transparent;
|
|||
|
|
border-bottom: 9px solid transparent;
|
|||
|
|
border-right: 12px solid #C8161D;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 字体美化:确保标题厚重稳重 */
|
|||
|
|
h2 {
|
|||
|
|
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 针对标签内文字的微调 */
|
|||
|
|
span {
|
|||
|
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.more-btn {
|
|||
|
|
display: inline-block;
|
|||
|
|
background-color: #C8161D; /* 品牌红 */
|
|||
|
|
color: #ffffff;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
padding: 10px 38px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
letter-spacing: 0.5px;
|
|||
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|||
|
|
box-shadow: 0 4px 12px rgba(200, 22, 29, 0.15);
|
|||
|
|
|
|||
|
|
&:hover {
|
|||
|
|
background-color: #a31218;
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 6px 18px rgba(200, 22, 29, 0.25);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 文本截断 */
|
|||
|
|
.line-clamp-2 {
|
|||
|
|
display: -webkit-box;
|
|||
|
|
-webkit-box-orient: vertical;
|
|||
|
|
-webkit-line-clamp: 2;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 字体优化 */
|
|||
|
|
h3, .more-btn {
|
|||
|
|
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Microsoft YaHei", sans-serif;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
</style>
|