|
|
@@ -0,0 +1,381 @@
|
|
|
+<template>
|
|
|
+ <div class="dialog-chat-answer-content">
|
|
|
+ <div v-if="is_loading && type == 'znyj'" class="loading">
|
|
|
+ <a-spin></a-spin> <span>{{ loadingText }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="is_loading && type == 'znws'" class="loading">
|
|
|
+ <a-spin></a-spin> <span>{{ loadingText }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="answer-content-text answer-content-report" v-for="(item, index) in result_list" :key="index">
|
|
|
+ <div ref="answerRef" class="answer-content" v-if="typeof item === 'string'" v-html="renderHtml(item)"></div>
|
|
|
+ <HomeChart v-if="typeof item === 'object' && item.type != 'excel'" :origin="'home'" :data="item"></HomeChart>
|
|
|
+ </div>
|
|
|
+ <slot></slot>
|
|
|
+ <div class="report-loading" v-if="answer_status === 1">
|
|
|
+ <img src="../../assets/chat-report/report-ing.png" alt="" />
|
|
|
+ <span>{{ loadingText }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- <div v-html="html_1"></div> -->
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed, watch, onMounted, onUnmounted, render, h } from 'vue';
|
|
|
+import HomeChart from '../chat_echart/HomeChart.vue';
|
|
|
+import marked from '../../3rd-libs/marked';
|
|
|
+import { Tooltip } from '@arco-design/web-vue';
|
|
|
+import { IconDownload } from '@arco-design/web-vue/es/icon';
|
|
|
+import * as XLSX from 'xlsx';
|
|
|
+// import TableHasDownload from '../chat_files/TableHasDownload.vue';
|
|
|
+// 接收markdown数据,进行一些转换
|
|
|
+const props = defineProps({
|
|
|
+ /**
|
|
|
+ * 内容类型
|
|
|
+ */
|
|
|
+ type: String,
|
|
|
+ /**
|
|
|
+ * 文本内容数组
|
|
|
+ */
|
|
|
+ result_list: Array,
|
|
|
+ /**
|
|
|
+ * 状态值
|
|
|
+ */
|
|
|
+ is_loading: Boolean,
|
|
|
+ /**
|
|
|
+ * 会话状态
|
|
|
+ * */
|
|
|
+ answer_status: Boolean
|
|
|
+});
|
|
|
+const answerRef = ref(null);
|
|
|
+const exportButtonList = ref([]);
|
|
|
+// 随机生成3-5之间的一位小数
|
|
|
+const generateRandomDecimal = () => {
|
|
|
+ // 生成3到5之间的随机数
|
|
|
+ const randomNum = 3 + Math.random() * 2;
|
|
|
+ // 保留一位小数
|
|
|
+ return Number(randomNum.toFixed(1));
|
|
|
+};
|
|
|
+
|
|
|
+// 加载文本状态
|
|
|
+const loadingText = ref('正在查询中...');
|
|
|
+const loadingTime = generateRandomDecimal();
|
|
|
+const loadingTexts = ['正在查询中...', '正在生成内容...', '正在处理数据...', '正在发送邮件...', '即将完成...'];
|
|
|
+let loadingInterval = null;
|
|
|
+let currentLoadingIndex = 0;
|
|
|
+
|
|
|
+// 开始顺序展示加载文本
|
|
|
+const startLoadingTextCycle = () => {
|
|
|
+ if (loadingInterval) clearInterval(loadingInterval);
|
|
|
+ currentLoadingIndex = 0;
|
|
|
+ loadingText.value = loadingTexts[currentLoadingIndex] + '(预计' + loadingTime + '分钟完成)';
|
|
|
+ // 生成随机数并添加到加载文本
|
|
|
+ // const randomTime = generateRandomDecimal();
|
|
|
+ // loadingText.value = `${loadingTexts[currentLoadingIndex]} (预计${randomTime}分钟后完成)`;
|
|
|
+
|
|
|
+ // 每分钟展示一条,不重复
|
|
|
+ loadingInterval = setInterval(() => {
|
|
|
+ currentLoadingIndex++;
|
|
|
+ if (currentLoadingIndex < loadingTexts.length) {
|
|
|
+ loadingText.value = loadingTexts[currentLoadingIndex] + '(预计' + loadingTime + '分钟完成)';
|
|
|
+ // const newRandomTime = generateRandomDecimal();
|
|
|
+ // loadingText.value = `${loadingTexts[currentLoadingIndex]} (预计${newRandomTime}秒后完成)`;
|
|
|
+ } else {
|
|
|
+ // 展示到最后一条后停止
|
|
|
+ clearInterval(loadingInterval);
|
|
|
+ loadingInterval = null;
|
|
|
+ }
|
|
|
+ }, 60000); // 每60秒切换一次
|
|
|
+};
|
|
|
+
|
|
|
+// 停止切换加载文本
|
|
|
+const stopLoadingTextCycle = () => {
|
|
|
+ if (loadingInterval) {
|
|
|
+ clearInterval(loadingInterval);
|
|
|
+ loadingInterval = null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const content_style = computed(() => {
|
|
|
+ if (props.type === 'q') {
|
|
|
+ return {
|
|
|
+ background: 'var(--primary-default)',
|
|
|
+ color: '#fff'
|
|
|
+ };
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const renderHtml = (item) => {
|
|
|
+ let renderContent = marked.parse(item);
|
|
|
+ renderContent = renderContent.replace(/^ {2}/gm, ' ');
|
|
|
+ renderContent = renderContent
|
|
|
+ .replace(/<p>(.*?)<\/p>/gs, (match, content) => {
|
|
|
+ return `<p class="indented" style="text-indent: 0em;">${content}</p>`;
|
|
|
+ // return `<p>${content}</p>`
|
|
|
+ })
|
|
|
+ .replace(/<br\s*\/?>([^<]*)/g, (match, content) => {
|
|
|
+ if (content.trim() === '') return match;
|
|
|
+ return `<br/><span style="display:inline-block;text-indent:0em;">${content}</span>`;
|
|
|
+ });
|
|
|
+ renderContent = renderContent
|
|
|
+ .replace(/<pre><code>/gs, '<p style="text-indent: 0em;">')
|
|
|
+ .replace(/<\/code><\/pre>/gs, '</p>');
|
|
|
+ return renderContent;
|
|
|
+};
|
|
|
+
|
|
|
+const createSingTableExportBtn = (table, index) => {
|
|
|
+ table.style.display = 'none';
|
|
|
+ // 创建包含标题和导出按钮的容器
|
|
|
+ const tableContainer = document.createElement('div');
|
|
|
+ tableContainer.className = 'custom-table-container';
|
|
|
+
|
|
|
+ // 创建标题区域
|
|
|
+ const titleDiv = document.createElement('div');
|
|
|
+ titleDiv.className = 'table-title-container';
|
|
|
+ titleDiv.innerHTML = '<span class="table-title">表格数据</span>';
|
|
|
+
|
|
|
+ // 创建按钮容器
|
|
|
+ const btnContainer = document.createElement('div');
|
|
|
+ btnContainer.className = 'arco-export-btn';
|
|
|
+ btnContainer.dataset.tableIndex = index; //标记表格索引
|
|
|
+ console.log(btnContainer, 112121);
|
|
|
+ // 渲染组件
|
|
|
+ render(
|
|
|
+ h(
|
|
|
+ Tooltip,
|
|
|
+ { content: `导出表格${index + 1}` },
|
|
|
+ {
|
|
|
+ default: () =>
|
|
|
+ h(IconDownload, {
|
|
|
+ size: 18,
|
|
|
+ style: { cursor: 'pointer', color: 'var(--color-primary)' },
|
|
|
+ onClick: () => handleSingleTableExport(table, index)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ btnContainer
|
|
|
+ );
|
|
|
+
|
|
|
+ // 将按钮添加到标题区域
|
|
|
+ titleDiv.appendChild(btnContainer);
|
|
|
+ tableContainer.appendChild(titleDiv);
|
|
|
+ // 将容器插入到表格前面
|
|
|
+ table.parentNode.insertBefore(tableContainer, table);
|
|
|
+ exportButtonList.value.push(btnContainer);
|
|
|
+};
|
|
|
+const handleSingleTableExport = (table, tableIndex) => {
|
|
|
+ const workbook = XLSX.utils.book_new();
|
|
|
+ const worksheet = XLSX.utils.table_to_sheet(table);
|
|
|
+ // 添加工作表到工作簿
|
|
|
+ XLSX.utils.book_append_sheet(workbook, worksheet, `表格${tableIndex + 1}数据`);
|
|
|
+
|
|
|
+ // 生成并下载Excel文件
|
|
|
+ XLSX.writeFile(workbook, `表格${tableIndex + 1}_数据导出.xlsx`);
|
|
|
+};
|
|
|
+watch(
|
|
|
+ () => props.answer_status,
|
|
|
+ (new_value) => {
|
|
|
+ // console.log('变化', props.answer_status);
|
|
|
+ if (new_value / 1 == 1) {
|
|
|
+ // 开始加载,重置索引
|
|
|
+ currentLoadingIndex = 0;
|
|
|
+ startLoadingTextCycle();
|
|
|
+ } else if (new_value / 1 == 2) {
|
|
|
+ // 加载完成
|
|
|
+ stopLoadingTextCycle();
|
|
|
+ if (props.type == 'znws') {
|
|
|
+ setTimeout(() => {
|
|
|
+ const tableContainers = document.querySelectorAll('.answer-content');
|
|
|
+ console.log(tableContainers);
|
|
|
+ const tableContainers_filter = Array.from(tableContainers).filter((item) => !item.getAttribute('data-table'));
|
|
|
+ const allTables = [];
|
|
|
+ tableContainers_filter.forEach((item) => {
|
|
|
+ item.setAttribute('data-table', '1');
|
|
|
+ if (item.querySelectorAll('table') && item.querySelectorAll('table').length) {
|
|
|
+ allTables.push(...Array.from(item.querySelectorAll('table')));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (!allTables || !allTables.length) return;
|
|
|
+ Array.from(allTables).forEach((table, index) => {
|
|
|
+ createSingTableExportBtn(table, index);
|
|
|
+ });
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
+// 监听is_loading变化
|
|
|
+watch(
|
|
|
+ () => props.is_loading,
|
|
|
+ (newValue) => {
|
|
|
+ if (newValue) {
|
|
|
+ // 每次进入加载状态时重置索引,从第一条开始
|
|
|
+ currentLoadingIndex = 0;
|
|
|
+ startLoadingTextCycle();
|
|
|
+ } else {
|
|
|
+ stopLoadingTextCycle();
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+onMounted(() => {
|
|
|
+ if (props.result_list && props.result_list.length) {
|
|
|
+ if (props.type == 'znws') {
|
|
|
+ const tableContainers = document.querySelectorAll('.answer-content');
|
|
|
+ console.log(tableContainers);
|
|
|
+ const tableContainers_filter = Array.from(tableContainers).filter((item) => !item.getAttribute('data-table'));
|
|
|
+ const allTables = [];
|
|
|
+ tableContainers_filter.forEach((item) => {
|
|
|
+ item.setAttribute('data-table', '1');
|
|
|
+ if (item.querySelectorAll('table') && item.querySelectorAll('table').length) {
|
|
|
+ allTables.push(...Array.from(item.querySelectorAll('table')));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // const allTables = tableContainer?.querySelectorAll('table');
|
|
|
+ if (!allTables || !allTables.length) return;
|
|
|
+ Array.from(allTables).forEach((table, index) => {
|
|
|
+ createSingTableExportBtn(table, index);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始检查加载状态
|
|
|
+ if (props.is_loading || props.answer_status === 1) {
|
|
|
+ startLoadingTextCycle();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 组件卸载时清除定时器
|
|
|
+onUnmounted(() => {
|
|
|
+ stopLoadingTextCycle();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="css" scoped>
|
|
|
+.dialog-chat-answer-content {
|
|
|
+ position: relative;
|
|
|
+ background-color: var(--color-bg-1);
|
|
|
+ color: var(--color-text-1);
|
|
|
+ border-radius: 12px;
|
|
|
+
|
|
|
+ padding-top: 15px;
|
|
|
+ padding-bottom: 15px;
|
|
|
+ padding-left: 30px;
|
|
|
+ padding-right: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-content-report .answer-content * p {
|
|
|
+ text-indent: 2em !important;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-content * p {
|
|
|
+ text-indent: 2em !important;
|
|
|
+}
|
|
|
+
|
|
|
+p {
|
|
|
+ text-indent: 2em !important;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-chat-answer-content code {
|
|
|
+ overflow-x: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-content-text {
|
|
|
+ line-height: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.loading {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+</style>
|
|
|
+<style>
|
|
|
+.refferences {
|
|
|
+ background: rgb(238, 243, 254);
|
|
|
+ border: 1px solid rgb(255, 255, 255);
|
|
|
+ box-shadow: rgba(29, 48, 91, 0.2) 0px 1px 1px 0px;
|
|
|
+
|
|
|
+ padding: 2px 6px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.refferences:hover {
|
|
|
+ background-color: var(--primary-default);
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.indented br + * {
|
|
|
+ display: inline-block;
|
|
|
+ text-indent: 2em;
|
|
|
+}
|
|
|
+.report-loading {
|
|
|
+ margin-top: 10px;
|
|
|
+}
|
|
|
+.report-loading img {
|
|
|
+ width: 32px;
|
|
|
+}
|
|
|
+</style>
|
|
|
+<style>
|
|
|
+/* 表格基础样式 */
|
|
|
+.answer-content-text table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse; /* 合并边框 */
|
|
|
+ border-spacing: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表头样式 */
|
|
|
+.answer-content-text th {
|
|
|
+ padding: 12px 16px;
|
|
|
+ background-color: #f5f5f5; /* 浅灰色背景 */
|
|
|
+ border: 1px solid #e8e8e8; /* 边框颜色 */
|
|
|
+ font-weight: 500;
|
|
|
+ text-align: center; /* 表头文字居中 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 单元格样式 */
|
|
|
+.answer-content-text td {
|
|
|
+ padding: 12px 16px;
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
+ text-align: left; /* 默认左对齐,会被Markdown的对齐方式覆盖 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 隔行变色(可选) */
|
|
|
+.answer-content-text tr:nth-child(even) {
|
|
|
+ background-color: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+/* 鼠标悬停效果(可选) */
|
|
|
+.answer-content-text tr:hover {
|
|
|
+ background-color: #f0f7ff;
|
|
|
+}
|
|
|
+.arco-export-btn {
|
|
|
+ margin: 3px 0 0 0;
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+.arco-export-btn :deep(.arco-icon) {
|
|
|
+ transition: color 0.2s ease;
|
|
|
+}
|
|
|
+.arco-export-btn :deep(.arco-icon:hover) {
|
|
|
+ color: var(--primary-default);
|
|
|
+}
|
|
|
+
|
|
|
+.custom-table-container {
|
|
|
+ /* margin: 3px 0; */
|
|
|
+}
|
|
|
+
|
|
|
+.table-title-container {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ padding: 8px 16px;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.table-title {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+</style>
|