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

合并分支 'zym-dev' 到 'pre'

Zym dev 查看合并请求 !200
流水线 #3 已失败
VITE_BASE_API= '/api'
# 图表解读等 /aiAnalysis 代理服务密钥(勿提交到公开仓库时可改为本地 .env.local)
VITE_AI_ANALYSIS_API_KEY=aircasKEY19491001
\ No newline at end of file
# 线上地址
VITE_BASE_API= '/api'
# 图表解读等 AI 服务(与部署环境一致时填写)
VITE_AI_ANALYSIS_API_KEY=aircasKEY19491001
\ No newline at end of file
......@@ -14,6 +14,7 @@ lerna-debug.log*
# Dependencies
node_modules
*node_modules
.pnpm
.npm
......
stages:
- build
- deploy
default:
tags:
- risk-monitor-frontend
cache:
key:
files:
- package-lock.json
paths:
- .npm/
policy: pull-push
build_pre:
stage: build
image: node:18-alpine
rules:
- if: '$CI_COMMIT_BRANCH == "pre"'
script:
- npm config set cache .npm --global
- npm ci --prefer-offline --no-audit --no-fund
- npm run build
artifacts:
expire_in: 1 hour
paths:
- dist/
deploy_pre:
stage: deploy
image: alpine:3.20
rules:
- if: '$CI_COMMIT_BRANCH == "pre"'
dependencies:
- build_pre
script:
- apk add --no-cache rsync
- rsync -av --delete dist/ /nas/kjb_service/zm/pre-project/html/
\ No newline at end of file
......@@ -9,5 +9,6 @@
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script src="/js/config.js"></script>
</body>
</html>
\ No newline at end of file
差异被折叠。
......@@ -26,6 +26,7 @@
"axios": "^1.12.2",
"d3": "^7.9.0",
"d3-cloud": "^1.2.7",
"dayjs": "^1.11.20",
"default-passive-events": "^4.0.0",
"echarts": "^5.4.3",
"echarts-liquidfill": "^3.1.0",
......@@ -42,7 +43,10 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"autoprefixer": "^10.4.27",
"postcss": "^8.5.8",
"sass": "^1.93.3",
"tailwindcss": "^3.4.17",
"unplugin-auto-import": "^0.17.0",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.0.0"
......
const baseUrl = `http://8.140.26.4:9085`
\ No newline at end of file
差异被折叠。
<template>
<div id="app">
<div class="pro-wrapper">
<div class="home-page">
<ModuleHeader />
<div class="main-container">
<router-view />
</div>
</div>
<div class="right-btn" @click="handleClickToolBox">
<div class="item">
<div class="icon">
<img src="@/assets/icons/overview/domain.png" alt="" />
</div>
<div class="text">{{ "领域" }}</div>
</div>
<div class="item">
<div class="icon">
<img src="@/assets/icons/overview/element.png" alt="" />
</div>
<div class="text">{{ "要素" }}</div>
</div>
</div>
<div class="tool-box">
<!-- <div class="tool-item">
<img src="@/assets/icons/tool-item-icon1.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon2.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon3.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon4.png" alt="" />
</div> -->
<el-tooltip content="智能写报" placement="left" :offset="10">
<div class="tool-item" @click="handleOpenPage('znxb')">
<img src="@/assets/icons/tool-item-icon1.png" alt="" />
</div>
</el-tooltip>
<el-tooltip content="智能翻译" placement="left" :offset="10">
<div class="tool-item" @click="handleClickToolBox">
<img src="@/assets/icons/tool-item-icon2.png" alt="" />
</div>
</el-tooltip>
<!-- <div class="tool-item">
<img src="@/assets/icons/tool-item-icon3.png" alt="" />
</div> -->
<el-tooltip content="智能问答" placement="left" :offset="10">
<div class="tool-item" @click="handleOpenPage('znwd')">
<img src="@/assets/icons/tool-item-icon4.png" alt="" />
</div>
</el-tooltip>
</div>
<!-- <div class="ai-btn" @click="openAiBox">
<div class="icon">
<img src="@/assets/icons/ai-icon.png" alt="" />
</div>
<div class="text">智能问答</div>
</div> -->
<div class="ai-dialog" v-if="isShowAiBox">
<AiBox @close="closeAiBox" />
</div>
</div>
<router-view></router-view>
</div>
</template>
......@@ -95,27 +29,6 @@ import { ElMessage } from "element-plus";
const router = useRouter();
const route = useRoute();
// const target = ref(null);
// const { x, y, isDragging } = useDraggable(target, {
// initialValue: { x: 1770, y: 800 },
// onStart: () => console.log("开始拖动"),
// onEnd: () => console.log("结束拖动")
// });
// const style = computed(() => ({
// position: "absolute",
// left: `${x.value}px`,
// top: `${y.value}px`,
// cursor: isDragging.value ? "grabbing" : "grab"
// }));
const handleToHome = () => {
router.push({
path: "/ZMOverView"
});
isCurrentOverview.value = true;
};
const isShowAiBox = ref(false);
......@@ -322,12 +235,26 @@ body {
display: none;
}
/* #region 公共样式类名 */
/* 单行文本溢出隐藏显示省略号 */
.one-line-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 多行文本两端对齐 最后一行正常显示 */
.text-align-justify {
text-align: justify;
text-align-last: left;
-webkit-text-align-last: left;
}
/* 可点击文本 鼠标悬浮样式 */
#app .text-click-hover:hover {
text-decoration: underline;
color: var(--color-primary-100);
cursor: pointer;
}
/* #endregion 公共样式类名 */
</style>
<style lang="scss" scoped>
......
import request from "@/api/request.js";
import { getToken } from "@/api/request.js";
const CHART_INTERPRETATION_URL = "/aiAnalysis/chart_interpretation";
const API_KEY = "aircasKEY19491001";
/**
* 从模型返回的 markdown code fence 中提取数组 JSON
* 例如:```json\n[ ... ]\n```
* @param {string} buffer
* @returns {unknown[]}
*/
function parseChartInterpretationArray(buffer) {
// 可能带有前后空白、换行、以及 ```/```json 包裹
let cleaned = String(buffer || "").trim();
// 移除开头 fence
cleaned = cleaned.replace(/^```(?:json)?\s*/i, "");
// 移除结尾 fence(允许末尾有换行)
cleaned = cleaned.replace(/```[\s\r\n]*$/i, "").trim();
// 优先直接解析
try {
const parsed = JSON.parse(cleaned);
if (Array.isArray(parsed)) return parsed;
} catch (_) { }
// 兜底:从 first '[' 到 last ']' 截取
const start = cleaned.indexOf("[");
const end = cleaned.lastIndexOf("]");
if (start !== -1 && end !== -1 && end > start) {
const arrStr = cleaned.slice(start, end + 1);
const parsed = JSON.parse(arrStr);
if (Array.isArray(parsed)) return parsed;
}
throw new Error("无法解析图表解读 JSON 数组");
}
// 图表解读(流式)
/**
* @param {text}
* 图表解读(SSE 流式)
* @param {object} data - 请求体
* @param {object} [options] - 可选配置
* @param {function(string): void} [options.onChunk] - 每收到一条 SSE 消息时回调,参数为当前 chunk 的 text
* @returns {Promise<{data: unknown[]}>}
*/
export function getChartAnalysis(data) {
return request({
method: 'POST',
url: `/aiAnalysis/chart_interpretation`,
data,
})
export function getChartAnalysis(data, options = {}) {
const { onChunk } = options;
return new Promise((resolve, reject) => {
let buffer = "";
let settled = false;
const abortController = new AbortController();
const safeResolve = value => {
if (settled) return;
settled = true;
resolve(value);
};
const safeReject = err => {
if (settled) return;
settled = true;
reject(err);
};
(async () => {
try {
const { fetchEventSource } = await import("@microsoft/fetch-event-source");
await fetchEventSource(CHART_INTERPRETATION_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
"Cache-Control": "no-cache",
"X-API-Key": API_KEY,
// 后端同项目其它接口使用 token 字段名(axios 拦截器里就是这样注入的)
token: getToken()
},
body: JSON.stringify(data),
signal: abortController.signal,
openWhenHidden: true,
retryDelay: 1000,
maxRetries: 2,
onopen: response => {
const contentType = response.headers.get("content-type") || "";
if (!contentType.includes("text/event-stream")) {
throw new Error("SSE连接格式异常:content-type 不是 text/event-stream");
}
},
onmessage: event => {
const raw = (event?.data || "").trim();
if (!raw) return;
if (raw === "[DONE]") return;
let chunk = "";
// 兼容后端返回格式:
// - {"text":"```"} / {"text":"json\n[\n"}
// - {"type":"reasoning","chunk":"..."}(新格式)
try {
const msg = JSON.parse(raw);
if (Array.isArray(msg?.chunk)) {
safeResolve({ data: msg.chunk });
abortController.abort();
return;
}
if (msg && typeof msg === "object" && "chunk" in msg) {
chunk = typeof msg.chunk === "string" ? msg.chunk : "";
if (chunk) buffer += chunk;
} else if (msg && typeof msg === "object" && "text" in msg) {
chunk = String(msg.text ?? "");
if (chunk) buffer += chunk;
} else {
chunk = raw;
buffer += raw;
}
} catch (e) {
chunk = raw;
buffer += raw;
}
// 每收到一条消息即回调,用于流式渲染
if (chunk && typeof onChunk === "function") {
onChunk(chunk);
}
// 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接
const trimmed = buffer.trim();
if (trimmed.endsWith("```")) {
try {
const arr = parseChartInterpretationArray(trimmed);
safeResolve({ data: arr });
abortController.abort();
} catch (_) { }
}
},
onclose: () => {
try {
const arr = parseChartInterpretationArray(buffer);
safeResolve({ data: arr });
} catch (e) {
safeReject(e);
}
},
onerror: error => {
if (error?.name === "AbortError") return true;
safeReject(error);
return true;
}
});
} catch (error) {
safeReject(error);
}
})();
});
}
......@@ -192,3 +192,45 @@ export function getBillFullText(params) {
params,
})
}
// 条款对比-根据两版版本与筛选条件获取配对条款列表
/**
* @param {billId,oldVersionId,newVersionId,diffType,cRelated,keyword}
* @header token
* @returns { list: Array<{ oldTerm: object|null, newTerm: object|null }> }
*/
export function getBillTermsCompare(params) {
return request({
method: "GET",
url: "/api/billInfoBean/content/compare",
params,
});
}
// 版本对比-根据两版版本与筛选条件获取条款列表(分页)
/**
* @param {billId,content,currentPage,currentVersion,isCn,originalVersion,pageSize,status}
* @header token
*/
export function getBillVersionCompare(params, config = {}) {
return request({
method: "GET",
url: "/api/billInfoBean/versionCompare",
params,
signal: config.signal,
});
}
// 版本对比-根据筛选条件获取变更统计
/**
* @param {billId,content,currentVersion,isCn,originalVersion}
* @header token
*/
export function getBillVersionCompareStatistics(params, config = {}) {
return request({
method: "GET",
url: "/api/billInfoBean/versionCompareStatistics",
params,
signal: config.signal,
});
}
......@@ -2,17 +2,24 @@ import request from "@/api/request.js";
// 涉华法案领域分布
/**
* @param {year}
* @param {Object} params
* @param {string} params.year - 年份
* @param {string} [params.status] - 状态:提出法案/通过法案
*/
export function getBillIndustry(params) {
return request({
method: 'GET',
url: `/api/BillOverview/billIndustry/${params.year}`,
params,
params: { status: params.status }
})
}
// 涉华法案统计
/**
* @param {Object} params
* @param {string} [params.dateDesc] - 时间范围:近一年/近两年/近三年/全部
* @param {string} [params.industryId] - 行业领域 ID
*/
export function getBillCount(params) {
return request({
method: 'GET',
......@@ -21,6 +28,19 @@ export function getBillCount(params) {
})
}
// 近期美国国会各委员会涉华提案数量汇总
/**
* @param {Object} params
* @param {string} params.dateDesc - 时间范围:近一周/近一月/近一年
*/
export function getStatisticsBillCountByCommittee(params) {
return request({
method: 'GET',
url: `/api/BillOverview/statisticsBillCountByCommittee`,
params
})
}
// 获取关键条款
export function getBillOverviewKeyTK() {
return request({
......@@ -233,6 +253,30 @@ export async function getHistoryBillListWithStage(personId, params = {}) {
}
}
// /**
// * 获取潜在提案举措分析
// * GET /api/personHomepage/historyBill/clause/{personId}
// * @param {string} personId - 人物ID
// * @param {Object} params - 查询参数
// */
// export async function getPotentialClauseAnalysis(personId, params = {}) {
// const queryString = Object.entries(params)
// .filter(([, value]) => value !== undefined && value !== null && value !== '')
// .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
// .join('&')
// const url = queryString
// ? `/api/personHomepage/historyBill/clause/${personId}?${queryString}`
// : `/api/personHomepage/historyBill/clause/${personId}`
// return request(url, {
// method: 'GET',
// })
// }
/**
* 获取排序选项
*/
export function getSortOptions() {
return Promise.resolve({
......
......@@ -129,6 +129,18 @@ export function getCharacterRelation(params) {
params,
})
}
// 获取人物教育履历
/**
* @param {personId}
* @header token
*/
export function getCharacterReducationResume(params) {
return request({
method: 'GET',
url: `/api/personHomepage/educationResume/${params.personId}`,
params,
})
}
// 获取相关实体
/**
......@@ -142,19 +154,15 @@ export function getCharacterRelatedEntity(params) {
params,
})
}
// 获取人物教育履历
/**
* @param {personId}
* @header token
*/
export function getCharacterReducationResume(params) {
export function getareaType(params) {
return request({
method: 'GET',
url: `/api/personHomepage/educationResume/${params.personId}`,
params,
url: `/api/commonDict/areaType`,
params
})
}
export async function getFindingsReport(personId, params = {}) {
const queryParts = []
......@@ -193,10 +201,3 @@ export async function getSubjectList(params) {
params
})
}
export function getareaType(params) {
return request({
method: 'GET',
url: `/api/commonDict/areaType`,
params
})
}
\ No newline at end of file
import request from "@/api/request.js";
// 最新科技政令
export function getDepartmentList() {
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() {
export function getKeyDecree(params) {
return request({
method: 'GET',
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
})
}
......@@ -86,3 +89,21 @@ export function getDecreeTypeList() {
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}
......
......@@ -78,3 +78,19 @@ export function getDecreeReport(params) {
url: `/api/administrativeOrderInfo/contentUrl/${params.id}`,
})
}
// 政令关键词云
export function getKeyWordUp() {
return request({
method: 'GET',
url: `/api/element/getKeyWordUp/2025-01-01`,
})
}
// 报告内容摘要
export function getOverview(params) {
return request({
method: 'GET',
url: `/api/administrativeOrderInfo/overview/${params.id}`,
})
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import { ElMessage } from "element-plus";
const request200 = requestP => {
return requestP.then(data => {
if (data.code === 200) {
console.log('返回的数据结构 =>', data.data)
return data.data;
}
ElMessage({
......@@ -122,13 +123,14 @@ export function getSanctionsInfoCount() {
* sanReason: string
* }[]>}
*/
export function getSanctionProcess(typeName = "实体清单", pageNum = 1, pageSize = 10, isCn = false) {
export function getSanctionProcess(sanTypeIds = "1", pageNum = 1, pageSize = 10, isCn = false) {
return request200(
request({
method: "POST",
url: "/api/entitiesDataCount/getSanctionProcess",
data: {
typeName,
sanTypeIds,
// typeName: tabMap[sanTypeId],
pageNum,
pageSize,
isCn
......@@ -391,13 +393,13 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") {
* startTime: string
* }[]>}
*/
export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize = 10, sanctionDate = "", isCn = false) {
export function getEntitiesList(sanTypeId=1, pageNum = 1, pageSize = 10, sanctionDate = "", isCn = false) {
return request200(
request({
method: "POST",
url: "/api/sanctionList/pageQuery",
data: {
typeName,
sanTypeId,
pageNum,
pageSize,
sanctionDate,
......
import request from "@/api/request.js";
// 实体清单-制裁概况-获取实体清单基本信息
export function getEntityInfo(sanType) {
export function getEntityInfo(id) {
return request({
method: "GET",
url: `/api/sanctionList/baseInfo/${sanType}`
url: `/api/sanctionList/baseInfoById/${id}`
});
}
......@@ -98,10 +98,10 @@ export function get50PercentEntityCount(data) {
}
// 实体清单-数据统计-总量统计
export function getTotalCount() {
export function getTotalCount(id) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/total`
url: `/api/sanctionList/statistics/total?sanTypeId=${id}`
});
}
......@@ -113,7 +113,7 @@ export function getTotalCount() {
export function getSanctionCountChange(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/num`,
url: `/api/sanctionList/statistics/num`,
params
});
}
......@@ -128,7 +128,7 @@ export function getSanctionCountChange(params) {
export function getRegionCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/region`,
url: `/api/sanctionList/statistics/region`,
params
});
}
......@@ -143,7 +143,7 @@ export function getRegionCount(params) {
export function getTechDomainCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/domain`,
url: `/api/sanctionList/statistics/domain`,
params
});
}
......@@ -158,7 +158,7 @@ export function getTechDomainCount(params) {
export function getEntityTypeCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/entityType`,
url: `/api/sanctionList/statistics/entityType`,
params
});
}
......@@ -247,7 +247,7 @@ export function getSingleSanctionOverview(params) {
export function getSingleSanctionEntityCountry(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/countryRegion`,
url: `/api/sanctionList/statistics/countryRegion`,
params
});
}
......@@ -292,11 +292,10 @@ export function getSingleSanctionOverviewList(data) {
* @param {string} params.sanRecordId - 制裁记录ID
* @header token
*/
export function getSingleSanctionTotalCount(params) {
export function getSingleSanctionTotalCount(id) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/total`,
params
url: `/api/sanctionList/statistics/total?sanTypeId=${id}`,
});
}
......@@ -311,7 +310,7 @@ export function getSingleSanctionTotalCount(params) {
export function getSingleSanctionDomainCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/domain`,
url: `/api/sanctionList/statistics/domain`,
params
});
}
......@@ -327,7 +326,7 @@ export function getSingleSanctionDomainCount(params) {
export function getSingleSanctionEntityTypeCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/entityType`,
url: `/api/sanctionList/statistics/entityType`,
params
});
}
......@@ -341,7 +340,7 @@ export function getSingleSanctionEntityTypeCount(params) {
export function getSingleSanctionEntityCountryCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/countryRegion`,
url: `/api/sanctionList/statistics/countryRegion`,
params
});
}
......@@ -357,7 +356,7 @@ export function getSingleSanctionEntityCountryCount(params) {
export function getSingleSanctionEntityRegionCount(params) {
return request({
method: "GET",
url: `/api/sanctionList/statistics/el/region`,
url: `/api/sanctionList/statistics/region`,
params
});
}
......@@ -393,6 +392,14 @@ export function getSingleSanctionEntitySupplyChain(params) {
});
}
// 单次制裁-深度挖掘-制裁实体信息
export function getSingleSanctionEntityInfo(id) {
return request({
method: "GET",
url: `/api/organization/sanInfo?orgId=${id}`,
});
}
// 单次制裁-深度挖掘-制裁实体股权信息
/**
* @param {Object} params
......@@ -551,10 +558,10 @@ export function getSingleSanctionEntityInternationalPaper(params) {
}
// 商业管制清单-CCL清单简介-基本信息
export function getCCLInfo() {
export function getCCLInfo(sanTypeId = 13) {
return request({
method: "GET",
url: `/api/sanctionList/baseInfo/ccl`
url: `/api/sanctionList/baseInfoById/${sanTypeId}`
});
}
......@@ -621,3 +628,11 @@ export function getCclQuery(data) {
data
});
}
// 商业管制清单-CCL清单列表-清单版本
export function getCCLVersionList() {
return request({
method: "GET",
url: `/api/ccl/version/dateList`
});
}
import request from "@/api/request.js";
// 根据行业领域id获取公司列表
// 获取实体列表(按行业/公司名筛选)
/**
* @param {id}
* @param {Object} params
* @param {string} [params.id] - 行业领域id(全部领域不传)
* @param {string} [params.companyName] - 公司名称(搜索框为空不传)
* @returns {Array<{id: string, name: string, marketChange: number|null, chainCompanyId: string|number}>}
* 说明:右侧详情查询当前建议优先使用 chainCompanyId;若缺失可回退 id,兼容后续接口调整
*/
export function getCompanyList(params) {
return request({
method: 'GET',
url: `/api/billImpactAnalysis/industry/company/${params.id}`,
url: `/api/billImpactAnalysis/industry/company`,
params,
})
}
......
......@@ -10,12 +10,16 @@ export class TextEntity {
type: string;
}
// 智能化:提取文本实体
export function extractTextEntity(text: string): Promise<IntelligentResultWrapper<TextEntity[]>> {
export function extractTextEntity(
text: string,
config: { signal?: AbortSignal } = {}
): Promise<IntelligentResultWrapper<TextEntity[]>> {
return request({
url: `${INTELLECTUAL_API}/extract-entity`,
method: "POST",
data: {
text
}
},
signal: config.signal
});
}
......@@ -51,6 +51,14 @@ service.interceptors.request.use(config => {
config.headers['token'] = token
// config.headers['Authorization'] = `Bearer ${token}` // 如果后端需要Bearer格式可以使用这个
}
// 图表解读等 AI 分析服务(Vite 代理 /aiAnalysis)需要 X-API-Key
const reqUrl = String(config.url || '')
if (reqUrl.includes('aiAnalysis')) {
const aiApiKey = import.meta.env.VITE_AI_ANALYSIS_API_KEY
if (aiApiKey) {
config.headers['X-API-Key'] = aiApiKey
}
}
return config
}, error => {
console.log(error)
......@@ -82,8 +90,14 @@ service.interceptors.response.use(
// 重复请求触发的取消不提示错误
if (isCanceledError) return Promise.reject(error)
// 处理token过期或无效的情况
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
// 处理token过期或无效的情况(排除 AI 分析服务:其 401 多为 API Key 问题)
const errUrl = String(error.config?.url || '')
const isAiAnalysisRequest = errUrl.includes('aiAnalysis')
if (
error.response &&
(error.response.status === 401 || error.response.status === 403) &&
!isAiAnalysisRequest
) {
ElMessage({
message: 'Token已过期,请重新登录',
type: 'error',
......
......@@ -126,7 +126,6 @@ export function getPersonSummaryInfo(params) {
})
}
// 获取人物全局信息 通过personId 获取personType
export function getareaType(params) {
return request({
method: 'GET',
......
......@@ -10,6 +10,13 @@ export function getThinkTankList() {
})
}
export function getAllThinkTankList(params) {
return request({
method: 'GET',
url: '/api/thinkTankOverview/thinkTanks/page',
params: params
})
}
//智库概览:获取智库发布
export function getNewReport() {
return request({
......@@ -28,13 +35,19 @@ export function getThinkTankRiskSignal() {
})
}
// 政策建议趋势分布
/**
* 政策建议趋势分布(数量变化趋势)
* @param {{ startDate: string, endDate: string }} params - 如 2024-01-01 ~ 2024-12-31
*/
export function getThinkTankPolicyIndustryChange(params) {
return request({
method: 'GET',
url: `/api/thinkTankOverview/policyIndustryChange/${params}`,
})
method: "GET",
url: `/api/thinkTankOverview/policyIndustryChange`,
params: {
startDate: params.startDate,
endDate: params.endDate
}
});
}
// 政策建议领域分布
......@@ -113,10 +126,44 @@ export function getThinkDynamicsReportType() {
//智库动态:获取智库报告
export function getThinkDynamicsReport(params) {
const safe = params || {}
// 兼容两种调用方式:
// 1) { id, startDate, authorName, currentPage, pageSize, researchTypeIds, searchText, sortFun, years }
// 2) { id, startDate, parmas: { authorName, currentPage, pageSize, researchTypeIds, searchText, sortFun, years } }
const inner = safe.parmas && typeof safe.parmas === 'object' ? safe.parmas : {}
const id = safe.id
const startDate = safe.startDate
const authorName = inner.authorName ?? safe.authorName ?? ''
const currentPage = inner.currentPage ?? safe.currentPage ?? 1
const pageSize = inner.pageSize ?? safe.pageSize ?? 10
const researchTypeIds = inner.researchTypeIds ?? safe.researchTypeIds ?? ''
const searchText = inner.searchText ?? safe.searchText ?? ''
const sortFun = inner.sortFun ?? safe.sortFun ?? false
const years = inner.years ?? safe.years ?? null
const query = { currentPage, pageSize, sortFun }
// 仅在有值时才传,避免后端按空值筛选
if (authorName) query.authorName = authorName
if (researchTypeIds) query.researchTypeIds = researchTypeIds
if (searchText) query.searchText = searchText
if (years !== null && years !== undefined && years !== '') query.years = years
return request({
method: 'GET',
url: `/api/thinkTankInfo/report/${params.id}/${params.startDate}`,
params: params.parmas
url: `/api/thinkTankInfo/report/${id}/${startDate}`,
params: query
})
}
// 智库领域观点分析(流式)
// [POST] 8.140.26.4:10029/report-domain-view-analysis
export function postReportDomainViewAnalysis(data) {
return request({
method: 'POST',
// 开发环境走 Vite 同源代理,避免浏览器跨域(见 vite.config.js:/intelligent-api -> 8.140.26.4:10029)
url: '/intelligent-api/report-domain-view-analysis',
data
})
}
......@@ -152,15 +199,31 @@ export function getThinkPolicyIndustryChange(params) {
})
}
//获取智库政策
/**
* 获取智库政策(政策追踪列表)
* GET /api/thinkTankInfo/policy
* Query: thinkTankId, startDate, endDate, orgIds, domainIds(科技领域/智库领域,逗号分隔 id), pageNum, pageSize, sortField, sortOrder, sortFun, reportId 等
*/
export function getThinkPolicy(params) {
return request({
method: 'GET',
url: `/api/thinkTankInfo/policy/${params.id}/${params.startDate}`,
url: '/api/thinkTankInfo/policy',
params
})
}
/**
* 政府机构字典(政策追踪-涉及部门筛选项)
* GET /api/commonDict/gov/agency
* @returns {Promise<{ code: number, data: Array<{ id: string, name: string }> }>}
*/
export function getGovAgencyDict() {
return request({
method: 'GET',
url: '/api/commonDict/gov/agency'
})
}
//智库百科基本信息
export function getThinkTankInfoBasic(params) {
return request({
......@@ -299,9 +362,26 @@ export function getThinkTankReportPolicy(params) {
//获取相关政策动态
export function getThinkTankReportPolicyAction(params) {
const {
reportId,
currentPage,
pageSize,
keyword = "",
orgIds = "",
// 新增:按科技领域 / 标签过滤
industryName = ""
} = params;
return request({
method: 'GET',
url: `/api/thinkTankReport/policyAction/${params}`,
url: `/api/thinkTankReport/policyDetail/${reportId}`,
params: {
currentPage,
pageSize,
keyword,
// 后端按标签过滤使用的字段
industryName,
orgIds,
}
})
}
......
......@@ -50,7 +50,7 @@ export const countryCoordMap = {
// 欧洲
俄罗斯: [37.6184, 55.7558], // 俄罗斯莫斯科
德国: [10.4515, 51.1657], // 德国柏林
英国: [-3.436, 55.3781], // 英国伦敦
英国: [-2, 54], // 英国伦敦
法国: [2.2137, 46.2276], // 法国巴黎
意大利: [12.5674, 41.8719], // 意大利罗马
西班牙: [-3.7492, 40.4637], // 西班牙马德里
......@@ -218,12 +218,12 @@ export function convertAsiaCenterCoord(coord) {
const [lng, lat] = coord;
// 将以本初子午线为基准的坐标转换为以亚洲为中心的坐标
// world-asia-center.json 是将标准坐标的经度减去了 180 度
let newLng = lng - 180;
// world-asia-center.json 是将标准坐标的经度加上了 180 度(地图向右平移)
let newLng = lng + 180;
// 规范化到 [-180, 180] 范围
if (newLng < -180) {
newLng += 360;
if (newLng > 180) {
newLng -= 360;
}
return [newLng, lat];
......
......@@ -135,8 +135,8 @@ const headerTitleClasses = computed(() => [
.header-icon {
width: 22px;
height: 18px;
margin-left: 5px;
margin-right: 14px;
margin-left: 0px;
margin-right: 10px;
}
.blue-title-block {
......@@ -155,6 +155,7 @@ const headerTitleClasses = computed(() => [
/* color: var(--base-color); */
color: $base-color;
line-height: 48px;
padding: 0 5px;
// padding: 0 12px;
}
......
......@@ -10,8 +10,7 @@
</div>
<div
class="search-type-tab"
:class="{ active: billSearchType === 'state' }"
@click="handleChangeBillSearchType('state')"
:class="{ active: billSearchType === 'state', 'is-disabled': true }"
>
州议会
</div>
......@@ -206,6 +205,11 @@ const handleToPosi = id => {
color: rgb(5, 95, 194);
border-color: rgb(255, 255, 255);
}
.search-type-tab.is-disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
.search-main-with-tabs {
......
......@@ -17,7 +17,7 @@ defineProps({
})
</script>
<style scoped>
<style lang="scss" scoped>
.action-button {
height: 28px;
padding: 0 8px;
......@@ -29,11 +29,16 @@ defineProps({
font-size: 16px;
font-weight: 400;
transition: all 0.3s;
}
.action-button[type="normal"] {
background-color: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
&:hover {
background: var(--color-primary-2);
}
}
.action-button[type="active"] {
......@@ -52,6 +57,4 @@ defineProps({
.action-button[type="active"]:hover {
background-color: #40a9ff;
} */
</style>
\ No newline at end of file
<svg viewBox="0 0 16 12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="12.000000" fill="none" customFrame="url(#clipPath_1)">
<defs>
<clipPath id="clipPath_1">
<rect width="16.000000" height="12.000000" x="0.000000" y="0.000000" rx="6.000000" fill="rgb(255,255,255)" />
</clipPath>
</defs>
<rect id="AI-logo" width="16.000000" height="12.000000" x="0.000000" y="0.000000" rx="6.000000" />
<path id="合并" d="M13.0238 1.46629C13.7681 0.916637 14.14 0 14.14 0C14.14 0 14.0472 0.916623 14.5122 1.46629C14.9772 2.01595 16 2.19866 16 2.19866C16 2.19866 14.8838 2.38224 14.1396 2.93288C13.3955 3.48351 13.0234 4.40121 13.0234 4.40121C13.0234 4.40121 13.1163 3.48353 12.6512 2.93288C12.1861 2.38222 11.163 2.1986 11.163 2.1986C11.163 2.1986 12.2795 2.01594 13.0238 1.46629ZM8.72107 2.20032L9.85481 10.5L7.29299 10.5L6.99502 8.24068L3.42302 8.24068L2.42736 10.5L0 10.5L3.28857 2.61208L2.85978 2.20032L8.72107 2.20032ZM5.4325 3.60747L4.00442 6.83353L6.86057 6.83353L6.43179 3.60747L5.4325 3.60747ZM13.9457 10.4999L14.9162 4.57493L11.475 4.57493L11.9074 5.13349L11.2386 10.4999L13.9457 10.4999Z" fill="rgb(5,95,194)" fill-rule="evenodd" />
</svg>
<template>
<div class="ai-button-wrapper">
<div class="icon">
<img src="./ai-icon.svg" alt="">
</div>
<div class="text text-tip-1">{{ '总结' }}</div>
</div>
</template>
<style lang="scss" scoped>
.ai-button-wrapper{
width: 74px;
height: 28px;
border-radius: 20px 0 0 20px;
background: var(--color-primary-10);
display: flex;
gap: 6px;
align-items: center;
justify-content: left;
box-sizing: border-box;
padding-left: 12px;
cursor: pointer;
.text{
color: var(--color-primary-100);
}
}
</style>
\ No newline at end of file
<svg viewBox="0 0 20 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20.000000" height="16.000000" fill="none" customFrame="url(#clipPath_2)">
<defs>
<clipPath id="clipPath_2">
<rect width="20.000000" height="16.000000" x="0.000000" y="0.000000" rx="8.000000" fill="rgb(255,255,255)" />
</clipPath>
</defs>
<rect id="AI-logo" width="20.000000" height="16.000000" x="0.000000" y="0.000000" rx="8.000000" />
<path id="合并" d="M16.2798 1.95505C17.2101 1.22218 17.6751 0 17.6751 0C17.6751 0 17.559 1.22216 18.1403 1.95505C18.7215 2.68794 20 2.93155 20 2.93155C20 2.93155 18.6047 3.17632 17.6745 3.9105C16.7443 4.64468 16.2793 5.86828 16.2793 5.86828C16.2793 5.86828 16.3954 4.6447 15.814 3.9105C15.2326 3.1763 13.9537 2.93147 13.9537 2.93147C13.9537 2.93147 15.3494 2.68792 16.2798 1.95505ZM10.9013 2.93376L12.3185 14L9.11624 14L8.74378 10.9876L4.27877 10.9876L3.03421 14L0 14L4.11071 3.48277L3.57473 2.93376L10.9013 2.93376ZM6.79062 4.80996L5.00553 9.11138L8.57572 9.11138L8.03974 4.80996L6.79062 4.80996ZM17.4321 13.9999L18.6452 6.09991L14.3437 6.09991L14.8843 6.84466L14.0482 13.9999L17.4321 13.9999Z" fill="rgb(5,95,194)" fill-rule="evenodd" />
</svg>
<template>
<div class="ai-pane-wrapper">
<div class="header">
<div class="icon">
<img src="./ai-icon.svg" alt="">
</div>
<div class="title text-title-3-show">{{ '智能总结' }}</div>
</div>
<div class="content text-regular">{{ aiContent }}</div>
</div>
</template>
<script setup>
const props = defineProps({
aiContent: {
type: String,
default: ''
}
})
</script>
<style lang="scss">
.ai-pane-wrapper {
width: 100%;
min-height: 156px;
height: auto;
background: var(--color-primary-2);
box-sizing: border-box;
padding: 12px 16px;
.header {
width: 126px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
border-radius: 20px;
background: var(--color-primary-10);
.icon {
width: 20px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.title {
color: var(--color-primary-100);
}
}
.content {
margin-top: 8px;
width: 100%;
min-height: 90px;
height: auto;
box-sizing: border-box;
padding: 0 12px;
color: var(--color-primary-100);
display: block;
overflow: visible;
word-break: break-word;
white-space: pre-wrap;
}
}
</style>
\ No newline at end of file
<svg viewBox="0 0 1019 61" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1019.000000" height="61.000000" fill="none" customFrame="#000000">
<defs>
<linearGradient id="paint_linear_1" x1="7.60449219" x2="10.1398888" y1="-1.90734863e-06" y2="61.0955963" gradientUnits="userSpaceOnUse">
<stop stop-color="rgb(231,243,255)" offset="0" stop-opacity="1" />
<stop stop-color="rgb(231,243,255)" offset="1" stop-opacity="0" />
</linearGradient>
</defs>
<path id="矩形 5734" d="M0.000322558 0L1019 0L1019 31.5006L0 61.0008L0.000322558 0Z" fill="url(#paint_linear_1)" fill-rule="evenodd" />
</svg>
<svg viewBox="0 0 15 12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15.000000" height="12.000000" fill="none" customFrame="url(#clipPath_0)">
<defs>
<clipPath id="clipPath_0">
<rect width="15.000000" height="12.000000" x="0.000000" y="0.000000" rx="6.000000" fill="rgb(255,255,255)" />
</clipPath>
<linearGradient id="paint_linear_0" x1="7.49999952" x2="7.49999952" y1="0" y2="10.5" gradientUnits="userSpaceOnUse">
<stop stop-color="rgb(5,95,194)" offset="0" stop-opacity="1" />
<stop stop-color="rgb(137,193,255)" offset="1" stop-opacity="1" />
</linearGradient>
</defs>
<rect id="AI-logo" width="15.000000" height="12.000000" x="0.000000" y="0.000000" rx="6.000000" />
<path id="合并" d="M12.2098 1.46629C12.9076 0.916637 13.2563 0 13.2563 0C13.2563 0 13.1693 0.916623 13.6052 1.46629C14.0411 2.01595 15 2.19866 15 2.19866C15 2.19866 13.9535 2.38224 13.2559 2.93288C12.5583 3.48351 12.2095 4.40121 12.2095 4.40121C12.2095 4.40121 12.2966 3.48353 11.8605 2.93288C11.4245 2.38222 10.4653 2.1986 10.4653 2.1986C10.4653 2.1986 11.5121 2.01594 12.2098 1.46629ZM8.176 2.20032L9.23888 10.5L6.83718 10.5L6.55783 8.24068L3.20908 8.24068L2.27565 10.5L0 10.5L3.08303 2.61208L2.68105 2.20032L8.176 2.20032ZM5.09297 3.60747L3.75415 6.83353L6.43179 6.83353L6.0298 3.60747L5.09297 3.60747ZM13.0741 10.4999L13.9839 4.57493L10.7578 4.57493L11.1632 5.13349L10.5361 10.4999L13.0741 10.4999Z" fill="url(#paint_linear_0)" fill-rule="evenodd" />
</svg>
<svg viewBox="0 0 6.70703 13.4102" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6.707031" height="13.410156" fill="none" customFrame="#000000">
<rect id="容器 848" width="6.000000" height="11.997072" x="0.353516" y="0.707031" />
<path id="矢量 626" d="M0 0L2.99854 2.99854L6 0" stroke="rgb(5,95,194)" stroke-width="1.000000" transform="matrix(1,0,0,-1,0.353516,3.70508)" />
<path id="矢量 627" d="M0.353516 9.70508L3.35205 12.7036L6.35352 9.70508" stroke="rgb(5,95,194)" stroke-width="1.000000" />
</svg>
<template>
<div class="summary-wrapper">
<div class="summary-header">
<div class="icon1">
<img src="./icon1.svg" alt="">
</div>
<div class="text text-tip-1">{{ '智库报告总结摘要' }}</div>
<div class="icon2">
<img src="./icon2.svg" alt="">
</div>
</div>
<div class="summary-main">
<slot name="summary-content"></slot>
</div>
</div>
</template>
<style lang="scss">
.summary-wrapper {
width: 100%;
height: 100%;
// background: linear-gradient(90deg, var(--color-primary-10) 0%, var(--bg-white-100) 100%) ;
background: url('./bg.svg') no-repeat;
overflow: hidden;
border-radius: 4px;
border: 1px solid #E7F3FF;
.summary-header {
margin-top: 16px;
margin-left: 24px;
width: 199px;
height: 32px;
border: 1px solid var(--color-primary-100);
border-radius: 16px;
background: var(--color-primary-10);
display: flex;
justify-content: center;
align-items: center;
gap: 9px;
.icon1 {
width: 15px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: var(--color-primary-100);
}
.icon2 {
width: 6px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
}
.summary-main {
margin: 0 auto;
margin-top: 24px;
width: calc(100% - 52px);
height: calc(100% - 92px);
}
}
</style>
\ No newline at end of file
......@@ -11,18 +11,16 @@
<el-switch v-model="isTranslate" />
<div class="switch-label">原文显示</div>
<div class="btn" @click="emits('download')">
<div class="icon icon-gap-4">
<img :src="defaultDownloadIcon" alt="" />
</div>
<div class="text">下载</div>
</div>
<div class="btn" @click="handleFindWord('open')">
<div class="icon icon-gap-6">
<img :src="defaultSearchIcon" alt="" />
<div
v-for="action in headerActions"
:key="action.key"
class="btn"
@click="action.onClick"
>
<div :class="['icon', action.iconGapClass]">
<img :src="action.icon" alt="" />
</div>
<div class="text">查找</div>
<div class="text">{{ action.text }}</div>
</div>
<div class="find-word-box" v-if="findWordBox">
......@@ -47,15 +45,16 @@
</div>
<div class="report-main">
<div v-if="!displayReportData.length" class="noContent">{{ "暂无数据" }}</div>
<el-scrollbar height="100%" v-else>
<div v-if="!displayReportData.length" class="no-content">暂无数据</div>
<el-scrollbar v-else height="100%">
<div
v-for="item in displayReportData"
:key="item.num"
:class="['content-row', { 'high-light': isHighlight }]"
class="content-row"
:class="{ 'high-light': isHighlight }"
>
<div :class="['content-cn', { 'translate-cn': !isTranslate }]" v-html="item.content"></div>
<div class="content-en" v-html="item.contentEn" v-if="isTranslate"></div>
<div class="content-cn" :class="{ 'translate-cn': !isTranslate }" v-html="item.content" />
<div v-if="isTranslate" class="content-en" v-html="item.contentEn" />
</div>
</el-scrollbar>
</div>
......@@ -70,8 +69,6 @@ import defaultSearchIcon from "./assets/icons/search.png";
import { nextTick, ref, watch } from "vue";
import { debounce } from "lodash";
// 该组件为公共展示组件:网络请求/下载等业务由父组件处理;查找、高亮、显示切换等通用交互在组件内部封装。
// 图标资源固定使用组件内置文件,保证组件资源自包含。
const props = defineProps({
reportData: { type: Array, default: () => [] },
});
......@@ -88,6 +85,23 @@ const findWordMax = ref(0);
const originReportData = ref([]);
const displayReportData = ref([]);
const headerActions = [
{
key: "download",
text: "下载",
icon: defaultDownloadIcon,
iconGapClass: "icon-gap-4",
onClick: () => emits("download"),
},
{
key: "search",
text: "查找",
icon: defaultSearchIcon,
iconGapClass: "icon-gap-6",
onClick: () => handleFindWord("open"),
},
];
function escapeRegExp(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
......@@ -137,7 +151,9 @@ const doUpdateWord = () => {
displayReportData.value = originReportData.value.map((item) => {
const cn = applyHighlightToText(item.content, term);
const en = isTranslate.value ? applyHighlightToText(item.contentEn, term) : { html: item.contentEn, count: 0 };
const en = isTranslate.value
? applyHighlightToText(item.contentEn, term)
: { html: item.contentEn, count: 0 };
findWordMax.value += cn.count + en.count;
return {
...item,
......@@ -222,8 +238,9 @@ watch(isTranslate, () => {
width: 1600px;
background-color: white;
padding: 0 60px;
height: 20px;
flex: auto;
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
.report-header {
......@@ -245,7 +262,6 @@ watch(isTranslate, () => {
display: flex;
align-items: center;
.find-word-input {
width: 20px;
flex: auto;
}
.find-word-limit {
......@@ -265,8 +281,7 @@ watch(isTranslate, () => {
font-size: 20px;
line-height: 20px;
font-weight: 700;
width: 20px;
flex: auto;
flex: 1;
}
.btn {
margin-left: 10px;
......@@ -310,25 +325,15 @@ watch(isTranslate, () => {
}
.report-main {
height: 20px;
flex: auto;
box-sizing: border-box;
padding-top: 10px;
&::-webkit-scrollbar {
display: none;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
height: 20px;
padding: 10px 0;
&::-webkit-scrollbar-track {
background: #f1f1f1;
:deep(.el-scrollbar) {
height: 100%;
}
.noContent {
.no-content {
height: 100%;
display: flex;
align-items: center;
......@@ -347,10 +352,6 @@ watch(isTranslate, () => {
min-height: 100px;
gap: 80px;
&:last-child {
border-bottom: none;
}
.content-en,
.content-cn {
width: 50%;
......
......@@ -41,6 +41,12 @@ const getGraphChart = (nodes, links, layoutType) => {
itemStyle: {
color: '#73C0DE'
},
// 方法1:通过 left/right/top/bottom 控制绘图区域
left: '5%',
right: '5%',
top: '5%',
bottom: '5%',
layout: layoutType,
data: nodes,
links: links,
......
......@@ -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,
......@@ -32,11 +33,17 @@ const props = defineProps({
}
})
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>
......@@ -45,5 +52,7 @@ onMounted(() => {
.graph-chart-wrapper {
width: 100%;
height: 100%;
// width: 800px;
// height: 500px;
}
</style>
\ No newline at end of file
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.000000" height="16.000000" fill="none" customFrame="#000000">
<rect id="Line/Calendar" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
<path id="形状" d="M11 3.08L13.52 3.08C13.7855 3.08 14 3.2945 14 3.56L14 13.52C14 13.7855 13.7855 14 13.52 14L2.48 14C2.2145 14 2 13.7855 2 13.52L2 3.56C2 3.2945 2.2145 3.08 2.48 3.08L5 3.08L5 2.12C5 2.054 5.054 2 5.12 2L5.96 2C6.026 2 6.08 2.054 6.08 2.12L6.08 3.08L9.92 3.08L9.92 2.12C9.92 2.054 9.974 2 10.04 2L10.88 2C10.946 2 11 2.054 11 2.12L11 3.08ZM3.08 4.16L3.08 6.2L12.92 6.2L12.92 4.16L11 4.16L11 4.88C11 4.946 10.946 5 10.88 5L10.04 5C9.974 5 9.92 4.946 9.92 4.88L9.92 4.16L6.08 4.16L6.08 4.88C6.08 4.946 6.026 5 5.96 5L5.12 5C5.054 5 5 4.946 5 4.88L5 4.16L3.08 4.16ZM12.92 12.92L3.08 12.92L3.08 7.22L12.92 7.22L12.92 12.92Z" fill="rgb(59,65,75)" fill-rule="evenodd" />
</svg>
<template>
<div class="time-tab-pane-wrapper">
<div
class="time-item"
:class="{'time-item-active': item.active}"
v-for="item,index in timeList"
:key="index"
@click="handleTimeClick(item,index)"
>
<div class="icon" v-if="item.active">
<img src="./calendat-icon.svg" alt="">
</div>
<div class="text text-tip-1" :class="{'text-active': item.active}">{{ item.time }}</div>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue'
const timeList = ref([
{
time: '近一周',
active: true
},
{
time: '近一月',
active: false
},
{
time: '近一年',
active: false
},
])
const handleTimeClick = (item, index) => {
timeList.value.forEach(time => {
time.active = false
})
timeList.value[index].active = true
emit('time-click', item)
}
const emit = defineEmits(['time-click'])
</script>
<style lang="scss" scoped>
.time-tab-pane-wrapper{
display: flex;
width: 248px;
height: 36px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50px;
border: 2px solid rgba(255,255,255,1);
.time-item{
height: 32px;
box-sizing: border-box;
padding: 4px 12px;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
cursor: pointer;
.icon{
width: 16px;
height: 16px;
img{
width: 100%;
height: 100%;
}
}
.text{
color: var(--text-primary-50-color);
}
.text-active{
color: var(--text-primary-80-color)
}
}
.time-item-active{
background: rgba(255, 255, 255, 0.65);
border-radius: 50px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
}
}
</style>
\ No newline at end of file
......@@ -3,13 +3,18 @@
<div class="icon">
<img src="./tip-icon.svg" alt="">
</div>
<div class="text text-tip-2 text-primary-50-clor">{{ `数据来源:${dataSource},数据时间:${dataTime}` }}</div>
<div class="text text-tip-2 text-primary-50-clor">{{ tipText }}</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
text: {
type: String,
default: ''
},
dataSource: {
type: String,
default: '美国国会官网'
......@@ -21,20 +26,24 @@ const props = defineProps({
})
const tipText = computed(() => props.text || `数据来源:${props.dataSource},数据时间:${props.dataTime}`)
</script>
<style lang="scss" scoped>
.tip-wrapper{
.tip-wrapper {
width: 100%;
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
height: 22px;
.icon{
.icon {
width: 16px;
height: 16px;
img{
img {
width: 100%;
height: 100%;
}
......
<template>
<div class="tree-chart-wrapper" id="tree-chart">
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import setChart from '@/utils/setChart';
import getTreeChart from './treeChart';
import CompanyImg from "@/assets/icons/symbol.png";
const props = defineProps({
treeData: {
type: Array,
default: [
{
id: 1,
name: 'a1',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
children: [
{
id: 11,
name: 'b1',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 12,
name: 'b2',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 13,
name: 'b3',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 14,
name: 'b4',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
}
]
}
]
}
})
onMounted(() => {
const treeChart = getTreeChart(props.treeData)
setChart(treeChart, 'tree-chart')
})
</script>
<style lang="scss" scoped>
.tree-chart-wrapper {
width: 100%;
height: 100%;
}
</style>
\ No newline at end of file
const getTreeChart = (treeData) => {
const option = {
series: [{
type: 'tree',
layout: 'orthogonal', // 从上到下布局
orient: 'TB', // Top to Bottom
data: treeData,
top: '10%',
bottom: '10%',
left: '3%',
right: '3%',
symbol: 'circle',
symbolSize: 40,
label: {
position: 'top',
verticalAlign: 'middle',
align: 'center',
fontSize: 20, // 字体大小
color: 'rgb(59, 65, 75)', // 字体颜色
fontWeight: 'normal', // 字体粗细
// formatter: '{b}', // 标签内容格式器
// rotate: 0, // 旋转角度
offset: [0, -6], // 偏移量
// lineHeight: 20, // 行高
// backgroundColor: 'transparent', // 背景色
// borderColor: 'transparent', // 边框颜色
// borderWidth: 0, // 边框宽度
// borderRadius: 0, // 圆角
// padding: 0, // 内边距
// shadowBlur: 0, // 阴影模糊
// shadowColor: 'transparent' // 阴影颜色
},
leaves: {
label: {
position: 'bottom',
verticalAlign: 'middle',
align: 'center',
offset: [0, 6],
}
},
lineStyle: {
color: '#ccc',
width: 2,
type: 'dashed', // 线条类型:'solid'(实线), 'dashed'(虚线), 'dotted'(点线)
curveness: 0.5, // 曲线弧度(0-1),仅当 edgeShape 为 'curve' 时有效
cap: 'round', // 线帽:'butt', 'round', 'square'
join: 'round', // 连接点:'bevel', 'round', 'miter'
shadowBlur: 0, // 阴影模糊大小
shadowColor: 'rgba(0,0,0,0.5)', // 阴影颜色
shadowOffsetX: 0, // 阴影水平偏移
shadowOffsetY: 0 // 阴影垂直偏移
},
emphasis: {
focus: 'descendant'
},
expandAndCollapse: false,
initialTreeDepth: 3
}]
};
return option
}
export default getTreeChart
\ No newline at end of file
<template>
<div class="warnning-pane-wrapper" :style="{ width: width ? width : '1600px', height: height ? height : '116px' }"
<div class="warnning-pane-wrapper" :style="{ width: width ? width : '1600px', height: height ? height : 'auto', minHeight: height ? undefined : '116px' }"
:class="{
level1: warnningLevel === '特别重大风险',
level2: warnningLevel === '重大风险',
......@@ -146,15 +146,12 @@ const handleClickPane = () => {
.warnning-pane-content{
width: calc(100% - 40px);
margin: 0 auto;
height: 60px;
display: -webkit-box;
/* 2. 设置内部布局方向为垂直 */
-webkit-box-orient: vertical;
/* 3. 限制显示的行数为 2 行 */
-webkit-line-clamp: 2;
/* 4. 隐藏超出部分 */
overflow: hidden;
/* 5. 设置文本溢出显示省略号 */
text-overflow: ellipsis;
margin-bottom: 16px;
min-height: 60px;
height: auto;
display: block;
overflow: visible;
white-space: pre-wrap;
word-break: break-word;
}
</style>
\ No newline at end of file
......@@ -21,7 +21,8 @@ const getWordCloudChart = data => {
gridSize: 15, // 网格大小,影响词间距。
sizeRange: [16, 36], // 定义词云中文字大小的范围
rotationRange: [0, 0],
rotationStep: 0,
// rotationRange: [-90, 90],
// rotationStep: 10,
drawOutOfBound: false, // 是否超出画布
shrinkToFit: true, // 是否自动缩小以适应容器
// 字体
......
......@@ -133,11 +133,12 @@ const emit = defineEmits(['save', 'download', 'collect'])
}
}
// .header-btn {
.header-btn {
// display: flex;
// justify-content: flex-end;
// gap: 8px;
// }
margin-right: 10px;
}
// .header-btn1 {
// position: absolute;
......
......@@ -96,16 +96,16 @@ const homeTitleList = ref([
path: "/ZMOverView",
disabled: false
},
{
name: "主要国家科技动向感知",
path: "",
disabled: true
},
{
name: "主要国家竞争科技安全",
path: "",
disabled: true
}
// {
// name: "主要国家科技动向感知",
// path: "",
// disabled: true
// },
// {
// name: "主要国家竞争科技安全",
// path: "",
// disabled: true
// }
]);
const homeActiveTitleIndex = ref(0);
......
<template>
<p class="p-regular-rereg">
<span class="text-regular" v-for="(segment, index) in processedText" :key="index">
<a v-if="segment.isEntity" :href="`https://cn.bing.com/search?q=${segment.entity?.text_span}`"
class="entity-link" target="_blank" rel="noopener noreferrer">
<span v-if="segment.isEntity" @click="$emit('onEntityClick', segment.entity)" class="entity-link">
{{ segment.entity?.text_span }}
<img :src="SearchIcon" :width="10" :height="10" alt="search" />
</a>
</span>
<span v-else>
{{ segment.text }}
</span>
......@@ -13,110 +12,112 @@
</p>
</template>
<script lang="ts" setup>
import { TextEntity } from '@/api/intelligent';
import { ref, watch, onMounted } from 'vue';
import SearchIcon from './images/search.png'
import { TextEntity } from "@/api/intelligent";
import { ref, watch, onMounted } from "vue";
import SearchIcon from "./images/search.png";
export interface ProcessedTextSegment {
text: string
isEntity: boolean
entity?: TextEntity
text: string;
isEntity: boolean;
entity?: TextEntity;
}
const props = defineProps({
text: {
type: String,
default: ''
default: ""
},
entities: {
type: Array<TextEntity>,
default: () => []
}
})
});
const emit = defineEmits(["onEntityClick"]);
// 处理后的文本段
const processedText = ref<ProcessedTextSegment[]>([])
const processedText = ref<ProcessedTextSegment[]>([]);
// 处理文本,识别并替换实体
const processText = () => {
console.log('props.entities.length', props.entities.length)
console.log("props.entities.length", props.entities.length);
if (!props.text || !props.entities) {
// console.log('props.text', props.entities.length)
processedText.value = [{ text: '', isEntity: false }]
return
processedText.value = [{ text: "", isEntity: false }];
return;
}
const result = []
let currentPosition = 0
const result = [];
let currentPosition = 0;
// 按实体文本长度排序,优先匹配长文本
const sortedEntities = [...props.entities].sort((a, b) =>
b.text_span.length - a.text_span.length
)
const sortedEntities = [...props.entities].sort((a, b) => b.text_span.length - a.text_span.length);
while (currentPosition < props.text.length) {
let matched = false
let matched = false;
for (const entity of sortedEntities) {
const entityText = entity.text_span
const endPosition = currentPosition + entityText.length
const entityText = entity.text_span;
const endPosition = currentPosition + entityText.length;
if (props.text.substring(currentPosition, endPosition) === entityText) {
// 如果当前位置是实体,添加到结果
result.push({
isEntity: true,
entity: { ...entity }
})
currentPosition = endPosition
matched = true
break
});
currentPosition = endPosition;
matched = true;
break;
}
}
if (!matched) {
// 如果不是实体,收集普通文本
let nextEntityStart = props.text.length
let nextEntityStart = props.text.length;
for (const entity of sortedEntities) {
const pos = props.text.indexOf(entity.text_span, currentPosition)
const pos = props.text.indexOf(entity.text_span, currentPosition);
if (pos !== -1 && pos < nextEntityStart) {
nextEntityStart = pos
nextEntityStart = pos;
}
}
if (nextEntityStart > currentPosition) {
const plainText = props.text.substring(currentPosition, nextEntityStart)
const plainText = props.text.substring(currentPosition, nextEntityStart);
result.push({
text: plainText,
isEntity: false
})
currentPosition = nextEntityStart
});
currentPosition = nextEntityStart;
} else {
// 没有更多实体,添加剩余文本
const remainingText = props.text.substring(currentPosition)
const remainingText = props.text.substring(currentPosition);
if (remainingText) {
result.push({
text: remainingText,
isEntity: false
})
});
}
currentPosition = props.text.length
currentPosition = props.text.length;
}
}
}
processedText.value = result
}
processedText.value = result;
};
// 监听文本和实体变化
watch(() => props.text, processText)
watch(() => props.entities, processText, { deep: true })
watch(() => props.text, processText);
watch(() => props.entities, processText, { deep: true });
// 初始化处理
onMounted(processText)
onMounted(processText);
</script>
<style lang="scss" scoped>
@use '@/styles/common.scss';
@use "@/styles/common.scss";
.entity-link {
color: var(--color-primary-100);
&:hover {
cursor: pointer;
}
}
.p-regular-rereg {
......
<template>
<div class="full-width">
<div class="flex-display" style="align-items: center;">
<common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{ isOpenTranslation
? '中文' : '原文' }}</common-text>
<div class="flex-fill" style="margin: 0 10px;">
<div class="flex-display" style="align-items: center">
<common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{
isOpenTranslation ? "中文" : "原文"
}}</common-text>
<div class="flex-fill" style="margin: 0 10px">
<el-divider></el-divider>
</div>
<el-button v-if="showMoreVisible" @click="() => { showMore = !showMore; updateText() }">
{{ showMore ? '收起' : '展开' }}
<el-button
v-if="showMoreVisible"
@click="
() => {
showMore = !showMore;
updateText();
}
"
>
{{ showMore ? "收起" : "展开" }}
<el-icon>
<arrow-up v-if="showMore" />
<arrow-down v-else />
......@@ -17,21 +26,24 @@
<el-row :gutter="32">
<el-col :span="textColSpan" v-for="(item, index) in allTexts" :key="index">
<!-- <p class="p-news-content"> {{ item }}</p> -->
<intelligent-entity-text :text="item"
:entities="isHighlightEntity ? textEntities : []"></intelligent-entity-text>
<intelligent-entity-text
:text="item"
@on-entity-click="e => $emit('onEntityClick', e)"
:entities="isHighlightEntity ? textEntities : []"
></intelligent-entity-text>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import '@/styles/container.scss';
import '@/styles/common.scss';
import "@/styles/container.scss";
import "@/styles/common.scss";
import { ref, watch, onMounted } from 'vue';
import { TextEntity } from '@/api/intelligent';
import IntelligentEntityText from '@/components/base/texts/IntelligentEntityText.vue';
import { ElIcon, ElButton, ElDivider, ElRow, ElCol } from 'element-plus';
import CommonText from './CommonText.vue';
import { ref, watch, onMounted } from "vue";
import { TextEntity } from "@/api/intelligent";
import IntelligentEntityText from "@/components/base/texts/IntelligentEntityText.vue";
import { ElIcon, ElButton, ElDivider, ElRow, ElCol } from "element-plus";
import CommonText from "./CommonText.vue";
const allTexts = ref([]);
const textColSpan = ref(12);
......@@ -64,30 +76,30 @@ const props = defineProps({
type: Array<TextEntity>,
default: () => []
}
})
});
const emit = defineEmits(["onEntityClick"]);
function updateText() {
const tempTexts = []
const tempRaws = props.textsRaw ?? []
const tempTranslates = props.textsTranslate ?? []
hasTranslation.value = tempTranslates.length > 0
const tempTexts = [];
const tempRaws = props.textsRaw ?? [];
const tempTranslates = props.textsTranslate ?? [];
hasTranslation.value = tempTranslates.length > 0;
if (hasTranslation.value && props.isOpenTranslation) {
// 遍历原始文本和翻译文本,将它们交替添加到 tempTexts 中,并保持原始文本和翻译文本的的数量一致
const maxCount = Math.max(tempRaws.length, tempTranslates.length)
const maxCount = Math.max(tempRaws.length, tempTranslates.length);
for (let i = 0; i < maxCount; i++) {
if (i < tempTranslates.length) {
tempTexts.push(tempTranslates[i]);
} else {
tempTexts.push('');
tempTexts.push("");
}
if (i < tempRaws.length) {
tempTexts.push(tempRaws[i]);
} else {
tempTexts.push('');
tempTexts.push("");
}
}
console.log(tempTexts.length)
console.log(tempTexts.length);
textColSpan.value = 12;
showMoreVisible.value = tempTexts.length > 6;
allTexts.value = showMore.value ? tempTexts : tempTexts.slice(0, 6);
......@@ -98,12 +110,14 @@ function updateText() {
}
}
watch(() => [props.textsRaw, props.textsTranslate, props.isOpenTranslation], () => {
watch(
() => [props.textsRaw, props.textsTranslate, props.isOpenTranslation],
() => {
updateText();
})
}
);
onMounted(() => {
updateText();
})
});
</script>
// 法案资源库
const CountryBill = () => import('@/views/dataLibrary/bill/countryBill/index.vue')
const StateBill = () => import('@/views/dataLibrary/bill/stateBill/index.vue')
const dataBillRoutes = [
// 科技法案资源库路由
{
path: "/dataLibrary/countryBill",
name: "CountryBill",
component: CountryBill,
meta: {
title: '国会法案', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/stateBill",
name: "StateBill",
component: StateBill,
meta: {
title: '州法案', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataBillRoutes
\ No newline at end of file
// 法案资源库
const Decree = () => import('@/views/dataLibrary/decree/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/dataLibrary/dataDecree",
name: "DataDecree",
component: Decree,
meta: {
title: '科技政令', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const CommerceControlList = () => import('@/views/dataLibrary/exportControl/commerceControlList/index.vue')
const CommerceControlListEvent = () => import('@/views/dataLibrary/exportControl/commerceControlListEvent/index.vue')
const EntityList = () => import('@/views/dataLibrary/exportControl/entityList/index.vue')
const EntityListEvent = () => import('@/views/dataLibrary/exportControl/entityListEvent/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/dataLibrary/dataCommerceControlList",
name: "CommerceControlList",
component: CommerceControlList,
meta: {
title: '商业管制清单', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/dataCommerceControlListEvent",
name: "CommerceControlListEvent",
component: CommerceControlListEvent,
meta: {
title: '商业管制清单事件', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/dataEntityList",
name: "EntityList",
component: EntityList,
meta: {
title: '实体清单', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/dataEntityListEvent",
name: "EntityListEvent",
component: EntityListEvent,
meta: {
title: '实体清单事件', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const MREList = () => import('@/views/dataLibrary/financeControl/mREList/index.vue')
const MREListEvent = () => import('@/views/dataLibrary/financeControl/mREListEvent/index.vue')
const SDNList = () => import('@/views/dataLibrary/financeControl/sDNList/index.vue')
const SDNListEvent = () => import('@/views/dataLibrary/financeControl/sDNListEvent/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/dataLibrary/mREList",
name: "MREList",
component: MREList,
meta: {
title: '涉军企业清单', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/mREListEvent",
name: "MREListEvent",
component: MREListEvent,
meta: {
title: '涉军企业清单事件', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/sDNList",
name: "SDNList",
component: SDNList,
meta: {
title: 'SDN清单', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/sDNListEvent",
name: "SDNListEvent",
component: SDNListEvent,
meta: {
title: 'SDN清单事件', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const KeyLab = () => import('@/views/dataLibrary/innovationSubject/keyLab/index.vue')
const ResearchUniversity = () => import('@/views/dataLibrary/innovationSubject/researchUniversity/index.vue')
const TechnologyCompany = () => import('@/views/dataLibrary/innovationSubject/technologyCompany/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/dataLibrary/keyLab",
name: "KeyLab",
component: KeyLab,
meta: {
title: '重点实验室', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/researchUniversity",
name: "ResearchUniversity",
component: ResearchUniversity,
meta: {
title: '研究型大学', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/technologyCompany",
name: "TechnologyCompany",
component: TechnologyCompany,
meta: {
title: '科技企业', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const Case337 = () => import('@/views/dataLibrary/marketControl/case337/index.vue')
const Case232 = () => import('@/views/dataLibrary/marketControl/case232/index.vue')
const Case301 = () => import('@/views/dataLibrary/marketControl/case301/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/dataLibrary/case337",
name: "Case337",
component: Case337,
meta: {
title: '337调查', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/case232",
name: "Case232",
component: Case232,
meta: {
title: '232调查', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/case301",
name: "Case301",
component: Case301,
meta: {
title: '301调查', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const CongressMan = () => import('@/views/dataLibrary/technologyFigures/congressMan/index.vue')
const Minister = () => import('@/views/dataLibrary/technologyFigures/minister/index.vue')
const TechnologyLeader = () => import('@/views/dataLibrary/technologyFigures/technologyLeader/index.vue')
const ThinkTankResearcher = () => import('@/views/dataLibrary/technologyFigures/thinkTankResearcher/index.vue')
const dataDecreeRoutes = [
// 科技法案资源库路由
{
path: "/dataLibrary/congressMan",
name: "CongressMan",
component: CongressMan,
meta: {
title: '国会议员', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/minister",
name: "Minister",
component: Minister,
meta: {
title: '机构主官', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/technologyLeader",
name: "TechnologyLeader",
component: TechnologyLeader,
meta: {
title: '科技企业领袖', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
{
path: "/dataLibrary/thinkTankResearcher",
name: "ThinkTankResearcher",
component: ThinkTankResearcher,
meta: {
title: '智库研究人员', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataDecreeRoutes
\ No newline at end of file
// 法案资源库
const ThinkTank = () => import('@/views/dataLibrary/thinkTank/index.vue')
const dataThinkTankRoutes = [
// 科技法案资源库路由
{
path: "/dataLibrary/dataThinkTank",
name: "DataThinkTank",
component: ThinkTank,
meta: {
title: '美国科技智库', // 显示在tag上的文字
affix: false, // 是否为固定tag(首页通常设置为true,不可关闭)
keepAlive: true // 是否需要缓存
}
},
]
export default dataThinkTankRoutes
\ No newline at end of file
import { createRouter, createWebHistory } from "vue-router";
const Home = () => import('@/views/home/index.vue')
const DataLibrary = () => import('@/views/dataLibrary/index.vue')
// 自动导入所有模块路由
const modules = import.meta.glob('./modules/*.js', { eager: true })
......@@ -9,8 +11,38 @@ const fileRoutes = Object.keys(modules).reduce((acc, path) => {
return acc
}, [])
// 自动导入所有资源库模块路由
const datas = import.meta.glob('./dataLibrary/*.js', { eager: true })
const dataRoutes = Object.keys(datas).reduce((acc, path) => {
const module = datas[path].default
acc.push(...module)
return acc
}, [])
const routes = [
...fileRoutes,
{
path: "/",
name: "Home",
component: Home,
children: [
...fileRoutes
]
},
{
path: "/dataLibrary",
name: "DataLibrary",
component: DataLibrary,
meta: {
title: '数据资源库'
},
children: [
...dataRoutes
]
},
];
......
......@@ -14,7 +14,7 @@ const BillInfluenceIndustry = () => import('@/views/bill/influence/industry/inde
const BillProgressForecast = () => import('@/views/bill/influence/ProgressForecast/index.vue')
const BillInfluenceScientificResearch = () => import('@/views/bill/influence/scientificResearch/index.vue')
const BillRelevantCircumstance = () => import('@/views/bill/relevantCircumstance/index.vue')
const BillOriginalText = () => import('@/views/bill/billOriginalText/index.vue')
const BillVersionCompare = () => import('@/views/bill/versionCompare/index.vue')
const billRoutes = [
......@@ -37,14 +37,6 @@ const billRoutes = [
dynamicTitle: true // 标记需要动态设置标题
},
children: [
{
path: "originalText",
name: "BillOriginalText",
component: BillOriginalText,
meta: {
title: "法案原文"
}
},
// 法案分析路由
{
path: "bill",
......@@ -151,6 +143,14 @@ const billRoutes = [
// meta: {
// title: "相关情况"
// }
},
{
path: "versionCompare",
name: "BillVersionCompare",
component: BillVersionCompare,
meta: {
title: "版本对比"
}
}
]
},
......
// 综合搜索
const ComprehensiveSearch = () => import('@/views/comprehensiveSearch/index.vue')
const SearchResults = () => import('@/views/comprehensiveSearch/searchResults/index.vue')
const Chat = () => import('@/views/comprehensiveSearch/chat/index.vue')
const ComprehensiveSearch = () => import("@/views/comprehensiveSearch/index.vue");
const SearchResults = () => import("@/views/comprehensiveSearch/searchResults/index.vue");
const Chat = () => import("@/views/comprehensiveSearch/chat/index.vue");
const comprehensiveSearchRoutes = [
// 综合搜索
......@@ -29,19 +29,19 @@ const comprehensiveSearchRoutes = [
meta: {
title: "智能问答"
}
},
]
}
];
import { useGotoPage } from "../common.js";
export function useGotoComprehensiveSearch() {
const gotoPage = useGotoPage();
return (isNewTabs = true) => gotoPage("/comprehensiveSearch/", {}, isNewTabs)
return (isNewTabs = true) => gotoPage("/comprehensiveSearch/", {}, isNewTabs);
}
export function useGotoSearchResults() {
const gotoPage = useGotoPage();
return (isNewTabs = true) => gotoPage("/searchResults/", {searchText, areaName}, isNewTabs)
return (searchText, areaName, isNewTabs = true) => gotoPage("/searchResults/", { searchText, areaName }, isNewTabs);
}
export default comprehensiveSearchRoutes
\ No newline at end of file
export default comprehensiveSearchRoutes;
......@@ -18,7 +18,7 @@ const decreeRoutes = [
name: "Decree",
component: Decree,
meta: {
title: "政令概览"
title: "科技政令概况"
}
},
{
......
......@@ -120,7 +120,7 @@ const exportControlRoutes = [
name: "commercialControlList",
component: () => import("@/views/exportControl/v2.0CommercialControlList/index.vue"),
meta: {
title: "全部实体清单"
title: "商业管制清单"
}
}
]
......
......@@ -3,6 +3,8 @@ const thinkTank = () => import('@/views/thinkTank/index.vue')
const ThinkTankDetail = () => import('@/views/thinkTank/ThinkTankDetail/index.vue')
const ReportDetail = () => import('@/views/thinkTank/ReportDetail/index.vue')
const ReportOriginal = () => import('@/views/thinkTank/reportOriginal/index.vue')
const allThinkTank= () => import('@/views/thinkTank/allThinkTank/index.vue')
const MultiThinkTankViewAnalysis= () => import('@/views/thinkTank/MultiThinkTankViewAnalysis/index.vue')
const thinktankRoutes = [
// 智库系统的主要路由
......@@ -36,9 +38,19 @@ const thinktankRoutes = [
path: "/thinkTank/reportOriginal/:id",
name: "ReportOriginal",
component: ReportOriginal,
// meta: {
// title: "报告原文"
// }
},
{
path: "/thinkTank/allThinkTank",
name: "allThinkTank",
component: allThinkTank,
},
{
path: "/thinkTank/MultiThinkTankViewAnalysis/:id",
name: "MultiThinkTankViewAnalysis",
component: MultiThinkTankViewAnalysis,
},
]
......
import { defineStore } from 'pinia'
const useTagsViewStore = defineStore('tags-view', {
state: () => ({
visitedViews: [], // 存放打开的标签页列表 [{ path, title, name, affix }]
cachedViews: [] // 存放需要缓存的组件名称 (用于 keep-alive)
}),
actions: {
// 添加标签页
addView(view) {
this.addVisitedView(view)
this.addCachedView(view)
},
addVisitedView(view) {
this.visitedViews.forEach(item => {
item.active = false
})
// 防止重复添加
const isExists = this.visitedViews.some(v => v.path === view.path)
if (!isExists) {
// 可以给不同路由设置不同的标题,比如从 route.meta.title 获取
this.visitedViews.push({
...view,
title: view.meta?.title || '未命名'
})
} else {
this.visitedViews.forEach(v => {
if (v.path === view.path) {
v.active = true
}
})
}
},
addCachedView(view) {
// 只有配置了 keepAlive: true 的路由才加入缓存数组
if (view.meta?.keepAlive && !this.cachedViews.includes(view.name)) {
this.cachedViews.push(view.name)
}
},
// 关闭当前标签页
delView(view) {
return new Promise((resolve) => {
// 1. 先从缓存数组中移除(如果有)
this.delCachedView(view)
// 2. 再从访问数组中移除
const index = this.visitedViews.findIndex(v => v.path === view.path)
if (index !== -1) {
this.visitedViews.splice(index, 1)
}
resolve([...this.visitedViews])
})
},
// 从缓存中移除
delCachedView(view) {
if (view.meta?.keepAlive) {
const index = this.cachedViews.indexOf(view.name)
if (index !== -1) {
this.cachedViews.splice(index, 1)
}
}
},
// 关闭其他/右侧/全部
delOthersViews(view) {
// ...
}
}
})
export default useTagsViewStore
\ No newline at end of file
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import AiButton from '@/components/base/AiButton/index.vue'
import AiPane from '@/components/base/AiPane/index.vue'
<div class="chart-box">
<div class="btn-box" @mouseenter="handleSwitchAiContentShow(true)">
<AiButton />
</div>
<div class="content-box" v-if="isShowAiContent" @mouseleave="handleSwitchAiContentShow(false)">
<AiPane :aiContent="aiContent" />
</div>
</div>
`
}}
</pre>
<div class="chart-box">
<div class="btn-box" @mouseenter="handleSwitchAiContentShow(true)">
<AiButton />
</div>
<div class="content-box" v-if="isShowAiContent" @mouseleave="handleSwitchAiContentShow(false)">
<AiPane :aiContent="aiContent" />
</div>
</div>
</el-col>
</el-row>
</template>
<script setup>
import { ref } from 'vue'
import '@/styles/common.scss'
import AiButton from '@/components/base/Ai/AiButton/index.vue'
import AiPane from '@/components/base/Ai/AiPane/index.vue'
const span = 12
const handleSwitchAiContentShow = (isShow) => {
isShowAiContent.value = isShow
}
const isShowAiContent = ref(false)
const aiContent = ref(`整个立法过程反映了美国政治的高度极化特点。民主党全员反对该法案,批评其"劫贫济富"。而共和党内部也有分歧,特别是在财政赤字问题上。马斯克多次公开批评该法案是"疯狂的支出计划"。整个立法过程反映了美国政治的高度极化特点。民主党全员反对该法案,批评其"劫贫济富"。而共和党内部也有分歧,特别是在财政赤字问题上。马斯克多次公开批评该法案是"疯狂的支出计划"。`)
</script>
<style lang="scss" scoped>
.chart-box {
width: 520px;
height: 300px;
border: 1px solid var(--bg-black-5);
position: relative;
.btn-box {
position: absolute;
right: 0;
bottom: 18px;
}
.content-box {
position: absolute;
left: 0;
bottom: 0;
}
}
</style>
\ No newline at end of file
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import AiSummary from '@/components/base/Ai/AiSummary/index.vue'
<div class="summary-box">
<AiSummary />
</div>
`
}}
</pre>
<div class="summary-box">
<AiSummary>
<template #summary-content>
<div class="content-box">
我是插槽内容区域(忽略背景色)
</div>
</template>
</AiSummary>
</div>
</el-col>
</el-row>
</template>
<script setup>
import { ref } from 'vue'
import '@/styles/common.scss'
import AiSummary from '@/components/base/Ai/AiSummary/index.vue'
const span = 24
const aiContent = ref(`整个立法过程反映了美国政治的高度极化特点。民主党全员反对该法案,批评其"劫贫济富"。而共和党内部也有分歧,特别是在财政赤字问题上。马斯克多次公开批评该法案是"疯狂的支出计划"。整个立法过程反映了美国政治的高度极化特点。民主党全员反对该法案,批评其"劫贫济富"。而共和党内部也有分歧,特别是在财政赤字问题上。马斯克多次公开批评该法案是"疯狂的支出计划"。`)
</script>
<style lang="scss" scoped>
.summary-box {
margin-left: 10px;
margin-bottom: 10px;
width: 1019px;
height: 453px;
.content-box{
width: 100%;
height: 100%;
background: var(--color-primary-2);
}
}
</style>
\ No newline at end of file
......@@ -11,7 +11,7 @@
`}}
</pre>
<div class="chart-box">
<GraphChart :nodes="nodes" :links="links" layoutType="none">
<GraphChart :nodes="nodes" :links="links" layoutType="force">
</GraphChart>
</div>
</el-col>
......@@ -207,6 +207,7 @@ const links = ref([
{ source: 15, target: 7, label: { show: true, formatter: '合作', color: 'red', borderColor: 'red' } },
]);
</script>
<style lang="scss" scoped>
......@@ -218,5 +219,4 @@ const links = ref([
width: 800px;
height: 500px;
}
</style>
\ No newline at end of file
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import TimeTabPane from '@/components/base/TimeTabPane/index.vue'
<TimeTabPane @time-click="handleTimeClick" />
`
}}
</pre>
<div class="time-box">
<TimeTabPane @time-click="handleTimeClick" />
</div>
</el-col>
</el-row>
</template>
<script setup>
import { ref } from 'vue'
import '@/styles/common.scss'
import TimeTabPane from '@/components/base/TimeTabPane/index.vue'
const span = 12
const handleTimeClick = (val) => {
console.log('val',val);
}
</script>
<style lang="scss" scoped>
.time-box {
width: 700px;
height: 400px;
padding: 100px;
background: #F2F8FF;
border: 1px solid var(--bg-black-5);
}
</style>
\ No newline at end of file
<template>
<el-row class="wrapper layout-grid-line">
<el-col :span="span">
<pre>
{{
`
import TreeChart from '@/components/base/TreeChart/index.vue'
<div class="chart-box">
<TreeChart :treeData="treeData" />
</div>
`
}}
</pre>
<div class="chart-box">
<TreeChart :treeData="treeData" />
</div>
</el-col>
</el-row>
</template>
<script setup>
import { ref } from 'vue'
import '@/styles/common.scss'
import TreeChart from '@/components/base/TreeChart/index.vue'
import CompanyImg from "@/assets/icons/symbol.png";
const span = 12
const treeData = ref([
{
id: 1,
name: 'a1',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
children: [
{
id: 11,
name: 'b1',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 12,
name: 'b2',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 13,
name: 'b3',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
},
{
id: 14,
name: 'b4',
symbolSize: 30,
value: 5,
symbol: `image://${CompanyImg}`,
}
]
}
])
</script>
<style lang="scss" scoped>
.chart-box {
width: 700px;
height: 400px;
border: 1px solid var(--bg-black-5);
}
</style>
\ No newline at end of file
......@@ -11,10 +11,15 @@ const span = 24
<pre>
{{
`import WarnningPane from '@/components/base/WarningPane/index.vue';
<template>
<WarnningPane warnningLevel="特别重大风险" warnningContent="我是特别重大风险内容文字我是特别重大风险内容文字" />
</template>
`}}
<template>
<WarnningPane warnningLevel="特别重大风险" warnningContent="我是特别重大风险内容我是特别重大风险内容我是特别重大风险内容" />
<WarnningPane warnningLevel="重大风险" warnningContent="我是重大风险内容我是重大风险内容我是重大风险内容" />
<WarnningPane warnningLevel="较大风险" warnningContent="我是较大风险内容我是较大风险内容我是较大风险内容" />
<WarnningPane warnningLevel="一般风险" warnningContent="我是一般风险内容我是一般风险内容我是一般风险内容" />
<WarnningPane warnningLevel="低风险" warnningContent="我是低风险内容我是低风险内容我是低风险内容" />
</template>
`
}}
</pre>
<div class="warnning-box">
<WarnningPane warnningLevel="特别重大风险" warnningContent="我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字我是特别重大风险内容文字" />
......
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论