Vue3记录时间格式化转换:formatTime.ts

/**
 * 时间日期转换
 * @param date 当前时间,new Date() 格式
 * @param format 需要转换的时间格式字符串
 * @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
 * @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
 * @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
 * @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
 * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
 * @returns 返回拼接后的时间字符串
 */
export function formatDate(date: Date, format: string): string {
	let we = date.getDay(); // 星期
	let z = getWeek(date); // 周
	let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
	const opt: { [key: string]: string } = {
		'Y+': date.getFullYear().toString(), // 年
		'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
		'd+': date.getDate().toString(), // 日
		'H+': date.getHours().toString(), // 时
		'M+': date.getMinutes().toString(), // 分
		'S+': date.getSeconds().toString(), // 秒
		'q+': qut, // 季度
	};
	// 中文数字 (星期)
	const week: { [key: string]: string } = {
		'0': '日',
		'1': '一',
		'2': '二',
		'3': '三',
		'4': '四',
		'5': '五',
		'6': '六',
	};
	// 中文数字(季度)
	const quarter: { [key: string]: string } = {
		'1': '一',
		'2': '二',
		'3': '三',
		'4': '四',
	};
	if (/(W+)/.test(format))
		format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
	if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
	if (/(Z+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '');
	for (let k in opt) {
		let r = new RegExp('(' + k + ')').exec(format);
		// 若输入的长度不为1,则前面补零
		if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
	}
	return format;
}

/**
 * 获取当前日期是第几周
 * @param dateTime 当前传入的日期值
 * @returns 返回第几周数字值
 */
export function getWeek(dateTime: Date): number {
	let temptTime = new Date(dateTime.getTime());
	// 周几
	let weekday = temptTime.getDay() || 7;
	// 周1+5天=周六
	temptTime.setDate(temptTime.getDate() - weekday + 1 + 5);
	let firstDay = new Date(temptTime.getFullYear(), 0, 1);
	let dayOfWeek = firstDay.getDay();
	let spendDay = 1;
	if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1;
	firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay);
	let d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000);
	let result = Math.ceil(d / 7);
	return result;
}

/**
 * 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前`
 * @param param 当前时间,new Date() 格式或者字符串时间格式
 * @param format 需要转换的时间格式字符串
 * @description param 10秒:  10 * 1000
 * @description param 1分:   60 * 1000
 * @description param 1小时: 60 * 60 * 1000
 * @description param 24小时:60 * 60 * 24 * 1000
 * @description param 3天:   60 * 60* 24 * 1000 * 3
 * @returns 返回拼接后的时间字符串
 */
export function formatPast(param: string | Date, format: string = 'YYYY-mm-dd'): string {
	// 传入格式处理、存储转换值
	let t: any, s: number;
	// 获取js 时间戳
	let time: number = new Date().getTime();
	// 是否是对象
	typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
	// 当前时间戳 - 传入时间戳
	time = Number.parseInt(`${time - t}`);
	if (time < 10000) {
		// 10秒内
		return '刚刚';
	} else if (time < 60000 && time >= 10000) {
		// 超过10秒少于1分钟内
		s = Math.floor(time / 1000);
		return `${s}秒前`;
	} else if (time < 3600000 && time >= 60000) {
		// 超过1分钟少于1小时
		s = Math.floor(time / 60000);
		return `${s}分钟前`;
	} else if (time < 86400000 && time >= 3600000) {
		// 超过1小时少于24小时
		s = Math.floor(time / 3600000);
		return `${s}小时前`;
	} else if (time < 259200000 && time >= 86400000) {
		// 超过1天少于3天内
		s = Math.floor(time / 86400000);
		return `${s}天前`;
	} else {
		// 超过3天
		let date = typeof param === 'string' || 'object' ? new Date(param) : param;
		return formatDate(date, format);
	}
}

/**
 * 时间问候语
 * @param param 当前时间,new Date() 格式
 * @description param 调用 `formatAxis(new Date())` 输出 `上午好`
 * @returns 返回拼接后的时间字符串
 */
export function formatAxis(param: Date): string {
	let hour: number = new Date(param).getHours();
	if (hour < 6) return '凌晨好';
	else if (hour < 9) return '早上好';
	else if (hour < 12) return '上午好';
	else if (hour < 14) return '中午好';
	else if (hour < 17) return '下午好';
	else if (hour < 19) return '傍晚好';
	else if (hour < 22) return '晚上好';
	else return '夜里好';
}

// 日期格式化
export function parseTime(time, pattern) {
	if (arguments.length === 0 || !time) {
		return null;
	}
	const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
	let date;
	if (typeof time === 'object') {
		date = time;
	} else {
		if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
			time = parseInt(time);
		} else if (typeof time === 'string') {
			time = time
				.replace(new RegExp(/-/gm), '/')
				.replace('T', ' ')
				.replace(new RegExp(/\.[\d]{3}/gm), '');
		}
		if (typeof time === 'number' && time.toString().length === 10) {
			time = time * 1000;
		}
		date = new Date(time);
	}
	const formatObj = {
		y: date.getFullYear(),
		m: date.getMonth() + 1,
		d: date.getDate(),
		h: date.getHours(),
		i: date.getMinutes(),
		s: date.getSeconds(),
		a: date.getDay(),
	};
	const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
		let value = formatObj[key];
		// Note: getDay() returns 0 on Sunday
		if (key === 'a') {
			return ['日', '一', '二', '三', '四', '五', '六'][value];
		}
		if (result.length > 0 && value < 10) {
			value = '0' + value;
		}
		return value || 0;
	});
	return time_str;
}

// 日期格式化
export function parseDate(time, pattern) {
	if (arguments.length === 0 || !time) {
		return null;
	}
	const format = pattern || '{y}-{m}-{d}';
	let date;
	if (typeof time === 'object') {
		date = time;
	} else {
		if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
			time = parseInt(time);
		} else if (typeof time === 'string') {
			time = time
				.replace(new RegExp(/-/gm), '/')
				.replace('T', ' ')
				.replace(new RegExp(/\.[\d]{3}/gm), '');
		}
		if (typeof time === 'number' && time.toString().length === 10) {
			time = time * 1000;
		}
		date = new Date(time);
	}
	const formatObj = {
		y: date.getFullYear(),
		m: date.getMonth() + 1,
		d: date.getDate(),
		h: date.getHours(),
		i: date.getMinutes(),
		s: date.getSeconds(),
		a: date.getDay(),
	};
	const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
		let value = formatObj[key];
		// Note: getDay() returns 0 on Sunday
		if (key === 'a') {
			return ['日', '一', '二', '三', '四', '五', '六'][value];
		}
		if (result.length > 0 && value < 10) {
			value = '0' + value;
		}
		return value || 0;
	});
	return time_str;
}

export const dateTimeStr: string = 'YYYY-MM-DD HH:mm:ss';

export const dateStr: string = 'YYYY-MM-DD';

export const timeStr: string = 'HH:mm:ss';
VM38344 index.vue:472 Markdown解析错误: TypeError: marked__WEBPACK_IMPORTED_MODULE_31__ is not a function at VueComponent.formatMarkdown (VM38344 index.vue:470:16) at _this4.socket.onmessage (VM38344 index.vue:379:56) <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}`); this.socket = new WebSocket(wsUrl); let accumulatedAnswer = ""; // 存储累积的Markdown内容 this.socket.onopen = () => { console.log("WebSocket连接成功"); this.sendAnalysisRequest(); // 发送分析请求 }; this.socket.onmessage = (event) => { this.isLoading = false;//实现打印机一字一字输出 try { const res = typeof event.data === "string" ? JSON.parse(event.data) : event.data; // 处理分块内容 if (res.answer) { accumulatedAnswer += res.answer; // 累积分块内容 this.talkStrategy = this.formatMarkdown(accumulatedAnswer); // 实时渲染 } // 处理结束标志 if (res.is_stop) { this.isLoading = false; if (this.socket) { this.socket.close(); this.socket = null; } console.log("流式输出结束"); } } catch (error) { console.error("消息解析错误:", error); this.talkStrategy = `<p>数据解析失败:${error.message}</p>`; this.isLoading = false; } }; this.socket.onerror = (error) => { console.error("WebSocket错误:", error); this.talkStrategy = "连接失败,请重试"; this.isLoading = false; }; this.socket.onclose = () => { if (this.isLoading) { this.isLoading = false; } console.log("WebSocket连接关闭"); }; }); } // 辅助 格式化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>
最新发布
08-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值