sparkmemory 9dc8fa97df feat(settings): 添加推送设置功能- 新增本地推送和钉钉推送的配置选项
- 实现配置的保存和读取功能- 添加测试通知按钮
-优化股票信息的显示格式
2025-01-11 14:16:28 +08:00

513 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import {computed, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
import {
Follow,
GetFollowList,
GetStockList,
Greet,
SendDingDingMessage, SendDingDingMessageByType,
SetAlarmChangePercent,
SetCostPriceAndVolume, SetStockSort,
UnFollow
} from '../../wailsjs/go/main/App'
import {NButton, NFlex, NForm, NFormItem, NInputNumber, NText, useMessage, useModal} from 'naive-ui'
import {EventsOn, WindowFullscreen, WindowReload, WindowUnfullscreen} from '../../wailsjs/runtime'
import {Add, Search,StarOutline} from '@vicons/ionicons5'
const message = useMessage()
const modal = useModal()
const stocks=ref([])
const results=ref({})
const ticker=ref({})
const stockList=ref([])
const followList=ref([])
const options=ref([])
const modalShow = ref(false)
const modalShow2 = ref(false)
const modalShow3 = ref(false)
const addBTN = ref(true)
const formModel = ref({
name: "",
code: "",
costPrice: 0.000,
volume: 0,
alarm: 0,
alarmPrice:0,
sort:999,
})
const data = reactive({
name: "",
code: "",
fenshiURL:"",
kURL:"",
resultText: "Please enter your name below 👇",
fullscreen: false,
})
const sortedResults = computed(() => {
//console.log("computed",sortedResults.value)
const sortedKeys =Object.keys(results.value).sort();
const sortedObject = {};
sortedKeys.forEach(key => {
sortedObject[key] = results.value[key];
});
return sortedObject
});
onBeforeMount(()=>{
GetStockList("").then(result => {
stockList.value = result
options.value=result.map(item => {
return {
label: item.name+" - "+item.ts_code,
value: item.ts_code
}
})
})
GetFollowList().then(result => {
followList.value = result
for (const followedStock of result) {
if (!stocks.value.includes(followedStock.StockCode)) {
stocks.value.push(followedStock.StockCode)
}
}
monitor()
message.destroyAll
})
})
onMounted(() => {
message.loading("Loading...")
// console.log(`the component is now mounted.`)
ticker.value=setInterval(() => {
if(isTradingTime()){
//monitor()
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
}
}, 3500)
})
onBeforeUnmount(() => {
// console.log(`the component is now unmounted.`)
clearInterval(ticker.value)
})
EventsOn("refresh",(data)=>{
message.success(data)
})
EventsOn("showSearch",(data)=>{
addBTN.value = data === 1;
})
EventsOn("stock_price",(data)=>{
//console.log("stock_price",data['股票代码'])
updateData(data)
})
EventsOn("refreshFollowList",(data)=>{
WindowReload()
// message.loading("refresh...")
// GetFollowList().then(result => {
// followList.value = result
// for (const followedStock of result) {
// if (!stocks.value.includes(followedStock.StockCode)) {
// stocks.value.push(followedStock.StockCode)
// }
// }
// monitor()
// message.destroyAll
// })
})
//判断是否是A股交易时间
function isTradingTime() {
const now = new Date();
const day = now.getDay(); // 获取星期几0表示周日1-6表示周一至周六
if (day >= 1 && day <= 5) { // 周一至周五
const hours = now.getHours();
const minutes = now.getMinutes();
const totalMinutes = hours * 60 + minutes;
const startMorning = 9 * 60 + 15; // 上午9点15分换算成分钟数
const endMorning = 11 * 60 + 30; // 上午11点30分换算成分钟数
const startAfternoon = 13 * 60; // 下午13点换算成分钟数
const endAfternoon = 15 * 60; // 下午15点换算成分钟数
if ((totalMinutes >= startMorning && totalMinutes < endMorning) ||
(totalMinutes >= startAfternoon && totalMinutes < endAfternoon)) {
return true;
}
}
return false;
}
function AddStock(){
if (!stocks.value.includes(data.code)) {
stocks.value.push(data.code)
Follow(data.code).then(result => {
message.success(result)
})
monitor()
}else{
message.error("已经关注了")
}
}
function removeMonitor(code,name,key) {
console.log("removeMonitor",name,code,key)
stocks.value.splice(stocks.value.indexOf(code),1)
console.log("removeMonitor-key",key)
console.log("removeMonitor-v",results.value[key])
delete results.value[key]
console.log("removeMonitor-v",results.value[key])
UnFollow(code).then(result => {
message.success(result)
})
}
function getStockList(value){
// console.log("getStockList",value)
let result;
result=stockList.value.filter(item => item.name.includes(value)||item.ts_code.includes(value))
options.value=result.map(item => {
return {
label: item.name+" - "+item.ts_code,
value: item.ts_code
}
})
if(value&&value.indexOf("-")<=0){
data.code=value
}
}
async function updateData(result) {
if(result["当前价格"]<=0){
result["当前价格"]=result["卖一报价"]
}
if (result.changePercent>0) {
result.type="error"
result.color="#E88080"
}else if (result.changePercent<0) {
result.type="success"
result.color="#63E2B7"
}else {
result.type="default"
result.color="#FFFFFF"
}
if(result.profitAmount>0){
result.profitType="error"
}else if(result.profitAmount<0){
result.profitType="success"
}
if(result["当前价格"]){
if(result.alarmChangePercent>0&&Math.abs(result.changePercent)>=result.alarmChangePercent){
SendMessage(result,1)
}
if(result.alarmPrice>0&&result["当前价格"]>=result.alarmPrice){
SendMessage(result,2)
}
if(result.costPrice>0&&result["当前价格"]>=result.costPrice){
SendMessage(result,3)
}
}
result.key=GetSortKey(result.sort,result["股票代码"])
results.value[GetSortKey(result.sort,result["股票代码"])]=result
}
async function monitor() {
for (let code of stocks.value) {
// console.log(code)
Greet(code).then(result => {
updateData(result)
})
}
}
//数字长度不够前面补0
function padZero(num, length) {
return (Array(length).join('0') + num).slice(-length);
}
function GetSortKey(sort,code){
return padZero(sort,6)+"_"+code
}
function onSelect(item) {
//console.log("onSelect",item)
if(item.indexOf("-")>0){
item=item.split("-")[1].toLowerCase()
}
if(item.indexOf(".")>0){
data.code=item.split(".")[1].toLowerCase()+item.split(".")[0]
}
}
function search(code,name){
setTimeout(() => {
window.open("https://xueqiu.com/S/"+code)
}, 500)
}
function setStock(code,name){
let res=followList.value.filter(item => item.StockCode===code)
//console.log("res:",res)
formModel.value.name=name
formModel.value.code=code
formModel.value.volume=res[0].Volume
formModel.value.costPrice=res[0].CostPrice
formModel.value.alarm=res[0].AlarmChangePercent
formModel.value.alarmPrice=res[0].AlarmPrice
formModel.value.sort=res[0].Sort
modalShow.value=true
}
function showFenshi(code,name){
data.code=code
data.name=name
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
modalShow2.value=true
}
function showK(code,name){
data.code=code
data.name=name
data.kURL='http://image.sinajs.cn/newchart/daily/n/'+data.code+'.gif'+"?t="+Date.now()
modalShow3.value=true
}
function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){
if(formModel.sort){
SetStockSort(formModel.sort,code).then(result => {
//message.success(result)
})
}
if(alarm||formModel.alarmPrice){
SetAlarmChangePercent(alarm,formModel.alarmPrice,code).then(result => {
//message.success(result)
})
}
SetCostPriceAndVolume(code,price,volume).then(result => {
modalShow.value=false
message.success(result)
GetFollowList().then(result => {
followList.value = result
for (const followedStock of result) {
if (!stocks.value.includes(followedStock.StockCode)) {
stocks.value.push(followedStock.StockCode)
}
}
monitor()
message.destroyAll
})
})
}
function fullscreen(){
if(data.fullscreen){
WindowUnfullscreen()
}else{
WindowFullscreen()
}
data.fullscreen=!data.fullscreen
}
//type 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警
function SendMessage(result,type){
let typeName=getTypeName(type)
let img='http://image.sinajs.cn/newchart/min/n/'+result["股票代码"]+'.gif'+"?t="+Date.now()
let markdown="### go-stock ["+typeName+"]\n\n"+
"### "+result["股票名称"]+"("+result["股票代码"]+")\n" +
"- 当前价格: "+result["当前价格"]+" "+result.changePercent+"%\n" +
"- 最高价: "+result["今日最高价"]+" "+result.highRate+"\n" +
"- 最低价: "+result["今日最低价"]+" "+result.lowRate+"\n" +
"- 昨收价: "+result["昨日收盘价"]+"\n" +
"- 今开价: "+result["今日开盘价"]+"\n" +
"- 成本价: "+result.costPrice+" "+result.profit+"% "+result.profitAmount+" ¥\n" +
"- 成本数量: "+result.costVolume+"股\n" +
"- 日期: "+result["日期"]+" "+result["时间"]+"\n\n"+
"![image]("+img+")\n"
let title=result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.changePercent
let msg='{' +
' "msgtype": "markdown",' +
' "markdown": {' +
' "title":"['+typeName+"]"+title+'",' +
' "text": "'+markdown+'"' +
' },' +
' "at": {' +
' "isAtAll": true' +
' }' +
' }'
// SendDingDingMessage(msg,result["股票代码"])
SendDingDingMessageByType(msg,result["股票代码"],type)
}
function getTypeName(type){
switch (type)
{
case 1:
return "涨跌报警"
case 2:
return "股价报警"
case 3:
return "成本价报警"
default:
return ""
}
}
//获取高度
function getHeight() {
return document.documentElement.clientHeight
}
</script>
<template>
<n-grid :x-gap="8" :cols="3" :y-gap="8" >
<n-gi v-for="result in sortedResults" >
<n-card :data-code="result['股票代码']" :bordered="false" :title="result['股票名称']" :closable="false" @close="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
<n-grid :cols="1" :y-gap="6">
<n-gi>
<n-text :type="result.type" >
<n-number-animation :duration="1000" :precision="2" :from="result['上次当前价格']" :to="Number(result['当前价格'])" />
</n-text>
<n-text style="padding-left: 10px;" :type="result.type">
<n-number-animation :duration="1000" :precision="3" :from="0" :to="result.changePercent" />%
</n-text>&nbsp;
<n-text size="small" v-if="result.costVolume>0" :type="result.type">
<n-number-animation :duration="1000" :precision="2" :from="0" :to="result.profitAmountToday" />
</n-text>
</n-gi>
</n-grid>
<n-grid :cols="2" :y-gap="4" :x-gap="4" >
<n-gi>
<n-text :type="'info'">{{"最高 "+result["今日最高价"]+" "+result.highRate }}%</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"最低 "+result["今日最低价"]+" "+result.lowRate }}%</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"昨收 "+result["昨日收盘价"]}}</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"今开 "+result["今日开盘价"]}}</n-text>
</n-gi>
</n-grid>
<template #header-extra>
<n-button size="tiny" secondary type="primary" @click="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
取消关注
</n-button>
</template>
<template #footer>
<n-flex justify="center">
<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+" ¥ )"}}</n-tag>
</n-flex>
</template>
<template #action>
<n-flex justify="space-between">
<n-text :type="'info'">{{result["日期"]+" "+result["时间"]}}</n-text>
<n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button>
<n-button size="tiny" type="success" @click="showFenshi(result['股票代码'],result['股票名称'])"> 分时 </n-button>
<n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K </n-button>
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
<!-- <n-button size="tiny" type="info" @click="SendMessage(result)"> 钉钉 </n-button>-->
</n-flex>
</template>
</n-card >
</n-gi>
</n-grid>
<n-affix :trigger-bottom="15" style="right:0;z-index: 99990;">
<!-- <n-card :bordered="false">-->
<n-input-group>
<n-button type="error" @click="addBTN=!addBTN" > <n-icon :component="Search"/>&nbsp;<n-text v-if="addBTN">隐藏</n-text></n-button>
<n-auto-complete v-model:value="data.name" v-if="addBTN"
:input-props="{
autocomplete: 'disabled',
}"
:options="options"
placeholder="请输入股票/指数名称或者代码"
clearable @update-value="getStockList" :on-select="onSelect"/>
<n-button type="primary" @click="AddStock" v-if="addBTN">
<n-icon :component="Add"/> &nbsp;关注该股票
</n-button>
</n-input-group>
<!-- </n-card>-->
</n-affix>
<n-modal transform-origin="center" size="small" v-model:show="modalShow" :title="formModel.name" style="width: 400px" :preset="'card'">
<n-form :model="formModel" :rules="{
costPrice: { required: true, message: '请输入成本'},
volume: { required: true, message: '请输入数量'},
alarm:{required: true, message: '涨跌报警值'} ,
alarmPrice: { required: true, message: '请输入报警价格'},
sort: { required: true, message: '请输入排序值'},
}" label-placement="left" label-width="80px">
<n-form-item label="股票成本" path="costPrice">
<n-input-number v-model:value="formModel.costPrice" min="0" placeholder="请输入股票成本" >
<template #suffix>
¥
</template>
</n-input-number>
</n-form-item>
<n-form-item label="股票数量" path="volume">
<n-input-number v-model:value="formModel.volume" min="0" step="100" placeholder="请输入股票数量" >
<template #suffix>
</template>
</n-input-number>
</n-form-item>
<n-form-item label="涨跌提醒" path="alarm">
<n-input-number v-model:value="formModel.alarm" min="0" placeholder="请输入涨跌报警值(%)" >
<template #suffix>
%
</template>
</n-input-number>
</n-form-item>
<n-form-item label="股价提醒" path="alarmPrice">
<n-input-number v-model:value="formModel.alarmPrice" min="0" placeholder="请输入股价报警值(¥)" >
<template #suffix>
¥
</template>
</n-input-number>
</n-form-item>
<n-form-item label="股票排序" path="sort">
<n-input-number v-model:value="formModel.sort" min="0" placeholder="请输入股价排序值" >
</n-input-number>
</n-form-item>
</n-form>
<template #footer>
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume,formModel.alarm,formModel)">保存</n-button>
</template>
</n-modal>
<n-modal v-model:show="modalShow2" :title="data.name" style="width: 600px" :preset="'card'">
<n-image :src="data.fenshiURL" />
</n-modal>
<n-modal v-model:show="modalShow3" :title="data.name" style="width: 600px" :preset="'card'">
<n-image :src="data.kURL" />
</n-modal>
</template>
<style scoped>
</style>