mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
Merge pull request #90 from GiCo001/dev-darwin
feat(app): 兼容darwin版本浏览跳转,保存图片文件等功能
This commit is contained in:
commit
119f0f8aa7
79
app.go
79
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
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
2d63c3a999d797889c01d6c96451b197
|
||||
8d3264f90073dfceb29c3619775d830d
|
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {onBeforeMount, onUnmounted, ref} from 'vue'
|
||||
import {HotTopic} from "../../wailsjs/go/main/App";
|
||||
import {HotTopic, OpenURL} from "../../wailsjs/go/main/App";
|
||||
import {Environment} from "../../wailsjs/runtime";
|
||||
const list = ref([])
|
||||
const task =ref()
|
||||
|
||||
@ -18,11 +19,20 @@ function openCenteredWindow(url, width, height) {
|
||||
const left = (window.screen.width - width) / 2;
|
||||
const top = (window.screen.height - height) / 2;
|
||||
|
||||
return window.open(
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
window.open(
|
||||
url,
|
||||
'centeredWindow',
|
||||
`width=${width},height=${height},left=${left},top=${top}`
|
||||
);
|
||||
)
|
||||
break
|
||||
default:
|
||||
OpenURL(url)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
function showPage(htid) {
|
||||
openCenteredWindow(`https://gubatopic.eastmoney.com/topic_v3.html?htid=${htid}`, 1000, 600)
|
||||
|
@ -1,14 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import {h, onBeforeMount, onMounted, onUnmounted, ref} from 'vue'
|
||||
import {SearchStock,GetHotStrategy} from "../../wailsjs/go/main/App";
|
||||
import {SearchStock, GetHotStrategy, OpenURL} from "../../wailsjs/go/main/App";
|
||||
import {useMessage, NText, NTag, NButton} from 'naive-ui'
|
||||
import {Environment} from "../../wailsjs/runtime"
|
||||
import {RefreshCircleSharp} from "@vicons/ionicons5";
|
||||
|
||||
const message = useMessage()
|
||||
const search = ref('')
|
||||
const columns = ref([])
|
||||
const dataList = ref([])
|
||||
const hotStrategy = ref([])
|
||||
const traceInfo = ref('')
|
||||
|
||||
function Search() {
|
||||
if (!search.value) {
|
||||
message.warning('请输入选股指标或者要求')
|
||||
@ -70,7 +73,6 @@ function Search() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
dataList.value = res.data.result.dataList
|
||||
@ -81,6 +83,7 @@ function Search() {
|
||||
message.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
function isNumeric(value) {
|
||||
return !isNaN(parseFloat(value)) && isFinite(value);
|
||||
}
|
||||
@ -98,6 +101,7 @@ onBeforeMount(() => {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
function DoSearch(question) {
|
||||
search.value = question
|
||||
Search()
|
||||
@ -107,11 +111,19 @@ function openCenteredWindow(url, width, height) {
|
||||
const left = (window.screen.width - width) / 2;
|
||||
const top = (window.screen.height - height) / 2;
|
||||
|
||||
return window.open(
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
window.open(
|
||||
url,
|
||||
'centeredWindow',
|
||||
`width=${width},height=${height},left=${left},top=${top},location=no,menubar=no,toolbar=no,display=standalone`
|
||||
);
|
||||
)
|
||||
break
|
||||
default:
|
||||
OpenURL(url)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -122,7 +134,8 @@ function openCenteredWindow(url, width, height) {
|
||||
<n-scrollbar style="max-height: calc(100vh - 170px);">
|
||||
<n-list-item v-for="item in hotStrategy" :key="item.rank" @click="DoSearch(item.question)">
|
||||
<n-ellipsis line-clamp="1" :tooltip="true">
|
||||
<n-tag size="small" :bordered="false" type="info">#{{item.rank}}</n-tag><n-text type="warning">{{item.question }}</n-text>
|
||||
<n-tag size="small" :bordered="false" type="info">#{{ item.rank }}</n-tag>
|
||||
<n-text type="warning">{{ item.question }}</n-text>
|
||||
<template #tooltip>
|
||||
<div style="text-align: center;max-width: 180px">
|
||||
<n-text type="warning">{{ item.question }}</n-text>
|
||||
@ -157,7 +170,8 @@ function openCenteredWindow(url, width, height) {
|
||||
<n-flex justify="start" v-if="traceInfo" style="margin: 5px 0">
|
||||
|
||||
<n-ellipsis line-clamp="1" :tooltip="true">
|
||||
<n-text type="info" :bordered="false">选股条件:</n-text><n-text type="warning" :bordered="true">{{traceInfo}}</n-text>
|
||||
<n-text type="info" :bordered="false">选股条件:</n-text>
|
||||
<n-text type="warning" :bordered="true">{{ traceInfo }}</n-text>
|
||||
<template #tooltip>
|
||||
<div style="text-align: center;max-width: 580px">
|
||||
<n-text type="warning">{{ traceInfo }}</n-text>
|
||||
@ -204,7 +218,10 @@ function openCenteredWindow(url, width, height) {
|
||||
}
|
||||
}"
|
||||
/>
|
||||
<n-text>共找到<n-tag type="info" :bordered="false">{{dataList.length}}</n-tag>只股</n-text>
|
||||
<n-text>共找到
|
||||
<n-tag type="info" :bordered="false">{{ dataList.length }}</n-tag>
|
||||
只股
|
||||
</n-text>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
|
@ -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: () => {
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
window.open(msg.html_url)
|
||||
break
|
||||
default :
|
||||
OpenURL(msg.html_url)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}, { default: () => '查看' })
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
@ -384,7 +388,15 @@ EventsOn("updateVersion", async (msg) => {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
window.open(msg.html_url)
|
||||
break
|
||||
default :
|
||||
OpenURL(msg.html_url)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, {default: () => '查看'})
|
||||
}
|
||||
@ -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) {
|
||||
@ -1437,6 +1461,9 @@ window.onerror = function (msg, source, lineno, colno, error) {
|
||||
};
|
||||
|
||||
function saveAsImage(name, code) {
|
||||
Environment().then(env => {
|
||||
switch (env.platform) {
|
||||
case 'windows':
|
||||
const element = document.querySelector('.md-editor-preview');
|
||||
if (element) {
|
||||
html2canvas(element, {
|
||||
@ -1452,6 +1479,24 @@ function saveAsImage(name, code) {
|
||||
} 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,6 +1556,9 @@ AI赋能股票分析:自选股行情获取,成本盈亏展示,涨跌报警
|
||||
`
|
||||
// landscape就是横着的,portrait是竖着的,默认是竖屏portrait。
|
||||
const blob = await asBlob(value, {orientation: 'portrait'})
|
||||
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`;
|
||||
@ -1518,6 +1566,16 @@ AI赋能股票分析:自选股行情获取,成本盈亏展示,涨跌报警
|
||||
// 下载后将标签移除
|
||||
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) {
|
||||
取消关注
|
||||
</n-button>
|
||||
|
||||
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning" @click="aiCheckStock(result['股票名称'],result['股票代码'])">
|
||||
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning"
|
||||
@click="aiCheckStock(result['股票名称'],result['股票代码'])">
|
||||
AI分析
|
||||
</n-button>
|
||||
</template>
|
||||
@ -1756,7 +1815,9 @@ function searchStockReport(stockCode) {
|
||||
<n-text :type="'info'">{{ result["日期"] + " " + result["时间"] }}</n-text>
|
||||
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{ result.volume + "股" }}</n-tag>
|
||||
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">
|
||||
{{ "成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )" }}
|
||||
{{
|
||||
"成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )"
|
||||
}}
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
</template>
|
||||
@ -1815,7 +1876,8 @@ function searchStockReport(stockCode) {
|
||||
</n-text>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<stock-spark-line :last-price="Number(result['当前价格'])" :open-price="Number(result['昨日收盘价'])" :stock-code="result['股票代码']" :stock-name="result['股票名称']" ></stock-spark-line>
|
||||
<stock-spark-line :last-price="Number(result['当前价格'])" :open-price="Number(result['昨日收盘价'])"
|
||||
:stock-code="result['股票代码']" :stock-name="result['股票名称']"></stock-spark-line>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid :cols="2" :y-gap="4" :x-gap="4">
|
||||
@ -1886,7 +1948,8 @@ function searchStockReport(stockCode) {
|
||||
取消关注
|
||||
</n-button>
|
||||
|
||||
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning" @click="aiCheckStock(result['股票名称'],result['股票代码'])">
|
||||
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning"
|
||||
@click="aiCheckStock(result['股票名称'],result['股票代码'])">
|
||||
AI分析
|
||||
</n-button>
|
||||
<n-button secondary type="error" size="tiny"
|
||||
@ -1898,7 +1961,9 @@ function searchStockReport(stockCode) {
|
||||
<n-text :type="'info'">{{ result["日期"] + " " + result["时间"] }}</n-text>
|
||||
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{ result.volume + "股" }}</n-tag>
|
||||
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">
|
||||
{{ "成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )" }}
|
||||
{{
|
||||
"成本:" + result.costPrice + "*" + result.costVolume + " " + result.profit + "%" + " ( " + result.profitAmount + " ¥ )"
|
||||
}}
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
</template>
|
||||
@ -2087,7 +2152,9 @@ function searchStockReport(stockCode) {
|
||||
不启用AI函数工具调用
|
||||
</template>
|
||||
</n-switch>
|
||||
<n-gradient-text type="error" style="margin-left: 10px">*AI函数工具调用可以增强AI获取数据的能力,但会消耗更多tokens。</n-gradient-text>
|
||||
<n-gradient-text type="error" style="margin-left: 10px">
|
||||
*AI函数工具调用可以增强AI获取数据的能力,但会消耗更多tokens。
|
||||
</n-gradient-text>
|
||||
</n-flex>
|
||||
<n-flex justify="space-between" style="margin-bottom: 10px">
|
||||
<n-select style="width: 49%" v-model:value="data.sysPromptId" label-field="name" value-field="ID"
|
||||
|
6
frontend/wailsjs/go/main/App.d.ts
vendored
6
frontend/wailsjs/go/main/App.d.ts
vendored
@ -89,6 +89,8 @@ export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:any,arg5:
|
||||
|
||||
export function NewsPush(arg1:any):Promise<void>;
|
||||
|
||||
export function OpenURL(arg1:string):Promise<void>;
|
||||
|
||||
export function ReFleshTelegraphList(arg1:string):Promise<any>;
|
||||
|
||||
export function RemoveGroup(arg1:number):Promise<string>;
|
||||
@ -99,6 +101,10 @@ export function SaveAIResponseResult(arg1:string,arg2:string,arg3:string,arg4:st
|
||||
|
||||
export function SaveAsMarkdown(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SaveImage(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SaveWordFile(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SearchStock(arg1:string):Promise<Record<string, any>>;
|
||||
|
||||
export function SendDingDingMessage(arg1:string,arg2:string):Promise<string>;
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user