提交 86a1c542 authored 作者: 朱政's avatar 朱政

Merge branch 'master' into zz-dev

const baseUrl = `http://8.140.26.4:9085/` const baseUrl = `http://8.140.26.4:9085`
\ No newline at end of file \ No newline at end of file
...@@ -251,7 +251,7 @@ body { ...@@ -251,7 +251,7 @@ body {
/* 可点击文本 鼠标悬浮样式 */ /* 可点击文本 鼠标悬浮样式 */
#app .text-click-hover:hover { #app .text-click-hover:hover {
text-decoration: underline; text-decoration: underline;
color: rgb(5, 95, 194); color: var(--color-primary-100);
cursor: pointer; cursor: pointer;
} }
/* #endregion 公共样式类名 */ /* #endregion 公共样式类名 */
......
...@@ -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,
}); });
} }
...@@ -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',
......
...@@ -5,6 +5,7 @@ export function getDepartmentList(params) { ...@@ -5,6 +5,7 @@ export function getDepartmentList(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/administrativeDict/department`, url: `/api/administrativeDict/department`,
params
}) })
} }
...@@ -27,34 +28,36 @@ export function getDecreeRiskSignal(params) { ...@@ -27,34 +28,36 @@ export function getDecreeRiskSignal(params) {
// 行政令发布频度 // 行政令发布频度
export function getDecreeYearOrder(params) { export function getDecreeYearOrder(params) {
return request({ return request({
method: 'GET', method: 'POST',
url: `/api/administrativeOrderOverview/yearOrder/${params.year}`, url: `/api/administrativeOrderOverview/yearOrder`,
params data: params
}) })
} }
// 政令涉及领域 // 政令涉及领域
export function getDecreeArea(params) { export function getDecreeArea(params) {
return request({ return request({
method: 'GET', method: 'POST',
url: `/api/administrativeOrderOverview/industry/${params.year}`, url: `/api/administrativeOrderOverview/industry`,
params data: params
}) })
} }
// 关键行政令 // 关键行政令
export function getKeyDecree(params) { export function getKeyDecree(params) {
return request({ return request({
method: 'GET', method: 'POST',
url: `/api/administrativeOrderOverview/action?pageSize=${params.pageSize}&pageNum=${params.pageNum}`, url: `/api/administrativeOrderOverview/action`,
data: params
}) })
} }
// 政令重点条款 // 政令重点条款
export function getDecreeKeyInstruction() { export function getDecreeKeyInstruction(params) {
return request({ return request({
method: 'GET', method: 'POST',
url: `/api/administrativeOrderOverview/instruction`, url: `/api/administrativeOrderOverview/instruction`,
data: params
}) })
} }
...@@ -86,3 +89,21 @@ export function getDecreeTypeList() { ...@@ -86,3 +89,21 @@ export function getDecreeTypeList() {
url: `/api/administrativeDict/type`, url: `/api/administrativeDict/type`,
}) })
} }
// 关键机构
export function getKeyOrganization() {
return request({
method: 'GET',
url: `/api/commonFeature/keyOrganization`,
})
}
// AI智能总结
export function getChartInterpretation(params) {
return request({
method: 'POST',
url: `/aiAnalysis/chart_interpretation`,
headers: {"X-API-Key": "aircasKEY19491001"},
data: params
})
}
\ No newline at end of file
...@@ -20,6 +20,39 @@ export function getDecreehylyList() { ...@@ -20,6 +20,39 @@ export function getDecreehylyList() {
}) })
} }
// 获取受影响实体列表
export function getDecreeEntities(params) {
return request({
method: 'POST',
url: `/api/administrativeOrderInfo/relatedEntities`,
data: params
})
}
// 获取实体产业链列表
export function getDecreeRelatedChain(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/relatedChain/${params.id}`,
})
}
// 获取产业链节点列表
export function getDecreeChainNodes(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/relatedChainNodes/${params.id}`,
})
}
// 获取实体关系节点列表
export function getDecreeRelatedEntitie(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/listRelatedEntitie/${params.id}`,
})
}
// 根据政行业领域ID获取公司列表 // 根据政行业领域ID获取公司列表
/** /**
* @param {cRelated, id} * @param {cRelated, id}
......
...@@ -78,3 +78,19 @@ export function getDecreeReport(params) { ...@@ -78,3 +78,19 @@ export function getDecreeReport(params) {
url: `/api/administrativeOrderInfo/contentUrl/${params.id}`, url: `/api/administrativeOrderInfo/contentUrl/${params.id}`,
}) })
} }
// 政令关键词云
export function getKeyWordUp() {
return request({
method: 'GET',
url: `/api/element/getKeyWordUp/2025-01-01`,
})
}
// 报告内容摘要
export function getOverview(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/overview/${params.id}`,
})
}
\ No newline at end of file
...@@ -4,6 +4,7 @@ import { ElMessage } from "element-plus"; ...@@ -4,6 +4,7 @@ import { ElMessage } from "element-plus";
const request200 = requestP => { const request200 = requestP => {
return requestP.then(data => { return requestP.then(data => {
if (data.code === 200) { if (data.code === 200) {
console.log('返回的数据结构 =>', data.data)
return data.data; return data.data;
} }
ElMessage({ ElMessage({
...@@ -122,13 +123,14 @@ export function getSanctionsInfoCount() { ...@@ -122,13 +123,14 @@ export function getSanctionsInfoCount() {
* sanReason: string * sanReason: string
* }[]>} * }[]>}
*/ */
export function getSanctionProcess(typeName = "实体清单", pageNum = 1, pageSize = 10, isCn = false) { export function getSanctionProcess(sanTypeIds = "1", pageNum = 1, pageSize = 10, isCn = false) {
return request200( return request200(
request({ request({
method: "POST", method: "POST",
url: "/api/entitiesDataCount/getSanctionProcess", url: "/api/entitiesDataCount/getSanctionProcess",
data: { data: {
typeName, sanTypeIds,
// typeName: tabMap[sanTypeId],
pageNum, pageNum,
pageSize, pageSize,
isCn isCn
......
import request from "@/api/request.js"; import request from "@/api/request.js";
// 实体清单-制裁概况-获取实体清单基本信息 // 实体清单-制裁概况-获取实体清单基本信息
export function getEntityInfo(sanType) { export function getEntityInfo(id) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/baseInfo/${sanType}` url: `/api/sanctionList/baseInfoById/${id}`
}); });
} }
...@@ -98,10 +98,10 @@ export function get50PercentEntityCount(data) { ...@@ -98,10 +98,10 @@ export function get50PercentEntityCount(data) {
} }
// 实体清单-数据统计-总量统计 // 实体清单-数据统计-总量统计
export function getTotalCount() { export function getTotalCount(id) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/total` url: `/api/sanctionList/statistics/total?sanTypeId=${id}`
}); });
} }
...@@ -113,7 +113,7 @@ export function getTotalCount() { ...@@ -113,7 +113,7 @@ export function getTotalCount() {
export function getSanctionCountChange(params) { export function getSanctionCountChange(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/num`, url: `/api/sanctionList/statistics/num`,
params params
}); });
} }
...@@ -128,7 +128,7 @@ export function getSanctionCountChange(params) { ...@@ -128,7 +128,7 @@ export function getSanctionCountChange(params) {
export function getRegionCount(params) { export function getRegionCount(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/region`, url: `/api/sanctionList/statistics/region`,
params params
}); });
} }
...@@ -143,7 +143,7 @@ export function getRegionCount(params) { ...@@ -143,7 +143,7 @@ export function getRegionCount(params) {
export function getTechDomainCount(params) { export function getTechDomainCount(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/domain`, url: `/api/sanctionList/statistics/domain`,
params params
}); });
} }
...@@ -158,7 +158,7 @@ export function getTechDomainCount(params) { ...@@ -158,7 +158,7 @@ export function getTechDomainCount(params) {
export function getEntityTypeCount(params) { export function getEntityTypeCount(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/entityType`, url: `/api/sanctionList/statistics/entityType`,
params params
}); });
} }
...@@ -247,7 +247,7 @@ export function getSingleSanctionOverview(params) { ...@@ -247,7 +247,7 @@ export function getSingleSanctionOverview(params) {
export function getSingleSanctionEntityCountry(params) { export function getSingleSanctionEntityCountry(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/countryRegion`, url: `/api/sanctionList/statistics/countryRegion`,
params params
}); });
} }
...@@ -292,11 +292,10 @@ export function getSingleSanctionOverviewList(data) { ...@@ -292,11 +292,10 @@ export function getSingleSanctionOverviewList(data) {
* @param {string} params.sanRecordId - 制裁记录ID * @param {string} params.sanRecordId - 制裁记录ID
* @header token * @header token
*/ */
export function getSingleSanctionTotalCount(params) { export function getSingleSanctionTotalCount(id) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/total`, url: `/api/sanctionList/statistics/total?sanTypeId=${id}`,
params
}); });
} }
...@@ -311,7 +310,7 @@ export function getSingleSanctionTotalCount(params) { ...@@ -311,7 +310,7 @@ export function getSingleSanctionTotalCount(params) {
export function getSingleSanctionDomainCount(params) { export function getSingleSanctionDomainCount(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/domain`, url: `/api/sanctionList/statistics/domain`,
params params
}); });
} }
...@@ -327,7 +326,7 @@ export function getSingleSanctionDomainCount(params) { ...@@ -327,7 +326,7 @@ export function getSingleSanctionDomainCount(params) {
export function getSingleSanctionEntityTypeCount(params) { export function getSingleSanctionEntityTypeCount(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/entityType`, url: `/api/sanctionList/statistics/entityType`,
params params
}); });
} }
...@@ -341,7 +340,7 @@ export function getSingleSanctionEntityTypeCount(params) { ...@@ -341,7 +340,7 @@ export function getSingleSanctionEntityTypeCount(params) {
export function getSingleSanctionEntityCountryCount(params) { export function getSingleSanctionEntityCountryCount(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/countryRegion`, url: `/api/sanctionList/statistics/countryRegion`,
params params
}); });
} }
...@@ -357,7 +356,7 @@ export function getSingleSanctionEntityCountryCount(params) { ...@@ -357,7 +356,7 @@ export function getSingleSanctionEntityCountryCount(params) {
export function getSingleSanctionEntityRegionCount(params) { export function getSingleSanctionEntityRegionCount(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/el/region`, url: `/api/sanctionList/statistics/region`,
params params
}); });
} }
......
...@@ -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
}); });
} }
...@@ -50,7 +50,7 @@ export const countryCoordMap = { ...@@ -50,7 +50,7 @@ export const countryCoordMap = {
// 欧洲 // 欧洲
俄罗斯: [37.6184, 55.7558], // 俄罗斯莫斯科 俄罗斯: [37.6184, 55.7558], // 俄罗斯莫斯科
德国: [10.4515, 51.1657], // 德国柏林 德国: [10.4515, 51.1657], // 德国柏林
英国: [-3.436, 55.3781], // 英国伦敦 英国: [-2, 54], // 英国伦敦
法国: [2.2137, 46.2276], // 法国巴黎 法国: [2.2137, 46.2276], // 法国巴黎
意大利: [12.5674, 41.8719], // 意大利罗马 意大利: [12.5674, 41.8719], // 意大利罗马
西班牙: [-3.7492, 40.4637], // 西班牙马德里 西班牙: [-3.7492, 40.4637], // 西班牙马德里
...@@ -218,12 +218,12 @@ export function convertAsiaCenterCoord(coord) { ...@@ -218,12 +218,12 @@ export function convertAsiaCenterCoord(coord) {
const [lng, lat] = coord; const [lng, lat] = coord;
// 将以本初子午线为基准的坐标转换为以亚洲为中心的坐标 // 将以本初子午线为基准的坐标转换为以亚洲为中心的坐标
// world-asia-center.json 是将标准坐标的经度减去了 180 度 // world-asia-center.json 是将标准坐标的经度加上了 180 度(地图向右平移)
let newLng = lng - 180; let newLng = lng + 180;
// 规范化到 [-180, 180] 范围 // 规范化到 [-180, 180] 范围
if (newLng < -180) { if (newLng > 180) {
newLng += 360; newLng -= 360;
} }
return [newLng, lat]; return [newLng, lat];
......
...@@ -135,8 +135,8 @@ const headerTitleClasses = computed(() => [ ...@@ -135,8 +135,8 @@ const headerTitleClasses = computed(() => [
.header-icon { .header-icon {
width: 22px; width: 22px;
height: 18px; height: 18px;
margin-left: 5px; margin-left: 0px;
margin-right: 14px; margin-right: 10px;
} }
.blue-title-block { .blue-title-block {
...@@ -155,6 +155,7 @@ const headerTitleClasses = computed(() => [ ...@@ -155,6 +155,7 @@ const headerTitleClasses = computed(() => [
/* color: var(--base-color); */ /* color: var(--base-color); */
color: $base-color; color: $base-color;
line-height: 48px; line-height: 48px;
padding: 0 5px;
// padding: 0 12px; // padding: 0 12px;
} }
......
...@@ -326,9 +326,8 @@ watch(isTranslate, () => { ...@@ -326,9 +326,8 @@ watch(isTranslate, () => {
.report-main { .report-main {
flex: auto; flex: auto;
min-height: 0; height: 20px;
box-sizing: border-box; padding: 10px 0;
padding-top: 10px;
:deep(.el-scrollbar) { :deep(.el-scrollbar) {
height: 100%; height: 100%;
......
...@@ -5,10 +5,11 @@ ...@@ -5,10 +5,11 @@
</template> </template>
<script setup> <script setup>
import { onMounted, nextTick } from 'vue'; import { onMounted, onBeforeUnmount } from 'vue';
import setChart from '@/utils/setChart'; import setChart from '@/utils/setChart';
import getGraphChart from './graphChart'; import getGraphChart from './graphChart';
const emits = defineEmits(["handleClickNode"])
const props = defineProps({ const props = defineProps({
nodes: { nodes: {
type: Array, type: Array,
...@@ -32,11 +33,17 @@ const props = defineProps({ ...@@ -32,11 +33,17 @@ const props = defineProps({
} }
}) })
let chart = null
onMounted(() => { onMounted(() => {
const graph = getGraphChart(props.nodes, props.links, props.layoutType) const graph = getGraphChart(props.nodes, props.links, props.layoutType)
setChart(graph, 'graph') chart = setChart(graph, 'graph')
chart.on("click", (event) => { emits("handleClickNode", event) })
}) })
onBeforeUnmount(() => {
chart.off("click")
chart.dispose()
})
</script> </script>
......
...@@ -96,16 +96,16 @@ const homeTitleList = ref([ ...@@ -96,16 +96,16 @@ const homeTitleList = ref([
path: "/ZMOverView", path: "/ZMOverView",
disabled: false disabled: false
}, },
{ // {
name: "主要国家科技动向感知", // name: "主要国家科技动向感知",
path: "", // path: "",
disabled: true // disabled: true
}, // },
{ // {
name: "主要国家竞争科技安全", // name: "主要国家竞争科技安全",
path: "", // path: "",
disabled: true // disabled: true
} // }
]); ]);
const homeActiveTitleIndex = ref(0); const homeActiveTitleIndex = ref(0);
......
...@@ -626,6 +626,7 @@ function createChart() { ...@@ -626,6 +626,7 @@ function createChart() {
map: "world", map: "world",
roam: true, roam: true,
zoom: 1.2, zoom: 1.2,
nameMap: nameMap,
label: { label: {
show: false show: false
}, },
......
...@@ -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,
......
...@@ -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,
......
...@@ -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="chart-ai-wrap">
<div :class="['right-box2-main', { 'right-box-main--full': !domainFooterText }]" id="chart2"></div> <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="chart-ai-wrap">
<div :class="['right-box1-main', { 'right-box-main--full': !limitFooterText }]" id="chart1"></div> <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: 315px; height: 275px;
padding: 16px; padding: 16px;
} }
.right-box-main--full { .right-box-main--full {
height: 375px; height: 315px;
} }
.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: 315px; height: 275px;
padding: 16px; padding: 16px;
} }
.right-box-main--full { .right-box-main--full {
height: 375px; height: 315px;
} }
.right-box2-footer { .right-box2-footer {
......
...@@ -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 = () => ({
const params = {
billId: billId.value, billId: billId.value,
content: keyword.value, content: keyword.value,
currentPage: Math.max(1, Number(page) || 1),
currentVersion: newVersionId.value, currentVersion: newVersionId.value,
isCn: onlyChinaRelated.value ? "Y" : "N", isCn: onlyChinaRelated.value ? "Y" : "N",
originalVersion: oldVersionId.value, originalVersion: oldVersionId.value
});
const fetchComparePage = async ({ diff, page, size, signal }) => {
const params = {
...getVersionCompareBaseParams(),
currentPage: Math.max(1, Number(page) || 1),
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 = (countTypeList, changeTypeLabel) => { const getCountByChangeType = (statisticsList, changeTypeLabel) => {
const list = Array.isArray(countTypeList) ? countTypeList : []; const list = Array.isArray(statisticsList) ? statisticsList : [];
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([
fetchComparePage({
diff: diffType.value, diff: diffType.value,
page: currentPage.value, page: currentPage.value,
size: pageSize.value size: pageSize.value,
}); signal: controller.signal
if (currentToken !== compareRequestToken.value) return; }),
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;
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="box1"> <div class="box1">
<AnalysisBox title="相关政令" :showAllBtn="false"> <AnalysisBox title="相关政令" :showAllBtn="false">
<div class="box1-main"> <div class="box1-main">
<el-empty v-if="!siderList?.length" style="padding-top: 40%;" description="暂无数据" :image-size="100" /> <el-empty v-if="!siderList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always> <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="left-item" :class="{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)">
<div class="item-head"> <div class="item-head">
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
</div> </div>
<div class="box2"> <div class="box2">
<AnalysisBox title="政令关系挖掘" :showAllBtn="false"> <AnalysisBox title="政令关系挖掘" :showAllBtn="false">
<el-empty v-if="!siderList?.length" style="padding-top: 20%;" description="暂无数据" :image-size="100" /> <el-empty v-if="!siderList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div class="box2-main"> <div class="box2-main" v-if="graphData.nodes?.length">
<div ref="containerRef" class="graph-container"></div> <GraphChart :nodes="graphData.nodes" :links="graphData.links" layoutType="force" @handleClickNode="handleClickNode" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
...@@ -48,12 +48,13 @@ ...@@ -48,12 +48,13 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onBeforeUnmount } from "vue"; import { ref, onMounted, onBeforeUnmount, reactive } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import router from "@/router"; import router from "@/router";
import * as G6 from '@antv/g6'; import * as G6 from '@antv/g6';
import { getDecreeRelatedOrder } from "@/api/decree/deepdig"; import { getDecreeRelatedOrder } from "@/api/decree/deepdig";
import { getDecreeSummary } from "@/api/decree/introduction"; import { getDecreeSummary } from "@/api/decree/introduction";
import GraphChart from "@/components/base/GraphChart/index.vue";
import icon1628 from "./assets/icons/icon1628.png"; import icon1628 from "./assets/icons/icon1628.png";
import icon1629 from "./assets/icons/icon1629.png"; import icon1629 from "./assets/icons/icon1629.png";
...@@ -64,7 +65,7 @@ const route = useRoute(); ...@@ -64,7 +65,7 @@ const route = useRoute();
const dialogVisible = ref(false); const dialogVisible = ref(false);
// 基本信息 // 基本信息
const mainInfo = ref({}); const mainInfo = ref({ label: "", time: "", id: "" });
const nodeInfo = ref({}); const nodeInfo = ref({});
const onDecreeSummaryData = async () => { const onDecreeSummaryData = async () => {
try { try {
...@@ -74,10 +75,9 @@ const onDecreeSummaryData = async () => { ...@@ -74,10 +75,9 @@ const onDecreeSummaryData = async () => {
mainInfo.value.label = res.data.name; mainInfo.value.label = res.data.name;
mainInfo.value.time = res.data.postDate; mainInfo.value.time = res.data.postDate;
mainInfo.value.id = route.query.id; mainInfo.value.id = route.query.id;
mainInfo.value.isCenter = true
} }
} catch (error) { } catch (error) {
mainInfo.value = {}; mainInfo.value = { label: "", time: "", id: "" };
console.log("获取基本信息数据失败:", error); console.log("获取基本信息数据失败:", error);
} }
}; };
...@@ -109,145 +109,64 @@ const handleGetRelateOrder = async () => { ...@@ -109,145 +109,64 @@ const handleGetRelateOrder = async () => {
}; };
// 政令关系挖掘 // 政令关系挖掘
const containerRef = ref(); const graphData = reactive({
let graphInstance = null; nodes: [],
// 文本插入换行符 links: [],
const onWordWrap = (word, num) => { })
const list = word.split(''); // 节点点击处理
let label = ""; const handleClickNode = ({data}) => {
for (let i = 0; i < list.length; i++) { if (data.target) {
if (i % num === 0 && i !== 0) { let node = siderList.value.find(item => item.id==data.target)
label += "\n"; if (node) handleClickSider(node)
} else {
let node = siderList.value.find(item => item.id==data.id)
if (node) handleClickDecree(node)
} }
label += list[i]; }
const initGraphChart = () => {
Promise.all([onDecreeSummaryData(), handleGetRelateOrder()]).then(() => {
if (mainInfo.value.id && siderList.value.length) {
graphData.links = siderList.value.map(onFormatLink)
graphData.nodes = siderList.value.map(onFormatNode)
graphData.nodes.unshift(onFormatNode(mainInfo.value))
} }
return label; })
} }
const onFormatNode = (item) => { const onFormatLink = (item, index) => {
let isCenter = item.isCenter || false
return { return {
id: item.id+'', label:onWordWrap(item.label, 15), isCenter, id: `link-${index+1}`,
img: isCenter ? icon1628 : icon1629, source: route.query.id, target: item.id+'',
clipCfg: { r: isCenter ? 40 : 30 }, label: { show: true, color: "#055fc2", backgroundColor: "#eef7ff", borderWidth: 0, offset: [0, 15], formatter: item.relation },
labelCfg: { lineStyle: { color: '#B9DCFF', type: "solid", opacity: 1 }
style: {
fill: isCenter ? "#1459BB" : "#333333",
fontSize: isCenter ? 13 : 13,
fontWeight: isCenter ? "bold" : "normal"
}
}
} }
} }
const onFormatEdge = (item, index) => { const onFormatNode = (item) => {
let leader = item.id == mainInfo.value.id;
return { return {
id: `edge-${index+1}`, id: item.id+'',
target: item.id+'', name: onWordWrap(item.label, 8),
source: route.query.id, label: {
// label: ["", "相似", "继承", "冲突"][1], show: true,
label: item.relation, color: leader ? "#055fc2" : "#3b414b",
style: { fontSize: leader ? 16 : 14,
stroke: ["", "#B9DCFF", "#87E8DE", "#FFCCC7"][1], fontWeight: leader ? 700 : 400,
fontFamily: 'Source Han Sans CN',
}, },
labelCfg: { symbolSize: leader ? 60 : 40,
style: { symbol: `image://${leader ? icon1628 : icon1629}`
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][1],
background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][1],
}
}
}
} }
} }
const initChart = () => { // 文本插入换行符
let edgeList = siderList.value.map(onFormatEdge) const onWordWrap = (word, num) => {
let nodeList = siderList.value.map(onFormatNode) const list = word.split('');
nodeList.unshift(onFormatNode(mainInfo.value)) let label = "";
console.log(nodeList) for (let i = 0; i < list.length; i++) {
if (i % num === 0 && i !== 0) {
const width = containerRef.value.offsetWidth || 800 label += "\n";
const height = containerRef.value.offsetHeight || 600
const centerX = width / 2
const centerY = height / 2
const radius = Math.min(width, height) / 2 - 120
const otherNodes = nodeList.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 = nodeList.find(n => n.isCenter)
if (centerNode) {
centerNode.x = centerX
centerNode.y = centerY
centerNode.fx = centerX
centerNode.fy = centerY
}
graphInstance = 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' ]
},
defaultNode: {
type: 'image',
size: 50,
style: { cursor: "pointer" },
clipCfg: { show: true, type: 'circle' },
labelCfg: {
position: "bottom", offset: 12,
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: "line",
style: { lineWidth: 1, endArrow: true },
labelCfg: {
autoRotate: true,
style: {
cursor: "pointer",
fontSize: 12,
fontFamily: 'Microsoft YaHei',
background: { padding: [4, 4, 4, 4] }
}
} }
label += list[i];
} }
}) return label;
// 节点点击处理
graphInstance.on('node:click', (evt) => {
let node = siderList.value.find(item => item.id==evt.item._cfg.model.id)
if (node) handleClickDecree(node)
});
graphInstance.on('edge:click', (evt) => {
let node = siderList.value.find(item => item.id==evt.item._cfg.model.target)
if (node) handleClickSider(node)
});
graphInstance.data({nodes: nodeList, edges: edgeList})
graphInstance.render()
} }
const handleClickDecree = decree => { const handleClickDecree = decree => {
...@@ -293,9 +212,9 @@ const onRelationChart = () => { ...@@ -293,9 +212,9 @@ const onRelationChart = () => {
}, },
labelCfg: { labelCfg: {
style: { style: {
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][1], fill: ["", "#055fc2", "#13A8A8", "#CE4F51"][1],
background: { background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][1], fill: ["", "#eef7ff", "#E6FFFB", "#FFE0E0"][1],
} }
} }
} }
...@@ -363,13 +282,10 @@ const onRelationChart = () => { ...@@ -363,13 +282,10 @@ const onRelationChart = () => {
} }
onMounted(() => { onMounted(() => {
Promise.all([onDecreeSummaryData(), handleGetRelateOrder()]).then(() => { initGraphChart()
if (mainInfo.value.id && siderList.value.length) initChart()
})
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
graphInstance?.destroy()
graph?.destroy() graph?.destroy()
}) })
</script> </script>
...@@ -462,10 +378,6 @@ onBeforeUnmount(() => { ...@@ -462,10 +378,6 @@ onBeforeUnmount(() => {
.box2-main { .box2-main {
height: 100%; height: 100%;
padding: 10px; padding: 10px;
.graph-container {
width: 100%;
height: 600px;
}
} }
} }
} }
......
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论