提交 f8cfb724 authored 作者: 张伊明's avatar 张伊明

合并分支 'zy-dev' 到 'master'

Zy dev 查看合并请求 !190
const baseUrl = `http://8.140.26.4:9085/`
\ No newline at end of file
const baseUrl = `http://8.140.26.4:9085`
\ No newline at end of file
......@@ -251,7 +251,7 @@ body {
/* 可点击文本 鼠标悬浮样式 */
#app .text-click-hover:hover {
text-decoration: underline;
color: rgb(5, 95, 194);
color: var(--color-primary-100);
cursor: pointer;
}
/* #endregion 公共样式类名 */
......
......@@ -5,6 +5,7 @@ export function getDepartmentList(params) {
return request({
method: 'GET',
url: `/api/administrativeDict/department`,
params
})
}
......@@ -27,34 +28,36 @@ export function getDecreeRiskSignal(params) {
// 行政令发布频度
export function getDecreeYearOrder(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderOverview/yearOrder/${params.year}`,
params
method: 'POST',
url: `/api/administrativeOrderOverview/yearOrder`,
data: params
})
}
// 政令涉及领域
export function getDecreeArea(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderOverview/industry/${params.year}`,
params
method: 'POST',
url: `/api/administrativeOrderOverview/industry`,
data: params
})
}
// 关键行政令
export function getKeyDecree(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderOverview/action?pageSize=${params.pageSize}&pageNum=${params.pageNum}`,
method: 'POST',
url: `/api/administrativeOrderOverview/action`,
data: params
})
}
// 政令重点条款
export function getDecreeKeyInstruction() {
export function getDecreeKeyInstruction(params) {
return request({
method: 'GET',
method: 'POST',
url: `/api/administrativeOrderOverview/instruction`,
data: params
})
}
......@@ -85,4 +88,22 @@ export function getDecreeTypeList() {
method: 'GET',
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() {
})
}
// 获取受影响实体列表
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获取公司列表
/**
* @param {cRelated, id}
......
......@@ -77,4 +77,20 @@ export function getDecreeReport(params) {
method: 'GET',
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
......@@ -326,9 +326,8 @@ watch(isTranslate, () => {
.report-main {
flex: auto;
min-height: 0;
box-sizing: border-box;
padding-top: 10px;
height: 20px;
padding: 10px 0;
:deep(.el-scrollbar) {
height: 100%;
......
......@@ -5,10 +5,11 @@
</template>
<script setup>
import { onMounted, nextTick } from 'vue';
import { onMounted, onBeforeUnmount } from 'vue';
import setChart from '@/utils/setChart';
import getGraphChart from './graphChart';
const emits = defineEmits(["handleClickNode"])
const props = defineProps({
nodes: {
type: Array,
......@@ -27,16 +28,22 @@ const props = defineProps({
default: 'force'
},
height: {
type: String,
type: String,
default: 'force'
}
})
let chart = null
onMounted(() => {
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>
......
......@@ -6,14 +6,6 @@
<div class="home-main-header-center">
<SearchContainer style="margin-bottom: 0; margin-top: 48px; height: fit-content" v-if="containerRef"
placeholder="搜索政令" :containerRef="containerRef" areaName="政令" />
<!-- <el-input v-model="searchDecreeText" @keyup.enter="handleSearch" style="width: 838px; height: 100%"
placeholder="搜索政令" />
<div class="search">
<div class="search-icon">
<img src="./assets/images/search-icon.png" alt="" />
</div>
<div class="search-text" @click="handleSearch">搜索</div>
</div> -->
</div>
<!-- <div class="home-main-header-footer" v-show="!isShow">
<div class="home-main-header-footer-item">
......@@ -37,43 +29,25 @@
<div class="item-footer">分析报告</div>
</div>
</div> -->
<!-- <div class="home-main-header-btn-box" v-show="!isShow">
<div class="btn" @click="handleToPosi('position1')">
<div class="btn-text">{{ "最新动态" }}</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
</div>
</div>
<div class="btn" @click="handleToPosi('position2')">
<div class="btn-text">{{ "资讯要闻" }}</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
</div>
</div>
<div class="btn" @click="handleToPosi('position3')">
<div class="btn-text">{{ "数据总览" }}</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
</div>
</div>
<div class="btn" @click="handleToPosi('position4')">
<div class="btn-text">{{ "资源库" }}</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
</div>
<div class="date-box" v-if="govInsList.length">
<div class="date-icon">
<img :src="tipsTcon" alt="">
</div>
</div> -->
<div class="date-text">近期美国各联邦政府机构发布涉华政令数量汇总</div>
<TimeTabPane @time-click="handleGetDepartmentList" />
</div>
<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="organization-item" v-for="(item, index) in govInsList.slice(0, 7)" :key="index" @click="handleToInstitution(item)">
<div class="item-left">
<img :src="item.orgImage || DefaultIcon2" alt="" />
</div>
<div class="item-right one-line-ellipsis">{{ item.orgName }}</div>
<div class="item-num">{{ item.total }}</div>
<div class="item-total">{{ item.total }}</div>
<el-icon color="var(--color-primary-100)"><ArrowRightBold /></el-icon>
<div class="item-dot" v-if="item.totalRecent">+{{item.totalRecent}}</div>
</div>
<div class="item">
<div class="item-num item-more">查看全部机构 ({{govInsList.length+1}}家)</div>
<div class="organization-item">
<div class="item-more">查看全部机构 ({{govInsList.length+1}}家)</div>
<el-icon color="var(--color-primary-100)"><ArrowRightBold /></el-icon>
</div>
</div>
......@@ -198,38 +172,74 @@
<div class="center-footer">
<div class="box5">
<div class="box5-header">
<div class="box5-header-left">
<div class="box5-header-icon">
<img src="./assets/images/box3-header-icon.png" alt="" />
</div>
<div class="box5-header-title">{{ "行政令发布频度" }}</div>
<div class="box5-header-icon">
<img src="./assets/images/box3-header-icon.png" alt="" />
</div>
<div class="box5-header-title">{{ "数量变化趋势" }}</div>
<div style="margin-right: 20px;">
<el-select @change="handleBox5" v-model="box5Params.proposeName" :empty-values="[null, undefined]" style="width:150px">
<el-option label="全部政府部门" value="" />
<el-option v-for="item in keyOrganizationList" :key="item.orgId" :label="item.orgName" :value="item.orgId" />
</el-select>
</div>
<div style="margin-right: 20px;">
<el-select @change="handleBox5" v-model="box5Params.domainId" :empty-values="[null, undefined]" style="width:120px">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<div class="box5-selectbox">
<el-select @change="handleBox5YearChange" v-model="box5SelectedYear" placeholder="选择时间"
style="width: 120px">
<el-option v-for="item in box5YearList" :key="item.value" :label="item.label" :value="item.value" />
<div style="margin-right: 20px;">
<el-select @change="handleBox5" v-model="box5Params.year" placeholder="选择时间" style="width:120px">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="box5-main">
<div class="box5-chart" id="chart1"></div>
</div>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsTcon" alt="">
</div>
<div class="data-origin-text">科技政令数量变化趋势,数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="summarize1" />
</div>
</div>
<div class="box6">
<div class="box6-header">
<div class="header-icon">
<div class="box5">
<div class="box5-header">
<div class="box5-header-icon">
<img src="./assets/images/box4-header-icon.png" alt="" />
</div>
<div class="header-title">{{ "政令科技领域" }}</div>
<div class="box6-selectbox">
<el-select @change="handleBox6YearChange" v-model="box6SelectedYear" placeholder="选择时间"
style="width: 120px">
<el-option v-for="item in box6YearList" :key="item.value" :label="item.label" :value="item.value" />
<div class="box5-header-title">{{ "领域分布情况" }}</div>
<div style="margin-right: 20px;">
<el-select @change="handleBox6YearChange" v-model="box6Params.proposeName" :empty-values="[null, undefined]" style="width:150px">
<el-option label="全部政府部门" value="" />
<el-option v-for="item in keyOrganizationList" :key="item.orgId" :label="item.orgName" :value="item.orgId" />
</el-select>
</div>
<div style="margin-right: 20px;">
<el-select @change="handleBox6YearChange" v-model="box6Params.year" placeholder="选择时间" style="width: 120px">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="box5-main">
<div class="box5-chart" id="chart2"></div>
</div>
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsTcon" alt="">
</div>
<div class="data-origin-text">科技政领领域分布情况,数据来源:美国各行政机构官网</div>
</div>
<div class="ai-pane">
<AiButton />
<AiPane :aiContent="summarize2" />
</div>
<div class="box6-main" id="chart2"></div>
</div>
</div>
<div class="center-footer1">
......@@ -238,7 +248,24 @@
<div class="header-icon">
<img src="./assets/images/box5-header-icon.png" alt="" />
</div>
<div class="header-title">{{ "关键行政令" }}</div>
<div class="header-title">{{ "关键科技政令" }}</div>
<div style="margin-right: 20px;">
<el-select @change="handleGetKeyDecree" v-model="box7Params.proposeName" :empty-values="[null, undefined]" style="width:150px">
<el-option label="全部政府部门" value="" />
<el-option v-for="item in keyOrganizationList" :key="item.orgId" :label="item.orgName" :value="item.orgId" />
</el-select>
</div>
<div style="margin-right: 20px;">
<el-select @change="handleGetKeyDecree" v-model="box7Params.domainId" :empty-values="[null, undefined]" style="width:120px">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<div style="margin-right: 20px;">
<el-select @change="handleGetKeyDecree" v-model="box7Params.year" placeholder="选择时间" style="width:120px">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="box7-main">
<div class="box7-list">
......@@ -248,14 +275,20 @@
</div>
<div class="info">
<div class="info-header">
<div class="title">{{ item.title }}</div>
<div class="title one-line-ellipsis">{{ item.title }}</div>
<div class="time">{{ item.time }}</div>
</div>
<div class="info-content">{{ item.content ? item.content : "暂无数据" }}</div>
<div class="info-content one-line-ellipsis">{{ item.content || "暂无数据" }}</div>
</div>
</div>
</div>
<SimplePagination v-model:current-page="keyDecreeInfo.page" :page-size="keyDecreeInfo.size" :total="keyDecreeInfo.total" @page-change="handleGetKeyDecree" />
</div>
<SimplePagination v-model:current-page="keyDecreeInfo.page" :page-size="keyDecreeInfo.size" :total="keyDecreeInfo.total" @page-change="handleGetKeyDecree" />
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsTcon" alt="">
</div>
<div class="data-origin-text">关键科技政领列表,数据来源:美国各行政机构官网</div>
</div>
</div>
<div class="box8">
......@@ -263,10 +296,33 @@
<div class="header-icon">
<img src="./assets/images/box5-header-icon.png" alt="" />
</div>
<div class="header-title">{{ "政令重点条款" }}</div>
<div class="header-title">{{ "关键条款词云" }}</div>
<div style="margin-right: 20px;">
<el-select @change="handleGetDecreeKeyInstruction" v-model="box8Params.proposeName" :empty-values="[null, undefined]" style="width:150px">
<el-option label="全部政府部门" value="" />
<el-option v-for="item in keyOrganizationList" :key="item.orgId" :label="item.orgName" :value="item.orgId" />
</el-select>
</div>
<div style="margin-right: 20px;">
<el-select @change="handleGetDecreeKeyInstruction" v-model="box8Params.domainId" :empty-values="[null, undefined]" style="width:120px">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<div style="margin-right: 20px;">
<el-select @change="handleGetDecreeKeyInstruction" v-model="box8Params.year" placeholder="选择时间" style="width:120px">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="box8-content">
<WordCloudChart v-if="wordCloudData?.length" :data="wordCloudData" width="100%" height="100%" />
</div>
<div class="box8-content" v-if="wordCloudData?.length">
<WordCloudChart :data="wordCloudData" width="100%" height="100%" />
<div class="data-origin-box">
<div class="data-origin-icon">
<img :src="tipsTcon" alt="">
</div>
<div class="data-origin-text">科技政领重点条款词云,数据来源:美国各行政机构官网</div>
</div>
</div>
</div>
......@@ -319,6 +375,10 @@
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox v-model="activeAreaList" label="all"
style="width: 100px" class="filter-checkbox" @change="checked => handleAreaChange('all', checked)">
{{ "全部领域" }}
</el-checkbox>
<el-checkbox v-for="area in areaList" :key="area.id" v-model="activeAreaList" :label="area.id"
style="width: 100px" class="filter-checkbox" @change="checked => handleAreaChange(area.id, checked)">
{{ area.name }}
......@@ -396,6 +456,9 @@ 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 TimeTabPane from '@/components/base/TimeTabPane/index.vue';
import AiButton from '@/components/base/Ai/AiButton/index.vue';
import AiPane from '@/components/base/Ai/AiPane/index.vue';
import {
getDepartmentList,
getLatestDecree,
......@@ -406,7 +469,8 @@ import {
getDecreeKeyInstruction,
getDecreeOrderList,
getDecreehylyList,
getDecreeTypeList
getDecreeTypeList,
getKeyOrganization,
} from "@/api/decree/home";
import { getPersonSummaryInfo } from "@/api/common/index";
import { getNews, getSocialMedia } from "@/api/general/index";
......@@ -418,21 +482,9 @@ import getPieChart from "./utils/piechart";
import setChart from "@/utils/setChart";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import tipsTcon from "./assets/images/tips-icon.png";
import { ElMessage } from "element-plus";
// 跳转行政机构主页
const handleToInstitution = item => {
window.sessionStorage.setItem("curTabName", item.orgName);
const curRoute = router.resolve({
path: "/institution",
query: {
id: item.orgId
}
});
window.open(curRoute.href, "_blank");
};
const containerRef = ref(null);
const { isShow } = useContainerScroll(containerRef);
const currentPage = ref(1);
......@@ -444,13 +496,17 @@ const handleCurrentChange = page => {
handleGetDecreeOrderList();
};
// 页面 header
// 机构列表
const govInsList = ref([]);
const checkedGovIns = ref([]);
const handleGetDepartmentList = async (event) => {
let day = 7
if (event?.time === '近一周') day = 7
if (event?.time === '近一月') day = 30
if (event?.time === '近一年') day = 365
const handleGetDepartmentList = async () => {
try {
const res = await getDepartmentList();
const res = await getDepartmentList({day});
console.log("机构列表", res);
if (res.code === 200 && res.data) {
govInsList.value = res.data;
......@@ -459,7 +515,17 @@ const handleGetDepartmentList = async () => {
console.error("获取机构列表error", error);
}
};
handleGetDepartmentList();
// 跳转行政机构主页
const handleToInstitution = item => {
window.sessionStorage.setItem("curTabName", item.orgName);
const curRoute = router.resolve({
path: "/institution",
query: {
id: item.orgId
}
});
window.open(curRoute.href, "_blank");
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
......@@ -486,16 +552,6 @@ const box1DataList = ref([
}
]);
// const curBox1Data = ref({
// id: 89,
// name: "",
// postDate: "",
// describe: null,
// imageUrl: null,
// officialUrl: null,
// industryList: null
// });
const handleGetLatestDecree = async () => {
try {
const res = await getLatestDecree();
......@@ -722,46 +778,37 @@ const handleClickPerson = async item => {
} catch (error) { }
};
// 获取最近年份列表
const currentYear = new Date().getFullYear();
const getYearList = (count = 6) => {
const yearOptions = [];
for (let i = 0; i < count; i++) {
const year = currentYear - i;
yearOptions.push({ label: year.toString(), value: year.toString() });
}
return yearOptions;
};
const yearList = getYearList();
// 行政令发布频度
const chart1Data = ref({
dataX: [],
dataY: []
});
const box5YearList = ref([
{
label: "2026",
value: "2026"
},
{
label: "2025",
value: "2025"
},
{
label: "2024",
value: "2024"
},
{
label: "2023",
value: "2023"
},
{
label: "2022",
value: "2022"
},
{
label: "2021",
value: "2021"
}
]);
const box5SelectedYear = ref("2026");
const box5Params = reactive({
year: yearList[0].value,
domainId: '',
proposeName: '',
})
const summarize1 = ref()
const handleGetDecreeYearOrder = async () => {
const params = {
year: box5SelectedYear.value
};
try {
const res = await getDecreeYearOrder(params);
let { year, domainId, proposeName } = box5Params;
const res = await getDecreeYearOrder({
year,
domainId: domainId || undefined,
orgId: proposeName || undefined
});
console.log("行政令发布频度", res);
if (res.code === 200 && res.data) {
chart1Data.value.dataX = res.data.map(item => {
......@@ -770,19 +817,52 @@ const handleGetDecreeYearOrder = async () => {
chart1Data.value.dataY = res.data.map(item => {
return item.count;
});
summarize1.value = await onChartInterpretation({type:"柱状图",name:"数量变化趋势",data:res.data})
}
} catch (error) {
console.error("行政令发布频度error", error);
}
};
const handleBox5YearChange = val => {
handleBox5();
};
// AI智能总结
const onChartInterpretation = async (text) => {
const response = await fetch('/aiAnalysis/chart_interpretation', {
method: 'POST',
headers: {
"X-API-Key": "aircasKEY19491001",
'Content-Type': 'application/json',
},
body: JSON.stringify({text}) // 把参数转为JSON字符串
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let summarize = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const content = line.substring(6);
const textMatch = content.match(/"解读":\s*"([^"]*)"/);
if (textMatch && textMatch[1]) summarize = textMatch[1];
}
}
}
return summarize
}
const handleBox5 = async () => {
await handleGetDecreeYearOrder();
let chart1 = getBarChart(chart1Data.value.dataX, chart1Data.value.dataY);
chart1.yAxis.name = "数量";
chart1.yAxis.nameTextStyle = { align: 'right' }
setChart(chart1, "chart1");
};
......@@ -799,40 +879,18 @@ const chart2Data = ref([
]);
// const colorList = ["#69B1FF", "#FFC069", "#87E8DE", "#85A5FF", "#FF7875", "#B37FEB", "#4096FF"];
const box6YearList = ref([
{
label: "2026",
value: "2026"
},
{
label: "2025",
value: "2025"
},
{
label: "2024",
value: "2024"
},
{
label: "2023",
value: "2023"
},
{
label: "2022",
value: "2022"
},
{
label: "2021",
value: "2021"
}
]);
const box6SelectedYear = ref("2026");
const box6Params = reactive({
year: yearList[0].value,
proposeName: '',
});
const summarize2 = ref()
const handleGetDecreeArea = async () => {
const params = {
year: box6SelectedYear.value
};
try {
const res = await getDecreeArea(params);
let { year, proposeName } = box6Params;
const res = await getDecreeArea({
year,
orgId: proposeName || undefined
});
console.log("政令科技领域", res);
if (res.code === 200 && res.data) {
chart2Data.value = res.data.map(item => {
......@@ -841,6 +899,7 @@ const handleGetDecreeArea = async () => {
value: item.count
};
});
summarize2.value = await onChartInterpretation({type:"环形图",name:"领域分布情况",data:res.data})
}
} catch (error) {
console.error("政令科技领域error", error);
......@@ -861,12 +920,23 @@ const keyDecreeList = ref([]);
const keyDecreeInfo = reactive({
total: 0,
page: 1,
size: 3,
size: 4,
})
const box7Params = reactive({
year: yearList[0].value,
domainId: '',
proposeName: '',
})
const handleGetKeyDecree = async () => {
try {
const res = await getKeyDecree({pageSize:keyDecreeInfo.size, pageNum:keyDecreeInfo.page-1});
let { year, domainId, proposeName } = box7Params;
const res = await getKeyDecree({
pageSize:keyDecreeInfo.size,
pageNum:keyDecreeInfo.page-1,
year,
domainId: domainId || undefined,
orgId: proposeName || undefined
});
console.log("关键行政令", res);
if (res.code === 200 && res.data?.total) {
keyDecreeInfo.total = res.data.total || 0;
......@@ -882,13 +952,21 @@ const handleGetKeyDecree = async () => {
} catch (error) { }
};
handleGetKeyDecree();
// 政令重点条款
const wordCloudData = ref([]);
const box8Params = reactive({
year: yearList[0].value,
domainId: '',
proposeName: '',
})
const handleGetDecreeKeyInstruction = async () => {
try {
const res = await getDecreeKeyInstruction();
let { year, domainId, proposeName } = box8Params;
const res = await getDecreeKeyInstruction({
year,
domainId: domainId || undefined,
orgId: proposeName || undefined
});
console.log("政令重点条款", res);
wordCloudData.value = res.data.map(item => ({name: item.clause, value: item.count}));
} catch (error) {
......@@ -952,7 +1030,7 @@ const handleChangeCheckedDecreeType = () => {
};
const pubTime = ref([
{ id: "all", name: "全时间" },
{ id: "all", name: "全时间" },
{ id: "2026", name: "2026年" },
{ id: "2025", name: "2025年" },
{ id: "2024", name: "2024年" },
......@@ -984,7 +1062,7 @@ const handlePubTimeChange = (id, checked) => {
};
const handleAreaChange = (id, checked) => {
const allIds = areaList.value.filter(item => item.id !== "all").map(item => item.id);
const allIds = areaList.value.map(item => item.id);
if (id === "all") {
activeAreaList.value = checked ? ["all", ...allIds] : [];
......@@ -1013,18 +1091,9 @@ const handleGetAreaList = async () => {
const res = await getDecreehylyList();
console.log("行业领域列表", res);
if (res.code === 200 && res.data) {
areaList.value = [
{ name: "全部领域", id: "all" },
...res.data.map(item => {
return {
name: item.name,
id: item.id
};
})
];
areaList.value = res.data;
// 设置默认全选
activeAreaList.value = ["all", ...res.data.map(item => item.id)];
console.log("areaList", areaList.value);
// 获取列表后重新查询
handleGetDecreeOrderList();
}
......@@ -1037,11 +1106,9 @@ const decreeList = ref([]);
// 修改请求方法,处理全选时不传参数的情况
const handleGetDecreeOrderList = async () => {
const p0 = checkedGovIns.value.join(",");
// 处理科技领域:如果包含 all 或全选,则 researchTypeIds 为空(不传)
let p1 = "";
const allAreaIds = areaList.value.filter(item => item.id !== "all").map(item => item.id);
const allAreaIds = areaList.value.map(item => item.id);
const selectedAreaIds = activeAreaList.value.filter(id => id !== "all");
if (!activeAreaList.value.includes("all") && selectedAreaIds.length > 0 && selectedAreaIds.length < allAreaIds.length) {
......@@ -1062,11 +1129,9 @@ const handleGetDecreeOrderList = async () => {
p2 = selectedPubTimeIds.join(",");
}
// 其他情况(包含all、长度为0、全部选中)p2保持为空,即不传years
console.log(activePubTime.value, "activePubTime.value");
const params = {
currentPage: currentPage.value,
pageSize: pageSize.value,
proposeName: p0,
researchTypeIds: p1, // 全选时不传(为空)
sortFun: isSort.value,
isCN: isChina.value ? 1 : 0,
......@@ -1130,12 +1195,27 @@ const handleSearch = () => {
window.open(curRoute.href, "_blank");
};
// 关键机构
const keyOrganizationList = ref([]);
const onKeyOrganization = async () => {
try {
const res = await getKeyOrganization();
console.log("关键机构", res);
if (res.code === 200) {
keyOrganizationList.value = res.data.map(item => ({ orgName:item.orgName, orgId:item.id }));
}
} catch (error) { }
}
onMounted(async () => {
onKeyOrganization();
handleGetDepartmentList();
handleGetNews();
handleGetDecreeTypeList();
handleGetAreaList();
handleGetDecreeOrderList();
handleBox1(); // 最新科技政令
handleGetKeyDecree();
handleBox5();
handleBox6();
handleGetDecreeKeyInstruction();
......@@ -1147,132 +1227,57 @@ onMounted(async () => {
box-shadow: none;
}
.home-wrapper {
width: 100%;
height: 100%;
position: relative;
overflow-y: hidden;
.search-header {
.ai-pane {
position: absolute;
right: 0px;
bottom: 15px;
z-index: 2;
:deep(.ai-pane-wrapper) {
display: none;
}
:deep(.ai-button-wrapper) {
display: flex;
}
&:hover {
width: 100%;
// height: 144px;
background: #fff;
overflow: hidden;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
.search-header-container {
width: 1600px;
margin: 0 auto;
padding: 16px 0px 16px 0px;
bottom: 0px;
:deep(.ai-pane-wrapper) {
display: block;
}
.home-main-header-center {
// margin-top: 20px;
// margin-left: 200px;
width: 800px;
height: 48px;
border-radius: 10px;
// box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
border: 1px solid var(--color-primary-35);
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
padding: 1px;
position: relative;
&:hover {
border: 1px solid var(--color-main-active);
}
.search {
position: absolute;
right: -1px;
top: 0px;
width: 120px;
height: 46px;
border-radius: 10px;
background: var(--color-main-active);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.search-icon {
width: 18px;
height: 18px;
img {
width: 100%;
height: 100%;
}
}
.search-text {
margin-left: 8px;
height: 22px;
color: #fff;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
}
}
:deep(.ai-button-wrapper) {
display: none;
}
}
}
.home-main-header-btn-box {
margin-top: 16px;
// margin-left: 200px;
display: flex;
gap: 16px;
.btn {
display: flex;
align-items: center;
gap: 9px;
width: 140px;
height: 36px;
border: 1px solid #aed6ff;
box-sizing: border-box;
border-radius: 24px;
background: #e7f3ff;
cursor: pointer;
position: relative;
&:hover {
background: #cae3fc;
}
.btn-text {
width: 80px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 400;
line-height: 48px;
margin-left: 24px;
text-align: center;
}
.btn-icon {
position: absolute;
top: 10px;
right: 19px;
width: 6px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
}
.data-origin-box {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 16px 0;
.data-origin-icon {
width: 16px;
height: 16px;
font-size: 0px;
margin-right: 8px;
img{
width: 100%;
height: 100%;
}
}
.scrollHomeMain {
width: 100%;
height: calc(100% - 144px);
overflow-y: auto;
.data-origin-text {
font-family: Source Han Sans CN;
font-size: 14px;
color: var(--text-primary-50-color);
}
}
.home-wrapper {
width: 100%;
height: 100%;
position: relative;
overflow-y: hidden;
.home-main {
position: relative;
......@@ -1389,14 +1394,41 @@ onMounted(async () => {
}
}
.date-box {
display: flex;
align-items: center;
width: 1600px;
margin-top: 48px;
.date-icon {
width: 16px;
height: 16px;
font-size: 0px;
margin-right: 6px;
img {
width: 100%;
height: 100%;
}
}
.date-text {
width: 20px;
flex: auto;
font-size: 18px;
line-height: 18px;
font-family: Source Han Sans CN;
color: var(--text-primary-80-color);
}
}
.home-main-header-item-box {
margin: 48px 0 64px;
margin: 20px 0 64px;
width: 1600px;
display: flex;
flex-wrap: wrap;
gap: 16px;
font-family: Microsoft YaHei;
.item {
.organization-item {
width: 20%;
flex: auto;
height: 80px;
......@@ -1410,9 +1442,8 @@ onMounted(async () => {
align-items: center;
justify-content: center;
cursor: pointer;
transition:
transform 0.3s ease,
box-shadow 0.3s ease;
transition: transform 0.3s ease, box-shadow 0.3s ease;
position: relative;
&:hover {
transform: translateY(-3px);
......@@ -1433,25 +1464,43 @@ onMounted(async () => {
width: 20px;
flex: auto;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 20px;
margin: 0 16px;
}
.item-num {
white-space: nowrap;
font-family: Microsoft YaHei;
.item-total {
font-size: 20px;
margin-right: 2px;
white-space: nowrap;
font-weight: 700;
line-height: 20px;
margin-right: 2px;
color: var(--color-primary-100);
}
.item-more {
font-size: 16px;
margin-right: 12px;
white-space: nowrap;
font-weight: 700;
line-height: 16px;
color: var(--color-primary-100);
}
.item-dot {
position: absolute;
right: -13px;
top: -10px;
padding: 0 8px;
height: 26px;
background-color: #FF4D4F;
color: white;
font-size: 16px;
line-height: 26px;
font-family: Source Han Sans CN;
border-radius: 14px;
letter-spacing: 1px;
}
}
}
......@@ -2084,693 +2133,100 @@ onMounted(async () => {
padding-bottom: 8px;
padding-left: 24px;
padding-top: 5px;
// .box4-main-item {
// margin-top: 16px;
// display: flex;
// margin-left: 21px;
// .left {
// margin-top: 5px;
// width: 36px;
// height: 36px;
// border-radius: 18px;
// overflow: hidden;
// cursor: pointer;
// img {
// width: 100%;
// height: 100%;
// }
// }
// .right {
// margin-left: 10px;
// width: 690px;
// border-radius: 4px;
// box-sizing: border-box;
// border: 1px solid rgba(231, 243, 255, 1);
// background: rgba(246, 250, 255, 1);
// padding: 10px 15px;
// .right-top {
// display: flex;
// justify-content: space-between;
// .name {
// height: 24px;
// color: rgba(59, 65, 75, 1);
// font-family: Microsoft YaHei;
// font-size: 16px;
// font-weight: 700;
// line-height: 24px;
// }
// .time {
// height: 30px;
// color: rgba(95, 101, 108, 1);
// font-family: Microsoft YaHei;
// font-size: 16px;
// font-weight: 400;
// line-height: 30px;
// }
// }
// .content {
// color: rgba(59, 65, 75, 1);
// font-family: Microsoft YaHei;
// font-size: 16px;
// font-weight: 400;
// line-height: 24px;
// }
// }
// }
}
}
}
.center-center1 {
margin: 0 auto;
margin-top: 24px;
height: 100px;
.divide3 {
width: 1600px;
border-radius: 50px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
margin: 0 auto;
margin-top: 68px;
margin-bottom: 36px;
}
.center-footer {
margin-top: 21px;
height: 460px;
display: flex;
justify-content: space-between;
box-sizing: border-box;
padding: 4px 5px;
gap: 16px;
justify-content: center;
.center1-item {
width: 351px;
height: 92px;
border-radius: 48px;
.box5 {
width: 792px;
height: 100%;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
border-radius: 10px;
position: relative;
display: flex;
cursor: pointer;
&:hover {
background: rgba(231, 243, 255, 1);
}
flex-direction: column;
.left {
margin-top: 15px;
margin-left: 39px;
width: 60px;
height: 60px;
.box5-header {
width: 100%;
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
align-items: center;
img {
width: 100%;
height: 100%;
.box5-header-icon {
margin-left: 24px;
width: 17px;
height: 17px;
img {
width: 100%;
height: 100%;
}
}
}
.right {
margin-left: 8px;
.name {
height: 31px;
margin-top: 16px;
color: rgba(59, 65, 75, 1);
.box5-header-title {
width: 20px;
flex: auto;
margin-left: 19px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 24px;
font-size: 20px;
font-weight: 700;
line-height: 31px;
}
.nameActive {
color: var(--color-main-active);
}
.time {
margin-top: 1px;
height: 30px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
}
.timeActive {
color: var(--color-main-active);
}
}
}
.center1ItemActive {
background: rgba(231, 243, 255, 1);
}
}
.center-center2 {
margin: 0 auto;
margin-top: 24px;
width: 1600px;
height: 960px;
border-radius: 4px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.center2-header {
display: flex;
justify-content: space-between;
.center2-header-left {
margin-left: 62px;
display: flex;
.left {
width: 140px;
height: 175px;
border-radius: 0px 0px 70px 70px;
background: rgba(231, 243, 255, 1);
overflow: hidden;
.icon {
width: 128px;
height: 128px;
border-radius: 75px;
margin-top: 41px;
margin-left: 6px;
img {
width: 100%;
height: 100%;
}
}
}
.right {
margin-left: 29px;
.right-item1 {
margin-top: 41px;
height: 31px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 24px;
font-weight: 700;
line-height: 32px;
}
.right-item2 {
display: flex;
margin-top: 7px;
.icon {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 8px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 24px;
}
}
.right-item3 {
margin-top: 10px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 24px;
}
.right-item4 {
margin-top: 8px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
line-height: 24px;
}
}
}
.center2-header-right {
margin-right: 59px;
margin-top: 47px;
.top {
display: flex;
.top-box {
width: 180px;
height: 72px;
margin-left: 50px;
.item1 {
height: 26px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
text-align: right;
}
.item2 {
margin-top: 4px;
font-family: Microsoft YaHei;
font-weight: 700;
display: flex;
justify-content: flex-end;
.item2-3 {
height: 42px;
color: var(--color-main-active);
font-size: 32px;
line-height: 42px;
}
.item2-2 {
height: 24px;
margin-top: 9px;
margin-left: 5px;
margin-right: 5px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 24px;
}
.item2-1 {
height: 42px;
color: rgba(206, 79, 81, 1);
font-size: 32px;
line-height: 42px;
}
}
}
}
.bottom {
height: 22px;
margin-top: 29px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 4px;
width: 14px;
height: 14px;
margin-right: 8px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 22px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 24px;
}
}
}
}
.center2-mid {
width: 1479px;
height: 189px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
margin: 40px auto 0;
}
.center2-footer {
display: flex;
.center2-footer-left {
width: 732px;
margin-left: 58px;
margin-top: 21px;
.header {
display: flex;
height: 48px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.icon {
margin-top: 15px;
margin-left: 20px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 20px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 48px;
}
}
.main {
width: 732px;
height: 470px;
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
padding-top: 9px;
.item {
width: 50%;
height: 80px;
display: flex;
margin-top: 8px;
.item-left {
width: 60px;
height: 60px;
margin-top: 10px;
margin-left: 16px;
img {
width: 100%;
height: 100%;
}
}
.item-right {
margin-left: 13px;
.item-right-box1 {
margin-top: 15px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 24px;
}
.item-right-box2 {
margin-top: 1px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
}
}
.center2-footer-right {
margin-left: 21px;
margin-top: 16px;
width: 730px;
.header {
height: 54px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
display: flex;
.icon {
width: 24px;
height: 24px;
margin-left: 16px;
margin-top: 16px;
img {
width: 100%;
height: 100%;
}
}
.tab-box {
margin-left: 20px;
width: 500px;
display: flex;
.tab {
height: 54px;
margin-right: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 400;
line-height: 54px;
padding: 0 2px;
box-sizing: border-box;
}
.tabActive {
font-weight: 700;
color: var(--color-main-active);
border-bottom: 3px solid var(--color-main-active);
}
}
.right {
width: 80px;
margin-left: 90px;
display: flex;
justify-content: space-between;
.btn {
width: 28px;
height: 28px;
margin-top: 13px;
img {
width: 100%;
height: 100%;
}
}
}
}
.main {
height: 475px;
margin-top: 5px;
overflow-y: hidden;
.main-item {
display: flex;
margin-top: 20px;
.id {
margin-top: 3px;
margin-left: 2px;
width: 36px;
height: 36px;
border-radius: 18px;
background: #e7f3ff;
line-height: 36px;
text-align: center;
color: var(--color-main-active);
}
.info {
margin-left: 13px;
width: 672px;
.info-header {
display: flex;
justify-content: space-between;
height: 24px;
.title {
height: 24px;
margin-top: 1px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 24px;
}
.tag {
height: 24px;
line-height: 24px;
border-radius: 4px;
padding: 0 8px;
background: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
}
}
}
}
}
.divide3 {
width: 1600px;
margin: 0 auto;
margin-top: 68px;
margin-bottom: 36px;
}
.center-footer {
margin-top: 21px;
height: 452px;
display: flex;
justify-content: center;
.box5 {
width: 792px;
height: 452px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
border-radius: 10px;
.box5-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
.box5-header-left {
display: flex;
.box5-header-icon {
margin-top: 17px;
margin-left: 24px;
width: 17px;
height: 17px;
img {
width: 100%;
height: 100%;
}
}
.box5-header-title {
margin-top: 11px;
margin-left: 19px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.box5-selectbox {
margin-right: 20px;
margin-top: 8px;
line-height: 26px;
}
}
.box5-main {
height: 397px;
flex: auto;
height: 20px;
.box5-chart {
height: 397px;
}
}
}
.box6 {
margin-left: 16px;
width: 792px;
height: 452px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
border-radius: 10px;
.box6-header {
margin: 0 auto;
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
position: relative;
.header-icon {
margin-top: 16px;
margin-left: 22px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
margin-top: 11px;
margin-left: 18px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.box6-selectbox {
position: absolute;
right: 20px;
top: 8px;
height: 100%;
}
}
.box6-main {
margin-top: 8px;
height: 390px;
}
}
}
.center-footer1 {
margin-top: 21px;
height: 450px;
height: 460px;
display: flex;
justify-content: center;
.box7 {
width: 792px;
height: 450px;
height: 100%;
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;
.box7-header {
width: 792px;
height: 48px;
display: flex;
align-items: center;
border-bottom: 1px solid rgba(240, 242, 244, 1);
.header-icon {
width: 22px;
height: 20px;
margin-left: 25px;
margin-top: 15px;
img {
width: 100%;
height: 100%;
......@@ -2778,8 +2234,9 @@ onMounted(async () => {
}
.header-title {
width: 20px;
flex: auto;
height: 26px;
margin-top: 11px;
margin-left: 13px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
......@@ -2790,27 +2247,23 @@ onMounted(async () => {
}
.box7-main {
margin-top: 10px;
height: 20px;
flex: auto;
.box7-list {
height: 310px;
padding: 10px 24px 0;
.box7-item {
width: 730px;
margin-top: 16px;
margin-left: 25px;
display: flex;
padding: 10px 0;
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
}
.icon {
margin-top: 6px;
margin-top: 8px;
width: 24px;
height: 22px;
font-size: 0px;
img {
width: 100%;
height: 100%;
......@@ -2819,8 +2272,8 @@ onMounted(async () => {
.info {
margin-left: 13px;
width: 100%;
width: 20px;
flex: auto;
.info-header {
height: 24px;
display: flex;
......@@ -2835,9 +2288,6 @@ onMounted(async () => {
font-size: 18px;
font-weight: 700;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
......@@ -2855,30 +2305,27 @@ onMounted(async () => {
}
.info-content {
min-height: 24px;
max-height: 48px;
height: 24px;
margin-top: 6px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
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;
}
}
}
}
}
:deep(.simple-pagination) {
padding: 0;
}
}
.box8 {
margin-left: 16px;
width: 792px;
height: 450px;
height: 100%;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
......@@ -2889,14 +2336,13 @@ onMounted(async () => {
width: 100%;
height: 48px;
display: flex;
align-items: center;
border-bottom: 1px solid rgba(240, 242, 244, 1);
.header-icon {
width: 22px;
height: 20px;
margin-left: 25px;
margin-top: 15px;
img {
width: 100%;
height: 100%;
......@@ -2904,8 +2350,9 @@ onMounted(async () => {
}
.header-title {
width: 20px;
flex: auto;
height: 26px;
margin-top: 11px;
margin-left: 13px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
......@@ -2920,10 +2367,6 @@ onMounted(async () => {
height: 20px;
flex: auto;
}
.box8-main {
height: 401px;
}
}
}
}
......
......@@ -3,7 +3,7 @@
<div class="box1">
<AnalysisBox title="相关政令" :showAllBtn="false">
<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>
<div class="left-item" :class="{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)">
<div class="item-head">
......@@ -18,9 +18,9 @@
</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>
<el-empty v-if="!siderList?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div class="box2-main" v-if="graphData.nodes?.length">
<GraphChart :nodes="graphData.nodes" :links="graphData.links" layoutType="force" @handleClickNode="handleClickNode" />
</div>
</AnalysisBox>
</div>
......@@ -48,12 +48,13 @@
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import { ref, onMounted, onBeforeUnmount, reactive } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import * as G6 from '@antv/g6';
import { getDecreeRelatedOrder } from "@/api/decree/deepdig";
import { getDecreeSummary } from "@/api/decree/introduction";
import GraphChart from "@/components/base/GraphChart/index.vue";
import icon1628 from "./assets/icons/icon1628.png";
import icon1629 from "./assets/icons/icon1629.png";
......@@ -64,7 +65,7 @@ const route = useRoute();
const dialogVisible = ref(false);
// 基本信息
const mainInfo = ref({});
const mainInfo = ref({ label: "", time: "", id: "" });
const nodeInfo = ref({});
const onDecreeSummaryData = async () => {
try {
......@@ -74,10 +75,9 @@ const onDecreeSummaryData = async () => {
mainInfo.value.label = res.data.name;
mainInfo.value.time = res.data.postDate;
mainInfo.value.id = route.query.id;
mainInfo.value.isCenter = true
}
} catch (error) {
mainInfo.value = {};
mainInfo.value = { label: "", time: "", id: "" };
console.log("获取基本信息数据失败:", error);
}
};
......@@ -109,8 +109,53 @@ const handleGetRelateOrder = async () => {
};
// 政令关系挖掘
const containerRef = ref();
let graphInstance = null;
const graphData = reactive({
nodes: [],
links: [],
})
// 节点点击处理
const handleClickNode = ({data}) => {
if (data.target) {
let node = siderList.value.find(item => item.id==data.target)
if (node) handleClickSider(node)
} else {
let node = siderList.value.find(item => item.id==data.id)
if (node) handleClickDecree(node)
}
}
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))
}
})
}
const onFormatLink = (item, index) => {
return {
id: `link-${index+1}`,
source: route.query.id, target: item.id+'',
label: { show: true, color: "#055fc2", backgroundColor: "#eef7ff", borderWidth: 0, offset: [0, 15], formatter: item.relation },
lineStyle: { color: '#B9DCFF', type: "solid", opacity: 1 }
}
}
const onFormatNode = (item) => {
let leader = item.id == mainInfo.value.id;
return {
id: item.id+'',
name: onWordWrap(item.label, 8),
label: {
show: true,
color: leader ? "#055fc2" : "#3b414b",
fontSize: leader ? 16 : 14,
fontWeight: leader ? 700 : 400,
fontFamily: 'Source Han Sans CN',
},
symbolSize: leader ? 60 : 40,
symbol: `image://${leader ? icon1628 : icon1629}`
}
}
// 文本插入换行符
const onWordWrap = (word, num) => {
const list = word.split('');
......@@ -123,132 +168,6 @@ const onWordWrap = (word, num) => {
}
return label;
}
const onFormatNode = (item) => {
let isCenter = item.isCenter || false
return {
id: item.id+'', label:onWordWrap(item.label, 15), isCenter,
img: isCenter ? icon1628 : icon1629,
clipCfg: { r: isCenter ? 40 : 30 },
labelCfg: {
style: {
fill: isCenter ? "#1459BB" : "#333333",
fontSize: isCenter ? 13 : 13,
fontWeight: isCenter ? "bold" : "normal"
}
}
}
}
const onFormatEdge = (item, index) => {
return {
id: `edge-${index+1}`,
target: item.id+'',
source: route.query.id,
// label: ["", "相似", "继承", "冲突"][1],
label: item.relation,
style: {
stroke: ["", "#B9DCFF", "#87E8DE", "#FFCCC7"][1],
},
labelCfg: {
style: {
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][1],
background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][1],
}
}
}
}
}
const initChart = () => {
let edgeList = siderList.value.map(onFormatEdge)
let nodeList = siderList.value.map(onFormatNode)
nodeList.unshift(onFormatNode(mainInfo.value))
console.log(nodeList)
const width = containerRef.value.offsetWidth || 800
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] }
}
}
}
})
// 节点点击处理
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 => {
window.sessionStorage.setItem("curTabName", decree.name);
......@@ -293,9 +212,9 @@ const onRelationChart = () => {
},
labelCfg: {
style: {
fill: ["", "#055FC2", "#13A8A8", "#CE4F51"][1],
fill: ["", "#055fc2", "#13A8A8", "#CE4F51"][1],
background: {
fill: ["", "#E7F3FF", "#E6FFFB", "#FFE0E0"][1],
fill: ["", "#eef7ff", "#E6FFFB", "#FFE0E0"][1],
}
}
}
......@@ -363,13 +282,10 @@ const onRelationChart = () => {
}
onMounted(() => {
Promise.all([onDecreeSummaryData(), handleGetRelateOrder()]).then(() => {
if (mainInfo.value.id && siderList.value.length) initChart()
})
initGraphChart()
});
onBeforeUnmount(() => {
graphInstance?.destroy()
graph?.destroy()
})
</script>
......@@ -462,10 +378,6 @@ onBeforeUnmount(() => {
.box2-main {
height: 100%;
padding: 10px;
.graph-container {
width: 100%;
height: 600px;
}
}
}
}
......
<template>
<div class="view-box">
<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' }">
<el-empty v-if="!listData?.length" style="padding: 60px 0;" description="暂无数据" :image-size="100" />
<div v-if="listData.length" class="main-content-main"
@wheel.prevent="handleWheel"
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
@mouseleave="handleMouseUp"
@mousemove="handleMouseMove"
>
<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,
'green-theme': index >= 2 && index < 4,
'purple-theme': index >= 4
}" :style="{ left: index * 200 + 220 + 'px' }">
{{ item.text }}
<div class="main-line" :style="{ width: listData.length * 200 + 300 + 'px' }">
<div v-for="(item, index) in listData" :key="item.id" class="main-line-text" :class="getThemeClass(item.level)" :style="{ left: index * 200 + 220 + 'px' }">
{{ item.name }}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div v-for="(causeGroup, groupIndex) in onFilterData(1)" :key="groupIndex"
class="top-bone" :style="{ left: groupIndex * 400 + 510 + 'px', height: (causeGroup.causes?.length) * 22 + 100 + 'px' }">
class="top-bone" :style="{ left: groupIndex * 400 + 510 + 'px', height: (causeGroup.children?.length) * 22 + 100 + 'px' }">
<div class="left-bone">
<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 class="bone-item-box bone-item-end bone-item-top" v-for="item in getLeftItems(causeGroup.children)" :key="item.id">
<div :class="['bone-item-word', {'bone-item-back':item.back}]">
<div class="bone-item-icon">
<img :src="item.image || defaultIcon2" alt="" />
</div>
<div class="bone-item-text one-line-ellipsis" :style="{color: item.isEntity==1? '#ce4f51' : '#3b414b'}" :title="item.companyName">{{ item.companyName }}</div>
</div>
<div class="bone-item-line"></div>
</div>
</div>
<div class="right-bone">
<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 class="bone-item-box bone-item-start bone-item-top" v-for="item in getRightItems(causeGroup.children)" :key="item.id">
<div class="bone-item-line"></div>
<div :class="['bone-item-word', {'bone-item-back':item.back}]">
<div class="bone-item-icon">
<img :src="item.image || defaultIcon2" alt="" />
</div>
<div class="bone-item-text one-line-ellipsis" :style="{color: item.isEntity==1? '#ce4f51' : '#3b414b'}" :title="item.companyName">{{ item.companyName }}</div>
</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div v-for="(causeGroup, groupIndex) in onFilterData(0)" :key="groupIndex"
class="bottom-bone" :style="{ left: groupIndex * 400 + 310 + 'px', height: (causeGroup.causes?.length) * 22 + 100 + 'px' }">
class="bottom-bone" :style="{ left: groupIndex * 400 + 310 + 'px', height: (causeGroup.children?.length) * 22 + 100 + 'px' }">
<div class="left-bone">
<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 class="bone-item-box bone-item-end bone-item-down" v-for="item in getRightItems(causeGroup.children)" :key="item.id">
<div :class="['bone-item-word', {'bone-item-back':item.back}]">
<div class="bone-item-icon">
<img :src="item.image || defaultIcon2" alt="" />
</div>
<div class="bone-item-text one-line-ellipsis" :style="{color: item.isEntity==1? '#ce4f51' : '#3b414b'}" :title="item.companyName">{{ item.companyName }}</div>
</div>
<div class="bone-item-line"></div>
</div>
</div>
<div class="right-bone">
<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>
<div class="bone-item-box bone-item-start bone-item-down" v-for="item in getLeftItems(causeGroup.children)" :key="item.id">
<div class="bone-item-line"></div>
<div :class="['bone-item-word', {'bone-item-back':item.back}]">
<div class="bone-item-icon">
<img :src="item.image || defaultIcon2" alt="" />
</div>
<div class="bone-item-text one-line-ellipsis" :style="{color: item.isEntity==1? '#ce4f51' : '#3b414b'}" :title="item.companyName">{{ item.companyName }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="dataList.length" class="main-content-footer">
<div class="footer-item footer-item1">
<div v-if="listData.length" class="main-content-footer">
<div v-for="(item, index) in props.baseData" :key="index" class="footer-item">
<div class="footer-item-bottom">
<div class="icon">
<img :src="noticeIcon" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.upstreamInternalRate)}%),受制裁${cnEntityOnChainData.upstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.upstreamEntityRate)}%)`
`中国企业${item.isChinaCount}家(${formatRate(item, 'isChinaCount')}%),受制裁${item.sanCount}家(${formatRate(item, 'sanCount')}%)`
}}
</div>
</div>
<div class="footer-item-top">{{ "上游" }}</div>
</div>
<div class="footer-item footer-item2">
<div class="footer-item-bottom">
<div class="icon">
<img :src="noticeIcon" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.midstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.midstreamInternalRate)}%),受制裁${cnEntityOnChainData.midstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.midstreamEntityRate)}%)`
}}
</div>
</div>
<div class="footer-item-top">{{ "中游" }}</div>
</div>
<div class="footer-item footer-item3">
<div class="footer-item-bottom">
<div class="icon">
<img :src="noticeIcon" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.downstreamInternalRate)}%),受制裁${cnEntityOnChainData.downstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.downstreamEntityRate)}%)`
}}
</div>
</div>
<div class="footer-item-top">{{ "下游" }}</div>
<div class="footer-item-top" :class="getThemeClass(item.name)">{{ item.name }}</div>
</div>
</div>
</div>
</template>
<script setup name="ChartChain">
import { ref, onMounted } from "vue";
import { ref } from "vue";
import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "../assets/images/notice-icon.png";
import { getDeepMiningSelect, getDeepMiningIndustry, getDeepMiningIndustryFishbone, getDeepMiningIndustryEntity } from "@/api/exportControlV2.0";
// 缩放功能处理
const props = defineProps({
baseData: {
type: Object,
default: () => ([])
},
listData: {
type: Array,
default: () => ([])
}
});
const getThemeClass = (name) => {
if (name=="上游") return "blue-theme";
if (name=="中游") return "green-theme";
if (name=="下游") return "purple-theme";
}
// #region 缩放功能处理
const scale = ref(1)
const minScale = 0.1
const maxScale = 10
......@@ -128,7 +122,9 @@ const handleWheel = (e) => {
scale.value = Math.max(scale.value - 0.1, minScale)
}
}
// 移动功能处理
// #endregion 缩放功能处理
// #region 移动功能处理
const translateX = ref(0) // X轴位移
const translateY = ref(0) // Y轴位移
let isDragging = false
......@@ -149,40 +145,11 @@ const handleMouseDown = (e) => {
const handleMouseUp = () => {
isDragging = false
}
// #endregion 移动功能处理
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const cnEntityOnChainData = ref({});
const getCnEntityOnChainData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : '';
// 确保 date 格式正确
const formattedDate = date && date.includes('年') ? date.replace('年', '-').replace('月', '-').replace('日', '') : date;
const params = {
date: formattedDate
};
if (selectedIndustryId.value) {
params.chainId = selectedIndustryId.value;
}
try {
const res = await getDeepMiningIndustryEntity(params);
console.log("企业信息", res)
if (res.code === 200 && res.data) {
cnEntityOnChainData.value = res.data;
} else {
cnEntityOnChainData.value = {};
}
} catch (error) {
console.error("获取产业链中国企业实体信息失败:", error);
cnEntityOnChainData.value = {};
}
}
// 产业链鱼骨数据
const dataList = ref([]);
// 奇数索引的数据组放在上方, 偶数索引的数据组放在下方
const onFilterData = (num) => {
return dataList.value.filter((_, index) => index % 2 === num);
return props.listData.filter((_, index) => index % 2 === num);
};
// 获取左侧显示的项目(前半部分)
const getLeftItems = items => {
......@@ -194,122 +161,11 @@ const getRightItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(midpoint);
};
const getFishboneData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : '';
// 确保 date 格式正确
const formattedDate = date && date.includes('年') ? date.replace('年', '-').replace('月', '-').replace('日', '') : date;
const params = {
date: formattedDate
};
if (selectedIndustryId.value) {
params.chainId = selectedIndustryId.value;
}
try {
const res = await getDeepMiningIndustryFishbone(params);
console.log("获取产业链数据:", res);
if (res.code === 200 && res.data?.causes?.length) {
dataList.value = res.data.causes;
} else {
dataList.value = [];
}
} catch (error) {
console.error("获取产业链鱼骨图数据失败:", error);
dataList.value = [];
}
}
// 实体清单-深度挖掘-产业链列表信息
const selectedIndustryId = ref(null);
const getIndustryList = async () => {
try {
const res = await getDeepMiningIndustry();
if (res.code === 200 && res.data && res.data.length > 0) {
selectedIndustryId.value = res.data[0].id;
getFishboneData();
getCnEntityOnChainData();
} else {
selectedIndustryId.value = null;
}
} catch (error) {
console.error("获取产业链列表数据失败:", error);
selectedIndustryId.value = null;
}
}
// 获取选择制裁
const loading = ref(false);
const currentPage = ref(1);
const pageSize = ref(10000);
const getDeepMiningSelectData = async () => {
loading.value = true;
const params = {
startDate: dateRange.value && dateRange.value[0] ? dateRange.value[0] : '',
endDate: dateRange.value && dateRange.value[1] ? dateRange.value[1] : '',
typeName: "实体清单",
isCn: false,
pageNum: currentPage.value,
pageSize: pageSize.value
};
try {
const res = await getDeepMiningSelect(params);
if (res.code === 200 && res.data && res.data.content) {
sanctionList.value = res.data.content.map(item => ({
id: item.id,
date: item.postDate,
title: item.name,
count: item.cnEntityCount,
unit: '家中国实体', // 接口未返回单位,暂时固定
summary: item.summary, // 保留额外信息备用
techDomainList: item.techDomainList // 保留额外信息备用
}));
// 默认选中第一条
if (sanctionList.value.length > 0) {
currentSanctionId.value = sanctionList.value[0].id;
// getFishboneData(); // 这里不需要调用,因为getIndustryList会调用
}
} else {
sanctionList.value = [];
}
} catch (error) {
console.error("获取选择制裁数据失败:", error);
sanctionList.value = [];
} finally {
loading.value = false;
}
}
const dateRange = ref(["2025-01-01", "2025-12-31"]);
const sanctionList = ref([
{ id: 1, date: "2025年2月8日", title: "实体清单更新", count: 2, unit: "家中国实体" },
{ id: 2, date: "2025年4月10日", title: "实体清单更新", count: 5, unit: "家中国实体" },
{ id: 3, date: "2025年6月29日", title: "实体清单更新", count: 6, unit: "家中国实体" },
{ id: 4, date: "2025年8月12日", title: "实体清单更新", count: 24, unit: "家中国实体" },
{ id: 5, date: "2025年8月19日", title: "实体清单更新", count: 11, unit: "家中国实体" },
{ id: 6, date: "2025年9月12日", title: "实体清单更新", count: 3, unit: "家中国实体" },
{ id: 7, date: "2025年9月26日", title: "实体清单更新", count: 6, unit: "家中国实体" },
{ id: 8, date: "2025年10月12日", title: "实体清单更新", count: 18, unit: "家中国实体" }
]);
const currentSanctionId = ref(5);
// 格式化比率
const formatRate = (rate, ratio=false) => {
if (!rate) return '0.00';
return (rate * 100).toFixed(2);
const formatRate = (item, key) => {
if (!item[key] || !item.total) return "0.00"
return (item[key]*100/item.total).toFixed(2)
};
onMounted(() => {
// 获取选择制裁
getDeepMiningSelectData();
// 获取产业链信息
getIndustryList();
});
</script>
<style scoped lang="scss">
......@@ -327,14 +183,6 @@ onMounted(() => {
align-items: center;
justify-content: center;
overflow: hidden;
.main-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 3;
}
.fishbone-container {
position: relative;
......@@ -344,52 +192,25 @@ onMounted(() => {
background: rgb(230, 231, 232);
display: flex;
justify-content: space-between;
align-items: center;
// 添加中间的文字块
.main-line-text {
top: -16px;
position: absolute;
// top: -14px;
font-size: 16px;
color: #055FC2;
font-weight: bold;
background-color: #f7f8f9;
padding: 0 10px;
z-index: 2;
// 箭头背景
height: 32px;
line-height: 32px;
width: 160px;
text-align: center;
background: rgba(231, 243, 255, 1);
clip-path: polygon(0% 0%, 90% 0%, 100% 50%, 90% 100%, 0% 100%, 10% 50%);
&.blue-theme {
background: rgba(231, 243, 255, 1);
color: rgba(22, 119, 255, 1);
}
&.green-theme {
background: rgba(225, 255, 251, 1);
color: rgba(19, 168, 168, 1);
}
&.purple-theme {
background: rgba(246, 235, 255, 1);
color: rgba(146, 84, 222, 1);
}
}
}
}
.company-icon {
width: 16px;
height: 16px;
margin: 0 4px;
object-fit: contain;
}
.top-bone {
position: absolute;
bottom: 0px;
......@@ -409,31 +230,6 @@ onMounted(() => {
display: flex;
flex-direction: column;
justify-content: flex-end;
.left-bone-item {
transform: skew(-30deg);
height: 40px;
margin: 4px 0;
display: flex;
justify-content: flex-end;
align-items: center;
.text {
margin-left: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
}
}
.right-bone {
......@@ -446,32 +242,6 @@ onMounted(() => {
display: flex;
flex-direction: column;
justify-content: flex-end;
.right-bone-item {
transform: skew(-30deg);
height: 40px;
margin: 4px 0;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
margin-right: 7px;
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
}
.text {
max-width: 100px;
margin-right: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
......@@ -494,32 +264,6 @@ onMounted(() => {
display: flex;
flex-direction: column;
justify-content: flex-start;
.left-bone-item {
transform: skew(30deg);
height: 40px;
margin: 4px 0;
display: flex;
justify-content: flex-end;
align-items: center;
.text {
margin-left: 4px;
height: 25px;
max-width: 130px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
}
}
.right-bone {
......@@ -532,34 +276,71 @@ onMounted(() => {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
}
.right-bone-item {
transform: skew(30deg);
height: 40px;
margin: 4px 0;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
margin-right: 7px;
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
.bone-item-box {
transform: skew(30deg);
height: 40px;
margin: 4px 0;
display: flex;
align-items: center;
.bone-item-line {
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
}
.bone-item-word {
display: flex;
align-items: center;
height: 30px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 8px;
.bone-item-icon {
width: 16px;
height: 16px;
font-size: 0px;
position: relative;
&::after {
content: "";
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
}
.text {
max-width: 100px;
margin-right: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.bone-item-text {
max-width: 100px;
line-height: 14px;
font-size: 14px;
}
}
}
.bone-item-back {
background-color: #FFF1F0;
border: 1px solid var(--color-red-100);
border-radius: 4px;
}
.bone-item-start {
justify-content: flex-start;
}
.bone-item-end {
justify-content: flex-end;
}
.bone-item-top {
transform: skew(-30deg);
}
.bone-item-down {
transform: skew(30deg);
}
}
.main-content-footer {
......@@ -609,25 +390,19 @@ onMounted(() => {
}
}
}
}
.footer-item1 {
color: rgba(22, 119, 255, 1);
.footer-item-top {
background: rgba(231, 243, 255, 1);
}
}
.footer-item2 {
color: rgba(19, 168, 168, 1);
.footer-item-top {
background: rgba(225, 255, 251, 1);
}
}
.footer-item3 {
color: rgba(146, 84, 222, 1);
.footer-item-top {
background: rgba(246, 235, 255, 1);
}
}
.blue-theme {
background: rgba(231, 243, 255, 1);
color: rgba(22, 119, 255, 1);
}
.green-theme {
background: rgba(225, 255, 251, 1);
color: rgba(19, 168, 168, 1);
}
.purple-theme {
background: rgba(246, 235, 255, 1);
color: rgba(146, 84, 222, 1);
}
}
</style>
\ No newline at end of file
<template>
<div class="fishbone-wrapper">
<div class="fishbone-scroll-container" ref="scrollContainerRef">
<div class="fishbone" ref="fishboneRef" v-if="fishboneData.length > 0">
<div class="main-line" :style="{ width: (fishboneData.length / 2) * 340 - 200 + 'px' }"></div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for="(causeGroup, groupIndex) in getOddGroups(fishboneData)"
:key="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)"
:style="{ left: groupIndex * 300 + 400 + 'px' }"
>
<div class="left-bone">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-' + index"
>
<div class="text">{{ 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="line"></div>
<div class="text">{{ item.name }}</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div
v-for="(causeGroup, groupIndex) in getEvenGroups(fishboneData)"
:key="'bottom-' + groupIndex"
:class="getBottomBoneClass(groupIndex)"
:style="{ left: groupIndex * 300 + 200 + 'px' }"
>
<div class="left-bone">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-bottom-' + index"
>
<div class="text">{{ 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="line"></div>
<div class="text">{{ item.name }}</div>
</div>
</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 class="scroll-indicators" v-if="showScrollIndicator">
<div class="scroll-btn left" :class="{ disabled: !canScrollLeft }" @click="scrollLeft">‹</div>
<div class="scroll-btn right" :class="{ disabled: !canScrollRight }" @click="scrollRight">›</div>
</div> -->
</div>
</template>
<script setup>
import { getChainFishbone } from "@/api/exportControl";
import { onMounted, ref, nextTick, watch } from "vue";
// 这儿需要接收父组件传递来的产业链ID
const props = defineProps({
chainId: {
type: Number,
default: 1
}
});
// const chainId = ref(1);
const fishboneData = ref([]);
const scrollContainerRef = ref(null);
const fishboneRef = ref(null);
const showScrollIndicator = ref(false);
const canScrollLeft = ref(false);
const canScrollRight = ref(true);
// 获取奇数索引的数据组(放在上方)
const getOddGroups = data => {
console.log(
"getOddGroups:",
data.filter((_, index) => index % 2 === 1)
);
return data.filter((_, index) => index % 2 === 1);
};
// 获取偶数索引的数据组(放在下方)
const getEvenGroups = data => {
console.log(
"getEvenGroups:",
data.filter((_, index) => index % 2 === 0)
);
return data.filter((_, index) => index % 2 === 0);
};
// 获取上方鱼骨图位置类名
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 getLeftItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(0, midpoint);
};
// 获取右侧显示的项目(后半部分)
const getRightItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(midpoint);
};
// 检查滚动状态
const updateScrollState = () => {
if (!scrollContainerRef.value) return;
const container = scrollContainerRef.value;
canScrollLeft.value = container.scrollLeft > 0;
canScrollRight.value = container.scrollLeft < container.scrollWidth - container.clientWidth;
};
// 滚动处理
const scrollLeft = () => {
if (scrollContainerRef.value) {
scrollContainerRef.value.scrollBy({ left: -200, behavior: "smooth" });
}
};
const scrollRight = () => {
if (scrollContainerRef.value) {
scrollContainerRef.value.scrollBy({ left: 200, behavior: "smooth" });
}
};
// 处理滚动事件
const handleScroll = () => {
updateScrollState();
};
onMounted(async () => {
// try {
// const chainFishboneData = await getChainFishbone(props.chainId);
// fishboneData.value = chainFishboneData?.causes ?? [];
// // 等待DOM更新后检查是否需要滚动
// nextTick(() => {
// if (scrollContainerRef.value && fishboneRef.value) {
// showScrollIndicator.value = fishboneRef.value.scrollWidth > scrollContainerRef.value.clientWidth;
// updateScrollState();
// }
// });
// console.log("鱼骨图数据:", fishboneData.value);
// } catch (error) {
// console.log(error);
// }
});
// 监听props中的chainId变化
watch(
() => props.chainId,
async () => {
try {
const chainFishboneData = await getChainFishbone(props.chainId);
fishboneData.value = chainFishboneData?.causes ?? [];
} catch (error) {
console.log(error);
}
}
);
</script>
<style lang="scss" scoped>
.fishbone-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.fishbone-scroll-container {
display: flex;
align-items: center;
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
scrollbar-color: rgba(144, 202, 249, 0.5) transparent;
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 202, 249, 0.5);
border-radius: 3px;
}
}
/* ... 原有的样式保持不变 ... */
.fishbone {
position: relative;
width: fit-content;
height: 100%;
margin-top: 40px;
min-width: 100%;
padding-left: 275px;
.main-line {
margin-top: 280px;
width: 1888px;
height: 3px;
background: rgba(174, 208, 255, 1);
}
}
.top-bone {
position: absolute;
top: 20px;
right: 200px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
margin-left: 4px;
height: 70px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.top-bone1 {
position: absolute;
top: 20px;
right: 500px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.top-bone2 {
position: absolute;
top: 20px;
right: 800px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bone {
position: absolute;
top: 280px;
right: 360px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(-30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bone1 {
position: absolute;
top: 280px;
right: 660px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(-30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bone2 {
position: absolute;
top: 280px;
right: 960px;
width: 3px;
height: 260px;
background: #90caf9;
transform: skew(-30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 15px;
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
width: 30px;
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.scroll-indicators {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
pointer-events: none;
padding: 0 10px;
z-index: 10;
}
.scroll-btn {
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
color: #90caf9;
cursor: pointer;
pointer-events: auto;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
transition: all 0.2s ease;
&:hover:not(.disabled) {
background: #90caf9;
color: white;
transform: scale(1.1);
}
&.disabled {
color: #c0c4cc;
cursor: not-allowed;
background: rgba(255, 255, 255, 0.5);
}
}
</style>
......@@ -5,25 +5,25 @@
<div class="box1-main">
<div class="data-filter">
<div class="filter-select">
<el-select v-model="curAreaId" :empty-values="[null, undefined]" style="width: 100%">
<el-select v-model="areaInfo.id" :empty-values="[null, undefined]" @change="onDecreeEntities()" style="width:100%">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
<el-option v-for="item in areaInfo.list" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<div class="filter-input">
<el-input v-model="commandWord" @keyup.enter="handleSearch" style="width: 100%; height: 100%;" :suffix-icon="Search" placeholder="搜索实体" />
<el-input v-model="entityInfo.keyword" @keyup.enter="onDecreeEntities()" :suffix-icon="Search" placeholder="搜索实体" />
</div>
</div>
<div class="data-title">实体名称</div>
<div style="height: 20px; flex: auto;">
<el-empty v-if="!showCompanyList?.length" style="padding-top: 35%;" description="暂无数据" :image-size="100" />
<el-empty v-if="!entityInfo.list?.length" style="padding: 60px 0;" 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)">
<div class="list-item" v-for="item in entityInfo.list" :key="item.id" :class="{ 'item-active': entityInfo.id==item.id }" @click="headerChartData(item)">
<div class="item-icon">
<img :src="defaultIcon2" alt="" class="item-img" />
</div>
<div class="item-name one-line-ellipsis">{{ item.name }}</div>
<div class="item-name one-line-ellipsis">{{ item.companyName }}</div>
<div class="item-icon item-icon-tag">
<img :src="noticeIcon" alt="" class="item-img" />
</div>
......@@ -33,16 +33,16 @@
</el-scrollbar>
</div>
<div class="pagination-info">
<div class="pagination-left">{{ `共 ${companyTotalNum} 家企业` }}</div>
<div class="pagination-left">{{ `共 ${entityInfo.total} 家企业` }}</div>
<div class="pagination-right">
<el-pagination
@current-change="handleCurrentChange"
:pageSize="pageSize"
:current-page="currentPage"
@current-change="onDecreeEntities"
:pageSize="entityInfo.pageSize"
:current-page="entityInfo.pageNum"
background
layout="prev, pager, next"
size="small"
:total="companyTotalNum"
:total="entityInfo.total"
/>
</div>
</div>
......@@ -68,9 +68,8 @@
</div>
</div>
<div class="title-right" v-if="contentType==1">
<el-select v-model="curAreaId" :empty-values="[null, undefined]" style="width: 100%">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
<el-select v-model="industryChain.id" style="width: 100%" @change="onDecreeChainNodes">
<el-option v-for="item in industryChain.list" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
</div>
......@@ -78,10 +77,10 @@
<div class="box2-main">
<AiTips :tips="tips" />
<div class="graph-box" v-if="contentType==1">
<ChartChain />
<ChartChain :listData="fishbone.list" :baseData="fishbone.base" />
</div>
<div class="graph-box" v-if="contentType==2">
<GraphChart :nodes="testData.nodes" :links="testData.links" layoutType="force" />
<div class="graph-box" v-if="contentType==2 && graphInfo.nodes.length">
<GraphChart :nodes="graphInfo.nodes" :links="graphInfo.links" layoutType="force" />
</div>
</div>
</AnalysisBox>
......@@ -90,13 +89,16 @@
</template>
<script setup>
import { ref, onMounted } from "vue";
import setChart from "@/utils/setChart";
import { ref, onMounted, reactive } from "vue";
import { useRoute } from "vue-router";
import { Search } from '@element-plus/icons-vue'
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 {
getDecreehylyList,
getDecreeEntities,
getDecreeRelatedChain,
getDecreeChainNodes,
getDecreeRelatedEntitie
} from "@/api/decree/influence";
import ChartChain from "./com/ChartChain.vue";
import AiTips from "./com/AiTips.vue";
import GraphChart from "@/components/base/GraphChart/index.vue";
......@@ -107,319 +109,197 @@ import icon423 from "./assets/images/icon423.png";
import icon1620 from "./assets/images/icon1620.png";
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}` },
],
const route = useRoute();
// 关系数据
links: [
{
source: 1, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 2, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 3, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 4, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '从属' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 5, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 6, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 7, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
{
source: 8, target: 0,
label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
lineStyle: { color: '#B9DCFF', type: "solid" }
},
],
};
const tips = "这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。"
// 受影响实体
const companyList = ref([]);
const activeEntityId = ref(1);
const currentPage = ref(1);
const pageSize = ref(10);
const handleToCompanyDetail = (item) => {
activeEntityId.value = item.id;
};
const handleCurrentChange = page => {
currentPage.value = page;
};
// const showCompanyList = computed(() => {
// const startIndex = (currentPage.value - 1) * pageSize.value;
// const endIndex = startIndex + pageSize.value;
// return companyList.value.slice(startIndex, endIndex);
// });
const showCompanyList = ref([
{ id: 1, name: "北京市", status: "上市" },
{ id: 2, name: "上海市", status: "上市" },
{ id: 3, name: "广州市广州市广州市广州市广州市广州市广州市广州市", status: "上市" },
{ id: 4, name: "深圳市", status: "上市" },
{ id: 5, name: "成都市", status: "上市" },
{ id: 7, name: "天津市", status: "上市" },
{ id: 9, name: "武汉市", status: "上市" },
{ id: 10, name: "西安市", status: "上市" },
]);
const handleGetCompanyListByArea = async () => {
const params = {
id: curAreaId.value
};
// 行业领域
const areaInfo = reactive({
list: [],
id: "",
})
const handleGetHylyList = async () => {
try {
const res = await getDecreeCompany(params);
console.log("行业领域公司列表", res);
if (res.code === 200 && res.data) {
companyList.value = res.data.map(item => {
return {
name: item.name,
id: item.id,
status: item.marketChange
};
});
companyTotalNum.value = companyList.value.length;
if (res.data?.length) handleToCompanyDetail(res.data[0])
} else {
companyList.value = [];
companyTotalNum.value = 0;
const res = await getDecreehylyList();
console.log("行业领域:", res);
if (res.code === 200) {
areaInfo.list = res.data || [];
}
} catch (error) {
companyList.value = [];
companyTotalNum.value = 0;
areaInfo.list = [];
}
};
// 指令搜索
const commandWord = ref("");
const handleSearch = () => {
};
// 行业领域
const curAreaId = ref("");
const areaList = ref([]);
const handleGetHylyList = async () => {
// 受影响实体
const entityInfo = reactive({
keyword: "",
pageSize: 10,
pageNum: 1,
total: 0,
list: [],
id: '',
node: {id: '', companyName: ''},
})
const onDecreeEntities = async (page=1) => {
entityInfo.pageNum = page;
try {
const res = await getDecreehylyList();
console.log("行业领域列表", res);
if (res.code === 200 && res.data) {
areaList.value = res.data;
handleGetChainId();
let params = {
id: route.query.id,
pageSize: entityInfo.pageSize,
pageNum: entityInfo.pageNum - 1,
keyword: entityInfo.keyword,
domainId: areaInfo.id || undefined
}
const res = await getDecreeEntities(params);
console.log("受影响实体:", res);
if (res.code === 200) {
entityInfo.list = res.data.companyInfos;
entityInfo.total = res.data.total;
if (entityInfo.total && entityInfo.list.every(item => item.id!=entityInfo.id)) {
headerChartData(entityInfo.list[0])
}
}
} catch (error) {}
} catch (error) {
console.log("获取受影响实体失败", error);
}
};
// 产业链/实体关系
const contentType = ref(1);
const headerContentType = (type) => {
contentType.value = type;
headerChartData(entityInfo.node)
};
const chainId = ref(0);
const handleGetChainId = async () => {
try {
const res = await getChainInfoByDomainId(curAreaId.value);
console.log("获取chainId", res);
if (res && res.length) {
chainId.value = res[0].id;
// handleGetChainInfoByChainId();
}
} catch (error) {
console.error("chainId error", error);
const headerChartData = (row) => {
entityInfo.id = row.id;
entityInfo.node = row;
industryChain.id = "";
fishbone.list = []
fishbone.base = []
graphInfo.nodes = [];
graphInfo.links = [];
switch (contentType.value) {
case 1:
onDecreeRelatedChain(row.id)
break;
case 2:
onDecreeRelatedEntitie(row.id)
break;
}
};
const handleNodeClick = (node) => {
};
const handleLayoutChange = (type) => {
};
const treeData = ref(null);
const graphData = ref({ nodes: [], links: [] });
const singleSanctionEntitySupplyChainData = ref(null);
const updateGraphData = () => {
const data = singleSanctionEntitySupplyChainData.value;
if (!data) return;
const nodes = [];
const links = [];
nodes.push({
id: "0",
name: data.orgName,
image: companyActive,
symbolSize: 60,
isSanctioned: true
});
const parentList = data.parentOrgList || [];
parentList.forEach((item, index) => {
nodes.push({
id: `p-${item.id || index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 40,
isSanctioned: item.isSanctioned
});
links.push({
source: `p-${item.id || index}`,
target: "0",
name: "供应商"
});
});
const childList = data.childrenOrgList || [];
childList.forEach((item, index) => {
nodes.push({
id: `c-${item.id || index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 40,
isSanctioned: item.isSanctioned
});
}
links.push({
source: "0",
target: `c-${item.id || index}`,
name: "客户"
});
});
graphData.value = { nodes, links };
};
const getSingleSanctionEntitySupplyChainRequest = async () => {
// 产业链
const industryChain = reactive({
list: [],
id: "",
})
const onDecreeRelatedChain = async (id) => {
try {
const res = await getSingleSanctionEntitySupplyChain({
orgId: "91370102723265504D"
});
console.log("data1", res)
if (res.code === 200 && res.data) {
singleSanctionEntitySupplyChainData.value = res.data;
updateGraphData();
treeData.value = {
id: res.data.orgId,
name: res.data.orgName,
image: companyActive,
symbolSize: 50,
children: (res.data.parentOrgList || []).map((item, index) => ({
id: item.id || `p-${index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 30
}))
};
const res = await getDecreeRelatedChain({ id });
console.log("产业链:", res);
if (res.code === 200) {
industryChain.list = res.data;
if (industryChain.list.length) onDecreeChainNodes(industryChain.list[0].id)
}
} catch (error) {
console.log(error);
console.log("获取产业链失败", error);
}
};
// 企业影响分析
const companyTotalNum = ref(0); // 企业数量
const chart1Data = ref({
// title: ["集成电路", "新能源", "人工智能", "先进制造", "量子科技"],
// value: [109, 95, 79, 25, 11]
});
const handleGetChart1Data = async () => {
const params = {
id: 147
};
// 产业链鱼骨图
const fishbone = reactive({
list: [],
base: [],
})
const onDecreeChainNodes = async (id) => {
industryChain.id = id;
try {
const res = await getDecreeIndustry(params);
console.log("企业影响分析", res);
if (res.code === 200 && res.data) {
chart1Data.value.title = res.data.map(item => {
return item.hylyName;
});
chart1Data.value.value = res.data.map(item => {
return item.companyNum;
const res = await getDecreeChainNodes({ id });
console.log("产业链鱼骨图:", res);
if (res.code === 200) {
let obj = res.data.chains.reduce((result, item) => {
result['chain-'+item.id] = {...item, children: []};
return result;
}, {});
res.data.children.forEach(item => {
if (item.companyId== entityInfo.id) {
obj['chain-'+item.chainId].children.push({ ...item, back:true})
} else if (obj['chain-'+item.chainId]?.children?.length < 10) {
obj['chain-'+item.chainId].children.push(item)
}
})
fishbone.list = Object.values(obj);
console.log("fishbone.list:", fishbone.list);
fishbone.base = res.data.levelInfos.map((item, index) => {
return {...item, name: ['上游', '中游', '下游'][index]}
});
} else {
chart1Data.value.title = [];
chart1Data.value.value = [];
}
} catch (error) {
chart1Data.value.title = [];
chart1Data.value.value = [];
console.log("获取产业链鱼骨图失败", error);
}
};
const handelBox1 = async () => {
await handleGetChart1Data();
let chart1 = getBarChart(chart1Data.value.title, chart1Data.value.value);
setChart(chart1, "chart1");
};
const chainInfo = ref({
upstreamInternalCount: 0,
upstreamInternalRate: 0,
upstreamEntityCount: 0,
upstreamEntityRate: 0,
midstreamInternalCount: 0,
midstreamInternalRate: 0,
midstreamEntityCount: 0,
midstreamEntityRate: 0,
downstreamInternalCount: 0,
downstreamInternalRate: 0,
downstreamEntityCount: 0,
downstreamEntityRate: 0
// 实体关系
const graphInfo = reactive({
nodes: [],
links: [],
});
// 根据chainId获取chainInfo
const handleGetChainInfoByChainId = async () => {
const onDecreeRelatedEntitie = async (id) => {
try {
const res = await getCnEntityOnChain(chainId.value);
console.log("chainInfo", res);
if (res) {
chainInfo.value = res;
const res = await getDecreeRelatedEntitie({ id });
console.log("实体关系:", res);
if (res.code === 200) {
graphInfo.links = res.data.map(onFormatLink)
graphInfo.nodes = res.data.map(onFormatNode)
graphInfo.nodes.unshift(onFormatNode(entityInfo.node))
}
} catch (error) {
console.log("chainInfo error", error);
console.log("获取实体关系失败", error);
}
};
}
const onFormatLink = (item, index) => {
return {
id: `link-${index+1}`,
source: item.id+'', target: entityInfo.id+'',
label: { show: true, color: "#055fc2", backgroundColor: "#eef7ff", borderWidth: 0, offset: [0, 15], formatter: item.relation },
lineStyle: { color: '#B9DCFF', type: "solid", opacity: 1 }
}
}
const onFormatNode = (item) => {
let leader = item.id == entityInfo.id;
return {
id: item.id+'',
name: onWordWrap(item.companyName, 7),
label: {
show: true,
color: "#3b414b",
fontSize: leader ? 18 : 14,
fontWeight: leader ? 700 : 400,
fontFamily: 'Source Han Sans CN',
},
symbolSize: 40,
symbol: `image://${company}`
}
}
// 文本插入换行符
const onWordWrap = (word, num) => {
const list = word.split('');
let label = "";
for (let i = 0; i < list.length; i++) {
if (i % num === 0 && i !== 0) {
label += "\n";
}
label += list[i];
}
return label;
}
onMounted(() => {
// handleGetCompanyListByArea();
handleGetChart1Data();
onDecreeEntities();
handleGetHylyList();
handelBox1();
getSingleSanctionEntitySupplyChainRequest()
});
</script>
......@@ -458,6 +338,11 @@ onMounted(() => {
box-shadow: 0 0 0 1px var(--el-border-color) inset;
box-sizing: border-box;
height: 32px;
:deep(.el-input) {
height: 100%;
width: 100%;
}
}
}
......
......@@ -79,7 +79,7 @@ onMounted(() => {
.left {
position: absolute;
left: -160px;
top: 0px;
top: 25px;
width: 160px;
padding: 0px 16px;
}
......
......@@ -4,7 +4,7 @@
<WarnningPane :warnningLevel="riskInfo.riskLevel" :warnningContent="riskInfo.content" />
</div>
<div class="introduction-wrap">
<div class="left">
<div class="page-left">
<div class="box1">
<AnalysisBox title="基本信息" :showAllBtn="false">
<div class="box1-main">
......@@ -23,14 +23,10 @@
</div>
<div class="item">
<div class="item-left">{{ "英文全称:" }}</div>
<div class="item-right text" v-if="basicInfo.eName?.length < 60">
{{ basicInfo.eName }}
</div>
<div class="item-right text" v-if="basicInfo.eName?.length < 60"> {{ basicInfo.eName }} </div>
<el-popover v-else effect="dark" :width="500" :content="basicInfo.eName" placement="top-start">
<template #reference>
<div class="item-right text">
{{ basicInfo.eName }}
</div>
<div class="item-right text"> {{ basicInfo.eName }} </div>
</template>
</el-popover>
</div>
......@@ -66,7 +62,19 @@
</div>
</AnalysisBox>
</div>
<div class="box2">
<div>
<AnalysisBox title="报告内容摘要" :showAllBtn="false">
<el-empty v-if="false" style="margin: 20px 0;" description="暂无数据" :image-size="100" />
<div class="box5-main">
<AiSummary>
<template #summary-content>
<div class="box5-text">{{ box1Data }}</div>
</template>
</AiSummary>
</div>
</AnalysisBox>
</div>
<div>
<AnalysisBox title="相关事件" :showAllBtn="false">
<div class="box2-main">
<el-empty v-if="!relatedData.length" description="暂无数据" :image-size="100" />
......@@ -80,18 +88,21 @@
<span class="meta">{{ item.sjsj }} · {{ item.source }}</span>
</div>
<div class="content">{{ item.sjnr }}</div>
<!-- <el-popover effect="dark" :width="1000" :content="item.content" placement="top-start">
<template #reference>
<div class="content">{{ item.content }}</div>
</template>
</el-popover> -->
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="right">
<div class="page-right">
<div class="box4">
<AnalysisBox title="政令关键词云" :showAllBtn="false">
<div class="box4-main">
<el-empty v-if="!wordCloudData.length" description="暂无数据" :image-size="100" />
<WordCloudChart v-if="wordCloudData.length" :data="wordCloudData" width="100%" height="100%" />
</div>
</AnalysisBox>
</div>
<div class="box3">
<AnalysisBox title="发布机构" :showAllBtn="false">
<div class="box3-top">
......@@ -151,12 +162,16 @@ import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import router from "@/router";
import WarnningPane from '@/components/base/WarningPane/index.vue'
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import {
getDecreeBasicInfo,
getDecreeRiskSignal,
getDecreeIssueOrganization
getDecreeIssueOrganization,
getKeyWordUp,
getOverview,
} from "@/api/decree/introduction";
import { getDecreeRelatedEvent } from "@/api/decree/background";
import AiSummary from '@/components/base/Ai/AiSummary/index.vue'
import DefaultIcon1 from "@/assets/icons/default-icon1.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
......@@ -216,6 +231,30 @@ const handleGetBasicInfo = async () => {
};
handleGetBasicInfo();
// 政令关键词云
const wordCloudData = ref([])
const onKeyWordUp = async () => {
try {
const res = await getKeyWordUp();
console.log("政令关键词云", res);
wordCloudData.value = res.data.slice(0, 10).map(item => ({name: item.name, value: item.count}));
} catch (error) {
console.error("获取政令关键词云数据失败", error);
}
};
// 报告内容摘要
const box1Data = ref('');
const onOverview = async () => {
try {
const res = await getOverview({id: decreeId.value});
console.log("报告内容摘要", res);
box1Data.value = res.data;
} catch (error) {
console.error("获取报告内容摘要数据失败", error);
}
};
// 相关事件
const relatedData = ref([]);
const handleGetRelateEvents = async () => {
......@@ -304,6 +343,8 @@ const handleClickUser = item => {
};
onMounted(() => {
onOverview()
onKeyWordUp()
onRiskSignalData()
handleGetRelateEvents();
handleGetOrgnization();
......@@ -322,15 +363,15 @@ onMounted(() => {
width: 100%;
display: flex;
.left {
.page-left {
display: flex;
flex-direction: column;
flex: auto;
margin-right: 16px;
gap: 16px;
.box1 {
height: 414px;
.box1-main {
display: flex;
padding: 0 24px;
......@@ -427,161 +468,183 @@ onMounted(() => {
}
}
.box2 {
margin-top: 16px;
height: 420px;
.box2-main {
margin-top: 3px;
margin-left: 31px;
height: 100%;
width: 1004px;
overflow: hidden;
overflow-y: auto;
.box2-item {
display: flex;
height: 60px;
width: 100%;
margin-bottom: 2px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin-top: 10px;
.item-left {
width: 64px;
height: 48px;
border-radius: 2px;
.box5-main {
padding: 0px 22px 22px;
.box5-text {
padding-bottom: 20px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 30px;
min-height: 300px;
}
}
img {
width: 100%;
height: 100%;
}
.box2-main {
padding: 0 22px;
.box2-item {
display: flex;
height: 60px;
width: 100%;
margin-bottom: 2px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin-top: 10px;
.item-left {
width: 64px;
height: 48px;
border-radius: 2px;
font-size: 0px;
img {
width: 100%;
height: 100%;
}
}
.item-center {
flex: auto;
width: 20px;
margin-left: 14px;
.item-center {
flex: auto;
width: 20px;
margin-left: 14px;
.bubble-header {
display: flex;
align-items: center;
cursor: pointer;
.name {
flex: auto;
width: 20px;
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
text-decoration: underline;
color: var(--color-main-active);
}
}
.bubble-header {
display: flex;
align-items: center;
cursor: pointer;
.meta {
margin-left: 10px;
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
letter-spacing: 0px;
white-space: nowrap;
.name {
flex: auto;
width: 20px;
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
text-decoration: underline;
color: var(--color-main-active);
}
}
.content {
width: 100%;
height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
.meta {
margin-left: 10px;
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 30px;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: 0px;
white-space: nowrap;
}
}
}
}
.box2-footer {
margin: 20px 22px;
display: flex;
justify-content: space-between;
.box2-footer-left {
height: 20px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
.content {
width: 100%;
height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.box2-footer {
margin: 20px 22px;
display: flex;
justify-content: space-between;
.box2-footer-left {
height: 20px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
}
}
.right {
.page-right {
display: flex;
flex-direction: column;
width: 520px;
flex: none;
gap: 16px;
.box3 {
width: 520px;
height: 100%;
.box3-top {
border-bottom: 1px solid rgba(234, 236, 238, 1);
.box3-top-top {
width: 473px;
height: 88px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
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;
.box4 {
height: 414px;
.box4-main {
height: 100%;
}
}
.text {
height: 16px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 16px;
}
.box3-top {
border-bottom: 1px solid rgba(234, 236, 238, 1);
padding: 0 22px;
.icon {
width: 16px;
height: 16px;
.box3-top-top {
height: 88px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
background: rgba(247, 248, 249, 1);
display: flex;
align-items: center;
margin: 0 auto;
position: relative;
padding: 0 16px;
cursor: pointer;
.left {
width: 64px;
height: 64px;
font-size: 0px;
img {
width: 100%;
height: 100%;
}
}
img {
width: 100%;
height: 100%;
}
}
.right {
width: 20px;
flex: auto;
margin-left: 15px;
.name {
height: 26px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.ename {
margin-top: 6px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
.box3-top-bottom {
height: 193px;
margin: 0 auto;
.left {
width: 64px;
height: 64px;
margin-left: 17px;
.box3-top-bottom-header {
height: 40px;
display: flex;
padding-top: 14px;
box-sizing: border-box;
gap: 12px;
.icon {
margin-top: 5px;
width: 14px;
height: 14px;
img {
width: 100%;
......@@ -589,47 +652,41 @@ onMounted(() => {
}
}
.right {
width: 370px;
margin-left: 15px;
.name {
height: 26px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.ename {
margin-top: 6px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
.text {
width: 100px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
}
.box3-top-bottom {
width: 473px;
height: 193px;
margin: 0 auto;
.box3-top-bottom-main {
margin-top: 2px;
height: 130px;
overflow: hidden;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.box3-top-bottom-header {
height: 40px;
.box3-top-bottom-item {
margin-top: 12px;
height: 48px;
width: 200px;
display: flex;
padding-top: 14px;
box-sizing: border-box;
gap: 12px;
justify-content: flex-start;
.icon {
margin-top: 5px;
width: 14px;
height: 14px;
.box3-top-bottom-item-left {
width: 48px;
height: 48px;
border-radius: 24px;
overflow: hidden;
img {
width: 100%;
......@@ -637,155 +694,102 @@ onMounted(() => {
}
}
.text {
width: 100px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
}
.box3-top-bottom-main {
margin-top: 2px;
height: 130px;
overflow: hidden;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.box3-top-bottom-item-right {
margin-left: 8px;
width: 144px;
.box3-top-bottom-item {
margin-top: 12px;
height: 48px;
width: 200px;
display: flex;
justify-content: flex-start;
.box3-top-bottom-item-left {
width: 48px;
height: 48px;
border-radius: 24px;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
.name {
width: 144px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.box3-top-bottom-item-right {
margin-left: 8px;
.position {
margin-top: 1px;
width: 144px;
.name {
width: 144px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.position {
margin-top: 1px;
width: 144px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
}
.box3-bottom {
padding-right: 22px;
.box3-bottom-header {
height: 59px;
display: flex;
.box3-bottom {
.box3-bottom-header {
height: 59px;
display: flex;
.header-icon {
margin-left: 22px;
margin-top: 27px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.header-icon {
margin-left: 22px;
margin-top: 27px;
width: 16px;
height: 16px;
.header-title {
margin-left: 12px;
margin-top: 23px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
img {
width: 100%;
height: 100%;
}
}
.box3-bottom-main {
width: 510px;
height: 440px;
overflow: hidden;
overflow-y: auto;
.header-title {
margin-left: 12px;
margin-top: 23px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
}
:deep(.el-timeline) {
padding: 8px 0px 0px 25px !important;
}
.box3-bottom-main {
:deep(.el-timeline) {
padding: 8px 0px 0px 25px !important;
}
:deep(.el-timeline-item__node) {
border: 4px solid var(--color-main-active) !important;
background-color: #fff;
}
:deep(.el-timeline-item__node) {
border: 4px solid var(--color-main-active) !important;
background-color: #fff;
}
:deep(.el-timeline-item) {
padding-bottom: 12px !important;
}
:deep(.el-timeline-item) {
padding-bottom: 12px !important;
}
:deep(.el-timeline-item__timestamp) {
color: var(--color-main-active) !important;
font-family: Microsoft YaHei !important;
font-size: 16px !important;
font-weight: 600 !important;
padding-top: 0px !important;
}
:deep(.el-timeline-item__timestamp) {
color: var(--color-main-active) !important;
font-family: Microsoft YaHei !important;
font-size: 16px !important;
font-weight: 600 !important;
padding-top: 0px !important;
}
.timeline-content {
color: var(--text-primary-65-color);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 26px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
.timeline-content {
color: var(--text-primary-65-color);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 26px;
}
}
}
......
......@@ -41,10 +41,11 @@ import { getDecreeReport } from "@/api/decree/introduction";
import BaseDecreeOriginal from "@/components/base/DecreeOriginal/index.vue";
const route = useRoute();
let pdfUrl = "";
const handleDownload = async () => {
if (summaryInfo.value?.url) {
if (pdfUrl) {
try {
const response = await fetch(summaryInfo.value.url, {
const response = await fetch(pdfUrl, {
method: 'GET',
headers: { 'Content-Type': 'application/pdf' },
});
......@@ -103,6 +104,7 @@ const handleGetReport = async () => {
const res = await getDecreeReport({id: route.query.id});
console.log("报告原文", res);
if (res.code === 200 && res.data) {
pdfUrl = res.data.pdfUrl;
const originData = [];
let num = Math.max(res.data.content.length, res.data.contentEn.length)
for (let i = 0; i < num; i++) {
......
......@@ -47,16 +47,15 @@ export default defineConfig({
port: 3000,
open: true,
proxy: {
'/reportData': {
target: 'http://8.140.26.4:10022/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/reportData/, '')
},
'/api': {
target: 'http://8.140.26.4:9085/',
// target: 'http://192.168.0.6:28080/',
// target: 'http://192.168.0.4:28080/',
// target: 'http://172.20.10.3:28080/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论