d3.event=null

本文介绍如何通过模块化方式使用d3库,并解决在模块化使用中遇到的d3.event返回null的问题。提供了具体实现方法及示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

d3功能奇多, 已经模块化,(其实感觉和react差不多了).

所以默认打包的单个文件

<script src="https://d3js.org/d3.v5.min.js"></script>

或者直接

const d3 = require("d3")

这样默认打包体积大,但是也没有包括全部d3的模块

所以, github官方https://github.com/d3/d3

推荐这样

var d3 = Object.assign({}, require("d3-format"), require("d3-geo"), require("d3-geo-projection"));

尤其是,d3-selection-multi 这个没有默认打包的库,只能这么用. 这样可以使用attrs({}) 直接导入多个属性,尤其适合不需要数据绑定回调函数  (d, i) =>{} 的时候.

 

但是模块化使用d3的时候, 在响应事件的时候出现了问题.

d3里为了数据绑定需要, 响应事件统一这样:

selection.on('click', (d, i)={
})

而不能用字面的方式得到完整的event信息

selection.attr('onclick', 'callback($event)')

但万能的d3当然也能得到标准dom事件:

selection.on('click', (d, i)={
   const event = d3.event;
   ......
})

但是之但是,在模块导入的时候, 这个d3.event 竟然返回null

稍微搜索了一下,不是1个人遇到这个问题, 参考https://brianschiller.com/blog/2017/09/28/d3-event-issues 这个人的发现之后

我这样干:

1导入d3

export const d3Selection = require("d3-selection")
export const d3 = Object.assign({}, event, require("d3-dispatch"), require("d3-selection"), require("d3-selection-multi"), require("d3-geo"));

其他d3功能,照样用下面模块化导入的d3,

而专门导入一个d3Selection 用来处理event

2事件

selection.on('click', (d, i)={
   const event = d3Selection.event;
   ......
})

这样就能得到标准event的各种信息了

比如

event.target.className, event.target.checked

 

转载于:https://www.cnblogs.com/xuanmanstein/p/10631029.html

this.sbtp = this.$echarts.init(document.getElementById("sbtp")); this.sbtp.setOption({ color: ["#09022C", "#040193", "#073CFE", "#0065C2"], series: [ { //图片配置 type: "graph", //关系图 // layout: &#39;force&#39;, // force: { // repulsion: 300, //节点相距距离 // edgeLength: [100, 200], //节点间线的长短 // layoutAnimation: true, // }, legendHoverLink: true, //是否启用图例 hover(悬停) 时的联动高亮。 emphasis: { scale: true, focus: &#39;adjacency&#39;, }, roam: true, //是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移,可以设置成 &#39;scale&#39; 或者 &#39;move&#39;。设置成 true 为都开启 nodeScaleRatio: 0.6, //鼠标漫游缩放时节点的相应缩放比例,当设为0时节点不随着鼠标的缩放而缩放 draggable: true, //节点是否可拖拽,只在使用力引导布局的时候有用。 edgeSymbol: ["none", "none"], //边两端的标记类型,可以是一个数组分别指定两端,也可以是单个统一指定。默认不显示标记,常见的可以设置为箭头,如下:edgeSymbol: [&#39;circle&#39;, &#39;arrow&#39;] symbolSize: [20, 20], //图形大小 edgeSymbolSize: 10, //边两端的标记大小,可以是一个数组分别指定两端,也可以是单个统一指定。 lineStyle: { //todo==========节点连线样式。 color: "#fff", width: "1", type: "solid", //线的类型 &#39;solid&#39;(实线)&#39;dashed&#39;(虚线)&#39;dotted&#39;(点线) curveness: 0, //线条的曲线程度,从0到1 opacity: 1, // 图形透明度。支持从 0 到 1 的数字,为 0 时不绘制该图形。默认0.5 }, label: { //todo=============图形上的文本标签(图片名称) show: true, //是否显示标签。 position: "bottom", //标签的位置。[&#39;50%&#39;, &#39;50%&#39;] [x,y] &#39;inside&#39; //标签的字体样式 color: "#fff", //字体颜色 fontStyle: "normal", //文字字体的风格 &#39;normal&#39;标准 &#39;italic&#39;斜体 &#39;oblique&#39; 倾斜 fontWeight: "bolder", //&#39;normal&#39;标准&#39;bold&#39;粗的&#39;bolder&#39;更粗的&#39;lighter&#39;更细的或100 | 200 | 300 | 400... fontFamily: "sans-serif", //文字的字体系列 fontSize: 12, //字体大小 }, data: this.nodedata, links: this.linedata, //edges是其别名代表节点间的关系数据。 }, ], }); this.sbtp.on("mouseover", (param) => { this.menuisshow = true; this.setname = param.data.name; this.setip = param.data.ip; this.setmac = param.data.mac; this.settype = param.data.type; this.setjg = param.data.cabinetname != null?param.data.cabinetname:&#39;&#39;; this.setaz = param.data.installposition != null?param.data.installposition:&#39;&#39;; this.setmemo = param.data.memo; this.setid = param.data.id; this.setline1 = param.data.source; this.setline2 = param.data.target; if (param.data.Isasset == 0) { this.ribushow = true; } else { this.ribushow = false; } if (param.data.id) { this.rightshow = true; } else { this.rightshow = false; } document.getElementById("rightMenu").style.display = "block"; if(param.data.id){ document.getElementById("rightMenu").style.left = param.event.offsetX + 30 + "px"; document.getElementById("rightMenu").style.top = param.event.offsetY + 50 + "px"; }else{ document.getElementById("rightMenu").style.left = param.event.offsetX + 20 + "px"; document.getElementById("rightMenu").style.top = param.event.offsetY + 50 + "px"; } }); this.sbtp.on("mouseout", (param) => { this.menuisshow = false; setTimeout(() => { if(!this.menuisshow){ document.getElementById("rightMenu").style.display = "none"; } }, 300); }); setTimeout(() => { this.sbtp.on(&#39;dragstart&#39;, function(params) { console.log(params) }); this.sbtp.on(&#39;dragend&#39;, function(params) { console.log(&#39;1234&#39;) }); }, 100);为什么 this.sbtp.on(&#39;dragstart&#39;, function(params) { console.log(params) }); this.sbtp.on(&#39;dragend&#39;, function(params) { console.log(&#39;1234&#39;) });不生效
07-26
Failed to invoke the method selectOptStepVOList in the service com.sdtjla.risk.api.RemoteRiskLibraryService. Tried 1 times of the providers [192.168.137.74:20881] (1/1) from the registry 127.0.0.1:8848 on the consumer 192.168.137.74 using the dubbo version 3.2.14. Last error is: Failed to invoke remote method: selectOptStepVOList, provider: DefaultServiceInstance{serviceName=&#39;aqsc-risk&#39;, host=&#39;192.168.137.74&#39;, port=20881, enabled=true, healthy=true, metadata={dubbo.endpoints=[{"port":20881,"protocol":"dubbo"}], dubbo.metadata.revision=c03cee6db2f2d2895ea5450b77ad5d3a, dubbo.metadata.storage-type=remote, timestamp=1750323797617}}, service{name=&#39;com.sdtjla.risk.api.RemoteRiskLibraryService&#39;,group=&#39;null&#39;,version=&#39;null&#39;,protocol=&#39;dubbo&#39;,port=&#39;20881&#39;,params={check.serializable=false, side=provider, release=3.2.14, methods=selectCtrlMeasureVOList,selectOptStepVOList,selectVOById, logger=slf4j, deprecated=false, dubbo=2.0.2, interface=com.sdtjla.risk.api.RemoteRiskLibraryService, service-name-mapping=true, generic=false, serialize.check.status=DISABLE, metadata-type=remote, application=aqsc-risk, prefer.serialization=fastjson2,hessian2, dynamic=true, REGISTRY_CLUSTER=default:dev},}, cause: Failed to send message Request [id=3145409504665833018, version=2.0.2, twoWay=true, event=false, broken=false, mPayload=0, data=null] to /192.168.137.74:20881, cause: failed to access class java.util.zip.ZipFile$CleanableResource from class com.alibaba.fastjson2.writer.OWG_38_15_JarFile (java.util.zip.ZipFile$CleanableResource is in module java.base of loader &#39;bootstrap&#39;; com.alibaba.fastjson2.writer.OWG_38_15_JarFile is in unnamed module of loader com.alibaba.fastjson2.util.DynamicClassLoader @6f8fa189) 怎么解决
06-20
<template> <el-container> <el-header> <div class="float-right"> <div class="query_item" style="margin: 0 0 10px 0"> <label class="field" v-if="showFieldLabel">时间区间</label> <el-date-picker style="margin: 0 5px 0 0" v-model="queryParam.timearea" type="datetimerange" start-placeholder="开始日期" end-placeholder="结束日期" > </el-date-picker> </div> <div class="query_item" v-if="user.user.role && user.user.role.isAdmin"> <ul class="ul" @click.stop="showFullScreenButton"> <!-- 修改这里的判断条件 --> <span class="field" style="margin: 0 6px" v-if="selectedNodes.length === 0" >区域</span > <li v-for="node in selectedNodes" v-show="isShow" :key="node.id"> <el-tag closable :key="node.id" type="info" @close="clearSelectionById(node.id)" >{{ node.name }}</el-tag > </li> <li v-show="!isShow" style="width: 70px"> <el-tag closable type="info" @close="clearSelectionById1()" >+{{ selectedNodes.length }}</el-tag > </li> </ul> <el-tree v-show="isShow1" style="position: absolute; z-index: 1000; width: 280px" v-model="queryParam.regionIds" show-checkbox node-key="id" ref="tree" :check-strictly="true" :data="options.regions" :options="options.regions" @check-change="handleCheckChange" :props="{ multiple: true, checkStrictly: true, leaf: &#39;isLeaf&#39;, label: &#39;name&#39;, value: &#39;id&#39;, children: &#39;childs&#39;, }" ></el-tree> </div> <el-select v-model="queryParam.isCustody" placeholder="请选择"> <el-option v-for="item in options1" :key="item.value" :label="item.text" :value="item.value" ></el-option> </el-select> <div class="query_item" v-if="user.user.role && user.user.role.isAdmin"> <label class="field" v-if="showFieldLabel">{{ $t("pages.common.speakerA") }}</label> <el-input v-model="queryParam.talkerName" :placeholder="`${$t(&#39;pages.common.speakerA&#39;)}`" > </el-input> </div> <div class="query_item" v-if="true"> <label class="field" v-if="showFieldLabel">{{ $t("pages.common.speakerB") }}</label> <el-input class="keyword" v-model="queryParam.keyword" :placeholder="`${$t(&#39;pages.common.speakerB&#39;)}/谈话原因`" > </el-input> </div> <template v-else> <div class="query_item"> <label class="field" v-if="showFieldLabel">{{ $t("pages.common.speakerB") }}</label> <el-input v-model="queryParam.intervieweeName" :placeholder="`${$t(&#39;pages.common.speakerB&#39;)}`" > </el-input> </div> <div class="query_item"> <label class="field" v-if="showFieldLabel">流水号</label> <el-input v-model="queryParam.serialNum" placeholder="流水号" ></el-input> </div> </template> <el-button type="primary" @click="loadData(), loadData1(1)" style="height: 30px" >查询</el-button > </div> </el-header> <el-main> <el-table class="header-center" v-loading="loading" element-loading-text="数据加载中..." :data="tableData" border :height="tableHeight" style="width: 100%" > <el-table-column label="序号" type="index" :index="indexOrder" ></el-table-column> <el-table-column label="流水号" prop="serialNum" v-if="false" ></el-table-column> <el-table-column label="区域" prop="regionName" width="120"> <template #default="scope"> <el-tooltip :content="scope.row.regionName" placement="top"> <span class="custom-text">{{ scope.row.regionName }}</span> </el-tooltip> </template> </el-table-column> <el-table-column :label="`${$t(&#39;pages.common.locutory&#39;)}`" prop="locutoryName" ></el-table-column> <el-table-column v-if="user.user.role && user.user.role.isAdmin" :label="`${$t(&#39;pages.common.speakerA&#39;)}`" prop="talkerName" ></el-table-column> <el-table-column :label="`${$t(&#39;pages.common.speakerB&#39;)}`" prop="intervieweeName" ></el-table-column> <el-table-column label="人员状态" prop="ryzt"></el-table-column> <el-table-column label="谈话原因" prop="thyy" width="150px"> <template slot-scope="scope"> <el-tooltip :content="scope.row.thyy" placement="top"> <span style=" white-space: nowrap; overflow: hidden; text-overflow: ellipsis; " >{{ scope.row.thyy }}</span > </el-tooltip> </template> </el-table-column> <el-table-column label="开始时间" width="120"> <template #default="scope"> <el-tooltip :content="formatDate(scope.row.startTime)" placement="top" > <span class="custom-text">{{ formatDate(scope.row.startTime) }}</span> </el-tooltip> </template> </el-table-column> <el-table-column label="时长" prop="talkTime" :formatter="formatDuration" ></el-table-column> <el-table-column label="结束时间" width="120"> <template #default="scope"> <el-tooltip :content="formatDate(scope.row.endTime)" placement="top" > <span class="custom-text">{{ formatDate(scope.row.endTime) }}</span> </el-tooltip> </template> </el-table-column> <el-table-column label="关键词" prop="keywords" width="80px"> <template slot="header" slot-scope="scope"> <el-tooltip content="关键词报警次数" placement="top"> <span>关键词</span> </el-tooltip> </template> </el-table-column> <el-table-column label="行为倾向"> <template #default="scope"> {{ scope.row.behavior ? scope.row.behavior : "正常" }} </template> </el-table-column> <el-table-column label="情绪"> <template #default="scope"> {{ scope.row.emotion ? scope.row.emotion : "正常" }} </template> </el-table-column> <el-table-column label="质检状态" prop="qualityState" width="120px"> <template slot-scope="scope"> <el-tooltip v-if="scope.row.qualityMsg" :content="scope.row.qualityMsg" placement="top" > <el-tag >{{ bahjMap[scope.row.qualityState] }} <i style="margin: 2px" class="el-icon-warning-outline"></i ></el-tag> </el-tooltip> <el-tag v-else >{{ bahjMap[scope.row.qualityState] }}<i style="margin: 2px" class="el-icon-warning-outline"></i ></el-tag> </template> </el-table-column> <el-table-column label="操作" prop="id" width="350px"> <template slot-scope="scope"> <div style="display: flex"> <el-button type="primary" @click="trend(scope.row)" >历史趋势</el-button > <el-button :disabled="scope.row.qualityState == 2 ? true : false" @click="review(scope.row)" :type=" scope.row.fileExist || scope.row.isOnline ? &#39;primary&#39; : &#39;&#39; " >复查</el-button > <!-- <el-button @click="review(scope.row)" :type="scope.row.fileExist || scope.row.isOnline ? &#39;primary&#39; : &#39;&#39;" >复查</el-button> --> <el-button type="primary" @click="exportText(scope.row)" >导出文本</el-button > <el-button type="primary" @click="exportFile(scope.row)" v-if="scope.row.fileExist" >导出{{ scope.row.isVideo ? "视频" : "录音" }}</el-button > </div> </template> </el-table-column> </el-table> </el-main> <el-footer class="text-right" height="50px"> <el-pagination class="float-right" layout="prev, pager, next" :total="pagination.total" :current-page.sync="pagination.current" :page-size="pagination.size" @current-change="loadData(pagination.current)" > </el-pagination> </el-footer> <base-drawer v-model="drawerVisible" title="" direction="rtl" size="25%" :before-close="onDrawerClose" > <!-- 抽屉内容 --> <div v-if="currentRow"> <!-- <h3>{{ currentRow.talkerName }} 的历史趋势</h3> --> <h3>历史趋势</h3> <div class="trend-content"> <div class="chart-placeholder"> <!-- 加载中提示 --> <div v-if="isLoading" class="loading"> <!-- <el-loading-spinner class="el-icon-loading"></el-loading-spinner> --> <p>正在加载历史趋势...</p> </div> <!-- 后端返回内容渲染(支持Markdown格式) --> <div v-else class="strategy-content" v-html="talkStrategy"></div> </div> </div> </div> <!-- 底部插槽 --> <div slot="footer"></div> </base-drawer> </el-container> </template> <script lang="ts"> import { Component, Vue } from "nuxt-property-decorator"; import { State, Action } from "vuex-class"; import { Pagination, UserState } from "../../types"; import { duration } from "../../utils/time"; import dayjs from "dayjs"; import { removeEmptyChilds } from "../../utils/tools"; import { fetchData } from "../../utils/concurrency"; import { getQuality } from "../../api/websocket"; import { downloadFile } from "@/utils/tools"; import { historyStatus } from "../../store/history"; import BaseDrawer from "./BaseDrawer.vue"; import * as marked from "marked"; import { cloneDeep } from "lodash"; import {getWsUrl} from &#39;@/api/websocket&#39; @Component({ components: { BaseDrawer, }, filters: { formatTime: (time: number) => new Date(time).toLocaleDateString(), }, }) export default class extends Vue { @State user!: UserState; tableHeight: number = 200; showFieldLabel: boolean = false; @State("history") historyState!: historyStatus; @Action("history/saveQueryParams") saveQueryParams!: (params: any) => void; @Action("history/resetQueryParams") resetQueryParams!: () => void; queryParam: { regionIds: []; //类型[] timearea: []; serialNum: string; talkerName: string; intervieweeName: string; keyword: string; //监管人 谈话原因筛选值 isCustody: number; //在押历史筛选操作 } = { regionIds: [], timearea: [], serialNum: "", talkerName: "", intervieweeName: "", keyword: "", isCustody: 0, }; options: { regions: [] } = { regions: [] }; tableData: [] = []; loading: boolean = false; pagination: Pagination = { total: 0, current: 1, size: 50 }; options1: any = [ { text: "全部", value: -1 }, { text: "在押", value: 0 }, { text: "历史", value: 1 }, ]; selectedNodes: any[] = []; // 用于存储已勾选的节点 isShow: boolean = true; isShow1: boolean = false; isRegin: boolean = true; // 抽屉相关状态 drawerVisible: boolean = false; currentRow: any = null; private socket: WebSocket | null = null; private wsMessage: any = null; // 存储 WebSocket 消息 isLoading: boolean = false; // 加载状态 talkStrategy: string = ""; // 存储后端返回 showFullScreenButton() { this.isShow1 = !this.isShow1; //1次悬停展示再次悬停隐藏(依次类推) } handleCheckChange(data, checked, indeterminate) { if (checked) { this.isRegin = false; // 如果节点被勾选,添加到selectedNodes中(避免重复) if (!this.selectedNodes.some((n) => n.id === data.id)) { this.selectedNodes.push({ ...data }); this.queryParam.regionIds = this.selectedNodes.map((node) => node.id); if (this.selectedNodes.length > 2) { this.isShow = false; } else { this.isShow = true; } } } else { // 如果节点被取消勾选,从selectedNodes中移除 this.selectedNodes = this.selectedNodes.filter((n) => n.id !== data.id); this.queryParam.regionIds = this.selectedNodes.map((node) => node.id); if (this.selectedNodes.length <= 2) { this.isShow = true; } if (this.selectedNodes.length == 0) { this.isRegin = true; this.isShow1 = false; this.queryParam.regionIds = []; } } // 保存当前筛选状态 this.saveFilterParams(); } clearSelectionById(id) { // 从selectedNodes中移除对应项 this.selectedNodes = this.selectedNodes.filter((node) => node.id !== id); // 取消el-tree中对应节点的勾选状态 this.$nextTick(() => { const treeNode = this.$refs.tree.getNode(id); if (treeNode) { this.$refs.tree.setChecked(treeNode, false, true); } // 更新regionIds this.queryParam.regionIds = this.selectedNodes.map((node) => node.id); // 保存当前筛选状态 this.saveFilterParams(); }); } // 修改 clearSelectionById1 方法 clearSelectionById1() { //点击+1清除全部 this.isShow1 = false; this.selectedNodes = []; this.isRegin = true; // 取消el-tree中对应节点的勾选状态 this.$nextTick(() => { this.$refs.tree.setCheckedKeys([]); // 更新regionIds this.queryParam.regionIds = []; // 保存当前筛选状态 this.saveFilterParams(); }); } bahjMap: { [key: number]: string } = { // 新增 bahj 映射对象 0: "未开启", 1: "等待中", 2: "质检中", 3: "质检成功", 4: "质检失败", }; async asyncData(ctx) { let res = await ctx.app.$api.region.list(null); const regions = res.data; removeEmptyChilds(regions); return { options: { regions } }; } // 处理抽屉关闭事件 onDrawerClose(done) { // 关闭WebSocket连接 if (this.socket) { this.socket.close(); this.socket = null; } // 重置状态 this.isLoading = false; this.talkStrategy = ""; this.currentRow = null; // 必须调用done来完成关闭操作 done(); } // trend 方法 async trend(row: any) { // 每次点击都重新初始化状态 this.currentRow = row; this.drawerVisible = true; this.isLoading = true; // 开始加载 this.talkStrategy = ""; // 清空之前的内容 // 关闭现有连接(如果存在) if (this.socket) { this.socket.close(); this.socket = null; } // 确保在下一次DOM更新周期创建新连接 this.$nextTick(() => { const taskId = row.id; const wsUrl = process.env.IS_DEV ? `/tendency/ws/talkAnalysis/${taskId}` : getWsUrl(`/ws/talkAnalysis/${taskId}`); // const wsUrl = !process.env.IS_DEV // ? `/tendency/ws/talkAnalysis/${taskId}` // : `ws://${window.location.host}/ws/talkAnalysis/${taskId}`; // const wsUrl = !process.env.IS_DEV // ? `/tendency/ws/talkAnalysis/${taskId}` // : `ws://192.168.1.78:9103/ws/talkAnalysis/${taskId} `; console.log(wsUrl, "opopop"); // 延迟创建WebSocket连接,确保状态已更新 setTimeout(() => { this.socket = new WebSocket(wsUrl); this.socket.onopen = () => { console.log("直接连接后端 WebSocket 成功"); this.sendAnalysisRequest(); }; // trend 方法中的 onmessage 处理 this.socket.onmessage = (event) => { try { const res = typeof event.data === "string" ? JSON.parse(event.data) : event.data; console.log("后端返回消息:", res); if (res.answer) { this.talkStrategy = this.formatMarkdown(res.answer); // 手动解析 Markdown } else if (typeof res === "string") { this.talkStrategy = this.formatMarkdown(res); } else { this.talkStrategy = "<p>后端未返回有效分析结果</p>"; } } catch (error) { console.error("消息解析错误:", error); this.talkStrategy = `<p>数据解析失败:${error.message}</p>`; } finally { this.isLoading = false; } }; this.socket.onerror = (error) => { this.isLoading = false; // 错误时结束加载 this.talkStrategy = "连接失败,请重试"; console.error("直接连接后端错误:", error); }; this.socket.onclose = () => { if (this.isLoading) { this.isLoading = false; } console.log("直接连接关闭"); // 确保 socket 引用被清除 this.socket = null; }; }, 100); }); } // 辅助 格式化Markdown private formatMarkdown(content: string): string { if (!content) return "<p>暂无分析结果</p>"; // 使用marked库解析Markdown(更规范的处理方式) try { // 自定义marked渲染器来优化样式 const renderer = new marked.Renderer(); // 自定义标题渲染 - 修复方法签名 renderer.heading = ({ text, depth }: { text: string; depth: number }) => { const headingClass = `md-heading md-heading-${depth}`; return `<h${depth} class="${headingClass}">${text}</h${depth}>`; }; // 自定义段落渲染 renderer.paragraph = (text) => { return `<p class="md-paragraph">${text}</p>`; }; // 自定义列表渲染 renderer.list = (body, ordered, start) => { const tag = ordered ? "ol" : "ul"; return `<${tag} class="md-list"${ start ? ` start="${start}"` : "" }>${body}</${tag}>`; }; renderer.listitem = (text) => { return `<li class="md-list-item">${text}</li>`; }; // 自定义分隔线 renderer.hr = () => &#39;<hr class="md-divider">&#39;; // 配置marked选项 marked.setOptions({ renderer: renderer, breaks: true, gfm: true, }); // 解析Markdown return marked(content); } catch (error) { console.error("Markdown解析错误:", error); // 降级处理:手动解析关键结构 let html = content // 处理标题 .replace(/^######### (.*$)/gm, &#39;<h10 class="md-heading-1">$1</h10>&#39;) .replace(/^######## (.*$)/gm, &#39;<h9 class="md-heading-1">$1</h9>&#39;) .replace(/^######## (.*$)/gm, &#39;<h8 class="md-heading-1">$1</h8>&#39;) .replace(/^####### (.*$)/gm, &#39;<h7 class="md-heading-1">$1</h7>&#39;) .replace(/^###### (.*$)/gm, &#39;<h6 class="md-heading-1">$1</h6>&#39;) .replace(/^##### (.*$)/gm, &#39;<h5 class="md-heading-1">$1</h5>&#39;) .replace(/^#### (.*$)/gm, &#39;<h4 class="md-heading-1">$1</h4>&#39;) .replace(/^### (.*$)/gm, &#39;<h3 class="md-heading-3">$1</h3>&#39;) .replace(/^## (.*$)/gm, &#39;<h2 class="md-heading-2">$1</h2>&#39;) .replace(/^# (.*$)/gm, &#39;<h1 class="md-heading-1">$1</h1>&#39;) // 处理列表 .replace(/^\s*[-*+]\s+(.*)$/gm, &#39;<li class="md-list-item">$1</li>&#39;) .replace( /(<li class="md-list-item">.*<\/li>\s*)+/gm, &#39;<ul class="md-list">$&</ul>&#39; ) // 处理分隔线 .replace(/^[-*_]{3,}$/gm, &#39;<hr class="md-divider">&#39;) // 处理段落 .replace(/\n{2,}/g, &#39;</p><p class="md-paragraph">&#39;) .replace(/\n/g, "<br>") // 处理加粗和斜体 .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>") .replace(/\*(.*?)\*/g, "$1"); // 确保根元素包裹 if (!html.startsWith("<p>") && !html.startsWith("<h")) { html = `<p class="md-paragraph">${html}</p>`; } return html; } } // 发送请求参数的方法( private sendAnalysisRequest() { if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { console.error("WebSocket 未连接"); return; } // 构建参数(确保无 undefined 或循环引用) const requestData = { task_type: "历史趋势", // person_name: "王传参", person_name: this.currentRow?.intervieweeName, talk_reason: this.currentRow?.thyy, timestamp: this.currentRow?.startTime, context: "", question: "", }; try { // 验证 JSON 格式 const jsonStr = JSON.stringify(requestData); console.log("待发送的 JSON 字符串:", jsonStr); // 检查是否有异常字符 this.socket.send(jsonStr); } catch (error) { console.error("JSON 序列化失败:", error); } } created() { // 在加载数据前快速恢复查询参数 this.restoreFilterParams(); this.$api.region.list(null).then((res) => { const regions = res.data; removeEmptyChilds(regions); this.options.regions = regions; // 确保树组件加载完成后再设置选中状态 this.$nextTick(() => { this.restoreFilterParams(); }); }); } // 生命周期钩子:路由变化时恢复筛选条件 beforeRouteEnter(to, from, next) { next((vm) => { vm.restoreFilterParams(); vm.loadData(); }); } beforeRouteUpdate(to, from, next) { this.restoreFilterParams(); this.loadData(); next(); } // 恢复筛选条件 restoreFilterParams() { // 从Vuex获取保存的筛选条件 this.queryParam = { ...this.historyState.queryParam, regionIds: this.historyState.regionIds || [], timearea: this.historyState.timearea || [], serialNum: this.historyState.serialNum || "", talkerName: this.historyState.talkerName || "", intervieweeName: this.historyState.intervieweeName || "", keyword: this.historyState.keyword || "", isCustody: this.historyState.isCustody || 0, }; // 恢复区域节点 this.selectedNodes = cloneDeep(this.historyState.regionNodes || []); this.isRegin = this.selectedNodes.length === 0; // 恢复树形选择状态 this.$nextTick(() => { if (this.$refs.tree) { const tree = this.$refs.tree as any; tree.setCheckedKeys(this.queryParam.regionIds || []); // 更新显示状态 if (this.selectedNodes.length > 2) { this.isShow = false; } else { this.isShow = true; } } }); } // 保存筛选条件到Vuex saveFilterParams() { const paramsToSave = { ...this.queryParam, regionIds: this.selectedNodes.map((node) => node.id), regionNodes: cloneDeep(this.selectedNodes), }; this.$store.commit("history/SET_QUERY_PARAMS", paramsToSave); } handleClickOutside(e) { //判断是否点击的是盒子之外 if (this.$refs.tree && !this.$refs.tree.$el.contains(e.target)) { this.isShow1 = false; } } mounted() { this.$nextTick(this.resize.bind(this)); window.addEventListener("resize", this.resize); this.$once("hook:beforeDestroy", () => window.removeEventListener("resize", this.resize) ); // 恢复筛选条件 this.restoreFilterParams(); this.loadData(); // 添加全局点击事件监听器 document.addEventListener("click", this.handleClickOutside); } beforeDestroy() { // 移除全局点击事件监听器 document.removeEventListener("click", this.handleClickOutside); } resize() { this.tableHeight = ((this.$el.querySelector(".el-table") as Element).parentNode as Element) .clientHeight - 1; // this.console.debug(this.$el, this.tableHeight) } loadData1(e) {} async loadData(index: number = 1) { this.saveFilterParams(); // 保存当前筛选条件 let arr: number[] = []; this.pagination.current = index; let param: any = { pageIndex: this.pagination.current }; if (this.queryParam.regionIds != null) { arr = this.queryParam.regionIds.flat(); // flat 方法将嵌套数组拍平替代foreach循环语法 param.regionIds = [...new Set(arr)]; } param.ryzt = this.queryParam.isCustody; // 筛选在押历史的过滤queryParam.isCustody if ( this.queryParam.timearea != null && this.queryParam.timearea.length > 0 ) { this.queryParam.timearea.forEach((it: Date, idx) => { param[idx == 0 ? "startTime" : "endTime"] = it.getTime() / 1000; }); } if ( this.queryParam.talkerName != null && this.queryParam.talkerName.length > 0 ) { param.talkerName = this.queryParam.talkerName; } if ( this.queryParam.intervieweeName != null && this.queryParam.intervieweeName.length > 0 ) { param.intervieweeName = this.queryParam.intervieweeName; } if ( this.queryParam.serialNum != null && this.queryParam.serialNum.length > 0 ) { param.serialNum = this.queryParam.serialNum; } if (this.queryParam.keyword != null && this.queryParam.keyword.length > 0) { param.keyword = this.queryParam.keyword; } this.loading = true; const res = await this.$api.talk.history(param); this.tableData = res.data; this.pagination = { total: res.page.totalCount, current: res.page.pageIndex, size: res.page.pageSize, }; this.loading = false; } review(item: any) { this.saveFilterParams(); // 跳转前保存筛选条件 this.$router.push({ path: `/${ this.user.user.role && this.user.user.role.isAdmin ? "admin" : "home" }/review`, query: { id: item.id, type: (item.isOnline ? 1 : 0).toString(), talkerId: item.id, fillcontent: item, }, }); } exportText(item: any) { this.$api.download(this.$api.talk.exportTextUrl(item.id)); } async exportFile(item: any) { if (item.isVideo) { console.log(item.videoPath, "989"); downloadFile(item.videoPath); } else { this.$api.download(this.$api.talk.exportAudioUrl(item.id)); } // this.$api.download(this.$api.talk.exportAudioUrl(item.id)); } // --------------------------------------------------------------- filterHandler(value, row, column) { const property = column["property"]; return row[property] === value; } indexOrder(index) { return (this.pagination.current - 1) * this.pagination.size + index + 1; } formatDate(timestamp: number): string { const milliseconds = timestamp * 1000; return dayjs(milliseconds).format("YYYY-MM-DD HH:mm:ss"); } formatDuration(row, column, cellValue, index) { return duration( (cellValue ? cellValue : row.endTime == null || row.startTime == null ? "--" : row.endTime - row.startTime) * 1000 ); } } </script> <style lang="less" scoped> .trend-content { margin-top: 20px; height: calc(100% - 60px); .chart-placeholder { height: 100%; padding: 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } // 加载中样式 .loading { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #666; .el-icon-loading { font-size: 32px; margin-bottom: 16px; animation: loading 1s linear infinite; } } // 谈话策略内容样式 .strategy-content { line-height: 1.6; color: #333; padding: 15px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; // 标题样式 .md-heading { margin: 1.5em 0 0.8em; font-weight: 600; color: #2c3e50; &-1 { font-size: 1.8em; } &-2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; } &-3 { font-size: 1.3em; } &-4 { font-size: 1.1em; } } // 段落样式 .md-paragraph { margin: 1em 0; line-height: 1.7; } // 列表样式 .md-list { padding-left: 2em; margin: 1em 0; &-item { margin: 0.5em 0; position: relative; &::before { content: "•"; color: #409eff; position: absolute; left: -1.2em; } } } // 分隔线 .md-divider { border: 0; border-top: 1px solid #eaecef; margin: 1.5em 0; } // 强调样式 strong { font-weight: 600; color: #2c3e50; } em { font-style: italic; color: #555; } // 代码块样式(如果需要) pre { background-color: #f8f8f8; padding: 1em; border-radius: 4px; overflow-x: auto; } // 链接样式 a { color: #409eff; text-decoration: none; &:hover { text-decoration: underline; } } // 引用块样式 blockquote { margin: 1em 0; padding-left: 1em; color: #6a737d; border-left: 0.25em solid #dfe2e5; } } } // 加载动画 @keyframes loading { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> <style lang="less" scoped> /** 骨架样式 ****************************************************************/ /deep/.el-table { .custom-text { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } .container, .el-container, .el-main, .el-row, .el-col { height: 100%; } .container { overflow: hidden; } .el-container { padding: 5px; // background-color: #d3d3d3; } .ul { position: relative; list-style-type: none; padding: 0; margin: 0; border: 1px solid rgb(222, 221, 221); width: 280px; height: 30px; line-height: 30px; background: white; li { margin-bottom: 10px; float: left; margin: 0 5px 0 2px; padding: 0 2px; } } .el-header { height: auto !important; .query_item { display: inline-block; margin: 0 5px; .field { margin-right: 5px; } .keyword { width: 300px; } } } .float-right { display: flex; justify-content: space-around; .query_item { z-index: 1000; } } .el-main { padding: 0px; .el-row, .el-col { border: #7f828b solid 1px; } } .el-footer { /*background-color: #838b89;*/ padding: 10px; } /** 表单 ****************************************************************/ .el-input, .el-select { width: 120px; } .el-date-editor { width: 340px; } </style> <style lang="less"> </style> {"reasoning_content":"","answer":"由于","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"提供的","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"【","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"历史","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"谈话","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"数据","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"】","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"中","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"`","task_type":"历史趋势","is_stop":false} {"reasoning_content":"","answer":"","task_type":"历史趋势","is_stop":true} 对应的trend方法 使用了流式输出answer是输出值且is_stop为false为在输出 当is_stop为true输出结束 基于这种数据流格式修复一下代码 并提供下修复后的完整代码呢 亲
最新发布
08-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值