Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
67bc4f74
提交
67bc4f74
authored
3月 23, 2026
作者:
张伊明
浏览文件
操作
浏览文件
下载
差异文件
合并分支 'zym-dev' 到 'master'
Zym dev 查看合并请求
!192
上级
f8cfb724
0892a0ac
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
10 个修改的文件
包含
446 行增加
和
79 行删除
+446
-79
index.js
src/api/aiAnalysis/index.js
+142
-9
bill.js
src/api/bill.js
+16
-1
billHome.js
src/api/bill/billHome.js
+9
-2
index.ts
src/api/intelligent/index.ts
+6
-2
index.vue
src/views/bill/billHome/index.vue
+0
-0
multiLineChart.js
src/views/bill/billHome/utils/multiLineChart.js
+29
-4
piechart.js
src/views/bill/billHome/utils/piechart.js
+12
-2
index.vue
src/views/bill/deepDig/processOverview/index.vue
+0
-0
index.vue
src/views/bill/template/index.vue
+161
-6
index.vue
src/views/bill/versionCompare/index.vue
+71
-53
没有找到文件。
src/api/aiAnalysis/index.js
浏览文件 @
67bc4f74
import
request
from
"@/api/request.js"
;
import
{
getToken
}
from
"@/api/request.js"
;
const
CHART_INTERPRETATION_URL
=
"/aiAnalysis/chart_interpretation"
;
const
API_KEY
=
"aircasKEY19491001"
;
/**
* 从模型返回的 markdown code fence 中提取数组 JSON
* 例如:```json\n[ ... ]\n```
* @param {string} buffer
* @returns {unknown[]}
*/
function
parseChartInterpretationArray
(
buffer
)
{
// 可能带有前后空白、换行、以及 ```/```json 包裹
let
cleaned
=
String
(
buffer
||
""
).
trim
();
// 移除开头 fence
cleaned
=
cleaned
.
replace
(
/^```
(?:
json
)?\s
*/i
,
""
);
// 移除结尾 fence(允许末尾有换行)
cleaned
=
cleaned
.
replace
(
/```
[\s\r\n]
*$/i
,
""
).
trim
();
// 优先直接解析
try
{
const
parsed
=
JSON
.
parse
(
cleaned
);
if
(
Array
.
isArray
(
parsed
))
return
parsed
;
}
catch
(
_
)
{
}
// 兜底:从 first '[' 到 last ']' 截取
const
start
=
cleaned
.
indexOf
(
"["
);
const
end
=
cleaned
.
lastIndexOf
(
"]"
);
if
(
start
!==
-
1
&&
end
!==
-
1
&&
end
>
start
)
{
const
arrStr
=
cleaned
.
slice
(
start
,
end
+
1
);
const
parsed
=
JSON
.
parse
(
arrStr
);
if
(
Array
.
isArray
(
parsed
))
return
parsed
;
}
throw
new
Error
(
"无法解析图表解读 JSON 数组"
);
}
// 图表解读(流式)
/**
/**
* @param {text}
* 图表解读(SSE 流式)
* @param {object} data - 请求体
* @param {object} [options] - 可选配置
* @param {function(string): void} [options.onChunk] - 每收到一条 SSE 消息时回调,参数为当前 chunk 的 text
* @returns {Promise<{data: unknown[]}>}
*/
*/
export
function
getChartAnalysis
(
data
)
{
export
function
getChartAnalysis
(
data
,
options
=
{})
{
return
request
({
const
{
onChunk
}
=
options
;
method
:
'POST'
,
return
new
Promise
((
resolve
,
reject
)
=>
{
url
:
`/aiAnalysis/chart_interpretation`
,
let
buffer
=
""
;
data
,
let
settled
=
false
;
})
const
abortController
=
new
AbortController
();
const
safeResolve
=
value
=>
{
if
(
settled
)
return
;
settled
=
true
;
resolve
(
value
);
};
const
safeReject
=
err
=>
{
if
(
settled
)
return
;
settled
=
true
;
reject
(
err
);
};
(
async
()
=>
{
try
{
const
{
fetchEventSource
}
=
await
import
(
"@microsoft/fetch-event-source"
);
await
fetchEventSource
(
CHART_INTERPRETATION_URL
,
{
method
:
"POST"
,
headers
:
{
"Content-Type"
:
"application/json"
,
Accept
:
"text/event-stream"
,
"Cache-Control"
:
"no-cache"
,
"X-API-Key"
:
API_KEY
,
// 后端同项目其它接口使用 token 字段名(axios 拦截器里就是这样注入的)
token
:
getToken
()
},
body
:
JSON
.
stringify
(
data
),
signal
:
abortController
.
signal
,
openWhenHidden
:
true
,
retryDelay
:
1000
,
maxRetries
:
2
,
onopen
:
response
=>
{
const
contentType
=
response
.
headers
.
get
(
"content-type"
)
||
""
;
if
(
!
contentType
.
includes
(
"text/event-stream"
))
{
throw
new
Error
(
"SSE连接格式异常:content-type 不是 text/event-stream"
);
}
},
onmessage
:
event
=>
{
const
raw
=
(
event
?.
data
||
""
).
trim
();
if
(
!
raw
)
return
;
if
(
raw
===
"[DONE]"
)
return
;
let
chunk
=
""
;
// 后端返回格式示例:{"text":"```"} / {"text":"json\n[\n"}
try
{
const
msg
=
JSON
.
parse
(
raw
);
if
(
msg
&&
typeof
msg
===
"object"
&&
"text"
in
msg
)
{
chunk
=
String
(
msg
.
text
??
""
);
buffer
+=
chunk
;
}
else
{
chunk
=
raw
;
buffer
+=
raw
;
}
}
catch
(
e
)
{
chunk
=
raw
;
buffer
+=
raw
;
}
// 每收到一条消息即回调,用于流式渲染
if
(
chunk
&&
typeof
onChunk
===
"function"
)
{
onChunk
(
chunk
);
}
// 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接
const
trimmed
=
buffer
.
trim
();
if
(
trimmed
.
endsWith
(
"```"
))
{
try
{
const
arr
=
parseChartInterpretationArray
(
trimmed
);
safeResolve
({
data
:
arr
});
abortController
.
abort
();
}
catch
(
_
)
{
}
}
},
onclose
:
()
=>
{
try
{
const
arr
=
parseChartInterpretationArray
(
buffer
);
safeResolve
({
data
:
arr
});
}
catch
(
e
)
{
safeReject
(
e
);
}
},
onerror
:
error
=>
{
if
(
error
?.
name
===
"AbortError"
)
return
true
;
safeReject
(
error
);
return
true
;
}
});
}
catch
(
error
)
{
safeReject
(
error
);
}
})();
});
}
}
\ No newline at end of file
src/api/bill.js
浏览文件 @
67bc4f74
...
@@ -212,10 +212,25 @@ export function getBillTermsCompare(params) {
...
@@ -212,10 +212,25 @@ export function getBillTermsCompare(params) {
* @param {billId,content,currentPage,currentVersion,isCn,originalVersion,pageSize,status}
* @param {billId,content,currentPage,currentVersion,isCn,originalVersion,pageSize,status}
* @header token
* @header token
*/
*/
export
function
getBillVersionCompare
(
params
)
{
export
function
getBillVersionCompare
(
params
,
config
=
{}
)
{
return
request
({
return
request
({
method
:
"GET"
,
method
:
"GET"
,
url
:
"/api/billInfoBean/versionCompare"
,
url
:
"/api/billInfoBean/versionCompare"
,
params
,
params
,
signal
:
config
.
signal
,
});
}
// 版本对比-根据筛选条件获取变更统计
/**
* @param {billId,content,currentVersion,isCn,originalVersion}
* @header token
*/
export
function
getBillVersionCompareStatistics
(
params
,
config
=
{})
{
return
request
({
method
:
"GET"
,
url
:
"/api/billInfoBean/versionCompareStatistics"
,
params
,
signal
:
config
.
signal
,
});
});
}
}
src/api/bill/billHome.js
浏览文件 @
67bc4f74
...
@@ -2,17 +2,24 @@ import request from "@/api/request.js";
...
@@ -2,17 +2,24 @@ import request from "@/api/request.js";
// 涉华法案领域分布
// 涉华法案领域分布
/**
/**
* @param {year}
* @param {Object} params
* @param {string} params.year - 年份
* @param {string} [params.status] - 状态:提出法案/通过法案
*/
*/
export
function
getBillIndustry
(
params
)
{
export
function
getBillIndustry
(
params
)
{
return
request
({
return
request
({
method
:
'GET'
,
method
:
'GET'
,
url
:
`/api/BillOverview/billIndustry/
${
params
.
year
}
`
,
url
:
`/api/BillOverview/billIndustry/
${
params
.
year
}
`
,
params
,
params
:
{
status
:
params
.
status
}
})
})
}
}
// 涉华法案统计
// 涉华法案统计
/**
* @param {Object} params
* @param {string} [params.dateDesc] - 时间范围:近一年/近两年/近三年/全部
* @param {string} [params.industryId] - 行业领域 ID
*/
export
function
getBillCount
(
params
)
{
export
function
getBillCount
(
params
)
{
return
request
({
return
request
({
method
:
'GET'
,
method
:
'GET'
,
...
...
src/api/intelligent/index.ts
浏览文件 @
67bc4f74
...
@@ -10,12 +10,16 @@ export class TextEntity {
...
@@ -10,12 +10,16 @@ export class TextEntity {
type
:
string
;
type
:
string
;
}
}
// 智能化:提取文本实体
// 智能化:提取文本实体
export
function
extractTextEntity
(
text
:
string
):
Promise
<
IntelligentResultWrapper
<
TextEntity
[]
>>
{
export
function
extractTextEntity
(
text
:
string
,
config
:
{
signal
?:
AbortSignal
}
=
{}
):
Promise
<
IntelligentResultWrapper
<
TextEntity
[]
>>
{
return
request
({
return
request
({
url
:
`
${
INTELLECTUAL_API
}
/extract-entity`
,
url
:
`
${
INTELLECTUAL_API
}
/extract-entity`
,
method
:
"POST"
,
method
:
"POST"
,
data
:
{
data
:
{
text
text
}
},
signal
:
config
.
signal
});
});
}
}
src/views/bill/billHome/index.vue
浏览文件 @
67bc4f74
差异被折叠。
点击展开。
src/views/bill/billHome/utils/multiLineChart.js
浏览文件 @
67bc4f74
...
@@ -65,9 +65,22 @@ const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => {
...
@@ -65,9 +65,22 @@ const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => {
{
{
type
:
'value'
,
type
:
'value'
,
position
:
'left'
,
position
:
'left'
,
// 纵轴单位只在纵轴上方显示一次(通过 axis.name),避免每个刻度重复显示
name
:
'项'
,
nameLocation
:
'end'
,
nameGap
:
12
,
nameTextStyle
:
{
color
:
'#666'
,
fontSize
:
14
,
fontWeight
:
400
,
// 给单位一点点下移空间,使其更贴近顶部刻度数字的视觉基线
padding
:
[
0
,
0
,
6
,
-
20
]
},
axisLabel
:
{
axisLabel
:
{
formatter
:
'{value}项'
,
formatter
:
'{value}'
,
color
:
'#666'
color
:
'#666'
,
fontSize
:
14
,
fontWeight
:
400
},
},
splitLine
:
{
splitLine
:
{
show
:
true
,
show
:
true
,
...
@@ -83,9 +96,21 @@ const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => {
...
@@ -83,9 +96,21 @@ const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => {
min
:
0
,
min
:
0
,
max
:
100
,
max
:
100
,
interval
:
20
,
interval
:
20
,
// 通过率单位仅展示一次
name
:
'%'
,
nameLocation
:
'end'
,
nameGap
:
12
,
nameTextStyle
:
{
color
:
'#666'
,
fontSize
:
14
,
fontWeight
:
400
,
padding
:
[
0
,
0
,
6
,
20
]
},
axisLabel
:
{
axisLabel
:
{
formatter
:
'{value}%'
,
formatter
:
'{value}'
,
color
:
'#666'
color
:
'#666'
,
fontSize
:
14
,
fontWeight
:
400
},
},
splitLine
:
{
splitLine
:
{
show
:
true
,
show
:
true
,
...
...
src/views/bill/billHome/utils/piechart.js
浏览文件 @
67bc4f74
...
@@ -6,9 +6,18 @@ const truncateLabel = (value, maxLen = 6) => {
...
@@ -6,9 +6,18 @@ const truncateLabel = (value, maxLen = 6) => {
return
`
${
chars
.
slice
(
0
,
maxLen
).
join
(
''
)}
...`
return
`
${
chars
.
slice
(
0
,
maxLen
).
join
(
''
)}
...`
}
}
const
getPieChart
=
(
data
,
colorList
)
=>
{
const
getPieChart
=
(
data
,
colorList
,
options
=
{})
=>
{
const
showCount
=
options
.
showCount
!==
false
let
option
=
{
let
option
=
{
// color: colorList,
// color: colorList,
tooltip
:
showCount
?
undefined
:
{
formatter
:
params
=>
{
const
percent
=
typeof
params
.
percent
===
'number'
?
params
.
percent
:
0
return
`
${
params
.
name
}
:
${
percent
}
%`
}
},
series
:
[
series
:
[
{
{
type
:
'pie'
,
type
:
'pie'
,
...
@@ -26,7 +35,8 @@ const getPieChart = (data, colorList) => {
...
@@ -26,7 +35,8 @@ const getPieChart = (data, colorList) => {
const
name
=
truncateLabel
(
params
?.
name
,
6
)
const
name
=
truncateLabel
(
params
?.
name
,
6
)
const
value
=
params
?.
value
??
0
const
value
=
params
?.
value
??
0
const
percent
=
typeof
params
?.
percent
===
'number'
?
params
.
percent
:
0
const
percent
=
typeof
params
?.
percent
===
'number'
?
params
.
percent
:
0
return
`{name|
${
name
}
}\n{time|
${
value
}
条
${
percent
}
%}`
const
labelText
=
showCount
?
`
${
value
}
条
${
percent
}
%`
:
`
${
percent
}
%`
return
`{name|
${
name
}
}\n{time|
${
labelText
}
}`
},
},
minMargin
:
5
,
minMargin
:
5
,
edgeDistance
:
10
,
edgeDistance
:
10
,
...
...
src/views/bill/deepDig/processOverview/index.vue
浏览文件 @
67bc4f74
差异被折叠。
点击展开。
src/views/bill/template/index.vue
浏览文件 @
67bc4f74
...
@@ -43,7 +43,17 @@
...
@@ -43,7 +43,17 @@
<div
class=
"side"
>
<div
class=
"side"
>
<div
class=
"side-box side-box-domain"
>
<div
class=
"side-box side-box-domain"
>
<AnalysisBox
title=
"涉及领域"
width=
"520px"
height=
"415px"
v-loading=
"domainLoading"
>
<AnalysisBox
title=
"涉及领域"
width=
"520px"
height=
"415px"
v-loading=
"domainLoading"
>
<div
:class=
"['right-box2-main', { 'right-box-main--full': !domainFooterText }]"
id=
"chart2"
></div>
<div
class=
"chart-ai-wrap"
>
<div
:class=
"['right-box2-main', { 'right-box-main--full': !domainFooterText }]"
id=
"chart2"
></div>
<div
class=
"overview-tip-row"
>
<TipTab
class=
"overview-tip"
/>
<AiButton
class=
"overview-tip-action"
@
mouseenter=
"handleShowAiPane('domain')"
/>
</div>
<div
v-if=
"aiPaneVisible.domain"
class=
"overview-ai-pane"
@
mouseleave=
"handleHideAiPane('domain')"
>
<AiPane
:aiContent=
"overviewAiContent.domain"
/>
</div>
</div>
<div
v-if=
"domainFooterText"
class=
"right-box2-footer"
>
<div
v-if=
"domainFooterText"
class=
"right-box2-footer"
>
<div
class=
"right-box2-footer-left"
>
<div
class=
"right-box2-footer-left"
>
<img
src=
"./assets/icons/right-icon1.png"
alt=
""
/>
<img
src=
"./assets/icons/right-icon1.png"
alt=
""
/>
...
@@ -59,7 +69,17 @@
...
@@ -59,7 +69,17 @@
</div>
</div>
<div
class=
"side-box side-box-limit"
>
<div
class=
"side-box side-box-limit"
>
<AnalysisBox
title=
"限制手段"
width=
"520px"
height=
"415px"
v-loading=
"limitLoading"
>
<AnalysisBox
title=
"限制手段"
width=
"520px"
height=
"415px"
v-loading=
"limitLoading"
>
<div
:class=
"['right-box1-main', { 'right-box-main--full': !limitFooterText }]"
id=
"chart1"
></div>
<div
class=
"chart-ai-wrap"
>
<div
:class=
"['right-box1-main', { 'right-box-main--full': !limitFooterText }]"
id=
"chart1"
></div>
<div
class=
"overview-tip-row"
>
<TipTab
class=
"overview-tip"
/>
<AiButton
class=
"overview-tip-action"
@
mouseenter=
"handleShowAiPane('limit')"
/>
</div>
<div
v-if=
"aiPaneVisible.limit"
class=
"overview-ai-pane"
@
mouseleave=
"handleHideAiPane('limit')"
>
<AiPane
:aiContent=
"overviewAiContent.limit"
/>
</div>
</div>
<div
v-if=
"limitFooterText"
class=
"right-box1-footer"
>
<div
v-if=
"limitFooterText"
class=
"right-box1-footer"
>
<div
class=
"right-box1-footer-left"
>
<div
class=
"right-box1-footer-left"
>
<img
src=
"./assets/icons/right-icon1.png"
alt=
""
/>
<img
src=
"./assets/icons/right-icon1.png"
alt=
""
/>
...
@@ -136,6 +156,10 @@ import * as echarts from "echarts";
...
@@ -136,6 +156,10 @@ import * as echarts from "echarts";
import
{
Search
}
from
"@element-plus/icons-vue"
;
import
{
Search
}
from
"@element-plus/icons-vue"
;
import
getPieChart
from
"./utils/piechart"
;
import
getPieChart
from
"./utils/piechart"
;
import
{
getBillContentId
,
getBillContentTk
,
getBillContentXzfs
,
getBillHyly
}
from
"@/api/bill"
;
import
{
getBillContentId
,
getBillContentTk
,
getBillContentXzfs
,
getBillHyly
}
from
"@/api/bill"
;
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
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
{
MUTICHARTCOLORS
}
from
"@/common/constant"
;
import
{
MUTICHARTCOLORS
}
from
"@/common/constant"
;
import
{
extractTextEntity
}
from
"@/api/intelligent/index"
;
import
{
extractTextEntity
}
from
"@/api/intelligent/index"
;
...
@@ -318,6 +342,26 @@ const chart2ColorList = ref([...MUTICHARTCOLORS]);
...
@@ -318,6 +342,26 @@ const chart2ColorList = ref([...MUTICHARTCOLORS]);
const
chart2Data
=
ref
([]);
const
chart2Data
=
ref
([]);
const
aiPaneVisible
=
ref
({
domain
:
false
,
limit
:
false
});
const
overviewAiContent
=
ref
({
domain
:
"智能总结生成中..."
,
limit
:
"智能总结生成中..."
});
const
aiPaneFetched
=
ref
({
domain
:
false
,
limit
:
false
});
const
aiPaneLoading
=
ref
({
domain
:
false
,
limit
:
false
});
const
displayTermsList
=
computed
(()
=>
{
const
displayTermsList
=
computed
(()
=>
{
const
keyword
=
(
searchKeyword
.
value
||
""
).
trim
().
toLowerCase
();
const
keyword
=
(
searchKeyword
.
value
||
""
).
trim
().
toLowerCase
();
const
domain
=
selectedDomain
.
value
;
const
domain
=
selectedDomain
.
value
;
...
@@ -356,6 +400,83 @@ watch(
...
@@ -356,6 +400,83 @@ watch(
{
immediate
:
true
}
{
immediate
:
true
}
);
);
const
buildAiChartPayload
=
key
=>
{
if
(
key
===
"domain"
)
{
return
{
type
:
"饼图"
,
name
:
"涉及领域"
,
data
:
(
chart2Data
.
value
||
[]).
map
(
item
=>
({
industry_name
:
item
.
name
,
count_bill
:
Number
(
item
.
value
||
0
)
}))
};
}
if
(
key
===
"limit"
)
{
return
{
type
:
"饼图"
,
name
:
"限制手段"
,
data
:
(
chart1Data
.
value
||
[]).
map
(
item
=>
({
industry_name
:
item
.
name
,
count_bill
:
Number
(
item
.
value
||
0
)
}))
};
}
return
{
type
:
""
,
name
:
""
,
data
:
[]
};
};
const
requestAiPaneContent
=
async
key
=>
{
if
(
!
key
||
aiPaneLoading
.
value
[
key
]
||
aiPaneFetched
.
value
[
key
])
return
;
aiPaneLoading
.
value
=
{
...
aiPaneLoading
.
value
,
[
key
]:
true
};
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
"智能总结生成中..."
};
try
{
const
payload
=
buildAiChartPayload
(
key
);
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
payload
)
},
{
onChunk
:
chunk
=>
{
const
current
=
overviewAiContent
.
value
[
key
];
const
base
=
current
===
"智能总结生成中..."
?
""
:
current
;
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
base
+
chunk
};
}
}
);
const
list
=
res
?.
data
;
const
first
=
Array
.
isArray
(
list
)
?
list
[
0
]
:
null
;
const
interpretation
=
first
?.
解读
||
first
?.[
"解读"
];
if
(
interpretation
)
{
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
interpretation
};
}
aiPaneFetched
.
value
=
{
...
aiPaneFetched
.
value
,
[
key
]:
true
};
}
catch
(
error
)
{
console
.
error
(
"获取图表解读失败"
,
error
);
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
"智能总结生成失败"
};
}
finally
{
aiPaneLoading
.
value
=
{
...
aiPaneLoading
.
value
,
[
key
]:
false
};
}
};
const
handleShowAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
true
};
requestAiPaneContent
(
key
);
};
const
handleHideAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
false
};
};
const
handleSearchSubmit
=
()
=>
{
const
handleSearchSubmit
=
()
=>
{
searchKeyword
.
value
=
searchValue
.
value
;
searchKeyword
.
value
=
searchValue
.
value
;
currentPage
.
value
=
1
;
currentPage
.
value
=
1
;
...
@@ -554,6 +675,7 @@ const handleGetBillContentXzfs = async () => {
...
@@ -554,6 +675,7 @@ const handleGetBillContentXzfs = async () => {
value: item.countTk
value: item.countTk
};
};
});
});
aiPaneFetched.value = { ...aiPaneFetched.value, limit: false };
let chart1 = getPieChart(chart1Data.value, chart1ColorList.value);
let chart1 = getPieChart(chart1Data.value, chart1ColorList.value);
setChart(chart1, "chart1");
setChart(chart1, "chart1");
} catch (error) {
} catch (error) {
...
@@ -602,6 +724,7 @@ const handleGetBillHyly = async () => {
...
@@ -602,6 +724,7 @@ const handleGetBillHyly = async () => {
value: item.countTk
value: item.countTk
};
};
});
});
aiPaneFetched.value = { ...aiPaneFetched.value, domain: false };
let chart2 = getPieChart(chart2Data.value, chart2ColorList.value);
let chart2 = getPieChart(chart2Data.value, chart2ColorList.value);
setChart(chart2, "chart2");
setChart(chart2, "chart2");
...
@@ -1017,6 +1140,38 @@ onMounted(async () => {
...
@@ -1017,6 +1140,38 @@ onMounted(async () => {
width
:
520px
;
width
:
520px
;
}
}
.chart-ai-wrap
{
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
}
.overview-tip-row
{
margin-top
:
10px
;
position
:
relative
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.overview-tip-action
{
position
:
absolute
;
right
:
0px
;
}
.overview-ai-pane
{
position
:
absolute
;
left
:
0
;
bottom
:
-22px
;
width
:
100%
;
z-index
:
3
;
pointer-events
:
none
;
:deep
(
.ai-pane-wrapper
)
{
pointer-events
:
auto
;
}
}
.side-box-limit
{
.side-box-limit
{
margin-top
:
15px
;
margin-top
:
15px
;
width
:
520px
;
width
:
520px
;
...
@@ -1024,12 +1179,12 @@ onMounted(async () => {
...
@@ -1024,12 +1179,12 @@ onMounted(async () => {
.right-box1-main
{
.right-box1-main
{
width
:
520px
;
width
:
520px
;
height
:
31
5px
;
height
:
27
5px
;
padding
:
16px
;
padding
:
16px
;
}
}
.right-box-main--full
{
.right-box-main--full
{
height
:
3
7
5px
;
height
:
3
1
5px
;
}
}
.right-box1-footer
{
.right-box1-footer
{
...
@@ -1091,12 +1246,12 @@ onMounted(async () => {
...
@@ -1091,12 +1246,12 @@ onMounted(async () => {
.right-box2-main
{
.right-box2-main
{
width
:
520px
;
width
:
520px
;
height
:
31
5px
;
height
:
27
5px
;
padding
:
16px
;
padding
:
16px
;
}
}
.right-box-main--full
{
.right-box-main--full
{
height
:
3
7
5px
;
height
:
3
1
5px
;
}
}
.right-box2-footer
{
.right-box2-footer
{
...
...
src/views/bill/versionCompare/index.vue
浏览文件 @
67bc4f74
...
@@ -151,7 +151,7 @@
...
@@ -151,7 +151,7 @@
<
script
setup
>
<
script
setup
>
import
{
computed
,
nextTick
,
onMounted
,
ref
,
watch
}
from
"vue"
;
import
{
computed
,
nextTick
,
onMounted
,
ref
,
watch
}
from
"vue"
;
import
{
useRoute
}
from
"vue-router"
;
import
{
useRoute
}
from
"vue-router"
;
import
{
getBillContentId
,
getBillVersionCompare
}
from
"@/api/bill"
;
import
{
getBillContentId
,
getBillVersionCompare
,
getBillVersionCompareStatistics
}
from
"@/api/bill"
;
import
{
extractTextEntity
}
from
"@/api/intelligent/index"
;
import
{
extractTextEntity
}
from
"@/api/intelligent/index"
;
import
{
ArrowDown
,
ArrowUp
,
Close
,
Search
}
from
"@element-plus/icons-vue"
;
import
{
ArrowDown
,
ArrowUp
,
Close
,
Search
}
from
"@element-plus/icons-vue"
;
import
translateIcon
from
"./assert/icons/translate-icons.svg"
;
import
translateIcon
from
"./assert/icons/translate-icons.svg"
;
...
@@ -197,6 +197,22 @@ const isLoading = ref(false);
...
@@ -197,6 +197,22 @@ const isLoading = ref(false);
const
comparePairs
=
ref
([]);
const
comparePairs
=
ref
([]);
const
compareRequestToken
=
ref
(
0
);
const
compareRequestToken
=
ref
(
0
);
const
queryAbortController
=
ref
(
null
);
const
resetAbortController
=
()
=>
{
queryAbortController
.
value
?.
abort
?.();
queryAbortController
.
value
=
new
AbortController
();
return
queryAbortController
.
value
;
};
const
isAbortError
=
error
=>
{
return
(
error
?.
code
===
"ERR_CANCELED"
||
error
?.
name
===
"CanceledError"
||
error
?.
name
===
"AbortError"
||
(
typeof
error
?.
message
===
"string"
&&
/canceled|aborted/i
.
test
(
error
.
message
))
);
};
const
handleLoadVersionOptions
=
async
()
=>
{
const
handleLoadVersionOptions
=
async
()
=>
{
if
(
!
billId
.
value
)
{
if
(
!
billId
.
value
)
{
...
@@ -273,21 +289,6 @@ const handleDiffTabClick = value => {
...
@@ -273,21 +289,6 @@ const handleDiffTabClick = value => {
});
});
};
};
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
mapDiffTypeToStatus
=
value
=>
{
const
diff
=
normalizeDiffType
(
value
);
const
diff
=
normalizeDiffType
(
value
);
...
@@ -296,12 +297,6 @@ const mapDiffTypeToStatus = value => {
...
@@ -296,12 +297,6 @@ const mapDiffTypeToStatus = value => {
return
"update"
;
return
"update"
;
};
};
const
mapStatusToDiffType
=
value
=>
{
if
(
value
===
"add"
)
return
"ADD"
;
if
(
value
===
"del"
)
return
"DELETE"
;
return
"CHANGE"
;
};
const
mapVersionCompareItemToPair
=
item
=>
{
const
mapVersionCompareItemToPair
=
item
=>
{
const
oldTerm
=
item
?.
originalClauseMainId
const
oldTerm
=
item
?.
originalClauseMainId
?
{
?
{
...
@@ -324,63 +319,78 @@ const mapVersionCompareItemToPair = item => {
...
@@ -324,63 +319,78 @@ const mapVersionCompareItemToPair = item => {
return
{
oldTerm
,
newTerm
};
return
{
oldTerm
,
newTerm
};
};
};
const
fetchComparePage
=
async
({
diff
,
page
,
size
})
=>
{
const
getVersionCompareBaseParams
=
()
=>
({
billId
:
billId
.
value
,
content
:
keyword
.
value
,
currentVersion
:
newVersionId
.
value
,
isCn
:
onlyChinaRelated
.
value
?
"Y"
:
"N"
,
originalVersion
:
oldVersionId
.
value
});
const
fetchComparePage
=
async
({
diff
,
page
,
size
,
signal
})
=>
{
const
params
=
{
const
params
=
{
billId
:
billId
.
value
,
...
getVersionCompareBaseParams
(),
content
:
keyword
.
value
,
currentPage
:
Math
.
max
(
1
,
Number
(
page
)
||
1
),
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
),
pageSize
:
Math
.
max
(
1
,
Number
(
size
)
||
10
),
status
:
mapDiffTypeToStatus
(
diff
)
status
:
mapDiffTypeToStatus
(
diff
)
};
};
const
res
=
await
getBillVersionCompare
(
params
);
const
res
=
await
getBillVersionCompare
(
params
,
{
signal
}
);
const
data
=
res
?.
data
?.
data
??
res
?.
data
??
{};
const
data
=
res
?.
data
?.
data
??
res
?.
data
??
{};
const
raw
=
Array
.
isArray
(
data
?.
content
)
?
data
.
content
:
[];
const
raw
=
Array
.
isArray
(
data
?.
content
)
?
data
.
content
:
[];
const
countType
=
Array
.
isArray
(
data
?.
countType
)
?
data
.
countType
:
[];
return
{
return
{
list
:
raw
.
map
(
mapVersionCompareItemToPair
),
list
:
raw
.
map
(
mapVersionCompareItemToPair
),
total
:
Number
(
data
?.
totalElements
??
0
)
||
0
,
total
:
Number
(
data
?.
totalElements
??
0
)
||
0
countType
};
};
};
};
const
getCountByChangeType
=
(
countType
List
,
changeTypeLabel
)
=>
{
const
getCountByChangeType
=
(
statistics
List
,
changeTypeLabel
)
=>
{
const
list
=
Array
.
isArray
(
countTypeList
)
?
countType
List
:
[];
const
list
=
Array
.
isArray
(
statisticsList
)
?
statistics
List
:
[];
const
target
=
list
.
find
(
item
=>
String
(
item
?.
changeType
??
""
)
===
changeTypeLabel
);
const
target
=
list
.
find
(
item
=>
String
(
item
?.
changeType
??
""
)
===
changeTypeLabel
);
return
Number
(
target
?.
count
??
0
)
||
0
;
return
Number
(
target
?.
count
??
0
)
||
0
;
};
};
const
loadCompareStatistics
=
async
(
currentToken
,
signal
)
=>
{
debugger
const
res
=
await
getBillVersionCompareStatistics
(
getVersionCompareBaseParams
(),
{
signal
});
if
(
currentToken
!==
compareRequestToken
.
value
)
return
;
const
list
=
res
.
data
diffCounts
.
value
=
{
CHANGE
:
getCountByChangeType
(
list
,
"更新"
),
ADD
:
getCountByChangeType
(
list
,
"新增"
),
DELETE
:
getCountByChangeType
(
list
,
"删除"
)
};
};
const
loadComparePairs
=
async
()
=>
{
const
loadComparePairs
=
async
()
=>
{
if
(
!
billId
.
value
||
!
oldVersionId
.
value
||
!
newVersionId
.
value
)
{
if
(
!
billId
.
value
||
!
oldVersionId
.
value
||
!
newVersionId
.
value
)
{
queryAbortController
.
value
?.
abort
?.();
comparePairs
.
value
=
[];
comparePairs
.
value
=
[];
updateDiffCounts
([])
;
diffCounts
.
value
=
{
CHANGE
:
0
,
ADD
:
0
,
DELETE
:
0
}
;
total
.
value
=
0
;
total
.
value
=
0
;
return
;
return
;
}
}
const
controller
=
resetAbortController
();
const
currentToken
=
++
compareRequestToken
.
value
;
const
currentToken
=
++
compareRequestToken
.
value
;
isLoading
.
value
=
true
;
isLoading
.
value
=
true
;
try
{
try
{
const
currentRes
=
await
fetchComparePage
({
const
[
currentRes
]
=
await
Promise
.
all
([
diff
:
diffType
.
value
,
fetchComparePage
({
page
:
currentPage
.
value
,
diff
:
diffType
.
value
,
size
:
pageSize
.
value
page
:
currentPage
.
value
,
});
size
:
pageSize
.
value
,
if
(
currentToken
!==
compareRequestToken
.
value
)
return
;
signal
:
controller
.
signal
}),
loadCompareStatistics
(
currentToken
,
controller
.
signal
)
]);
if
(
currentToken
!==
compareRequestToken
.
value
||
controller
.
signal
.
aborted
)
return
;
comparePairs
.
value
=
currentRes
.
list
;
comparePairs
.
value
=
currentRes
.
list
;
total
.
value
=
currentRes
.
total
;
total
.
value
=
currentRes
.
total
;
diffCounts
.
value
=
{
await
ensureEntitiesForPairs
(
comparePairs
.
value
,
controller
.
signal
);
CHANGE
:
getCountByChangeType
(
currentRes
.
countType
,
"更新"
),
ADD
:
getCountByChangeType
(
currentRes
.
countType
,
"新增"
),
DELETE
:
getCountByChangeType
(
currentRes
.
countType
,
"删除"
)
};
await
ensureEntitiesForPairs
(
comparePairs
.
value
);
}
catch
(
error
)
{
}
catch
(
error
)
{
if
(
currentToken
!==
compareRequestToken
.
value
)
return
;
if
(
currentToken
!==
compareRequestToken
.
value
||
isAbortError
(
error
)
)
return
;
comparePairs
.
value
=
[];
comparePairs
.
value
=
[];
updateDiffCounts
([])
;
diffCounts
.
value
=
{
CHANGE
:
0
,
ADD
:
0
,
DELETE
:
0
}
;
total
.
value
=
0
;
total
.
value
=
0
;
}
finally
{
}
finally
{
if
(
currentToken
===
compareRequestToken
.
value
)
{
if
(
currentToken
===
compareRequestToken
.
value
)
{
...
@@ -532,8 +542,9 @@ const getTermEntityKey = (term, lang) => {
...
@@ -532,8 +542,9 @@ const getTermEntityKey = (term, lang) => {
return
`
${
baseKey
}
__
${
lang
}
`
;
return
`
${
baseKey
}
__
${
lang
}
`
;
};
};
const
ensureEntitiesForPairs
=
async
pairs
=>
{
const
ensureEntitiesForPairs
=
async
(
pairs
,
signal
=
queryAbortController
.
value
?.
signal
)
=>
{
if
(
!
termsHighlight
.
value
)
return
;
if
(
!
termsHighlight
.
value
)
return
;
if
(
signal
?.
aborted
)
return
;
const
list
=
Array
.
isArray
(
pairs
)
?
pairs
:
[];
const
list
=
Array
.
isArray
(
pairs
)
?
pairs
:
[];
if
(
!
list
.
length
)
return
;
if
(
!
list
.
length
)
return
;
...
@@ -563,18 +574,19 @@ const ensureEntitiesForPairs = async pairs => {
...
@@ -563,18 +574,19 @@ const ensureEntitiesForPairs = async pairs => {
try
{
try
{
const
results
=
await
Promise
.
all
(
const
results
=
await
Promise
.
all
(
tasks
.
map
(
async
item
=>
{
tasks
.
map
(
async
item
=>
{
const
res
=
await
extractTextEntity
(
item
.
text
);
if
(
signal
?.
aborted
)
throw
new
DOMException
(
"Aborted"
,
"AbortError"
);
const
res
=
await
extractTextEntity
(
item
.
text
,
{
signal
});
const
entities
=
normalizeEntities
(
res
?.
result
??
res
?.
data
?.
result
??
res
?.
data
??
res
);
const
entities
=
normalizeEntities
(
res
?.
result
??
res
?.
data
?.
result
??
res
?.
data
??
res
);
return
{
key
:
item
.
key
,
entities
};
return
{
key
:
item
.
key
,
entities
};
})
})
);
);
if
(
currentToken
!==
entityRequestToken
.
value
)
return
;
if
(
currentToken
!==
entityRequestToken
.
value
||
signal
?.
aborted
)
return
;
for
(
const
r
of
results
)
{
for
(
const
r
of
results
)
{
termEntityCache
.
value
.
set
(
r
.
key
,
r
.
entities
);
termEntityCache
.
value
.
set
(
r
.
key
,
r
.
entities
);
}
}
}
catch
(
error
)
{
}
catch
(
error
)
{
if
(
currentToken
!==
entityRequestToken
.
value
)
return
;
if
(
currentToken
!==
entityRequestToken
.
value
||
isAbortError
(
error
)
)
return
;
}
}
};
};
...
@@ -721,6 +733,8 @@ onMounted(async () => {
...
@@ -721,6 +733,8 @@ onMounted(async () => {
flex-direction
:
column
;
flex-direction
:
column
;
row-gap
:
16px
;
row-gap
:
16px
;
width
:
100%
;
width
:
100%
;
height
:
848px
;
overflow
:
hidden
;
background
:
#ffffff
;
background
:
#ffffff
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.04
);
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.04
);
padding
:
16px
75px
;
padding
:
16px
75px
;
...
@@ -974,6 +988,10 @@ onMounted(async () => {
...
@@ -974,6 +988,10 @@ onMounted(async () => {
.compare-columns
{
.compare-columns
{
margin-top
:
16px
;
margin-top
:
16px
;
flex
:
1
;
min-height
:
0
;
overflow-y
:
auto
;
overflow-x
:
hidden
;
display
:
flex
;
display
:
flex
;
flex-direction
:
column
;
flex-direction
:
column
;
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论