完成2.0版

master
宇天 2020-12-12 19:24:23 +08:00
parent e51c15db73
commit 1fb6b67fa8
16 changed files with 752 additions and 6316 deletions

View File

@ -1,7 +1,9 @@
## 摸鱼搞基小工具 ## 搞基爱啪啪
> 搞基专用, 你懂的。 > “搞基爱啪啪” 是一款开源的, 非专业的搞基软件, 上面的数据全来自网络, 不对准确性作任何保证.
搞基有风险, 入行需谨慎. 你亏了别找我, 赚了可以给我发红包.
## 编译 ## 编译
```bash ```bash
@ -10,4 +12,4 @@ npm run pack
``` ```
![preview](./preview.jpg) ![preview](./preview.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

BIN
preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -1 +1 @@
html{font-size:12.8px;width:100%;height:100vh}body{overflow:hidden;display:flex;flex-direction:column;width:100%;height:100%;line-height:1.25;font-size:14px;color:var(--color-dark-1);background:rgba(255,255,255,0.3)}.app-drag{-webkit-app-region:drag;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.app-nodrag{-webkit-app-region:no-drag}.app{position:relative;display:flex;height:100%}.app .sidebar{display:flex;flex-direction:column;justify-content:space-between;width:76px;height:100%;padding:48px 22px 24px;background:var(--color-dark-1);color:var(--color-plain-1)}.app .sidebar .item{cursor:pointer}.app .sidebar .item:hover,.app .sidebar .item.active{color:var(--color-orange-1)}.app .sidebar .item:active{color:var(--color-orange-2)}.app .select-box{display:flex;flex-direction:column;width:200px;height:100%;background:rgba(255,255,255,0.5)}.app .select-box .form{display:flex;align-items:center;width:100%;height:35px;padding:0 6px;background:#fff;border-bottom:1px solid var(--color-plain-2)}.app .select-box .form wc-input{flex:1;margin-right:12px}.app .select-box .list{flex:1}.app .select-box .list .item{display:flex;flex-direction:column;justify-content:center;height:48px;padding:6px;transition:color 0.15s ease-in-out, background 0.15s ease-in-out;cursor:pointer}.app .select-box .list .item section{display:flex;justify-content:space-between;align-items:center}.app .select-box .list .item cite{color:var(--color-grey-2)}.app .select-box .list .item .percent{padding:0 4px;border-radius:2px;font-size:12px;color:#fff;background:var(--color-grey-1)}.app .select-box .list .item .percent.red{background:var(--color-red-1)}.app .select-box .list .item .percent.green{background:var(--color-green-3)}.app .select-box .list .item:hover{color:var(--color-orange-1);background:rgba(255,255,255,0.35)}.app .select-box .list .item.active{color:var(--color-orange-3);background:rgba(255,255,255,0.7)}.app .detail{flex:1;height:100%;border-left:1px solid var(--color-plain-2);background:#fff}.app .detail .title{width:100%;height:35px;padding:0 16px;line-height:35px;font-size:16px;font-weight:bold}.app .detail .card{width:96%;padding:12px 12px 16px;margin:12px 2% 24px;border:0;background:#fff;box-shadow:0 0 8px rgba(0,0,0,0.075)}.app .detail .card legend{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#64b5f6} html{font-size:12.8px;width:100%;height:100vh}body{overflow:hidden;display:flex;flex-direction:column;width:100%;height:100%;line-height:1.25;font-size:14px;color:var(--color-dark-1);background:rgba(255,255,255,0.3)}.app-drag{-webkit-app-region:drag;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.app-nodrag{-webkit-app-region:no-drag}.app{position:relative;display:flex;height:100%}.app .sidebar{display:flex;flex-direction:column;justify-content:space-between;width:76px;height:100%;padding:48px 22px 24px;background:var(--color-dark-1);color:var(--color-plain-1)}.app .sidebar .item{cursor:pointer}.app .sidebar .item:hover,.app .sidebar .item.active{color:var(--color-orange-1)}.app .sidebar .item:active{color:var(--color-orange-2)}.app .select-box{display:flex;flex-direction:column;width:200px;height:100%;background:rgba(255,255,255,0.5)}.app .select-box .form{display:flex;align-items:center;width:100%;height:35px;padding:0 6px;background:#fff;border-bottom:1px solid var(--color-plain-2)}.app .select-box .form wc-input{flex:1}.app .select-box .list{flex:1}.app .select-box .list .item{display:flex;flex-direction:column;justify-content:center;height:48px;padding:6px;transition:color 0.15s ease-in-out, background 0.15s ease-in-out;cursor:pointer}.app .select-box .list .item section{display:flex;justify-content:space-between;align-items:center}.app .select-box .list .item cite{color:var(--color-grey-2)}.app .select-box .list .item .percent{padding:0 4px;border-radius:2px;font-size:12px;font-weight:bold;color:var(--color-grey-1)}.app .select-box .list .item .percent.red{color:var(--color-red-1)}.app .select-box .list .item .percent.green{color:var(--color-green-3)}.app .select-box .list .item:nth-child(odd){background:rgba(255,255,255,0.35)}.app .select-box .list .item:hover{color:var(--color-blue-1);background:rgba(255,255,255,0.35)}.app .select-box .list .item.active{color:var(--color-blue-3);background:rgba(255,255,255,0.7)}.app .detail{position:relative;flex:1;height:100%;border-left:1px solid var(--color-plain-2);background:#fff}.app .detail .title{display:flex;justify-content:space-between;align-items:center;width:100%;height:35px;padding:0 16px;font-size:16px;font-weight:bold}.app .detail .title span{display:inline-flex}.app .detail .title wc-button{margin:0 6px}.app .detail .card{width:96%;padding:12px 12px 16px;margin:12px 2% 24px;border:0;background:#fff;box-shadow:0 0 8px rgba(0,0,0,0.075)}.app .detail .card legend{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#64b5f6}.app .detail.blur::after{position:absolute;left:0;top:0;z-index:999;width:100%;height:100%;content:'';background:rgba(255,255,255,0.75);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.app .preferences{width:640px;height:360px;border-radius:10px}.app .preferences .titlebar{width:100%;height:72px;border-bottom:1px solid var(--color-plain-3);background:var(--color-plain-1)}.app .preferences .titlebar .title{width:100%;height:24px;line-height:24px;text-align:center}.app .preferences .titlebar nav{display:flex;width:100%;height:40px;padding:0 16px;--size: 18px}.app .preferences .titlebar nav span{display:flex;flex-direction:column;align-items:center;justify-content:center;width:52px;height:40px;margin:0 6px;border-radius:6px;font-size:12px}.app .preferences .titlebar nav span.active{background:var(--color-plain-2);color:var(--color-blue-1)}.app .preferences .titlebar nav span:hover{background:var(--color-plain-2)}.app .preferences .tab-panel{padding:64px}.app .preferences .tab-panel p{margin-bottom:16px}.app .preferences .tab-panel .field{display:flex;align-items:center;height:64px}.app .preferences .tab-panel .field .label{width:200px;color:var(--color-grey-1)}

View File

@ -77,7 +77,6 @@ body {
wc-input { wc-input {
flex: 1; flex: 1;
margin-right: 12px;
} }
} }
@ -107,24 +106,28 @@ body {
padding: 0 4px; padding: 0 4px;
border-radius: 2px; border-radius: 2px;
font-size: 12px; font-size: 12px;
color: #fff; font-weight: bold;
background: var(--color-grey-1); color: var(--color-grey-1);
&.red { &.red {
background: var(--color-red-1); color: var(--color-red-1);
} }
&.green { &.green {
background: var(--color-green-3); color: var(--color-green-3);
} }
} }
&:nth-child(odd) {
background: rgba(255, 255, 255, 0.35);
}
&:hover { &:hover {
color: var(--color-orange-1); color: var(--color-blue-1);
background: rgba(255, 255, 255, 0.35); background: rgba(255, 255, 255, 0.35);
} }
&.active { &.active {
color: var(--color-orange-3); color: var(--color-blue-3);
background: rgba(255, 255, 255, 0.7); background: rgba(255, 255, 255, 0.7);
} }
} }
@ -132,18 +135,28 @@ body {
} }
.detail { .detail {
position: relative;
flex: 1; flex: 1;
height: 100%; height: 100%;
border-left: 1px solid var(--color-plain-2); border-left: 1px solid var(--color-plain-2);
background: #fff; background: #fff;
.title { .title {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%; width: 100%;
height: 35px; height: 35px;
padding: 0 16px; padding: 0 16px;
line-height: 35px;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
span {
display: inline-flex;
}
wc-button {
margin: 0 6px;
}
} }
.card { .card {
@ -160,5 +173,86 @@ body {
color: #64b5f6; color: #64b5f6;
} }
} }
&.blur {
&::after {
position: absolute;
left: 0;
top: 0;
z-index: 999;
width: 100%;
height: 100%;
content: '';
background: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(10px);
}
}
}
.preferences {
width: 640px;
height: 360px;
border-radius: 10px;
.titlebar {
width: 100%;
height: 72px;
border-bottom: 1px solid var(--color-plain-3);
background: var(--color-plain-1);
.title {
width: 100%;
height: 24px;
line-height: 24px;
text-align: center;
}
nav {
display: flex;
width: 100%;
height: 40px;
padding: 0 16px;
span {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 52px;
height: 40px;
margin: 0 6px;
border-radius: 6px;
font-size: 12px;
&.active {
background: var(--color-plain-2);
color: var(--color-blue-1);
}
&:hover {
background: var(--color-plain-2);
}
}
--size: 18px;
}
}
.tab-panel {
padding: 64px;
p {
margin-bottom: 16px;
}
.field {
display: flex;
align-items: center;
height: 64px;
.label {
width: 200px;
color: var(--color-grey-1);
}
}
}
} }
} }

View File

@ -19,17 +19,16 @@
<cite :text="it.code"></cite> <cite :text="it.code"></cite>
</div> </div>
<canvas class="last-days" width="128" height="60" :draw="it.last"></canvas>
<div class="today"> <div class="today">
<span class="money" :text="it.curr"></span> <span class="money" :text="'¥' + it.cm"></span>
<span <span
class="percent" class="percent"
:class="{red: it.percent > 0, green: it.percent < 0}" :class="{red: it.cp > 0, green: it.cp < 0}"
:text="it.percent + '%'"> :text="it.cp + '%'">
</span> </span>
</div> </div>
</section> </section>
<section :if="list.length === 0" class="item">啥基都没有...</section>
</wc-scroll> </wc-scroll>
</div> </div>

View File

@ -15,26 +15,33 @@
<aside class="sidebar app-drag"> <aside class="sidebar app-drag">
<wc-icon class="app-nodrag item stat active" is="chart"></wc-icon> <wc-icon class="app-nodrag item stat active" is="chart"></wc-icon>
<wc-icon class="app-nodrag item opt" is="menu-dot"></wc-icon> <wc-icon class="app-nodrag item opt" is="menu-dot" @click="showPreferencesPanel"></wc-icon>
</aside> </aside>
<div class="select-box"> <div class="select-box">
<section class="form"> <section class="form">
<wc-input maxlength="6" placeholder="输入编号搞新基" round size="mini"></wc-input> <wc-input
<wc-button circle size="mini" color="orange" icon="plus"></wc-button> maxlength="6"
placeholder="输入编号搞新基"
round
@submit="addGay"
:duplex="input"
size="mini">
</wc-input>
</section> </section>
<wc-scroll class="list"> <wc-scroll class="list">
<item <item
class="item" class="item"
:class="{active: curr.code === it.code}" :class="{active: curr.code === it.code}"
@click="viewGay(it)"
:for="it in list"> :for="it in list">
<strong class="text-ell" :text="it.name"></strong> <strong class="text-ell" :text="it.name"></strong>
<section> <section>
<cite :text="it.code"></cite> <cite :text="it.code"></cite>
<span <span
class="percent" class="percent"
:class="{red: it.percent > 0, green: it.percent < 0}" :class="{red: it.cp > 0, green: it.cp < 0}"
:text="it.percent + '%'"> :text="it.cp + '%'">
</span> </span>
</section> </section>
</item> </item>
@ -42,23 +49,70 @@
</div> </div>
<div class="detail"> <div class="detail" :class="{blur: !curr.code}">
<section class="title app-drag"> <section class="title app-drag">
[{{curr.code}}] {{curr.name}} <span>[{{curr.code}}] {{curr.name}}</span>
<span>
<wc-button
circle
size="mini"
@click="removeGay"
icon="trash">
</wc-button>
<wc-button
circle
color="red"
size="mini"
@click="updateGay"
icon="eye">
</wc-button>
</span>
</section> </section>
<fieldset class="card"> <fieldset class="card">
<legend>60天红绿榜</legend> <legend>实时数据</legend>
<wc-rank :attr-list="curr.last60"></wc-rank> <wc-rank :attr-stat="curr.stat"></wc-rank>
</fieldset> </fieldset>
<fieldset class="card"> <fieldset class="card">
<legend>单位净值走势</legend> <legend>单位净值走势</legend>
<wc-rank :attr-list="curr.last60"></wc-rank> <wc-line :attr-list="curr.line"></wc-line>
</fieldset> </fieldset>
</div> </div>
<wc-layer ref="pre" mask mask-close radius="10px" >
<div class="preferences">
<div class="titlebar">
<div class="title">hello</div>
<nav>
<span :class="{active: preferences.tab === 1}" @click="switchTab(1)">
<wc-icon is="setting"></wc-icon>
常规
</span>
<span :class="{active: preferences.tab === 2}" @click="switchTab(2)">
<wc-icon is="info"></wc-icon>
关于
</span>
</nav>
</div>
<div class="tab-panel" :visible="preferences.tab === 1">
<section class="field">
<span class="label">神奇的2点半提醒</span>
<wc-switch :duplex="preferences.notify"></wc-switch>
</section>
</div>
<div class="tab-panel" :visible="preferences.tab === 2">
<p>“搞基爱啪啪” 是一款开源的, 非专业的搞基软件, 上面的数据全来自网络, 不对准确性作任何保证. </p>
<p>搞基有风险, 入行需谨慎. 你亏了别找我, 赚了可以给我发红包.</p>
</div>
</div>
</wc-layer>
</div> </div>

View File

@ -8,21 +8,18 @@
import '/lib/anot.js' import '/lib/anot.js'
import '/lib/form/button.js' import '/lib/form/button.js'
import '/lib/form/switch.js'
import '/lib/scroll/index.js' import '/lib/scroll/index.js'
import '/lib/chart/rank.js' import '/lib/chart/rank.js'
import '/lib/canvas-draw.js' import '/lib/chart/line.js'
import layer from '/lib/layer/index.js' import layer from '/lib/layer/index.js'
import Utils from '/lib/utils.js' import Utils from '/lib/utils.js'
import app from '/lib/socket.js'
const log = console.log const log = console.log
const { ipcRenderer } = require('electron')
// http://fund.eastmoney.com/pingzhongdata/161725.js?v=20201209153939
const $doc = Anot(document)
function getJsonp(str) { function getJsonp(str) {
if (~str.indexOf('jsonpgz')) { if (~str.indexOf('jsonpgz')) {
return new Function(`function jsonpgz(d){return d}; return ${str}`)() return new Function(`function jsonpgz(d){return d}; return ${str}`)()
@ -30,216 +27,255 @@ function getJsonp(str) {
return false return false
} }
function getTableData(str) { function getLineStat(str) {
var match = str.match(/<tbody[^]*?>.*?<\/tbody>/) return new Function(`${str}; return {line: Data_netWorthTrend.map(it => ({
var table = document.createElement('table') x: ~~(it.x/1000),
var list = [] y: +(it.y * 10000).toFixed(0),
var max = 0 p: it.equityReturn
var min = 99 })), e1: +syl_1y, e3: +syl_3y, e6: +syl_6y, e12: +syl_1n}`)()
table.innerHTML = match[0]
list = Array.from(table.children[0].children)
.map(it => {
let m = +it.children[2].textContent
if (m > max) {
max = m
}
if (m < min) {
min = m
}
return { m }
})
.reverse()
list.forEach(it => {
it.h = +(((it.m - min) * 60) / (max - min)).toFixed(2)
})
return list
} }
Anot({ Anot({
$id: 'app', $id: 'app',
state: { state: {
input: '',
curr: { curr: {
code: '161725', code: '',
name: '招商中证白酒指数分级', name: '',
last60: [ stat: '',
1.56, line: ''
2.81,
0.82,
-0.18,
-1.67,
-2.34,
1.36,
-1.52,
-0.92,
-0.49,
-1.74,
0.03,
1.15,
-0.21,
0.46,
1.45,
5.54,
1.7,
-0.33,
-0.11,
-1.11,
-0.76,
3.16,
0.32,
1.85,
-2.54,
-1.08,
0.91,
3.27,
2.84,
-2.83,
1.67,
1.1,
0.48,
1.89,
-0.66,
1.91,
2.15,
0.12,
1.75,
-3.43,
3.88,
-1.37,
-1.62,
0.38,
1.49,
1.03,
0.6,
-3.51,
0.5,
0.6,
-3.01,
0.87,
-0.03,
0.99,
3.4,
0.32,
1.53,
-0.46,
0.84
].join(',')
}, },
list: [], list: [],
$dict: {} $dict: {},
preferences: {
tab: 1,
notify: Anot.ls('notify') === '1'
}
}, },
watch: { watch: {
'chapter.content'() { 'preferences.notify'(v) {
this.calcuteWords = this.chapter.content.length Anot.ls('notify', v ^ 0)
}, if (v) {
currCate() { app.dispatch('notify')
this.renderChapterList() }
} }
}, },
mounted() { mounted() {
// WIN.on('blur', _ => { var gays = Anot.ls('gays') || '{}'
// WIN.hide() var list = []
// }) var old = this.syncOldStat()
var watch_list = Anot.ls('watch_list') || '[]' if (old === false) {
gays = JSON.parse(gays)
watch_list = JSON.parse(watch_list) for (let code in gays) {
let { name, cm, cp, t } = gays[code]
list.push({ code, name, cm, cp, t })
this.$dict[code] = 1
}
list.sort((a, b) => b.cp - a.cp)
this.list = watch_list this.list = list
}
for (let it of this.list) { if (this.preferences.notify) {
this.$dict[it.code] = it app.dispatch('notify')
} }
}, },
methods: { methods: {
close() { syncOldStat() {
// WIN.close() var old = Anot.ls('watch_list')
var list = []
var dict = {}
if (old) {
old = JSON.parse(old)
for (let it of old) {
dict[it.code] = {
name: it.name,
cm: +it.curr,
cp: it.percent,
t: Date.now()
}
list.push({ code: it.code, ...dict[it.code] })
}
list.sort((a, b) => b.cp - a.cp)
this.list = list
Anot.ls('gays', dict)
Anot.ls('watch_list', null)
return true
}
return false
}, },
getTodayStat(id) {
var res = ipcRenderer.sendSync( showPreferencesPanel() {
'net', this.$refs.pre.show()
},
switchTab(n) {
this.preferences.tab = n
},
getGayStat(id) {
var res = app.dispatch(
'fetch',
`https://fundgz.1234567.com.cn/js/${id}.js` `https://fundgz.1234567.com.cn/js/${id}.js`
) )
return getJsonp(res) return getJsonp(res)
}, },
getLastMonth(id) {
var res = ipcRenderer.sendSync(
'net',
`https://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&per=42&code=${id}`
)
return getTableData(res)
},
addGay() { addGay() {
layer var code = this.input
.prompt('请输入鸡精代号', (val, done) => { var gay
if (val.trim()) {
done() if (this.$dict[code]) {
} layer.toast('这个鸡精在列表呢~~~', 'warn')
}) this.input = ''
.then(id => {
if (this.$dict[id]) {
return return
} }
Anot.nextTick(_ => {
var info = this.getTodayStat(id) if (code.length < 6) {
var last return
if (info) {
last = this.getLastMonth(id)
var tmp = {
code: info.fundcode,
name: info.name,
yesterday: info.dwjz,
curr: info.gsz,
percent: +info.gszzl,
last
} }
this.list.unshift(tmp)
this.$dict[tmp.code] = this.list[0] if (/[^\d]/.test(code)) {
Anot.ls('watch_list', this.list.$model) layer.toast('只能通过鸡精编号添加', 'error')
this.input = ''
return
}
gay = this.getGayStat(code)
if (gay) {
let tmp = {
code: gay.fundcode,
name: gay.name,
cm: +gay.gsz,
cp: +gay.gszzl,
t: Date.now()
}
this.input = ''
this.list.push(tmp)
this.$dict[tmp.code] = 1
this.list.sort((a, b) => b.cp - a.cp)
this.saveCache()
} else { } else {
layer.toast('鸡精不存在', 'error') layer.toast('鸡精不存在', 'error')
} }
})
})
.catch(Anot.noop)
}, },
updateGay(item) { updateGay() {
var info = this.getTodayStat(item.code) var { code, stat } = this.curr
if (info.dwjz !== item.yesterday) { var info = this.getGayStat(code)
item.yesterday = info.dwjz
item.last = this.getLastMonth(item.code)
}
item.curr = info.gsz
item.percent = +info.gszzl
},
removeGay(item) {
layer
.confirm(`是否移除[${item.name.slice(0, 5)}...]?`)
.then(_ => {
item.$ups.it.$remove()
delete this.$dict[item.code]
Anot.ls('watch_list', this.list.$model)
})
.catch(Anot.noop)
},
updateGays() {
for (let it of this.list) { for (let it of this.list) {
this.updateGay(it) if (it.code === code) {
let d
it.cm = +info.gsz
it.cp = +info.gszzl
this.list.sort((a, b) => b.cp - a.cp)
d = new Date(info.gztime.slice(0, 10) + ' 00:00:00')
d = ~~(d.getTime() / 1000) - 24 * 3600
// 如果走势最后的日期比当前最新的小, 则全量更新
if (it.t < d) {
Anot.ls(code, null)
this.viewGay(it)
console.log('update all stat...')
return
} }
this.list.sort((a, b) => { stat = JSON.parse(stat)
return b.percent - a.percent stat.cm = it.cm
}) stat.cp = it.cp
this.curr.stat = JSON.stringify(stat)
Anot.ls('watch_list', this.list.$model) this.saveCache()
layer.toast('数据更新成功', 'success')
Anot.ss('last_update', Date.now())
return
}
}
},
removeGay() {
var { code, name } = this.curr
layer
.confirm(`是否移除「${name}」?`)
.then(_ => {
for (let it of this.list) {
if (it.code === code) {
this.list.remove(it)
delete this.$dict[code]
Anot.ls(code, null)
this.saveCache()
break
}
}
this.viewGay(this.list[0])
})
.catch(Anot.noop)
},
saveCache() {
var dict = {}
for (let it of this.list) {
var { code, name, cm, cp, t } = it
dict[code] = { name, cm, cp, t }
}
Anot.ls('gays', dict)
},
viewGay(item) {
var gay = Anot.ls(item.code)
var rank, line
var { cm, cp, t } = item
this.curr.code = item.code
this.curr.name = item.name
if (gay) {
gay = JSON.parse(gay)
var last = gay.line[gay.line.length - 1].x
if (last < t) {
gay = null
}
}
if (!gay) {
gay = app.dispatch(
'fetch',
`http://fund.eastmoney.com/pingzhongdata/${
item.code
}.js?v=${Date.now()}`
)
gay = getLineStat(gay)
item.t = gay.line[gay.line.length - 1].x
this.saveCache()
Anot.ls(item.code, JSON.stringify(gay))
}
rank = gay.line.slice(-60).map(_ => _.p)
line = JSON.stringify(gay.line)
this.curr.stat = JSON.stringify({
rank,
e1: gay.e1,
e3: gay.e3,
e6: gay.e6,
e12: gay.e12,
cm,
cp
})
this.curr.line = line
} }
} }
}) })

View File

@ -5,18 +5,11 @@
*/ */
import '/lib/anot.js' import '/lib/anot.js'
import '/lib/form/button.js'
import '/lib/scroll/index.js' import '/lib/scroll/index.js'
import '/lib/canvas-draw.js'
import layer from '/lib/layer/index.js' import layer from '/lib/layer/index.js'
import Utils from '/lib/utils.js'
import app from '/lib/socket.js' import app from '/lib/socket.js'
const log = console.log
const $doc = Anot(document)
function getJsonp(str) { function getJsonp(str) {
if (~str.indexOf('jsonpgz')) { if (~str.indexOf('jsonpgz')) {
return new Function(`function jsonpgz(d){return d}; return ${str}`)() return new Function(`function jsonpgz(d){return d}; return ${str}`)()
@ -24,156 +17,71 @@ function getJsonp(str) {
return false return false
} }
function getTableData(str) {
var match = str.match(/<tbody[^]*?>.*?<\/tbody>/)
var table = document.createElement('table')
var list = []
var max = 0
var min = 99
table.innerHTML = match[0]
list = Array.from(table.children[0].children)
.map(it => {
let m = +it.children[2].textContent
if (m > max) {
max = m
}
if (m < min) {
min = m
}
return { m }
})
.reverse()
list.forEach(it => {
it.h = +(((it.m - min) * 60) / (max - min)).toFixed(2)
})
return list
}
Anot({ Anot({
$id: 'app', $id: 'app',
state: { state: {
list: [], list: []
$dict: {}
},
watch: {
'chapter.content'() {
this.calcuteWords = this.chapter.content.length
},
currCate() {
this.renderChapterList()
}
}, },
mounted() { mounted() {
var watch_list = Anot.ls('watch_list') || '[]' this.reloadGays()
watch_list = JSON.parse(watch_list)
this.list = watch_list
for (let it of this.list) {
this.$dict[it.code] = it
}
app.on('float-visible', data => { app.on('float-visible', data => {
var time = +Anot.ss('last_update') || 0 var time = +Anot.ss('last_update') || 0
var now = Date.now() var now = Date.now()
// 如果离上次更新超过15分钟, 则自动更新 // 如果离上次更新超过15分钟, 则自动更新
if (now - time > 15 * 60 * 1000) { if (now - time > 15 * 60 * 1000) {
this.updateGays() this.updateGays()
Anot.ss('last_update', now) Anot.ss('last_update', now)
} else {
this.reloadGays()
} }
}) })
}, },
methods: { methods: {
close() { reloadGays() {
// WIN.close() var gays = Anot.ls('gays') || '{}'
var list = []
gays = JSON.parse(gays)
for (let code in gays) {
let { name, cm, cp, t } = gays[code]
list.push({ code, name, cm, cp, t })
}
list.sort((a, b) => b.cp - a.cp)
this.list = list
}, },
getTodayStat(id) {
getGayStat(id) {
var res = app.dispatch( var res = app.dispatch(
'fetch', 'fetch',
`https://fundgz.1234567.com.cn/js/${id}.js` `https://fundgz.1234567.com.cn/js/${id}.js`
) )
return getJsonp(res) return getJsonp(res)
}, },
getLastMonth(id) {
var res = app.dispatch(
'fetch',
`https://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&per=42&code=${id}`
)
return getTableData(res)
},
addGay() {
layer
.prompt('请输入鸡精代号', (val, done) => {
if (val.trim()) {
done()
}
})
.then(id => {
if (this.$dict[id]) {
return
}
Anot.nextTick(_ => {
var info = this.getTodayStat(id)
var last
if (info) {
last = this.getLastMonth(id)
var tmp = {
code: info.fundcode,
name: info.name,
yesterday: info.dwjz,
curr: info.gsz,
percent: +info.gszzl,
last
}
this.list.unshift(tmp)
this.$dict[tmp.code] = this.list[0]
Anot.ls('watch_list', this.list.$model)
} else {
layer.toast('鸡精不存在', 'error')
}
})
})
.catch(Anot.noop)
},
updateGay(item) { updateGay(item) {
var info = this.getTodayStat(item.code) var info = this.getGayStat(item.code)
if (info.dwjz !== item.yesterday) { item.cm = +info.gsz
item.yesterday = info.dwjz item.cp = +info.gszzl
item.last = this.getLastMonth(item.code)
}
item.curr = info.gsz
item.percent = +info.gszzl
},
removeGay(item) {
layer
.confirm(`是否移除[${item.name.slice(0, 5)}...]?`)
.then(_ => {
item.$ups.it.$remove()
delete this.$dict[item.code]
Anot.ls('watch_list', this.list.$model)
})
.catch(Anot.noop)
}, },
updateGays() { updateGays() {
for (let it of this.list) { for (let it of this.list) {
this.updateGay(it) this.updateGay(it)
} }
this.list.sort((a, b) => b.cp - a.cp)
this.list.sort((a, b) => { this.saveCache()
return b.percent - a.percent },
}) saveCache() {
var dict = {}
Anot.ls('watch_list', this.list.$model) for (let it of this.list) {
var { code, name, cm, cp, t } = it
dict[code] = { name, cm, cp, t }
}
Anot.ls('gays', dict)
} }
} }
}) })

View File

@ -1,44 +0,0 @@
/**
* canvas渲染
* @author yutent<yutent.io@gmail.com>
* @date 2020/07/27 11:34:59
*/
'use strict'
const log = console.log
const RED = '#ff5061'
const GREEN = '#4caf50'
Anot.directive('draw', {
priority: 1500,
init(binding) {
var elem = binding.element
var ctx = elem.getContext('2d')
binding.$ctx = ctx
},
update: function(val) {
var list = val.$model
var start = list.shift()
var end = list[list.length - 1]
var x = 0
this.$ctx.clearRect(0, 0, 128, 60)
this.$ctx.fillStyle = '#a7a8ab'
this.$ctx.fillRect(0, 29, 128, 1)
this.$ctx.beginPath()
this.$ctx.strokeStyle = start.m < end.m ? RED : GREEN
this.$ctx.lineWidth = 2
this.$ctx.moveTo(0, 60 - start.h)
while (list.length) {
start = list.shift()
x += 3
this.$ctx.lineTo(x, 60 - start.h)
}
this.$ctx.stroke()
}
})

253
src/lib/chart/line.js Normal file
View File

@ -0,0 +1,253 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2020-12-08 11:30:52
* @version v1.0.0
*
*/
import $ from '../utils.js'
import '../form/button.js'
const DARK = '#62778d'
const BLUE = '#64b5f6'
const PLAIN = '#f2f5fc'
export default class Line extends HTMLElement {
static get observedAttributes() {
return ['list']
}
props = {
list: []
}
state = {
key: 1,
list: []
}
constructor() {
super()
Object.defineProperty(this, 'root', {
value: this.attachShadow({ mode: 'open' }),
writable: true,
enumerable: false,
configurable: true
})
this.root.innerHTML = `<style>* {
box-sizing: border-box;
margin: 0;
padding: 0; }
::before,
::after {
box-sizing: border-box; }
:host {
display: flex;
width: 680px; }
.container {
position: relative;
padding: 24px 0 0; }
canvas {
width: 680px;
height: 230px; }
section {
position: absolute;
right: 0;
top: 0; }
</style>
<div class="container">
<canvas></canvas>
<section>
<wc-button color="blue" data-key="1" size="mini">1</wc-button>
<wc-button data-key="3" size="mini">3</wc-button>
<wc-button data-key="6" size="mini">半年</wc-button>
<wc-button data-key="12" size="mini">1</wc-button>
<wc-button data-key="36" size="mini">3</wc-button>
<wc-button data-key="999" size="mini">所有</wc-button>
</section>
</div>
`
var elem = this.root.children[1]
this.__SCENE__ = elem.firstElementChild
this.__FILTER__ = elem.lastElementChild
this.__CTX__ = this.__SCENE__.getContext('2d')
this.__SCENE__.width = 680
this.__SCENE__.height = 230
}
_getTime(n) {
var now = new Date()
var time = { getTime: _ => 0 }
var Y = now.getFullYear()
var m = now.getMonth()
var d = now.getDate()
switch (n) {
case 1:
time = new Date(Y, m - 1, d, 0, 0, 0)
break
case 3:
time = new Date(Y, m - 3, d, 0, 0, 0)
break
case 6:
time = new Date(Y, m - 6, d, 0, 0, 0)
break
case 12:
time = new Date(Y - 1, m, d, 0, 0, 0)
break
case 36:
time = new Date(Y - 3, m, d, 0, 0, 0)
break
}
return time.getTime()
}
_filter(n) {
if (n < 999) {
var time = this._getTime(n)
this.state.list = this.props.list.filter(it => it.x >= time)
} else {
this.state.list = this.props.list.concat()
}
}
draw() {
var { list, key } = this.state
var ctx = this.__CTX__
var x = 36
var max = 0
var min = Number.MAX_SAFE_INTEGER
var step = 0 // 纵坐标间隔
var dis = +(640 / list.length).toFixed(2) || 1 // 横坐标间隔
var point
var p1, p2, p3, p4
var format = key > 12 ? 'Y/m' : 'm/d'
for (let it of list) {
if (max < it.y) {
max = it.y
}
if (min > it.y) {
min = it.y
}
}
min = ~~(min / 100)
max = Math.ceil(max / 100)
step = ~~((max - min) / 3)
p1 = Math.floor(list.length / 4)
p2 = Math.floor(list.length / 2)
p3 = Math.floor((list.length * 3) / 4)
p4 = list.length - 1
ctx.clearRect(0, 0, 680, 230)
// 纵坐标数值
ctx.font = '12px Arial'
ctx.textAlign = 'right'
ctx.fillStyle = DARK
ctx.fillText(min / 100, 32, 205)
ctx.fillText((min + step) / 100, 32, 155)
ctx.fillText((min + step + step) / 100, 32, 105)
ctx.fillText((min + step + step + step) / 100, 32, 55)
ctx.font = '10px Arial'
ctx.textAlign = 'left'
ctx.fillText(new Date(list[0].x).format(format), x - 12, 225)
ctx.fillText(new Date(list[p1].x).format(format), x + dis * p1 - 12, 225)
ctx.fillText(new Date(list[p2].x).format(format), x + dis * p2 - 12, 225)
ctx.fillText(new Date(list[p3].x).format(format), x + dis * p3 - 12, 225)
ctx.fillText(
new Date(list[p4].x).format(format),
x + dis * p4 - 12 - (key > 12 ? 24 : 4),
225
)
// x轴参考线
ctx.fillStyle = PLAIN
ctx.fillRect(x, 50, 648, 1)
ctx.fillRect(x, 100, 648, 1)
ctx.fillRect(x, 150, 648, 1)
ctx.fillRect(x, 200, 648, 1)
// y轴参考 线
ctx.fillRect(x, 0, 1, 210)
ctx.fillRect(x + dis * p1, 0, 1, 210)
ctx.fillRect(x + dis * p2, 0, 1, 210)
ctx.fillRect(x + dis * p3, 0, 1, 210)
ctx.fillRect(x + dis * p4, 0, 1, 210)
point = list.shift()
// 曲线
ctx.beginPath()
ctx.strokeStyle = BLUE
ctx.lineWidth = 1
ctx.moveTo(x, 200 - (((point.y / 100 - min) / step) * 50).toFixed(0))
while (list.length) {
let y
point = list.shift()
y = 200 - (((point.y / 100 - min) / step) * 50).toFixed(0)
x += dis
ctx.lineTo(x, y)
}
ctx.stroke()
}
connectedCallback() {
$.bind(this.__FILTER__, 'click', ev => {
var el = ev.target
if (this.props.list.length < 1) {
return
}
if (el.tagName === 'WC-BUTTON') {
var k = +el.dataset.key
$.each(this.__FILTER__.children, function(it) {
it.removeAttribute('color')
})
el.setAttribute('color', 'blue')
this.state.key = k
this._filter(k)
this.draw()
}
})
}
attributeChangedCallback(name, old, val) {
if (val === null || old === val) {
return
}
switch (name) {
case 'list':
try {
var list = JSON.parse(val)
list.forEach(it => (it.x = it.x * 1000))
this.props.list = list
this._filter(this.state.key)
this.removeAttribute('list')
this.draw()
} catch (e) {}
break
}
}
}
if (!customElements.get('wc-line')) {
customElements.define('wc-line', Line)
}

View File

@ -8,16 +8,18 @@
const RED = '#ff5061' const RED = '#ff5061'
const GREEN = '#4caf50' const GREEN = '#4caf50'
const BLUE = '#64b5f6'
const GREY = '#bdbdbd' const GREY = '#bdbdbd'
const PLAIN = '#f2f5fc' const PLAIN = '#f2f5fc'
const DARK = '#62778d'
export default class Rank extends HTMLElement { export default class Rank extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ['list'] return ['stat']
} }
props = { props = {
list: '' stat: {}
} }
constructor() { constructor() {
@ -56,12 +58,12 @@ canvas {
} }
draw() { draw() {
var { list } = this.props var { rank, e1, e3, e6, e12, cm, cp } = this.props.stat
var ctx = this.__CTX__ var ctx = this.__CTX__
var x = 32 var x = 32
while (list.length < 60) { while (rank.length < 60) {
list.unshift(0) rank.unshift(0)
} }
ctx.clearRect(0, 0, 680, 101) ctx.clearRect(0, 0, 680, 101)
@ -75,15 +77,34 @@ canvas {
ctx.fillText('-5%', 28, 80) ctx.fillText('-5%', 28, 80)
ctx.fillText('-10%', 28, 100) ctx.fillText('-10%', 28, 100)
ctx.font = '10px menlo,Hiragino Sans GB'
ctx.textAlign = 'left'
ctx.fillStyle = DARK
ctx.fillText('60天红绿榜', 160, 10)
ctx.font = '12px menlo,Hiragino Sans GB'
ctx.fillText(`最近1个月收益: ${e1}%`, 360, 25)
ctx.fillText(`最近3个月收益: ${e3}%`, 360, 45)
ctx.fillText(`最近半年收益: ${e6}%`, 528, 25)
ctx.fillText(`最近一年收益: ${e12}%`, 528, 45)
ctx.fillStyle = cp > 0 ? RED : cp === 0 ? GREY : GREEN
ctx.fillRect(360, 65, 140, 20)
ctx.fillRect(526, 65, 140, 20)
ctx.fillStyle = '#fff'
ctx.font = 'bold 14px menlo,Hiragino Sans GB'
ctx.fillText(`实时净值: ¥${cm}`, 364, 80)
ctx.fillText(`实时涨跌: ${cp}%`, 532, 80)
ctx.fillStyle = PLAIN ctx.fillStyle = PLAIN
ctx.fillRect(28, 25, 652, 1) ctx.fillRect(28, 25, 320, 1)
ctx.fillRect(28, 75, 652, 1) ctx.fillRect(28, 75, 320, 1)
ctx.fillStyle = GREY ctx.fillStyle = GREY
ctx.fillRect(28, 0, 1, 140) ctx.fillRect(28, 0, 1, 140)
ctx.fillRect(0, 50, 680, 1) ctx.fillRect(0, 50, 348, 1)
while (list.length) { while (rank.length) {
var n = list.shift() var n = rank.shift()
var y = Math.ceil(50 - (n / 10) * 50) var y = Math.ceil(50 - (n / 10) * 50)
ctx.fillStyle = n > 0 ? RED : GREEN ctx.fillStyle = n > 0 ? RED : GREEN
@ -94,7 +115,7 @@ canvas {
ctx.fillRect(x, y, 3, 50 - y) ctx.fillRect(x, y, 3, 50 - y)
} }
x += 10 x += 5
} }
} }
@ -103,11 +124,13 @@ canvas {
return return
} }
switch (name) { switch (name) {
case 'list': case 'stat':
var list = val.split(',') try {
this.props.list = list.map(n => +n) var stat = JSON.parse(val)
this.removeAttribute('list') this.props.stat = stat
this.removeAttribute('stat')
this.draw() this.draw()
} catch (e) {}
break break
} }
} }

View File

@ -4,7 +4,14 @@
* @date 2019/09/16 20:51:19 * @date 2019/09/16 20:51:19
*/ */
const { app, BrowserWindow, protocol, ipcMain, net } = require('electron') const {
app,
BrowserWindow,
protocol,
ipcMain,
net,
Notification
} = require('electron')
const path = require('path') const path = require('path')
const fs = require('iofs') const fs = require('iofs')
@ -24,6 +31,8 @@ const MIME_TYPES = {
const ROOT = __dirname const ROOT = __dirname
var timer
function fetch(url) { function fetch(url) {
return new Promise((y, n) => { return new Promise((y, n) => {
var conn = net.request(url) var conn = net.request(url)
@ -47,6 +56,22 @@ function fetch(url) {
}) })
} }
function ring() {
var n = 5
var t = setInterval(() => {
var notify = new Notification({
title: '搞基⏰',
subtitle: '神奇的2点半到啦',
body: '神奇的2点半到啦, 该加仓的加仓, 该卖的卖啦'
})
notify.show()
n--
if (n === 0) {
clearInterval(t)
}
}, 1000)
}
/* ----------------------------------------------------- */ /* ----------------------------------------------------- */
app.commandLine.appendSwitch('--lang', 'zh-CN') app.commandLine.appendSwitch('--lang', 'zh-CN')
app.commandLine.appendSwitch('--autoplay-policy', 'no-user-gesture-required') app.commandLine.appendSwitch('--autoplay-policy', 'no-user-gesture-required')
@ -85,5 +110,18 @@ ipcMain.on('app', (ev, conn) => {
fetch(conn.data).then(r => { fetch(conn.data).then(r => {
ev.returnValue = r ev.returnValue = r
}) })
} else if (conn.type === 'notify') {
clearTimeout(timer)
var t1 = Date.now()
var t2 = new Date()
t2.setHours(14)
t2.setMinutes(0)
t2.setSeconds(0)
if (t2.getTime() - t1 > 0) {
timer = setTimeout(ring, t2.getTime() - t1)
}
ev.returnValue = true
} }
}) })

View File

@ -13,7 +13,7 @@ module.exports = function(win) {
win.__TRAY__.on('click', _ => { win.__TRAY__.on('click', _ => {
var b = win.__TRAY__.getBounds() var b = win.__TRAY__.getBounds()
win.setBounds({ x: b.x - 145, y: b.y + b.height }) win.setBounds({ x: b.x - 120, y: b.y + b.height })
win.show() win.show()
win.focus() win.focus()
win.webContents.send('app', { type: 'float-visible', data: null }) win.webContents.send('app', { type: 'float-visible', data: null })

View File

@ -34,19 +34,13 @@ exports.createMainWindow = function(icon) {
show: false show: false
}) })
// 然后加载应用的 index.html。
win.loadURL('app://local/index.html') win.loadURL('app://local/index.html')
// createAppTray(win)
// ctrlTrayBtn(win)
// createLrcTray(win)
createMenu(win) createMenu(win)
win.on('ready-to-show', _ => { win.on('ready-to-show', _ => {
win.show() win.show()
win.openDevTools() // win.openDevTools()
}) })
win.on('close', ev => { win.on('close', ev => {
@ -60,18 +54,15 @@ exports.createMainWindow = function(icon) {
// 创建悬浮窗口 // 创建悬浮窗口
exports.createFloatWindow = function() { exports.createFloatWindow = function() {
var win = new BrowserWindow({ var win = new BrowserWindow({
width: 320, width: 280,
height: 360, height: 360,
resizable: false, resizable: false,
maximizable: false, maximizable: false,
frame: false, frame: false,
// transparent: true,
// hasShadow: false,
show: false, show: false,
vibrancy: 'hud', vibrancy: 'hud',
visualEffectState: 'active', visualEffectState: 'active',
webPreferences: { webPreferences: {
// webSecurity: false,
experimentalFeatures: true, experimentalFeatures: true,
nodeIntegration: true, nodeIntegration: true,
spellcheck: false spellcheck: false

5918
test.js

File diff suppressed because it is too large Load Diff