Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
2ad8e183
提交
2ad8e183
authored
3月 25, 2026
作者:
朱政
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat:智库中政令法案跳转,tip标签样式修改
上级
b46e0652
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
9 个修改的文件
包含
196 行增加
和
129 行删除
+196
-129
index.js
src/api/aiAnalysis/index.js
+78
-2
overview.js
src/api/thinkTank/overview.js
+1
-2
index.vue
src/views/thinkTank/ReportDetail/policyTracking/index.vue
+28
-3
index.vue
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
+13
-8
index.vue
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
+30
-9
index.vue
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
+3
-76
index.vue
src/views/thinkTank/ThinkTankDetail/thinkInfo/index.vue
+5
-5
ThinkTankPolicyAdviceOverview.vue
...ws/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
+38
-24
index.vue
src/views/thinkTank/index.vue
+0
-0
没有找到文件。
src/api/aiAnalysis/index.js
浏览文件 @
2ad8e183
...
...
@@ -77,6 +77,58 @@ function extractInterpretationFromLooseText(text) {
return
String
(
m
[
1
]).
replace
(
/
\\
n/g
,
"
\
n"
).
trim
();
}
/**
* 从流式累积 buffer 中提取「解读」字符串的已生成部分(滤掉 ```json、[、{ 等外壳,避免界面出现 json\n[\n)
* 支持未闭合的字符串(流式进行中)
* @param {string} buffer
* @returns {string}
*/
function
extractStreamingInterpretationFromBuffer
(
buffer
)
{
const
s
=
String
(
buffer
||
""
);
let
rest
=
s
.
replace
(
/^
\u
FEFF/
,
""
);
const
fence
=
rest
.
match
(
/^```
(?:
json
)?\s
*/i
);
if
(
fence
)
{
rest
=
rest
.
slice
(
fence
[
0
].
length
);
}
const
keyRe
=
/
[
"'
](?:
解读|interpretation|analysis|content
)[
"'
]\s
*:
\s
*"/i
;
const
m
=
rest
.
match
(
keyRe
);
if
(
!
m
)
{
return
""
;
}
let
pos
=
m
.
index
+
m
[
0
].
length
;
let
out
=
""
;
while
(
pos
<
rest
.
length
)
{
const
ch
=
rest
[
pos
];
if
(
ch
===
'"'
)
{
break
;
}
if
(
ch
===
"
\
\"
) {
pos += 1;
if (pos >= rest.length) {
break;
}
const esc = rest[pos];
if (esc === "
n
") {
out += "
\
n
";
} else if (esc === "
r
") {
out += "
\
r
";
} else if (esc === "
t
") {
out += "
\
t
";
} else if (esc === '"
' || esc === "
\\
") {
out += esc;
} else {
out += esc;
}
pos += 1;
continue;
}
out += ch;
pos += 1;
}
return out;
}
/**
* 图表解读(SSE 流式)
* @param {object} data - 请求体
...
...
@@ -94,6 +146,8 @@ export function getChartAnalysis(data, options = {}) {
return new Promise((resolve, reject) => {
let buffer = "";
let latestInterpretation = "";
/** 已推给前端的「解读」正文长度,用于只增量回调 onChunk */
let lastStreamedInterpretationLen = 0;
let settled = false;
const abortController = new AbortController();
...
...
@@ -175,9 +229,31 @@ export function getChartAnalysis(data, options = {}) {
return;
}
//
每收到一条消息即回调,用于流式渲染
//
流式渲染:不把 ```json、[、{ 等 markdown/JSON 外壳拼到界面
if (chunk && onDelta) {
onDelta
(
chunk
);
let parsedMsg = null;
try {
parsedMsg = JSON.parse(raw);
} catch (_) {
parsedMsg = null;
}
const isReasoningChunk =
parsedMsg &&
typeof parsedMsg === "object" &&
parsedMsg.type === "reasoning" &&
typeof parsedMsg.chunk === "string";
if (isReasoningChunk) {
onDelta(parsedMsg.chunk);
} else {
const visible = extractStreamingInterpretationFromBuffer(buffer);
if (visible.length > lastStreamedInterpretationLen) {
onDelta(
visible.slice(lastStreamedInterpretationLen)
);
lastStreamedInterpretationLen = visible.length;
}
}
}
// 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接
...
...
src/api/thinkTank/overview.js
浏览文件 @
2ad8e183
...
...
@@ -286,8 +286,7 @@ export function getThinkPolicyIndustryChange(params) {
/**
* 获取智库政策(政策追踪列表)
* GET /api/thinkTankInfo/policy
* Query: thinkTankId, startDate, endDate, orgIds, domainIds(科技领域/智库领域,逗号分隔 id), pageNum, pageSize, sortField, sortOrder, sortFun, reportId 等
*/
export
function
getThinkPolicy
(
params
)
{
return
request
({
...
...
src/views/thinkTank/ReportDetail/policyTracking/index.vue
浏览文件 @
2ad8e183
...
...
@@ -103,17 +103,19 @@
</div>
<div
class=
"right-footer-box"
>
<div
class=
"relatedBills"
v-for=
"(item, index) in box1DataItem.relatedBills"
:key=
"index"
v-show=
"item"
>
<div
class=
"tag"
>
{{
"
政令
"
}}
</div>
<div
class=
"tag"
>
{{
"
法案
"
}}
</div>
<div
class=
"tag"
>
{{
"科技领域相似"
}}
</div>
<div
class=
"relatedBills-content"
>
{{
item
.
name
}}
</div>
<div
class=
"footer-image"
>
<img
src=
"../images/image-right.png"
alt=
""
/></div>
<div
class=
"footer-image"
@
click
.
stop=
"handleBillMoreClick(item)"
>
<img
src=
"../images/image-right.png"
alt=
""
/></div>
</div>
<div
class=
"relatedAdministrativeOrders"
v-for=
"(item, index) in box1DataItem.relatedAdministrativeOrders"
v-show=
"item"
:key=
"index"
>
<div
class=
"tag"
>
{{
"政令"
}}
</div>
<div
class=
"tag"
>
{{
"科技领域相似"
}}
</div>
<div
class=
"relatedAdministrativeOrders-content"
>
{{
item
.
content
}}
</div>
<div
class=
"footer-image"
>
<img
src=
"../images/image-right.png"
alt=
""
/></div>
<div
class=
"footer-image"
@
click
.
stop=
"handleAdministrativeMoreClick(item)"
>
<img
src=
"../images/image-right.png"
alt=
""
/></div>
</div>
</div>
</div>
...
...
@@ -217,6 +219,29 @@ const handleTagClick = tag => {
activeItemIndex
.
value
=
0
;
updatePageData
();
};
const
handleBillMoreClick
=
(
bill
)
=>
{
const
billId
=
bill
?.
id
;
if
(
!
billId
)
{
return
;
}
const
route
=
router
.
resolve
({
path
:
"/billLayout/bill/introduction"
,
query
:
{
billId
:
String
(
billId
)
}
});
window
.
open
(
route
.
href
,
"_blank"
);
};
/** 政策建议关联法案:新标签页打开政令介绍页*/
const
handleAdministrativeMoreClick
=
(
ad
)
=>
{
const
id
=
ad
?.
bcId
;
if
(
!
id
)
{
return
;
}
const
route
=
router
.
resolve
({
path
:
"/decreeLayout/overview/introduction"
,
query
:
{
id
:
String
(
id
)
}
});
window
.
open
(
route
.
href
,
"_blank"
);
};
const
handleSearchOpinions
=
()
=>
{
// 搜索时默认切回“全部”标签
activeTag
.
value
=
""
;
...
...
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
浏览文件 @
2ad8e183
...
...
@@ -74,11 +74,12 @@
</div>
<div
class=
"box5-footer"
>
<TipTab
:text=
"REPORT_ANALYSIS_TIP_BOX5"
/>
<div
class=
"ai-wrap"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<AiButton
/>
</div>
<div
class=
"ai-wrap"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<AiButton
/>
</div>
</div>
<div
class=
"ai-content"
v-if=
"isShowAiContentBox5"
@
mouseleave=
"handleSwitchAiContentShowBox5(false)"
>
<AiPane
:aiContent=
"aiContentBox5"
/>
</div>
...
...
@@ -927,9 +928,18 @@ onMounted(() => {
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
position
:
absolute
;
bottom
:
20px
;
left
:
32px
;
}
.ai-wrap
{
position
:
absolute
;
bottom
:
18px
;
right
:
0
;
cursor
:
pointer
;
}
...
...
@@ -942,12 +952,7 @@ onMounted(() => {
}
.ai-wrap
{
position
:
relative
;
cursor
:
pointer
;
}
}
}
...
...
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
浏览文件 @
2ad8e183
...
...
@@ -252,7 +252,7 @@
<div
class=
"file"
v-for=
"(file, idxx) in item.relatedBills"
:key=
"`${file.id}-${idxx}`"
>
<div
class=
"type"
>
法案
</div>
<div
class=
"title"
>
{{ file.name }}
</div>
<div
class=
"more"
>
<div
class=
"more"
@
click
.
stop=
"handleBillMoreClick(file)"
>
<img
src=
"./images/arrow-right.png"
alt=
""
/>
</div>
</div>
...
...
@@ -260,7 +260,7 @@
:key=
"file.bcId != null ? String(file.bcId) + '-' + idxx : idxx"
>
<div
class=
"type"
>
政令
</div>
<div
class=
"title"
>
{{ file.content }}
</div>
<div
class=
"more"
>
<div
class=
"more"
@
click
.
stop=
"handleAdministrativeMoreClick(file)"
>
<img
src=
"./images/arrow-right.png"
alt=
""
/>
</div>
</div>
...
...
@@ -458,6 +458,30 @@ const handleGetThinkPolicyIndustry = async () => {
}
};
/** 政策建议关联法案:新标签页打开法案介绍页,billId 随接口 id 变化 */
const
handleBillMoreClick
=
(
bill
)
=>
{
const
billId
=
bill
?.
id
;
if
(
!
billId
)
{
return
;
}
const
route
=
router
.
resolve
({
path
:
"/billLayout/bill/introduction"
,
query
:
{
billId
:
String
(
billId
)
}
});
window
.
open
(
route
.
href
,
"_blank"
);
};
/** 政策建议关联法案:新标签页打开政令介绍页*/
const
handleAdministrativeMoreClick
=
(
ad
)
=>
{
const
id
=
ad
?.
bcId
;
if
(
!
id
)
{
return
;
}
const
route
=
router
.
resolve
({
path
:
"/decreeLayout/overview/introduction"
,
query
:
{
id
:
String
(
id
)
}
});
window
.
open
(
route
.
href
,
"_blank"
);
};
// 政策建议涉及部门分布(饼图)
const
box2Data
=
ref
([]);
...
...
@@ -1423,9 +1447,8 @@ onMounted(() => {
.source
{
position
:
absolute
;
bottom
:
21px
;
left
:
50%
;
transform
:
translateX
(
-50%
);
width
:
350px
;
left
:
24px
;
width
:
420px
;
height
:
22px
;
display
:
flex
;
}
...
...
@@ -1486,10 +1509,8 @@ onMounted(() => {
.source
{
position
:
absolute
;
bottom
:
21px
;
left
:
50%
;
transform
:
translateX
(
-50%
);
width
:
350px
;
bottom
:
24px
;
width
:
420px
;
height
:
22px
;
display
:
flex
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
浏览文件 @
2ad8e183
...
...
@@ -402,80 +402,8 @@ const handleSelectedFiltersUpdate = val => {
const
author
=
ref
(
''
)
// 作者
const
curFooterList
=
ref
([
// {
// title: "中国对AI的转型产业政策",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img1
// },
// {
// title: "中美对抗、竞争和合作跨越人工智能通用领域...",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img2
// },
// {
// title: "中国、智慧城市和中东:地区和美国的选择",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img3
// },
// {
// title: "中国对AI的转型产业政策",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img4
// },
// {
// title: "中美经济竞争:复杂经济和地缘政治关系中的...",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img5
// },
// {
// title: "中国、智慧城市和中东:留给地区和美国的选择",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img6
// },
// {
// title: "中国对AI的转型产业政策",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img7
// },
// {
// title: "中美对抗、竞争和合作跨越人工智能通用领域...",
// time: "2025年6月26日",
// from: "",
// img: Img8
// },
// {
// title: "中国、智慧城市和中东:地区和美国的选择",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img9
// },
// {
// title: "中国对AI的转型产业政策",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img10
// },
// {
// title: "中美对抗、竞争和合作跨越人工智能通用领域...",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img11
// },
// {
// title: "中国、智慧城市和中东:地区和美国的选择",
// time: "2025年6月26日",
// from: "兰德科技智库",
// img: Img12
// }
]);
/** 智库报告 / 调查项目共用列表:与资源库一致,初始为空,接口非成功时清空 */
const
curFooterList
=
ref
([]);
...
...
@@ -579,11 +507,10 @@ const handleGetThinkDynamicsReport = async (payload) => {
params
.
keyword
=
keyword
;
}
const
res
=
await
getThinkTankReport
(
params
);
console
.
log
(
"智库动态报告"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
curFooterList
.
value
=
res
.
data
.
content
;
total
.
value
=
res
.
data
.
totalElements
;
}
else
if
(
res
.
code
===
500
&&
res
.
message
===
"未找到对应筛选的报告"
)
{
}
else
{
curFooterList
.
value
=
[];
total
.
value
=
0
;
}
...
...
src/views/thinkTank/ThinkTankDetail/thinkInfo/index.vue
浏览文件 @
2ad8e183
...
...
@@ -453,9 +453,9 @@ const handleGetThinkTankFundsSource = async () => {
alignTo
:
'edge'
,
offset
:
[
0
,
-
LABEL_OFFSET_UP
],
formatter
(
params
)
{
const
valueYi
=
(
params
.
data
.
value
||
0
)
/
10000
0000
const
valueYi
=
(
params
.
data
.
value
||
0
)
/
10000
const
percent
=
params
.
percent
||
0
const
valueStr
=
`
${
valueYi
.
toFixed
(
3
)}
亿
${
percent
}
%`
const
valueStr
=
`
${
valueYi
}
万
${
percent
}
%`
let
cumulative
=
0
for
(
let
i
=
0
;
i
<
params
.
dataIndex
;
i
++
)
cumulative
+=
dataList
[
i
].
value
||
0
const
centerAngle
=
90
+
((
cumulative
+
(
params
.
data
.
value
||
0
)
/
2
)
/
total
)
*
360
...
...
@@ -1096,10 +1096,10 @@ onMounted(() => {
}
.source
{
margin
:
0
auto
;
margin-top
:
10px
;
margin-left
:
240px
;
/* 上下0,左右自动居中 */
width
:
370px
;
height
:
22px
;
display
:
flex
;
...
...
@@ -1285,7 +1285,7 @@ onMounted(() => {
}
.source
{
margin
:
0
auto
;
margin
-left
:
35px
;
margin-top
:
10px
;
/* 上下0,左右自动居中 */
...
...
src/views/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
浏览文件 @
2ad8e183
...
...
@@ -7,18 +7,11 @@
<div
class=
"title"
>
{{
"科技领域"
}}
</div>
</div>
<div
class=
"select-main"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedTypeIds"
@
change=
"handleAreaGroupChange"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedTypeIds"
@
change=
"handleAreaGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_AREA"
>
{{
RESOURCE_FILTER_ALL_AREA
}}
</el-checkbox>
<el-checkbox
class=
"filter-checkbox"
v-for=
"t in (researchTypeList || [])"
:key=
"t.id"
:label=
"t.id"
>
<el-checkbox
class=
"filter-checkbox"
v-for=
"t in (researchTypeList || [])"
:key=
"t.id"
:label=
"t.id"
>
{{
t
.
name
}}
</el-checkbox>
</el-checkbox-group>
...
...
@@ -31,18 +24,11 @@
<div
class=
"title"
>
{{
"发布时间"
}}
</div>
</div>
<div
class=
"select-main"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedYearIds"
@
change=
"handleYearGroupChange"
>
<el-checkbox-group
class=
"checkbox-group"
:model-value=
"selectedYearIds"
@
change=
"handleYearGroupChange"
>
<el-checkbox
class=
"filter-checkbox"
:label=
"RESOURCE_FILTER_ALL_TIME"
>
{{
RESOURCE_FILTER_ALL_TIME
}}
</el-checkbox>
<el-checkbox
class=
"filter-checkbox"
v-for=
"y in (researchTimeList || [])"
:key=
"y.id"
:label=
"y.id"
>
<el-checkbox
class=
"filter-checkbox"
v-for=
"y in (researchTimeList || [])"
:key=
"y.id"
:label=
"y.id"
>
{{
y
.
name
}}
</el-checkbox>
</el-checkbox-group>
...
...
@@ -71,17 +57,17 @@
</div>
<div
class=
"file-box"
>
<div
class=
"file"
>
<div
class=
"file"
v-for=
"sv in item.billInfoList"
:key=
"sv.id || sv.name"
>
<div
class=
"type"
>
法案
</div>
<div
class=
"title"
>
{{
item
.
billInfoList
[
0
]
.
name
}}
</div>
<div
class=
"more"
>
<div
class=
"title"
>
{{
sv
.
name
}}
</div>
<div
class=
"more"
@
click
.
stop=
"handleBillMoreClick(sv)"
>
<img
src=
"../assets/images/image-right.png"
alt=
""
/>
</div>
</div>
<div
class=
"file"
>
<div
class=
"file"
v-for=
"(sv, index) in item.administrativeOrderInfoVOList"
:key=
"index"
>
<div
class=
"type"
>
政令
</div>
<div
class=
"title"
>
{{
item
.
billInfoList
[
0
].
name
}}
</div>
<div
class=
"more"
>
<div
class=
"title"
>
{{
sv
.
content
}}
</div>
<div
class=
"more"
@
click
.
stop=
"handleAdministrativeMoreClick(sv)"
>
<img
src=
"../assets/images/image-right.png"
alt=
""
/>
</div>
</div>
...
...
@@ -106,6 +92,7 @@
<
script
setup
>
import
{
ref
}
from
"vue"
;
import
{
useRouter
}
from
"vue-router"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
import
{
RESOURCE_FILTER_ALL_AREA
,
...
...
@@ -126,6 +113,32 @@ defineProps({
const
emit
=
defineEmits
([
"filter-change"
,
"page-change"
,
"item-click"
]);
const
router
=
useRouter
();
/** 政策建议关联法案:新标签页打开法案介绍页,billId 随接口 id 变化 */
const
handleBillMoreClick
=
(
bill
)
=>
{
const
billId
=
bill
?.
id
;
if
(
!
billId
)
{
return
;
}
const
route
=
router
.
resolve
({
path
:
"/billLayout/bill/introduction"
,
query
:
{
billId
:
String
(
billId
)
}
});
window
.
open
(
route
.
href
,
"_blank"
);
};
/** 政策建议关联法案:新标签页打开政令介绍页*/
const
handleAdministrativeMoreClick
=
(
ad
)
=>
{
const
id
=
ad
?.
bcId
;
if
(
!
id
)
{
return
;
}
const
route
=
router
.
resolve
({
path
:
"/decreeLayout/overview/introduction"
,
query
:
{
id
:
String
(
id
)
}
});
window
.
open
(
route
.
href
,
"_blank"
);
};
const
selectedTypeIds
=
ref
([
RESOURCE_FILTER_ALL_AREA
]);
const
selectedYearIds
=
ref
([
RESOURCE_FILTER_ALL_TIME
]);
...
...
@@ -428,6 +441,7 @@ const handleYearGroupChange = (val) => {
height
:
20px
;
display
:
flex
;
margin-top
:
2px
;
cursor
:
pointer
;
.img
{
width
:
100%
;
...
...
src/views/thinkTank/index.vue
浏览文件 @
2ad8e183
差异被折叠。
点击展开。
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论