准备覆盖远程仓库

This commit is contained in:
RayLaruki 2026-01-06 11:13:04 +08:00
parent 7979e6ea4c
commit bdf8cdd8c6
20 changed files with 4424 additions and 96 deletions

View File

@ -8,17 +8,26 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@types/d3": "^7.4.3",
"core-js": "^3.8.3",
"vue": "^3.2.13"
"d3": "^7.9.0",
"element-plus": "^2.13.0",
"vue": "^3.2.13",
"vue-router": "4"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@types/node": "^25.0.3",
"@typescript-eslint/eslint-plugin": "^8.52.0",
"@typescript-eslint/parser": "^8.52.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-typescript": "^5.0.9",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
"eslint-plugin-vue": "^8.0.3",
"typescript": "^5.9.3"
},
"eslintConfig": {
"root": true,
@ -30,9 +39,20 @@
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
"parser": "@typescript-eslint/parser",
"ecmaVersion": 2020,
"sourceType": "module"
},
"rules": {}
"rules": {},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.vue"],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
}
}
]
},
"browserslist": [
"> 1%",

View File

@ -14,4 +14,10 @@
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</html>

View File

@ -1,16 +1,12 @@
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<div id="app">
<router-view/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
name: 'App'
}
</script>
@ -21,6 +17,21 @@ export default {
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
nav {
padding: 30px;
background-color: #f5f5f5;
}
nav a {
font-weight: bold;
color: #2c3e50;
text-decoration: none;
margin: 0 10px;
}
nav a.router-link-exact-active {
color: #409eff;
}
</style>

View File

@ -1,58 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -0,0 +1,312 @@
<template>
<footer class="page-footer">
<div class="footer-container">
<div class="footer-content">
<div class="footer-section">
<div class="footer-brand">
<div class="brand-logo">
<div class="brand-mark"></div>
<h3>科技大脑智慧服务平台</h3>
</div>
<p class="brand-description">
致力于整合优质科技资源为企业和个人提供全方位的科技创新服务
推动科技成果转化助力创新发展
</p>
<div class="social-links">
<el-button circle size="small" class="social-btn">
<el-icon>
<ChatDotRound />
</el-icon>
</el-button>
<el-button circle size="small" class="social-btn">
<el-icon>
<Message />
</el-icon>
</el-button>
<el-button circle size="small" class="social-btn">
<el-icon>
<Phone />
</el-icon>
</el-button>
</div>
</div>
</div>
<div class="footer-section">
<h4>服务导航</h4>
<ul class="footer-links">
<li>
<router-link to="/news-policy">新闻政策</router-link>
</li>
<li>
<router-link to="/smart-qa">智慧问答</router-link>
</li>
<li>
<router-link to="/tech-resources">科技资源</router-link>
</li>
<li>
<router-link to="/talent-profile">人才画像</router-link>
</li>
</ul>
</div>
<div class="footer-section">
<h4>政策法规</h4>
<ul class="footer-links">
<li>
<a href="#">高新技术企业认定</a>
</li>
<li>
<a href="#">科技成果转化政策</a>
</li>
<li>
<a href="#">人才引进政策</a>
</li>
<li>
<a href="#">创新创业扶持</a>
</li>
</ul>
</div>
<div class="footer-section">
<h4>帮助中心</h4>
<ul class="footer-links">
<li>
<a href="#">使用指南</a>
</li>
<li>
<a href="#">常见问题</a>
</li>
<li>
<a href="#">联系我们</a>
</li>
<li>
<a href="#">意见反馈</a>
</li>
</ul>
</div>
<div class="footer-section">
<h4>联系信息</h4>
<div class="contact-info">
<div class="contact-item">
<el-icon>
<Location />
</el-icon>
<span>长春理工大学</span>
</div>
<div class="contact-item">
<el-icon>
<Phone />
</el-icon>
<span>400-123-4567</span>
</div>
<div class="contact-item">
<el-icon>
<Message />
</el-icon>
<span>service@techbrain.com</span>
</div>
</div>
</div>
</div>
<el-divider />
<div class="footer-bottom">
<div class="copyright">
<p>&copy; 2024 科技大脑智慧服务平台. 保留所有权利.</p>
</div>
<div class="footer-links-bottom">
<a href="#">隐私政策</a>
<span class="divider">|</span>
<a href="#">服务条款</a>
<span class="divider">|</span>
<a href="#">网站地图</a>
<span class="divider">|</span>
<a href="#">备案号: 京ICP备12345678号</a>
</div>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.page-footer {
background: #2c3e50;
color: #ecf0f1;
margin-top: 60px;
}
.footer-container {
max-width: 1200px;
margin: 0 auto;
padding: 60px 40px 30px;
}
.footer-content {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr;
gap: 40px;
margin-bottom: 40px;
}
.footer-section h4 {
color: #409eff;
font-size: 18px;
font-weight: 600;
margin-bottom: 20px;
}
.footer-brand .brand-logo {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.brand-mark {
width: 20px;
height: 5px;
border-radius: 3px;
background: linear-gradient(90deg, #409eff, #337ecc);
box-shadow: 0 0 10px rgba(64, 158, 255, 0.6);
}
.footer-brand h3 {
color: #409eff;
font-size: 18px;
font-weight: 600;
margin: 0;
}
.brand-description {
color: #bdc3c7;
line-height: 1.6;
margin-bottom: 20px;
font-size: 14px;
}
.social-links {
display: flex;
gap: 12px;
}
.social-btn {
background: rgba(64, 158, 255, 0.1);
border-color: rgba(64, 158, 255, 0.3);
color: #409eff;
}
.social-btn:hover {
background: rgba(64, 158, 255, 0.2);
border-color: #409eff;
}
.footer-links {
list-style: none;
padding: 0;
margin: 0;
}
.footer-links li {
margin-bottom: 12px;
}
.footer-links a {
color: #bdc3c7;
text-decoration: none;
font-size: 14px;
transition: color 0.3s ease;
}
.footer-links a:hover {
color: #409eff;
}
.contact-info {
display: flex;
flex-direction: column;
gap: 12px;
}
.contact-item {
display: flex;
align-items: center;
gap: 8px;
color: #bdc3c7;
font-size: 14px;
}
.contact-item .el-icon {
color: #409eff;
font-size: 16px;
}
.footer-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20px;
}
.copyright p {
color: #95a5a6;
font-size: 14px;
margin: 0;
}
.footer-links-bottom {
display: flex;
align-items: center;
gap: 8px;
}
.footer-links-bottom a {
color: #95a5a6;
text-decoration: none;
font-size: 12px;
transition: color 0.3s ease;
}
.footer-links-bottom a:hover {
color: #409eff;
}
.footer-links-bottom .divider {
color: #7f8c8d;
font-size: 12px;
}
@media (max-width: 1024px) {
.footer-content {
grid-template-columns: 1fr 1fr 1fr;
gap: 30px;
}
.footer-section:first-child {
grid-column: 1 / -1;
}
}
@media (max-width: 768px) {
.footer-content {
grid-template-columns: 1fr;
gap: 30px;
}
.footer-bottom {
flex-direction: column;
gap: 16px;
text-align: center;
}
.footer-container {
padding: 40px 20px 20px;
}
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<header class="topbar">
<!-- 左侧品牌 -->
<router-link to="/" class="brand">
<div class="brand-mark"></div>
<div class="brand-text">科技大脑智慧服务平台</div>
</router-link>
<!-- 右侧整条胶囊导航条 -->
<div class="nav-wrap">
<div class="nav-list" role="navigation" aria-label="主导航">
<router-link to="/" class="nav-item" :class="{ 'is-active': $route.name === 'home' }">首页</router-link>
<router-link to="/news-policy" class="nav-item" :class="{ 'is-active': $route.name === 'news-policy' || $route.name === 'news-policy-detail' }">新闻政策</router-link>
<router-link to="/smart-qa" class="nav-item" :class="{ 'is-active': $route.name === 'smart-qa' }">智慧问答</router-link>
<router-link to="/tech-resources" class="nav-item" :class="{ 'is-active': $route.name === 'tech-resources' }">科技资源</router-link>
<router-link to="/talent-profile" class="nav-item" :class="{ 'is-active': $route.name === 'talent-profile' }">人才画像</router-link>
<router-link to="/login" class="nav-item" :class="{ 'is-active': $route.name === 'login' }">登录</router-link>
</div>
</div>
</header>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
/* ===== 顶栏 ===== */
/* 顶部整行:左右两端布局 - 浅色风格 */
.topbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
padding: 24px 32px;
display: flex;
align-items: center;
justify-content: space-between;
pointer-events: auto;
background: #ffffff;
border-bottom: 1px solid #e4e7ed;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* 左侧品牌区 */
.brand {
display: flex;
align-items: center;
gap: 14px;
color: #409eff;
text-decoration: none;
transition: opacity 0.2s ease;
}
.brand:hover {
opacity: 0.85;
}
.brand-mark {
width: 24px;
height: 6px;
border-radius: 4px;
background: #49b0ff;
box-shadow: 0 0 8px rgba(73, 176, 255, 0.5);
}
.brand-text {
font-size: 20px;
letter-spacing: 0.5px;
font-weight: 500;
}
/* 右侧胶囊导航条:纯 divflex 布局 */
.nav-wrap {
display: flex;
align-items: flex-end;
gap: 16px;
padding: 10px 10px 10px 18px;
min-width: 720px;
backdrop-filter: blur(2px);
}
/* 菜单区:居中对齐、等间距 */
.nav-list {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 42px;
}
/* 菜单项router-link 样式hover 高亮active 发光 */
.nav-item {
position: relative;
padding: 6px 2px;
color: #606266;
font-size: 16px;
letter-spacing: 0.5px;
transition: color 0.2s ease;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.nav-item:hover {
color: #409eff;
}
.nav-item.is-active {
color: #409eff;
font-weight: 600;
}
</style>

View File

@ -1,4 +1,11 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).mount('#app')
const app = createApp(App)
app.use(router)
app.use(ElementPlus)
app.mount('#app')

11
src/main.ts Normal file
View File

@ -0,0 +1,11 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(router)
app.use(ElementPlus)
app.mount('#app')

53
src/router/index.js Normal file
View File

@ -0,0 +1,53 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeIndex from '@/views/HomeIndex.vue'
import NewsPolicyIndex from '@/views/NewsPolicy/NewsPolicyIndex.vue'
import PolicyDetail from '@/views/NewsPolicy/PolicyDetail.vue'
import SmartQAIndex from '@/views/SmartQA/SmartQAIndex.vue'
import TechResourcesIndex from '@/views/TechResources/TechResourcesIndex.vue'
import TalentProfileIndex from '@/views/TalentProfile/TalentProfileIndex.vue'
import UserLoginIndex from '@/views/UserLogin/UserLoginIndex.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeIndex
},
{
path: '/news-policy',
name: 'news-policy',
component: NewsPolicyIndex
},
{
path: '/news-policy/detail/:id',
name: 'news-policy-detail',
component: PolicyDetail
},
{
path: '/smart-qa',
name: 'smart-qa',
component: SmartQAIndex
},
{
path: '/tech-resources',
name: 'tech-resources',
component: TechResourcesIndex
},
{
path: '/talent-profile',
name: 'talent-profile',
component: TalentProfileIndex
},
{
path: '/login',
name: 'login',
component: UserLoginIndex
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router

5
src/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

802
src/views/HomeIndex.vue Normal file
View File

@ -0,0 +1,802 @@
<template>
<div class="screen">
<div class="stage">
<div class="bg"></div>
<!-- 顶部导航 -->
<header class="topbar">
<!-- 左侧品牌 -->
<div class="brand">
<div class="brand-mark"></div>
<div class="brand-text">科技大脑智慧服务平台</div>
</div>
<!-- 右侧整条胶囊导航条 -->
<div class="nav-wrap">
<div class="nav-list" role="navigation" aria-label="主导航">
<router-link to="/" class="nav-item" :class="{ 'is-active': $route.name === 'home' }">首页</router-link>
<router-link to="/news-policy" class="nav-item" :class="{ 'is-active': $route.name === 'news-policy' }">新闻政策</router-link>
<router-link to="/smart-qa" class="nav-item" :class="{ 'is-active': $route.name === 'smart-qa' }">智慧问答</router-link>
<router-link to="/tech-resources" class="nav-item" :class="{ 'is-active': $route.name === 'tech-resources' }">科技资源</router-link>
<router-link to="/talent-profile" class="nav-item" :class="{ 'is-active': $route.name === 'talent-profile' }">人才画像</router-link>
<router-link to="/login" class="nav-item" :class="{ 'is-active': $route.name === 'login' }">登录</router-link>
</div>
</div>
</header>
<!-- ===== 核心大圆风格化按设计稿 ===== -->
<section class="core">
<!-- 背景环 -->
<div class="ring ring-1"></div>
<div class="ring ring-2"></div>
<div class="ring ring-3"></div>
<!-- 大圆盘面 + 渐变描边 + 文案 -->
<div class="center">
<div class="center-bezel"></div>
<div class="center-disc">
<div class="disc-gloss"></div>
<div class="disc-vignette"></div>
</div>
<div class="center-title">科技大脑</div>
<!-- 一圈蓝色小点可慢速自转也可设为静止 -->
<ul class="center-dots">
<li v-for="n in DOT_COUNT" :key="`cd-${n}`" :style="dotHostStyle(n)">
<i class="spark" :style="dotDelayStyle(n)"></i>
</li>
</ul>
<!-- 4 个小三角指示器缓慢公转轻微呼吸 -->
<ul class="center-tris">
<li v-for="t in 4" :key="`tri-${t}`" :style="triStyle(t)"></li>
</ul>
</div>
<!-- 顶层中心循环发光点轨道上的亮点 -->
<div class="light-sprite"></div>
<!-- 内部小粒子 -->
<ul class="inner-dots">
<li v-for="n in 12" :key="`inner-${n}`" :style="innerDotStyle(n)"></li>
</ul>
<!-- ===== 轨道与 5 个功能圆保持不自转圆心落在轨道上 ===== -->
<div class="orbit">
<div class="node" style="--theta: 320deg;">
<div class="align">
<div class="pos">
<router-link to="/news-policy" class="bubble">
<div class="dot-ring"></div>
<span class="label">新闻<br/>政策</span><i class="marker"></i>
</router-link>
</div>
</div>
</div>
<div class="node" style="--theta: 20deg;">
<div class="align">
<div class="pos">
<router-link to="/smart-qa" class="bubble">
<div class="dot-ring"></div>
<span class="label">智慧<br/>问答</span><i class="marker"></i>
</router-link>
</div>
</div>
</div>
<div class="node" style="--theta: 70deg;">
<div class="align">
<div class="pos">
<router-link to="/talent-profile" class="bubble">
<div class="dot-ring"></div>
<span class="label">人才<br/>画像</span><i class="marker"></i>
</router-link>
</div>
</div>
</div>
<div class="node" style="--theta: 210deg;">
<div class="align">
<div class="pos">
<div class="bubble">
<div class="dot-ring"></div>
<span class="label">科技<br/>地图</span><i class="marker"></i>
</div>
</div>
</div>
</div>
<div class="node" style="--theta: 280deg;">
<div class="align">
<div class="pos">
<router-link to="/tech-resources" class="bubble">
<div class="dot-ring"></div>
<span class="label">科技<br/>资源</span><i class="marker"></i>
</router-link>
</div>
</div>
</div>
</div>
</section>
<!-- 左侧引言 -->
<aside class="intro">
<div class="intro-title">引言</div>
<div class="intro-body">
挖掘信息要素价值<br/>图谱 | 图谱 | 脉络<br/>
赋能主题检索与决策<br/>帮助企业更快了解<br/>投资机遇
</div>
</aside>
<!-- 右侧 AI 浮标 -->
<div class="ai-fab">
<div class="ai-icon">🤖</div>
<div class="ai-text">AI<br/>问答</div>
</div>
<!-- 底部装饰 -->
<footer class="progress">
<div class="bar bar-left"></div>
<div class="bar bar-right"></div>
</footer>
</div>
</div>
</template>
<script setup lang="ts">
/** ===== 中心小点圈参数(参考设计) ===== */
const DOT_COUNT = 24 // 36
const DOT_RADIUS = 92 //
const dotHostStyle = (n: number) => {
const deg = (360 / DOT_COUNT) * n
return {transform: `rotate(${deg}deg) translateY(-${DOT_RADIUS}px)`}
}
const dotDelayStyle = (n: number) => {
//
const delay = (n % 6) * 0.15
return {animationDelay: `${delay}s`}
}
/** 四个三角的位置与延迟(在描边外侧一点点) */
const TRI_RADIUS = 115
const triStyle = (i: number) => {
const deg = 90 * (i - 1) // 0,90,180,270
const delay = (i - 1) * 0.3
return {
transform: `rotate(${deg}deg) translateY(-${TRI_RADIUS}px)`,
animationDelay: `${delay}s`
}
}
/** 内部微粒(淡) */
const innerDotStyle = (n: number) => {
const deg = (360 / 12) * n
return {transform: `rotate(${deg}deg) translateY(-70px)`}
}
</script>
<style scoped>
/* ===== 一屏缩放 ===== */
.screen {
height: 100vh;
width: 100vw;
background: #081a2b;
overflow: hidden
}
.stage {
position: relative;
width: 1920px;
height: 1080px;
margin: 0 auto;
transform-origin: 50% 50%
}
@media (min-aspect-ratio: 16/9) {
.stage {
transform: scale(calc(100vh / 1080))
}
}
@media (max-aspect-ratio: 16/9) {
.stage {
transform: scale(calc(100vw / 1920))
}
}
/* ===== 背景(渐变+网格) ===== */
.bg {
position: absolute;
inset: 0;
background: radial-gradient(1000px 500px at 50% 70%, rgba(23, 94, 150, .35), rgba(8, 26, 43, 0) 60%),
linear-gradient(180deg, #0b2238 0%, #061626 60%, #04111f 100%)
}
.bg::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(60% 40% at 50% 70%, rgba(255, 255, 255, .06) 0, rgba(255, 255, 255, 0) 60%),
repeating-linear-gradient(to right, rgba(255, 255, 255, .06) 0 1px, transparent 1px 40px),
repeating-linear-gradient(to top, rgba(255, 255, 255, .06) 0 1px, transparent 1px 40px);
mask: linear-gradient(to top, rgba(0, 0, 0, 0) 0%, #000 35%, #000 100%);
transform: perspective(800px) rotateX(65deg) translateY(320px);
}
/* ===== 顶栏 ===== */
/* 顶部整行:左右两端布局 */
.topbar {
position: absolute;
inset: 24px 32px auto 32px;
display: flex;
align-items: center;
justify-content: space-between;
pointer-events: auto;
}
/* 左侧品牌区 */
.brand {
display: flex;
align-items: center;
gap: 14px;
color: #cfe8ff
}
.brand-mark {
width: 24px;
height: 6px;
border-radius: 4px;
background: #49b0ff;
box-shadow: 0 0 12px #49b0ff
}
.brand-text {
font-size: 20px;
letter-spacing: .5px;
opacity: .95
}
/* 右侧胶囊导航条:纯 divflex 布局 */
.nav-wrap {
display: flex;
align-items: flex-end;
gap: 16px;
padding: 10px 10px 10px 18px;
min-width: 720px; /* 让条看起来更长一些,可按需调整 */
backdrop-filter: blur(2px);
cursor: pointer;
}
/* 菜单区:居中对齐、等间距 */
.nav-list {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 42px;
}
/* 菜单项router-link 样式hover 高亮active 发光 */
.nav-item {
position: relative;
padding: 6px 2px;
color: #ffffff;
font-size: 16px;
letter-spacing: .5px;
transition: color .2s ease, text-shadow .2s ease, transform .2s ease;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.nav-item:hover {
color: #d7ecff;
text-shadow: 0 0 10px rgba(144, 210, 255, .5);
cursor: pointer;
}
.nav-item.is-active {
color: #8fd3ff;
font-weight: 600;
text-shadow: 0 0 10px rgba(150, 210, 255, .85);
}
/* 登录按钮(右侧圆角按钮) */
.nav-login {
padding: 6px 16px;
border-radius: 10px;
color: #e6f5ff;
font-size: 14px;
letter-spacing: .5px;
border: 1px solid rgba(130, 196, 255, .35);
background: linear-gradient(180deg, #163a57, #0c2135);
box-shadow: inset 0 0 12px rgba(73, 176, 255, .18), 0 6px 16px rgba(6, 22, 38, .35);
transition: transform .15s ease, box-shadow .2s ease;
}
.nav-login:hover {
transform: translateY(-1px);
box-shadow: inset 0 0 12px rgba(73, 176, 255, .25), 0 10px 20px rgba(6, 22, 38, .45)
}
/* ===== 核心(同心) ===== */
.core {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 720px;
height: 720px
}
.ring {
position: absolute;
inset: 0;
border-radius: 50%;
border: 1px solid rgba(130, 196, 255, .12)
}
.ring-1 {
transform: scale(.78)
}
.ring-2 {
transform: scale(.60);
border-color: rgba(130, 196, 255, .2)
}
.ring-3 {
transform: scale(.43);
border-color: rgba(130, 196, 255, .28)
}
/* ===== 中心大圆(设计还原) ===== */
.center {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 280px;
height: 280px;
pointer-events: none;
}
.center-bezel {
position: absolute;
inset: 10px;
border-radius: 50%;
/* 渐变描边:内外发光 + 2px 蓝色描边 */
background: radial-gradient(circle at 50% 50%, rgba(86, 184, 255, .9), rgba(86, 184, 255, 0) 60%),
conic-gradient(from 0deg, rgba(86, 184, 255, .9), #5db9ff, #5db9ff, rgba(86, 184, 255, .9));
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 0); /* 4px 描边 */
mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 0);
filter: drop-shadow(0 0 10px rgba(86, 184, 255, .55));
}
.center-disc {
position: absolute;
inset: 28px;
border-radius: 50%;
overflow: hidden;
background: radial-gradient(140px 120px at 50% 30%, rgba(120, 200, 255, .35), rgba(120, 200, 255, 0) 70%),
radial-gradient(circle at 50% 60%, #1b4d76 0%, #0d2e4a 60%, #07263f 100%);
box-shadow: inset 0 8px 20px rgba(255, 255, 255, .16),
inset 0 -14px 22px rgba(0, 0, 0, .35);
}
.disc-gloss {
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(180px 70px at 50% 12%, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0) 60%);
mix-blend-mode: screen;
opacity: .25;
}
.disc-vignette {
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(70% 80% at 50% 100%, rgba(0, 0, 0, .3), rgba(0, 0, 0, 0) 60%);
mix-blend-mode: multiply;
opacity: .35;
}
.center-title {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: #f5fbff;
font-size: 28px;
font-weight: 700;
letter-spacing: 2px;
text-shadow: 0 0 8px rgba(160, 210, 255, .9);
}
/* 点环:父层定位 + 子层闪烁,环整体慢速转(更灵动;不想转可去掉 animation */
.center-dots {
position: absolute;
inset: 0;
list-style: none;
margin: 0;
padding: 0;
animation: slowSpin 60s linear infinite
}
.center-dots li {
position: absolute;
left: 50%;
top: 50%;
width: 0;
height: 0;
transform-origin: 0 0
}
.center-dots .spark {
display: block;
width: 8px;
height: 8px;
margin: -4px 0 0 -4px;
border-radius: 50%;
background: #9cd6ff;
filter: drop-shadow(0 0 6px #5db9ff) drop-shadow(0 0 10px #5db9ff);
animation: sparkTwinkle 2.2s ease-in-out infinite;
}
@keyframes sparkTwinkle {
0%, 100% {
opacity: .25;
transform: scale(.85)
}
50% {
opacity: .95;
transform: scale(1.2)
}
}
@keyframes slowSpin {
to {
transform: rotate(360deg)
}
}
/* 四个小三角指示器 */
.center-tris {
position: absolute;
inset: 0;
list-style: none;
margin: 0;
padding: 0;
animation: trisSpin 24s linear infinite
}
.center-tris li {
position: absolute;
left: 50%;
top: 50%;
width: 0;
height: 0;
transform-origin: 0 0;
}
.center-tris li::after {
content: "";
display: block;
transform: translate(-6px, -8px);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 10px solid #8fd3ff;
filter: drop-shadow(0 0 4px rgba(100, 190, 255, .9));
opacity: .8;
animation: triPulse 1.8s ease-in-out infinite;
}
@keyframes trisSpin {
to {
transform: rotate(360deg)
}
}
@keyframes triPulse {
0%, 100% {
opacity: .45;
transform: translate(-6px, -8px) scale(.9)
}
50% {
opacity: 1;
transform: translate(-6px, -8px) scale(1.1)
}
}
/* 中心循环发光点(轨道上的亮点) */
.light-sprite {
position: absolute;
left: 50%;
top: 50%;
width: 18px;
height: 18px;
margin: -9px 0 0 -9px;
border-radius: 50%;
transform: translate(-185px, -205px);
background: radial-gradient(circle, #bfe6ff 0 30%, rgba(191, 230, 255, 0) 70%), radial-gradient(circle, #5cc8ff 0 40%, rgba(92, 200, 255, 0) 65%);
filter: drop-shadow(0 0 8px #7fd2ff) drop-shadow(0 0 16px #7fd2ff);
animation: spriteBlink 1.2s ease-in-out infinite
}
@keyframes spriteBlink {
0%, 100% {
transform: translate(-185px, -205px) scale(.6);
opacity: .25
}
50% {
transform: translate(-185px, -205px) scale(1.25);
opacity: 1
}
}
/* 内部小粒子(淡) */
.inner-dots {
position: absolute;
inset: 0;
list-style: none;
margin: 0;
padding: 0;
animation: innerSpin 30s linear infinite
}
.inner-dots li {
position: absolute;
left: 50%;
top: 50%;
width: 5px;
height: 5px;
margin: -2.5px 0 0 -2.5px;
border-radius: 50%;
background: #aee1ff;
opacity: .5;
filter: drop-shadow(0 0 4px #62c2ff);
transform-origin: 0 0
}
@keyframes innerSpin {
to {
transform: rotate(360deg)
}
}
/* ===== 轨道与功能圆保持不变(关键:圆心落在轨道上、文字不歪) ===== */
.orbit {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 560px;
height: 560px;
border-radius: 50%;
animation: spin 36s linear infinite;
box-shadow: 0 0 0 1px rgba(130, 196, 255, .1) inset, 0 0 22px rgba(73, 176, 255, .05) inset
}
@keyframes spin {
to {
transform: translate(-50%, -50%) rotate(360deg)
}
}
.node {
--radius: 290px;
position: absolute;
left: 50%;
top: 50%;
transform: rotate(var(--theta)) translate(var(--radius));
transform-origin: 0 0
}
.align {
transform: rotate(calc(-1 * var(--theta)));
transform-origin: 0 0
}
.pos {
position: relative;
transform: translate(-50%, -50%)
}
/* 功能小圆主体 */
.bubble{
position: relative;
width: 138px;
height: 138px;
border-radius: 50%;
overflow: hidden;
display: grid;
place-items: center;
text-align: center;
/* 仅这两点:不透明背景色 + 底部黑色阴影 */
background-color: #0B2444;
box-shadow: 0 18px 28px rgba(0, 0, 0, 0.55); /* 底部黑色阴影 */
animation: counterSpin 36s linear infinite; /* 如果你还在用轨道反转动画 */
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
}
.bubble:hover {
transform: scale(1.05);
box-shadow: 0 20px 35px rgba(0, 0, 0, 0.65), 0 0 20px rgba(73, 176, 255, 0.3);
}
/* 外圈柔和发光(薄描边 + 外发光) */
.bubble::before{
content:"";
position:absolute; inset:10px; border-radius:50%;
background:
radial-gradient(circle at 50% 50%, rgba(86,184,255,.9), rgba(86,184,255,0) 60%);
/* 只保留2px的“环” */
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 0);
mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 0);
filter: drop-shadow(0 2px 8px rgba(86,184,255,.45));
opacity:.55;
pointer-events:none;
}
/* 顶部镜面高光(柔和过渡,增加玻璃质感) */
.bubble::after{
content:"";
position:absolute; inset:0; border-radius:50%;
background:
radial-gradient(160px 120px at 50% 22%, rgba(255,255,255,.32), rgba(255,255,255,0) 60%);
mix-blend-mode: screen; /* 让高光更柔 */
opacity:.35;
pointer-events:none;
}
/* 如果你的蓝色箭头在底部:保持不转动 */
.marker{
position:absolute; left:50%; bottom:14px; transform:translateX(-50%);
width:0; height:0;
border-left:6px solid transparent; border-right:6px solid transparent;
border-bottom:10px solid #27b9ff;
filter: drop-shadow(0 0 4px rgba(73,176,255,.9));
}
/* (可选)整个小圆再来一点环境投影,更贴设计稿的“厚度” */
.bubble-wrap{ /* 如果外面还有一层容器,可加在容器上 */
filter: drop-shadow(0 10px 12px rgba(4,17,31,.55));
}
@keyframes counterSpin {
to {
transform: rotate(-360deg)
}
}
.dot-ring {
position: absolute;
inset: 12px;
border-radius: 50%;
pointer-events: none;
background: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 138 138'>\
<defs><filter id='g' x='-50%' y='-50%' width='200%' height='200%'>\
<feGaussianBlur stdDeviation='0.9' result='b'/><feMerge><feMergeNode in='b'/><feMergeNode in='SourceGraphic'/></feMerge>\
</filter></defs>\
<circle cx='69' cy='69' r='53' fill='none' stroke='rgba(214,234,255,0.95)' stroke-width='2' stroke-linecap='round' stroke-dasharray='0 14' filter='url(%23g)'/>\
</svg>") center/contain no-repeat;
opacity: .95
}
.label {
position: relative;
z-index: 1;
color: #f2fbff;
font-size: 20px;
line-height: 1.15;
letter-spacing: 1px;
text-shadow: 0 1px 0 rgba(0, 0, 0, .4), 0 0 6px rgba(173, 220, 255, .35)
}
.marker {
position: absolute;
left: 50%;
bottom: 14px;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 10px solid #27b9ff;
filter: drop-shadow(0 0 4px rgba(73, 176, 255, .9))
}
/* ===== 侧边与底部装饰 ===== */
.intro {
position: absolute;
left: 72px;
top: 418px;
width: 240px;
padding: 18px 16px 20px;
color: #cfe8ff;
border: 1px solid rgba(130, 196, 255, .25);
border-radius: 8px;
background: linear-gradient(180deg, rgba(20, 54, 88, .25), rgba(8, 24, 41, .25));
box-shadow: 0 0 18px rgba(73, 176, 255, .12), inset 0 0 24px rgba(173, 220, 255, .08)
}
.intro-title {
font-weight: 700;
margin-bottom: 12px;
color: #8fd3ff;
letter-spacing: 2px
}
.intro-body {
font-size: 12px;
opacity: .9
}
.ai-fab {
position: absolute;
right: 56px;
top: 560px;
width: 78px;
height: 140px;
border-radius: 14px;
border: 1px solid rgba(130, 196, 255, .35);
background: linear-gradient(180deg, rgba(19, 51, 82, .55), rgba(9, 26, 44, .55));
color: #e6f5ff;
display: grid;
place-items: center;
gap: 6px;
box-shadow: 0 0 18px rgba(73, 176, 255, .2)
}
.ai-icon {
font-size: 28px;
filter: drop-shadow(0 0 6px rgba(73, 176, 255, .8))
}
.ai-text {
text-align: center;
font-size: 14px;
line-height: 1.2
}
.progress {
position: absolute;
left: 60px;
right: 60px;
bottom: 56px;
height: 16px
}
.bar {
position: absolute;
height: 4px;
border-radius: 4px;
background: linear-gradient(90deg, #93d2ff, #1a73b6);
box-shadow: 0 0 12px rgba(73, 176, 255, .6)
}
.bar-left {
left: 0;
width: 560px
}
.bar-right {
right: 0;
width: 560px
}
:where(.stage) {
font-family: "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif
}
</style>

View File

@ -0,0 +1,459 @@
<template>
<div class="page-container">
<PageNavigation />
<div class="main-content">
<div class="page-header">
<h1>新闻政策</h1>
<p>最新科技政策资讯与行业动态</p>
</div>
<!-- 标签页切换 -->
<div class="tabs-container">
<el-tabs v-model="activeTab" class="news-tabs" @tab-change="handleTabChange">
<el-tab-pane label="政策法规" name="policy">
<div class="news-grid">
<div v-for="item in policyNews" :key="item.id" class="news-card" @click="goToDetail(item.id)">
<div class="news-image">
<div class="news-placeholder">{{ item.type }}</div>
<el-tag v-if="item.isNew" type="success" size="small" class="news-tag">最新</el-tag>
</div>
<div class="news-content">
<h3>{{ item.title }}</h3>
<p>{{ item.summary }}</p>
<div class="news-meta">
<span class="date">{{ item.date }}</span>
<span class="views">{{ item.views }} 次浏览</span>
</div>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="热点新闻" name="news">
<div class="news-grid">
<div v-for="item in hotNews" :key="item.id" class="news-card" @click="goToDetail(item.id)">
<div class="news-image">
<div class="news-placeholder">{{ item.type }}</div>
<el-tag v-if="item.isHot" type="danger" size="small" class="news-tag">热门</el-tag>
</div>
<div class="news-content">
<h3>{{ item.title }}</h3>
<p>{{ item.summary }}</p>
<div class="news-meta">
<span class="date">{{ item.date }}</span>
<span class="views">{{ item.views }} 次浏览</span>
</div>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="totalItems"
layout="prev, pager, next, jumper"
@current-change="handlePageChange"
/>
</div>
</div>
<!-- 页脚 -->
<PageFooter />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import PageNavigation from '@/components/PageNavigation.vue'
import PageFooter from '@/components/PageFooter.vue'
const router = useRouter()
interface NewsItem {
id: string
title: string
summary: string
type: string
date: string
views: number
isNew?: boolean
isHot?: boolean
}
const activeTab = ref('policy')
const currentPage = ref(1)
const pageSize = ref(9)
const totalItems = ref(27)
const policyNews = ref<NewsItem[]>([
{
id: 'policy-1',
title: '科技创新支持政策发布',
summary: '为进一步推动科技创新发展,相关部门发布了一系列支持政策,涵盖资金支持、税收优惠等多个方面。',
type: 'NEWS',
date: '2024-01-15',
views: 1250,
isNew: true
},
{
id: 'policy-2',
title: '数字化转型指导意见',
summary: '加快推进企业数字化转型,提升科技创新能力,构建数字经济新优势。',
type: 'NEWS',
date: '2024-01-12',
views: 980
},
{
id: 'policy-3',
title: '高新技术企业认定管理办法',
summary: '详细解读高新技术企业认定的条件和流程,为企业申报提供指导。',
type: 'NEWS',
date: '2024-01-10',
views: 1580
},
{
id: 'policy-4',
title: '科技成果转化激励政策',
summary: '促进科技成果转化的相关激励措施和支持政策,推动产学研深度融合。',
type: 'NEWS',
date: '2024-01-08',
views: 720
},
{
id: 'policy-5',
title: '人才引进优惠政策',
summary: '针对高层次人才引进的住房补贴、子女教育等配套优惠政策。',
type: 'PRESS',
date: '2024-01-05',
views: 890
},
{
id: 'policy-6',
title: '创新创业扶持措施',
summary: '支持大众创业万众创新,提供资金、场地、服务等全方位扶持。',
type: 'NEWS',
date: '2024-01-03',
views: 1120
},
{
id: 'policy-7',
title: '知识产权保护条例',
summary: '加强知识产权保护,营造良好的创新环境和营商环境。',
type: 'PRESS',
date: '2024-01-01',
views: 650
},
{
id: 'policy-8',
title: '产业发展引导基金',
summary: '设立产业发展引导基金,支持战略性新兴产业发展。',
type: 'NEWS',
date: '2023-12-28',
views: 1350
},
{
id: 'policy-9',
title: '科研项目管理规定',
summary: '规范科研项目立项、实施、验收等全过程管理。',
type: 'PRESS',
date: '2023-12-25',
views: 780
}
])
const hotNews = ref<NewsItem[]>([
{
id: 'news-1',
title: '人工智能产业发展迅猛',
summary: '我国人工智能产业规模持续扩大,技术创新能力不断提升。',
type: 'NEWS',
date: '2024-01-14',
views: 2150,
isHot: true
},
{
id: 'news-2',
title: '新能源汽车销量创新高',
summary: '2023年新能源汽车销量突破800万辆产业发展势头强劲。',
type: 'NEWS',
date: '2024-01-13',
views: 1890
},
{
id: 'news-3',
title: '5G技术应用场景不断拓展',
summary: '5G技术在工业互联网、智慧城市等领域应用日益广泛。',
type: 'NEWS',
date: '2024-01-11',
views: 1420
},
{
id: 'news-4',
title: '生物医药创新成果丰硕',
summary: '我国生物医药领域取得多项重大突破,创新药物研发加速。',
type: 'PRESS',
date: '2024-01-09',
views: 1680,
isHot: true
},
{
id: 'news-5',
title: '量子计算技术获得突破',
summary: '国内量子计算研究取得重要进展,技术水平达到国际先进。',
type: 'NEWS',
date: '2024-01-07',
views: 2300
},
{
id: 'news-6',
title: '绿色低碳技术快速发展',
summary: '碳达峰碳中和目标推动绿色低碳技术创新和产业发展。',
type: 'NEWS',
date: '2024-01-06',
views: 1150
},
{
id: 'news-7',
title: '芯片产业自主创新加速',
summary: '国产芯片技术不断突破,产业链自主可控能力持续提升。',
type: 'PRESS',
date: '2024-01-04',
views: 1950
},
{
id: 'news-8',
title: '航空航天领域成就显著',
summary: '我国航空航天事业取得历史性成就,技术实力不断增强。',
type: 'NEWS',
date: '2024-01-02',
views: 1720
},
{
id: 'news-9',
title: '数字经济规模持续扩大',
summary: '数字经济成为经济增长新引擎规模占GDP比重不断提升。',
type: 'PRESS',
date: '2023-12-30',
views: 1380
}
])
const handleTabChange = (tabName: string) => {
currentPage.value = 1
console.log('切换到标签页:', tabName)
}
const handlePageChange = (page: number) => {
console.log('切换到第', page, '页')
}
const goToDetail = (id: string) => {
router.push(`/news-policy/detail/${id}`)
}
</script>
<style scoped>
.page-container {
min-height: 100vh;
background: #f5f7fa;
color: #303133;
padding: 80px 0 0;
}
.main-content {
max-width: 1400px;
margin: 0 auto;
padding: 0 20px 60px;
}
.page-header {
text-align: center;
margin-bottom: 30px;
}
.page-header h1 {
font-size: 48px;
color: #409eff;
margin-bottom: 16px;
font-weight: 600;
}
.page-header p {
font-size: 18px;
color: #606266;
}
.tabs-container {
background: #ffffff;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
margin-top: 8px;
}
.news-tabs {
--el-tabs-header-height: 50px;
}
.news-tabs :deep(.el-tabs__header) {
margin-bottom: 20px;
}
.news-tabs :deep(.el-tabs__nav-wrap::after) {
height: 1px;
background-color: #e4e7ed;
}
.news-tabs :deep(.el-tabs__item) {
font-size: 16px;
font-weight: 500;
padding: 0 30px;
}
.news-tabs :deep(.el-tabs__item.is-active) {
color: #409eff;
font-weight: 600;
}
.news-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
padding-top: 8px;
}
.news-card {
background: #ffffff;
border: 1px solid #e4e7ed;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.news-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
border-color: #409eff;
}
.news-image {
position: relative;
height: 120px;
background: linear-gradient(135deg, #e3f2fd, #bbdefb);
display: flex;
align-items: center;
justify-content: center;
}
.news-placeholder {
font-size: 24px;
font-weight: bold;
color: #409eff;
letter-spacing: 2px;
}
.news-tag {
position: absolute;
top: 8px;
right: 8px;
}
.news-content {
padding: 20px;
}
.news-content h3 {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.news-content p {
font-size: 14px;
color: #606266;
line-height: 1.5;
margin-bottom: 16px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.news-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #909399;
}
.date {
color: #909399;
}
.views {
color: #c0c4cc;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 30px;
}
.pagination-container :deep(.el-pagination) {
--el-pagination-font-size: 14px;
--el-pagination-bg-color: #ffffff;
--el-pagination-text-color: #606266;
--el-pagination-border-radius: 8px;
}
.pagination-container :deep(.el-pager li) {
background-color: #ffffff;
border: 1px solid #e4e7ed;
margin: 0 4px;
border-radius: 6px;
}
.pagination-container :deep(.el-pager li.is-active) {
background-color: #409eff;
border-color: #409eff;
color: #ffffff;
}
@media (max-width: 1024px) {
.news-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.news-grid {
grid-template-columns: 1fr;
}
.main-content {
padding: 0 16px 40px;
}
.page-container {
padding: 70px 0 0;
}
}
</style>

View File

@ -0,0 +1,276 @@
<template>
<div class="page-container">
<PageNavigation />
<div class="detail-container">
<div class="breadcrumb">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<router-link to="/">首页</router-link>
</el-breadcrumb-item>
<el-breadcrumb-item>
<router-link to="/news-policy">新闻政策</router-link>
</el-breadcrumb-item>
<el-breadcrumb-item>政策详情</el-breadcrumb-item>
</el-breadcrumb>
</div>
<el-card class="detail-card" shadow="never">
<template #header>
<div class="detail-header">
<h1>{{ policyDetail.title }}</h1>
<div class="meta-info">
<el-tag :type="policyDetail.tagType" size="small">{{ policyDetail.tag }}</el-tag>
<span class="publish-date">发布时间{{ policyDetail.publishDate }}</span>
</div>
</div>
</template>
<div class="detail-content">
<div class="content-section">
<h2>政策概述</h2>
<p>{{ policyDetail.summary }}</p>
</div>
<div class="content-section">
<h2>主要内容</h2>
<div v-html="policyDetail.content"></div>
</div>
<div class="content-section">
<h2>适用范围</h2>
<ul>
<li v-for="scope in policyDetail.scopes" :key="scope">{{ scope }}</li>
</ul>
</div>
<div class="content-section">
<h2>申请流程</h2>
<el-steps :active="policyDetail.steps.length" finish-status="success">
<el-step
v-for="(step, index) in policyDetail.steps"
:key="index"
:title="step.title"
:description="step.description"
/>
</el-steps>
</div>
</div>
<div class="detail-actions">
<el-button type="primary" size="large" @click="downloadPolicy">
<el-icon><Download /></el-icon>
下载政策文件
</el-button>
<el-button size="large" @click="goBack">返回列表</el-button>
</div>
</el-card>
</div>
<!-- 页脚 -->
<PageFooter />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import PageNavigation from '@/components/PageNavigation.vue'
import PageFooter from '@/components/PageFooter.vue'
const route = useRoute()
const router = useRouter()
interface PolicyDetail {
id: string
title: string
tag: string
tagType: string
publishDate: string
summary: string
content: string
scopes: string[]
steps: Array<{
title: string
description: string
}>
}
const policyDetail = ref<PolicyDetail>({
id: '',
title: '',
tag: '',
tagType: 'info',
publishDate: '',
summary: '',
content: '',
scopes: [],
steps: []
})
const loadPolicyDetail = () => {
const id = route.params.id as string
// API
const mockData: Record<string, PolicyDetail> = {
'policy-1': {
id: 'policy-1',
title: '科技创新支持政策发布',
tag: '最新',
tagType: 'success',
publishDate: '2024-01-15',
summary: '为进一步推动科技创新发展,相关部门发布了一系列支持政策,涵盖资金支持、税收优惠、人才引进等多个方面。',
content: `
<p>本政策旨在通过多维度的支持措施激发企业创新活力提升科技创新能力</p>
<h3>资金支持</h3>
<p>设立专项资金对符合条件的科技创新项目给予资金支持最高可达500万元</p>
<h3>税收优惠</h3>
<p>享受研发费用加计扣除高新技术企业所得税优惠等政策</p>
<h3>人才政策</h3>
<p>对引进的高层次人才给予住房补贴子女教育等配套支持</p>
`,
scopes: [
'高新技术企业',
'科技型中小企业',
'新型研发机构',
'科技服务机构'
],
steps: [
{ title: '申请准备', description: '准备相关材料和证明文件' },
{ title: '在线申报', description: '通过官方平台提交申请' },
{ title: '专家评审', description: '组织专家进行项目评审' },
{ title: '公示公告', description: '评审结果公示' },
{ title: '资金拨付', description: '审核通过后拨付资金' }
]
},
'policy-2': {
id: 'policy-2',
title: '数字化转型指导意见',
tag: '热门',
tagType: 'info',
publishDate: '2024-01-12',
summary: '加快推进企业数字化转型,提升科技创新能力,构建数字经济新优势。',
content: `
<p>数字化转型是企业适应数字经济发展的必然选择</p>
<h3>转型目标</h3>
<p>到2025年重点行业数字化转型取得明显成效</p>
<h3>支持措施</h3>
<p>提供技术指导资金支持人才培训等全方位服务</p>
`,
scopes: [
'制造业企业',
'服务业企业',
'传统行业企业'
],
steps: [
{ title: '现状评估', description: '评估企业数字化现状' },
{ title: '方案制定', description: '制定数字化转型方案' },
{ title: '试点实施', description: '选择重点领域试点' },
{ title: '全面推广', description: '在全企业范围推广' }
]
}
}
policyDetail.value = mockData[id] || mockData['policy-1']
}
const downloadPolicy = () => {
ElMessage.success('政策文件下载中...')
}
const goBack = () => {
router.push('/news-policy')
}
onMounted(() => {
loadPolicyDetail()
})
</script>
<style scoped>
.page-container {
min-height: 100vh;
background: #f5f7fa;
color: #303133;
padding: 80px 20px 0;
}
.detail-container {
max-width: 1200px;
margin: 0 auto;
}
.breadcrumb {
margin-bottom: 24px;
}
.detail-card {
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.detail-header h1 {
font-size: 32px;
color: #303133;
margin-bottom: 16px;
font-weight: 600;
}
.meta-info {
display: flex;
align-items: center;
gap: 16px;
}
.publish-date {
color: #909399;
font-size: 14px;
}
.detail-content {
line-height: 1.8;
}
.content-section {
margin-bottom: 32px;
}
.content-section h2 {
color: #409eff;
font-size: 24px;
margin-bottom: 16px;
font-weight: 600;
border-left: 4px solid #409eff;
padding-left: 12px;
}
.content-section h3 {
color: #606266;
font-size: 18px;
margin: 16px 0 8px 0;
font-weight: 600;
}
.content-section p {
color: #606266;
margin-bottom: 12px;
}
.content-section ul {
padding-left: 20px;
}
.content-section li {
color: #606266;
margin-bottom: 8px;
}
.detail-actions {
display: flex;
gap: 16px;
justify-content: center;
padding-top: 24px;
border-top: 1px solid #e4e7ed;
}
</style>

View File

@ -0,0 +1,287 @@
<template>
<div class="page-container">
<PageNavigation />
<div class="qa-interface">
<div class="chat-container">
<div class="messages" ref="messagesContainer">
<div v-for="message in messages" :key="message.id"
:class="['message', message.type]">
<div class="message-content">
<div class="avatar">
<span v-if="message.type === 'user'">👤</span>
<span v-else>🤖</span>
</div>
<div class="text">{{ message.text }}</div>
</div>
</div>
</div>
<div class="input-area">
<div class="input-container">
<el-input
v-model="currentQuestion"
@keyup.enter="sendMessage"
placeholder="请输入您的问题..."
size="large"
clearable
/>
<el-button
@click="sendMessage"
type="primary"
size="large"
:loading="isLoading"
:icon="isLoading ? '' : 'ChatDotRound'"
>
{{ isLoading ? '发送中...' : '发送' }}
</el-button>
</div>
</div>
</div>
<div class="quick-questions">
<h3>常见问题</h3>
<div class="question-tags">
<button
v-for="question in quickQuestions"
:key="question"
@click="selectQuickQuestion(question)"
class="question-tag"
>
{{ question }}
</button>
</div>
</div>
</div>
<!-- 页脚 -->
<PageFooter />
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import PageNavigation from '@/components/PageNavigation.vue'
import PageFooter from '@/components/PageFooter.vue'
interface Message {
id: number
type: 'user' | 'ai'
text: string
}
const currentQuestion = ref('')
const messages = ref<Message[]>([
{
id: 1,
type: 'ai',
text: '您好!我是智慧问答助手,有什么可以帮助您的吗?'
}
])
const quickQuestions = ref([
'如何申请高新技术企业认定?',
'科技成果转化有哪些支持政策?',
'人才引进政策有哪些?',
'科技项目申报流程是什么?',
'知识产权保护相关政策',
'创新创业扶持措施'
])
const messagesContainer = ref<HTMLElement>()
const isLoading = ref(false)
const sendMessage = async () => {
if (!currentQuestion.value.trim()) return
const userMessage: Message = {
id: Date.now(),
type: 'user',
text: currentQuestion.value
}
messages.value.push(userMessage)
const question = currentQuestion.value
currentQuestion.value = ''
isLoading.value = true
scrollToBottom()
// AI
setTimeout(() => {
const aiResponse: Message = {
id: Date.now() + 1,
type: 'ai',
text: getAIResponse(question)
}
messages.value.push(aiResponse)
isLoading.value = false
scrollToBottom()
}, 1500)
}
const selectQuickQuestion = (question: string) => {
currentQuestion.value = question
sendMessage()
}
const getAIResponse = (question: string): string => {
//
if (question.includes('高新技术企业')) {
return '高新技术企业认定需要满足以下条件1. 企业申请认定时须注册成立一年以上2. 企业通过自主研发、受让、受赠、并购等方式,获得对其主要产品(服务)在技术上发挥核心支持作用的知识产权的所有权...'
} else if (question.includes('科技成果转化')) {
return '科技成果转化支持政策包括1. 财政资金支持2. 税收优惠政策3. 金融支持措施4. 人才激励政策等。具体可以申请科技成果转化引导基金、享受技术转让所得税优惠等。'
} else if (question.includes('人才引进')) {
return '人才引进政策主要包括1. 高层次人才引进计划2. 住房补贴和安家费3. 子女教育优待4. 医疗保障5. 科研启动资金支持等。不同层次人才享受不同标准的政策支持。'
}
return '感谢您的提问!这是一个很好的问题。建议您联系相关部门获取更详细的信息,或者查阅最新的政策文件。如果您有其他问题,我很乐意为您解答。'
}
const scrollToBottom = () => {
nextTick(() => {
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
}
})
}
</script>
<style scoped>
.page-container {
min-height: 100vh;
background: #f5f7fa;
color: #303133;
padding: 80px 20px 0;
}
.page-header {
text-align: center;
margin-bottom: 30px;
}
.page-header h1 {
font-size: 48px;
color: #409eff;
margin-bottom: 16px;
font-weight: 600;
}
.page-header p {
font-size: 18px;
color: #606266;
}
.qa-interface {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 40px;
max-width: 1400px;
margin: 40px auto 0;
height: 600px;
}
.chat-container {
background: #ffffff;
border: 1px solid #e4e7ed;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
.messages {
flex: 1;
padding: 20px;
overflow-y: auto;
max-height: 480px;
}
.message {
margin-bottom: 20px;
}
.message-content {
display: flex;
align-items: flex-start;
gap: 12px;
}
.message.user .message-content {
flex-direction: row-reverse;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #e4e7ed;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.text {
background: #f0f2f5;
padding: 12px 16px;
border-radius: 12px;
max-width: 70%;
line-height: 1.5;
color: #303133;
}
.message.user .text {
background: #409eff;
color: #ffffff;
}
.input-area {
padding: 20px;
border-top: 1px solid #e4e7ed;
}
.input-container {
display: flex;
gap: 12px;
}
.quick-questions {
background: #ffffff;
border: 1px solid #e4e7ed;
border-radius: 16px;
padding: 30px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.quick-questions h3 {
color: #409eff;
font-size: 24px;
margin-bottom: 20px;
text-align: center;
font-weight: 600;
}
.question-tags {
display: flex;
flex-direction: column;
gap: 12px;
}
.question-tag {
padding: 12px 16px;
background: #f0f2f5;
border: 1px solid #e4e7ed;
border-radius: 8px;
color: #303133;
cursor: pointer;
transition: all 0.3s ease;
text-align: left;
font-size: 14px;
}
.question-tag:hover {
border-color: #409eff;
background: rgba(64, 158, 255, 0.1);
transform: translateX(4px);
}
</style>

View File

@ -0,0 +1,369 @@
<template>
<div class="page-container">
<PageNavigation />
<div class="talent-layout">
<!-- 左侧人物列表 -->
<div class="talent-sidebar">
<div class="sidebar-header"><h3>人物名单</h3></div>
<div class="talent-list">
<div
v-for="person in talentList"
:key="person.id"
:class="['talent-item', { active: selectedTalent?.id === person.id }]"
@click="selectTalent(person)"
>
<div class="talent-name-main">{{ person.name }}</div>
<div class="talent-title-small">{{ person.title }}</div>
</div>
</div>
</div>
<!-- 中间个人详情 -->
<div class="talent-detail">
<div v-if="selectedTalent" class="detail-content">
<!-- 基本信息 -->
<div class="basic-info-section">
<div class="avatar-section">
<div class="detail-avatar">
<img src="http://cdn.rayrayray.cn/avatar.png" :alt="selectedTalent.name" />
</div>
</div>
<div class="info-grid">
<div class="info-row">
<div class="info-item"><span class="label">姓名</span><span class="value">{{ selectedTalent.name }}</span></div>
<div class="info-item"><span class="label">地区</span><span class="value">{{ selectedTalent.location }}</span></div>
</div>
<div class="info-row">
<div class="info-item"><span class="label">性别</span><span class="value">{{ selectedTalent.gender }}</span></div>
<div class="info-item"><span class="label">研究领域</span><span class="value">{{ selectedTalent.field }}</span></div>
</div>
<div class="info-row">
<div class="info-item"><span class="label">年龄</span><span class="value">{{ selectedTalent.age }}</span></div>
</div>
</div>
</div>
<!-- 个人介绍 -->
<div class="biography-section">
<h3>个人介绍</h3>
<div class="biography-content">{{ selectedTalent.biography }}</div>
</div>
<!-- 人脉关系网 -->
<div class="network-section">
<div class="network-header">
<h3>人脉关系网</h3>
<div class="legend">
<div class="legend-item"><div class="legend-line colleague"></div><span>合作关系</span></div>
<div class="legend-item"><div class="legend-line friend"></div><span>朋友关系</span></div>
<div class="legend-item"><div class="legend-line mentor"></div><span>师生关系</span></div>
</div>
</div>
<div id="network-graph" class="network-container"></div>
<!-- Popover -->
<el-popover
v-if="selectedNode"
:visible="!!selectedNode"
placement="right"
:width="220"
trigger="manual"
:virtual-ref="popoverTarget"
virtual-triggering
@hide="selectedNode = null"
>
<template #default>
<div class="popover-content">
<div class="popover-header"><h4>详细介绍</h4></div>
<div class="popover-body">
<div class="popover-item"><span class="popover-label">姓名</span><span class="popover-value">{{ selectedNode.name }}</span></div>
<div class="popover-item"><span class="popover-label">关系类型</span><span class="popover-value">{{ getRelationText(selectedNode.relation) }}</span></div>
<div class="popover-item"><span class="popover-label">性别</span><span class="popover-value">{{ selectedNode.gender || '男' }}</span></div>
<div class="popover-item"><span class="popover-label">年龄</span><span class="popover-value">{{ selectedNode.age || '35' }}</span></div>
<div class="popover-item"><span class="popover-label">工作单位</span><span class="popover-value">{{ selectedNode.workplace || '某某公司' }}</span></div>
<div class="popover-item"><span class="popover-label">职务</span><span class="popover-value">{{ selectedNode.position || '高级工程师' }}</span></div>
<div class="popover-item"><span class="popover-label">联系方式</span><span class="popover-value">{{ selectedNode.contact || '***-****-****' }}</span></div>
</div>
</div>
</template>
</el-popover>
<div class="network-footer">
<p>该关系网基于公开信息生成仅供参考</p>
</div>
</div>
</div>
<div v-else class="no-selection">
<el-empty description="请从左侧选择一个人才查看详情" />
</div>
</div>
</div>
<PageFooter />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
import * as d3 from 'd3'
import PageNavigation from '@/components/PageNavigation.vue'
import PageFooter from '@/components/PageFooter.vue'
interface TalentPerson {
id: number
name: string
title: string
field: string
gender: string
age: number
location: string
avatar?: string
biography: string
connections: Array<{
id: number
name: string
relation: string
strength: number
gender?: string
age?: number
workplace?: string
position?: string
contact?: string
}>
}
interface NetworkNode {
id: number
name: string
type: 'center' | 'connection'
relation?: string
strength?: number
gender?: string
age?: number
workplace?: string
position?: string
contact?: string
x?: number
y?: number
}
const selectedTalent = ref<TalentPerson | null>(null)
const selectedNode = ref<NetworkNode | null>(null)
const popoverTarget = ref<HTMLElement | null>(null)
const talentList = ref<TalentPerson[]>([
{
id: 1,
name: '周小然',
title: '高级工程师',
field: '人工智能',
gender: '男',
age: 28,
location: '吉林省长春市长春理工大学',
biography: '1994年毕业于中国人民解放军国防科技大学1960年毕业于中国人民解放军国防科技大学获得11月3日中国科学院1961年5月获得计算机硕士学位现任为产品经理部副经理请务必为产品经理部副经理工作上提供支持。1962年2月19日开始从事软件开发工作先后22年获得软件开发证书等多项证书请务必为产品经理部副经理工作上提供支持。1965年至今少年时期在中国人民解放军国防科技大学后进入中国科学院计算技术研究所先后在多个科研院所工作现任某某公司产品经理部副经理。1951年7月8日少年时期在中国人民解放军国防科技大学后进入中国科学院计算技术研究所先后在多个科研院所工作现任某某公司产品经理部副经理。1949年教育背景为中国人民解放军国防科技大学计算机系后进入中国科学院计算技术研究所先后在多个科研院所工作现任某某公司产品经理部副经理。1951年在某某公司担任产品经理部副经理负责产品规划和团队管理工作。10月在某某公司担任产品经理部副经理负责产品规划和团队管理工作。1951年11月在某某公司担任产品经理部副经理负责产品规划和团队管理工作。',
connections: [
{ id: 2, name: '王小轩', relation: 'colleague', strength: 0.8 },
{ id: 3, name: '程小龙', relation: 'friend', strength: 0.6 },
{ id: 4, name: '高小睿', relation: 'colleague', strength: 0.7 },
{ id: 5, name: '张小祖', relation: 'mentor', strength: 0.9 },
{ id: 6, name: '张小君', relation: 'colleague', strength: 0.5 }
]
},
{ id: 2, name: '王小轩', title: '高级工程师', field: '人工智能', gender: '男', age: 32, location: '北京市海淀区', biography: '……', connections: [{ id: 1, name: '周小强', relation: 'colleague', strength: 0.8 }] },
{ id: 3, name: '程小龙', title: '高级工程师', field: '生物技术', gender: '男', age: 29, location: '上海市浦东新区', biography: '……', connections: [{ id: 1, name: '周小强', relation: 'friend', strength: 0.6 }] },
{ id: 4, name: '高小睿', title: '高级工程师', field: '计算机科学', gender: '男', age: 35, location: '深圳市南山区', biography: '……', connections: [{ id: 1, name: '周小强', relation: 'colleague', strength: 0.7 }] },
{ id: 5, name: '张小祖', title: '高级工程师', field: '人工智能', gender: '男', age: 55, location: '北京市朝阳区', biography: '……', connections: [{ id: 1, name: '周小强', relation: 'mentor', strength: 0.9 }] },
{ id: 6, name: '张小君', title: '高级工程师', field: '软件工程', gender: '女', age: 26, location: '杭州市西湖区', biography: '……', connections: [{ id: 1, name: '周小强', relation: 'colleague', strength: 0.5 }] }
])
const selectTalent = (talent: TalentPerson) => {
selectedTalent.value = talent
selectedNode.value = null
nextTick(renderNetworkGraph)
}
const getRelationText = (relation: string) => {
const map: Record<string, string> = { colleague: '合作关系', friend: '朋友关系', mentor: '师生关系' }
return map[relation] || relation
}
const getRelationColor = (relation?: string) => ({ colleague: '#1890ff', friend: '#52c41a', mentor: '#fa8c16' }[relation || 'colleague']!)
const renderNetworkGraph = () => {
if (!selectedTalent.value) return
d3.select('#network-graph').selectAll('*').remove()
const width = 800, height = 500
const svg = d3.select('#network-graph').append('svg').attr('width', width).attr('height', height).attr('viewBox', `0 0 ${width} ${height}`)
const center = { id: selectedTalent.value.id, name: selectedTalent.value.name, type: 'center' as const, x: width / 2, y: height / 2 }
const conns = selectedTalent.value.connections.map((c, i) => {
const angle = (i * 2 * Math.PI) / selectedTalent.value?.connections.length
const r = 180
return { id: c.id, name: c.name, type: 'connection' as const, relation: c.relation, strength: c.strength, x: width / 2 + Math.cos(angle) * r, y: height / 2 + Math.sin(angle) * r }
})
const nodes = [center, ...conns]
const links = selectedTalent.value.connections.map((c, i) => ({ source: center, target: conns[i], relation: c.relation }))
// 线
const linkGroup = svg.append('g').selectAll('g').data(links as any).enter().append('g')
linkGroup.each(function(d: any) {
const group = d3.select(this)
const x1 = d.source.x, y1 = d.source.y
const x2 = d.target.x, y2 = d.target.y
// 线
const dx = x2 - x1
const dy = y2 - y1
const length = Math.sqrt(dx * dx + dy * dy)
const angle = Math.atan2(dy, dx)
//
const triangleCount = Math.floor(length / 15) // 15
//
for (let i = 0; i < triangleCount; i++) {
const progress = (i + 0.5) / triangleCount
const x = x1 + dx * progress
const y = y1 + dy * progress
//
const size = 4
//
const trianglePath = `M ${-size} ${-size/2} L ${size} 0 L ${-size} ${size/2} Z`
group.append('path')
.attr('d', trianglePath)
.attr('transform', `translate(${x}, ${y}) rotate(${angle * 180 / Math.PI})`)
.attr('fill', getRelationColor(d.relation))
.attr('opacity', 0.8)
}
})
const nodeGroup = svg.append('g').selectAll('g').data(nodes).enter().append('g')
.attr('class', 'node-group').attr('transform', (d: any) => `translate(${d.x},${d.y})`)
.style('cursor', 'pointer')
.on('click', (event, d: any) => {
if (d.type !== 'center') { popoverTarget.value = event.currentTarget as HTMLElement; selectedNode.value = { ...d } }
})
//
const rContent = (d: any) => d.type === 'center' ? 45 : 30
const bandH = (d: any) => d.type === 'center' ? 22 : 18 // 1/4~1/3
const fontSize = (d: any) => d.type === 'center' ? 13 : 11
//
//
//
nodeGroup.append('image')
.attr('href', 'http://cdn.rayrayray.cn/avatar.png')
.attr('x', (d: any) => -rContent(d))
.attr('y', (d: any) => -rContent(d))
.attr('width', (d: any) => rContent(d) * 2)
.attr('height', (d: any) => rContent(d) * 2)
.attr('clip-path', (d: any) => `url(#clip-avatar-${d.id})`)
//
const avatarDefs = nodeGroup.append('defs')
avatarDefs.append('clipPath')
.attr('id', (d: any) => `clip-avatar-${d.id}`)
.append('circle')
.attr('r', (d: any) => rContent(d))
.attr('cx', 0).attr('cy', 0)
// === clipPath ===
const defs = nodeGroup.append('defs')
defs.append('clipPath')
.attr('id', (d: any) => `clip-${d.id}`)
.append('circle')
.attr('r', (d: any) => rContent(d))
.attr('cx', 0).attr('cy', 0)
// => overflow hidden
const labelWrap = nodeGroup.append('g').attr('clip-path', (d: any) => `url(#clip-${d.id})`)
labelWrap.append('rect')
.attr('width', (d: any) => 2 * rContent(d))
.attr('height', (d: any) => bandH(d))
.attr('x', (d: unknown) => -rContent(d))
.attr('y', (d: unknown) => rContent(d) - bandH(d))
.attr('fill', '#023d5e')
.attr('opacity', 0.98)
// 线
nodeGroup.append('text')
.text((d: unknown) => d.name)
.attr('font-size', (d: unknown) => `${fontSize(d)}px`)
.attr('text-anchor', 'middle')
.attr('dy', (d: unknown) => rContent(d) - bandH(d) / 2 + (d.type === 'center' ? 4 : 3))
.attr('fill', '#fff')
.attr('font-weight', 'bold')
.style('paint-order', 'stroke')
.style('stroke', 'rgba(0,0,0,0.22)')
.style('stroke-width', '0.6px')
}
onMounted(() => {
if (talentList.value.length > 0) selectTalent(talentList.value[0])
document.addEventListener('click', (e) => {
const target = e.target as HTMLElement
if (selectedNode.value && !target.closest('.el-popover') && !target.closest('.node-group')) selectedNode.value = null
})
})
</script>
<style scoped>
.page-container{min-height:100vh;background:#f5f7fa;color:#303133;padding:80px 0 0;}
.talent-layout{display:flex;max-width:1400px;margin:0 auto;gap:20px;padding:20px 40px;margin-top:20px;}
.talent-sidebar{width:240px;background:#fff;border-radius:8px;box-shadow:0 2px 8px rgba(0,0,0,.1);height:fit-content;}
.sidebar-header{padding:16px;border-bottom:1px solid #e4e7ed;}
.sidebar-header h3{margin:0;font-size:16px;color:#303133;font-weight:600;}
.talent-list{padding:8px 0;}
.talent-item{padding:12px 16px;cursor:pointer;transition:all .2s;border-left:3px solid transparent;}
.talent-item:hover{background:#f5f7fa;}
.talent-item.active{background:#ecf5ff;border-left-color:#409eff;}
.talent-name-main{font-size:14px;color:#303133;font-weight:600;margin-bottom:4px;}
.talent-title-small{font-size:12px;color:#909399;}
.talent-detail{flex:1;background:#fff;border-radius:8px;box-shadow:0 2px 8px rgba(0,0,0,.1);padding:24px;}
.detail-content{display:flex;flex-direction:column;gap:24px;}
.basic-info-section{display:flex;gap:24px;padding-bottom:24px;border-bottom:1px solid #e4e7ed;}
.detail-avatar{width:120px;height:120px;border-radius:8px;overflow:hidden;}
.detail-avatar img{width:100%;height:100%;object-fit:cover;}
.avatar-placeholder-large{width:100%;height:100%;background:#f0f0f0;display:flex;align-items:center;justify-content:center;font-size:48px;color:#c0c4cc;}
.info-grid{flex:1;display:flex;flex-direction:column;gap:16px;}
.info-row{display:flex;gap:40px;}
.info-item{display:flex;align-items:center;gap:8px;min-width:200px;}
.info-item .label{font-size:14px;color:#606266;min-width:80px;}
.info-item .value{font-size:14px;color:#303133;font-weight:500;}
.biography-section{padding-bottom:24px;border-bottom:1px solid #e4e7ed;}
.biography-section h3{margin:0 0 16px;font-size:16px;color:#303133;font-weight:600;}
.biography-content{font-size:14px;line-height:1.6;color:#606266;text-align:justify;}
.network-section{display:flex;flex-direction:column;gap:16px;}
.network-header{display:flex;justify-content:space-between;align-items:center;}
.network-header h3{margin:0;font-size:16px;color:#303133;font-weight:600;}
.legend{display:flex;gap:20px;}
.legend-item{display:flex;align-items:center;gap:8px;font-size:12px;color:#606266;}
.legend-line{width:20px;height:2px;}
.legend-line.colleague{background:#1890ff;}
.legend-line.friend{background:#52c41a;background-image:repeating-linear-gradient(90deg,#52c41a,#52c41a 6px,transparent 6px,transparent 9px);}
.legend-line.mentor{background:#fa8c16;background-image:repeating-linear-gradient(90deg,#fa8c16,#fa8c16 9px,transparent 9px,transparent 13px);}
.network-container{width:100%;height:500px;background:#fafafa;position:relative;overflow:visible;}
.network-footer{text-align:center;}
.network-footer p{margin:0;font-size:12px;color:#909399;}
.popover-content{padding:0;}
.popover-header{padding:12px 16px;border-bottom:1px solid #e4e7ed;background:#f8f9fa;margin:-12px -12px 0 -12px;}
.popover-header h4{margin:0;font-size:14px;color:#303133;font-weight:600;}
.popover-body{padding:12px 0;}
.popover-item{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid #f0f0f0;}
.popover-item:last-child{border-bottom:none;}
.popover-label{font-size:12px;color:#606266;min-width:60px;}
.popover-value{font-size:12px;color:#303133;font-weight:500;text-align:right;flex:1;}
.no-selection{display:flex;align-items:center;justify-content:center;height:400px;}
:deep(.node-group){transition:all .2s ease;}
:deep(.node-group:hover circle){stroke-width:4px;}
</style>

View File

@ -0,0 +1,424 @@
<template>
<div class="page-container">
<PageNavigation />
<div class="tech-resources-layout">
<!-- 左侧分类导航 -->
<div class="sidebar">
<div class="sidebar-header">
<h3>科技资源</h3>
</div>
<el-menu
:default-active="activeCategory"
class="category-menu"
@select="handleCategorySelect"
>
<el-sub-menu index="papers">
<template #title>
<el-icon><Document /></el-icon>
<span>科技论文</span>
</template>
<el-menu-item index="papers-all">科技资讯</el-menu-item>
<el-menu-item index="papers-ai">科技专利</el-menu-item>
<el-menu-item index="papers-bio">科技政策</el-menu-item>
<el-menu-item index="papers-cs">科技学者</el-menu-item>
<el-menu-item index="papers-physics">科技院校</el-menu-item>
</el-sub-menu>
</el-menu>
</div>
<!-- 右侧内容区域 -->
<div class="content-area">
<div class="content-header">
<h2>科技论文</h2>
<div class="search-controls">
<el-input
v-model="searchKeyword"
placeholder="搜索"
class="search-input"
clearable
>
<template #suffix>
<el-icon class="search-icon"><Search /></el-icon>
</template>
</el-input>
<el-button type="primary">搜索</el-button>
<el-button type="primary">导出</el-button>
<el-button type="primary">批量下载</el-button>
</div>
</div>
<!-- 论文列表 -->
<div class="papers-table">
<el-table
:data="papersList"
style="width: 100%"
:header-cell-style="{ background: '#f8f9fa', color: '#606266' }"
>
<el-table-column prop="title" label="题名" min-width="300" />
<el-table-column prop="author" label="作者" width="150" />
<el-table-column prop="journal" label="来源" width="120" />
<el-table-column prop="date" label="发表时间" width="120" />
<el-table-column prop="cited" label="被引" width="80" />
<el-table-column prop="downloads" label="下载次数" width="100" />
<el-table-column label="操作" width="80">
<template #default="scope">
<el-button
type="primary"
link
@click="handleView(scope.row)"
>
查看
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="pagination-wrapper">
<div class="pagination-info">
{{ total }} 条记录
</div>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
<PageFooter />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Document, Search } from '@element-plus/icons-vue'
import PageNavigation from '@/components/PageNavigation.vue'
import PageFooter from '@/components/PageFooter.vue'
interface Paper {
id: number
title: string
author: string
journal: string
date: string
cited: number
downloads: number
}
const activeCategory = ref('papers-all')
const searchKeyword = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(126)
const papersList = ref<Paper[]>([
{
id: 1,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 2,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 3,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 4,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 5,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 6,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 7,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 8,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 9,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
},
{
id: 10,
title: '基于深度学习的医疗诊断技术',
author: '王小明, 黄小红',
journal: '计算机工程',
date: '2024/03/03',
cited: 1587,
downloads: 1579
}
])
const handleCategorySelect = (key: string) => {
activeCategory.value = key
//
console.log('Selected category:', key)
}
const handleView = (row: Paper) => {
console.log('View paper:', row)
//
}
const handleSizeChange = (val: number) => {
pageSize.value = val
//
}
const handleCurrentChange = (val: number) => {
currentPage.value = val
//
}
onMounted(() => {
//
})
</script>
<style scoped>
.page-container {
min-height: 100vh;
background: #f5f7fa;
padding: 80px 0 0;
}
.tech-resources-layout {
display: flex;
max-width: 1400px;
margin: 0 auto;
gap: 20px;
padding: 20px 40px;
margin-top: 20px;
}
/* 左侧边栏 */
.sidebar {
width: 240px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
height: fit-content;
}
.sidebar-header {
padding: 16px;
border-bottom: 1px solid #e4e7ed;
}
.sidebar-header h3 {
margin: 0;
font-size: 16px;
color: #303133;
font-weight: 600;
}
.category-menu {
border: none;
}
.category-menu .el-menu-item {
height: 40px;
line-height: 40px;
font-size: 14px;
}
.category-menu .el-sub-menu__title {
height: 48px;
line-height: 48px;
font-size: 14px;
font-weight: 600;
}
/* 右侧内容区域 */
.content-area {
flex: 1;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 24px;
}
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #e4e7ed;
}
.content-header h2 {
margin: 0;
font-size: 20px;
color: #303133;
font-weight: 600;
}
.search-controls {
display: flex;
gap: 12px;
align-items: center;
}
.search-input {
width: 200px;
}
.search-icon {
color: #909399;
}
/* 表格样式 */
.papers-table {
margin-bottom: 24px;
}
:deep(.el-table) {
border-radius: 8px;
overflow: hidden;
}
:deep(.el-table th) {
background: #f8f9fa !important;
}
:deep(.el-table td) {
border-bottom: 1px solid #f0f0f0;
}
:deep(.el-table tr:hover > td) {
background-color: #f8f9fa !important;
}
/* 分页样式 */
.pagination-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 16px;
border-top: 1px solid #e4e7ed;
}
.pagination-info {
font-size: 14px;
color: #606266;
}
:deep(.el-pagination) {
--el-pagination-font-size: 14px;
}
:deep(.el-pagination .btn-prev),
:deep(.el-pagination .btn-next) {
background: #f5f7fa;
border: 1px solid #dcdfe6;
}
:deep(.el-pagination .btn-prev:hover),
:deep(.el-pagination .btn-next:hover) {
color: #409eff;
}
:deep(.el-pager li.is-active) {
background: #409eff;
color: #fff;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.tech-resources-layout {
flex-direction: column;
padding: 20px;
}
.sidebar {
width: 100%;
}
.content-header {
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
.search-controls {
width: 100%;
justify-content: flex-end;
}
}
@media (max-width: 768px) {
.pagination-wrapper {
flex-direction: column;
gap: 16px;
}
.search-input {
width: 150px;
}
}
</style>

View File

@ -0,0 +1,314 @@
<template>
<div class="login-container">
<!-- 背景装饰 -->
<div class="background-decoration">
<div class="decoration-circle circle-1"></div>
<div class="decoration-circle circle-2"></div>
<div class="decoration-circle circle-3"></div>
<div class="decoration-lines">
<div class="line line-1"></div>
<div class="line line-2"></div>
<div class="line line-3"></div>
</div>
</div>
<!-- 登录表单 -->
<div class="login-form-container">
<div class="login-card">
<div class="login-header">
<h1>科技大脑·平台登录</h1>
</div>
<el-form :model="loginForm" class="login-form" size="large">
<el-form-item>
<el-input
v-model="loginForm.username"
placeholder="请输入用户名"
class="login-input"
>
<template #prefix>
<el-icon><User /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
class="login-input"
show-password
>
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button
@click="handleLogin"
type="primary"
class="login-button"
:loading="isLoading"
>
登录
</el-button>
</el-form-item>
<div class="login-footer">
<span class="footer-text">没有账号请联系管理员开通账号</span>
</div>
</el-form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { User, Lock } from '@element-plus/icons-vue'
const router = useRouter()
const loginForm = reactive({
username: '',
password: ''
})
const isLoading = ref(false)
const handleLogin = async () => {
if (!loginForm.username || !loginForm.password) {
ElMessage.warning('请填写完整的登录信息')
return
}
isLoading.value = true
setTimeout(() => {
isLoading.value = false
ElMessage.success('登录成功!')
router.push('/')
}, 1500)
}
</script>
<style scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 50%, #1a365d 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
/* 背景装饰 */
.background-decoration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.decoration-circle {
position: absolute;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
}
.circle-1 {
width: 200px;
height: 200px;
top: 10%;
left: 10%;
animation: float 6s ease-in-out infinite;
}
.circle-2 {
width: 150px;
height: 150px;
top: 60%;
right: 15%;
animation: float 8s ease-in-out infinite reverse;
}
.circle-3 {
width: 100px;
height: 100px;
bottom: 20%;
left: 20%;
animation: float 10s ease-in-out infinite;
}
.decoration-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.line {
position: absolute;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
height: 1px;
}
.line-1 {
width: 300px;
top: 25%;
left: 5%;
transform: rotate(15deg);
}
.line-2 {
width: 200px;
top: 70%;
right: 10%;
transform: rotate(-20deg);
}
.line-3 {
width: 250px;
bottom: 15%;
left: 15%;
transform: rotate(10deg);
}
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-20px);
}
}
/* 登录表单容器 */
.login-form-container {
z-index: 10;
position: relative;
}
.login-card {
background: rgba(45, 82, 130, 0.9);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
padding: 48px 40px;
width: 400px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
.login-header {
text-align: center;
margin-bottom: 32px;
}
.login-header h1 {
color: #ffffff;
font-size: 24px;
font-weight: 600;
margin: 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.login-form {
width: 100%;
}
.login-input {
margin-bottom: 20px;
}
:deep(.login-input .el-input__wrapper) {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
box-shadow: none;
padding: 12px 16px;
}
:deep(.login-input .el-input__wrapper:hover) {
border-color: rgba(255, 255, 255, 0.3);
}
:deep(.login-input .el-input__wrapper.is-focus) {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
:deep(.login-input .el-input__inner) {
color: #ffffff;
background: transparent;
}
:deep(.login-input .el-input__inner::placeholder) {
color: rgba(255, 255, 255, 0.6);
}
:deep(.login-input .el-input__prefix) {
color: rgba(255, 255, 255, 0.7);
}
:deep(.login-input .el-input__suffix) {
color: rgba(255, 255, 255, 0.7);
}
.login-button {
width: 100%;
height: 48px;
background: linear-gradient(135deg, #409eff 0%, #337ecc 100%);
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
margin-top: 8px;
transition: all 0.3s ease;
}
.login-button:hover {
background: linear-gradient(135deg, #337ecc 0%, #2b6cb0 100%);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(64, 158, 255, 0.3);
}
.login-footer {
text-align: center;
margin-top: 24px;
}
.footer-text {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.login-card {
width: 90%;
max-width: 360px;
padding: 32px 24px;
}
.login-header h1 {
font-size: 20px;
}
.decoration-circle {
display: none;
}
.decoration-lines {
display: none;
}
}
</style>

41
tsconfig.json Normal file
View File

@ -0,0 +1,41 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": false,
"jsx": "preserve",
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env",
"node"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

View File

@ -1,4 +1,5 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
transpileDependencies: true,
publicPath: './'
})

920
yarn.lock

File diff suppressed because it is too large Load Diff