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: 'isLeaf',
label: 'name',
value: 'id',
children: 'childs',
}"
></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('pages.common.speakerA')}`"
>
</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('pages.common.speakerB')}/谈话原因`"
>
</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('pages.common.speakerB')}`"
>
</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('pages.common.locutory')}`"
prop="locutoryName"
></el-table-column>
<el-table-column
v-if="user.user.role && user.user.role.isAdmin"
:label="`${$t('pages.common.speakerA')}`"
prop="talkerName"
></el-table-column>
<el-table-column
:label="`${$t('pages.common.speakerB')}`"
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 ? 'primary' : ''
"
>复查</el-button
>
<!-- <el-button @click="review(scope.row)" :type="scope.row.fileExist || scope.row.isOnline ? 'primary' : ''" >复查</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 '@/api/websocket'
@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 = () => '<hr class="md-divider">';
// 配置marked选项
marked.setOptions({
renderer: renderer,
breaks: true,
gfm: true,
});
// 解析Markdown
return marked(content);
} catch (error) {
console.error("Markdown解析错误:", error);
// 降级处理:手动解析关键结构
let html = content
// 处理标题
.replace(/^######### (.*$)/gm, '<h10 class="md-heading-1">$1</h10>')
.replace(/^######## (.*$)/gm, '<h9 class="md-heading-1">$1</h9>')
.replace(/^######## (.*$)/gm, '<h8 class="md-heading-1">$1</h8>')
.replace(/^####### (.*$)/gm, '<h7 class="md-heading-1">$1</h7>')
.replace(/^###### (.*$)/gm, '<h6 class="md-heading-1">$1</h6>')
.replace(/^##### (.*$)/gm, '<h5 class="md-heading-1">$1</h5>')
.replace(/^#### (.*$)/gm, '<h4 class="md-heading-1">$1</h4>')
.replace(/^### (.*$)/gm, '<h3 class="md-heading-3">$1</h3>')
.replace(/^## (.*$)/gm, '<h2 class="md-heading-2">$1</h2>')
.replace(/^# (.*$)/gm, '<h1 class="md-heading-1">$1</h1>')
// 处理列表
.replace(/^\s*[-*+]\s+(.*)$/gm, '<li class="md-list-item">$1</li>')
.replace(
/(<li class="md-list-item">.*<\/li>\s*)+/gm,
'<ul class="md-list">$&</ul>'
)
// 处理分隔线
.replace(/^[-*_]{3,}$/gm, '<hr class="md-divider">')
// 处理段落
.replace(/\n{2,}/g, '</p><p class="md-paragraph">')
.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>
最新发布