Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
5466ba6a
提交
5466ba6a
authored
3月 19, 2026
作者:
coderBryanFu
浏览文件
操作
浏览文件
下载
差异文件
feat:新增通用组件AiSummary
上级
d5eb651a
31f63814
显示空白字符变更
内嵌
并排
正在显示
32 个修改的文件
包含
1938 行增加
和
1945 行删除
+1938
-1945
App.vue
src/App.vue
+14
-0
bill.js
src/api/bill.js
+27
-0
home.js
src/api/decree/home.js
+3
-3
index.vue
src/components/base/DecreeOriginal/index.vue
+7
-0
analysisBox.vue
src/components/base/boxBackground/analysisBox.vue
+3
-2
bill.js
src/router/modules/bill.js
+9
-9
decree.js
src/router/modules/decree.js
+1
-1
ResourceLibrarySection.vue
src/views/bill/billHome/ResourceLibrarySection.vue
+25
-4
index.vue
src/views/bill/billHome/index.vue
+86
-1
BillHeader.vue
src/views/bill/billLayout/components/BillHeader.vue
+1
-52
index.vue
src/views/bill/billLayout/index.vue
+12
-18
index.vue
src/views/bill/deepDig/poliContribution/index.vue
+1
-0
index.vue
src/views/bill/deepDig/processOverview/index.vue
+0
-119
index.vue
src/views/bill/index.vue
+2
-2
index.vue
src/views/bill/introdoction/index.vue
+50
-209
index.vue
src/views/bill/template/index.vue
+3
-3
translate-icons.svg
...iews/bill/versionCompare/assert/icons/translate-icons.svg
+3
-0
index.vue
src/views/bill/versionCompare/index.vue
+1167
-0
index.vue
src/views/decree/decreeHome/index.vue
+116
-230
index.vue
src/views/decree/decreeLayout/deepdig/index.vue
+2
-1
index.vue
src/views/decree/decreeLayout/index.vue
+32
-86
ChartRelation.vue
src/views/decree/decreeLayout/influence/ChartRelation.vue
+0
-697
AiTips.vue
src/views/decree/decreeLayout/influence/com/AiTips.vue
+62
-0
ChartChain.vue
src/views/decree/decreeLayout/influence/com/ChartChain.vue
+107
-205
index.vue
src/views/decree/decreeLayout/influence/index.vue
+56
-178
index.vue
src/views/decree/decreeLayout/overview/background/index.vue
+32
-12
index.vue
...views/decree/decreeLayout/overview/introduction/index.vue
+11
-18
index.vue
src/views/decree/decreeLayout/overview/measures/index.vue
+105
-93
download.png
src/views/decree/decreeOriginal/assets/icons/download.png
+0
-0
search.png
src/views/decree/decreeOriginal/assets/icons/search.png
+0
-0
index.vue
src/views/decree/decreeOriginal/index.vue
+0
-1
vite.config.js
vite.config.js
+1
-1
没有找到文件。
src/App.vue
浏览文件 @
5466ba6a
...
...
@@ -235,12 +235,26 @@ body {
display
:
none
;
}
/* #region 公共样式类名 */
/* 单行文本溢出隐藏显示省略号 */
.one-line-ellipsis
{
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
/* 多行文本两端对齐 最后一行正常显示 */
.text-align-justify
{
text-align
:
justify
;
text-align-last
:
left
;
-webkit-text-align-last
:
left
;
}
/* 可点击文本 鼠标悬浮样式 */
#app
.text-click-hover
:hover
{
text-decoration
:
underline
;
color
:
rgb
(
5
,
95
,
194
);
cursor
:
pointer
;
}
/* #endregion 公共样式类名 */
</
style
>
<
style
lang=
"scss"
scoped
>
...
...
src/api/bill.js
浏览文件 @
5466ba6a
...
...
@@ -192,3 +192,30 @@ export function getBillFullText(params) {
params
,
})
}
// 条款对比-根据两版版本与筛选条件获取配对条款列表
/**
* @param {billId,oldVersionId,newVersionId,diffType,cRelated,keyword}
* @header token
* @returns { list: Array<{ oldTerm: object|null, newTerm: object|null }> }
*/
export
function
getBillTermsCompare
(
params
)
{
return
request
({
method
:
"GET"
,
url
:
"/api/billInfoBean/content/compare"
,
params
,
});
}
// 版本对比-根据两版版本与筛选条件获取条款列表(分页)
/**
* @param {billId,content,currentPage,currentVersion,isCn,originalVersion,pageSize,status}
* @header token
*/
export
function
getBillVersionCompare
(
params
)
{
return
request
({
method
:
"GET"
,
url
:
"/api/billInfoBean/versionCompare"
,
params
,
});
}
src/api/decree/home.js
浏览文件 @
5466ba6a
import
request
from
"@/api/request.js"
;
// 最新科技政令
export
function
getDepartmentList
()
{
export
function
getDepartmentList
(
params
)
{
return
request
({
method
:
'GET'
,
url
:
`/api/administrativeDict/department`
,
...
...
@@ -43,10 +43,10 @@ export function getDecreeArea(params) {
}
// 关键行政令
export
function
getKeyDecree
()
{
export
function
getKeyDecree
(
params
)
{
return
request
({
method
:
'GET'
,
url
:
`/api/administrativeOrderOverview/action`
,
url
:
`/api/administrativeOrderOverview/action
?pageSize=
${
params
.
pageSize
}
&pageNum=
${
params
.
pageNum
}
`
,
})
}
...
...
src/components/base/DecreeOriginal/index.vue
浏览文件 @
5466ba6a
...
...
@@ -239,6 +239,8 @@ watch(isTranslate, () => {
background-color
:
white
;
padding
:
0
60px
;
flex
:
auto
;
height
:
100%
;
min-height
:
0
;
display
:
flex
;
flex-direction
:
column
;
.report-header
{
...
...
@@ -324,9 +326,14 @@ watch(isTranslate, () => {
.report-main
{
flex
:
auto
;
min-height
:
0
;
box-sizing
:
border-box
;
padding-top
:
10px
;
:deep
(
.el-scrollbar
)
{
height
:
100%
;
}
.no-content
{
height
:
100%
;
display
:
flex
;
...
...
src/components/base/boxBackground/analysisBox.vue
浏览文件 @
5466ba6a
...
...
@@ -133,11 +133,12 @@ const emit = defineEmits(['save', 'download', 'collect'])
}
}
//
.header-btn {
.header-btn
{
// display: flex;
// justify-content: flex-end;
// gap: 8px;
// }
margin-right
:
10px
;
}
// .header-btn1 {
// position: absolute;
...
...
src/router/modules/bill.js
浏览文件 @
5466ba6a
...
...
@@ -13,7 +13,7 @@ const BillInfluenceLayout = () => import('@/views/bill/influence/index.vue')
const
BillInfluenceIndustry
=
()
=>
import
(
'@/views/bill/influence/industry/index.vue'
)
const
BillInfluenceScientificResearch
=
()
=>
import
(
'@/views/bill/influence/scientificResearch/index.vue'
)
const
BillRelevantCircumstance
=
()
=>
import
(
'@/views/bill/relevantCircumstance/index.vue'
)
const
Bill
OriginalText
=
()
=>
import
(
'@/views/bill/billOriginalText
/index.vue'
)
const
Bill
VersionCompare
=
()
=>
import
(
'@/views/bill/versionCompare
/index.vue'
)
const
billRoutes
=
[
...
...
@@ -36,14 +36,6 @@ const billRoutes = [
dynamicTitle
:
true
// 标记需要动态设置标题
},
children
:
[
{
path
:
"originalText"
,
name
:
"BillOriginalText"
,
component
:
BillOriginalText
,
meta
:
{
title
:
"法案原文"
}
},
// 法案分析路由
{
path
:
"bill"
,
...
...
@@ -138,6 +130,14 @@ const billRoutes = [
// meta: {
// title: "相关情况"
// }
},
{
path
:
"versionCompare"
,
name
:
"BillVersionCompare"
,
component
:
BillVersionCompare
,
meta
:
{
title
:
"版本对比"
}
}
]
},
...
...
src/router/modules/decree.js
浏览文件 @
5466ba6a
...
...
@@ -18,7 +18,7 @@ const decreeRoutes = [
name
:
"Decree"
,
component
:
Decree
,
meta
:
{
title
:
"
政令概览
"
title
:
"
科技政令概况
"
}
},
{
...
...
src/views/bill/billHome/ResourceLibrarySection.vue
浏览文件 @
5466ba6a
...
...
@@ -204,16 +204,20 @@
<
div
class
=
"coop-members"
>
<
div
class
=
"coop-member"
>
<
img
class
=
"coop-avatar"
:
src
=
"item.avatar || defaultAvatar"
alt
=
"committee-avatar"
/>
<
div
class
=
"coop-member-info"
>
<
div
class
=
"coop-member-name"
:
title
=
"item.name"
>
{{
item
.
name
}}
<
/div
>
<
div
v
-
if
=
"item.nameEn"
class
=
"coop-member-name-en"
:
title
=
"item.nameEn"
>
{{
item
.
nameEn
}}
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"coop-summary"
:
title
=
"item.desc"
>
{{
item
.
desc
}}
<
/div
>
<!--
<
div
class
=
"coop-summary"
:
title
=
"item.desc"
>
{{
item
.
desc
}}
<
/div> --
>
<
div
class
=
"coop-count"
>
{{
`${item.proposalSize ?? (item.bills || []).length
}
项重点法案`
}}
<
/div
>
<
slot
name
=
"committee-extra"
:
committee
=
"item"
/>
<
/div
>
<
div
class
=
"coop-proposals"
>
<
div
...
...
@@ -447,6 +451,7 @@ const handleGetCommitteeList = async () => {
const
descText
=
billInfoPage
[
0
]?.
originDepart
||
""
;
return
{
id
:
item
.
id
,
nameEn
:
item
.
nameEn
||
""
,
avatar
:
""
,
name
:
item
.
name
||
"-"
,
desc
:
descText
,
...
...
@@ -1232,6 +1237,12 @@ onMounted(() => {
min
-
width
:
0
;
}
.
coop
-
member
-
info
{
display
:
flex
;
flex
-
direction
:
column
;
min
-
width
:
0
;
}
.
coop
-
avatar
{
width
:
40
px
;
height
:
40
px
;
...
...
@@ -1273,6 +1284,17 @@ onMounted(() => {
text
-
overflow
:
ellipsis
;
}
.
coop
-
member
-
name
-
en
{
color
:
var
(
--
text
-
primary
-
65
-
color
);
font
-
family
:
"Microsoft YaHei"
;
font
-
size
:
14
px
;
font
-
weight
:
400
;
line
-
height
:
22
px
;
white
-
space
:
nowrap
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
}
.
coop
-
count
{
flex
-
shrink
:
0
;
color
:
#
1459
bb
;
...
...
@@ -1284,7 +1306,6 @@ onMounted(() => {
}
.
coop
-
proposals
{
margin
-
top
:
10
px
;
padding
-
top
:
10
px
;
border
-
top
:
1
px
solid
#
eaeced
;
display
:
grid
;
...
...
src/views/bill/billHome/index.vue
浏览文件 @
5466ba6a
...
...
@@ -127,7 +127,13 @@
<el-empty
v-if=
"!box5HasData"
description=
"暂无数据"
:image-size=
"100"
/>
<div
v-else
id=
"box5Chart"
class=
"overview-chart"
></div>
</div>
<div
class=
"overview-tip-row"
>
<TipTab
class=
"overview-tip"
/>
<AiButton
class=
"overview-tip-action"
@
mouseenter=
"handleShowAiPane('box5')"
/>
</div>
<div
v-if=
"aiPaneVisible.box5"
class=
"overview-ai-pane"
@
mouseleave=
"handleHideAiPane('box5')"
>
<AiPane
:aiContent=
"overviewAiContent.box5"
/>
</div>
</div>
</OverviewCard>
<OverviewCard
class=
"overview-card--single box6"
title=
"涉华法案领域分布"
:icon=
"box6HeaderIcon"
>
...
...
@@ -141,7 +147,13 @@
<el-empty
v-if=
"!box9HasData"
description=
"暂无数据"
:image-size=
"100"
/>
<div
v-else
id=
"box9Chart"
class=
"overview-chart"
></div>
</div>
<div
class=
"overview-tip-row"
>
<TipTab
class=
"overview-tip"
/>
<AiButton
class=
"overview-tip-action"
@
mouseenter=
"handleShowAiPane('box6')"
/>
</div>
<div
v-if=
"aiPaneVisible.box6"
class=
"overview-ai-pane"
@
mouseleave=
"handleHideAiPane('box6')"
>
<AiPane
:aiContent=
"overviewAiContent.box6"
/>
</div>
</div>
</OverviewCard>
</div>
...
...
@@ -157,7 +169,13 @@
<el-empty
v-if=
"!box7HasData"
description=
"暂无数据"
:image-size=
"100"
/>
<div
v-else
id=
"box7Chart"
class=
"overview-chart"
></div>
</div>
<div
class=
"overview-tip-row"
>
<TipTab
class=
"overview-tip"
/>
<AiButton
class=
"overview-tip-action"
@
mouseenter=
"handleShowAiPane('box7')"
/>
</div>
<div
v-if=
"aiPaneVisible.box7"
class=
"overview-ai-pane"
@
mouseleave=
"handleHideAiPane('box7')"
>
<AiPane
:aiContent=
"overviewAiContent.box7"
/>
</div>
</div>
</OverviewCard>
<OverviewCard
class=
"overview-card--single box8"
title=
"涉华法案进展分布"
:icon=
"box7HeaderIcon"
>
...
...
@@ -174,7 +192,13 @@
<div
id=
"box8Chart"
class=
"overview-chart box8-chart"
></div>
</
template
>
</div>
<div
class=
"overview-tip-row"
>
<TipTab
class=
"overview-tip"
/>
<AiButton
class=
"overview-tip-action"
@
mouseenter=
"handleShowAiPane('box8')"
/>
</div>
<div
v-if=
"aiPaneVisible.box8"
class=
"overview-ai-pane"
@
mouseleave=
"handleHideAiPane('box8')"
>
<AiPane
:aiContent=
"overviewAiContent.box8"
/>
</div>
</div>
</OverviewCard>
<OverviewCard
class=
"overview-card--single box9"
title=
"涉华法案关键条款"
:icon=
"box7HeaderIcon"
>
...
...
@@ -183,7 +207,13 @@
<el-empty
v-if=
"!wordCloudHasData"
description=
"暂无数据"
:image-size=
"100"
/>
<WordCloundChart
v-else
class=
"overview-chart"
width=
"100%"
height=
"100%"
:data=
"wordCloudData"
/>
</div>
<div
class=
"overview-tip-row"
>
<TipTab
class=
"overview-tip"
/>
<AiButton
class=
"overview-tip-action"
@
mouseenter=
"handleShowAiPane('box9')"
/>
</div>
<div
v-if=
"aiPaneVisible.box9"
class=
"overview-ai-pane"
@
mouseleave=
"handleHideAiPane('box9')"
>
<AiPane
:aiContent=
"overviewAiContent.box9"
/>
</div>
</div>
</OverviewCard>
</div>
...
...
@@ -221,6 +251,8 @@ import OverviewCard from "./OverviewCard.vue";
import
ResourceLibrarySection
from
"./ResourceLibrarySection.vue"
;
import
{
useContainerScroll
}
from
"@/hooks/useScrollShow"
;
import
TipTab
from
"@/components/base/TipTab/index.vue"
;
import
AiButton
from
"@/components/base/Ai/AiButton/index.vue"
;
import
AiPane
from
"@/components/base/Ai/AiPane/index.vue"
;
import
WordCloundChart
from
"@/components/base/WordCloundChart/index.vue"
;
import
getMultiLineChart
from
"./utils/multiLineChart"
;
...
...
@@ -370,6 +402,36 @@ const box7YearList = ref([
}
]);
const
aiPaneVisible
=
ref
({
box5
:
false
,
box6
:
false
,
box7
:
false
,
box8
:
false
,
box9
:
false
});
const
overviewAiContent
=
ref
({
box5
:
"智能总结生成中..."
,
box6
:
"智能总结生成中..."
,
box7
:
"智能总结生成中..."
,
box8
:
"智能总结生成中..."
,
box9
:
"智能总结生成中..."
});
const
handleShowAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
true
};
};
const
handleHideAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
false
};
};
const
box8selectetedTime
=
ref
(
"2025"
);
const
box8YearList
=
ref
([
{
...
...
@@ -2109,6 +2171,20 @@ onUnmounted(() => {
.overview-card-body
{
display
:
flex
;
flex-direction
:
column
;
position
:
relative
;
}
.overview-ai-pane
{
position
:
absolute
;
left
:
0
;
bottom
:
0
;
width
:
100%
;
z-index
:
3
;
pointer-events
:
none
;
.ai-pane-wrapper
{
pointer-events
:
auto
;
}
}
.overview-chart-wrap
{
...
...
@@ -2131,8 +2207,17 @@ onUnmounted(() => {
min-height
:
0
;
}
.overview-tip
{
.overview-tip
-row
{
margin-top
:
10px
;
position
:
relative
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.overview-tip-action
{
position
:
absolute
;
right
:
-30px
;
}
}
}
...
...
src/views/bill/billLayout/components/BillHeader.vue
浏览文件 @
5466ba6a
...
...
@@ -78,14 +78,6 @@
<div
class=
"right-box-bottom"
v-if=
"showActions"
>
<
template
v-if=
"isLoading"
>
<div
class=
"btn1 is-skeleton"
>
<div
class=
"icon"
>
<el-skeleton-item
class=
"skeleton-action-icon"
variant=
"text"
/>
</div>
<div
class=
"text"
>
<el-skeleton-item
class=
"skeleton-action-text"
variant=
"text"
/>
</div>
</div>
<div
class=
"btn3 is-skeleton"
>
<div
class=
"icon"
>
<el-skeleton-item
class=
"skeleton-action-icon"
variant=
"text"
/>
...
...
@@ -96,13 +88,6 @@
</div>
</
template
>
<
template
v-else
>
<div
class=
"btn1"
@
click=
"emit('open-original-text')"
>
<div
class=
"icon"
>
<img
:src=
"btnIconOriginalText"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
"法案原文"
}}
</div>
</div>
<div
class=
"btn3"
@
click=
"emit('open-analysis')"
>
<div
class=
"icon"
>
<img
:src=
"btnIconAnalysis"
alt=
""
/>
...
...
@@ -118,7 +103,6 @@
<
script
setup
>
import
{
computed
}
from
"vue"
;
import
btnIconOriginalText
from
"@/views/thinkTank/ReportDetail/images/btn-icon1.png"
;
import
btnIconAnalysis
from
"@/views/thinkTank/ReportDetail/images/btn-icon3.png"
;
const
props
=
defineProps
({
...
...
@@ -150,7 +134,7 @@ const props = defineProps({
const
isLoading
=
computed
(()
=>
!
props
.
billInfo
||
!
props
.
billInfo
.
billName
);
const
emit
=
defineEmits
([
"tab-click"
,
"open-
original-text"
,
"open-
analysis"
]);
const
emit
=
defineEmits
([
"tab-click"
,
"open-analysis"
]);
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
@@ -363,41 +347,6 @@ const emit = defineEmits(["tab-click", "open-original-text", "open-analysis"]);
justify-content
:
flex-end
;
gap
:
8px
;
.btn1
{
cursor
:
pointer
;
width
:
120px
;
height
:
36px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
6px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
gap
:
8px
;
.icon
{
width
:
16px
;
height
:
16px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
height
:
24px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
}
}
.btn3
{
cursor
:
pointer
;
width
:
120px
;
...
...
src/views/bill/billLayout/index.vue
浏览文件 @
5466ba6a
...
...
@@ -7,10 +7,9 @@
:defaultLogo=
"USALogo"
:tabs=
"mainHeaderBtnList"
:activeTitle=
"activeTitle"
:showTabs=
"
!isBillOriginalTextPage
"
:showActions=
"
!isBillOriginalTextPage
"
:showTabs=
"
showHeaderTabs
"
:showActions=
"
showHeaderActions
"
@
tab-click=
"handleClickMainHeaderBtn"
@
open-original-text=
"handleOpenBillOriginalText"
@
open-analysis=
"handleAnalysisClick"
/>
...
...
@@ -22,14 +21,13 @@
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
,
computed
,
watch
}
from
"vue"
;
import
{
ref
,
onMounted
,
watch
}
from
"vue"
;
import
router
from
"@/router"
;
import
{
useRoute
}
from
"vue-router"
;
import
{
getBillInfoGlobal
}
from
"@/api/bill"
;
import
BillHeader
from
"./components/BillHeader.vue"
;
const
route
=
useRoute
();
const
isBillOriginalTextPage
=
computed
(()
=>
route
.
path
===
"/billLayout/originalText"
);
import
icon1
from
"./assets/icons/icon1.svg"
;
import
icon1Active
from
"./assets/icons/icon1_active.svg"
;
...
...
@@ -55,16 +53,6 @@ const getBillInfoGlobalFn = async () => {
}
};
const
handleOpenBillOriginalText
=
()
=>
{
const
targetRoute
=
router
.
resolve
({
path
:
"/billLayout/originalText"
,
query
:
{
billId
:
route
.
query
.
billId
}
});
window
.
open
(
targetRoute
.
href
,
"_blank"
);
};
const
mainHeaderBtnList
=
ref
([
{
icon
:
icon1
,
...
...
@@ -93,6 +81,8 @@ const mainHeaderBtnList = ref([
]);
const
activeTitle
=
ref
(
"法案概况"
);
const
showHeaderTabs
=
ref
(
true
);
const
showHeaderActions
=
ref
(
true
);
const
getActiveTitleByRoutePath
=
path
=>
{
if
(
path
.
startsWith
(
"/billLayout/deepDig"
))
return
"深度挖掘"
;
...
...
@@ -102,8 +92,12 @@ const getActiveTitleByRoutePath = path => {
return
"法案概况"
;
};
const
syncActiveTitleFromRoute
=
()
=>
{
const
syncHeaderStateFromRoute
=
()
=>
{
const
currentPath
=
route
.
path
||
""
;
activeTitle
.
value
=
getActiveTitleByRoutePath
(
route
.
path
);
const
isVersionCompare
=
currentPath
.
startsWith
(
"/billLayout/versionCompare"
);
showHeaderTabs
.
value
=
!
isVersionCompare
;
showHeaderActions
.
value
=
!
isVersionCompare
;
};
const
handleClickMainHeaderBtn
=
item
=>
{
...
...
@@ -134,7 +128,7 @@ const handleAnalysisClick = () => {
onMounted
(()
=>
{
getBillInfoGlobalFn
();
// 以当前路由为准,避免 sessionStorage 造成高亮错乱
sync
ActiveTitl
eFromRoute
();
sync
HeaderStat
eFromRoute
();
// 兜底:如果未来出现未知路由且有缓存,再用缓存
const
cachedTitle
=
window
.
sessionStorage
.
getItem
(
"activeTitle"
);
if
(
!
activeTitle
.
value
&&
cachedTitle
)
activeTitle
.
value
=
cachedTitle
;
...
...
@@ -143,7 +137,7 @@ onMounted(() => {
watch
(
()
=>
route
.
path
,
()
=>
{
sync
ActiveTitl
eFromRoute
();
sync
HeaderStat
eFromRoute
();
},
{
immediate
:
true
}
);
...
...
src/views/bill/deepDig/poliContribution/index.vue
浏览文件 @
5466ba6a
...
...
@@ -697,6 +697,7 @@ onMounted(() => {
right
:
84px
;
top
:
15px
;
.btn
{
height
:
28px
;
padding
:
0
8px
;
...
...
src/views/bill/deepDig/processOverview/index.vue
浏览文件 @
5466ba6a
<
template
>
<div
class=
"process-overview-wrap"
>
<!--
<div
class=
"box-header"
>
<div
class=
"header-left"
></div>
<div
class=
"title"
>
流程概要
</div>
<div
class=
"header-right"
>
<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"
>
<div
class=
"left"
:style=
"
{ width: (maxLineWidth + 250) + 'px' }">
<div
class=
"top"
>
<div
class=
"top-line"
:style=
"
{ width: lineWidth }">
<div
class=
"top-line1"
></div>
</div>
<div
class=
"start"
>
<div
class=
"icon"
>
<img
src=
"./assets/images/logo1.png"
alt=
""
/>
</div>
<div
class=
"name"
>
{{
"参议院"
}}
</div>
</div>
<div
class=
"content-box"
:style=
"senateBoxStyle"
>
<div
class=
"item-box"
v-for=
"(item, index) in senateList"
:key=
"item.id"
style=
"width: 280px; flex-shrink: 0;"
>
<div
class=
"item-box-dot"
>
<img
src=
"./assets/images/top-line-dot.png"
alt=
""
/>
</div>
<div
class=
"item-content"
>
<div
class=
"item-header"
>
<div
class=
"item-title"
:title=
"item.actionTitle"
>
{{
item
.
actionTitle
}}
<span
v-if=
"item.versionId"
>
(
{{
item
.
versionId
}}
)
</span>
</div>
<div
class=
"item-header-icon"
@
click=
"handleClickDetail(true, item, $event)"
>
<img
src=
"./assets/images/item-header-icon.png"
alt=
""
/>
</div>
</div>
<div
class=
"item-info"
v-if=
"item.agreeVote !== null || item.disagreeVote !== null"
>
{{
(
item
.
agreeVote
||
0
)
+
"赞成:"
+
(
item
.
disagreeVote
||
0
)
+
"反对"
}}
</div>
<div
class=
"item-main"
v-if=
"item.fynrList && item.fynrList.length"
>
<div
class=
"item-main-item"
v-for=
"(sub, subIndex) in item.fynrList"
:key=
"subIndex"
>
<div
class=
"icon"
></div>
<CommonPrompt
:content=
"sub"
>
<div
class=
"text"
>
{{
sub
}}
</div>
</CommonPrompt>
</div>
</div>
</div>
<div
class=
"item-time"
>
{{
item
.
actionDate
}}
</div>
</div>
</div>
</div>
<div
class=
"bottom"
>
<div
class=
"bottom-line"
:style=
"
{ width: lineWidth }">
<div
class=
"bottom-line1"
></div>
</div>
<div
class=
"start"
>
<div
class=
"name"
>
{{
"众议院"
}}
</div>
<div
class=
"icon"
>
<img
src=
"./assets/images/logo2.png"
alt=
""
/>
</div>
</div>
<div
class=
"content-box"
:style=
"houseBoxStyle"
>
<div
class=
"item-box"
v-for=
"(item, index) in houseList"
:key=
"item.id"
style=
"width: 280px; flex-shrink: 0;"
>
<div
class=
"item-time"
>
{{
item
.
actionDate
}}
</div>
<div
class=
"item-box-dot"
>
<img
src=
"./assets/images/bottom-line-dot.png"
alt=
""
/>
</div>
<div
class=
"item-content"
>
<div
class=
"item-header"
>
<div
class=
"item-title"
:title=
"item.actionTitle"
>
{{
item
.
actionTitle
}}
<span
v-if=
"item.versionId"
>
(
{{
item
.
versionId
}}
)
</span>
</div>
<div
class=
"item-header-icon"
@
click=
"handleClickDetail(true, item, $event)"
>
<img
src=
"./assets/images/item-header-icon.png"
alt=
""
/>
</div>
</div>
<div
class=
"item-info"
v-if=
"item.agreeVote !== null || item.disagreeVote !== null"
>
{{
(
item
.
agreeVote
||
0
)
+
"赞成:"
+
(
item
.
disagreeVote
||
0
)
+
"反对"
}}
</div>
<div
class=
"item-main"
v-if=
"item.fynrList && item.fynrList.length"
>
<div
class=
"item-main-item"
v-for=
"(sub, subIndex) in item.fynrList"
:key=
"subIndex"
>
<div
class=
"icon"
></div>
<CommonPrompt
:content=
"sub"
>
<div
class=
"text"
>
{{
sub
}}
</div>
</CommonPrompt>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"right"
:style=
"
{ left: rightPos }">
<div
class=
"junction-dot"
>
<div
class=
"inner-dot"
></div>
</div>
<div
class=
"right-line"
></div>
</div>
</div>
</div>
-->
<AnalysisBox
title=
"流程概要"
:showAllBtn=
"false"
>
<div
class=
"main"
>
<div
class=
"left"
:style=
"
{ width: (maxLineWidth + 250) + 'px' }">
...
...
@@ -207,17 +99,6 @@
</div>
</div>
</div>
<!--
<div
class=
"footer"
>
<div
class=
"footer-left"
>
<img
src=
"./assets/icons/right-icon1.png"
alt=
""
/>
</div>
<div
class=
"footer-center"
>
立法过程始于2025年2月共和党启动预算框架谈判,5月22日众议院以215:214的1票优势通过初始版本;随后参议院历经16小时全文朗读和马拉松式辩论,于7月1日以51:50的票数通过修订版(副总统万斯投关键票);因参议院版本大改(如永久化减税、提高债限至5万亿美元),法案重返众议院审议,经程序性投票(219:213)和最终表决(218:214),于7月3日深夜通过;特朗普在7月4日独立日签署生效,全程凸显两党对立、党内分歧及程序博弈。
</div>
<div
class=
"footer-right"
>
<img
src=
"./assets/icons/arrow-right.png"
alt=
""
/>
</div>
</div>
-->
</AnalysisBox>
<ProcessOverviewDetailDialog
...
...
src/views/bill/index.vue
浏览文件 @
5466ba6a
...
...
@@ -29,7 +29,7 @@ const siderBtnList = ref([
path
:
'/billLayout/bill/background'
},
{
name
:
'
内容概要
'
,
name
:
'
法案原文
'
,
path
:
'/billLayout/bill/template'
},
])
...
...
@@ -38,7 +38,7 @@ const siderBtnActive = ref("法案简介");
const
getSiderActiveByRoutePath
=
path
=>
{
if
(
path
.
includes
(
"/billLayout/bill/background"
))
return
"法案背景"
;
if
(
path
.
includes
(
"/billLayout/bill/template"
))
return
"
内容概要
"
;
if
(
path
.
includes
(
"/billLayout/bill/template"
))
return
"
法案原文
"
;
return
"法案简介"
;
};
...
...
src/views/bill/introdoction/index.vue
浏览文件 @
5466ba6a
<
template
>
<div
class=
"introduction-wrap"
>
<WarningPane
v-if=
"riskSignal"
class=
"risk-signal-pane-top"
:warnningLevel=
"riskSignal.riskLevel"
:warnningContent=
"riskSignal.riskContent"
/>
<div
class=
"introduction-wrap-content"
>
<div
class=
"introduction-wrap-left"
>
<div
class=
"introduction-wrap-left-box1"
>
<!--
<div
class=
"box-header"
>
<div
class=
"header-left"
></div>
<div
class=
"title"
>
基本信息
</div>
<div
class=
"header-right"
>
<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"
>
<div
class=
"box1-left"
>
<img
:src=
" basicInfo.imageUrl || defaultBill"
alt=
""
/>
</div>
<div
class=
"box1-right"
>
<div
class=
"box1-right-item"
>
<div
class=
"item-left"
>
提案人:
</div>
<div
class=
"item-right"
>
{{
basicInfo
.
tarName
}}
</div>
</div>
<div
class=
"box1-right-item"
>
<div
class=
"item-left"
>
提出时间:
</div>
<div
class=
"item-right"
>
{{
basicInfo
.
introductionDate
}}
</div>
</div>
<div
class=
"box1-right-item"
>
<div
class=
"item-left"
>
相关领域:
</div>
<div
class=
"item-right1"
>
<div
class=
"right1-item"
v-for=
"item in basicInfo.hylyList"
:key=
"item"
>
{{
item
}}
</div>
</div>
</div>
<div
class=
"box1-right-item"
>
<div
class=
"item-left"
>
委员会报告:
</div>
<div
class=
"item-right2"
v-if=
"basicInfo.reportList"
>
<div
class=
"right2-item"
v-for=
"(item, index) in basicInfo.reportList"
:key=
"index"
>
{{
item
}}
</div>
</div>
</div>
<div
class=
"box1-right-item"
>
<div
class=
"item-left"
>
表决记录:
</div>
<div
class=
"item-right3"
>
{{
`全程共进行${basicInfo.votetotal
}
次唱名表决`
}}
<
/div
>
<
/div
>
<
div
class
=
"box1-right-item"
>
<
div
class
=
"item-left"
>
最近状态:
<
/div
>
<
div
class
=
"item-right3"
>
{{
basicInfo
.
status
}}
<
/div
>
<
/div
>
<
div
class
=
"box1-right-item"
>
<
div
class
=
"item-left"
>
立案流程:
<
/div
>
<
div
class
=
"item-right4"
>
<
div
class
=
"step"
v
-
for
=
"(item, index) in basicInfo.stageList ? [...basicInfo.stageList].reverse() : []"
:
key
=
"index"
:
style
=
"{ zIndex: (basicInfo.stageList?.length || 0) - index
}
"
>
<
div
class
=
"step-box"
:
class
=
"{ 'step-box-active': index === (basicInfo.stageList?.length || 0) - 1
}
"
>
{{
item
}}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div> --
>
<AnalysisBox
title=
"基本信息"
:showAllBtn=
"false"
>
<div
class=
"box1-main"
>
<div
class=
"box1-left"
>
...
...
@@ -88,14 +28,13 @@
<div
class=
"box1-right-item"
>
<div
class=
"item-left"
>
相关领域:
</div>
<div
class=
"item-right1"
>
<!--
<
div
class
=
"right1-item"
v
-
for
=
"item in basicInfo.hylyList"
:
key
=
"item"
>
{{
item
}}
<
/div> --
>
<
AreaTag
v
-
for
=
"item, index in basicInfo.hylyList"
:
key
=
"index"
:
tagName
=
"item"
/>
<AreaTag
v-for=
"item in hylyList"
:key=
"item"
:tagName=
"item"
/>
</div>
</div>
<div
class=
"box1-right-item"
>
<div
class=
"item-left"
>
委员会报告:
</div>
<
div
class
=
"item-right2"
v
-
if
=
"
basicInfo.reportList
"
>
<
div
class
=
"right2-item"
v
-
for
=
"(item, index) in
basicInfo.reportList"
:
key
=
"index
"
>
<div
class=
"item-right2"
v-if=
"
reportList.length
"
>
<div
class=
"right2-item"
v-for=
"(item, index) in
reportList"
:key=
"getReportKey(item, index)
"
>
{{
item
}}
</div>
</div>
...
...
@@ -111,11 +50,14 @@
<
div
class
=
"box1-right-item"
>
<
div
class
=
"item-left"
>
立案流程:
<
/div
>
<
div
class
=
"item-right4"
>
<
div
class
=
"step"
v
-
for
=
"(item, index) in basicInfo.stageList ? [...basicInfo.stageList].reverse() : []"
:
key
=
"index"
:
style
=
"{ zIndex: (basicInfo.stageList?.length || 0) - index
}
"
>
<
div
v
-
for
=
"(item, index) in reversedStageList"
:
key
=
"getStageKey(item, index)"
class
=
"step"
:
style
=
"{ zIndex: getStageZIndex(index)
}
"
>
<
div
class
=
"step-box"
:
class
=
"{ 'step-box-active': index ===
(basicInfo.stageList?.length || 0) - 1
}
"
>
:
class
=
"{ 'step-box-active': index ===
stageActiveIndex
}
"
>
{{
item
}}
<
/div
>
<
/div
>
...
...
@@ -126,33 +68,8 @@
<
/AnalysisBox
>
<
/div
>
<
div
class
=
"introduction-wrap-left-box2"
>
<!--
<
div
class
=
"box-header"
>
<
div
class
=
"header-left"
><
/div
>
<
div
class
=
"title"
>
法案进展
<
/div
>
<
div
class
=
"header-right"
>
<
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"
>
<
div
class
=
"box2-main-center"
>
<
STimeline
:
dataList
=
"timelineData"
/>
<
/div
>
<
/div> --
>
<
AnalysisBox
title
=
"法案进展"
:
showAllBtn
=
"false"
>
<
template
#
header
-
btn
>
<!--
<
div
class
=
"progress-header-btns"
>
<
div
class
=
"btn"
:
class
=
"{ btnActive: progressMode === 'latest'
}
"
@
click
=
"handleSwitchProgressMode('latest')"
>
最新进展
<
/div
>
<
div
class
=
"btn"
:
class
=
"{ btnActive: progressMode === 'early'
}
"
@
click
=
"handleSwitchProgressMode('early')"
>
前期进程
<
/div
>
<
/div> --
>
<
/template
>
<
div
class
=
"box2-main"
>
<
div
class
=
"box2-main-center"
>
...
...
@@ -163,105 +80,10 @@
<
/div
>
<
/div
>
<
div
class
=
"introduction-wrap-right"
>
<!--
<
div
class
=
"box-header"
>
<
div
class
=
"header-left"
><
/div
>
<
div
class
=
"title"
>
提出人
<
/div
>
<
div
class
=
"header-right"
>
<
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
=
"introduction-wrap-right-main"
>
<
div
class
=
"right-main-box1"
>
<
div
class
=
"name-box"
>
<
div
class
=
"person-box"
>
<
div
class
=
"person-item"
:
class
=
"{ nameItemActive: box3BtnActive === item.name
}
"
@
click
=
"handleClcikBox3Btn(item.name, index)"
v
-
for
=
"(item, index) in personList"
:
key
=
"index"
>
{{
item
.
name
}}
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"info-box"
>
<
div
class
=
"info-left"
>
<
img
:
src
=
"defaultAvatar"
alt
=
""
@
click
=
"handleClickAvatar(curPerson)"
/>
<
div
class
=
"usr-icon1"
>
<
img
src
=
"./assets/images/usr-icon1.png"
alt
=
""
/>
<
/div
>
<
div
class
=
"usr-icon2"
>
<
img
src
=
"./assets/images/usr-icon2.png"
alt
=
""
/>
<
/div
>
<
/div
>
<
div
class
=
"info-right"
>
<
div
class
=
"info-right-title"
@
click
=
"handleClickAvatar(curPerson)"
>
{{
curPerson
.
name
}}
<
/div
>
<
div
class
=
"info-right-item"
>
<
div
class
=
"item-left"
>
英文名
:
<
/div
>
<
div
class
=
"item-right"
>
{{
curPerson
.
ename
}}
<
/div
>
<
/div
>
<
div
class
=
"info-right-item"
>
<
div
class
=
"item-left"
>
党派
:
<
/div
>
<
div
class
=
"item-right"
>
{{
curPerson
.
dp
}}
<
/div
>
<
/div
>
<
div
class
=
"info-right-item"
>
<
div
class
=
"item-left"
>
选区
:
<
/div
>
<
div
class
=
"item-right"
>
{{
curPerson
.
xq
}}
<
/div
>
<
/div
>
<
div
class
=
"info-right-item"
>
<
div
class
=
"item-left"
>
职位
:
<
/div
>
<
div
class
=
"item-right"
>
{{
curPerson
.
zw
}}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"right-main-box2"
v
-
if
=
"curPerson.tagList && curPerson.tagList.length"
>
<
div
class
=
"tag-box"
:
class
=
"{
status0: index % 4 === 0,
status1: index % 4 === 1,
status2: index % 4 === 2,
status3: index % 4 === 3
}
"
v
-
for
=
"(tag, index) in curPerson.tagList"
:
key
=
"index"
>
{{
tag
.
industryName
}}
<
/div
>
<
/div
>
<
div
class
=
"right-main-box3"
>
<
div
class
=
"right-main-box3-header"
>
<
div
class
=
"icon"
>
<
img
src
=
"./assets/images/right-main-box3-header-icon.svg"
alt
=
""
/>
<
/div
>
<
div
class
=
"title"
>
人物动态
<
/div
>
<
/div
>
<
div
class
=
"right-main-box3-main"
>
<
el
-
timeline
style
=
"max-width: 500px"
>
<
el
-
timeline
-
item
:
timestamp
=
"item.newsDate"
placement
=
"top"
v
-
for
=
"(item, index) in curPerson.newsList?.slice(0, 3)"
:
key
=
"index"
>
<
div
class
=
"timeline-content"
>
{{
item
.
newsContent
}}
<
/div
>
<
/el-timeline-item
>
<
/el-timeline
>
<
/div
>
<
div
class
=
"right-main-box3-footer"
>
<
div
class
=
"btn-more"
@
click
=
"handleClickMore2"
>
<
img
src
=
"../assets/images/btn-more.png"
alt
=
""
/>
<
/div
>
<
/div
>
<
/div
>
<
/div> --
>
<
AnalysisBox
title
=
"提出人"
:
showAllBtn
=
"false"
>
<
div
class
=
"introduction-wrap-right-main"
>
<
div
class
=
"right-main-box1"
>
<
div
class
=
"name-box"
>
<!--
<
el
-
select
v
-
model
=
"selectValue"
placeholder
=
"请选择"
style
=
"width: 180px; margin: 0 10px"
@
change
=
"handleChangeFaId"
>
<
el
-
option
v
-
for
=
"item in faList"
:
key
=
"item.value"
:
label
=
"item.label"
:
value
=
"item.id"
/>
<
/el-select> --
>
<
div
class
=
"person-box"
>
<
div
class
=
"person-item"
:
class
=
"{ nameItemActive: box3BtnActive === item.name
}
"
@
click
=
"handleClcikBox3Btn(item.name, index)"
v
-
for
=
"(item, index) in personList"
:
key
=
"index"
>
...
...
@@ -301,7 +123,6 @@
<
/div
>
<
/div
>
<
div
class
=
"right-main-box2"
v
-
if
=
"curPerson.tagList && curPerson.tagList.length"
>
<!--
<
WordCloudMap
:
data
=
"wordCloudData"
:
shape
=
"circle"
/>
-->
<
div
class
=
"tag-box status"
v
-
for
=
"(tag, index) in curPerson.tagList"
:
key
=
"index"
>
{{
tag
}}
<
/div
>
...
...
@@ -321,17 +142,6 @@
{{
item
.
newsContent
}}
<
/div
>
<
/el-timeline-item
>
<!--
<
el
-
timeline
-
item
timestamp
=
"2018/4/3"
placement
=
"top"
>
<
div
class
=
"timeline-content"
>
借
OBBBA
通过势头,在得州巩固军工、能源集团支持,为
2026
年连任铺路,同时协调党内资源争夺关键摇摆选区。
<
/div
>
<
/el-timeline-item
>
<
el
-
timeline
-
item
timestamp
=
"2018/4/2"
placement
=
"top"
>
<
div
class
=
"timeline-content"
>
特朗普力挺阿灵顿,白宫声明强调法案“美丽且必要”,双方矛盾凸显共和党内
商业资本与传统能源势力裂痕。
<
/div
>
<
/el-timeline-item> --
>
<
/el-timeline
>
<
/div
>
<
/div
>
...
...
@@ -339,13 +149,15 @@
<
/AnalysisBox
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
script
setup
>
import
{
onMounted
,
ref
}
from
"vue"
;
import
{
computed
,
onMounted
,
ref
}
from
"vue"
;
import
{
useRoute
,
useRouter
}
from
"vue-router"
;
import
WordCloudMap
from
"./WordCloudMap.vue"
;
import
STimeline
from
"./STimeline.vue"
;
import
WarningPane
from
"@/components/base/WarningPane/index.vue"
;
import
{
getBillInfo
,
getBillPerson
,
getBillEvent
,
getBillDyqk
}
from
"@/api/bill"
;
import
defaultAvatar
from
"../assets/images/default-icon1.png"
;
import
defaultNew
from
"../assets/images/default-icon-news.png"
;
...
...
@@ -383,6 +195,19 @@ const handleSwitchProgressMode = mode => {
const
basicInfo
=
ref
({
}
);
const
riskSignal
=
computed
(()
=>
basicInfo
.
value
?.
riskSignalVO
||
null
);
const
hylyList
=
computed
(()
=>
(
Array
.
isArray
(
basicInfo
.
value
?.
hylyList
)
?
basicInfo
.
value
.
hylyList
:
[]));
const
reportList
=
computed
(()
=>
(
Array
.
isArray
(
basicInfo
.
value
?.
reportList
)
?
basicInfo
.
value
.
reportList
:
[]));
const
reversedStageList
=
computed
(()
=>
{
const
list
=
Array
.
isArray
(
basicInfo
.
value
?.
stageList
)
?
basicInfo
.
value
.
stageList
:
[];
return
[...
list
].
reverse
();
}
);
const
stageListLength
=
computed
(()
=>
(
Array
.
isArray
(
basicInfo
.
value
?.
stageList
)
?
basicInfo
.
value
.
stageList
.
length
:
0
));
const
stageActiveIndex
=
computed
(()
=>
stageListLength
.
value
-
1
);
const
getStageZIndex
=
index
=>
stageListLength
.
value
-
index
;
const
getStageKey
=
(
item
,
index
)
=>
`${item
}
-${index
}
`
;
const
getReportKey
=
(
item
,
index
)
=>
`${item
}
-${index
}
`
;
const
handleGetBasicInfo
=
async
()
=>
{
const
params
=
{
id
:
billId
.
value
...
...
@@ -452,8 +277,10 @@ onMounted(() => {
<
style
lang
=
"scss"
scoped
>
.
introduction
-
wrap
{
width
:
100
%
;
height
:
880
px
;
min
-
height
:
880
px
;
height
:
auto
;
display
:
flex
;
flex
-
direction
:
column
;
.
progress
-
header
-
btns
{
display
:
flex
;
...
...
@@ -536,6 +363,19 @@ onMounted(() => {
}
}
.
risk
-
signal
-
pane
-
top
{
width
:
1600
px
;
margin
-
top
:
16
px
;
margin
-
right
:
18
px
;
height
:
116
px
;
min
-
height
:
116
px
;
flex
-
shrink
:
0
;
}
.
introduction
-
wrap
-
content
{
display
:
flex
;
}
.
introduction
-
wrap
-
left
{
width
:
1064
px
;
margin
-
top
:
16
px
;
...
...
@@ -1358,6 +1198,7 @@ onMounted(() => {
line
-
height
:
26
px
;
display
:
-
webkit
-
box
;
-
webkit
-
box
-
orient
:
vertical
;
line
-
clamp
:
3
;
-
webkit
-
line
-
clamp
:
3
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
...
...
src/views/bill/template/index.vue
浏览文件 @
5466ba6a
...
...
@@ -387,7 +387,7 @@ const handleChangeBill = val => {
};
const
handleOpenVersionCompare
=
()
=>
{
const
targetUrl
=
`/billLayout/
deepDig/processOverview
?billId=
${
route
.
query
.
billId
}
`
;
const
targetUrl
=
`/billLayout/
versionCompare
?billId=
${
route
.
query
.
billId
}
`
;
window
.
open
(
targetUrl
,
"_blank"
);
};
...
...
@@ -404,8 +404,8 @@ const handleGetBillList = async () => {
billList
.
value
=
rawList
.
map
(
item
=>
{
return
{
label
:
item
.
bbmc
,
value
:
item
.
bbmc
label
:
item
.
contentZh
,
value
:
item
.
contentZh
};
})
.
filter
(
item
=>
{
...
...
src/views/bill/versionCompare/assert/icons/translate-icons.svg
0 → 100644
浏览文件 @
5466ba6a
<svg
viewBox=
"0 0 13.5996 12.666"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"13.599609"
height=
"12.666016"
fill=
"none"
customFrame=
"#000000"
>
<path
id=
"矢量 455"
d=
"M2 8.66667L2 10C1.99999 10.0416 2.00192 10.0831 2.00578 10.1245C2.00965 10.1659 2.01545 10.207 2.02317 10.2479C2.03089 10.2888 2.0405 10.3292 2.052 10.3691C2.0635 10.4091 2.07684 10.4484 2.09202 10.4872C2.1072 10.5259 2.12416 10.5638 2.14289 10.6009C2.16162 10.6381 2.18204 10.6742 2.20416 10.7095C2.22627 10.7447 2.24998 10.7788 2.27529 10.8118C2.3006 10.8448 2.32739 10.8765 2.35566 10.907C2.38394 10.9375 2.41358 10.9666 2.44458 10.9944C2.47558 11.0221 2.50781 11.0483 2.54126 11.073C2.57472 11.0977 2.60925 11.1208 2.64487 11.1423C2.68048 11.1637 2.71702 11.1835 2.75448 11.2016C2.79195 11.2196 2.83017 11.2359 2.86916 11.2503C2.90814 11.2648 2.94772 11.2775 2.98788 11.2882C3.02805 11.299 3.06863 11.3079 3.10963 11.3149C3.15063 11.3218 3.19186 11.3269 3.23333 11.33L3.33333 11.3333L5.33333 11.3333L5.33333 12.6667L3.33333 12.6667C3.246 12.6667 3.15887 12.6624 3.07195 12.6538C2.98504 12.6453 2.89875 12.6325 2.81309 12.6154C2.72743 12.5984 2.64282 12.5772 2.55924 12.5518C2.47566 12.5265 2.39353 12.4971 2.31284 12.4637C2.23216 12.4303 2.1533 12.393 2.07627 12.3518C1.99925 12.3106 1.92443 12.2658 1.85181 12.2173C1.77919 12.1687 1.70913 12.1168 1.64162 12.0614C1.57411 12.006 1.50947 11.9474 1.44771 11.8856C1.38596 11.8239 1.32738 11.7592 1.27197 11.6917C1.21657 11.6242 1.1646 11.5541 1.11608 11.4815C1.06756 11.4089 1.02271 11.3341 0.981543 11.2571C0.940373 11.18 0.903077 11.1012 0.869654 11.0205C0.836232 10.9398 0.806845 10.8577 0.781492 10.7741C0.75614 10.6905 0.734944 10.6059 0.717906 10.5202C0.700867 10.4346 0.688068 10.3483 0.679507 10.2614C0.670947 10.1745 0.666666 10.0873 0.666666 10L0.666666 8.66667L2 8.66667L2 8.66667ZM10.6667 5.33333L13.6 12.6667L12.1633 12.6667L11.3627 10.6667L8.636 10.6667L7.83667 12.6667L6.40067 12.6667L9.33333 5.33333L10.6667 5.33333L10.6667 5.33333ZM10 7.25667L9.16867 9.33333L10.83 9.33333L10 7.25667ZM4 0L4 1.33333L6.66667 1.33333L6.66667 6L4 6L4 8L2.66667 8L2.66667 6L0 6L0 1.33333L2.66667 1.33333L2.66667 0L4 0ZM10 0.666667C10.0873 0.666667 10.1745 0.670947 10.2614 0.679507C10.3483 0.688068 10.4346 0.700867 10.5202 0.717906C10.6059 0.734944 10.6905 0.75614 10.7741 0.781492C10.8577 0.806845 10.9398 0.836232 11.0205 0.869654C11.1012 0.903077 11.18 0.940373 11.2571 0.981543C11.3341 1.02271 11.4089 1.06756 11.4815 1.11608C11.5541 1.1646 11.6242 1.21657 11.6917 1.27197C11.7592 1.32738 11.8239 1.38596 11.8856 1.44772C11.9474 1.50947 12.006 1.57411 12.0614 1.64162C12.1168 1.70913 12.1687 1.77919 12.2173 1.85181C12.2658 1.92443 12.3106 1.99925 12.3518 2.07628C12.393 2.1533 12.4303 2.23216 12.4637 2.31284C12.4971 2.39353 12.5265 2.47566 12.5518 2.55924C12.5772 2.64282 12.5984 2.72743 12.6154 2.81309C12.6325 2.89875 12.6453 2.98504 12.6538 3.07195C12.6624 3.15887 12.6667 3.246 12.6667 3.33333L12.6667 4.66667L11.3333 4.66667L11.3333 3.33333C11.3333 3.28966 11.3312 3.2461 11.3269 3.20264C11.3226 3.15919 11.3162 3.11604 11.3077 3.07321C11.2992 3.03038 11.2886 2.98807 11.2759 2.94629C11.2632 2.9045 11.2486 2.86343 11.2318 2.82309C11.2151 2.78274 11.1965 2.74332 11.1759 2.7048C11.1553 2.66629 11.1329 2.62888 11.1086 2.59257C11.0844 2.55626 11.0584 2.52123 11.0307 2.48748C11.003 2.45372 10.9737 2.4214 10.9428 2.39052C10.9119 2.35965 10.8796 2.33036 10.8459 2.30265C10.8121 2.27495 10.7771 2.24897 10.7408 2.22471C10.7045 2.20045 10.667 2.17802 10.6285 2.15744C10.59 2.13685 10.5506 2.1182 10.5102 2.10149C10.4699 2.08478 10.4288 2.07009 10.387 2.05741C10.3453 2.04474 10.303 2.03414 10.2601 2.02562C10.2173 2.0171 10.1741 2.0107 10.1307 2.00642C10.0872 2.00214 10.0437 2 10 2L8 2L8 0.666667L10 0.666667L10 0.666667ZM2.66667 2.66667L1.33333 2.66667L1.33333 4.66667L2.66667 4.66667L2.66667 2.66667ZM5.33333 2.66667L4 2.66667L4 4.66667L5.33333 4.66667L5.33333 2.66667Z"
fill=
"rgb(95,101,108)"
fill-rule=
"nonzero"
/>
</svg>
src/views/bill/versionCompare/index.vue
0 → 100644
浏览文件 @
5466ba6a
<
template
>
<div
class=
"version-compare-wrap"
>
<div
class=
"compare-top"
>
<div
class=
"compare-top-col"
>
<div
class=
"compare-top-label"
>
原版本:
</div>
<el-select
v-model=
"oldVersionId"
placeholder=
"请选择版本"
class=
"compare-top-select"
clearable
>
<el-option
v-for=
"item in versionOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
:disabled=
"item.value === newVersionId"
/>
</el-select>
</div>
<div
class=
"compare-top-col"
>
<div
class=
"compare-top-label"
>
现版本:
</div>
<el-select
v-model=
"newVersionId"
placeholder=
"请选择版本"
class=
"compare-top-select"
clearable
>
<el-option
v-for=
"item in versionOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
:disabled=
"item.value === oldVersionId"
/>
</el-select>
</div>
</div>
<div
class=
"compare-tools"
>
<div
class=
"compare-tools-tabs"
ref=
"tabsWrapRef"
>
<button
v-for=
"(tab, index) in diffTabs"
:key=
"tab.value"
class=
"compare-tools-tab"
:class=
"
{ 'is-active': diffType === tab.value }"
type="button"
@click="handleDiffTabClick(tab.value)"
:ref="el => setTabRef(el, index)"
>
<span
class=
"label"
>
{{
tab
.
label
}}
</span>
<span
class=
"count"
v-if=
"diffCounts[tab.value] !== null && diffCounts[tab.value] !== undefined"
>
{{
diffCounts
[
tab
.
value
]
}}
</span>
</button>
<span
class=
"compare-tools-tabs-active"
:style=
"activeBarStyle"
></span>
</div>
<div
class=
"compare-tools-actions"
>
<el-checkbox
v-model=
"onlyChinaRelated"
label=
"只看涉华条款"
size=
"large"
/>
<div
class=
"compare-tools-switches"
>
<div
class=
"compare-tools-switch"
>
<el-switch
v-model=
"termsHighlight"
inline-prompt
/>
<span
class=
"label"
>
高亮实体
</span>
</div>
<div
class=
"compare-tools-switch"
>
<el-switch
v-model=
"termsShowOriginal"
inline-prompt
/>
<span
class=
"label"
>
<img
class=
"label-icon"
:src=
"translateIcon"
alt=
""
/>
显示原文
</span>
</div>
</div>
<div
class=
"find-word-wrap"
>
<div
class=
"find-word-box"
v-if=
"findWordBox"
>
<div
class=
"find-word-input"
>
<el-input
ref=
"findWordInputRef"
v-model=
"findWordTxt"
placeholder=
"查找条款内容"
clearable
@
input=
"handleUpdateWord"
/>
</div>
<div
class=
"find-word-limit"
>
{{
findWordNum
}}
/
{{
findWordMax
}}
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('last')"
>
<el-icon><ArrowUp
/></el-icon>
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('next')"
>
<el-icon><ArrowDown
/></el-icon>
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('close')"
>
<el-icon><Close
/></el-icon>
</div>
</div>
<el-button
type=
"primary"
plain
class=
"find-word-open-btn"
@
click=
"handleFindWord('open')"
>
<el-icon
class=
"find-word-open-icon"
><Search
/></el-icon>
查找
</el-button>
</div>
</div>
</div>
<div
class=
"compare-columns"
v-loading=
"isLoading"
@
click=
"handleEntityClick"
>
<div
class=
"compare-row"
v-for=
"(pair, index) in comparePairs"
:key=
"getPairKey(pair, index)"
>
<div
class=
"compare-cell"
>
<template
v-if=
"pair?.oldTerm"
>
<div
class=
"term-body"
>
<div
class=
"term-main"
>
<div
class=
"term-row term-row-cn"
>
<div
class=
"term-no-cn"
>
第
{{
pair
.
oldTerm
.
tkxh
}}
条.
</div>
<div
class=
"term-content-cn"
v-html=
"getTermContentHtml(pair.oldTerm, 'cn')"
></div>
</div>
<div
class=
"term-row term-row-en"
v-if=
"termsShowOriginal"
>
<div
class=
"term-no-en"
>
Sec.
{{
pair
.
oldTerm
.
tkxh
}}
</div>
<div
class=
"term-content-en"
v-html=
"getTermContentHtml(pair.oldTerm, 'en')"
></div>
</div>
</div>
</div>
</
template
>
<div
v-else
class=
"term-empty"
>
—
</div>
</div>
<div
class=
"compare-cell"
>
<
template
v-if=
"pair?.newTerm"
>
<div
class=
"term-body"
>
<div
class=
"term-main"
>
<div
class=
"term-row term-row-cn"
>
<div
class=
"term-no-cn"
>
第
{{
pair
.
newTerm
.
tkxh
}}
条.
</div>
<div
class=
"term-content-cn"
v-html=
"getTermContentHtml(pair.newTerm, 'cn')"
></div>
</div>
<div
class=
"term-row term-row-en"
v-if=
"termsShowOriginal"
>
<div
class=
"term-no-en"
>
Sec.
{{
pair
.
newTerm
.
tkxh
}}
</div>
<div
class=
"term-content-en"
v-html=
"getTermContentHtml(pair.newTerm, 'en')"
></div>
</div>
</div>
</div>
</
template
>
<div
v-else
class=
"term-empty"
>
—
</div>
</div>
</div>
</div>
<div
class=
"compare-footer"
>
<div
class=
"compare-footer-text"
>
{{ `共 ${total} 项` }}
</div>
<div
class=
"compare-footer-right"
>
<el-pagination
background
layout=
"prev, pager, next"
:total=
"total"
v-model:current-page=
"currentPage"
v-model:page-size=
"pageSize"
@
current-change=
"handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<
script
setup
>
import
{
computed
,
nextTick
,
onMounted
,
ref
,
watch
}
from
"vue"
;
import
{
useRoute
}
from
"vue-router"
;
import
{
getBillContentId
,
getBillVersionCompare
}
from
"@/api/bill"
;
import
{
extractTextEntity
}
from
"@/api/intelligent/index"
;
import
{
ArrowDown
,
ArrowUp
,
Close
,
Search
}
from
"@element-plus/icons-vue"
;
import
translateIcon
from
"./assert/icons/translate-icons.svg"
;
const
route
=
useRoute
();
const
billId
=
computed
(()
=>
route
.
query
.
billId
);
const
versionOptions
=
ref
([]);
const
oldVersionId
=
ref
(
""
);
const
newVersionId
=
ref
(
""
);
const
diffType
=
ref
(
"CHANGE"
);
const
onlyChinaRelated
=
ref
(
false
);
const
keyword
=
ref
(
""
);
const
termsHighlight
=
ref
(
true
);
const
termsShowOriginal
=
ref
(
true
);
const
currentPage
=
ref
(
1
);
const
pageSize
=
ref
(
10
);
const
total
=
ref
(
0
);
const
diffTabs
=
[
{
label
:
"变更"
,
value
:
"CHANGE"
},
{
label
:
"新增"
,
value
:
"ADD"
},
{
label
:
"删除"
,
value
:
"DELETE"
}
];
const
diffCounts
=
ref
({
CHANGE
:
0
,
ADD
:
0
,
DELETE
:
0
});
const
tabsWrapRef
=
ref
(
null
);
const
tabItemRefs
=
ref
([]);
const
activeBarStyle
=
ref
({});
const
findWordBox
=
ref
(
false
);
const
findWordInputRef
=
ref
();
const
findWordTxt
=
ref
(
""
);
const
findWordKeyword
=
ref
(
""
);
const
findWordNum
=
ref
(
0
);
const
findWordMax
=
ref
(
0
);
const
findWordTimer
=
ref
(
null
);
const
isLoading
=
ref
(
false
);
const
comparePairs
=
ref
([]);
const
compareRequestToken
=
ref
(
0
);
const
handleLoadVersionOptions
=
async
()
=>
{
if
(
!
billId
.
value
)
{
versionOptions
.
value
=
[];
oldVersionId
.
value
=
""
;
newVersionId
.
value
=
""
;
return
;
}
const
res
=
await
getBillContentId
({
id
:
billId
.
value
});
const
rawList
=
Array
.
isArray
(
res
?.
data
)
?
res
.
data
:
[];
const
seen
=
new
Set
();
versionOptions
.
value
=
rawList
.
map
(
item
=>
{
return
{
label
:
item
?.
contentZh
,
value
:
item
?.
contentZh
};
})
.
filter
(
item
=>
item
.
value
)
.
filter
(
item
=>
{
if
(
seen
.
has
(
item
.
value
))
return
false
;
seen
.
add
(
item
.
value
);
return
true
;
});
if
(
!
versionOptions
.
value
.
length
)
{
oldVersionId
.
value
=
""
;
newVersionId
.
value
=
""
;
return
;
}
if
(
versionOptions
.
value
.
length
===
1
)
{
oldVersionId
.
value
=
versionOptions
.
value
[
0
].
value
;
newVersionId
.
value
=
""
;
return
;
}
oldVersionId
.
value
=
versionOptions
.
value
[
0
].
value
;
newVersionId
.
value
=
versionOptions
.
value
[
versionOptions
.
value
.
length
-
1
].
value
;
};
const
normalizeDiffType
=
value
=>
{
if
(
value
===
"ADD"
)
return
"ADD"
;
if
(
value
===
"DELETE"
)
return
"DELETE"
;
return
"CHANGE"
;
};
const
setTabRef
=
(
el
,
index
)
=>
{
if
(
el
)
tabItemRefs
.
value
[
index
]
=
el
;
};
const
updateActiveBar
=
()
=>
{
const
index
=
diffTabs
.
findIndex
(
tab
=>
tab
.
value
===
diffType
.
value
);
const
target
=
tabItemRefs
.
value
[
index
];
const
wrap
=
tabsWrapRef
.
value
;
if
(
!
target
||
!
wrap
)
{
activeBarStyle
.
value
=
{};
return
;
}
const
wrapRect
=
wrap
.
getBoundingClientRect
();
const
targetRect
=
target
.
getBoundingClientRect
();
activeBarStyle
.
value
=
{
width
:
`
${
targetRect
.
width
}
px`
,
transform
:
`translateX(
${
targetRect
.
left
-
wrapRect
.
left
}
px)`
};
};
const
handleDiffTabClick
=
value
=>
{
if
(
diffType
.
value
===
value
)
return
;
diffType
.
value
=
value
;
nextTick
(()
=>
{
updateActiveBar
();
});
};
const
updateDiffCounts
=
list
=>
{
const
counts
=
{
CHANGE
:
0
,
ADD
:
0
,
DELETE
:
0
};
for
(
const
pair
of
list
)
{
const
isOld
=
Boolean
(
pair
?.
oldTerm
);
const
isNew
=
Boolean
(
pair
?.
newTerm
);
if
(
isOld
&&
isNew
)
{
counts
.
CHANGE
+=
1
;
}
else
if
(
isNew
&&
!
isOld
)
{
counts
.
ADD
+=
1
;
}
else
if
(
isOld
&&
!
isNew
)
{
counts
.
DELETE
+=
1
;
}
}
diffCounts
.
value
=
counts
;
};
const
mapDiffTypeToStatus
=
value
=>
{
const
diff
=
normalizeDiffType
(
value
);
if
(
diff
===
"ADD"
)
return
"add"
;
if
(
diff
===
"DELETE"
)
return
"del"
;
return
"update"
;
};
const
mapStatusToDiffType
=
value
=>
{
if
(
value
===
"add"
)
return
"ADD"
;
if
(
value
===
"del"
)
return
"DELETE"
;
return
"CHANGE"
;
};
const
mapVersionCompareItemToPair
=
item
=>
{
const
oldTerm
=
item
?.
originalClauseMainId
?
{
id
:
item
.
originalClauseMainId
,
ywid
:
item
.
originalClauseMainId
,
tkxh
:
item
?.
originalClauseNumber
??
""
,
fynr
:
item
?.
originalContentZh
??
""
,
ywnr
:
item
?.
originalContent
??
""
}
:
null
;
const
newTerm
=
item
?.
currentClauseMainId
?
{
id
:
item
.
currentClauseMainId
,
ywid
:
item
.
currentClauseMainId
,
tkxh
:
item
?.
currentClauseNumber
??
""
,
fynr
:
item
?.
currentContentZh
??
""
,
ywnr
:
item
?.
currentContent
??
""
}
:
null
;
return
{
oldTerm
,
newTerm
};
};
const
fetchComparePage
=
async
({
diff
,
page
,
size
})
=>
{
const
params
=
{
billId
:
billId
.
value
,
content
:
keyword
.
value
,
currentPage
:
Math
.
max
(
1
,
Number
(
page
)
||
1
),
currentVersion
:
newVersionId
.
value
,
isCn
:
onlyChinaRelated
.
value
?
"Y"
:
"N"
,
originalVersion
:
oldVersionId
.
value
,
pageSize
:
Math
.
max
(
1
,
Number
(
size
)
||
10
),
status
:
mapDiffTypeToStatus
(
diff
)
};
const
res
=
await
getBillVersionCompare
(
params
);
const
data
=
res
?.
data
?.
data
??
res
?.
data
??
{};
const
raw
=
Array
.
isArray
(
data
?.
content
)
?
data
.
content
:
[];
const
countType
=
Array
.
isArray
(
data
?.
countType
)
?
data
.
countType
:
[];
return
{
list
:
raw
.
map
(
mapVersionCompareItemToPair
),
total
:
Number
(
data
?.
totalElements
??
0
)
||
0
,
countType
};
};
const
getCountByChangeType
=
(
countTypeList
,
changeTypeLabel
)
=>
{
const
list
=
Array
.
isArray
(
countTypeList
)
?
countTypeList
:
[];
const
target
=
list
.
find
(
item
=>
String
(
item
?.
changeType
??
""
)
===
changeTypeLabel
);
return
Number
(
target
?.
count
??
0
)
||
0
;
};
const
loadComparePairs
=
async
()
=>
{
if
(
!
billId
.
value
||
!
oldVersionId
.
value
||
!
newVersionId
.
value
)
{
comparePairs
.
value
=
[];
updateDiffCounts
([]);
total
.
value
=
0
;
return
;
}
const
currentToken
=
++
compareRequestToken
.
value
;
isLoading
.
value
=
true
;
try
{
const
currentRes
=
await
fetchComparePage
({
diff
:
diffType
.
value
,
page
:
currentPage
.
value
,
size
:
pageSize
.
value
});
if
(
currentToken
!==
compareRequestToken
.
value
)
return
;
comparePairs
.
value
=
currentRes
.
list
;
total
.
value
=
currentRes
.
total
;
diffCounts
.
value
=
{
CHANGE
:
getCountByChangeType
(
currentRes
.
countType
,
"更新"
),
ADD
:
getCountByChangeType
(
currentRes
.
countType
,
"新增"
),
DELETE
:
getCountByChangeType
(
currentRes
.
countType
,
"删除"
)
};
await
ensureEntitiesForPairs
(
comparePairs
.
value
);
}
catch
(
error
)
{
if
(
currentToken
!==
compareRequestToken
.
value
)
return
;
comparePairs
.
value
=
[];
updateDiffCounts
([]);
total
.
value
=
0
;
}
finally
{
if
(
currentToken
===
compareRequestToken
.
value
)
{
isLoading
.
value
=
false
;
}
}
};
const
handleCurrentChange
=
page
=>
{
currentPage
.
value
=
Number
(
page
)
||
1
;
};
const
getPairKey
=
(
pair
,
index
)
=>
{
const
oldKey
=
pair
?.
oldTerm
?.
ywid
??
pair
?.
oldTerm
?.
id
??
pair
?.
oldTerm
?.
tkxh
??
""
;
const
newKey
=
pair
?.
newTerm
?.
ywid
??
pair
?.
newTerm
?.
id
??
pair
?.
newTerm
?.
tkxh
??
""
;
return
`
${
oldKey
||
"old"
}
__
${
newKey
||
"new"
}
__
${
index
}
`
;
};
const
escapeHtml
=
value
=>
{
const
str
=
String
(
value
??
""
);
return
str
.
replace
(
/&/g
,
"&"
)
.
replace
(
/</g
,
"<"
)
.
replace
(
/>/g
,
">"
)
.
replace
(
/"/g
,
"""
)
.
replace
(
/'/g
,
"'"
);
};
const
escapeRegExp
=
text
=>
{
return
String
(
text
||
""
).
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
"
\\
$&"
);
};
const
getSearchRanges
=
(
text
,
searchTerm
)
=>
{
const
rawText
=
String
(
text
??
""
);
const
term
=
String
(
searchTerm
??
""
).
trim
();
if
(
!
rawText
||
!
term
)
return
[];
const
ranges
=
[];
const
reg
=
new
RegExp
(
escapeRegExp
(
term
),
"g"
);
let
match
;
while
((
match
=
reg
.
exec
(
rawText
))
!==
null
)
{
ranges
.
push
({
start
:
match
.
index
,
end
:
match
.
index
+
match
[
0
].
length
});
if
(
match
[
0
].
length
===
0
)
reg
.
lastIndex
+=
1
;
}
return
ranges
;
};
const
normalizeEntities
=
entities
=>
{
const
list
=
Array
.
isArray
(
entities
)
?
entities
:
[];
return
list
.
map
(
item
=>
{
return
{
text_span
:
String
(
item
?.
text_span
??
""
).
trim
(),
type
:
String
(
item
?.
type
??
""
).
trim
()
};
})
.
filter
(
item
=>
item
.
text_span
);
};
const
getEntityRanges
=
(
text
,
entities
)
=>
{
const
ranges
=
[];
const
rawText
=
String
(
text
??
""
);
if
(
!
rawText
)
return
ranges
;
const
list
=
normalizeEntities
(
entities
).
sort
((
a
,
b
)
=>
b
.
text_span
.
length
-
a
.
text_span
.
length
);
for
(
const
ent
of
list
)
{
let
startIndex
=
0
;
while
(
startIndex
<
rawText
.
length
)
{
const
idx
=
rawText
.
indexOf
(
ent
.
text_span
,
startIndex
);
if
(
idx
===
-
1
)
break
;
ranges
.
push
({
start
:
idx
,
end
:
idx
+
ent
.
text_span
.
length
,
ent
});
startIndex
=
idx
+
ent
.
text_span
.
length
;
}
}
ranges
.
sort
((
a
,
b
)
=>
a
.
start
-
b
.
start
||
b
.
end
-
a
.
end
);
const
merged
=
[];
let
lastEnd
=
0
;
for
(
const
r
of
ranges
)
{
if
(
r
.
start
<
lastEnd
)
continue
;
merged
.
push
(
r
);
lastEnd
=
r
.
end
;
}
return
merged
;
};
const
buildHighlightedHtml
=
(
text
,
entities
,
enableHighlight
,
searchTerm
)
=>
{
const
rawText
=
String
(
text
??
""
);
if
(
!
rawText
)
return
""
;
const
safeText
=
escapeHtml
(
rawText
).
replace
(
/
\n
/g
,
"<br />"
);
const
term
=
String
(
searchTerm
??
""
).
trim
();
const
enableSearch
=
Boolean
(
term
);
if
(
!
enableHighlight
&&
!
enableSearch
)
return
safeText
;
const
ranges
=
getEntityRanges
(
rawText
,
entities
);
const
searchRanges
=
enableSearch
?
getSearchRanges
(
rawText
,
term
)
:
[];
if
(
!
ranges
.
length
&&
!
searchRanges
.
length
)
return
safeText
;
const
merged
=
[];
for
(
const
r
of
ranges
)
{
merged
.
push
({
start
:
r
.
start
,
end
:
r
.
end
,
type
:
"entity"
,
ent
:
r
.
ent
});
}
for
(
const
r
of
searchRanges
)
{
const
overlapsEntity
=
ranges
.
some
(
er
=>
r
.
start
<
er
.
end
&&
r
.
end
>
er
.
start
);
if
(
overlapsEntity
)
continue
;
merged
.
push
({
start
:
r
.
start
,
end
:
r
.
end
,
type
:
"search"
});
}
merged
.
sort
((
a
,
b
)
=>
a
.
start
-
b
.
start
||
b
.
end
-
a
.
end
);
let
html
=
""
;
let
cursor
=
0
;
for
(
const
r
of
merged
)
{
if
(
cursor
<
r
.
start
)
{
html
+=
escapeHtml
(
rawText
.
slice
(
cursor
,
r
.
start
));
}
const
spanText
=
rawText
.
slice
(
r
.
start
,
r
.
end
);
if
(
r
.
type
===
"entity"
)
{
const
type
=
escapeHtml
(
r
.
ent
?.
type
??
""
);
const
text
=
escapeHtml
(
spanText
);
html
+=
`<span class="term-entity" data-entity-type="
${
type
}
" data-entity-text="
${
text
}
">
${
text
}
</span>`
;
}
else
{
html
+=
`<span class="term-find-highlight">
${
escapeHtml
(
spanText
)}
</span>`
;
}
cursor
=
r
.
end
;
}
if
(
cursor
<
rawText
.
length
)
{
html
+=
escapeHtml
(
rawText
.
slice
(
cursor
));
}
return
html
.
replace
(
/
\n
/g
,
"<br />"
);
};
const
handleEntityClick
=
event
=>
{
const
el
=
event
?.
target
?.
closest
?.(
"span.term-entity"
);
if
(
!
el
)
return
;
const
q
=
String
(
el
.
getAttribute
(
"data-entity-text"
)
??
""
).
trim
();
if
(
!
q
)
return
;
const
url
=
`https://www.bing.com/search?q=
${
encodeURIComponent
(
q
)}
`
;
window
.
open
(
url
,
"_blank"
,
"noopener"
);
};
const
termEntityCache
=
ref
(
new
Map
());
const
entityRequestToken
=
ref
(
0
);
const
getTermEntityKey
=
(
term
,
lang
)
=>
{
const
baseKey
=
term
?.
ywid
??
term
?.
id
??
term
?.
tkxh
??
""
;
return
`
${
baseKey
}
__
${
lang
}
`
;
};
const
ensureEntitiesForPairs
=
async
pairs
=>
{
if
(
!
termsHighlight
.
value
)
return
;
const
list
=
Array
.
isArray
(
pairs
)
?
pairs
:
[];
if
(
!
list
.
length
)
return
;
const
currentToken
=
++
entityRequestToken
.
value
;
const
tasks
=
[];
const
maxTasks
=
40
;
for
(
const
pair
of
list
)
{
const
terms
=
[
pair
?.
oldTerm
,
pair
?.
newTerm
].
filter
(
Boolean
);
for
(
const
term
of
terms
)
{
const
cnKey
=
getTermEntityKey
(
term
,
"cn"
);
const
enKey
=
getTermEntityKey
(
term
,
"en"
);
if
(
!
termEntityCache
.
value
.
has
(
cnKey
)
&&
String
(
term
?.
fynr
??
""
).
trim
())
{
tasks
.
push
({
key
:
cnKey
,
text
:
term
.
fynr
});
}
if
(
!
termEntityCache
.
value
.
has
(
enKey
)
&&
String
(
term
?.
ywnr
??
""
).
trim
())
{
tasks
.
push
({
key
:
enKey
,
text
:
term
.
ywnr
});
}
if
(
tasks
.
length
>=
maxTasks
)
break
;
}
if
(
tasks
.
length
>=
maxTasks
)
break
;
}
if
(
!
tasks
.
length
)
return
;
try
{
const
results
=
await
Promise
.
all
(
tasks
.
map
(
async
item
=>
{
const
res
=
await
extractTextEntity
(
item
.
text
);
const
entities
=
normalizeEntities
(
res
?.
result
??
res
?.
data
?.
result
??
res
?.
data
??
res
);
return
{
key
:
item
.
key
,
entities
};
})
);
if
(
currentToken
!==
entityRequestToken
.
value
)
return
;
for
(
const
r
of
results
)
{
termEntityCache
.
value
.
set
(
r
.
key
,
r
.
entities
);
}
}
catch
(
error
)
{
if
(
currentToken
!==
entityRequestToken
.
value
)
return
;
}
};
const
getTermContentHtml
=
(
term
,
lang
)
=>
{
const
raw
=
lang
===
"en"
?
term
?.
ywnr
:
term
?.
fynr
;
const
key
=
getTermEntityKey
(
term
,
lang
);
const
entities
=
termEntityCache
.
value
.
get
(
key
)
||
[];
return
buildHighlightedHtml
(
raw
,
entities
,
termsHighlight
.
value
,
findWordKeyword
.
value
);
};
const
countOccurrences
=
(
text
,
searchTerm
)
=>
{
const
rawText
=
String
(
text
??
""
);
const
term
=
String
(
searchTerm
??
""
).
trim
();
if
(
!
rawText
||
!
term
)
return
0
;
const
reg
=
new
RegExp
(
escapeRegExp
(
term
),
"g"
);
const
matches
=
rawText
.
match
(
reg
);
return
matches
?
matches
.
length
:
0
;
};
const
updateActiveFindHighlight
=
()
=>
{
const
spans
=
document
.
querySelectorAll
(
"span.term-find-highlight"
);
spans
.
forEach
((
span
,
index
)
=>
{
if
(
index
+
1
===
findWordNum
.
value
)
{
span
.
classList
.
add
(
"is-active"
);
span
.
scrollIntoView
({
block
:
"center"
});
}
else
{
span
.
classList
.
remove
(
"is-active"
);
}
});
};
const
doUpdateFindWord
=
async
()
=>
{
findWordNum
.
value
=
0
;
findWordMax
.
value
=
0
;
const
term
=
String
(
findWordTxt
.
value
||
""
).
trim
();
findWordKeyword
.
value
=
term
;
if
(
!
term
)
{
await
nextTick
();
return
;
}
const
list
=
Array
.
isArray
(
comparePairs
.
value
)
?
comparePairs
.
value
:
[];
for
(
const
pair
of
list
)
{
const
terms
=
[
pair
?.
oldTerm
,
pair
?.
newTerm
].
filter
(
Boolean
);
for
(
const
t
of
terms
)
{
findWordMax
.
value
+=
countOccurrences
(
t
?.
fynr
,
term
);
if
(
termsShowOriginal
.
value
)
{
findWordMax
.
value
+=
countOccurrences
(
t
?.
ywnr
,
term
);
}
}
}
if
(
findWordMax
.
value
>
0
)
{
await
nextTick
();
findWordNum
.
value
=
1
;
updateActiveFindHighlight
();
}
};
const
handleUpdateWord
=
()
=>
{
if
(
findWordTimer
.
value
)
{
clearTimeout
(
findWordTimer
.
value
);
findWordTimer
.
value
=
null
;
}
findWordTimer
.
value
=
setTimeout
(()
=>
{
doUpdateFindWord
();
},
300
);
};
const
handleFindWord
=
event
=>
{
switch
(
event
)
{
case
"open"
:
findWordBox
.
value
=
true
;
nextTick
(()
=>
{
findWordInputRef
.
value
?.
focus
?.();
});
break
;
case
"last"
:
if
(
findWordMax
.
value
>
1
)
{
findWordNum
.
value
=
findWordNum
.
value
===
1
?
findWordMax
.
value
:
findWordNum
.
value
-
1
;
updateActiveFindHighlight
();
}
break
;
case
"next"
:
if
(
findWordMax
.
value
>
1
)
{
findWordNum
.
value
=
findWordNum
.
value
===
findWordMax
.
value
?
1
:
findWordNum
.
value
+
1
;
updateActiveFindHighlight
();
}
break
;
case
"close"
:
findWordBox
.
value
=
false
;
findWordTxt
.
value
=
""
;
findWordKeyword
.
value
=
""
;
findWordNum
.
value
=
0
;
findWordMax
.
value
=
0
;
break
;
}
};
watch
(
[
billId
,
oldVersionId
,
newVersionId
,
diffType
,
onlyChinaRelated
,
keyword
,
currentPage
,
pageSize
],
()
=>
{
loadComparePairs
();
},
{
immediate
:
true
}
);
watch
(
diffType
,
()
=>
{
currentPage
.
value
=
1
;
nextTick
(()
=>
{
updateActiveBar
();
});
});
watch
([
oldVersionId
,
newVersionId
,
onlyChinaRelated
,
keyword
],
()
=>
{
currentPage
.
value
=
1
;
});
watch
(
termsHighlight
,
()
=>
{
ensureEntitiesForPairs
(
comparePairs
.
value
);
});
watch
(
[
comparePairs
,
termsShowOriginal
],
()
=>
{
if
(
!
findWordBox
.
value
)
return
;
doUpdateFindWord
();
},
{
deep
:
true
}
);
onMounted
(
async
()
=>
{
await
handleLoadVersionOptions
();
nextTick
(()
=>
{
updateActiveBar
();
});
});
</
script
>
<
style
lang=
"scss"
scoped
>
.version-compare-wrap
{
display
:
flex
;
flex-direction
:
column
;
row-gap
:
16px
;
width
:
100%
;
background
:
#ffffff
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.04
);
padding
:
16px
75px
;
box-sizing
:
border-box
;
}
.compare-top
{
display
:
grid
;
grid-template-columns
:
1fr
1fr
;
gap
:
37px
;
margin-top
:
0
;
}
.compare-top-col
{
display
:
flex
;
align-items
:
center
;
gap
:
22px
;
background
:
transparent
;
border-radius
:
0
;
padding
:
0
;
box-shadow
:
none
;
}
.compare-top-label
{
font-size
:
16px
;
font-weight
:
400
;
color
:
var
(
--
text-primary-65-color
);
white-space
:
nowrap
;
}
.compare-top-select
{
flex
:
1
;
:deep
(
.el-select__wrapper
)
{
background-color
:
rgb
(
246
,
250
,
255
);
box-shadow
:
0
0
0
1px
var
(
--
color-primary-35
)
inset
;
}
:deep
(
.el-select__wrapper.is-hovering
)
{
box-shadow
:
0
0
0
1px
var
(
--
color-primary-35
)
inset
;
}
:deep
(
.el-select__wrapper.is-focused
)
{
box-shadow
:
0
0
0
1px
var
(
--
color-primary-35
)
inset
;
}
:deep
(
.el-select__selected-item
),
:deep
(
.el-select__placeholder
),
:deep
(
.el-select__input
),
:deep
(
.el-select__caret
),
:deep
(
.el-select__suffix
),
:deep
(
.el-select__icon
)
{
color
:
var
(
--
color-primary-100
);
}
}
.compare-tools
{
height
:
66px
;
border-bottom
:
1px
solid
var
(
--
border-black-5
);
border-top
:
1px
solid
var
(
--
border-black-5
);
background
:
transparent
;
display
:
flex
;
align-items
:
center
;
gap
:
24px
;
flex-wrap
:
nowrap
;
white-space
:
nowrap
;
}
.compare-tools-tabs
{
flex
:
0
0
auto
;
position
:
relative
;
display
:
flex
;
align-items
:
center
;
gap
:
28px
;
height
:
100%
;
}
.compare-tools-tab
{
background
:
transparent
;
border
:
none
;
padding
:
0
;
cursor
:
pointer
;
display
:
inline-flex
;
align-items
:
center
;
gap
:
8px
;
font-size
:
16px
;
font-weight
:
400
;
color
:
var
(
--
text-primary-65-color
);
position
:
relative
;
&
.is-active
{
color
:
var
(
--
color-primary-100
);
}
&
:focus-visible
{
outline
:
2px
solid
rgba
(
66
,
133
,
244
,
0
.5
);
outline-offset
:
4px
;
border-radius
:
6px
;
}
.label
{
line-height
:
1
;
}
.count
{
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
min-width
:
22px
;
height
:
22px
;
padding
:
0
6px
;
border-radius
:
999px
;
background
:
var
(
--
color-primary-100
);
color
:
var
(
--
bg-white-100
);
font-size
:
12px
;
font-weight
:
600
;
}
}
.compare-tools-tabs-active
{
position
:
absolute
;
left
:
0
;
bottom
:
-1px
;
height
:
2px
;
background
:
var
(
--
color-primary-100
);
border-radius
:
2px
;
transition
:
transform
0
.2s
ease
,
width
0
.2s
ease
;
}
.compare-tools-actions
{
display
:
flex
;
align-items
:
center
;
gap
:
24px
;
flex-wrap
:
wrap
;
margin-left
:
auto
;
justify-content
:
flex-end
;
font-size
:
16px
;
font-weight
:
400
;
color
:
var
(
--
text-primary-65-color
);
:deep
(
.el-checkbox__label
)
{
font-size
:
16px
;
font-weight
:
400
;
color
:
var
(
--
text-primary-65-color
);
}
}
.compare-tools-switches
{
display
:
flex
;
align-items
:
center
;
gap
:
16px
;
}
.compare-tools-switch
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
8px
;
.label
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
font-size
:
16px
;
font-weight
:
400
;
color
:
var
(
--
text-primary-65-color
);
}
}
.label-icon
{
width
:
16px
;
height
:
16px
;
}
.compare-tools-search
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
8px
;
}
.find-word-box
{
width
:
430px
;
height
:
60px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
background-color
:
#ffffff
;
border-radius
:
6px
;
display
:
flex
;
align-items
:
center
;
position
:
absolute
;
right
:
0
;
top
:
-68px
;
box-shadow
:
0
8px
20px
rgba
(
0
,
0
,
0
,
0
.06
);
.find-word-input
{
flex
:
1
;
min-width
:
0
;
padding-left
:
8px
;
}
.find-word-limit
{
border-right
:
solid
1px
rgba
(
230
,
231
,
232
,
1
);
color
:
#5f656c
;
padding
:
0
16px
0
8px
;
white-space
:
nowrap
;
}
.find-word-icon
{
padding
:
6px
10px
;
margin
:
0
2px
;
cursor
:
pointer
;
display
:
inline-flex
;
align-items
:
center
;
}
}
.find-word-wrap
{
position
:
relative
;
display
:
inline-flex
;
align-items
:
center
;
}
.find-word-open-btn
{
height
:
32px
;
background-color
:
var
(
--
bg-white-100
);
border-color
:
var
(
--
bg-black-10
);
color
:
var
(
--
text-primary-80-color
);
&
:hover
{
background-color
:
var
(
--
bg-black-5
);
border-color
:
var
(
--
bg-black-10
);
color
:
var
(
--
text-primary-80-color
);
}
&
:active
{
background-color
:
var
(
--
bg-black-10
);
border-color
:
var
(
--
bg-black-10
);
color
:
var
(
--
text-primary-80-color
);
}
}
.find-word-open-icon
{
margin-right
:
4px
;
}
:deep
(
span
.term-find-highlight
)
{
background-color
:
#ffff00
;
}
:deep
(
span
.term-find-highlight.is-active
)
{
background-color
:
#ff9632
;
}
.compare-columns
{
margin-top
:
16px
;
display
:
flex
;
flex-direction
:
column
;
}
.compare-row
{
width
:
100%
;
display
:
grid
;
grid-template-columns
:
1fr
1fr
;
}
.compare-cell
{
width
:
100%
;
box-sizing
:
border-box
;
border-radius
:
2px
;
background
:
var
(
--
bg-white-100
);
display
:
flex
;
align-items
:
flex-start
;
position
:
relative
;
padding
:
16px
;
}
.compare-row
:nth-child
(
2n-1
)
.compare-cell
{
background
:
var
(
--
bg-black-2
);
}
.compare-footer
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
16px
;
padding
:
12px
0
0
;
}
.compare-footer-text
{
font-size
:
14px
;
color
:
var
(
--
text-primary-65-color
);
white-space
:
nowrap
;
}
.compare-footer-right
{
display
:
flex
;
justify-content
:
flex-end
;
flex
:
1
;
min-width
:
0
;
}
.term-empty
{
width
:
100%
;
min-height
:
48px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
rgba
(
132
,
136
,
142
,
1
);
}
.term-body
{
display
:
flex
;
column-gap
:
18px
;
flex
:
1
;
min-width
:
0
;
width
:
100%
;
}
.term-main
{
flex
:
1
;
min-width
:
0
;
display
:
flex
;
flex-direction
:
column
;
row-gap
:
6px
;
}
.term-row
{
display
:
flex
;
align-items
:
flex-start
;
column-gap
:
18px
;
}
.term-no-cn
{
font-size
:
16px
;
font-weight
:
700
;
line-height
:
24px
;
color
:
var
(
--
color-primary-100
);
white-space
:
nowrap
;
width
:
90px
;
text-align
:
center
;
flex
:
0
0
90px
;
}
.term-no-en
{
font-size
:
14px
;
font-weight
:
400
;
line-height
:
24px
;
color
:
var
(
--
color-primary-100
);
white-space
:
nowrap
;
width
:
90px
;
text-align
:
center
;
flex
:
0
0
90px
;
}
.term-content-cn
{
flex
:
1
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
24px
;
color
:
var
(
--
text-primary-80-color
);
:deep
(
.term-entity
)
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
4px
;
padding
:
0
6px
0
4px
;
background
:
var
(
--
color-primary-10
);
cursor
:
pointer
;
transition
:
background-color
0
.15s
ease
,
transform
0
.15s
ease
;
}
:deep
(
.term-entity
:hover
)
{
background
:
var
(
--
color-primary-35
);
transform
:
translateY
(
-1px
);
}
:deep
(
.term-entity
::before
)
{
content
:
""
;
display
:
inline-block
;
width
:
14px
;
height
:
14px
;
flex
:
0
0
14px
;
background-color
:
var
(
--
color-primary-100
);
mask-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M448 768c176.736 0 320-143.264 320-320S624.736 128 448 128 128 271.264 128 448s143.264 320 320 320zm0 64C236.288 832 64 659.712 64 448S236.288 64 448 64s384 172.288 384 384-172.288 384-384 384z'/%3E%3Cpath d='M832 832a32 32 0 0 1-22.624-9.376l-160-160a32 32 0 0 1 45.248-45.248l160 160A32 32 0 0 1 832 832z'/%3E%3C/svg%3E")
;
mask-repeat
:
no-repeat
;
mask-position
:
center
;
mask-size
:
contain
;
}
}
.term-content-en
{
flex
:
1
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
24px
;
color
:
var
(
--
text-primary-65-color
);
:deep
(
.term-entity
)
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
4px
;
padding
:
0
6px
0
4px
;
background
:
var
(
--
color-primary-10
);
cursor
:
pointer
;
transition
:
background-color
0
.15s
ease
,
transform
0
.15s
ease
;
}
:deep
(
.term-entity
:hover
)
{
background
:
var
(
--
color-primary-35
);
transform
:
translateY
(
-1px
);
}
:deep
(
.term-entity
::before
)
{
content
:
""
;
display
:
inline-block
;
width
:
14px
;
height
:
14px
;
flex
:
0
0
14px
;
background-color
:
var
(
--
color-primary-100
);
mask-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M448 768c176.736 0 320-143.264 320-320S624.736 128 448 128 128 271.264 128 448s143.264 320 320 320zm0 64C236.288 832 64 659.712 64 448S236.288 64 448 64s384 172.288 384 384-172.288 384-384 384z'/%3E%3Cpath d='M832 832a32 32 0 0 1-22.624-9.376l-160-160a32 32 0 0 1 45.248-45.248l160 160A32 32 0 0 1 832 832z'/%3E%3C/svg%3E")
;
mask-repeat
:
no-repeat
;
mask-position
:
center
;
mask-size
:
contain
;
}
}
@media
(
max-width
:
1680px
)
{
.compare-row
,
.compare-top
{
grid-template-columns
:
1fr
;
}
.compare-tools-actions
{
width
:
100%
;
}
.compare-tools-search
{
width
:
100%
;
}
.find-word-box
{
width
:
100%
;
}
}
</
style
>
src/views/decree/decreeHome/index.vue
浏览文件 @
5466ba6a
...
...
@@ -63,12 +63,18 @@
</div>
</div>
</div>
-->
<div
class=
"home-main-header-item-box"
>
<div
class=
"item"
v-for=
"(item, index) in govInsList"
:key=
"index"
@
click=
"handleToInstitution(item)"
>
<div
class=
"home-main-header-item-box"
v-if=
"govInsList.length"
>
<div
class=
"item"
v-for=
"(item, index) in govInsList
.slice(0, 7)
"
:key=
"index"
@
click=
"handleToInstitution(item)"
>
<div
class=
"item-left"
>
<img
:src=
"item.img ? item.img : DefaultIcon2"
alt=
""
/>
<img
:src=
"item.orgImage || DefaultIcon2"
alt=
""
/>
</div>
<div
class=
"item-right one-line-ellipsis"
>
{{
item
.
orgName
}}
</div>
<div
class=
"item-num"
>
{{
item
.
total
}}
项
</div>
<el-icon
color=
"var(--color-primary-100)"
><ArrowRightBold
/></el-icon>
</div>
<div
class=
"item-right"
>
{{
item
.
name
}}
</div>
<div
class=
"item"
>
<div
class=
"item-num item-more"
>
查看全部机构 (
{{
govInsList
.
length
+
1
}}
家)
</div>
<el-icon
color=
"var(--color-primary-100)"
><ArrowRightBold
/></el-icon>
</div>
</div>
</div>
...
...
@@ -115,15 +121,7 @@
{{
item
.
name
}}
</div>
<div
class=
"box1-main-right-info"
>
<!--
<div
class=
"tag"
:class=
"
{
tag1: tag.status == 1,
tag2: tag.status == 2,
tag3: tag.status == 3
}" v-for="(tag, index) in item.industryList" :key="index">
{{
tag
.
industryName
}}
</div>
-->
<AreaTag
v-for=
"(tag, index) in item.industryList"
:key=
"index"
:tagName=
"tag.industryName"
>
</AreaTag>
<AreaTag
v-for=
"(tag, index) in item.industryList"
:key=
"index"
:tagName=
"tag.industryName"
/>
</div>
<div
class=
"box1-main-right-center"
>
{{
item
.
describe
}}
...
...
@@ -243,6 +241,7 @@
<div
class=
"header-title"
>
{{
"关键行政令"
}}
</div>
</div>
<div
class=
"box7-main"
>
<div
class=
"box7-list"
>
<div
class=
"box7-item"
v-for=
"(item, index) in keyDecreeList"
:key=
"index"
@
click=
"handleKeyDecree(item)"
>
<div
class=
"icon"
>
<img
src=
"./assets/images/warning.png"
alt=
""
/>
...
...
@@ -253,14 +252,11 @@
<div
class=
"time"
>
{{
item
.
time
}}
</div>
</div>
<div
class=
"info-content"
>
{{
item
.
content
?
item
.
content
:
"暂无数据"
}}
</div>
<!--
<el-popover
effect=
"dark"
:width=
"800"
:content=
"item.content"
placement=
"top-start"
>
<template
#
reference
>
<div
class=
"info-content"
>
{{
item
.
content
?
item
.
content
:
"暂无数据"
}}
</div>
</
template
>
</el-popover>
-->
</div>
</div>
</div>
<SimplePagination
v-model:current-page=
"keyDecreeInfo.page"
:page-size=
"keyDecreeInfo.size"
:total=
"keyDecreeInfo.total"
@
page-change=
"handleGetKeyDecree"
/>
</div>
</div>
<div
class=
"box8"
>
<div
class=
"box8-header"
>
...
...
@@ -269,17 +265,19 @@
</div>
<div
class=
"header-title"
>
{{
"政令重点条款"
}}
</div>
</div>
<div
class=
"box8-main"
id=
"wordCloudChart"
></div>
<div
class=
"box8-content"
v-if=
"wordCloudData?.length"
>
<WordCloudChart
:data=
"wordCloudData"
width=
"100%"
height=
"100%"
/>
</div>
</div>
</div>
</div>
<div
class=
"home-main-footer"
>
<DivideHeader
id=
"position4"
class=
"divide4"
:titleText=
"'
资源
库'"
></DivideHeader>
<DivideHeader
id=
"position4"
class=
"divide4"
:titleText=
"'
科技政令
库'"
></DivideHeader>
<div
class=
"home-main-footer-header"
>
<div
class=
"search-box"
>
<el-select
v-model=
"searchType"
:empty-values=
"[null, undefined]"
style=
"width: 100%"
>
<el-select
v-model=
"searchType"
:empty-values=
"[null, undefined]"
style=
"width: 100%"
filterable
>
<el-option
label=
"全部政府部门"
value=
""
/>
<el-option
v-for=
"item in govInsList"
:key=
"item.
id"
:label=
"item.name"
:value=
"item.i
d"
/>
<el-option
v-for=
"item in govInsList"
:key=
"item.
orgId"
:label=
"item.orgName"
:value=
"item.orgI
d"
/>
</el-select>
</div>
<div
style=
"flex: auto;"
></div>
...
...
@@ -306,38 +304,23 @@
</div>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
v-for=
"type in decreeTypeList"
:key=
"type.id"
v-model=
"checkedDecreeType"
:label=
"type.typeId"
style=
"width: 180px"
class=
"filter-checkbox"
<el-checkbox
v-for=
"type in decreeTypeList"
:key=
"type.id"
v-model=
"checkedDecreeType"
:label=
"type.typeId"
style=
"width: 180px"
class=
"filter-checkbox"
@
change=
"handleChangeCheckedDecreeType"
>
{{ type.typeName }}
</el-checkbox>
</div>
</div>
</div>
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "发布机构" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-for="cate in govInsList" :key="cate.id" v-model="checkedGovIns"
:label="cate.id" style="width: 180px" class="filter-checkbox"
@change="handleChangeCheckedGovIns">
{{ cate.name }}
</el-checkbox>
</div>
</div>
</div> -->
<div
class=
"select-box"
>
<div
class=
"select-box-header"
>
<div
class=
"icon"
></div>
<div
class=
"title"
>
{{ "科技领域" }}
</div>
</div>
<div
class=
"select-main
select-main1
"
>
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
v-for=
"area in areaList"
:key=
"area.id"
v-model=
"activeAreaList"
:label=
"area.id"
style=
"width: 100px"
@
change=
"checked => handleAreaChange(area.id, checked)"
>
style=
"width: 100px"
class=
"filter-checkbox"
@
change=
"checked => handleAreaChange(area.id, checked)"
>
{{ area.name }}
</el-checkbox>
</div>
...
...
@@ -351,28 +334,12 @@
<div
class=
"select-main"
>
<div
class=
"checkbox-group"
>
<el-checkbox
v-for=
"time in pubTime"
:key=
"time.id"
v-model=
"activePubTime"
:label=
"time.id"
style=
"width: 100px"
class=
"filter-checkbox"
@
change=
"checked => handlePubTimeChange(time.id, checked)"
>
style=
"width: 100px"
class=
"filter-checkbox"
@
change=
"checked => handlePubTimeChange(time.id, checked)"
>
{{ time.name }}
</el-checkbox>
</div>
</div>
</div>
<!-- <div class="select-box">
<div class="select-box-header">
<div class="icon"></div>
<div class="title">{{ "涉及领域" }}</div>
</div>
<div class="select-main select-main1">
<div class="checkbox-group">
<el-checkbox v-for="area in areaList" :key="area.id" v-model="activeAreaList"
:label="area.id" style="width: 100px"
@change="checked => handleAreaChange(area.id, checked)">
{{ area.name }}
</el-checkbox>
</div>
</div>
</div> -->
</div>
<div
class=
"right"
>
<div
class=
"content-header"
>
...
...
@@ -403,9 +370,7 @@
</div>
<div
class=
"desc"
>
{{ item.desc }}
</div>
<div
class=
"tag-box"
>
<div
class=
"tag"
v-for=
"(val, idx) in item.tagList"
:key=
"idx"
>
{{ val.industryName }}
</div>
<AreaTag
v-for=
"(tag, index) in item.tagList"
:key=
"index"
:tagName=
"tag.industryName"
/>
</div>
</div>
</div>
...
...
@@ -427,9 +392,10 @@
</template>
<
script
setup
>
import
NewsList
from
"@/components/base/NewsList/index.vue"
;
import
{
onMounted
,
ref
,
watch
,
nextTick
}
from
"vue"
;
import
{
onMounted
,
ref
,
watch
,
nextTick
,
reactive
}
from
"vue"
;
import
router
from
"@/router"
;
import
WordCloudChart
from
"@/components/base/WordCloundChart/index.vue"
import
SimplePagination
from
"@/components/SimplePagination.vue"
;
import
{
getDepartmentList
,
getLatestDecree
,
...
...
@@ -448,7 +414,6 @@ import DivideHeader from "@/components/DivideHeader.vue";
import
{
useContainerScroll
}
from
"@/hooks/useScrollShow"
;
import
getBarChart
from
"./utils/barChart"
;
import
getPieChart
from
"./utils/piechart"
;
import
getWordCloudChart
from
"./utils/wordCloudChart"
;
import
setChart
from
"@/utils/setChart"
;
...
...
@@ -458,11 +423,11 @@ import { ElMessage } from "element-plus";
// 跳转行政机构主页
const
handleToInstitution
=
item
=>
{
window
.
sessionStorage
.
setItem
(
"curTabName"
,
item
.
n
ame
);
window
.
sessionStorage
.
setItem
(
"curTabName"
,
item
.
orgN
ame
);
const
curRoute
=
router
.
resolve
({
path
:
"/institution"
,
query
:
{
id
:
item
.
i
d
id
:
item
.
orgI
d
}
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
...
...
@@ -480,32 +445,15 @@ const handleCurrentChange = page => {
};
// 页面 header
const
govInsList
=
ref
([
// {
// img: Gov1,
// name: "美国白宫"
// },
// {
// img: Gov2,
// name: "美国财政部"
// },
]);
const
govInsList
=
ref
([]);
const
checkedGovIns
=
ref
([]);
const
handleChangeCheckedGovIns
=
val
=>
{
};
const
handleGetDepartmentList
=
async
()
=>
{
try
{
const
res
=
await
getDepartmentList
();
console
.
log
(
"机构列表"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
govInsList
.
value
=
res
.
data
.
map
(
item
=>
{
return
{
id
:
item
.
orgId
,
name
:
item
.
orgName
,
img
:
item
.
orgImage
};
});
govInsList
.
value
=
res
.
data
;
}
}
catch
(
error
)
{
console
.
error
(
"获取机构列表error"
,
error
);
...
...
@@ -649,11 +597,8 @@ const newsList = ref([
// }
]);
const
handleGetNews
=
async
()
=>
{
const
params
=
{
moduleId
:
"0101"
};
try
{
const
res
=
await
getNews
(
params
);
const
res
=
await
getNews
(
{
moduleId
:
"0101"
}
);
console
.
log
(
"新闻资讯"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
// newsList.value = res.data || []
...
...
@@ -671,7 +616,6 @@ const handleGetNews = async () => {
console
.
error
(
"新闻资讯error"
,
error
);
}
};
handleGetNews
();
// 点击新闻条目,跳转到新闻分析页
const
handleToNewsAnalysis
=
news
=>
{
const
route
=
router
.
resolve
({
...
...
@@ -914,13 +858,19 @@ const handleBox6YearChange = () => {
// 关键行政令
const
keyDecreeList
=
ref
([]);
const
keyDecreeInfo
=
reactive
({
total
:
0
,
page
:
1
,
size
:
3
,
})
const
handleGetKeyDecree
=
async
()
=>
{
try
{
const
res
=
await
getKeyDecree
();
const
res
=
await
getKeyDecree
(
{
pageSize
:
keyDecreeInfo
.
size
,
pageNum
:
keyDecreeInfo
.
page
-
1
}
);
console
.
log
(
"关键行政令"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
keyDecreeList
.
value
=
res
.
data
.
map
(
item
=>
{
if
(
res
.
code
===
200
&&
res
.
data
?.
total
)
{
keyDecreeInfo
.
total
=
res
.
data
.
total
||
0
;
keyDecreeList
.
value
=
res
.
data
.
list
.
map
(
item
=>
{
return
{
title
:
item
.
name
,
content
:
item
.
describe
,
...
...
@@ -935,38 +885,17 @@ const handleGetKeyDecree = async () => {
handleGetKeyDecree
();
// 政令重点条款
const
wordCloudData
=
[
// { name: "与马斯克公开冲突", value: 100 },
// { name: "传统能源", value: 5 },
// { name: "共和党财政鹰派", value: 77 },
// { name: "未实现赤字控制目标", value: 35 },
// { name: "得克萨斯州", value: 88 },
// { name: "选举压力", value: 57 },
// { name: "主张财政紧缩", value: 72 },
// { name: "财政保守", value: 18 },
];
const
wordCloudData
=
ref
([]);
const
handleGetDecreeKeyInstruction
=
async
()
=>
{
try
{
const
res
=
await
getDecreeKeyInstruction
();
console
.
log
(
"政令重点条款"
,
res
);
wordCloudData
.
value
=
res
.
data
.
map
(
item
=>
{
return
{
name
:
item
.
clause
,
value
:
item
.
count
};
});
wordCloudData
.
value
=
res
.
data
.
map
(
item
=>
({
name
:
item
.
clause
,
value
:
item
.
count
}));
}
catch
(
error
)
{
console
.
error
(
"政令重点条款error"
,
error
);
}
};
const
handleBox8
=
async
()
=>
{
await
handleGetDecreeKeyInstruction
();
let
chart3
=
getWordCloudChart
(
wordCloudData
.
value
);
setChart
(
chart3
,
"wordCloudChart"
);
};
// 资源库
const
searchType
=
ref
(
""
);
const
isChina
=
ref
(
false
);
...
...
@@ -995,22 +924,6 @@ const handleToPosi = id => {
}
};
// const handleGetAreaList = async () => {
// try {
// const res = await getDecreehylyList();
// console.log("行业领域列表", res);
// if (res.code === 200 && res.data) {
// areaList.value = res.data.map(item => {
// return {
// name: item.name,
// id: item.id
// };
// });
// console.log("areaList", areaList.value);
// }
// } catch (error) { }
// };
// 政令类型
const
decreeTypeList
=
ref
([]);
const
checkedDecreeType
=
ref
([]);
...
...
@@ -1101,7 +1014,7 @@ const handleGetAreaList = async () => {
console
.
log
(
"行业领域列表"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
areaList
.
value
=
[
{
name
:
"全
选
"
,
id
:
"all"
},
{
name
:
"全
部领域
"
,
id
:
"all"
},
...
res
.
data
.
map
(
item
=>
{
return
{
name
:
item
.
name
,
...
...
@@ -1218,13 +1131,14 @@ const handleSearch = () => {
};
onMounted
(
async
()
=>
{
handleGetNews
();
handleGetDecreeTypeList
();
handleGetAreaList
();
handleGetDecreeOrderList
();
handleBox1
();
// 最新科技政令
handleBox5
();
handleBox6
();
handle
Box8
();
handle
GetDecreeKeyInstruction
();
});
</
script
>
...
...
@@ -1476,15 +1390,17 @@ onMounted(async () => {
}
.home-main-header-item-box
{
margin-top
:
48px
;
margin-bottom
:
64px
;
margin
:
48px
0
64px
;
width
:
1600px
;
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
16px
;
.item
{
width
:
254px
;
height
:
72px
;
width
:
20%
;
flex
:
auto
;
height
:
80px
;
padding
:
0
16px
;
display
:
flex
;
box-sizing
:
border-box
;
background
:
rgba
(
255
,
255
,
255
,
0
.65
);
...
...
@@ -1492,8 +1408,7 @@ onMounted(async () => {
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
align-items
:
center
;
gap
:
17px
;
margin
:
0
6px
16px
6px
;
justify-content
:
center
;
cursor
:
pointer
;
transition
:
transform
0
.3s
ease
,
...
...
@@ -1505,10 +1420,9 @@ onMounted(async () => {
}
.item-left
{
margin-left
:
24px
;
width
:
48px
;
height
:
48px
;
font-size
:
0px
;
img
{
width
:
100%
;
height
:
100%
;
...
...
@@ -1516,12 +1430,28 @@ onMounted(async () => {
}
.item-right
{
width
:
140px
;
width
:
20px
;
flex
:
auto
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
20px
;
font-weight
:
700
;
line-height
:
24px
;
line-height
:
20px
;
margin
:
0
16px
;
}
.item-num
{
white-space
:
nowrap
;
font-family
:
Microsoft
YaHei
;
font-size
:
20px
;
font-weight
:
700
;
line-height
:
20px
;
margin-right
:
2px
;
color
:
var
(
--
color-primary-100
);
}
.item-more
{
margin-right
:
12px
;
font-size
:
16px
;
}
}
}
...
...
@@ -1708,15 +1638,10 @@ onMounted(async () => {
line-height
:
26px
;
display
:
-
webkit-box
;
/* 将元素设置为弹性盒模型 */
-webkit-line-clamp
:
2
;
/* 限制文本显示的行数 */
-webkit-box-orient
:
vertical
;
/* 设置弹性盒子的子元素垂直排列 */
overflow
:
hidden
;
/* 隐藏溢出的内容 */
text-overflow
:
ellipsis
;
/* 文本溢出时显示省略号 */
}
.box1-main-right-info
{
...
...
@@ -1724,58 +1649,21 @@ onMounted(async () => {
display
:
flex
;
height
:
24px
;
gap
:
8px
;
.tag
{
height
:
24px
;
line-height
:
24px
;
padding
:
0
8px
;
box-sizing
:
border-box
;
border-radius
:
4px
;
margin-right
:
5px
;
border
:
1px
solid
rgba
(
255
,
163
,
158
,
1
);
color
:
rgba
(
245
,
34
,
45
,
1
);
background
:
rgba
(
255
,
241
,
240
,
1
);
}
.tag1
{
border
:
1px
solid
rgba
(
135
,
232
,
222
,
1
);
color
:
rgba
(
19
,
168
,
168
,
1
);
background
:
rgba
(
230
,
255
,
251
,
1
);
}
.tag2
{
border
:
1px
solid
rgba
(
186
,
224
,
255
,
1
);
background
:
rgba
(
230
,
244
,
255
,
1
);
color
:
rgba
(
22
,
119
,
255
,
1
);
}
.tag3
{
border
:
1px
solid
rgba
(
255
,
229
,
143
,
1
);
color
:
rgba
(
250
,
173
,
20
,
1
);
background
:
rgba
(
255
,
251
,
230
,
1
);
}
.tag4
{
border
:
1px
solid
rgba
(
255
,
163
,
158
,
1
);
color
:
rgba
(
245
,
34
,
45
,
1
);
background
:
rgba
(
255
,
241
,
240
,
1
);
}
}
.box1-main-right-center
{
margin-top
:
10px
;
height
:
20
0px
;
height
:
18
0px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
30px
;
display
:
-
webkit-box
;
/* 将元素设置为弹性盒模型 */
-webkit-line-clamp
:
6
;
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
/* 隐藏溢出的内容 */
text-overflow
:
ellipsis
;
/* 文本溢出时显示省略号 */
}
.box1-main-right-footer
{
...
...
@@ -2730,15 +2618,6 @@ onMounted(async () => {
font-weight
:
400
;
}
}
.info-content
{
margin-top
:
3px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
30px
;
}
}
}
}
...
...
@@ -2912,10 +2791,9 @@ onMounted(async () => {
.box7-main
{
margin-top
:
10px
;
height
:
380px
;
box-sizing
:
border-box
;
overflow-y
:
auto
;
overflow-x
:
hidden
;
.box7-list
{
height
:
310px
;
.box7-item
{
width
:
730px
;
...
...
@@ -2985,7 +2863,12 @@ onMounted(async () => {
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
display
:
-
webkit-box
;
-webkit-line-clamp
:
2
;
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
}
}
}
...
...
@@ -2999,9 +2882,11 @@ onMounted(async () => {
border-radius
:
10px
;
box-shadow
:
0px
0px
15px
0px
rgba
(
60
,
87
,
126
,
0
.2
);
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
flex-direction
:
column
;
.box8-header
{
width
:
792px
;
width
:
100%
;
height
:
48px
;
display
:
flex
;
border-bottom
:
1px
solid
rgba
(
240
,
242
,
244
,
1
);
...
...
@@ -3030,6 +2915,12 @@ onMounted(async () => {
}
}
.box8-content
{
width
:
100%
;
height
:
20px
;
flex
:
auto
;
}
.box8-main
{
height
:
401px
;
}
...
...
@@ -3039,7 +2930,6 @@ onMounted(async () => {
.home-main-footer
{
margin-top
:
34px
;
max-height
:
1860px
;
padding-bottom
:
160px
;
background
:
rgba
(
248
,
249
,
250
,
1
);
overflow
:
hidden
;
...
...
@@ -3133,16 +3023,21 @@ onMounted(async () => {
.left
{
width
:
360px
;
box-shadow
:
0px
0px
15px
0px
rgba
(
60
,
87
,
126
,
0
.2
);
background
:
rgba
(
255
,
255
,
255
,
1
);
height
:
100%
;
padding-bottom
:
24px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-radius
:
10px
;
box-shadow
:
0px
0px
20px
0px
rgba
(
94
,
95
,
95
,
0
.1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
position
:
relative
;
.select-box
{
margin-top
:
16px
;
.select-box-header
{
display
:
flex
;
gap
:
1
6
px
;
gap
:
1
7
px
;
.icon
{
margin-top
:
4px
;
...
...
@@ -3155,7 +3050,7 @@ onMounted(async () => {
.title
{
height
:
24px
;
color
:
var
(
--
color-main-active
);
font-family
:
Microsoft
YaHei
;
font-family
:
"Source Han Sans CN"
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
24px
;
...
...
@@ -3165,11 +3060,20 @@ onMounted(async () => {
}
.select-main
{
margin-left
:
2
5
px
;
}
margin-left
:
2
4
px
;
margin-top
:
12px
;
.select-main1
{
width
:
260px
;
.checkbox-group
{
display
:
grid
;
grid-template-columns
:
repeat
(
2
,
160px
);
gap
:
8px
4px
;
.filter-checkbox
{
width
:
160px
;
height
:
24px
;
margin-right
:
0
!
important
;
}
}
}
}
}
...
...
@@ -3177,7 +3081,6 @@ onMounted(async () => {
.right
{
width
:
20px
;
flex
:
auto
;
max-height
:
1489px
;
box-shadow
:
0px
0px
15px
0px
rgba
(
60
,
87
,
126
,
0
.2
);
background
:
rgba
(
255
,
255
,
255
,
1
);
box-sizing
:
border-box
;
...
...
@@ -3213,12 +3116,9 @@ onMounted(async () => {
}
.content-box
{
max-height
:
1367px
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
overflow
:
hidden
;
min-height
:
790px
;
overflow
:
hidden
;
overflow-y
:
auto
;
box-sizing
:
border-box
;
.main-item
{
...
...
@@ -3349,20 +3249,6 @@ onMounted(async () => {
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
8px
;
.tag
{
height
:
28px
;
line-height
:
28px
;
text-align
:
center
;
padding
:
0
8px
;
border-radius
:
4px
;
background
:
rgba
(
231
,
243
,
255
,
1
);
color
:
var
(
--
color-main-active
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
letter-spacing
:
0px
;
}
}
}
}
...
...
src/views/decree/decreeLayout/deepdig/index.vue
浏览文件 @
5466ba6a
...
...
@@ -3,7 +3,7 @@
<div
class=
"box1"
>
<AnalysisBox
title=
"相关政令"
:showAllBtn=
"false"
>
<div
class=
"box1-main"
>
<el-empty
v-if=
"
siderList.length===0"
style=
"padding-top: 30%
"
description=
"暂无数据"
:image-size=
"100"
/>
<el-empty
v-if=
"
!siderList?.length"
style=
"padding-top: 40%;
"
description=
"暂无数据"
:image-size=
"100"
/>
<el-scrollbar
height=
"100%"
always
>
<div
class=
"left-item"
:class=
"
{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)">
<div
class=
"item-head"
>
...
...
@@ -18,6 +18,7 @@
</div>
<div
class=
"box2"
>
<AnalysisBox
title=
"政令关系挖掘"
:showAllBtn=
"false"
>
<el-empty
v-if=
"!siderList?.length"
style=
"padding-top: 20%;"
description=
"暂无数据"
:image-size=
"100"
/>
<div
class=
"box2-main"
>
<div
ref=
"containerRef"
class=
"graph-container"
></div>
</div>
...
...
src/views/decree/decreeLayout/index.vue
浏览文件 @
5466ba6a
...
...
@@ -8,22 +8,16 @@
<div
class=
"layout-main-header-left-box"
>
<div
class=
"left-box-top"
>
<div
class=
"icon"
>
<img
v-if=
"summaryInfo.imageUrl"
:src=
"summaryInfo.imageUrl"
alt=
""
style=
"height: 40px; margin-top: 12px"
/>
<img
v-else
:src=
"USALogo"
alt=
""
/>
<img
:src=
"summaryInfo.imageUrl || USALogo"
alt=
""
/>
</div>
<div
class=
"info"
>
<div
class=
"info-box1
"
>
{{
summaryInfo
.
name
}}
</div>
<div
class=
"info-box1
one-line-ellipsis"
>
{{
summaryInfo
.
name
||
"--"
}}
</div>
<div
class=
"info-box2"
>
<div
class=
"info-box2-item
item1"
>
{{
summaryInfo
.
postDate
}}
</div>
<div
class=
"info-box2-item
"
>
{{
summaryInfo
.
postDate
||
"--"
}}
</div>
|
<div
class=
"info-box2-item
item2"
>
{{
summaryInfo
.
orgName
}}
</div>
<div
class=
"info-box2-item
"
>
{{
summaryInfo
.
orgName
||
"--"
}}
</div>
|
<div
class=
"info-box2-item
item3"
>
{{
summaryInfo
.
ename
}}
</div>
<div
class=
"info-box2-item
one-line-ellipsis"
>
{{
summaryInfo
.
ename
||
"--"
}}
</div>
</div>
</div>
</div>
...
...
@@ -47,8 +41,8 @@
</div>
<div
class=
"layout-main-header-right-box"
>
<div
class=
"right-box-top"
>
<div
class=
"time"
>
{{
summaryInfo
.
postDate
}}
</div>
<div
class=
"name"
>
{{
summaryInfo
.
orgName
}}
</div>
<div
class=
"time"
>
{{
summaryInfo
.
postDate
||
"--"
}}
</div>
<div
class=
"name"
>
{{
summaryInfo
.
orgName
||
"--"
}}
</div>
</div>
<div
class=
"right-box-bottom"
>
<div
class=
"btn"
@
click=
"handleShowReport"
>
...
...
@@ -203,12 +197,12 @@ const mainHeaderBtnList = ref([
name
:
"深度挖掘"
,
path
:
"/decreeLayout/deepDig"
},
//
{
//
icon: icon3,
//
activeIcon: icon3Active,
//
name: "影响分析",
//
path: "/decreeLayout/influence"
//
},
{
icon
:
icon3
,
activeIcon
:
icon3Active
,
name
:
"影响分析"
,
path
:
"/decreeLayout/influence"
},
]);
const
activeTitle
=
ref
(
"政令概况"
);
...
...
@@ -377,9 +371,9 @@ onMounted(() => {
flex-direction
:
column
;
.header-main
{
width
:
100%
;
b
ackground-color
:
#fff
;
box-shadow
:
0px
4px
10px
0px
rgba
(
0
,
0
,
0
,
0
.05
);
overflow
:
hidden
;
b
order-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
)
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
background
:
rgba
(
255
,
255
,
255
,
1
)
;
}
.layout-main-header
{
width
:
1600px
;
...
...
@@ -396,30 +390,32 @@ onMounted(() => {
margin
:
0
auto
;
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
}
.layout-main-header-left-box
{
width
:
1100px
;
margin-top
:
13px
;
width
:
20px
;
flex
:
auto
;
margin-top
:
12px
;
.left-box-top
{
height
:
64px
;
display
:
flex
;
align-items
:
center
;
.icon
{
width
:
64px
;
height
:
64px
;
border-radius
:
4px
;
height
:
40px
;
overflow
:
hidden
;
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
fill
;
}
}
.info
{
width
:
700px
;
margin-left
:
9px
;
margin-left
:
10px
;
margin-right
:
40px
;
width
:
20px
;
flex
:
auto
;
.info-box1
{
width
:
700px
;
width
:
100%
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
20px
;
...
...
@@ -428,9 +424,6 @@ onMounted(() => {
letter-spacing
:
0px
;
text-align
:
left
;
margin-top
:
5px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.info-box2
{
margin-top
:
5px
;
...
...
@@ -444,15 +437,13 @@ onMounted(() => {
letter-spacing
:
0px
;
text-align
:
left
;
display
:
flex
;
margin-left
:
-10px
;
.info-box2-item
{
white-space
:
nowrap
;
padding
:
0
10px
;
}
.item3
{
max-width
:
420px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
.info-box2-item
:first-child
{
padding-left
:
0px
;
}
}
}
...
...
@@ -498,9 +489,9 @@ onMounted(() => {
}
}
.layout-main-header-right-box
{
width
:
450px
;
margin-top
:
19px
;
.right-box-top
{
white-space
:
nowrap
;
padding-top
:
11px
;
.time
{
height
:
24px
;
line-height
:
24px
;
...
...
@@ -710,49 +701,5 @@ 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%;
// }
// }
// }
}
</
style
>
\ No newline at end of file
src/views/decree/decreeLayout/influence/ChartRelation.vue
deleted
100644 → 0
浏览文件 @
d5eb651a
<
template
>
<div
class=
"relation-graph-wrapper"
>
<div
class=
"graph-controls"
>
<!-- 这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。 -->
<div
v-for=
"item in controlBtns"
:key=
"item.type"
:class=
"['control-btn',
{ 'control-btn-active': currentLayoutType === item.type }]" @click="handleClickControlBtn(item.type)">
<img
:src=
"item.icon"
alt=
""
/>
</div>
</div>
<div
ref=
"containerRef"
class=
"graph-container"
></div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
,
onUnmounted
,
watch
,
nextTick
}
from
'vue'
import
G6
from
'@antv/g6'
import
{
Close
}
from
'@element-plus/icons-vue'
import
echartsIcon01
from
'./assets/images/echartsicon01.png'
import
echartsIcon02
from
'./assets/images/echartsicon02.png'
import
echartsIcon03
from
'./assets/images/echartsicon03.png'
const
props
=
defineProps
({
graphData
:
{
type
:
Object
,
default
:
()
=>
({
nodes
:
[],
links
:
[]
})
},
treeData
:
{
type
:
Object
,
default
:
()
=>
null
},
controlActive
:
{
type
:
Number
,
default
:
1
}
})
const
emit
=
defineEmits
([
'nodeClick'
,
'layoutChange'
])
const
containerRef
=
ref
(
null
)
const
graphInstance
=
ref
(
null
)
const
currentLayoutType
=
ref
(
1
)
const
controlBtns
=
[
{
type
:
1
,
icon
:
echartsIcon01
,
name
:
'力导向布局'
},
{
type
:
2
,
icon
:
echartsIcon02
,
name
:
'树布局'
},
{
type
:
3
,
icon
:
echartsIcon03
,
name
:
'环状布局'
}
]
const
initGraph
=
(
layoutType
=
1
)
=>
{
if
(
!
containerRef
.
value
)
return
destroyGraph
()
nextTick
(()
=>
{
const
width
=
containerRef
.
value
.
offsetWidth
||
800
const
height
=
containerRef
.
value
.
offsetHeight
||
600
switch
(
layoutType
)
{
case
1
:
initNormalGraph
(
layoutType
,
width
,
height
)
break
case
2
:
initTreeGraph
(
width
,
height
)
break
case
3
:
initCircularGraph
(
width
,
height
)
break
}
})
}
const
initNormalGraph
=
(
layoutType
,
width
,
height
)
=>
{
const
data
=
processGraphData
(
props
.
graphData
)
if
(
!
data
.
nodes
||
data
.
nodes
.
length
===
0
)
return
const
layout
=
{
type
:
'force'
,
center
:
[
width
/
2
,
height
/
2
],
preventOverlap
:
true
,
nodeSpacing
:
80
,
linkDistance
:
250
,
nodeStrength
:
-
800
,
edgeStrength
:
0.1
,
collideStrength
:
0.8
,
alphaDecay
:
0.01
,
alphaMin
:
0.001
}
graphInstance
.
value
=
new
G6
.
Graph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
true
,
fitViewPadding
:
100
,
fitCenter
:
true
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
'easeLinear'
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
'drag-canvas'
,
'zoom-canvas'
,
'drag-node'
,
{
type
:
'activate-relations'
,
trigger
:
'mouseenter'
,
resetSelected
:
true
}
]
},
layout
,
defaultNode
:
{
type
:
'image'
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
'circle'
,
r
:
20
},
labelCfg
:
{
position
:
'bottom'
,
offset
:
10
,
style
:
{
fill
:
'#333'
,
fontSize
:
11
,
fontFamily
:
'Microsoft YaHei'
,
textAlign
:
'center'
,
background
:
{
fill
:
'rgba(255, 255, 255, 0.95)'
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
'quadratic'
,
style
:
{
stroke
:
'#5B8FF9'
,
lineWidth
:
3
,
opacity
:
0.9
,
endArrow
:
{
path
:
'M 0,0 L 12,6 L 12,-6 Z'
,
fill
:
'#5B8FF9'
}
},
labelCfg
:
{
autoRotate
:
true
,
style
:
{
fill
:
'#333'
,
fontSize
:
10
,
fontFamily
:
'Microsoft YaHei'
,
background
:
{
fill
:
'#fff'
,
padding
:
[
2
,
4
,
2
,
4
],
radius
:
2
}
}
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
'#1459BB'
,
shadowBlur
:
15
,
stroke
:
'#1459BB'
,
lineWidth
:
3
},
inactive
:
{
opacity
:
0.3
}
},
edgeStateStyles
:
{
active
:
{
stroke
:
'#1459BB'
,
lineWidth
:
4
},
inactive
:
{
opacity
:
0.15
}
}
})
graphInstance
.
value
.
data
(
data
)
graphInstance
.
value
.
render
()
bindGraphEvents
()
}
const
initCircularGraph
=
(
width
,
height
)
=>
{
const
data
=
processGraphData
(
props
.
graphData
)
if
(
!
data
.
nodes
||
data
.
nodes
.
length
===
0
)
return
const
centerX
=
width
/
2
const
centerY
=
height
/
2
const
radius
=
Math
.
min
(
width
,
height
)
/
2
-
120
const
otherNodes
=
data
.
nodes
.
filter
(
n
=>
!
n
.
isCenter
)
const
nodeCount
=
otherNodes
.
length
otherNodes
.
forEach
((
node
,
index
)
=>
{
const
angle
=
(
2
*
Math
.
PI
*
index
)
/
nodeCount
-
Math
.
PI
/
2
node
.
x
=
centerX
+
radius
*
Math
.
cos
(
angle
)
node
.
y
=
centerY
+
radius
*
Math
.
sin
(
angle
)
})
const
centerNode
=
data
.
nodes
.
find
(
n
=>
n
.
isCenter
)
if
(
centerNode
)
{
centerNode
.
x
=
centerX
centerNode
.
y
=
centerY
centerNode
.
fx
=
centerX
centerNode
.
fy
=
centerY
}
graphInstance
.
value
=
new
G6
.
Graph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
false
,
fitCenter
:
false
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
'easeLinear'
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
'drag-canvas'
,
'zoom-canvas'
,
'drag-node'
,
{
type
:
'activate-relations'
,
trigger
:
'mouseenter'
,
resetSelected
:
true
}
]
},
defaultNode
:
{
type
:
'image'
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
'circle'
,
r
:
20
},
labelCfg
:
{
position
:
'bottom'
,
offset
:
10
,
style
:
{
fill
:
'#333'
,
fontSize
:
11
,
fontFamily
:
'Microsoft YaHei'
,
textAlign
:
'center'
,
background
:
{
fill
:
'rgba(255, 255, 255, 0.95)'
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
'quadratic'
,
style
:
{
stroke
:
'#5B8FF9'
,
lineWidth
:
3
,
opacity
:
0.9
,
endArrow
:
{
path
:
'M 0,0 L 12,6 L 12,-6 Z'
,
fill
:
'#5B8FF9'
}
},
labelCfg
:
{
autoRotate
:
true
,
style
:
{
fill
:
'#333'
,
fontSize
:
10
,
fontFamily
:
'Microsoft YaHei'
,
background
:
{
fill
:
'#fff'
,
padding
:
[
2
,
4
,
2
,
4
],
radius
:
2
}
}
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
'#1459BB'
,
shadowBlur
:
15
,
stroke
:
'#1459BB'
,
lineWidth
:
3
},
inactive
:
{
opacity
:
0.3
}
},
edgeStateStyles
:
{
active
:
{
stroke
:
'#1459BB'
,
lineWidth
:
4
},
inactive
:
{
opacity
:
0.15
}
}
})
graphInstance
.
value
.
data
(
data
)
graphInstance
.
value
.
render
()
bindGraphEvents
()
}
const
initTreeGraph
=
(
width
,
height
)
=>
{
const
treeDataSource
=
convertGraphToTree
(
props
.
graphData
)
if
(
!
treeDataSource
)
return
graphInstance
.
value
=
new
G6
.
TreeGraph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
true
,
fitViewPadding
:
80
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
'easeLinear'
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
'drag-canvas'
,
'zoom-canvas'
,
'drag-node'
,
{
type
:
'collapse-expand'
,
onChange
:
function
onChange
(
item
,
collapsed
)
{
const
data
=
item
.
getModel
()
data
.
collapsed
=
collapsed
return
true
}
}
]
},
layout
:
{
type
:
'compactBox'
,
direction
:
'LR'
,
getId
:
function
getId
(
d
)
{
return
d
.
id
},
getHeight
:
function
getHeight
()
{
return
16
},
getWidth
:
function
getWidth
()
{
return
16
},
getVGap
:
function
getVGap
()
{
return
30
},
getHGap
:
function
getHGap
()
{
return
120
}
},
defaultNode
:
{
type
:
'image'
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
'circle'
,
r
:
20
},
labelCfg
:
{
position
:
'right'
,
offset
:
10
,
style
:
{
fill
:
'#333'
,
fontSize
:
11
,
fontFamily
:
'Microsoft YaHei'
,
background
:
{
fill
:
'rgba(255, 255, 255, 0.95)'
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
'cubic-horizontal'
,
style
:
{
stroke
:
'#5B8FF9'
,
lineWidth
:
3
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
'#1459BB'
,
shadowBlur
:
15
,
stroke
:
'#1459BB'
,
lineWidth
:
3
}
}
})
graphInstance
.
value
.
data
(
treeDataSource
)
graphInstance
.
value
.
render
()
graphInstance
.
value
.
fitView
()
bindGraphEvents
()
}
const
convertGraphToTree
=
(
graphData
)
=>
{
if
(
!
graphData
||
!
graphData
.
nodes
||
graphData
.
nodes
.
length
===
0
)
{
return
null
}
const
nodes
=
graphData
.
nodes
const
links
=
graphData
.
links
||
graphData
.
edges
||
[]
const
centerNode
=
nodes
[
0
]
const
centerId
=
String
(
centerNode
.
id
||
'0'
)
const
childIdSet
=
new
Set
()
const
childrenNodes
=
[]
links
.
forEach
((
link
)
=>
{
const
source
=
String
(
link
.
source
)
const
target
=
String
(
link
.
target
)
if
(
source
===
centerId
&&
!
childIdSet
.
has
(
target
))
{
const
node
=
nodes
.
find
(
n
=>
String
(
n
.
id
)
===
target
)
if
(
node
)
{
childIdSet
.
add
(
target
)
childrenNodes
.
push
({
id
:
target
,
label
:
node
.
name
||
''
,
img
:
node
.
image
||
echartsIcon03
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
})
}
}
else
if
(
target
===
centerId
&&
!
childIdSet
.
has
(
source
))
{
const
node
=
nodes
.
find
(
n
=>
String
(
n
.
id
)
===
source
)
if
(
node
)
{
childIdSet
.
add
(
source
)
childrenNodes
.
push
({
id
:
source
,
label
:
node
.
name
||
''
,
img
:
node
.
image
||
echartsIcon03
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
})
}
}
})
if
(
childrenNodes
.
length
===
0
)
{
nodes
.
slice
(
1
).
forEach
((
node
)
=>
{
const
nodeId
=
String
(
node
.
id
)
if
(
!
childIdSet
.
has
(
nodeId
))
{
childIdSet
.
add
(
nodeId
)
childrenNodes
.
push
({
id
:
nodeId
,
label
:
node
.
name
||
''
,
img
:
node
.
image
||
echartsIcon03
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
})
}
})
}
return
{
id
:
centerId
,
label
:
centerNode
.
name
||
''
,
img
:
centerNode
.
image
||
echartsIcon03
,
size
:
centerNode
.
symbolSize
||
60
,
name
:
centerNode
.
name
,
image
:
centerNode
.
image
,
isSanctioned
:
centerNode
.
isSanctioned
,
children
:
childrenNodes
}
}
const
processGraphData
=
(
rawData
)
=>
{
if
(
!
rawData
||
!
rawData
.
nodes
||
rawData
.
nodes
.
length
===
0
)
{
return
{
nodes
:
[],
edges
:
[]
}
}
const
nodeMap
=
new
Map
()
const
nodes
=
[]
rawData
.
nodes
.
forEach
((
node
,
index
)
=>
{
const
nodeId
=
String
(
node
.
id
||
index
)
if
(
nodeMap
.
has
(
nodeId
))
{
return
}
nodeMap
.
set
(
nodeId
,
true
)
const
isCenter
=
index
===
0
const
size
=
node
.
symbolSize
||
(
isCenter
?
60
:
40
)
nodes
.
push
({
id
:
nodeId
,
label
:
node
.
name
||
''
,
img
:
node
.
image
||
echartsIcon03
,
size
,
isCenter
,
clipCfg
:
{
show
:
true
,
type
:
'circle'
,
r
:
size
/
2
},
style
:
{
cursor
:
'pointer'
},
labelCfg
:
{
position
:
'bottom'
,
offset
:
12
,
style
:
{
fill
:
isCenter
?
'#1459BB'
:
'#333'
,
fontSize
:
isCenter
?
13
:
11
,
fontWeight
:
isCenter
?
'bold'
:
'normal'
,
fontFamily
:
'Microsoft YaHei'
,
textAlign
:
'center'
}
},
...
node
,
id
:
nodeId
})
})
const
edgeMap
=
new
Map
()
const
edges
=
[]
const
rawEdges
=
rawData
.
links
||
rawData
.
edges
||
[]
rawEdges
.
forEach
((
edge
,
index
)
=>
{
const
source
=
String
(
edge
.
source
)
const
target
=
String
(
edge
.
target
)
const
edgeKey
=
`
${
source
}
-
${
target
}
`
if
(
edgeMap
.
has
(
edgeKey
))
{
return
}
if
(
!
nodeMap
.
has
(
source
)
||
!
nodeMap
.
has
(
target
))
{
return
}
edgeMap
.
set
(
edgeKey
,
true
)
edges
.
push
({
id
:
`edge-
${
index
}
`
,
source
,
target
,
label
:
edge
.
name
||
''
})
})
return
{
nodes
,
edges
}
}
const
bindGraphEvents
=
()
=>
{
if
(
!
graphInstance
.
value
)
return
graphInstance
.
value
.
on
(
'node:click'
,
(
evt
)
=>
{
const
node
=
evt
.
item
const
model
=
node
.
getModel
()
emit
(
'nodeClick'
,
model
)
})
graphInstance
.
value
.
on
(
'canvas:click'
,
()
=>
{
})
}
const
handleClickControlBtn
=
(
btn
)
=>
{
currentLayoutType
.
value
=
btn
emit
(
'layoutChange'
,
btn
)
initGraph
(
btn
)
}
const
destroyGraph
=
()
=>
{
if
(
graphInstance
.
value
)
{
graphInstance
.
value
.
destroy
()
graphInstance
.
value
=
null
}
}
const
handleResize
=
()
=>
{
if
(
graphInstance
.
value
&&
containerRef
.
value
)
{
const
width
=
containerRef
.
value
.
offsetWidth
const
height
=
containerRef
.
value
.
offsetHeight
graphInstance
.
value
.
changeSize
(
width
,
height
)
graphInstance
.
value
.
fitView
()
}
}
watch
(
()
=>
props
.
graphData
,
()
=>
{
initGraph
(
currentLayoutType
.
value
)
}
)
watch
(
()
=>
props
.
treeData
,
()
=>
{
if
(
currentLayoutType
.
value
===
2
)
{
initGraph
(
2
)
}
}
)
watch
(
()
=>
props
.
controlActive
,
(
newVal
)
=>
{
if
(
newVal
!==
currentLayoutType
.
value
)
{
handleClickControlBtn
(
newVal
)
}
}
)
onMounted
(()
=>
{
initGraph
(
1
)
window
.
addEventListener
(
'resize'
,
handleResize
)
})
onUnmounted
(()
=>
{
window
.
removeEventListener
(
'resize'
,
handleResize
)
destroyGraph
()
})
defineExpose
({
refresh
:
()
=>
initGraph
(
currentLayoutType
.
value
),
changeLayout
:
(
type
)
=>
handleClickControlBtn
(
type
),
getGraph
:
()
=>
graphInstance
.
value
})
</
script
>
<
style
lang=
"scss"
scoped
>
.relation-graph-wrapper
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
}
.graph-container
{
width
:
100%
;
height
:
100%
;
}
.graph-controls
{
position
:
absolute
;
top
:
16px
;
right
:
16px
;
display
:
flex
;
gap
:
8px
;
z-index
:
10
;
.control-btn
{
width
:
32px
;
height
:
32px
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
img
{
width
:
16px
;
height
:
16px
;
}
&
:hover
{
border-color
:
rgba
(
5
,
95
,
194
,
0
.5
);
}
}
.control-btn-active
{
border-color
:
rgba
(
5
,
95
,
194
,
1
);
background
:
rgba
(
231
,
243
,
255
,
1
);
}
}
</
style
>
\ No newline at end of file
src/views/decree/decreeLayout/influence/com/AiTips.vue
0 → 100644
浏览文件 @
5466ba6a
<
template
>
<div
class=
"view-box"
>
<div
class=
"icon-left"
>
<img
src=
"../../assets/icons/ai.png"
alt=
""
>
</div>
<div
class=
"tips-content"
>
{{
props
.
tips
}}
</div>
<div
class=
"icon-right"
>
<img
src=
"../../assets/icons/right.png"
alt=
""
>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
name=
"AiTips"
>
const
props
=
defineProps
({
tips
:
{
type
:
String
,
default
:
''
}
});
</
script
>
<
style
scoped
lang=
"scss"
>
.view-box
{
width
:
100%
;
display
:
flex
;
align-items
:
center
;
padding
:
7px
12px
;
border
:
1px
solid
rgba
(
231
,
243
,
255
,
1
);
border-radius
:
4px
;
background
:
rgba
(
246
,
250
,
255
,
1
);
.icon-left
{
width
:
20px
;
height
:
20px
;
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
}
}
.tips-content
{
color
:
rgb
(
5
,
95
,
194
);
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
margin-left
:
13px
;
flex
:
1
;
}
.icon-right
{
width
:
24px
;
height
:
24px
;
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
}
}
}
</
style
>
\ No newline at end of file
src/views/decree/decreeLayout/influence/ChartChain.vue
→
src/views/decree/decreeLayout/influence/
com/
ChartChain.vue
浏览文件 @
5466ba6a
<
template
>
<div
class=
"view-box"
>
<div
class=
"right-main"
>
<div
class=
"right-main-content"
>
<div
class=
"hintWrap"
>
<div
class=
"icon1"
></div>
<div
class=
"title"
>
这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。
</div>
<div
class=
"icon2Wrap"
>
<div
class=
"icon2"
></div>
</div>
</div>
<div
class=
"right-main-content-main"
>
<div
class=
"fishbone-wrapper"
>
<div
class=
"fishbone-scroll-container"
ref=
"scrollContainerRef"
>
<div
class=
"fishbone"
v-if=
"dataList.length > 0"
>
<div
class=
"main-line"
:style=
"
{ width: dataList.length * 200 + 300 + 'px' }">
<el-empty
v-if=
"!dataList?.length"
style=
"padding-top: 15%;"
description=
"暂无数据"
:image-size=
"100"
/>
<div
v-if=
"dataList.length"
class=
"main-content-main"
>
<div
class=
"main-mask"
@
wheel
.
prevent=
"handleWheel"
@
mousedown=
"handleMouseDown"
@
mouseup=
"handleMouseUp"
@
mouseleave=
"handleMouseUp"
@
mousemove=
"handleMouseMove"
></div>
<div
class=
"fishbone-container"
:style=
"
{ transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`, transformOrigin: 'center center' }">
<!-- 主轴上的标签 -->
<div
class=
"main-line"
:style=
"
{ width: dataList.length * 200 + 300 + 'px' }">
<div
class=
"main-line-text"
v-for=
"(item, index) in dataList"
:key=
"'label-' + index"
:class=
"
{
'blue-theme': index
<
2
,
...
...
@@ -27,36 +22,35 @@
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for=
"(causeGroup, groupIndex) in onFilterData(1)"
:key=
"'top-' +
groupIndex"
:class=
"getTopBoneClass(groupIndex)"
:style=
"
{ left: groupIndex * 400 + 42
0 + 'px' }">
<div
v-for=
"(causeGroup, groupIndex) in onFilterData(1)"
:key=
"
groupIndex"
class=
"top-bone"
:style=
"
{ left: groupIndex * 400 + 510 + 'px', height: (causeGroup.causes?.length) * 22 + 10
0 + 'px' }">
<div
class=
"left-bone"
>
<div
class=
"left-bone-item"
v-for=
"(item, index) in getLeftItems(causeGroup.causes)"
:key=
"'left-' + index
"
>
<div
class=
"left-bone-item"
v-for=
"item in getLeftItems(causeGroup.causes)"
:key=
"item.id
"
>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
<div
class=
"line"
></div>
</div>
</div>
<div
class=
"right-bone"
>
<div
class=
"right-bone-item"
v-for=
"(item, index) in getRightItems(causeGroup.causes)"
:key=
"'right-' + index
"
>
<div
class=
"right-bone-item"
v-for=
"item in getRightItems(causeGroup.causes)"
:key=
"item.id
"
>
<div
class=
"line"
></div>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div
v-for=
"(causeGroup, groupIndex) in onFilterData(0)"
:key=
"'bottom-' +
groupIndex"
:class=
"getBottomBoneClass(groupIndex)"
:style=
"
{ left: groupIndex * 400 + 22
0 + 'px' }">
<div
v-for=
"(causeGroup, groupIndex) in onFilterData(0)"
:key=
"
groupIndex"
class=
"bottom-bone"
:style=
"
{ left: groupIndex * 400 + 310 + 'px', height: (causeGroup.causes?.length) * 22 + 10
0 + 'px' }">
<div
class=
"left-bone"
>
<div
class=
"left-bone-item"
v-for=
"(item, index) in getLeftItems(causeGroup.causes)"
:key=
"'left-bottom-' + index
"
>
<div
class=
"left-bone-item"
v-for=
"item in getRightItems(causeGroup.causes)"
:key=
"item.id
"
>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
<div
class=
"line"
></div>
</div>
</div>
<div
class=
"right-bone"
>
<div
class=
"right-bone-item"
v-for=
"(item, index) in getRightItems(causeGroup.causes)"
:key=
"'right-bottom-' + index
"
>
<div
class=
"right-bone-item"
v-for=
"item in getLeftItems(causeGroup.causes)"
:key=
"item.id
"
>
<div
class=
"line"
></div>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
...
...
@@ -64,14 +58,8 @@
</div>
</div>
</div>
<div
v-else
style=
"display: flex; justify-content: center; align-items: center; height: 200px; width: 100%"
>
<el-empty
description=
"暂无相关数据"
/>
</div>
</div>
</div>
</div>
<div
class=
"right-main-content-footer"
>
<div
v-if=
"dataList.length"
class=
"main-content-footer"
>
<div
class=
"footer-item footer-item1"
>
<div
class=
"footer-item-bottom"
>
<div
class=
"icon"
>
...
...
@@ -119,16 +107,49 @@
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
script
setup
name
=
"ChartChain"
>
import
{
ref
,
onMounted
}
from
"vue"
;
import
defaultIcon2
from
"@/assets/icons/default-icon2.png"
;
import
noticeIcon
from
"./assets/images/notice-icon.png"
;
import
noticeIcon
from
".
.
/assets/images/notice-icon.png"
;
import
{
getDeepMiningSelect
,
getDeepMiningIndustry
,
getDeepMiningIndustryFishbone
,
getDeepMiningIndustryEntity
}
from
"@/api/exportControlV2.0"
;
// 缩放功能处理
const
scale
=
ref
(
1
)
const
minScale
=
0.1
const
maxScale
=
10
const
handleWheel
=
(
e
)
=>
{
if
(
e
.
deltaY
<
0
)
{
// 放大:不超过最大值
scale
.
value
=
Math
.
min
(
scale
.
value
+
0.1
,
maxScale
)
}
else
{
// 缩小:不低于最小值
scale
.
value
=
Math
.
max
(
scale
.
value
-
0.1
,
minScale
)
}
}
// 移动功能处理
const
translateX
=
ref
(
0
)
// X轴位移
const
translateY
=
ref
(
0
)
// Y轴位移
let
isDragging
=
false
let
startX
=
0
let
startY
=
0
const
handleMouseMove
=
(
e
)
=>
{
if
(
!
isDragging
)
return
translateX
.
value
=
e
.
clientX
-
startX
translateY
.
value
=
e
.
clientY
-
startY
}
const
handleMouseDown
=
(
e
)
=>
{
// 排除右键/中键,只响应左键(e.button=0为左键)
if
(
e
.
button
!==
0
)
return
isDragging
=
true
startX
=
e
.
clientX
-
translateX
.
value
startY
=
e
.
clientY
-
translateY
.
value
}
const
handleMouseUp
=
()
=>
{
isDragging
=
false
}
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const
cnEntityOnChainData
=
ref
({
}
);
const
getCnEntityOnChainData
=
async
()
=>
{
...
...
@@ -173,16 +194,6 @@ const getRightItems = items => {
const
midpoint
=
Math
.
ceil
(
items
.
length
/
2
);
return
items
.
slice
(
midpoint
);
}
;
// 获取上方鱼骨图位置类名
const
getTopBoneClass
=
index
=>
{
const
positions
=
[
"top-bone"
,
"top-bone1"
,
"top-bone2"
];
return
positions
[
index
%
3
]
||
"top-bone"
;
}
;
// 获取下方鱼骨图位置类名
const
getBottomBoneClass
=
index
=>
{
const
positions
=
[
"bottom-bone"
,
"bottom-bone1"
,
"bottom-bone2"
];
return
positions
[
index
%
3
]
||
"bottom-bone"
;
}
;
const
getFishboneData
=
async
()
=>
{
const
currentSanction
=
sanctionList
.
value
.
find
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
const
date
=
currentSanction
?
currentSanction
.
date
:
''
;
...
...
@@ -305,126 +316,35 @@ onMounted(() => {
.
view
-
box
{
width
:
100
%
;
height
:
100
%
;
}
.
right
-
main
{
height
:
100
%
;
padding
:
11
px
16
px
20
px
;
.
right
-
main
-
content
{
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
.
hintWrap
{
display
:
flex
;
align
-
items
:
center
;
padding
:
7
px
12
px
;
border
:
1
px
solid
rgba
(
231
,
243
,
255
,
1
);
border
-
radius
:
4
px
;
background
:
rgba
(
246
,
250
,
255
,
1
);
margin
-
bottom
:
9
px
;
.
icon1
{
width
:
19
px
;
.
main
-
content
-
main
{
position
:
relative
;
height
:
20
px
;
background
-
image
:
url
(
"../assets/icons/ai.png"
);
background
-
size
:
100
%
100
%
;
flex
-
shrink
:
0
;
}
.
title
{
color
:
rgb
(
5
,
95
,
194
);
font
-
size
:
16
px
;
font
-
weight
:
400
;
line
-
height
:
24
px
;
margin
-
left
:
13
px
;
flex
:
1
;
}
.
icon2Wrap
{
width
:
24
px
;
height
:
24
px
;
background
-
color
:
rgba
(
231
,
243
,
255
,
1
);
flex
:
auto
;
display
:
flex
;
justify
-
content
:
center
;
align
-
items
:
center
;
border
-
radius
:
12
px
;
margin
-
left
:
20
px
;
flex
-
shrink
:
0
;
.
icon2
{
width
:
24
px
;
height
:
24
px
;
background
-
image
:
url
(
"../assets/icons/right.png"
);
background
-
size
:
100
%
100
%
;
}
}
}
.
right
-
main
-
content
-
main
{
flex
:
1
;
position
:
relative
;
justify
-
content
:
center
;
overflow
:
hidden
;
.
fishbone
-
wrapper
{
position
:
relative
;
width
:
100
%
;
height
:
100
%
;
}
.
fishbone
-
scroll
-
container
{
display
:
flex
;
align
-
items
:
center
;
.
main
-
mask
{
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100
%
;
height
:
100
%
;
overflow
-
x
:
auto
;
overflow
-
y
:
auto
;
scrollbar
-
width
:
thin
;
scrollbar
-
color
:
rgba
(
144
,
202
,
249
,
0.5
)
transparent
;
&
::
-
webkit
-
scrollbar
{
height
:
6
px
;
z
-
index
:
3
;
}
&
::
-
webkit
-
scrollbar
-
track
{
background
:
transparent
;
}
&
::
-
webkit
-
scrollbar
-
thumb
{
background
-
color
:
rgba
(
144
,
202
,
249
,
0.5
);
border
-
radius
:
3
px
;
}
}
.
fishbone
{
.
fishbone
-
container
{
position
:
relative
;
width
:
fit
-
content
;
height
:
100
%
;
margin
-
top
:
40
px
;
min
-
width
:
100
%
;
padding
-
left
:
275
px
;
margin
-
left
:
40
px
;
.
main
-
line
{
margin
-
top
:
280
px
;
width
:
1888
px
;
height
:
3
px
;
background
:
rgb
(
230
,
231
,
232
);
display
:
flex
;
justify
-
content
:
space
-
between
;
align
-
items
:
center
;
padding
:
0
100
px
;
// 虚线
&
::
after
{
content
:
''
;
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100
%
;
height
:
100
%
;
}
// 添加中间的文字块
.
main
-
line
-
text
{
...
...
@@ -472,28 +392,28 @@ onMounted(() => {
.
top
-
bone
{
position
:
absolute
;
top
:
20
px
;
right
:
200
px
;
bottom
:
0
px
;
width
:
3
px
;
height
:
260
px
;
background
:
rgb
(
230
,
231
,
232
);
transform
-
origin
:
bottom
center
;
transform
:
skew
(
30
deg
);
z
-
index
:
1
;
.
left
-
bone
{
color
:
#
777
;
position
:
absolute
;
top
:
0
;
left
:
-
150
px
;
width
:
150
px
;
height
:
50
px
;
top
:
-
20
px
;
right
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
end
;
// overflow: hidden;
.
left
-
bone
-
item
{
transform
:
skew
(
-
30
deg
);
height
:
45
px
;
margin
-
bottom
:
2
px
;
margin
-
top
:
2
px
;
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
end
;
align
-
items
:
center
;
...
...
@@ -519,17 +439,18 @@ onMounted(() => {
.
right
-
bone
{
color
:
#
777
;
position
:
absolute
;
top
:
0
;
right
:
-
150
px
;
width
:
150
px
;
height
:
210
px
;
overflow
:
hidden
;
top
:
-
44
px
;
left
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
end
;
.
right
-
bone
-
item
{
transform
:
skew
(
-
30
deg
);
height
:
39
px
;
margin
-
bottom
:
2
px
;
margin
-
top
:
2
px
;
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
start
;
align
-
items
:
center
;
...
...
@@ -554,39 +475,30 @@ onMounted(() => {
}
}
.
top
-
bone1
{
@
extend
.
top
-
bone
;
right
:
500
px
;
}
.
top
-
bone2
{
@
extend
.
top
-
bone
;
right
:
800
px
;
}
.
bottom
-
bone
{
position
:
absolute
;
top
:
280
px
;
right
:
360
px
;
top
:
0
px
;
width
:
3
px
;
height
:
260
px
;
background
:
rgb
(
230
,
231
,
232
);
transform
-
origin
:
top
center
;
transform
:
skew
(
-
30
deg
);
z
-
index
:
1
;
.
left
-
bone
{
color
:
#
777
;
position
:
absolute
;
top
:
50
px
;
left
:
-
150
px
;
width
:
150
px
;
height
:
260
px
;
bottom
:
-
44
px
;
right
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
start
;
.
left
-
bone
-
item
{
transform
:
skew
(
30
deg
);
height
:
39
px
;
margin
-
bottom
:
2
px
;
margin
-
top
:
2
px
;
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
end
;
align
-
items
:
center
;
...
...
@@ -613,16 +525,18 @@ onMounted(() => {
.
right
-
bone
{
color
:
#
777
;
position
:
absolute
;
top
:
50
px
;
right
:
-
150
px
;
width
:
150
px
;
height
:
260
px
;
bottom
:
-
20
px
;
left
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
start
;
.
right
-
bone
-
item
{
transform
:
skew
(
30
deg
);
height
:
35
px
;
margin
-
bottom
:
2
px
;
margin
-
top
:
2
px
;
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
start
;
align
-
items
:
center
;
...
...
@@ -646,19 +560,9 @@ onMounted(() => {
}
}
}
.
bottom
-
bone1
{
@
extend
.
bottom
-
bone
;
right
:
660
px
;
}
.
bottom
-
bone2
{
@
extend
.
bottom
-
bone
;
right
:
960
px
;
}
}
.
right
-
main
-
content
-
footer
{
.
main
-
content
-
footer
{
margin
-
top
:
16
px
;
display
:
flex
;
justify
-
content
:
space
-
between
;
...
...
@@ -725,6 +629,5 @@ onMounted(() => {
}
}
}
}
}
<
/style>
\ No newline at end of file
src/views/decree/decreeLayout/influence/index.vue
浏览文件 @
5466ba6a
...
...
@@ -16,7 +16,7 @@
</div>
<div
class=
"data-title"
>
实体名称
</div>
<div
style=
"height: 20px; flex: auto;"
>
<el-empty
v-if=
"
showCompanyList.length === 0"
style=
"padding-top: 30%
"
description=
"暂无数据"
:image-size=
"100"
/>
<el-empty
v-if=
"
!showCompanyList?.length"
style=
"padding-top: 35%;
"
description=
"暂无数据"
:image-size=
"100"
/>
<el-scrollbar
height=
"100%"
always
>
<div
class=
"list-data"
>
<div
class=
"list-item"
v-for=
"item in showCompanyList"
:key=
"item.id"
:class=
"
{ 'item-active': activeEntityId === item.id }" @click="handleToCompanyDetail(item)">
...
...
@@ -75,19 +75,15 @@
<
/div
>
<
/div
>
<
/template
>
<
div
class
=
"box2-main"
v
-
if
=
"contentType==1"
>
<
div
class
=
"box2-main"
>
<
AiTips
:
tips
=
"tips"
/>
<
div
class
=
"graph-box"
v
-
if
=
"contentType==1"
>
<
ChartChain
/>
<
/div
>
<
div
class
=
"box2-main"
v
-
if
=
"contentType==2"
>
<!--
<
ChartRelation
:
graph
-
data
=
"graphData"
:
tree
-
data
=
"treeData"
:
control
-
active
=
"1"
@
node
-
click
=
"handleNodeClick"
@
layout
-
change
=
"handleLayoutChange"
/>
-->
<
div
class
=
"graph-box"
v
-
if
=
"contentType==2"
>
<
GraphChart
:
nodes
=
"testData.nodes"
:
links
=
"testData.links"
layoutType
=
"force"
/>
<
/div
>
<
/div
>
<
/AnalysisBox
>
<
/div
>
<
/div
>
...
...
@@ -101,9 +97,9 @@ import getBarChart from "./utils/barChart";
import
{
getDecreeIndustry
,
getDecreehylyList
,
getDecreeCompany
}
from
"@/api/decree/influence"
;
import
{
getCnEntityOnChain
,
getChainInfoByDomainId
}
from
"@/api/exportControl"
;
import
{
getSingleSanctionEntitySupplyChain
}
from
"@/api/exportControlV2.0"
;
import
ChartChain
from
"./ChartChain.vue"
;
import
ChartChain
from
"./com/ChartChain.vue"
;
import
AiTips
from
"./com/AiTips.vue"
;
import
GraphChart
from
"@/components/base/GraphChart/index.vue"
;
import
ChartRelation
from
"./ChartRelation.vue"
;
import
defaultIcon2
from
"@/assets/icons/default-icon2.png"
;
import
noticeIcon
from
"./assets/images/notice-icon.png"
;
import
icon422
from
"./assets/images/icon422.png"
;
...
...
@@ -113,191 +109,65 @@ import icon1621 from "./assets/images/icon1621.png";
import
company
from
"./assets/images/company.png"
;
import
companyActive
from
"./assets/images/company-active.png"
;
const
tips
=
"这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。"
// 关系图数据
const
testData
=
{
// 节点数据
nodes
:
[
{
id
:
0
,
name
:
"泰丰先行"
,
symbolSize
:
60
,
symbol
:
`image://${company
}
`
,
x
:
0
,
y
:
0
}
,
{
id
:
1
,
name
:
"国轩高科"
,
symbolSize
:
40
,
symbol
:
`image://${company
}
`
}
,
{
id
:
2
,
name
:
"智方纳米"
,
symbolSize
:
40
,
symbol
:
`image://${company
}
`
}
,
{
id
:
3
,
name
:
"香百科技"
,
symbolSize
:
40
,
symbol
:
`image://${company
}
`
}
,
{
id
:
4
,
name
:
"格林滨"
,
symbolSize
:
40
,
symbol
:
`image://${company
}
`
}
,
{
id
:
5
,
name
:
"江西紫宸"
,
symbolSize
:
40
,
symbol
:
`image://${company
}
`
}
,
{
id
:
6
,
name
:
"紫江企业"
,
symbolSize
:
40
,
symbol
:
`image://${company
}
`
}
,
{
id
:
7
,
name
:
"大而美法案"
,
symbolSize
:
40
,
symbol
:
`image://${company
}
`
}
,
{
id
:
8
,
name
:
"比亚迪"
,
symbolSize
:
40
,
symbol
:
`image://${company
}
`
}
,
],
// 关系数据
links
:
[
{
id
:
0
,
name
:
"泰丰先行"
,
// category: 0,
symbolSize
:
30
,
value
:
8
,
symbol
:
`image://${company
}
`
,
x
:
50
,
y
:
10
}
,
{
id
:
1
,
name
:
"国轩高科"
,
// category: 0,
symbolSize
:
30
,
value
:
9
,
symbol
:
`image://${company
}
`
,
x
:
150
,
y
:
10
}
,
{
id
:
2
,
name
:
"智方纳米"
,
// category: 2,
symbolSize
:
30
,
value
:
7
,
symbol
:
`image://${company
}
`
,
x
:
250
,
y
:
10
}
,
{
id
:
3
,
name
:
"香百科技"
,
// category: 1,
symbolSize
:
30
,
value
:
6
,
symbol
:
`image://${company
}
`
,
x
:
350
,
y
:
10
}
,
{
id
:
4
,
name
:
"格林滨"
,
// category: 2,
symbolSize
:
30
,
value
:
6
,
symbol
:
`image://${company
}
`
,
x
:
450
,
y
:
10
}
,
{
id
:
5
,
name
:
"江西紫宸"
,
// category: 2,
symbolSize
:
30
,
value
:
7
,
symbol
:
`image://${company
}
`
,
x
:
550
,
y
:
10
}
,
{
id
:
6
,
name
:
"紫江企业"
,
// category: 4,
symbolSize
:
30
,
value
:
6
,
symbol
:
`image://${company
}
`
,
x
:
650
,
y
:
10
}
,
{
id
:
7
,
name
:
"大而美法案"
,
// category: 4,
symbolSize
:
50
,
value
:
5
,
symbol
:
`image://${company
}
`
,
x
:
300
,
y
:
200
}
,
{
id
:
8
,
name
:
"比亚迪"
,
// category: 0,
symbolSize
:
30
,
value
:
10
,
symbol
:
`image://${company
}
`
,
x
:
50
,
y
:
400
source
:
1
,
target
:
0
,
label
:
{
show
:
true
,
color
:
"#055FC2"
,
backgroundColor
:
"#E7F3FF"
,
borderWidth
:
0
,
offset
:
[
0
,
15
],
formatter
:
'持股'
}
,
lineStyle
:
{
color
:
'#B9DCFF'
,
type
:
"solid"
}
}
,
{
id
:
9
,
name
:
"铜陵有色"
,
// category: 3,
symbolSize
:
30
,
value
:
8
,
symbol
:
`image://${company
}
`
,
x
:
150
,
y
:
400
source
:
2
,
target
:
0
,
label
:
{
show
:
true
,
color
:
"#055FC2"
,
backgroundColor
:
"#E7F3FF"
,
borderWidth
:
0
,
offset
:
[
0
,
15
],
formatter
:
'持股'
}
,
lineStyle
:
{
color
:
'#B9DCFF'
,
type
:
"solid"
}
}
,
{
id
:
10
,
name
:
"长盛精密"
,
// category: 1,
symbolSize
:
30
,
value
:
7
,
symbol
:
`image://${company
}
`
,
x
:
250
,
y
:
400
source
:
3
,
target
:
0
,
label
:
{
show
:
true
,
color
:
"#055FC2"
,
backgroundColor
:
"#E7F3FF"
,
borderWidth
:
0
,
offset
:
[
0
,
15
],
formatter
:
'合作'
}
,
lineStyle
:
{
color
:
'#B9DCFF'
,
type
:
"solid"
}
}
,
{
id
:
11
,
name
:
"天合光能"
,
// category: 0,
symbolSize
:
30
,
value
:
8
,
symbol
:
`image://${company
}
`
,
x
:
350
,
y
:
400
source
:
4
,
target
:
0
,
label
:
{
show
:
true
,
color
:
"#055FC2"
,
backgroundColor
:
"#E7F3FF"
,
borderWidth
:
0
,
offset
:
[
0
,
15
],
formatter
:
'从属'
}
,
lineStyle
:
{
color
:
'#B9DCFF'
,
type
:
"solid"
}
}
,
{
id
:
12
,
name
:
"昆仑化学"
,
// category: 2,
symbolSize
:
30
,
value
:
6
,
symbol
:
`image://${company
}
`
,
x
:
250
,
y
:
400
source
:
5
,
target
:
0
,
label
:
{
show
:
true
,
color
:
"#055FC2"
,
backgroundColor
:
"#E7F3FF"
,
borderWidth
:
0
,
offset
:
[
0
,
15
],
formatter
:
'合作'
}
,
lineStyle
:
{
color
:
'#B9DCFF'
,
type
:
"solid"
}
}
,
{
id
:
13
,
name
:
"嘉源科技"
,
// category: 1,
symbolSize
:
30
,
value
:
6
,
symbol
:
`image://${company
}
`
,
x
:
450
,
y
:
400
source
:
6
,
target
:
0
,
label
:
{
show
:
true
,
color
:
"#055FC2"
,
backgroundColor
:
"#E7F3FF"
,
borderWidth
:
0
,
offset
:
[
0
,
15
],
formatter
:
'持股'
}
,
lineStyle
:
{
color
:
'#B9DCFF'
,
type
:
"solid"
}
}
,
{
id
:
14
,
name
:
"华阳集团"
,
// category: 4,
symbolSize
:
30
,
value
:
7
,
symbol
:
`image://${company
}
`
,
x
:
550
,
y
:
400
source
:
7
,
target
:
0
,
label
:
{
show
:
true
,
color
:
"#055FC2"
,
backgroundColor
:
"#E7F3FF"
,
borderWidth
:
0
,
offset
:
[
0
,
15
],
formatter
:
'合作'
}
,
lineStyle
:
{
color
:
'#B9DCFF'
,
type
:
"solid"
}
}
,
{
id
:
15
,
name
:
"海辰智能"
,
// category: 1,
symbolSize
:
30
,
value
:
7
,
symbol
:
`image://${company
}
`
,
x
:
650
,
y
:
400
source
:
8
,
target
:
0
,
label
:
{
show
:
true
,
color
:
"#055FC2"
,
backgroundColor
:
"#E7F3FF"
,
borderWidth
:
0
,
offset
:
[
0
,
15
],
formatter
:
'合作'
}
,
lineStyle
:
{
color
:
'#B9DCFF'
,
type
:
"solid"
}
}
,
],
// 关系数据
links
:
[
{
source
:
1
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
2
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'持股'
}
}
,
{
source
:
3
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
4
,
target
:
7
,
lineStyle
:
{
type
:
'dashed'
,
color
:
'#d32f2f'
}
,
label
:
{
show
:
true
,
formatter
:
'从属'
}
}
,
{
source
:
5
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
6
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'持股'
}
}
,
{
source
:
0
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'持股'
}
}
,
{
source
:
8
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
9
,
target
:
7
,
lineStyle
:
{
type
:
'dashed'
,
color
:
'#d32f2f'
}
,
label
:
{
show
:
true
,
formatter
:
'从属'
}
}
,
{
source
:
10
,
target
:
7
,
lineStyle
:
{
type
:
'dashed'
,
color
:
'#d32f2f'
}
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
11
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
12
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
13
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
14
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
}
}
,
{
source
:
15
,
target
:
7
,
label
:
{
show
:
true
,
formatter
:
'合作'
,
color
:
'red'
,
borderColor
:
'red'
}
}
,
],
}
;
// 受影响实体
...
...
@@ -374,7 +244,7 @@ const handleGetHylyList = async () => {
}
;
// 产业链/实体关系
const
contentType
=
ref
(
2
);
const
contentType
=
ref
(
1
);
const
headerContentType
=
(
type
)
=>
{
contentType
.
value
=
type
;
}
;
...
...
@@ -693,7 +563,7 @@ onMounted(() => {
align
-
items
:
flex
-
end
;
width
:
100
%
;
height
:
100
%
;
padding
:
0
16
px
;
padding
:
0
20
px
;
.
title
-
left
{
display
:
flex
;
border
:
1
px
solid
rgb
(
5
,
95
,
194
);
...
...
@@ -734,6 +604,14 @@ onMounted(() => {
.
box2
-
main
{
width
:
100
%
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
padding
:
16
px
20
px
;
.
graph
-
box
{
height
:
20
px
;
flex
:
auto
;
margin
-
top
:
16
px
;
}
}
}
}
...
...
src/views/decree/decreeLayout/overview/background/index.vue
浏览文件 @
5466ba6a
...
...
@@ -16,7 +16,7 @@
<div
class=
"box1-main"
>
<div
class=
"box1-item"
v-for=
"(item, index) in backgroundList"
:key=
"index"
>
<div
class=
"id"
>
{{ index + 1 }}
</div>
<div
class=
"title"
>
{{ item.content }}
</div>
<div
class=
"title
text-align-justify
"
>
{{ item.content }}
</div>
<div
class=
"open"
>
<img
src=
"./assets/images/open-icon.png"
alt=
""
/>
</div>
...
...
@@ -37,14 +37,17 @@
<div
class=
"box2-main"
>
<div
class=
"custom-collapse"
>
<el-collapse
v-model=
"dependActive"
>
<el-collapse-item
v-for=
"(item, index) in dependList"
:key=
"item.billId"
title=
"Consistency"
:name=
"item.billId"
>
<el-collapse-item
v-for=
"(item, index) in dependList"
:key=
"item.billId"
:name=
"item.billId"
>
<
template
#
icon
>
<el-icon><ArrowDownBold
/></el-icon>
<el-icon
v-if=
"dependActive.includes(item.billId)"
><ArrowDownBold
/></el-icon>
<el-icon
v-else
><ArrowUpBold
/></el-icon>
</
template
>
<
template
#
title
>
<div
class=
"custom-collapse-title"
>
<div
class=
"custom-collapse-index"
>
{{
index
+
1
}}
</div>
<div
class=
"custom-collapse-name one-line-ellipsis"
>
{{
item
.
title
}}
</div>
<div
class=
"custom-collapse-name one-line-ellipsis"
>
<span
class=
"text-click-hover"
@
click
.
stop=
"handleClickDecree(item)"
>
{{
item
.
title
}}
</span>
</div>
</div>
</
template
>
<div
class=
"custom-collapse-content"
>
...
...
@@ -192,6 +195,20 @@ const handleGetLaws = async () => {
console
.
error
(
"获取法律依据数据失败"
,
error
);
}
};
// 跳转科技法案详情页
const
handleClickDecree
=
decree
=>
{
window
.
sessionStorage
.
setItem
(
"billId"
,
decree
.
billId
);
window
.
sessionStorage
.
setItem
(
"curTabName"
,
decree
.
title
);
const
route
=
router
.
resolve
({
path
:
"/billLayout"
,
query
:
{
billId
:
decree
.
billId
}
});
console
.
log
(
route
);
window
.
open
(
route
.
href
,
"_blank"
);
};
onMounted
(()
=>
{
handleGetBackground
();
...
...
@@ -261,29 +278,31 @@ onMounted(() => {
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
align-items
:
center
;
padding
:
1
2
px
0
;
padding
:
1
8
px
0
;
.id
{
margin-right
:
16px
;
margin-left
:
15px
;
width
:
24px
;
height
:
24px
;
text-align
:
center
;
line-height
:
30px
;
border-radius
:
12px
;
background
:
#e7f3ff
;
color
:
#0a57a6
;
font-size
:
15px
;
line-height
:
24px
;
border-radius
:
50%
;
}
.title
{
width
:
914
px
;
line-height
:
24px
;
margin-left
:
13
px
;
width
:
20
px
;
flex
:
auto
;
line-height
:
30
px
;
}
.open
{
width
:
16px
;
height
:
16px
;
margin
-left
:
16px
;
margin
:
0
16px
;
img
{
width
:
100%
;
...
...
@@ -332,7 +351,8 @@ onMounted(() => {
.custom-collapse-title
{
position
:
relative
;
.custom-collapse-index
{
font-size
:
15px
;
font-family
:
Microsoft
YaHei
;
font-size
:
var
(
--
font-size-base
);
position
:
absolute
;
top
:
12px
;
left
:
-32px
;
...
...
src/views/decree/decreeLayout/overview/introduction/index.vue
浏览文件 @
5466ba6a
...
...
@@ -37,9 +37,6 @@
<div
class=
"item"
>
<div
class=
"item-left"
>
{{ "相关领域:" }}
</div>
<div
class=
"item-right tag-box"
>
<!-- <div class="tag" v-for="(area, index) in basicInfo.areaList" :key="index">
{{ area.industryName }}
</div> -->
<AreaTag
v-for=
"(area, index) in basicInfo.areaList"
:key=
"index"
:tagName=
"area.industryName"
></AreaTag>
</div>
</div>
...
...
@@ -120,7 +117,7 @@
<img
:src=
"item.avatar ? item.avatar : DefaultIcon1"
alt=
""
/>
</div>
<div
class=
"box3-top-bottom-item-right"
>
<div
class=
"name"
@
click=
"handleClickUser(item)"
>
{{ item.name }}
</div>
<div
class=
"name
text-click-hover one-line-ellipsis
"
@
click=
"handleClickUser(item)"
>
{{ item.name }}
</div>
<div
class=
"position"
>
{{ item.job }}
</div>
</div>
</div>
...
...
@@ -336,22 +333,20 @@ onMounted(() => {
.box1-main
{
display
:
flex
;
padding
:
0
24px
;
.box1-main-left
{
width
:
395px
;
height
:
332px
;
margin-left
:
24px
;
img
{
width
:
100%
;
// height: 100%;
}
}
.box1-main-left-img-mock
{
width
:
240px
;
height
:
332px
;
margin-left
:
24px
;
background-color
:
#0b1932
;
display
:
flex
;
align-items
:
center
;
...
...
@@ -378,7 +373,8 @@ onMounted(() => {
}
.box1-main-right
{
width
:
590px
;
width
:
20px
;
flex
:
auto
;
margin-left
:
20px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
...
...
@@ -387,24 +383,22 @@ onMounted(() => {
line-height
:
24px
;
.item
{
height
:
30px
;
display
:
flex
;
margin-bottom
:
17
px
;
margin-bottom
:
22
px
;
.item-left
{
width
:
100px
;
}
.item-right
{
width
:
470px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
width
:
20px
;
flex
:
auto
;
}
.tag-box
{
display
:
flex
;
gap
:
8px
;
flex-wrap
:
wrap
;
.tag
{
height
:
24px
;
...
...
@@ -423,6 +417,9 @@ onMounted(() => {
}
.text
{
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
font-weight
:
normal
!
important
;
}
}
...
...
@@ -697,10 +694,6 @@ onMounted(() => {
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
cursor
:
pointer
;
}
.position
{
...
...
src/views/decree/decreeLayout/overview/measures/index.vue
浏览文件 @
5466ba6a
<
template
>
<div
class=
"introduction-wrap"
>
<div
class=
"left"
>
<div
class=
"
page-
left"
>
<div
class=
"box1"
>
<AnalysisBox
title=
"主要指令"
:showAllBtn=
"false"
>
<div
class=
"analysis-box"
>
...
...
@@ -21,7 +21,7 @@
<el-empty
v-if=
"!contentList?.length"
style=
"padding: 60px 0;"
description=
"暂无数据"
:image-size=
"100"
/>
<div
v-for=
"(section, index) in contentList"
:key=
"index"
class=
"section"
>
<div
class=
"section-header"
>
<div
class=
"section-title
"
>
(
{{
simpleNumToChinese
(
index
+
1
)
}}
)
{{
section
.
content
}}
</div>
<div
class=
"section-title
text-align-justify"
v-html=
"section.content"
>
</div>
<div
class=
"section-icon"
>
<img
src=
"./assets/images/open-icon.png"
alt=
""
/>
</div>
...
...
@@ -30,19 +30,19 @@
<div
class=
"numbered-list"
>
<div
v-for=
"(item, itemIndex) in section.slaver"
:key=
"itemIndex"
class=
"list-item"
>
<div
class=
"list-item-dot"
>
{{
itemIndex
+
1
}}
.
</div>
<div
class=
"list-item-word"
>
{{
item
.
content
}}
</div>
<div
class=
"list-item-word"
v-html=
"item.content"
>
</div>
<!-- 渲染二级列表 -->
<div
v-if=
"item.slaver"
class=
"sub-list"
>
<div
v-for=
"(subItem, subIndex) in item.slaver"
:key=
"subIndex"
class=
"sub-item"
>
<div
class=
"sub-item-dot"
>
(
{{
subIndex
+
1
}}
)
</div>
<div
class=
"sub-item-word"
>
{{
subItem
.
content
}}
</div>
<div
class=
"sub-item-word"
v-html=
"subItem.content"
>
</div>
<!-- 渲染三级列表 -->
<div
v-if=
"subItem.slaver"
class=
"sub-sub-list"
>
<div
v-for=
"(subSubItem, subSubIndex) in subItem.slaver"
:key=
"subSubIndex"
class=
"sub-sub-item"
>
<div
class=
"sub-sub-item-dot"
>
{{
ALPHABET
[
subSubIndex
%
26
]
}}
.
</div>
<div
class=
"sub-sub-item-word"
>
{{
subSubItem
.
content
}}
</div>
<div
class=
"sub-sub-item-word"
v-html=
"subItem.content"
>
</div>
</div>
</div>
</div>
...
...
@@ -55,17 +55,22 @@
</AnalysisBox>
</div>
</div>
<div
class=
"right"
>
<div
class=
"
page-
right"
>
<div
class=
"box3"
>
<AnalysisBox
title=
"
发布
机构"
:showAllBtn=
"false"
>
<AnalysisBox
title=
"
执行
机构"
:showAllBtn=
"false"
>
<div
class=
"box3-top"
>
<div
class=
"box3-top-top"
@
click=
"handleToInstitution(box3TopTopData)"
>
<div
class=
"organization-list"
>
<div
class=
"organization-item"
v-for=
"item in organizationInfo.list"
:key=
"item.id"
>
<ActionButton
@
click=
"handleOrganization(item)"
:name=
"item.obb"
:type=
"item.id==organizationInfo.node.id?'active':'normal'"
/>
</div>
</div>
<div
class=
"box3-top-top"
@
click=
"handleToInstitution()"
>
<div
class=
"left"
>
<img
:src=
"
box3TopTopData.logo ? box3TopTopData.logo :
DefaultIcon2"
alt=
""
/>
<img
:src=
"
organizationInfo.node.logo ||
DefaultIcon2"
alt=
""
/>
</div>
<div
class=
"right"
>
<div
class=
"name"
>
{{
box3TopTopData
.
name
+
" >"
}}
</div>
<div
class=
"ename"
>
{{
box3TopTopData
.
eN
ame
}}
</div>
<div
class=
"name"
>
{{
organizationInfo
.
node
.
name
+
" >"
}}
</div>
<div
class=
"ename"
>
{{
organizationInfo
.
node
.
en
ame
}}
</div>
</div>
</div>
<div
class=
"box3-top-bottom"
>
...
...
@@ -76,17 +81,21 @@
<div
class=
"text"
>
{{
"关键人物"
}}
</div>
</div>
<div
class=
"box3-top-bottom-main"
>
<div
class=
"box3-top-bottom-item"
v-for=
"(item, index) in
box3TopBottomData
"
:key=
"index"
>
<div
class=
"box3-top-bottom-item"
v-for=
"(item, index) in
organizationInfo.node.leaders
"
:key=
"index"
>
<div
class=
"box3-top-bottom-item-left"
>
<img
:src=
"item.avatar
? item.avatar :
DefaultIcon1"
alt=
""
/>
<img
:src=
"item.avatar
||
DefaultIcon1"
alt=
""
/>
</div>
<div
class=
"box3-top-bottom-item-right"
>
<div
class=
"name"
@
click=
"handleClickUser(item)"
>
{{
item
.
name
}}
</div>
<div
class=
"name
one-line-ellipsis text-click-hover
"
@
click=
"handleClickUser(item)"
>
{{
item
.
name
}}
</div>
<div
class=
"position"
>
{{
item
.
job
}}
</div>
</div>
</div>
</div>
</div>
<div
class=
"organization-button"
>
<div
class=
"button-text"
>
查看政令执行情况
</div>
<el-icon
size=
"16"
><Right
/></el-icon>
</div>
</div>
</AnalysisBox>
</div>
...
...
@@ -109,14 +118,14 @@
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
}
from
"vue"
;
import
{
ref
,
onMounted
,
reactive
}
from
"vue"
;
import
{
useRoute
}
from
"vue-router"
;
import
router
from
"@/router"
;
import
{
Search
}
from
'@element-plus/icons-vue'
import
{
getDecree
Issue
Organization
}
from
"@/api/decree/introduction"
;
import
{
getDecreeOrganization
}
from
"@/api/decree/introduction"
;
import
{
getDecreeRelatedEntity
,
getDecreeMainContent
}
from
"@/api/decree/background"
;
import
{
getDecreehylyList
}
from
"@/api/decree/home"
;
import
ActionButton
from
'@/components/base/ActionButton/index.vue'
import
DefaultIcon1
from
"@/assets/icons/default-icon1.png"
;
import
DefaultIcon2
from
"@/assets/icons/default-icon2.png"
;
import
defaultCom
from
"@/views/coopRestriction/assets/images/default-icon2.png"
...
...
@@ -211,14 +220,16 @@ const contentList = ref([
const
ALPHABET
=
[
"a"
,
"b"
,
"c"
,
"d"
,
"e"
,
"f"
,
"g"
,
"h"
,
"i"
,
"j"
,
"k"
,
"l"
,
"m"
,
"n"
,
"o"
,
"p"
,
"q"
,
"r"
,
"s"
,
"t"
,
"u"
,
"v"
,
"w"
,
"x"
,
"y"
,
"z"
];
const
onMainContentData
=
async
()
=>
{
try
{
const
res
=
await
getDecreeMainContent
({
id
:
route
.
query
.
id
,
keyword
:
commandWord
.
value
,
domainId
:
areaType
.
value
});
const
keyword
=
commandWord
.
value
;
const
res
=
await
getDecreeMainContent
({
id
:
route
.
query
.
id
,
keyword
,
domainId
:
areaType
.
value
});
console
.
log
(
"主要指令"
,
res
);
if
(
res
&&
res
.
code
===
200
)
{
contentList
.
value
=
res
.
data
;
contentList
.
value
=
res
.
data
||
[];
contentList
.
value
.
forEach
((
item
,
index
)
=>
{
item
.
content
=
`(
${
simpleNumToChinese
(
index
+
1
)}
)
${
item
.
content
}
`
})
if
(
keyword
)
{
let
word
=
keyword
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
"
\\
$&"
);
contentList
.
value
.
forEach
(
item
=>
{
onHighlight
(
word
,
item
)})
}
}
else
{
contentList
.
value
=
[]
}
...
...
@@ -227,12 +238,19 @@ const onMainContentData = async () => {
console
.
error
(
"获取主要指令数据失败:"
,
error
);
}
};
// 搜索高亮效果
const
onHighlight
=
(
word
,
row
)
=>
{
row
.
content
=
String
(
row
.
content
).
replace
(
new
RegExp
(
word
,
"gi"
),
(
match
)
=>
{
return
`<span class="highlight">
${
match
}
</span>`
;
});
if
(
row
.
slaver
?.
length
)
{
row
.
slaver
.
forEach
(
item
=>
{
onHighlight
(
word
,
item
)
})
}
}
// 数字转中文(支持 0-99 整数)
const
simpleNumToChinese
=
(
num
)
=>
{
// 1. 基础校验:只处理 0-99 的整数
if
(
!
Number
.
isInteger
(
num
)
||
num
<
0
||
num
>
99
)
{
return
'仅支持 0-99 之间的整数'
;
}
if
(
!
Number
.
isInteger
(
num
)
||
num
<
0
||
num
>
99
)
return
'100'
;
// 2. 定义基础字符
const
singleChars
=
[
'零'
,
'一'
,
'二'
,
'三'
,
'四'
,
'五'
,
'六'
,
'七'
,
'八'
,
'九'
];
const
tenChar
=
'十'
;
...
...
@@ -271,20 +289,34 @@ const onRelatedEntityData = async () => {
}
};
// 发布机构
const
box3TopTopData
=
ref
({
id
:
""
,
logo
:
""
,
name
:
""
,
eName
:
""
});
const
box3TopBottomData
=
ref
([]);
// 跳转行政机构主页
const
handleToInstitution
=
item
=>
{
// 执行机构
const
organizationInfo
=
reactive
({
list
:
[],
node
:
{
id
:
""
,
obb
:
""
,
logo
:
""
,
name
:
""
,
ename
:
""
,
leaders
:
[]},
})
const
handleGetOrgnization
=
async
()
=>
{
try
{
const
res
=
await
getDecreeOrganization
({
id
:
route
.
query
.
id
});
console
.
log
(
"执行机构"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
?.
length
)
{
organizationInfo
.
list
=
res
.
data
;
organizationInfo
.
node
=
res
.
data
[
0
];
}
}
catch
(
error
)
{
organizationInfo
.
node
=
{
id
:
""
,
obb
:
""
,
logo
:
""
,
name
:
""
,
ename
:
""
,
leaders
:
[]};
console
.
error
(
"获取执行机构数据失败"
,
error
);
}
};
// 切换执行机构
const
handleOrganization
=
(
node
)
=>
{
organizationInfo
.
node
=
node
;
};
// 跳转机构主页
const
handleToInstitution
=
()
=>
{
const
curRoute
=
router
.
resolve
({
path
:
"/institution"
,
query
:
{
id
:
item
.
id
id
:
organizationInfo
.
node
.
id
}
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
...
...
@@ -300,21 +332,6 @@ const handleClickUser = item => {
});
window
.
open
(
routeData
.
href
,
"_blank"
);
};
const
handleGetOrgnization
=
async
()
=>
{
try
{
const
res
=
await
getDecreeIssueOrganization
({
id
:
route
.
query
.
id
});
console
.
log
(
"发布机构"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
let
{
id
,
image
,
name
,
ename
}
=
res
.
data
Object
.
assign
(
box3TopTopData
.
value
,
{
id
,
logo
:
image
,
name
,
eName
:
ename
});
box3TopBottomData
.
value
=
res
.
data
.
personList
;
}
}
catch
(
error
)
{
box3TopTopData
.
value
=
{
id
:
""
,
logo
:
""
,
name
:
""
,
eName
:
""
};
box3TopBottomData
.
value
=
[];
console
.
error
(
"执行机构error"
,
error
);
}
};
onMounted
(()
=>
{
handleGetAreaList
();
...
...
@@ -325,13 +342,19 @@ onMounted(() => {
</
script
>
<
style
lang=
"scss"
scoped
>
.analysis-content
{
:deep
(
span
.highlight
)
{
background-color
:
#ffff00
;
}
}
.introduction-wrap
{
display
:
flex
;
width
:
1600px
;
padding
:
16px
0
;
gap
:
16px
;
.left
{
.
page-
left
{
width
:
20px
;
flex
:
auto
;
...
...
@@ -387,7 +410,6 @@ onMounted(() => {
.section-title
{
font-size
:
18px
;
line-height
:
30px
;
font-weight
:
600
;
letter-spacing
:
1px
;
width
:
20px
;
flex
:
auto
;
...
...
@@ -457,7 +479,7 @@ onMounted(() => {
}
}
.right
{
.
page-
right
{
width
:
520px
;
display
:
flex
;
flex-direction
:
column
;
...
...
@@ -466,10 +488,34 @@ onMounted(() => {
.box3
{
.box3-top
{
margin-top
:
2px
;
padding
:
0
22px
20px
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
.organization-list
{
display
:
flex
;
flex-wrap
:
wrap
;
margin-bottom
:
16px
;
gap
:
8px
16px
;
}
.organization-button
{
height
:
36px
;
background-color
:
var
(
--
color-primary-100
);
color
:
var
(
--
bg-white-100
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border-radius
:
6px
;
cursor
:
pointer
;
.button-text
{
margin-right
:
8px
;
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
line-height
:
16px
;
}
}
.box3-top-top
{
width
:
473px
;
height
:
88px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
...
...
@@ -477,42 +523,13 @@ onMounted(() => {
background
:
rgba
(
247
,
248
,
249
,
1
);
display
:
flex
;
align-items
:
center
;
margin
:
0
auto
;
position
:
relative
;
cursor
:
pointer
;
.more
{
position
:
absolute
;
right
:
17px
;
top
:
17px
;
display
:
flex
;
gap
:
3px
;
.text
{
height
:
16px
;
color
:
rgba
(
5
,
95
,
194
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
16px
;
}
.icon
{
width
:
16px
;
height
:
16px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
.left
{
width
:
64px
;
height
:
64px
;
margin-left
:
17px
;
font-size
:
0px
;
img
{
width
:
100%
;
height
:
100%
;
...
...
@@ -520,7 +537,8 @@ onMounted(() => {
}
.right
{
width
:
370px
;
width
:
20px
;
flex
:
auto
;
margin-left
:
15px
;
.name
{
...
...
@@ -545,9 +563,7 @@ onMounted(() => {
}
.box3-top-bottom
{
width
:
473px
;
height
:
193px
;
margin
:
0
auto
;
.box3-top-bottom-header
{
height
:
40px
;
...
...
@@ -624,10 +640,6 @@ onMounted(() => {
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
cursor
:
pointer
;
}
.position
{
...
...
src/views/decree/decreeOriginal/assets/icons/download.png
deleted
100644 → 0
浏览文件 @
d5eb651a
350 Bytes
src/views/decree/decreeOriginal/assets/icons/search.png
deleted
100644 → 0
浏览文件 @
d5eb651a
399 Bytes
src/views/decree/decreeOriginal/index.vue
浏览文件 @
5466ba6a
...
...
@@ -135,7 +135,6 @@ onMounted(() => {
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
.header-main
{
padding
:
17px
0
;
width
:
100%
;
...
...
vite.config.js
浏览文件 @
5466ba6a
...
...
@@ -53,7 +53,7 @@ export default defineConfig({
'/api'
:
{
target
:
'http://8.140.26.4:9085/'
,
// target: 'http://192.168.0.
5
:28080/',
// target: 'http://192.168.0.
6
:28080/',
changeOrigin
:
true
,
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api/
,
''
)
},
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论