kagg886 пре 1 месец
родитељ
комит
37c1c948b7

+ 1 - 0
package.json

@@ -76,6 +76,7 @@
     "xlsx-with-styles": "0.17.2"
   },
   "devDependencies": {
+    "@types/downloadjs": "^1.4.6",
     "@types/js-cookie": "3.0.6",
     "@types/markdown-it": "^14.1.2",
     "@types/node": "17.0.21",

+ 14 - 2
src/api/assist/index.ts

@@ -9,7 +9,8 @@ import {
 	LmConfigDeleteParams,
 	LmConfigGetParams,
 	SessionMessagesListParams,
-	SessionMessagesSaveReq, LmDashboard,
+	SessionMessagesSaveReq,
+	LmDashboard,
 } from '/@/api/assist/type'
 import { get, post, del, put } from '/@/utils/request'
 import getOrigin from '/@/utils/origin'
@@ -65,10 +66,12 @@ export default {
 					messageId,
 					like,
 				}),
-			bookmark_list: (param: { keyWord?:string,pageNum?: number,pageSize?: number } = {}) => get('/system/lmsessions/messages/like/list', param),
+			bookmark_list: (param: { keyWord?: string; pageNum?: number; pageSize?: number } = {}) => get('/system/lmsessions/messages/like/list', param),
 		},
 	},
 
+	struct: (uuid: string) => post('/ai/chat/structdata', { uuid }),
+
 	// SSE聊天方法
 	chat: (data: {
 		chatRequest: ChatRequest
@@ -202,6 +205,15 @@ export default {
 									break
 								}
 
+								case 'structdata': {
+									const structDataResponse: ChatResponse = {
+										type: 'structdata',
+										uuid: data,
+									}
+									onReceive(structDataResponse)
+									break
+								}
+
 								case 'toolres': {
 									const tools = JSON.parse(data)
 									const toolresResponse: ChatResponse = {

+ 139 - 2
src/components/markdown/plugins/impl/VueStructData.vue

@@ -1,11 +1,148 @@
 <script setup lang="ts">
+import { ref, onMounted, computed } from 'vue'
+import { ElMessage } from 'element-plus'
+import { Download, Grid, TrendCharts, BarChart } from '@element-plus/icons-vue'
+import assist from '/@/api/assist'
+import { useLoading } from '/@/utils/loading-util'
+import VueCharts from './VueCharts.vue'
+import download from 'downloadjs'
+
 const props = defineProps<{
 	uuid: string
 }>()
+
+// 定义数据结构类型
+interface StructData {
+	fields: string[]
+	data: { [key: string]: string }[]
+}
+
+const data = ref<StructData>()
+
+const { loading: loadingStruct, doLoading: doLoadingStruct } = useLoading(async () => {
+	data.value = await assist.struct(props.uuid)
+})
+
+// 导出数据为CSV格式
+const exportToCSV = () => {
+	if (!data.value || !data.value.fields || !data.value.data) {
+		ElMessage.warning('暂无数据可导出')
+		return
+	}
+
+	try {
+		// 构建CSV内容
+		const headers = data.value.fields.map((it) => `"${it}"`).join(',')
+		const rows = data.value.data.map(rowJson => {
+			return data.value!.fields.map(field => `"${rowJson[field]}"`).join(',')
+		})
+
+		const csvContent = [headers, ...rows].join('\n')
+
+		download(csvContent, 'export.csv', 'text/csv,charset=utf-8;')
+
+		ElMessage.success('数据导出成功')
+	} catch (error) {
+		console.error('导出失败:', error)
+		ElMessage.error('导出失败,请重试')
+	}
+}
+
+// 组件挂载时加载数据
+onMounted(doLoadingStruct)
+
+const display = ref<'table' | 'bar' | 'line'>('table')
+
+const barChart = ref()
+const lineChart = ref()
 </script>
 
 <template>
-	<div>{{props.uuid}}</div>
+	<div class="struct-data-container">
+		<el-card v-loading="loadingStruct" shadow="none" :body-style="{ padding: 0, margin: 0 }">
+			<template #header>
+				<div class="card-header">
+					<span>结构化数据表格</span>
+					<el-popover placement="top" :width="120" trigger="hover" content="导出为CSV文件">
+						<template #reference>
+							<el-button
+								:icon="Download"
+								size="small"
+								text
+								circle
+								@click="exportToCSV"
+								:disabled="!data || !data.fields || !data.data || data.data.length === 0"
+								class="export-btn"
+							/>
+						</template>
+					</el-popover>
+				</div>
+			</template>
+			<!-- 表格内容 -->
+			<div v-if="data && data.fields && data.data" class="table-wrapper">
+				<el-table :data="data.data" stripe border size="small" max-height="400">
+					<el-table-column v-for="field in data.fields" :key="field" :prop="field" :label="field" align="center" min-width="120">
+						<template #default="{ row }">
+							<span>{{ row[field] || '-' }}</span>
+						</template>
+					</el-table-column>
+				</el-table>
+			</div>
+
+			<!-- 空数据状态 -->
+			<div v-else-if="!loadingStruct" class="empty-state">
+				<el-empty description="暂无数据" />
+			</div>
+		</el-card>
+	</div>
 </template>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.struct-data-container {
+	margin: 16px 0;
+
+	.card-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		font-weight: 500;
+		color: var(--el-text-color-primary);
+
+		.export-btn {
+			color: var(--el-color-primary);
+
+			&:hover {
+				background-color: var(--el-color-primary-light-9);
+			}
+
+			&:disabled {
+				color: var(--el-text-color-disabled);
+				cursor: not-allowed;
+			}
+		}
+	}
+
+	.table-wrapper {
+		.el-table {
+			overflow: hidden;
+		}
+	}
+
+	.empty-state {
+		padding: 40px 0;
+		text-align: center;
+	}
+
+	:deep(table) {
+		margin: 0 !important;
+	}
+}
+
+.el-card {
+	background: transparent !important;
+}
+
+:deep(.el-table__cell) {
+	background: transparent !important;
+}
+</style>

+ 7 - 7
src/components/markdown/plugins/struct-data.ts

@@ -5,22 +5,22 @@ import { defineMarkdownPlugin } from '../type/markdown.ts'
 import { h } from 'vue'
 import VueStructData from '/@/components/markdown/plugins/impl/VueStructData.vue'
 
-// 渲染echarts代码块
+// 渲染结构化数据代码块
 const renderStructData: RenderRule = (tokens: Token[], idx: number) => {
 	const token = tokens[idx]
 	const content = token.content.trim()
 
 	if (!content) {
-		return '<div style="padding: 16px;background-color: #fff5f5;border: 1px solid #fed7d7;border-radius: 6px;color: #c53030;margin: 16px 0;">ECharts配置不能为空</div>'
+		return '<div style="padding: 16px;background-color: #fff5f5;border: 1px solid #fed7d7;border-radius: 6px;color: #c53030;margin: 16px 0;">结构化数据UUID不能为空</div>'
 	}
 	// 生成完整HTML
-	return `<struct-data style="width: 100%;height: 350px;margin: 16px 0; border-radius: 6px" data="${encodeURIComponent(
+	return `<structdata style="width: 100%;margin: 16px 0; border-radius: 6px" uuid="${encodeURIComponent(
 		content
-	)}"></struct-data>`
+	)}"></structdata>`
 }
 
 const StructDataPlugin = defineMarkdownPlugin({
-	tagName: 'struct-data',
+	tagName: 'structdata',
 	mdItPlugin: function (md: MarkdownIt) {
 		// 保存原始的fence渲染器
 		const defaultRender =
@@ -38,7 +38,7 @@ const StructDataPlugin = defineMarkdownPlugin({
 			const token = tokens[idx]
 			const info = token.info ? token.info.trim() : ''
 
-			// 检查是否是echarts代码块
+			// 检查是否是结构化数据代码块
 			if (info === 'structdata') {
 				return renderStructData(tokens, idx, options, env, renderer)
 			}
@@ -49,7 +49,7 @@ const StructDataPlugin = defineMarkdownPlugin({
 	},
 	renderer: (node: { attribs: Record<string, string> }) => {
 		return h(VueStructData, {
-			data: node.attribs.data,
+			uuid: decodeURIComponent(node.attribs.uuid || ''),
 		})
 	},
 })

+ 5 - 0
yarn.lock

@@ -545,6 +545,11 @@
   resolved "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-2.0.3.tgz"
   integrity sha512-jhAJzaanK5LqyLQ50jJNIrB8fjL9gwWZTgYjevPvkDLMU+kTAZkYsobI59nYoeSrH1PucuyJEi247Pb90t6XUg==
 
+"@types/downloadjs@^1.4.6":
+  version "1.4.6"
+  resolved "https://registry.yarnpkg.com/@types/downloadjs/-/downloadjs-1.4.6.tgz#d03ed04d17332ebf3b47d7a04cbe745dab1ab8a9"
+  integrity sha512-mp3w70vsaiLRT9ix92fmI9Ob2yJAPZm6tShJtofo2uHbN11G2i6a0ApIEjBl/kv3e9V7Pv7jMjk1bUwYWvMHvA==
+
 "@types/js-cookie@3.0.6":
   version "3.0.6"
   resolved "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-3.0.6.tgz"