feat(frontend):添加AI分析结果保存和复制功能

- 在 stock.vue 组件中添加了保存分析结果为图片和复制到剪切板的功能
- 引入了 html2canvas 库用于截图
- 优化了 AI 分析结果弹窗的布局
This commit is contained in:
spark 2025-02-11 17:53:06 +08:00
parent 268bc8b1f6
commit c8827da35a
3 changed files with 87 additions and 3 deletions

View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
"html2canvas": "^1.4.1",
"md-editor-v3": "^5.2.1",
"vue": "^3.2.25",
"vue-router": "^4.5.0"
@ -1407,6 +1408,14 @@
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"dev": true
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.1.tgz",
@ -1439,6 +1448,14 @@
"resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/css-render": {
"version": "0.15.14",
"resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.14.tgz",
@ -1567,6 +1584,18 @@
"node": ">=12.0.0"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
@ -1826,6 +1855,14 @@
"resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz",
@ -1842,6 +1879,14 @@
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/vdirs": {
"version": "0.1.8",
"resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz",

View File

@ -10,6 +10,7 @@
},
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
"html2canvas": "^1.4.1",
"md-editor-v3": "^5.2.1",
"vue": "^3.2.25",
"vue-router": "^4.5.0"
@ -20,6 +21,9 @@
"vfonts": "^0.0.3",
"vite": "^5.4.12"
},
"keywords": ["AI赋能股票分析","go-stock"],
"keywords": [
"AI赋能股票分析",
"go-stock"
],
"author": "spark"
}

View File

@ -27,6 +27,7 @@ import {Add, Search,StarOutline} from '@vicons/ionicons5'
import { MdPreview } from 'md-editor-v3';
// preview.cssstyle.css
import 'md-editor-v3/lib/preview.css';
import html2canvas from "html2canvas";
const mdPreviewRef = ref(null)
const message = useMessage()
const modal = useModal()
@ -565,6 +566,34 @@ window.onerror = function (msg, source, lineno, colno, error) {
message.error("发生错误:"+msg)
return true;
};
function saveAsImage() {
const element = document.querySelector('.md-editor-preview');
if (element) {
html2canvas(element,{
useCORS: true, //
scale: 2, //
allowTaint: true, //
}).then(canvas => {
const link = document.createElement('a');
link.href = canvas.toDataURL('image/png');
link.download = 'ai-analysis-result.png';
link.click();
});
} else {
message.error('无法找到分析结果元素');
}
}
async function copyToClipboard() {
try {
await navigator.clipboard.writeText(data.airesult);
message.success('分析结果已复制到剪切板');
} catch (err) {
message.error('复制失败: ' + err);
}
}
</script>
<template>
@ -695,9 +724,9 @@ window.onerror = function (msg, source, lineno, colno, error) {
<n-image :src="data.kURL" />
</n-modal>
<n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;height: 500px" :title="'['+data.name+']AI分析结果'" >
<n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;" :title="'['+data.name+']AI分析结果'" >
<n-spin size="small" :show="data.loading">
<MdPreview ref="mdPreviewRef" style="height: 380px;text-align: left" :modelValue="data.airesult" :theme="'dark'"/>
<MdPreview ref="mdPreviewRef" style="height: 440px;text-align: left" :modelValue="data.airesult" :theme="'dark'"/>
</n-spin>
<template #header-extra>
@ -705,7 +734,13 @@ window.onerror = function (msg, source, lineno, colno, error) {
<template #footer>
<n-flex justify="space-between">
<n-text type="error" v-if="data.time" >分析时间:{{data.time}}</n-text>
</n-flex>
</template>
<template #action>
<n-flex justify="right">
<n-button size="tiny" type="warning" @click="aiReCheckStock(data.name,data.code)">再次分析</n-button>
<n-button size="tiny" type="info" @click="saveAsImage">保存为图片</n-button>
<n-button size="tiny" type="success" @click="copyToClipboard">复制到剪切板</n-button>
</n-flex>
</template>
</n-modal>