提交 b9aae742 authored 作者: coderBryanFu's avatar coderBryanFu

update

上级 635c86d8
......@@ -19,6 +19,7 @@
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.4.4",
"highlight.js": "^11.11.1",
"json5": "^2.2.3",
"markdown-it": "^14.1.0",
"vue": "^3.4.0",
"vue-router": "^4.2.5"
......@@ -2899,6 +2900,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/echarts-liquidfill/-/echarts-liquidfill-3.1.0.tgz",
"integrity": "sha512-5Dlqs/jTsdTUAsd+K5LPLLTgrbbNORUSBQyk8PSy1Mg2zgHDWm83FmvA4s0ooNepCJojFYRITTQ4GU1UUSKYLw==",
"license": "MIT",
"peerDependencies": {
"echarts": "^5.0.1"
}
......@@ -4026,6 +4028,18 @@
"sprintf-js": "~1.0.2"
}
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz",
......
......@@ -28,6 +28,7 @@
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.4.4",
"highlight.js": "^11.11.1",
"json5": "^2.2.3",
"markdown-it": "^14.1.0",
"vue": "^3.4.0",
"vue-router": "^4.2.5"
......
......@@ -27,6 +27,7 @@
<el-dropdown-item command="/decree">政令首页</el-dropdown-item>
<el-dropdown-item command="/thinkTank">智库首页</el-dropdown-item>
<el-dropdown-item command="/exportControl">出口管制</el-dropdown-item>
<el-dropdown-item command="/finance">投融资限制</el-dropdown-item>
<el-dropdown-item command="/marketAccessRestrictions">市场准入限制</el-dropdown-item>
</el-dropdown-menu>
</template>
......
......@@ -38,6 +38,52 @@ export function useMarkdownStream() {
)
// 关键配置:自定义图片渲染规则
md.renderer.rules.image = function (tokens, idx, options, env, self) {
const token = tokens[idx];
const src = token.attrs[token.attrIndex('src')][1];
const alt = token.content;
const title = token.attrs[token.attrIndex('title')] ? token.attrs[token.attrIndex('title')][1] : '';
// 获取自定义样式参数
const maxWidth = env.maxWidth || '100%';
const height = env.height || 'auto';
const objectFit = env.objectFit || 'cover';
const borderRadius = env.borderRadius || '0px';
const boxShadow = env.boxShadow || 'none';
const display = env.display || 'block';
const margin = env.margin || '0 auto';
// 构建样式字符串
const style = `
max-width: ${maxWidth};
height: ${height};
object-fit: ${objectFit};
border-radius: ${borderRadius};
box-shadow: ${boxShadow};
display: ${display};
margin: ${margin};
`.replace(/\s+/g, ' ').trim();
return `<img src="${src}" alt="${alt}"${title ? ` title="${title}"` : ''} style="${style}" loading="lazy">`;
};
// 链接图片的特殊处理
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
const token = tokens[idx];
const href = token.attrs[token.attrIndex('href')][1];
// 检查下一个token是否是图片
const nextToken = tokens[idx + 1];
if (nextToken && nextToken.type === 'image') {
// 为包含图片的链接添加特殊样式
const linkStyle = env.linkStyle || 'text-decoration: none; color: inherit;';
return `<a href="${href}" style="${linkStyle}" target="_blank" rel="noopener">`;
}
return self.renderToken(tokens, idx, options);
};
// 自定义代码块渲染规则
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
const token = tokens[idx]
......
// composables/useMarkdownStream.js
import { ref, computed, nextTick } from 'vue'
import MarkdownIt from 'markdown-it'
import hljs from 'highlight.js'
// 引入 highlight.js 样式
import 'highlight.js/styles/github.css'
import mdKatex from '@traptitech/markdown-it-katex'
export function useStream() {
const rawContent = ref('')
const createMd = () => {
const md = new MarkdownIt(
{
html: true,
breaks: true, // 将换行符转换为 <br>
linkify: true, // 自动链接 URL
typographer: true, // 启用排版扩展
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return '<pre class="hljs" style="fontSize:14px"><code class="language-' + lang + '">' +
hljs.highlight(str, {
language: lang,
ignoreIllegals: true
}).value +
'</code></pre>'
} catch (err) {
console.warn(`代码高亮错误 (${lang}):`, err)
}
}
// 默认处理
return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'
}
}
)
// 自定义代码块渲染规则
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
const token = tokens[idx]
const lang = token.info.trim()
const code = token.content
// 使用 highlight.js 进行高亮
if (lang && hljs.getLanguage(lang)) {
try {
const highlighted = hljs.highlight(code, {
language: lang,
ignoreIllegals: true
})
return `
<div class="code-block-wrapper">
<div class="code-header">
<span class="code-lang">${lang}</span>
</div>
<pre class="hljs"><code class="language-${lang}">${highlighted.value}</code></pre>
</div>
`
} catch (err) {
console.warn(`代码高亮错误 (${lang}):`, err)
}
}
// 默认代码块
return `
<div class="code-block-wrapper">
<div class="code-header">
<span class="code-lang">${lang || 'text'}</span>
<button class="copy-btn" onclick="copyCode(this)">复制</button>
</div>
<pre class="hljs"><code>${md.utils.escapeHtml(code)}</code></pre>
</div>
`
}
// 自定义表格渲染规则
md.renderer.rules.table_open = () =>
'<div class="table-container"><table style="border-collapse: collapse; width: 100%; margin: 1em 0; border: 1px solid #dfe2e5;background: rgb(239 241 243); border-radius: 5px">'
md.renderer.rules.table_close = () => '</table></div>'
md.renderer.rules.thead_open = () => '<thead style="background: #e6e7e8">'
md.renderer.rules.th_open = () =>
'<th style="border: 1px solid #dfe2e5; padding: 12px; text-align: left; font-weight: 600;">'
md.renderer.rules.td_open = () =>
'<td style="border: 1px solid #dfe2e5; padding: 12px; text-align: left; vertical-align: top;">'
md.renderer.rules.th_close = () => '</th>'
md.renderer.rules.td_close = () => '</td>'
md.renderer.rules.tr_open = () => '<tr>'
md.renderer.rules.tr_close = () => '</tr>'
md.renderer.rules.thead_close = () => '</thead>'
md.renderer.rules.tbody_open = () => '<tbody>'
md.renderer.rules.tbody_close = () => '</tbody>'
return md
}
// 渲染 markdown
const renderedProcess = computed(() => {
const md = createMd()
// 预处理内容
return md.render(rawContent.value)
})
// 自动滚动
const scrollToBottom = async (scrollContainer) => {
await nextTick()
if (scrollContainer) {
scrollContainer.scrollTop = scrollContainer.scrollHeight
}
}
// 更新内容并滚动
const updateProcess = async (newContent, scrollContainer) => {
rawContent.value = newContent
await scrollToBottom(scrollContainer)
}
// 清空内容
const clearContent = () => {
rawContent.value = ''
}
return {
rawContent,
renderedProcess,
updateProcess,
clearContent
}
}
\ No newline at end of file
......@@ -20,7 +20,12 @@
</div>
</div>
<div class="header-right">
<img src="./assets/images/header-icon.png" alt="" />
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box1-main">
......@@ -60,7 +65,12 @@
<div class="header-left"></div>
<div class="title">相关事件</div>
<div class="header-right">
<img src="./assets/images/header-icon.png" alt="" />
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box2-main">
......@@ -105,7 +115,12 @@
</div>
</div>
<div class="header-right">
<img src="./assets/images/header-icon.png" alt="" />
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="background-wrap-right-main">
......@@ -519,7 +534,7 @@ onMounted(() => {
.header-btn-box {
position: absolute;
top: 14px;
right: 52px;
right: 84px;
display: flex;
.btn {
margin-left: 8px;
......@@ -529,14 +544,19 @@ onMounted(() => {
position: absolute;
top: 14px;
right: 12px;
width: 32px;
height: 32px;
img {
display: flex;
justify-content: flex-end;
gap: 4px;
.icon{
width: 28px;
height: 28px;
img{
width: 100%;
height: 100%;
}
}
}
}
.background-wrap-left {
width: 1150px;
margin-top: 16px;
......
......@@ -31,8 +31,13 @@
</div>
<div class="home-box" :class="{ scrollHomeBox: isShow }" ref="containerRef">
<div class="home-header" v-show="!isShow">
<span>国家科技安全 </span>> <span>中美博弈概览 </span>>
<span>科技法案 </span>
<!-- <span>国家科技安全 </span>> <span style="cursor: pointer;">中美博弈概览 </span>>
<span>科技法案 </span> -->
<div class="header-item">国家科技安全</div>
<div class="header-item">></div>
<div class="header-item back-item" @click="handleBackHome">中美博弈概览</div>
<div class="header-item">></div>
<div class="header-item">科技法案</div>
</div>
<div class="home-main">
<div class="home-main-header" v-show="!isShow">
......@@ -566,6 +571,13 @@ import Zyy from "@/assets/icons/zyy.png";
import Ghd from "@/assets/icons/ghd.png";
import Mzd from "@/assets/icons/mzd.png";
// 返回首页
const handleBackHome = () => {
router.push({
path: '/overview'
})
}
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
......@@ -1073,79 +1085,77 @@ const box9ChartData = ref([
const box7Data = ref([
[
{
name: '众议院',
name: "众议院",
value: 298
},
{
name: '参议院',
name: "参议院",
value: 149
}
],
[
{
name: '拨款委员会',
name: "拨款委员会",
value: 50,
type: '众议院'
type: "众议院"
},
{
name: '筹款委员会',
name: "筹款委员会",
value: 50,
type: '众议院'
type: "众议院"
},
{
name: '外交事务委员会',
name: "外交事务委员会",
value: 46,
type: '众议院'
type: "众议院"
},
{
name: '国土安全委员会',
name: "国土安全委员会",
value: 40,
type: '众议院'
type: "众议院"
},
{
name: '司法委员会',
name: "司法委员会",
value: 40,
type: '众议院'
type: "众议院"
},
{
name: '军事委员会',
name: "军事委员会",
value: 40,
type: '众议院'
type: "众议院"
},
{
name: '能源和商业委员会',
name: "能源和商业委员会",
value: 32,
type: '众议院'
type: "众议院"
},
{
name: '拨款委员会1',
name: "拨款委员会1",
value: 32,
type: '参议院'
type: "参议院"
},
{
name: '财政委员会',
name: "财政委员会",
value: 31,
type: '参议院'
type: "参议院"
},
{
name: '能源',
name: "能源",
value: 30,
type: '参议院'
type: "参议院"
},
{
name: '能源1',
name: "能源1",
value: 30,
type: '参议院'
type: "参议院"
},
{
name: '其他',
name: "其他",
value: 24,
type: '参议院'
type: "参议院"
}
]
])
]);
const box8Data = ref([
{
......@@ -1203,8 +1213,8 @@ onMounted(async () => {
const wordCloudChart = getWordCloudChart(wordCloudData.value);
setChart(wordCloudChart, "wordCloudChart");
const box7Chart = getDoublePieChart(box7Data.value[0], box7Data.value[1])
setChart(box7Chart, 'box7Chart')
const box7Chart = getDoublePieChart(box7Data.value[0], box7Data.value[1]);
setChart(box7Chart, "box7Chart");
const box9Chart = getPieChart(box9ChartData.value, box9ChartColorList.value);
setChart(box9Chart, "box9Chart");
......@@ -1328,6 +1338,16 @@ onMounted(async () => {
background: url("./assets/images/header-bg.png");
box-sizing: border-box;
padding-left: 160px;
display: flex;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
.home-main {
width: 1600px;
......@@ -1565,9 +1585,10 @@ onMounted(async () => {
.box1-main-left-info {
margin-top: 17px;
display: flex;
gap: 8px;
.info-box {
height: 30px;
width: 80px;
max-width: 80px;
height: 24px;
text-align: center;
overflow: hidden;
padding: 0 8px;
......@@ -1579,8 +1600,7 @@ onMounted(async () => {
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 30px;
margin-right: 8px;
line-height: 24px;
}
.info1 {
border: 1px solid rgba(135, 232, 222, 1);
......@@ -2335,7 +2355,7 @@ onMounted(async () => {
align-items: center;
justify-content: flex-end;
}
.box7-main{
.box7-main {
height: 340px;
}
}
......@@ -2592,15 +2612,18 @@ onMounted(async () => {
.btn-wrapper {
width: 1300px;
overflow-x: auto;
// background: skyblue;
.btn-box {
display: flex;
gap: 4px;
// justify-content: space-between;
max-width: 2000px;
// background: orange;
.btn {
max-width: 100px;
max-width: 120px;
min-width: 80px;
height: 42px;
margin: 0 5px;
overflow: hidden;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
......@@ -2610,7 +2633,7 @@ onMounted(async () => {
text-align: center;
border-radius: 21px;
background: rgba(20, 89, 187, 0);
padding: 0 16px;
padding: 0 20px;
cursor: pointer;
&:hover {
background: rgba(20, 89, 187, 0.1);
......
const getBoxPlotChcart = (data) => {
const getBoxPlotChcart = (data,unit) => {
let option = {
// title: [
// {
......@@ -34,7 +34,12 @@ const getBoxPlotChcart = (data) => {
},
yAxis: {
type: 'value',
name: '单位:天',
name: `单位:${unit}`,
nameTextStyle: {
padding: [0, 0, 0, 1520], // [上, 右, 下, 左]
verticalAlign: 'middle',
align: 'center'
},
splitArea: {
show: true
}
......
......@@ -4,7 +4,12 @@
<div class="header-left"></div>
<div class="title">流程概要</div>
<div class="header-right">
<img src="./assets/images/header-icon.png" alt="" />
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="main">
......@@ -758,14 +763,19 @@ const handleClickDetail = (isShow) => {
position: absolute;
top: 14px;
right: 12px;
width: 32px;
height: 32px;
display: flex;
justify-content: flex-end;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
}
.main {
margin-left: 59px;
height: 680px;
......
......@@ -5,7 +5,15 @@
<div class="header-left"></div>
<div class="title">涉及企业</div>
<div class="header-right">
<img src="../assets/images/header-icon.png" alt="" />
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="left-top" id="chart1"></div>
......@@ -23,18 +31,16 @@
</div>
</div>
<div class="left-footer">
<div class="item-box">
<div
class="item"
:class="{ itemActive: companyActiveIndex === idx }"
@click="handleClickCompany(idx)"
v-for="(val, idx) in companyList"
v-for="(val, idx) in curCompanyList"
:key="idx"
>
<div class="id">{{ idx + 1 }}</div>
<div
class="title"
:class="{ titleActive: companyActiveIndex === idx }"
>
<div class="title" :class="{ titleActive: companyActiveIndex === idx }">
{{ val.name }}
</div>
<div class="icon">
......@@ -43,13 +49,36 @@
</div>
</div>
</div>
<div class="footer-box">
<div class="left">{{ `共${companyList.length}家企业` }}</div>
<div class="right">
<el-pagination
@current-change="handleCurrentChange"
:pageSize="pageSize"
:current-page="currentPage"
size="small"
background
layout="prev, pager, next"
:total="companyList.length"
/>
</div>
</div>
</div>
</div>
<div class="right">
<div class="box-header">
<div class="header-left"></div>
<div class="title">产业链分析</div>
<div class="header-right">
<img src="../assets/images/header-icon.png" alt="" />
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
......@@ -131,11 +160,7 @@
<div class="dialog-box1-title">{{ companyInfo.data1.title }}</div>
</div>
<div class="dialog-box1-main">
<div
class="item"
v-for="(val, idx) in companyInfo.data1.list"
:key="idx"
>
<div class="item" v-for="(val, idx) in companyInfo.data1.list" :key="idx">
<div class="item-left">
<!-- <img :src="uncheckIcon" alt=""> -->
<img :src="checkedIcon" alt="" />
......@@ -169,7 +194,7 @@
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { ref, onMounted, nextTick, computed } from "vue";
import * as echarts from "echarts";
import { getCompanyList, getIndustryHyly, getHylyList } from "@/api/influence";
import getBarChart from "./utils/barChart";
......@@ -216,40 +241,22 @@ const chart1Data = ref({
const industryActiveIndex = ref(0);
const companyActiveIndex = ref(0);
const handleClickCompany = (index) => {
const handleClickCompany = index => {
companyActiveIndex.value = index;
isShowCompanyDialog.value = true;
setTimeout(() => {
const chart2Data = {
dataX: [
"2025-01",
"2025-02",
"2025-03",
"2025-04",
"2025-05",
"2025-06",
"2025-07",
"2025-08",
],
dataY: [1.2, 1.5, 1.4, 1.8, 1.3, 1.5, 1.6, 1.4],
dataX: ["2025-01", "2025-02", "2025-03", "2025-04", "2025-05", "2025-06", "2025-07", "2025-08"],
dataY: [1.2, 1.5, 1.4, 1.8, 1.3, 1.5, 1.6, 1.4]
};
let chart2 = getLineChart(chart2Data.dataX, chart2Data.dataY);
setChart(chart2, "chart2");
const chart3Data = {
name: [
"2023Q3",
"2023Q4",
"2024Q1",
"2024Q2",
"2024Q3",
"2024Q4",
"2025Q1",
"2025Q2",
],
value: [49, 53, 52, 54, 52, 50, 51, 54],
name: ["2023Q3", "2023Q4", "2024Q1", "2024Q2", "2024Q3", "2024Q4", "2025Q1", "2025Q2"],
value: [49, 53, 52, 54, 52, 50, 51, 54]
};
let chart3 = getBarChart1(chart3Data.name, chart3Data.value);
......@@ -257,7 +264,22 @@ const handleClickCompany = (index) => {
}, 100);
};
const companyList = ref([]);
const pageSize = ref(10);
const currentPage = ref(1);
const companyList = ref([]); // 企业列表
// 当前企业列表
const curCompanyList = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return companyList.value.slice(startIndex, endIndex);
});
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
};
const industryList = ref([
// {
......@@ -278,34 +300,25 @@ const companyInfo = ref({
list: [
"严格限制外国敏感实体(FEOC)参与补贴",
"取消电动汽车补贴(2025年底生效)",
"任何实体若与被禁止外国实体签订超100万美元的关键技术许可协议,则不得向其提供联邦补贴",
],
"任何实体若与被禁止外国实体签订超100万美元的关键技术许可协议,则不得向其提供联邦补贴"
]
},
data2: {
icon: icon2,
title: "融资情况",
data: {
time: ["2025-06", "2025-07", "2025-08"],
value: [1.12, 1.42, 1.5],
},
value: [1.12, 1.42, 1.5]
}
},
data3: {
icon: icon3,
title: "研发投入",
data: {
time: [
"2023Q3",
"2023Q4",
"2024Q1",
"2024Q2",
"2024Q3",
"2024Q4",
"2025Q1",
"2025Q2",
],
value: [50, 53, 52, 54, 58, 60, 51, 56, 64],
},
},
time: ["2023Q3", "2023Q4", "2024Q1", "2024Q2", "2024Q3", "2024Q4", "2025Q1", "2025Q2"],
value: [50, 53, 52, 54, 58, 60, 51, 56, 64]
}
}
});
// 获取行业领域列表
......@@ -323,7 +336,7 @@ const curHylyId = ref("");
// 根据行业领域id获取公司列表
const handleGetCompanyListById = async () => {
const params = {
id: curHylyId.value,
id: curHylyId.value
};
try {
const res = await getCompanyList(params);
......@@ -339,15 +352,15 @@ const handleGetCompanyListById = async () => {
// 根据法案ID 获取行业领域统计
const handleGetIndustryHyly = async () => {
const params = {
id: 1,
id: 1
};
try {
const res = await getIndustryHyly(params);
// console.log('行业领域统计', res);
chart1Data.value.name = res.data.map((item) => {
chart1Data.value.name = res.data.map(item => {
return item.hylyName;
});
chart1Data.value.value = res.data.map((item) => {
chart1Data.value.value = res.data.map(item => {
return item.companyNum;
});
} catch (error) {}
......@@ -393,14 +406,19 @@ onMounted(async () => {
position: absolute;
top: 14px;
right: 12px;
width: 32px;
height: 32px;
display: flex;
justify-content: flex-end;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
}
.left {
margin-top: 16px;
width: 480px;
......@@ -414,11 +432,7 @@ onMounted(async () => {
box-sizing: border-box;
border: 1px solid rgba(231, 241, 255, 1);
border-radius: 4px;
background: linear-gradient(
180deg,
rgba(246, 251, 255, 1),
rgba(246, 251, 255, 0) 100%
);
background: linear-gradient(180deg, rgba(246, 251, 255, 1), rgba(246, 251, 255, 0) 100%);
}
.left-center {
margin-top: 12px;
......@@ -461,13 +475,15 @@ onMounted(async () => {
}
.left-footer {
margin: 0 auto;
margin-top: 5px;
width: 446px;
height: 520px;
overflow: auto;
overflow: hidden;
.item-box {
height: 480px;
overflow: hidden;
.item {
width: 100%;
height: 40px;
height: 48px;
border-radius: 4px;
border-bottom: 1px solid rgba(243, 243, 244, 1);
display: flex;
......@@ -521,6 +537,23 @@ onMounted(async () => {
color: rgba(22, 119, 255, 1) !important;
}
}
.footer-box {
display: flex;
justify-content: space-between;
.left {
width: 100px;
height: 20px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.right {
flex: 300px;
}
}
}
}
.right {
margin-top: 16px;
......@@ -690,11 +723,7 @@ onMounted(async () => {
box-sizing: border-box;
border: 1px solid rgba(231, 241, 255, 1);
border-radius: 4px;
background: linear-gradient(
180deg,
rgba(246, 251, 255, 1),
rgba(246, 251, 255, 0) 100%
);
background: linear-gradient(180deg, rgba(246, 251, 255, 1), rgba(246, 251, 255, 0) 100%);
}
}
.dialog-box3 {
......@@ -731,11 +760,7 @@ onMounted(async () => {
box-sizing: border-box;
border: 1px solid rgba(231, 241, 255, 1);
border-radius: 4px;
background: linear-gradient(
180deg,
rgba(246, 251, 255, 1),
rgba(255, 255, 255, 0) 100%
);
background: linear-gradient(180deg, rgba(246, 251, 255, 1), rgba(255, 255, 255, 0) 100%);
}
}
}
......
<template>
<div class="wrapper">
<div class="header"><span>首页 </span>> <span>综合检索 </span>> <span>智能问答 </span></div>
<div class="header">
<!-- <span>首页 </span>> <span>综合检索 </span>> <span>智能问答 </span> -->
<div class="header-item">首页</div>
<div class="header-item">></div>
<div class="header-item back-item" @click="handleBackHome">综合检索</div>
<div class="header-item">></div>
<div class="header-item">智能问答</div>
</div>
<div class="main">
<div class="left">
<div class="left-header">
......@@ -37,9 +44,20 @@
<div class="icon">
<img src="./assets/images/ai-avator.png" alt="" />
</div>
<div class="text">{{ `已深度思考(用时6秒钟)` }}</div>
<div class="text">{{ `已深度思考` }}</div>
</div>
<div class="content ai-content" v-html="renderMarkdown(message.content)"></div>
<div v-if="message.think" class="think-title">思考内容</div>
<div
v-if="message.think"
class="content think-content"
v-html="renderMarkdown(message.think)"
></div>
<div v-if="message.content" class="answer-title">正文内容</div>
<div
v-if="message.content"
class="content ai-content"
v-html="renderMarkdown(message.content)"
></div>
</div>
<!-- 用户消息 -->
<div v-else class="user-item">
......@@ -67,7 +85,7 @@
</div>
</div>
<div class="right-main-footer">
<el-input type="textarea" placeholder="发送消息" :rows="4" v-model="userInput" />
<el-input @keyup.enter="sendMessage" type="textarea" placeholder="发送消息" :rows="4" v-model="userInput" />
<div class="input-footer">
<div class="icon1">
<el-tooltip effect="dark" content="录音" placement="top">
......@@ -94,11 +112,20 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { ref, onMounted, onUnmounted, nextTick } from "vue";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import MarkdownIt from "markdown-it";
import { ElMessage } from "element-plus";
import { getChat } from "@/api/chat";
import router from "@/router/index";
import json5 from "json5";
// 返回综合检索
const handleBackHome = () => {
router.push({
path: "/comprehensiveSearch"
});
};
const contentContainer = ref(null);
......@@ -149,11 +176,38 @@ const addMessage = (type, content) => {
const updateLastAIMessage = content => {
const lastMessage = messages.value[messages.value.length - 1];
if (lastMessage && lastMessage.type === "ai") {
if (isCurAnswerMessage.value) {
lastMessage.content = content;
} else {
lastMessage.think = content;
}
scrollToBottom();
}
};
let aiMessage = ref("");
const isCurAnswerMessage = ref(false);
function parseOuterOnly(json5String, maxDepth = 1) {
let currentDepth = 0;
return json5.parse(json5String, (key, value) => {
// 检查当前深度
if (currentDepth > maxDepth) {
return value; // 超过最大深度,直接返回值
}
// 如果是对象或数组,增加深度计数
if (typeof value === "object" && value !== null) {
currentDepth++;
}
return value;
});
}
// 使用 fetchEventSource 连接
const connectSSE = async question => {
// 添加用户消息
......@@ -168,68 +222,71 @@ const connectSSE = async question => {
abortController.value = new AbortController();
const params = {
query: "如何检索?",
knowledge_base_name: "kb_test251112",
top_k: 6,
score_threshold: 0.5,
metadata: { year: 2024 }
// question: "川普1期对华制裁领域分布情况"
question: question
// knowledge_base_name: "kb_test251112",
// top_k: 6,
// score_threshold: 0.5,
// metadata: { year: 2024 }
};
try {
await fetchEventSource("/chat/knowledge_base/search_docs", {
fetchEventSource("/sseChat/rag/chat/stream", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(params),
signal: abortController.value.signal,
onopen: async response => {
console.log("SSE 连接已建立", response.status);
if (response.status !== 200) {
throw new Error(`请求失败: ${response.status}`);
}
},
onmessage: event => {
try {
if (event.data === "[DONE]") {
// 流式输出结束
openWhenHidden: true,
async onopen(res) {
isLoading.value = false;
console.log("流式回答开始", res);
},
async onmessage(res) {
console.log("res", res);
let msgData = parseOuterOnly(res.data);
if (res.event === "end_of_workflow") {
ElMessage.success("问答完成!");
abortController.value.abort();
abortController.value = new AbortController();
return;
}
const data = JSON.parse(event.data);
if (data.type === "content" && data.content) {
// 流式更新内容
updateLastAIMessage(prev => prev + data.content);
} else if (data.type === "error") {
throw new Error(data.message || "请求失败");
if (res.event === "start_of_agent" && msgData.agent_name === "answer") {
isCurAnswerMessage.value = true;
aiMessage.value = "";
}
if (res.event === "message") {
let content = msgData.delta.content;
console.log("msgData", msgData);
console.log("content", content);
if (content !== "[DONE]") {
aiMessage.value += content;
updateLastAIMessage(aiMessage.value);
} else {
aiMessage.value = "";
abortController.value.abort();
abortController.value = new AbortController();
}
} catch (error) {
console.error("解析 SSE 数据错误:", error);
}
},
onclose: () => {
console.log("SSE 连接已关闭");
isLoading.value = false;
},
onerror: error => {
console.error("SSE 连接错误:", error);
ElMessage.error("连接失败,请重试");
isLoading.value = false;
// 不要抛出错误,否则会重试
}
onerror(error) {
ElMessage({
message: "问答报错!",
type: "warning"
});
} catch (error) {
console.error("SSE 请求失败:", error);
if (error.name !== "AbortError") {
ElMessage.error(error.message || "请求失败");
}
isLoading.value = false;
abortController.value.abort();
abortController.value = new AbortController();
throw new Error(error);
}
}).catch(error => {
ElMessage({
message: "问答报错!",
type: "warning"
});
abortController.value.abort();
abortController.value = new AbortController();
throw new Error(error);
});
};
const chat = async () => {
......@@ -281,7 +338,7 @@ const chatList = ref([
},
{
id: 3,
title: "列举 2025 年科技产品"
title: "列举2025年科技产品"
},
{
id: 4,
......@@ -368,6 +425,16 @@ onUnmounted(() => {
background: url("../assets/images/header-bg.png");
box-sizing: border-box;
padding-left: 160px;
display: flex;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
.main {
margin-top: 16px;
......@@ -536,10 +603,27 @@ onUnmounted(() => {
/* 对话内容区域 */
.chat-content {
overflow-x: hidden;
overflow-y: auto;
// padding: 20px;
background: #fff;
width: 926px;
width: 1118px;
padding-right: 192px;
box-sizing: border-box;
height: 620px;
.chat-content::-webkit-scrollbar {
width: 30px;
}
.chat-content::-webkit-scrollbar-thumb {
background: #000;
border-radius: 3px;
}
.chat-content::-webkit-scrollbar-thumb:hover {
background: #555;
}
.message-list {
padding-bottom: 20px;
}
......@@ -569,10 +653,43 @@ onUnmounted(() => {
text-align: left;
}
}
.think-title {
margin-left: 26px;
height: 22px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
.answer-title {
margin-top: 10px;
margin-left: 26px;
height: 22px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.think-content {
background: rgba(245, 245, 245, 0.5);
width: 900px;
margin-left: 26px;
padding: 1px 10px;
border-radius: 5px;
}
.ai-content {
margin-top: 10px;
width: 900px;
margin-left: 26px;
background: rgba(246, 250, 255, 1);
padding: 1px 10px;
border-radius: 5px;
}
}
.user-item {
......
<template>
<div class="wrapper">
<div class="header"><span>首页 </span>> <span>综合检索 </span></div>
<div class="header">
<!-- <span>首页 </span>> <span>综合检索 </span> -->
<div class="header-item">首页</div>
<div class="header-item">></div>
<div class="header-item">综合检索</div>
</div>
<div class="main">
<div class="main-header">
<div class="title">{{ "一站式信息智能检索子系统" }}</div>
......@@ -92,7 +97,9 @@
tag2: item.tag.status === 2,
tag3: item.tag.status === 3
}"
>{{ item.tag.name }}</div>
>
{{ item.tag.name }}
</div>
</div>
</div>
</div>
......@@ -112,7 +119,7 @@
<script setup>
import { ref, onMounted } from "vue";
import router from '@/router/index'
import router from "@/router/index";
import getWordCloudChart from "./utils/wordCloud";
import setChart from "@/utils/setChart";
......@@ -122,7 +129,6 @@ import Img3 from "./assets/images/box3-img3.png";
import Img4 from "./assets/images/box3-img4.png";
import Img5 from "./assets/images/box3-img5.png";
const headerInfoList = ref([
{
name: "美国",
......@@ -221,7 +227,6 @@ const box2Data = ref([
{ name: "加强供应链风险管理", value: 73 }
]);
const box3List = ref([
{
img: Img1,
......@@ -282,23 +287,21 @@ const box3List = ref([
// 点击智能问答,进入智能问答页
const handleToChat = () => {
router.push({
path: '/chat'
})
}
path: "/chat"
});
};
// 点击全文搜索,进入搜索结果页
const handleToSearchResults = () => {
router.push({
path: '/searchResults'
})
}
path: "/searchResults"
});
};
onMounted(() => {
const box2Chart = getWordCloudChart(box2Data.value)
setChart(box2Chart, 'box2Chart')
})
const box2Chart = getWordCloudChart(box2Data.value);
setChart(box2Chart, "box2Chart");
});
</script>
<style lang="scss" scoped>
......@@ -354,6 +357,16 @@ onMounted(() => {
background: url("./assets/images/header-bg.png");
box-sizing: border-box;
padding-left: 160px;
display: flex;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
.main {
width: 100%;
......@@ -602,7 +615,7 @@ onMounted(() => {
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.box2-main{
.box2-main {
height: 390px;
}
}
......
......@@ -32,8 +32,11 @@
<div class="home-main" :class="{ scrollHomeMain: isShow }" ref="containerRef">
<div class="home-main-header">
<div class="home-main-header-top" v-show="!isShow">
<span>国家科技安全 </span>> <span>中美博弈概览 </span>>
<span>科技政令</span>
<div class="header-item">国家科技安全</div>
<div class="header-item">></div>
<div class="header-item back-item" @click="handleBackHome">中美博弈概览</div>
<div class="header-item">></div>
<div class="header-item">行政令</div>
</div>
<div class="home-main-header-center" v-show="!isShow">
<el-input v-model="input" style="width: 838px; height: 100%" placeholder="搜索科技政令" />
......@@ -638,6 +641,13 @@ import Message1 from "./assets/images/message-icon1.png";
import Message2 from "./assets/images/message-icon2.png";
import Message3 from "./assets/images/message-icon3.png";
// 返回首页
const handleBackHome = () => {
router.push({
path: '/overview'
})
}
const containerRef = ref(null);
const { isShow } = useContainerScroll(containerRef);
const currentPage = ref(1);
......@@ -855,7 +865,6 @@ const keyDecreeList = ref([
}
]);
// 政令重点条款
const wordCloudData = [
{ name: "与马斯克公开冲突", value: 100 },
......@@ -1024,7 +1033,6 @@ const handleClickCate = cate => {
activeCate.value = cate;
};
const calendarData = ref([
["2025-01-01", 20],
["2025-01-05", 120],
......@@ -1205,6 +1213,16 @@ onMounted(async () => {
color: #fff;
box-sizing: border-box;
padding-left: 160px;
display: flex;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
.home-main-header-center {
margin-top: 48px;
......
......@@ -118,7 +118,7 @@
</div>
</div>
</div>
<div class="tool-box">
<!-- <div class="tool-box">
<div class="tool1">
<img src="./assets/icons/tool-icon1.png" alt="" />
</div>
......@@ -128,7 +128,7 @@
<div class="tool3">
<img src="./assets/icons/tool-icon3.png" alt="" />
</div>
</div>
</div> -->
</div>
</template>
......@@ -622,49 +622,49 @@ onMounted(() => {
}
}
}
.tool-box {
position: fixed;
z-index: 10000;
bottom: 80px;
left: 0;
width: 48px;
height: 144px;
border-radius: 0px 10px 10px 0px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
.tool1 {
width: 17px;
height: 18px;
margin-top: 17px;
margin-left: 16px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.tool2 {
width: 22px;
height: 20px;
margin-top: 26px;
margin-left: 14px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.tool3 {
width: 20px;
height: 20px;
margin-top: 25px;
margin-left: 15px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
// .tool-box {
// position: fixed;
// z-index: 10000;
// bottom: 80px;
// left: 0;
// width: 48px;
// height: 144px;
// border-radius: 0px 10px 10px 0px;
// box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
// background: rgba(255, 255, 255, 1);
// .tool1 {
// width: 17px;
// height: 18px;
// margin-top: 17px;
// margin-left: 16px;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
// .tool2 {
// width: 22px;
// height: 20px;
// margin-top: 26px;
// margin-left: 14px;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
// .tool3 {
// width: 20px;
// height: 20px;
// margin-top: 25px;
// margin-left: 15px;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
// }
}
</style>
\ No newline at end of file
......@@ -13,7 +13,12 @@
/>
</div>
<div class="header-right">
<img src="./assets/images/header-right-icon.png" alt="" />
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box1-top" id="chart1"></div>
......@@ -65,7 +70,12 @@
<div class="title">{{ "政令举措落实分析" }}</div>
<div class="header-right1"></div>
<div class="header-right">
<img src="./assets/images/header-right-icon.png" alt="" />
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
......@@ -108,7 +118,12 @@
<div class="title">{{ "历史相似举措及落实情况" }}</div>
<div class="header-right1"></div>
<div class="header-right">
<img src="./assets/images/header-right-icon.png" alt="" />
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box3-main">
......@@ -318,20 +333,25 @@ onMounted(() => {
line-height: 24px;
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
position: absolute;
width: 28px;
display: flex;
justify-content: flex-end;
gap: 4px;
.header-right-icon{
width: 28px;
img {
height: 28px;
img{
width: 100%;
height: 100%;
}
}
}
.header-right1 {
position: absolute;
top: 8px;
right: 60px;
right: 84px;
}
}
.left {
......
<template>
<div class="home-wrapper">
<div class="home-header">
<span>国家科技安全 </span>> <span>中美博弈概览 </span>>
<span>出口管制 </span>
<div class="header-item">国家科技安全</div>
<div class="header-item">></div>
<div class="header-item back-item" @click="handleBackHome">中美博弈概览</div>
<div class="header-item">></div>
<div class="header-item">出口管制</div>
</div>
<div class="home-main">
<div class="home-main-header">
......@@ -655,12 +658,19 @@ import shoushiIcon from "./assets/images/shoushi.png";
import tianyiIcon from "./assets/images/tianyi.png";
import aircasIcon from "./assets/images/aircas.png";
const handleToDetail = () => {
// 返回首页
const handleBackHome = () => {
router.push({
path: '/exportControl/analysis'
path: '/overview'
})
}
const handleToDetail = () => {
router.push({
path: "/exportControl/analysis"
});
};
const billList = ref([]);
const curBillListIndex = ref(0);
......@@ -1591,6 +1601,16 @@ onMounted(async () => {
background: url("@/assets/images/nav-bg.png");
box-sizing: border-box;
padding-left: 160px;
display: flex;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
.box1 {
......
......@@ -7,7 +7,6 @@
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
import { ArrowRight } from "@element-plus/icons-vue";
import { useRouter } from "vue-router";
......
<template>
<div class="home-wrapper">
<div class="home-header">
<span>国家科技安全 </span>> <span>中美博弈概览 </span>>
<span>投融资限制 </span>
<!-- <span>国家科技安全 </span>> <span>中美博弈概览 </span>>
<span>投融资限制 </span> -->
<div class="header-item">国家科技安全</div>
<div class="header-item">></div>
<div class="header-item back-item" @click="handleBackHome">中美博弈概览</div>
<div class="header-item">></div>
<div class="header-item">投融资限制</div>
</div>
<div class="home-main">
<div class="home-main-header">
......@@ -655,6 +660,13 @@ import shoushiIcon from "./assets/images/shoushi.png";
import tianyiIcon from "./assets/images/tianyi.png";
import aircasIcon from "./assets/images/aircas.png";
// 返回首页
const handleBackHome = () => {
router.push({
path: '/overview'
})
}
const handleToDetail = () => {
router.push({
path: "/exportControl/analysis"
......@@ -1591,6 +1603,16 @@ onMounted(async () => {
background: url("@/assets/images/nav-bg.png");
box-sizing: border-box;
padding-left: 160px;
display: flex;
.header-item {
margin: 0 3px;
}
.back-item {
cursor: pointer;
&:hover {
color: #ccc;
}
}
}
.box1 {
......
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论