diff --git a/app.go b/app.go index 4513fc9..8f33d7b 100644 --- a/app.go +++ b/app.go @@ -14,6 +14,7 @@ import ( "go-stock/backend/logger" "go-stock/backend/models" "os" + "path/filepath" "strings" "time" @@ -1201,3 +1202,81 @@ func (a *App) GetStockMoneyTrendByDay(stockCode string, days int) []map[string]a slice.Reverse(res) return res } + +// OpenURL +// +// @Description: 跨平台打开默认浏览器 +// @receiver a +// @param url +func (a *App) OpenURL(url string) { + runtime.BrowserOpenURL(a.ctx, url) +} + +// SaveImage +// +// @Description: 跨平台保存图片 +// @receiver a +// @param name +// @param base64Data +// @return error +func (a *App) SaveImage(name, base64Data string) string { + // 打开保存文件对话框 + filePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ + Title: "保存图片", + DefaultFilename: name + "AI分析.png", + Filters: []runtime.FileFilter{ + { + DisplayName: "PNG 图片", + Pattern: "*.png", + }, + }, + }) + if err != nil || filePath == "" { + return "文件路径,无法保存。" + } + + // 解码并保存 + decodeString, err := base64.StdEncoding.DecodeString(base64Data) + if err != nil { + return "文件内容异常,无法保存。" + } + + err = os.WriteFile(filepath.Clean(filePath), decodeString, 0777) + if err != nil { + return "保存结果异常,无法保存。" + } + return filePath +} + +// SaveWordFile +// +// @Description: // 跨平台保存word +// @receiver a +// @param filename +// @param base64Data +// @return error +func (a *App) SaveWordFile(filename string, base64Data string) string { + // 弹出保存文件对话框 + filePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ + Title: "保存 Word 文件", + DefaultFilename: filename, + Filters: []runtime.FileFilter{ + {DisplayName: "Word 文件", Pattern: "*.docx"}, + }, + }) + if err != nil || filePath == "" { + return "文件路径,无法保存。" + } + + // 解码 base64 内容 + decodeString, err := base64.StdEncoding.DecodeString(base64Data) + if err != nil { + return "文件内容异常,无法保存。" + } + // 保存为文件 + err = os.WriteFile(filepath.Clean(filePath), decodeString, 0777) + if err != nil { + return "保存结果异常,无法保存。" + } + return filePath +} diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index fce852e..799119d 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -2d63c3a999d797889c01d6c96451b197 \ No newline at end of file +8d3264f90073dfceb29c3619775d830d \ No newline at end of file diff --git a/frontend/src/components/HotTopics.vue b/frontend/src/components/HotTopics.vue index e203788..c4c1c2b 100644 --- a/frontend/src/components/HotTopics.vue +++ b/frontend/src/components/HotTopics.vue @@ -1,6 +1,7 @@ - - - - - - #{{item.rank}}{{item.question }} - - - {{item.question }} - - - - + + + + + + #{{ item.rank }} + {{ item.question }} + + + {{ item.question }} + + + + - - - - - - - - - - + + + + + + + + + + - - + + - + - + 搜索A股 - - 选股条件:{{traceInfo}} + + 选股条件: + {{ traceInfo }} - {{traceInfo}} + {{ traceInfo }} @@ -204,7 +218,10 @@ function openCenteredWindow(url, width, height) { } }" /> - 共找到{{dataList.length}}只股 + 共找到 + {{ dataList.length }} + 只股 + diff --git a/frontend/src/components/about.vue b/frontend/src/components/about.vue index 72a723d..f00429f 100644 --- a/frontend/src/components/about.vue +++ b/frontend/src/components/about.vue @@ -3,8 +3,8 @@ // preview.css相比style.css少了编辑器那部分样式 import 'md-editor-v3/lib/preview.css'; import {h, onBeforeUnmount, onMounted, ref} from 'vue'; -import {CheckUpdate, GetVersionInfo,GetSponsorInfo} from "../../wailsjs/go/main/App"; -import {EventsOff, EventsOn} from "../../wailsjs/runtime"; +import {CheckUpdate, GetVersionInfo,GetSponsorInfo,OpenURL} from "../../wailsjs/go/main/App"; +import {EventsOff, EventsOn,Environment} from "../../wailsjs/runtime"; import {NAvatar, NButton, useNotification} from "naive-ui"; const updateLog = ref(''); const versionInfo = ref(''); @@ -85,7 +85,16 @@ EventsOn("updateVersion",async (msg) => { type: 'primary', size: 'small', onClick: () => { - window.open(msg.html_url) + Environment().then(env => { + switch (env.platform) { + case 'windows': + window.open(msg.html_url) + break + default : + OpenURL(msg.html_url) + break + } + }) } }, { default: () => '查看' }) } diff --git a/frontend/src/components/fund.vue b/frontend/src/components/fund.vue index f45b6fa..07d4047 100644 --- a/frontend/src/components/fund.vue +++ b/frontend/src/components/fund.vue @@ -7,7 +7,7 @@ import { GetConfig, GetFollowedFund, GetfundList, - GetVersionInfo, + GetVersionInfo, OpenURL, UnFollowFund } from "../../wailsjs/go/main/App"; import vueDanmaku from 'vue3-danmaku' @@ -147,8 +147,19 @@ function formatterTitle(title){ function search(code,name){ setTimeout(() => { - window.open("https://fund.eastmoney.com/"+code+".html","_blank","noreferrer,width=1000,top=100,left=100,status=no,toolbar=no,location=no,scrollbars=no") + //window.open("https://fund.eastmoney.com/"+code+".html","_blank","noreferrer,width=1000,top=100,left=100,status=no,toolbar=no,location=no,scrollbars=no") //window.open("https://finance.sina.com.cn/fund/quotes/"+code+"/bc.shtml","_blank","width=1000,height=800,top=100,left=100,toolbar=no,location=no") + + Environment().then(env => { + switch (env.platform) { + case 'windows': + window.open("https://fund.eastmoney.com/"+code+".html","_blank","noreferrer,width=1000,top=100,left=100,status=no,toolbar=no,location=no,scrollbars=no") + break + default : + OpenURL("https://fund.eastmoney.com/"+code+".html") + } + }) + }, 500) } diff --git a/frontend/src/components/stock.vue b/frontend/src/components/stock.vue index 4ee8f1d..b124dfc 100644 --- a/frontend/src/components/stock.vue +++ b/frontend/src/components/stock.vue @@ -26,7 +26,10 @@ import { SetStockAICron, SetStockSort, ShareAnalysis, - UnFollow + UnFollow, + OpenURL, + SaveImage, + SaveWordFile } from '../../wailsjs/go/main/App' import { NAvatar, @@ -41,6 +44,7 @@ import { useNotification } from 'naive-ui' import { + Environment, EventsEmit, EventsOff, EventsOn, @@ -103,7 +107,7 @@ const modalShow3 = ref(false) const modalShow4 = ref(false) const modalShow5 = ref(false) const addBTN = ref(true) -const enableTools= ref(false) +const enableTools = ref(false) const formModel = ref({ name: "", code: "", @@ -384,7 +388,15 @@ EventsOn("updateVersion", async (msg) => { type: 'primary', size: 'small', onClick: () => { - window.open(msg.html_url) + Environment().then(env => { + switch (env.platform) { + case 'windows': + window.open(msg.html_url) + break + default : + OpenURL(msg.html_url) + } + }) } }, {default: () => '查看'}) } @@ -441,7 +453,7 @@ function AddStock() { Follow(data.code).then(result => { if (result === "关注成功") { if (data.code.startsWith("us")) { - data.code= "gb_" + data.code.replace("us", "").toLowerCase() + data.code = "gb_" + data.code.replace("us", "").toLowerCase() } stocks.value.push(data.code) message.success(result) @@ -614,12 +626,24 @@ function onSelect(item) { function openCenteredWindow(url, width, height) { const left = (window.screen.width - width) / 2; const top = (window.screen.height - height) / 2; + Environment().then(env => { + switch (env.platform) { + case 'windows': + window.open(url, target, features) + break + default : + OpenURL(url) + break + } + }) - return window.open( - url, - 'centeredWindow', - `width=${width},height=${height},left=${left},top=${top}` - ); + + // + // return window.open( + // url, + // 'centeredWindow', + // `width=${width},height=${height},left=${left},top=${top}` + // ); } function search(code, name) { @@ -631,7 +655,7 @@ function search(code, name) { //window.open("https://www.iwencai.com/unifiedwap/result?w=" + name) //window.open("https://www.iwencai.com/chat/?question="+code) - openCenteredWindow("https://www.iwencai.com/unifiedwap/result?w=" + name,1000,800) + openCenteredWindow("https://www.iwencai.com/unifiedwap/result?w=" + name, 1000, 800) }, 500) } @@ -1359,7 +1383,7 @@ function aiReCheckStock(stock, stockCode) { // //message.info("sysPromptId:"+data.sysPromptId) - NewChatStream(stock, stockCode, data.question, data.sysPromptId,enableTools.value) + NewChatStream(stock, stockCode, data.question, data.sysPromptId, enableTools.value) } function aiCheckStock(stock, stockCode) { @@ -1437,21 +1461,42 @@ window.onerror = function (msg, source, lineno, colno, error) { }; function saveAsImage(name, code) { - 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 = name + "[" + code + ']-ai-analysis-result.png'; - link.click(); - }); - } else { - message.error('无法找到分析结果元素'); - } + Environment().then(env => { + switch (env.platform) { + case 'windows': + 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 = name + "[" + code + ']-ai-analysis-result.png'; + link.click(); + }); + } else { + message.error('无法找到分析结果元素'); + } + break + default : + saveCanvasImage(name) + } + }) +} + +async function saveCanvasImage(name) { + const element = document.querySelector('.md-editor-preview'); // 要截图的 DOM 节点 + const canvas = await html2canvas(element) + + const dataUrl = canvas.toDataURL('image/png') // base64 格式 + const base64 = dataUrl.replace(/^data:image\/png;base64,/, '') + + // 调用 Go 后端保存文件(Wails 绑定方法) + await SaveImage(name,base64).then(result => { + message.success(result) + }) } async function copyToClipboard() { @@ -1511,13 +1556,26 @@ AI赋能股票分析:自选股行情获取,成本盈亏展示,涨跌报警 ` // landscape就是横着的,portrait是竖着的,默认是竖屏portrait。 const blob = await asBlob(value, {orientation: 'portrait'}) - const a = document.createElement('a') - a.href = URL.createObjectURL(blob) - a.download = `${data.name}[${data.code}]-ai-analysis-result.docx`; - a.click() - // 下载后将标签移除 - URL.revokeObjectURL(a.href); - a.remove() + const { platform } = await Environment() + switch (platform) { + case 'windows': + const a = document.createElement('a') + a.href = URL.createObjectURL(blob) + a.download = `${data.name}[${data.code}]-ai-analysis-result.docx`; + a.click() + // 下载后将标签移除 + URL.revokeObjectURL(a.href); + a.remove() + break + default: + const arrayBuffer = await blob.arrayBuffer() + const uint8Array = new Uint8Array(arrayBuffer) + const binary = uint8Array.reduce((data, byte) => data + String.fromCharCode(byte), '') + const base64 = btoa(binary) + await SaveWordFile(`${data.name}[${data.code}]-ai-analysis-result.docx`, base64).then(result => { + message.success(result) + }) + } } function share(code, name) { @@ -1747,7 +1805,8 @@ function searchStockReport(stockCode) { 取消关注 - + AI分析 @@ -1756,7 +1815,9 @@ function searchStockReport(stockCode) { {{ result["日期"] + " " + result["时间"] }} {{ result.volume + "股" }} - {{ "成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )" }} + {{ + "成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )" + }} @@ -1815,7 +1876,8 @@ function searchStockReport(stockCode) { - + @@ -1886,9 +1948,10 @@ function searchStockReport(stockCode) { 取消关注 - - AI分析 - + + AI分析 + 移出分组 @@ -1898,7 +1961,9 @@ function searchStockReport(stockCode) { {{ result["日期"] + " " + result["时间"] }} {{ result.volume + "股" }} - {{ "成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )" }} + {{ + "成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )" + }} @@ -2087,7 +2152,9 @@ function searchStockReport(stockCode) { 不启用AI函数工具调用 - *AI函数工具调用可以增强AI获取数据的能力,但会消耗更多tokens。 + + *AI函数工具调用可以增强AI获取数据的能力,但会消耗更多tokens。 + ; +export function OpenURL(arg1:string):Promise; + export function ReFleshTelegraphList(arg1:string):Promise; export function RemoveGroup(arg1:number):Promise; @@ -99,6 +101,10 @@ export function SaveAIResponseResult(arg1:string,arg2:string,arg3:string,arg4:st export function SaveAsMarkdown(arg1:string,arg2:string):Promise; +export function SaveImage(arg1:string,arg2:string):Promise; + +export function SaveWordFile(arg1:string,arg2:string):Promise; + export function SearchStock(arg1:string):Promise>; export function SendDingDingMessage(arg1:string,arg2:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index d2625de..8034116 100755 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -174,6 +174,10 @@ export function NewsPush(arg1) { return window['go']['main']['App']['NewsPush'](arg1); } +export function OpenURL(arg1) { + return window['go']['main']['App']['OpenURL'](arg1); +} + export function ReFleshTelegraphList(arg1) { return window['go']['main']['App']['ReFleshTelegraphList'](arg1); } @@ -194,6 +198,14 @@ export function SaveAsMarkdown(arg1, arg2) { return window['go']['main']['App']['SaveAsMarkdown'](arg1, arg2); } +export function SaveImage(arg1, arg2) { + return window['go']['main']['App']['SaveImage'](arg1, arg2); +} + +export function SaveWordFile(arg1, arg2) { + return window['go']['main']['App']['SaveWordFile'](arg1, arg2); +} + export function SearchStock(arg1) { return window['go']['main']['App']['SearchStock'](arg1); }