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

Merge remote-tracking branch 'remotes/origin/master' into pre

# Conflicts: # src/views/thinkTank/MultiThinkTankViewAnalysis/index.vue
流水线 #25 已失败 于阶段
in 6 秒
......@@ -36,6 +36,47 @@ function parseChartInterpretationArray(buffer) {
throw new Error("无法解析图表解读 JSON 数组");
}
/**
* 从数组结果中提取可展示的解读文本
* @param {unknown[]} arr
* @returns {string}
*/
function pickInterpretationText(arr) {
if (!Array.isArray(arr) || arr.length === 0) {
return "";
}
const first = arr[0] || {};
return (
first["解读"] ||
first["interpretation"] ||
first["analysis"] ||
first["content"] ||
""
);
}
/**
* 从非标准 JSON 文本中兜底提取“解读”字段(兼容单引号/双引号)
* 示例:
* [{'图表标题': '数量变化趋势', '解读': 'xxx'}]
* [{"图表标题":"数量变化趋势","解读":"xxx"}]
* @param {string} text
* @returns {string}
*/
function extractInterpretationFromLooseText(text) {
const raw = String(text || "");
if (!raw) {
return "";
}
const reg =
/["']解读["']\s*:\s*["']([\s\S]*?)["']\s*(?:[,}\]])/;
const m = raw.match(reg);
if (!m || !m[1]) {
return "";
}
return String(m[1]).replace(/\\n/g, "\n").trim();
}
/**
* 图表解读(SSE 流式)
* @param {object} data - 请求体
......@@ -44,9 +85,15 @@ function parseChartInterpretationArray(buffer) {
* @returns {Promise<{data: unknown[]}>}
*/
export function getChartAnalysis(data, options = {}) {
const { onChunk } = options;
const onDelta =
typeof options?.onChunk === "function"
? options.onChunk
: typeof options?.onInterpretationDelta === "function"
? options.onInterpretationDelta
: null;
return new Promise((resolve, reject) => {
let buffer = "";
let latestInterpretation = "";
let settled = false;
const abortController = new AbortController();
......@@ -119,9 +166,18 @@ export function getChartAnalysis(data, options = {}) {
buffer += raw;
}
// 兜底:非标准 JSON(如单引号 Python 风格)时,尝试直接从文本提取“解读”
const looseInterpretation = extractInterpretationFromLooseText(raw);
if (looseInterpretation) {
latestInterpretation = looseInterpretation;
safeResolve({ data: [{ 解读: looseInterpretation }] });
abortController.abort();
return;
}
// 每收到一条消息即回调,用于流式渲染
if (chunk && typeof onChunk === "function") {
onChunk(chunk);
if (chunk && onDelta) {
onDelta(chunk);
}
// 如果 buffer 已经拼完 markdown code fence,则提前解析并中断连接
......@@ -129,6 +185,10 @@ export function getChartAnalysis(data, options = {}) {
if (trimmed.endsWith("```")) {
try {
const arr = parseChartInterpretationArray(trimmed);
const interpretation = pickInterpretationText(arr);
if (interpretation) {
latestInterpretation = interpretation;
}
safeResolve({ data: arr });
abortController.abort();
} catch (_) { }
......@@ -137,8 +197,22 @@ export function getChartAnalysis(data, options = {}) {
onclose: () => {
try {
const arr = parseChartInterpretationArray(buffer);
const interpretation = pickInterpretationText(arr);
if (interpretation) {
latestInterpretation = interpretation;
}
safeResolve({ data: arr });
} catch (e) {
// 兜底:整体 buffer 不是标准 JSON(如单引号)时直接提取“解读”
const looseInterpretation = extractInterpretationFromLooseText(buffer);
if (looseInterpretation) {
safeResolve({ data: [{ 解读: looseInterpretation }] });
return;
}
if (latestInterpretation) {
safeResolve({ data: [{ 解读: latestInterpretation }] });
return;
}
safeReject(e);
}
},
......
......@@ -1156,6 +1156,15 @@
| -------- | -------- | ----- | -------- | -------- | ------ |
|areas|区域名称列表|query|false|array|string|
|researchTypeIds|研究类型ID列表|query|false|array|string|
|domainIds|科技领域 ID 列表(逗号分隔)|query|false|string||
|startDate|发布时间起 YYYY-MM-DD(与政策追踪发布时间逻辑一致)|query|false|string||
|endDate|发布时间止 YYYY-MM-DD|query|false|string||
|category|分类(如调查项目)|query|false|string||
|pageNum|页码|query|false|integer||
|pageSize|每页条数|query|false|integer||
|sortFun|排序|query|false|boolean||
|thinkTankId|智库 ID(详情页动态列表限定当前智库)|query|false|string||
|keyword|关键词搜索(智库动态)|query|false|string||
|token|Token Request Header|header|false|string||
......
// 智库概览信息
import request from "@/api/request.js";
import request, { getToken } from "@/api/request.js";
// 智库列表
export function getThinkTankList() {
......@@ -87,7 +87,11 @@ export function getHylyList() {
}
//获取智库报告
/**
* 智库概览/智库动态-智库报告、调查项目
* GET /api/thinkTankOverview/report
* 常用 query:pageNum, pageSize, sortFun, domainIds, startDate, endDate, category(调查项目), thinkTankId(详情页), keyword(动态搜索)
*/
export function getThinkTankReport(params) {
return request({
method: 'GET',
......@@ -158,6 +162,7 @@ export function getThinkDynamicsReport(params) {
// 智库领域观点分析(流式)
// [POST] 8.140.26.4:10029/report-domain-view-analysis
// 每次请求体:{ domain, report_view_list }(一个 domain);多领域由前端按领域循环多次调用
export function postReportDomainViewAnalysis(data) {
return request({
method: 'POST',
......@@ -167,6 +172,86 @@ export function postReportDomainViewAnalysis(data) {
})
}
/**
* 智库领域观点分析(真正流式,逐 chunk 回调)
* @param {object} data
* @param {{ onReasoningChunk?: (chunk: string) => void, onMessage?: (msg: any) => void }} handlers
*/
export async function postReportDomainViewAnalysisStream(data, handlers = {}) {
const { onReasoningChunk, onMessage } = handlers
const token = getToken()
const response = await fetch('/intelligent-api/report-domain-view-analysis', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { token } : {})
},
body: JSON.stringify(data)
})
if (!response.ok) {
throw new Error(`流式分析请求失败: ${response.status}`)
}
// 兜底:非流式返回时仍可读取文本继续后续解析
if (!response.body) {
return await response.text()
}
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let done = false
let pending = ''
let fullText = ''
while (!done) {
const result = await reader.read()
done = result.done
if (result.value) {
const chunkText = decoder.decode(result.value, { stream: !done })
fullText += chunkText
pending += chunkText
const lines = pending.split(/\r?\n/)
pending = lines.pop() ?? ''
for (const rawLine of lines) {
const line = String(rawLine || '').trim()
if (!line || !line.startsWith('data:')) continue
const jsonText = line.slice(5).trim()
if (!jsonText || jsonText === '[DONE]') continue
try {
const msg = JSON.parse(jsonText)
if (typeof onMessage === 'function') onMessage(msg)
if (msg?.type === 'reasoning' && msg?.chunk != null && typeof onReasoningChunk === 'function') {
const c = String(msg.chunk)
if (c) onReasoningChunk(c)
}
} catch (e) {
// 忽略非 JSON 数据行
}
}
}
}
// 处理最后一行残留
const last = String(pending || '').trim()
if (last.startsWith('data:')) {
const jsonText = last.slice(5).trim()
if (jsonText && jsonText !== '[DONE]') {
try {
const msg = JSON.parse(jsonText)
if (typeof onMessage === 'function') onMessage(msg)
if (msg?.type === 'reasoning' && msg?.chunk != null && typeof onReasoningChunk === 'function') {
const c = String(msg.chunk)
if (c) onReasoningChunk(c)
}
} catch (e) {
// ignore
}
}
}
return fullText
}
//提出建议领域分布
export function getThinkPolicyIndustry(params) {
......@@ -240,18 +325,12 @@ export function getThinkTankInfoBranch(params) {
})
}
//获取经费来源统计
export function getThinkTankFundsTotal(params) {
return request({
method: 'GET',
url: `/api/thinkTankInfo/fundsTotal/${params}`,
})
}
//获取经费来源
export function getThinkTankFundsSource(params) {
return request({
method: 'GET',
url: `/api/thinkTankInfo/fundsSource/${params}`,
url: `/api/thinkTankInfo/fundsStatistics/${params}`,
})
}
......@@ -265,9 +344,17 @@ export function getThinkTankResearchAreae(params) {
//获取核心研究人员
export function getThinkTankPerson(params) {
const { thinkTankId, currentPage, pageSize } = params
return request({
method: 'GET',
url: `/api/thinkTankInfo/person/${params}`,
url: `/api/thinkTankInfo/person/page`,
params: {
currentPage,
pageNum: currentPage,
page: currentPage,
pageSize,
thinkTankId
}
})
}
......
//企业主页
import { useGotoPage } from "../common.js";
const companyPages = () => import('@/views/companyPages/index.vue')
const companyPages2 = () => import('@/views/companyPages2/index.vue')
......@@ -28,4 +29,9 @@ const companyPagesRoutes = [
]
export function useGotoCompanyPages() {
const gotoPage = useGotoPage();
return (id, isNewTabs = true) => gotoPage("/companyPages/" + id, {}, isNewTabs)
}
export default companyPagesRoutes
\ No newline at end of file
......@@ -17,7 +17,29 @@ const setChart = (option, chartId, allowClick, selectParam) => {
// 判断点击的是否为饼图的数据项
if (params.componentType === 'series' && params.seriesType === 'pie') {
console.log('点击的扇形名称:', params.name);
selectParam.domains = JSON.stringify([params.name])
if (selectParam.key === '领域') {
selectParam.domains = JSON.stringify([params.name])
} else if (selectParam.key === '议院委员会') {
if (params.name === '众议院' || params.name === '参议院') {
selectParam.selectedCongress = params.name
selectParam.selectedOrg = ''
} else {
selectParam.selectedOrg = params.name
selectParam.selectedCongress = ''
}
}
const route = router.resolve({
path: "/dataLibrary/countryBill",
query: selectParam
});
window.open(route.href, "_blank");
} else if (params.componentType === 'series' && params.seriesType === 'bar') {
if (params.name === '已立法') {
selectParam.selectedStauts = 1
} else {
selectParam.selectedStauts = 0
}
const route = router.resolve({
path: "/dataLibrary/countryBill",
query: selectParam
......
......@@ -866,14 +866,20 @@ const handleBox7Data = async () => {
if (t1 !== t2) return t1 - t2;
return (b.value ?? 0) - (a.value ?? 0);
});
const selectParam = {
moduleType: '国会法案',
key: '议院委员会',
selectedDate: box7selectetedTime.value,
}
const box7Chart = getDoublePieChart(data1, data2);
setChart(box7Chart, "box7Chart");
setChart(box7Chart, "box7Chart", true, selectParam);
box7AiData.value = { inner: data1, outer: data2 };
} else {
// 接口异常(如500)时,清空图表数据以避免报错或显示错误信息
box7HasData.value = false;
setChart({}, "box7Chart");
setChart({}, "box7Chart", true, selectParam);
box7AiData.value = { inner: [], outer: [] };
}
} catch (error) {
......@@ -991,11 +997,9 @@ const handleBox9Data = async () => {
);
const selectParam = {
moduleType: '国会法案',
proposedDateStart: box9selectetedTime.value,
selectedDate: box9selectetedTime.value,
status: box9LegislativeStatus.value === '提出法案' ? 0 : 1,
isInvolveCn: 1
}
box9ChartInstance = setChart(box9Chart, "box9Chart", true, selectParam);
}
......@@ -1172,9 +1176,17 @@ const handleBox8Data = async () => {
box8HasData.value = true;
box8Summary.value = countMap.get("完成立法") || 0;
box8StageList.value = stages;
const selectParam = {
moduleType: '国会法案',
key: '领域',
selectedDate: box8selectetedTime.value,
isInvolveCn: 1
}
await nextTick();
const box8Chart = getBox8ChartOption(stages);
box8ChartInstance = setChart(box8Chart, "box8Chart");
box8ChartInstance = setChart(box8Chart, "box8Chart", true, selectParam);
} else {
const data = box8MockDataByYear[box8selectetedTime.value];
if (data && data.stages && data.stages.length > 0) {
......@@ -1183,12 +1195,12 @@ const handleBox8Data = async () => {
box8StageList.value = data.stages;
await nextTick();
const box8Chart = getBox8ChartOption(data.stages);
box8ChartInstance = setChart(box8Chart, "box8Chart");
box8ChartInstance = setChart(box8Chart, "box8Chart", true, selectParam);
} else {
box8HasData.value = false;
box8Summary.value = 0;
box8StageList.value = [];
setChart({}, "box8Chart");
setChart({}, "box8Chart", true, selectParam);
}
}
} catch (error) {
......@@ -1200,12 +1212,12 @@ const handleBox8Data = async () => {
box8StageList.value = data.stages;
await nextTick();
const box8Chart = getBox8ChartOption(data.stages);
box8ChartInstance = setChart(box8Chart, "box8Chart");
box8ChartInstance = setChart(box8Chart, "box8Chart" , true, selectParam);
} else {
box8HasData.value = false;
box8Summary.value = 0;
box8StageList.value = [];
setChart({}, "box8Chart");
setChart({}, "box8Chart", true, selectParam);
}
}
};
......
......@@ -23,7 +23,7 @@
</div>
<div class="check-box-right">
<el-checkbox v-model="isInvolveCn" class="involve-checkbox" @change="handleInvolveCnChange">
{{ '只看涉华委员会' }}
{{ '只看涉华法案' }}
</el-checkbox>
</div>
</div>
......@@ -217,6 +217,7 @@ const staticsDemensionList = ref([
},
])
// 当前维度下的图表列表
const curChartTypeList = computed(() => {
let arr = staticsDemensionList.value.filter(item => item.active)
return arr[0].chartTypeList
......@@ -225,6 +226,7 @@ const curChartTypeList = computed(() => {
// 当前维度
const curDemension = ref('提案时间')
// 点击维度item
const handleClickDemensionItem = (val) => {
activeChart.value = ''
staticsDemensionList.value.forEach(item => {
......@@ -257,25 +259,25 @@ const timeList = ref([
// 激活的标签列表
const activeTagList = computed(() => {
const arr = []
if (selectedArea.value && selectedArea !== '全部领域') {
if (selectedArea.value && selectedArea.value !== '全部领域') {
arr.push(selectedArea.value)
}
if (selectedDate.value) {
arr.push(selectedDate.value)
}
if (selectedParty.value && selectedParty !== '全部党派') {
if (selectedParty.value && selectedParty.value !== '全部党派') {
arr.push(selectedParty.value)
}
if (selectedCongress.value && selectedCongress !== '全部议院') {
if (selectedCongress.value && selectedCongress.value !== '全部议院') {
arr.push(selectedCongress.value)
}
if (selectedOrg.value && selectedOrg !== '全部委员会') {
if (selectedOrg.value && selectedOrg.value !== '全部委员会') {
arr.push(selectedOrg.value)
}
if (selectedmember.value && selectedmember !== '全部议员') {
if (selectedmember.value && selectedmember.value !== '全部议员') {
arr.push(selectedmember.value)
}
if (selectedStauts.value && selectedStauts !== '全部阶段') {
if (selectedStauts.value && selectedStauts.value !== '全部阶段') {
arr.push(selectedStauts.value)
}
if (isInvolveCn.value) {
......@@ -298,67 +300,6 @@ const handleSwitchActiveChart = val => {
activeChart.value = val.name
}
const lineChartData = ref({
dataX: ['2025-08', '2025-09', '2025-10', '2025-11', '2025-12', '2026-01', '2026-02',],
dataY: [123, 51, 64, 72, 58, 69, 105]
})
const pieChartData = ref([
{
name: '核',
value: 24
},
{
name: '极地',
value: 24
},
{
name: '新材料',
value: 24
},
{
name: '深海',
value: 30
},
{
name: '海洋',
value: 31
},
{
name: '先进制造',
value: 31
},
{
name: '生物科技',
value: 32
},
{
name: '新能源',
value: 35
},
{
name: '集成电路',
value: 16
},
{
name: '新一代通信网络',
value: 55
},
{
name: '量子科技',
value: 33
},
{
name: '人工智能',
value: 55
}
])
const barChartData = ref({
dataX: ['2025-08', '2025-09', '2025-10', '2025-11', '2025-12', '2026-01', '2026-02',],
dataY: [123, 51, 64, 72, 58, 69, 105]
})
const radarChartData = ref({
title: [
{
......@@ -425,9 +366,9 @@ const operationList = ref([
},
])
// 领域
// 科技领域
const areaPlaceHolder = ref('请选择领域')
const selectedArea = ref('')
const selectedArea = ref('全部领域')
const areaList = ref([
{
name: '全部领域',
......@@ -494,7 +435,6 @@ const areaList = ref([
id: '其他'
},
])
const handleSelectArea = (value) => {
selectedArea.value = value
}
......@@ -529,9 +469,8 @@ const dateList = ref([
},
])
const customTime = ref('') // 自定义时间
const handleCustomDate = value => {
console.log('value', value);
// console.log('value', value);
customTime.value = value
}
......@@ -539,8 +478,12 @@ const handleSelectDate = (value) => {
selectedDate.value = value
}
// 党派列表
// 所属党派
const partyList = ref([
{
name: '全部党派',
id: '全部党派'
},
{
name: '共和党',
id: '共和党'
......@@ -554,8 +497,7 @@ const partyList = ref([
id: '其他'
},
])
const selectedParty = ref('')
const selectedParty = ref('全部党派')
const partyPlaceHolder = ref('请选择党派')
const handleSelectParty = value => {
......@@ -564,6 +506,10 @@ const handleSelectParty = value => {
// 议院列表
const congressList = ref([
{
name: '全部议院',
id: '全部议院'
},
{
name: '众议院',
id: '众议院'
......@@ -573,45 +519,15 @@ const congressList = ref([
id: '参议院'
}
])
const selectedCongress = ref('')
const selectedCongress = ref('全部议院')
const congressPlaceHolder = ref('请选择议院')
const handleSelectCongress = value => {
selectedCongress.value = value
}
// 议院列表
const statusList = ref([
{
name: '提出',
id: '0'
},
{
name: '通过',
id: '1'
}
])
const selectedStauts = ref('')
const statusPlaceHolder = ref('请选择立法阶段')
const handleSelectStauts = value => {
selectedStauts.value = value
}
// 是否涉华
const isInvolveCn = ref(true)
const handleInvolveCnChange = () => {
}
// 委员会
const orgList = ref([
])
const selectedOrg = ref('')
const orgList = ref([])
const selectedOrg = ref('全部委员会')
const orgPlaceHolder = ref('请选择委员会')
const handleSelectOrg = value => {
......@@ -630,6 +546,7 @@ const handleGetOrgList = async () => {
id: item.departmentName
}
})
orgList.value = [{ name: '全部委员会', id: '全部委员会' }, ...orgList.value]
}
} catch (error) {
......@@ -639,12 +556,9 @@ const handleGetOrgList = async () => {
}
// 提出议员
const memberList = ref([
])
const selectedmember = ref('')
const memberList = ref([])
const selectedmember = ref('全部议员')
const memberPlaceHolder = ref('请选择议员')
const handleSelectMember = value => {
selectedmember.value = value
}
......@@ -661,6 +575,7 @@ const handleGetMemberList = async () => {
id: item.memberName
}
})
memberList.value = [{ name: '全部议员', id: '全部议员' }, ...memberList.value]
}
} catch (error) {
......@@ -669,6 +584,34 @@ const handleGetMemberList = async () => {
}
}
// 阶段列表
const statusList = ref([
{
name: '全部阶段',
id: '全部阶段'
},
{
name: '提出',
id: '0'
},
{
name: '通过',
id: '1'
}
])
const selectedStauts = ref('全部阶段')
const statusPlaceHolder = ref('请选择立法阶段')
const handleSelectStauts = value => {
selectedStauts.value = value
}
// 是否涉华
const isInvolveCn = ref(true)
const handleInvolveCnChange = () => {
}
// 展开全部 / 收起
const isFolderAll = ref(false)
const handleSwitchFolderAll = () => {
......@@ -683,8 +626,6 @@ const tableRef = ref(null)
const tableData = ref([
])
const releaseTimeList = ref([
{
label: "按发布时间倒序",
......@@ -721,15 +662,16 @@ const fetchTableData = async () => {
size: pageSize.value,
keyword: '',
type: 1, // type 1= 法案 2= 政令 3 =智库 4=智库报告 5=实体清单【制裁记录】 6= 人物 7= 机构 8=新闻 9= 社媒
domains: selectedArea.value ? [selectedArea.value] : null,
domains: selectedArea.value === '全部领域' ? null : [selectedArea.value],
proposedDateStart: selectedDate.value ? selectedDate.value : null,
proposedDateEnd: null,
affiliation: selectedParty.value ? selectedParty.value : null,
originChamber: selectedCongress.value ? selectedCongress.value : null,
originDepart: selectedOrg.value ? selectedOrg.value : null,
sponsorPersonName: selectedmember.value ? selectedmember.value : null,
affiliation: selectedParty.value === '全部党派' ? null : selectedParty.value,
originChamber: selectedCongress.value === '全部议院' ? null : selectedCongress.value,
originDepart: selectedOrg.value === '全部委员会' ? null : selectedOrg.value,
sponsorPersonName: selectedmember.value === '全部议员' ? null : selectedmember.value,
status: selectedStauts.value === '通过' ? 1 : 0,
sleStatus: isInvolveCn ? 1 : 0
sleStatus: isInvolveCn ? 1 : 0,
description: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序
}
try {
const res = await search(params)
......@@ -892,13 +834,22 @@ watch(tableData, () => {
// 当前图表数据
const curChartData = ref(null)
// 跳转到当前页 初始化筛选条件
const initParam = () => {
selectedArea.value = route.query.domains ? JSON.parse(route.query.domains)[0] : '全部领域'
selectedDate.value = route.query.selectedDate,
isInvolveCn.value = route.query.isInvolveCn ? true : false
selectedStauts.value = route.query.status === '1' ? '通过' : '提出'
selectedCongress.value = route.query.selectedCongress ? route.query.selectedCongress : '全部议院'
selectedOrg.value = route.query.selectedOrg ? route.query.selectedOrg : '全部委员会'
}
onMounted(async () => {
handleGetOrgList()
handleGetMemberList()
selectedArea.value = route.query.domains ? JSON.parse(route.query.domains)[0] : ''
selectedDate.value = route.query.proposedDateStart,
isInvolveCn.value = route.query.isInvolveCn ? true : false
selectedStauts.value = route.query.status === '1' ? '通过' : '提出'
initParam()
// 初始化
await fetchTableData()
......
......@@ -39,7 +39,7 @@ const handleClickItem = (item) => {
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
gap: 8px;
......
......@@ -91,6 +91,8 @@ import Fishbone from "./fishbone.vue";
import { getHorizontalBarChart2 } from "../../utils/charts";
import { getDomainDistribution, getChainEntities, getChainInfoByDomainId, getCnEntityOnChain } from "@/api/exportControl";
import { useRoute, useRouter } from "vue-router";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
const route = useRoute();
const router = useRouter();
const buttonList = ref([]);
......@@ -155,13 +157,14 @@ const listData = ref([
const horizontalBarOptions = shallowRef({});
const handleEttClick = item => {
const route = router.resolve({
name: "companyPages",
params: {
id: item.id
}
});
window.open(route.href, "_blank");
// const route = router.resolve({
// name: "companyPages",
// params: {
// id: item.id
// }
// });
// window.open(route.href, "_blank");
gotoCompanyPages(item.id);
};
// 处理点击事件
const handleChainClick = async chainId => {};
......
......@@ -95,6 +95,8 @@ import Hint from "./hint.vue";
import { getEntitiesChangeCount, getEntitiesGrowthTrend, getEntitiesUpdateCount, getKeyEntityList } from "@/api/exportControl";
import _ from "lodash";
import { useRoute, useRouter } from "vue-router";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
const route = useRoute();
const router = useRouter();
const line1Option = shallowRef({});
......@@ -341,13 +343,14 @@ const handleDomainChange = async domain => {
const handleOrgClick = item => {
console.log(item);
const route = router.resolve({
name: "companyPages",
params: {
id: item.id
}
});
window.open(route.href, "_blank");
// const route = router.resolve({
// name: "companyPages",
// params: {
// id: item.id
// }
// });
// window.open(route.href, "_blank");
gotoCompanyPages(item.id);
};
</script>
......
......@@ -74,6 +74,8 @@ import _ from "lodash";
import Hint from "./hint.vue";
import { getEntityFinancing, getEntityMarketValue, getKeyListedEntityList, getSanStrength } from "@/api/exportControl";
import { useRoute, useRouter } from "vue-router";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
const route = useRoute();
const router = useRouter();
const value3 = ref("");
......@@ -304,13 +306,14 @@ watch(
const handleOrgClick = item => {
console.log(item);
const route = router.resolve({
name: "companyPages",
params: {
id: item.id
}
});
window.open(route.href, "_blank");
// const route = router.resolve({
// name: "companyPages",
// params: {
// id: item.id
// }
// });
// window.open(route.href, "_blank");
gotoCompanyPages(item.id);
};
</script>
......
......@@ -200,6 +200,8 @@ import {
import _ from "lodash";
import { useRoute, useRouter } from "vue-router";
import { formatAnyDateToChinese } from "../../utils";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
const route = useRoute();
const router = useRouter();
const organizationInfo = shallowRef({});
......@@ -406,13 +408,14 @@ const panel5TypeMap = {
const handleOrgClick = item => {
console.log(item);
if (item.entityType != 2) return;
const route = router.resolve({
name: "companyPages",
params: {
id: item.id
}
});
window.open(route.href, "_blank");
// const route = router.resolve({
// name: "companyPages",
// params: {
// id: item.id
// }
// });
// window.open(route.href, "_blank");
gotoCompanyPages(item.id);
};
// 处理"查看更多"点击事件
......
......@@ -65,7 +65,7 @@
:subtitle="item.nameAbbr"
:description="item.description"
:quantity="item.postCount"
unit=""
unit=""
:color="infoListColor[index]"
@click="handleToEntityListNoId(item)"
/>
......@@ -715,6 +715,10 @@ import { ElMessage, ElMessageBox } from "element-plus";
import { DArrowRight, Warning, Search } from "@element-plus/icons-vue";
import EChart from "@/components/Chart/index.vue";
import { TAGTYPE } from "@/public/constant";
import { useGotoCompanyPages } from "@/router/modules/company";
import { useGotoNewsDetail } from "@/router/modules/news";
const gotoCompanyPages = useGotoCompanyPages();
const gotoNewsDetail = useGotoNewsDetail();
import { useRouter } from "vue-router";
......@@ -868,14 +872,15 @@ const handleCompClick = item => {
// console.log("item", item);
// if (item.entityType != 2) return;
window.sessionStorage.setItem("curTabName", item.name);
const route = router.resolve({
name: "companyPages",
params: {
id: item.id,
sanTypeId: item.sanTypeId
}
});
window.open(route.href, "_blank");
gotoCompanyPages(item.entityId);
// const route = router.resolve({
// name: "companyPages",
// params: {
// id: item.id,
// sanTypeId: item.sanTypeId
// }
// });
// window.open(route.href, "_blank");
};
const tagsType = ["primary", "success", "warning", "danger"];
......@@ -1075,14 +1080,15 @@ const processYearDomainCountData = yearDomainCountData => {
const handleEntityClick = item => {
console.log("item", item);
window.sessionStorage.setItem("curTabName", item.name || item.entityNameZh);
const route = router.resolve({
name: "companyPages",
params: {
// startTime: item.startTime,
id: item.entityId
}
});
window.open(route.href, "_blank");
gotoCompanyPages(item.entityId);
// const route = router.resolve({
// name: "companyPages",
// params: {
// // startTime: item.startTime,
// id: item.entityId
// }
// });
// window.open(route.href, "_blank");
};
const carouselRef = ref(null);
......@@ -1353,8 +1359,8 @@ const fetchSanctionList = async () => {
const tags = Array.isArray(item.techDomains)
? item.techDomains
: item.techDomain
? [item.techDomain]
: item.techDomainList || [];
? [item.techDomain]
: item.techDomainList || [];
const fullTime = item.startTime
? formatAnyDateToChinese(item.startTime)
......@@ -1384,8 +1390,8 @@ const fetchSanctionList = async () => {
countTag: item.cnEntityCount
? `${item.cnEntityCount}家中国实体`
: item.ruleOrgCount
? `${item.ruleOrgCount}家关联实体`
: item.countTag || ""
? `${item.ruleOrgCount}家关联实体`
: item.countTag || ""
};
});
totalAll.value = res.totalElements;
......@@ -1717,7 +1723,8 @@ const handleSanc = item => {
const route = router.resolve({
path: "/exportControl/singleSanction",
query: {
id: item.id
id: item.id,
sanTypeId: activeResourceTabItem.value.id
}
});
window.open(route.href, "_blank");
......@@ -1737,14 +1744,15 @@ const handleToMoreNews = () => {
const handleNewsInfoClick = item => {
console.log("点击了社交媒体消息的更多信息:", item);
const route = router.resolve({
path: "/newsAnalysis",
query: {
newsId: item.newsId
}
});
window.open(route.href, "_blank");
// 应该跳转至哪儿???
// const route = router.resolve({
// path: "/newsAnalysis",
// query: {
// newsId: item.newsId
// }
// });
// window.open(route.href, "_blank");
gotoNewsDetail(item.newsId);
};
// 切换当前出口管制政策
......@@ -2324,7 +2332,8 @@ const handleMediaClick = item => {
overflow-y: auto;
.home-top-bg {
background: url("./assets/images/background.png"),
background:
url("./assets/images/background.png"),
linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
background-size: 100% 100%;
position: absolute;
......
......@@ -82,7 +82,8 @@ import { ref, computed, watch } from "vue";
import router from "@/router";
import { Close } from "@element-plus/icons-vue";
import defaultIcon from "@/assets/icons/default-icon1.png";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
const props = defineProps({
modelValue: {
type: Boolean,
......@@ -119,9 +120,9 @@ const tableData = computed(() => {
...item,
name: item.orgName,
domains: item.techDomains || [],
equityRatio: item.equityRatio ? (item.equityRatio * 100).toFixed(2) + '%' : '--',
location: '--',
revenue: item.revenue || '--'
equityRatio: item.equityRatio ? (item.equityRatio * 100).toFixed(2) + "%" : "--",
location: "--",
revenue: item.revenue || "--"
}));
});
......@@ -154,16 +155,16 @@ const getTagStyle = tag => {
// 跳转公司详情页
const handleCompClick = item => {
console.log("item", item);
window.sessionStorage.setItem('curTabName', item.entityNameZh || item.entityName)
const route = router.resolve({
name: "companyPages",
params: {
id: item.id
}
});
window.open(route.href, "_blank");
window.sessionStorage.setItem("curTabName", item.entityNameZh || item.entityName);
gotoCompanyPages(item.id);
// const route = router.resolve({
// name: "companyPages",
// params: {
// id: item.id
// }
// });
// window.open(route.href, "_blank");
};
</script>
<style lang="scss" scoped>
......@@ -363,4 +364,4 @@ const handleCompClick = item => {
}
}
}
</style>
\ No newline at end of file
</style>
......@@ -6,7 +6,7 @@
:key="index"
class="tab-item"
:class="{ active: index === activeIndex }"
@click="activeIndex = index"
@click="handleClickTab(index)"
>
{{ item }}
<span v-if="index === activeIndex" class="arrow"></span>
......@@ -14,10 +14,10 @@
</div>
<div class="content-box">
<introductionPage
v-show="activeIndex === 1"
v-show="activeIndex == 1"
@update-entity-info="data => $emit('update-entity-info', data)"
></introductionPage>
<listPage v-show="activeIndex === 0"></listPage>
<listPage v-show="activeIndex == 0"></listPage>
</div>
</div>
</template>
......@@ -31,6 +31,11 @@ const emit = defineEmits(["update-entity-info"]);
const activeTab = ref(["实体清单列表", "实体清单简介"]);
const activeIndex = ref(0);
const handleClickTab = index => {
console.log(index);
activeIndex.value = index;
};
</script>
<style scoped lang="scss">
......
......@@ -38,9 +38,7 @@
<div class="info-row">
<div class="label">制裁领域:</div>
<div class="value tags">
<span class="tag" v-for="(domain, index) in formattedData.domains" :key="index">{{
domain
}}</span>
<AreaTag v-for="(domain, index) in formattedData.domains" :key="index" :tagName="domain" />
</div>
</div>
</div>
......@@ -116,7 +114,7 @@
<el-input
v-model="searchKeyword"
placeholder="搜索实体"
style="width: 150px"
style="width: 150px; border: 1px solid rgba(170, 173, 177, 0.4); border-radius: 5px"
:suffix-icon="Search"
/>
</div>
......@@ -219,6 +217,7 @@
import { ref, defineProps, computed, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import AreaTag from "@/components/base/AreaTag/index.vue";
import { DArrowRight, Search } from "@element-plus/icons-vue";
import { debounce } from "lodash";
import title from "../../assets/title.png";
......@@ -232,6 +231,9 @@ import {
import RuleSubsidiaryDialog from "../../../v2.0EntityList/components/sanctionsOverview/components/listPage/RuleSubsidiaryDialog.vue";
import { useRoute } from "vue-router";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
const route = useRoute();
// 跳转公司详情页
const handleCompClick = item => {
......@@ -240,8 +242,9 @@ const handleCompClick = item => {
return;
}
window.sessionStorage.setItem("curTabName", item.name);
const curRoute = router.resolve({ name: "companyPages", params: { id: item.entityId } });
window.open(curRoute.href, "_blank");
gotoCompanyPages(item.entityId);
// const curRoute = router.resolve({ name: "companyPages", params: { id: item.entityId } });
// window.open(curRoute.href, "_blank");
};
// 跳转发布机构详情页
......
......@@ -330,11 +330,11 @@ onMounted(async () => {
.box1 {
margin-top: 19px;
width: 1600px;
height: 1173px;
.box1-main {
margin-top: 8px;
height: 1097px;
min-height: 778px;
padding-left: 21px;
padding-right: 50px;
padding-bottom: 21px;
......@@ -421,7 +421,7 @@ onMounted(async () => {
.item-box {
width: 506px;
height: 100%;
min-height: 639.2px;
border-top: 1px solid rgb(234, 236, 238);
.item {
......
import * as echarts from 'echarts'
/** 政策追踪「研究领域变化趋势」图例分页:每页条数(与概览数量变化趋势逻辑一致,条数按产品要求为 4) */
export const POLICY_TRACKING_LEGEND_PAGE_SIZE = 4
const colorList = [
'rgba(5, 95, 194, 1)',
'rgba(19, 168, 168, 1)',
......@@ -35,22 +32,16 @@ const parseRgba = (colorStr) => {
/**
* @param {{ title: unknown[], data: Array<{ name: string, value: unknown[], color?: string }> }} chartInput
* @param {{ legendShowCount?: number, legendPageIndex?: number }} [options]
*/
const getMultiLineChart = (chartInput, options = {}) => {
const getMultiLineChart = (chartInput) => {
const title = chartInput.title
const series = chartInput.data || []
const legendShowCount =
typeof options.legendShowCount === 'number' && options.legendShowCount > 0
? options.legendShowCount
: POLICY_TRACKING_LEGEND_PAGE_SIZE
const rawPageIndex = Number(options.legendPageIndex) || 0
const allNames = series.map((item) => item.name)
const pageCount = Math.max(1, Math.ceil(allNames.length / legendShowCount))
const legendPageIndex = Math.min(Math.max(0, rawPageIndex), pageCount - 1)
const legendStart = legendPageIndex * legendShowCount
const legendData = allNames.slice(legendStart, legendStart + legendShowCount)
const lineSize = Math.ceil(allNames.length / 3)
const legendLine1 = allNames.slice(0, lineSize)
const legendLine2 = allNames.slice(lineSize, lineSize * 2)
const legendLine3 = allNames.slice(lineSize * 2)
const xCount = Array.isArray(title) ? title.length : 0
const labelFontSize = xCount > 8 ? 10 : xCount > 5 ? 11 : 12
......@@ -91,31 +82,71 @@ const getMultiLineChart = (chartInput, options = {}) => {
},
/* 贴满 #box3Chart:四边 0,由 containLabel 在网格内为轴文字留位,避免左侧/底部大块留白 */
grid: {
top: 50,
top: 92,
right: 10,
bottom: 0,
left: 20,
containLabel: true
},
legend: {
show: true,
type: 'plain',
data: legendData,
top: 4,
left: 'center',
icon: 'circle',
textStyle: {
fontFamily: 'Source Han Sans CN',
fontWeight: 400,
fontSize: 14,
lineHeight: 24,
letterSpacing: 0,
align: 'left',
color: 'rgb(95, 101, 108)'
legend: [
{
show: true,
type: 'plain',
data: legendLine1,
top: 4,
left: 'center',
icon: 'circle',
textStyle: {
fontFamily: 'Source Han Sans CN',
fontWeight: 400,
fontSize: 14,
lineHeight: 24,
letterSpacing: 0,
align: 'left',
color: 'rgb(95, 101, 108)'
},
itemWidth: 12,
itemHeight: 12
},
itemWidth: 12,
itemHeight: 12
},
{
show: legendLine2.length > 0,
type: 'plain',
data: legendLine2,
top: 30,
left: 'center',
icon: 'circle',
textStyle: {
fontFamily: 'Source Han Sans CN',
fontWeight: 400,
fontSize: 14,
lineHeight: 24,
letterSpacing: 0,
align: 'left',
color: 'rgb(95, 101, 108)'
},
itemWidth: 12,
itemHeight: 12
},
{
show: legendLine3.length > 0,
type: 'plain',
data: legendLine3,
top: 56,
left: 'center',
icon: 'circle',
textStyle: {
fontFamily: 'Source Han Sans CN',
fontWeight: 400,
fontSize: 14,
lineHeight: 24,
letterSpacing: 0,
align: 'left',
color: 'rgb(95, 101, 108)'
},
itemWidth: 12,
itemHeight: 12
}
],
color: colorList,
xAxis: [
{
......@@ -142,6 +173,16 @@ const getMultiLineChart = (chartInput, options = {}) => {
yAxis: [
{
type: 'value',
name: '数量',
nameLocation: 'end',
nameGap: 20,
nameTextStyle: {
color: 'rgb(132, 136, 142)',
fontFamily: 'Source Han Sans CN',
fontWeight: 400,
fontSize: 11,
padding: [0, 0, 0, -20] // 👈 这个是左移 4px(上、右、下、左)
},
splitNumber: 4,
axisLabel: {
color: 'rgb(132, 136, 142)',
......
......@@ -21,16 +21,17 @@
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchTypeIds)"
@change="val => handleToggleAll(val, researchTypeIds)">
全部领域
<el-checkbox-group
class="checkbox-group"
:model-value="selectedResearchIds"
@change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox>
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchIds" :label="type.id"
class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
<el-checkbox v-for="type in researchTypeList" :key="type.id" class="filter-checkbox" :label="type.id">
{{ type.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
<div class="select-time-box">
......@@ -39,16 +40,17 @@
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchTimeIds)"
@change="val => handleToggleAll(val, researchTimeIds)">
全部时间
<el-checkbox-group
class="checkbox-group"
:model-value="selectedResearchTimeIds"
@change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }}
</el-checkbox>
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTimeIds"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
<el-checkbox v-for="type in researchTimeList" :key="type.id" class="filter-checkbox" :label="type.id">
{{ type.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
<!-- <div class="input-main">
<el-input placeholder="输入作者名字" v-model="author" @input="handleGetThinkDynamicsReport()" />
......@@ -63,7 +65,7 @@
<img :src="item.imageUrl" alt="" />
</div>
<div class="footer-card-title">
{{ item.name }}
<span v-html="highlightText(item.name)"></span>
</div>
<div class="footer-card-footer">
<div class="time">{{ item.times }}</div>
......@@ -84,7 +86,14 @@
</div>
</template>
<script setup>
import { computed, ref, toRefs, watch } from "vue";
import { ref, toRefs, watch } from "vue";
import {
RESOURCE_FILTER_ALL_AREA,
RESOURCE_FILTER_ALL_TIME,
normalizeExclusiveAllOption,
stripAllAreaForRequest,
stripAllTimeForRequest
} from "../../../utils/resourceLibraryFilters";
const props = defineProps({
researchTypeList: {
......@@ -114,6 +123,10 @@ const props = defineProps({
currentPage: {
type: Number,
default: 1
},
searchKeyword: {
type: String,
default: ""
}
});
......@@ -127,43 +140,58 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
const { researchTypeList, researchTimeList, curFooterList, total, currentPage } = toRefs(props);
const selectedResearchIds = ref([]);
const selectedResearchTimeIds = ref([]);
const selectedResearchIds = ref([RESOURCE_FILTER_ALL_AREA]);
const selectedResearchTimeIds = ref([RESOURCE_FILTER_ALL_TIME]);
const escapeHtml = (text) => {
return String(text ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
};
const escapeRegExp = (text) => {
return String(text ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
const highlightText = (text) => {
const safeText = escapeHtml(text);
const keyword = (props.searchKeyword || "").trim();
if (!keyword) return safeText;
const pattern = new RegExp(`(${escapeRegExp(keyword)})`, "gi");
return safeText.replace(pattern, '<span class="keyword-highlight">$1</span>');
};
// 父组件更新时同步到子组件
watch(
() => props.selectedFilters,
val => {
selectedResearchIds.value = val?.researchTypeIds ? [...val.researchTypeIds] : [];
selectedResearchTimeIds.value = val?.researchTimeIds ? [...val.researchTimeIds] : [];
selectedResearchIds.value =
val?.researchTypeIds?.length > 0
? [...val.researchTypeIds]
: [RESOURCE_FILTER_ALL_AREA];
selectedResearchTimeIds.value =
val?.researchTimeIds?.length > 0
? [...val.researchTimeIds]
: [RESOURCE_FILTER_ALL_TIME];
},
{ immediate: true, deep: true }
);
const buildSelectedFiltersPayload = () => ({
researchTypeIds: [...selectedResearchIds.value],
researchTimeIds: [...selectedResearchTimeIds.value],
researchTypeIds: stripAllAreaForRequest(selectedResearchIds.value),
researchTimeIds: stripAllTimeForRequest(selectedResearchTimeIds.value),
researchHearingIds: []
});
const researchTypeIds = computed(() => (researchTypeList.value || []).map(item => item.id));
const researchTimeIds = computed(() => (researchTimeList.value || []).map(item => item.id));
const getTargetSelection = ids => (ids === researchTypeIds.value ? selectedResearchIds : selectedResearchTimeIds);
const isGroupAllSelected = ids =>
ids.length > 0 && ids.every(id => getTargetSelection(ids).value.includes(id));
const handleAreaGroupChange = (val) => {
selectedResearchIds.value = normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_AREA);
handleGetThinkDynamicsReport();
};
const handleToggleAll = (checked, ids) => {
if (!ids.length) return;
const targetSelection = getTargetSelection(ids);
const nextSelected = new Set(targetSelection.value);
if (checked) {
ids.forEach(id => nextSelected.add(id));
} else {
ids.forEach(id => nextSelected.delete(id));
}
targetSelection.value = [...nextSelected];
const handleTimeGroupChange = (val) => {
selectedResearchTimeIds.value = normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_TIME);
handleGetThinkDynamicsReport();
};
......@@ -326,6 +354,10 @@ const handleToReportDetail = item => {
}
}
:deep(.keyword-highlight) {
background-color: #fff59d;
}
.right {
width: 1284px;
height: 1377px;
......
......@@ -21,16 +21,17 @@
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchTypeIds)"
@change="val => handleToggleAll(val, researchTypeIds)">
全部领域
<el-checkbox-group
class="checkbox-group"
:model-value="selectedResearchIds"
@change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox>
<el-checkbox v-for="type in researchTypeList" :key="type.id" v-model="selectedResearchIds" :label="type.id"
class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
<el-checkbox v-for="type in researchTypeList" :key="type.id" class="filter-checkbox" :label="type.id">
{{ type.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
<div class="select-time-box">
......@@ -39,16 +40,17 @@
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox class="filter-checkbox" :model-value="isGroupAllSelected(researchTimeIds)"
@change="val => handleToggleAll(val, researchTimeIds)">
全部时间
<el-checkbox-group
class="checkbox-group"
:model-value="selectedResearchTimeIds"
@change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }}
</el-checkbox>
<el-checkbox v-for="type in researchTimeList" :key="type.id" v-model="selectedResearchTimeIds"
:label="type.id" class="filter-checkbox" @change="handleGetThinkDynamicsReport()">
<el-checkbox v-for="type in researchTimeList" :key="type.id" class="filter-checkbox" :label="type.id">
{{ type.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
<!-- <div class="input-main">
<el-input placeholder="输入作者名字" v-model="author" @input="handleGetThinkDynamicsReport()" />
......@@ -63,7 +65,7 @@
<img :src="item.imageUrl" alt="" />
</div>
<div class="footer-card-title">
{{ item.name }}
<span v-html="highlightText(item.name)"></span>
</div>
<div class="footer-card-footer">
<div class="time">{{ item.times }}</div>
......@@ -84,7 +86,14 @@
</div>
</template>
<script setup>
import { computed, ref, toRefs, watch } from "vue";
import { ref, toRefs, watch } from "vue";
import {
RESOURCE_FILTER_ALL_AREA,
RESOURCE_FILTER_ALL_TIME,
normalizeExclusiveAllOption,
stripAllAreaForRequest,
stripAllTimeForRequest
} from "../../../utils/resourceLibraryFilters";
const props = defineProps({
researchTypeList: {
......@@ -114,6 +123,10 @@ const props = defineProps({
currentPage: {
type: Number,
default: 1
},
searchKeyword: {
type: String,
default: ""
}
});
......@@ -127,43 +140,59 @@ const emit = defineEmits([
// 解构 props,保持模板里变量名不变
const { researchTypeList, researchTimeList, curFooterList, total, currentPage } = toRefs(props);
const selectedResearchIds = ref([]);
const selectedResearchTimeIds = ref([]);
const selectedResearchIds = ref([RESOURCE_FILTER_ALL_AREA]);
const selectedResearchTimeIds = ref([RESOURCE_FILTER_ALL_TIME]);
const escapeHtml = (text) => {
return String(text ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
};
const escapeRegExp = (text) => {
return String(text ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
const highlightText = (text) => {
const safeText = escapeHtml(text);
const keyword = (props.searchKeyword || "").trim();
if (!keyword) return safeText;
const pattern = new RegExp(`(${escapeRegExp(keyword)})`, "gi");
return safeText.replace(pattern, '<span class="keyword-highlight">$1</span>');
};
// 父组件更新时同步到子组件
// 父组件更新时同步到子组件(接口层无选中时为「全部」)
watch(
() => props.selectedFilters,
val => {
selectedResearchIds.value = val?.researchTypeIds ? [...val.researchTypeIds] : [];
selectedResearchTimeIds.value = val?.researchTimeIds ? [...val.researchTimeIds] : [];
selectedResearchIds.value =
val?.researchTypeIds?.length > 0
? [...val.researchTypeIds]
: [RESOURCE_FILTER_ALL_AREA];
selectedResearchTimeIds.value =
val?.researchTimeIds?.length > 0
? [...val.researchTimeIds]
: [RESOURCE_FILTER_ALL_TIME];
},
{ immediate: true, deep: true }
);
const buildSelectedFiltersPayload = () => ({
researchTypeIds: [...selectedResearchIds.value],
researchTimeIds: [...selectedResearchTimeIds.value],
researchTypeIds: stripAllAreaForRequest(selectedResearchIds.value),
researchTimeIds: stripAllTimeForRequest(selectedResearchTimeIds.value),
researchHearingIds: []
});
const researchTypeIds = computed(() => (researchTypeList.value || []).map(item => item.id));
const researchTimeIds = computed(() => (researchTimeList.value || []).map(item => item.id));
const getTargetSelection = ids => (ids === researchTypeIds.value ? selectedResearchIds : selectedResearchTimeIds);
const isGroupAllSelected = ids =>
ids.length > 0 && ids.every(id => getTargetSelection(ids).value.includes(id));
const handleAreaGroupChange = (val) => {
selectedResearchIds.value = normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_AREA);
handleGetThinkDynamicsReport();
};
const handleToggleAll = (checked, ids) => {
if (!ids.length) return;
const targetSelection = getTargetSelection(ids);
const nextSelected = new Set(targetSelection.value);
if (checked) {
ids.forEach(id => nextSelected.add(id));
} else {
ids.forEach(id => nextSelected.delete(id));
}
targetSelection.value = [...nextSelected];
const handleTimeGroupChange = (val) => {
selectedResearchTimeIds.value = normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_TIME);
handleGetThinkDynamicsReport();
};
......@@ -326,6 +355,10 @@ const handleToReportDetail = item => {
}
}
:deep(.keyword-highlight) {
background-color: #fff59d;
}
.right {
width: 1284px;
height: 1377px;
......
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论