父组件代码:
<template>
<view>
<view class="top-bgm">
<view class="f-tip">规则说明</view>
</view>
<view class="content">
<view class="state-box">
<view class="item">
<view class="t">本局收益</view>
<view
:class="{v:true, 'f': chartMinData.councilProfit < 0, 'n': getInfoColor(chartMinData.councilProfit)}">
{{ chartMinData.councilProfit === '' ? '-' : chartMinData.councilProfit + '%' }}
</view>
</view>
<view class="item">
<view class="t">最新价</view>
<view class="v" :class="{
'f': chartMinData.riseFall < 0,
'n': getInfoColor(chartMinData.riseFall),
}">{{ chartMinData.latestPrice }}</view>
</view>
<view class="item">
<view class="t">涨跌</view>
<view class="v" :class="{
'f': chartMinData.riseFall < 0,
'n': getInfoColor(chartMinData.riseFall),
}">{{ chartMinData.riseFall }}</view>
</view>
<view class="item">
<view class="t">涨跌幅</view>
<view :class="{
'f': chartMinData.riseFallRange < 0,
'n': getInfoColor(chartMinData.riseFallRange),
}" class="v">
{{ chartMinData.riseFallRange }}%
</view>
</view>
</view>
<!-- K线图组件 -->
<kChart :kdata="kData" :symbol="SymbolCode" :market-data="marketData" @setma="setma">
<template v-slot:rightTool>
<view class="maOptions">
<text class="label ma5">MA5</text><text class="val">{{ maData.MA5 || "--" }}</text>
<text class="label ma10">MA10</text><text
class="val">{{ maData.MA10 || "--" }}</text>
<text class="label ma20">MA20</text><text
class="val">{{ maData.MA20 || "--" }}</text>
<text class="label ma30">MA30</text><text
class="val">{{ maData.MA30 || "--" }}</text>
</view>
</template>
</kChart>
<view class="tools-box">
<image src="/static/images/game/buy.png" mode="" v-if="!isBuyed" @tap="onSallInOrOut(1)"></image>
<image src="/static/images/game/sale.png" mode="" v-if="isBuyed" @tap="onSallInOrOut(2)"></image>
<image src="/static/images/game/see.png" mode="" @tap="onWaitSee" v-if="!isBuyed"></image>
<image src="/static/images/game/have.png" mode="" @tap="onWaitSee" v-if="isBuyed"></image>
<image src="/static/images/game/end.png" mode="" @tap="onEnd"></image>
</view>
<!-- 结束评分弹窗 -->
<b-zdialog :show="isEndDialog" v-on:hide="isEndDialog = false" :title="''" :mask-cb="() => {}" :isfoot="false">
<view class="go-box">
<view class="go-bg"></view>
<view class="go-content">
<view class="go-t">游戏结束</view>
<view class="go-c">
您在 <text class="name">{{endObj.name}}</text>
{{endObj.kLineBeginDate}}至{{endObj.kLineEndDate}}的K线游戏中收益率为
<text :class="turnClolor(0,endObj.syield)">{{endObj.yield}}</text>
</view>
</view>
<view class="go-foot">
<view class="back-btn" @click="goBack">返回</view>
<view class="again-btn" @click="onReopen">再来一次</view>
</view>
</view>
</b-zdialog>
</view>
</view>
</template>
<script>
import kChart from './component/kChart'
import {
InitKLineDatas,
WaitAndSee,
Trade,
EndGame
} from '@/api/invest.js' // 后端接口
import {
toDecimal
} from './numberFormat'
import dateFormat from '@/utils/dateFormat'
export default {
name: 'kgame',
components: {
kChart
},
data() {
return {
kData: [], // 订阅数据K图数组
maData: {}, // 存放当前MA数据
chartMinData: {
councilProfit: "", // 本局收益
yesdayPrice: 0.0, // 昨收价
latestPrice: 0.0, // 最新价
riseFall: 0.0, // 涨跌
riseFallRange: 0.0, // 涨跌幅
shortName: '', // 当前的k线图的名称
endtime: null
},
SymbolCode: '', // 订阅某个编码数据
marketData: [],
emptyArr: [],
isEnded: false, // 是否结束
taskID: '',
isBuyed: false, // 已买入
isEndDialog: false,
endObj: {},
isloading: false
}
},
onLoad() {
this.onInitkData()
},
beforeDestroy() {
if (!this.isEnded) {
EndGame({
taskID: this.taskID
})
}
},
methods: {
// 初始化K线数据
onInitkData() {
this.isloading = true
this.SymbolCode = ''
this.chartMinData = {
councilProfit: "", // 本局收益
latestPrice: 0.0, // 最新价
riseFall: 0.0, // 涨跌
riseFallRange: 0.0, // 涨跌幅
purchNum: 0, // 买入数量
purchShowNum: 0, // 买入数量展示
purchNumSall: 0, // 已经买入的数量
purchNumMax: 10, // 当前最大的买入数量
sellOutNum: 0, // 卖出数量
sellOutShowNum: 0, // 卖出数量展示
sellOutNumSall: 0, // 能卖出最大数量
sellOutNumMax: 0, // 当前最大的卖出数量
endtime: null
}
this.marketData = []
// 获取
InitKLineDatas({
token: sessionStorage.getItem('token')
}).then(resdata => {
this.isloading = false
this.kData = []
let klineData = resdata?.data?.kIndexQuotes || []
this.taskID = resdata?.data?.taskID
klineData.forEach((ele) => {
const arr = ele['kIndex']
const obj = {}
arr.map((i) => {
obj[i.key.toLowerCase()] = i.value
})
const item = {
...ele['kData'],
...obj
}
this.kData.push(item)
})
// 设置数据
let data = this.kData[this.kData.length - 1]
let dataLen = this.kData.length - 1
this.chartMinData.latestPrice = data['cp']
this.chartMinData.yesdayPrice = data['pp'] // 昨收价
this.chartMinData.riseFall = data['cg']
this.chartMinData.riseFallRange = this.toDecimal(
this.accMul(data['cr'], 100),
2,
true
) // 涨跌幅
})
},
// 获取MA的数据
setma(attr, val) {
this.$set(this.maData, attr, val)
},
// 观望&&持有
onWaitSee() {
if (this.isloading) { // 防止多次点击
return
}
this.isloading = true
WaitAndSee({
taskID: this.taskID
}).then(resdata => {
setTimeout(() => {
this.isloading = false
}, 500)
if (resdata?.data?.isEnd) {
let {
symbol,
kLineBeginDate,
kLineEndDate,
shortName,
market
} = resdata?.data
this.isEnded = true
this.isEndDialog = true
this.endObj = {
yield: this.toDecimal(this.accMul(resdata?.data?.yield || 0, 100), 2) + '%',
syield: resdata?.data?.yield || 0,
kLineBeginDate: this.formatDates(kLineBeginDate, 'yyyy-MM-dd'),
kLineEndDate: this.formatDates(kLineEndDate, 'yyyy-MM-dd'),
name: `${shortName} (${symbol}.${market}) `
}
} else {
let klineData = resdata?.data?.kIndexQuotes || []
klineData.forEach((ele) => {
const arr = ele['kIndex']
const obj = {}
arr.map((i) => {
obj[i.key.toLowerCase()] = i.value
})
const item = {
...ele['kData'],
...obj
}
this.kData.push(item)
})
// 设置数据
this.onSetChartData(resdata.data)
}
})
},
// 设置对应的数据
onSetChartData(obj) {
this.chartMinData.latestPrice = obj.lastPrice
this.chartMinData.councilProfit = this.toDecimal(this.accMul(obj.yield, 100), 2)
this.chartMinData.riseFall = obj.change
this.chartMinData.riseFallRange = this.toDecimal(
this.accMul(obj.changeRate, 100),
2,
true
) // 涨跌幅
},
// 买入
onSallInOrOut(type) {
if (this.isloading) { // 防止多次点击
return
}
this.isloading = true
this.chartMinData.sellOutNum = 0 // 重置卖出数量为0
Trade({
taskID: this.taskID,
side: type
}).then(resdata => {
let klineData = resdata?.data?.kIndexQuotes || []
this.chartMinData.councilProfit = this.toDecimal(this.accMul(resdata?.data?.yield, 100), 2)
if (resdata?.data?.isEnd) {
let {
symbol,
kLineBeginDate,
kLineEndDate,
shortName,
market
} = resdata?.data
this.isEnded = true
this.isEndDialog = true
this.endObj = {
yield: this.toDecimal(this.accMul(resdata?.data?.yield || 0, 100), 2) + '%',
syield: resdata?.data?.yield || 0,
kLineBeginDate: this.formatDates(kLineBeginDate, 'yyyy-MM-dd'),
kLineEndDate: this.formatDates(kLineEndDate, 'yyyy-MM-dd'),
name: `${shortName} (${symbol}.${market}) `
}
} else {
this.isBuyed = !this.isBuyed
setTimeout(() => {
this.isloading = false
}, 500)
klineData.forEach((ele) => {
const arr = ele['kIndex']
const obj = {}
arr.map((i) => {
obj[i.key.toLowerCase()] = i.value
})
const item = {
...ele['kData'],
...obj
}
this.kData.push(item)
// 加入买卖标记
const date = this.formatDates(new Date(item['utc']), 'yyyy-MM-dd')
this.marketData.push({
name: 'Mark',
symbol: 'triangle',
coord: [this.kData.length - 1, item.cp, 1, type == 1 ? '买' : '卖'],
type,
value: item.cp,
symbolSize: [20, 18],
symbolRotate: 180,
itemStyle: {
normal: {
color: type == 1 ? '#B60707' : '#0CB90C'
}
},
symbolOffset: [0, -10]
})
})
// 设置数据
this.onSetChartData(resdata.data)
}
})
},
// 结束
onEnd() {
EndGame({
taskID: this.taskID
}).then(res => {
let {
symbol,
kLineBeginDate,
kLineEndDate,
shortName,
market
} = res?.data
this.isEnded = true
this.endObj = {
yield: this.toDecimal(this.accMul(res?.data?.yield || 0, 100), 2) + '%',
syield: res?.data?.yield || 0,
kLineBeginDate: this.formatDates(kLineBeginDate, 'yyyy-MM-dd'),
kLineEndDate: this.formatDates(kLineEndDate, 'yyyy-MM-dd'),
name: `${shortName} (${symbol}.${market}) `
}
this.isEndDialog = true
})
},
// 再来一局
onReopen() {
this.isEndDialog = false
this.isBuyed = false
this.isEnded = false
this.onInitkData()
},
// 查看我的记录
onJumpRecord() {
this.$router.push({
path: '/TrainDiscRecord'
})
},
// 颜色状态控制
getInfoColor(val) {
let flag = false
// 昨收价相等
if (this.chartMinData.latestPrice == this.chartMinData.yesdayPrice) {
flag = true
}
// 昨收价相等
if (val == 0) {
flag = true
}
return flag
},
// 数值格式化(转化数字为几位小数,四舍五入或者截取多于位数,num:需转化的数字 digit:需保留或截取的位数 isRround:true是否四舍五入,false截取多于小数)
toDecimal(num, digit, isRround) {
if (num === null || num === undefined || isNaN(num) || num === '') {
return "--";
} else if (num == 0) {
return parseFloat(num).toFixed(n);
}
return toDecimal(num, digit, isRround);
},
// 时间格式化
formatDates(date, format) {
return dateFormat(date, format)
},
// 乘法函数,用来得到精确的乘法结果()
accMul(num1,num2){
var m=0,s1=num1.toString(),s2=num2.toString();
try{m+=s1.split(".")[1].length}catch(e){};
try{m+=s2.split(".")[1].length}catch(e){};
return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m);
},
}
}
</script>
<style lang="scss">
.top-bgm {
position: relative;
width: 750rpx;
height: 300rpx;
background: url('../../static/images/game/kline-bg.png') no-repeat;
background-size: 100% 100%;
.f-tip {
position: absolute;
top: 50rpx;
right: 0rpx;
display: flex;
justify-content: center;
align-items: center;
width: 54rpx;
background: rgba(0, 0, 0, .1);
box-shadow: 0px 2rpx 10rpx 0px rgba(161,194,255,0.2);
border-radius: 12rpx 0px 0px 12rpx;
font-size: 22rpx;
font-family: PingFang SC;
font-weight: 500;
color: #FFFFFF;
padding: 14rpx;
}
}
.content {
position: relative;
width: 710rpx;
// height: 1300rpx;
background: #FFFFFF;
box-shadow: 0px 2rpx 30rpx 0px rgba(52, 52, 52, 0.08);
border-radius: 20rpx;
margin: -54rpx auto 0;
z-index: 888;
padding-top: 0rpx;
padding-bottom: 80rpx;
.state-box {
display: flex;
padding: 32rpx 48rpx;
padding-bottom: 0rpx;
justify-content: space-between;
.item {
display: flex;
flex-direction: column;
align-items: center;
.t {
font-size: 36rpx;
margin-bottom: 26rpx;
color: #222;
font-weight: 400;
}
.v {
font-size: 28rpx;
color: $uni-color-rise;
}
// 普通
.n {
color: #222;
}
// 跌
.f {
color: $uni-color-fall;
}
// 涨
.r {
color: $uni-color-rise;
}
}
}
.tools-box {
display: flex;
justify-content: space-around;
position: relative;
z-index: 998;
margin-bottom: 40rpx;
image {
width: 209rpx;
height: 83rpx;
}
}
.go-box {
.go-bg {
position: absolute;
top: -158rpx;
z-index: 1000;
width: 100%;
height: 440rpx;
left: 0;
background-size: 65% auto;
background-repeat: no-repeat;
background-position: 134rpx;
background-image: url('/static/images/game/gameover_bg.png');
}
.go-content {
position: relative;
z-index: 1000;
min-height: 360rpx;
.go-t {
margin-top: 90rpx;
font-size: 64rpx;
color: #D43030;
font-weight: bold;
margin-bottom: 30rpx;
}
.go-c {
color: #222;
word-break: break-all;
text-align: left;
line-height: 56rpx;
font-size: 36rpx;
.name {
color: #D6B47E;
}
.yeild {
color: #D43030;
}
}
}
.go-foot {
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx 0;
.back-btn {
display: flex;
justify-content: center;
align-items: center;
width: 200rpx;
height: 70rpx;
background-repeat: no-repeat;
background-image: url('/static/images/game/back_bg.png');
background-size: 100% 100%;
margin-right: 40rpx;
color: #222;
font-weight: 400;
}
.again-btn {
display: flex;
justify-content: center;
align-items: center;
width: 200rpx;
height: 70rpx;
background-repeat: no-repeat;
background-image: url('/static/images/game/again_bg.png');
background-size: 100% 100%;
color: #222;
font-weight: 400;
}
}
}
.close-btn {
width: 424rpx;
height: 80rpx;
margin-top: 30rpx;
background-color: #EDA66E;
color: #fff;
border-radius: 40rpx;
}
}
.xflex {
display: flex;
}
.maOptions {
font-size: 12px;
display: flex;
justify-content: space-around;
margin-top: 20rpx;
padding: 0 30rpx;
.label {
margin-right: 3px;
font-size: 24rpx;
&.ma5 {
color: #39afe6;
}
&.ma10 {
color: #da6ee8;
}
&.ma20 {
color: #ffab42;
}
&.ma30 {
color: #00940b;
}
}
.val {
margin-right: 15px;
font-size: 24rpx;
&:last-child {
margin-right: 0;
}
}
}
</style>
kChart子组件代码:
<template>
<div class="kChartDetail">
<div class="kChart">
<div v-if="listK.length" style="width: 100%" class="chart">
<slot name="rightTool" />
<ChartK :security-type="securityType" :data-list="listK" :digit="2" :xaxis="xaxis" :curline="curline"
:range="range" :current-tab="type" :isfresh="isfresh" :current-index="currentIndex"
:mark-data="sallOutMark" :predata="predata" class="chartMin" @getma="getma" @click="clicknode"
@getHoverData="getHoverData" />
</div>
<div v-if="!listK.length" class="nodata">
<img :src="nodImg">
<div class="nodtx">{{ "暂无图表数据" }}</div>
</div>
</div>
<div v-if="listK.length" class="indexBtn">
<span :class="{ active: currentIndex === 1 }" @click="choseIndex(1)">
成交量
</span>
<span :class="{ active: currentIndex === 2 }" @click="choseIndex(2)">
MACD
</span>
</div>
<div v-if="currentIndex===1&&listK.length" class="pos-box macd-box">
<p>
成交量(手):
<span>{{ KHoverData[5]==null?'':formatNumUnit(KHoverData[5],2,10000) }}</span>
</p>
</div>
<div v-if="(currentIndex==2)&&listK.length" class="pos-box macd-box">
<p>
<span class="label MACD">MACD:</span>
<span class="val">{{ KHoverData[8] }}</span>
<span class="label DEA">DEA:</span>
<span class="val">{{ KHoverData[9] }}</span>
<span class="label DIF">DIF:</span>
<span class="val">{{ KHoverData[10] }}</span>
</p>
</div>
<div v-if="(currentIndex==3)&&listK.length" class="pos-box macd-box">
<p>
<span class="label D">D:</span>
<span class="val">{{ KHoverData[11] }}</span>
<span class="label J">J:</span>
<span class="val">{{ KHoverData[12] }}</span>
<span class="label K">K:</span>
<span class="val">{{ KHoverData[13] }}</span>
</p>
</div>
</div>
</template>
<script>
import {
formatNumUnit
} from './numberFormat'
import ChartK from './chartk'
import nodImg from '@/static/images/noData.png'
export default {
name: 'KChart',
components: {
ChartK
},
props: {
kdata: {
type: Array,
default: () => []
},
type: {
type: String | Number,
default: 1
},
// 证券代码
symbol: {
type: String | Number,
default: ''
},
market: {
type: String | Number,
default: ''
},
shortName: {
type: String,
default: ''
},
hasStart: {
type: Boolean,
default: false
},
// 当前特征线
curline: {
type: String,
default: ''
},
// 范围值
range: {
type: Number,
default: 70
},
// 横坐标
xaxis: {
type: Array,
default: () => []
},
// 买入&&卖出标识数据
marketData: {
type: Array,
default: () => []
},
predata: {
type: Array,
default: () => []
},
currentIndex: {
type: Number,
default: 1
},
// 刷新图表
isfresh: {
type: Boolean,
default: false
}
},
data() {
return {
securityType: '1', // 品种id 1表示股票 2表示基金 3是债券 11是债券逆回购
KHoverData: [], // k线hoverdata
listK: [], // K线处理的数据
nodImg, // 没数据时候
sallOutMark: [], // 买入卖出标识数组
}
},
watch: {
kdata: {
handler: function(n) {
this.handleKdata(this.kdata)
},
deep: true
},
listK: {
handler: function(n) {
if (!n.length) {
console.log('触发listK当前为空1000000')
}
},
deep: true
},
marketData: {
handler: function(n) {
this.sallOutMark = JSON.parse(JSON.stringify(this.marketData))
},
deep: true
}
},
methods: {
// 获取k线数据
getHoverData(data) {
this.KHoverData = data
},
// 切换指标
choseIndex(index) {
this.currentIndex = index
this.KHoverData = []
},
handleKdata(arr) {
if (arr.length > 0) {
this.listK = arr.map((item) => {
return [
this.formatDate(new Date(item.utc), 'yyyy-MM-dd'),
item.op, // 开盘值
item.cp, // 收盘值
item.lp, // 最低值
item.hp, // 最高值
// this.numToShou(item.tv), // 总量
item.tv, // 总量
item.ta, // 总额
this.toDecimal(this.accMul(item.cr, 100), 2, true), // 涨跌幅
item.macd, // macd
item.dea, // dea
item.dif, // dif
item.d, // d
item.j, // j
item.k, // k
item.ma5,
item.ma10,
item.ma20,
item.ma30,
]
})
// 需要标识的数据
if (this.marketData.length) {
this.sallOutMark = JSON.parse(JSON.stringify(this.marketData))
}
} else {
this.listK = []
}
},
// 成交量单位转换为手
numToShou(num) {
const volume = this.accDiv(num, 100)
return volume
},
// 大数字单位处理(小于10万不处理)
formatNumUnit(value, fixedLen = 2, num = 100000) {
return formatNumUnit(value, fixedLen, num)
},
clicknode(node) {
this.$emit('click', node)
},
setBottom(type) {
let p
if (type == 1) {
p = '10%'
} else if (type == 4) {
p = '10.5%'
} else if (type == 5) {
p = '10.5%'
} else if (type == 7) {
p = 'calc(10% + 4px)'
}
return p
},
getma(attr, val) {
this.$emit('setma', attr, val)
}
}
}
</script>
<style lang="scss" scoped>
.kChartDetail {
background-repeat: no-repeat;
background-position: center center;
// height: 880rpx;
margin-bottom: 40rpx;
width: 710rpx;
position: relative;
background-color: #fff;
.kTitle {
background-color: #eff1f8;
height: 60px;
line-height: 60px;
width: 100%;
padding: 0 30px;
color: #666;
font-size: 18px;
}
.kRadio {
height: 50px;
line-height: 50px;
padding: 0 30px;
border-bottom: 1px solid #d9dfea;
}
.kChart {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
width: 100%;
-webkit-flex-direction: row;
-moz-flex-direction: row;
-ms-flex-direction: row;
-o-flex-direction: row;
flex-direction: row;
justify-content: center;
align-items: center;
.chart {
width: 100%;
height: 100%;
.chartMin {
width: 100%;
height: 100%;
}
}
.quotation {
margin-top: 20px;
color: #666;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
-o-flex-direction: column;
flex-direction: column;
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
-o-flex: 1;
flex: 1;
.title {
font-size: 16px;
background-color: #eff1f8;
height: 40px;
line-height: 40px;
width: 100%;
display: inline-block;
padding: 0 15px;
}
.content {
font-size: 14px;
line-height: 45px;
padding: 0 15px;
background-color: #f6f7fb;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
.text {
width: 14%;
}
.price,
.rectangle {
width: 30%;
color: #666;
}
.price {
text-align: right;
}
.rectangle {
text-align: left;
padding-left: 4px;
.el-progress {
margin-top: 25px;
/deep/.el-progress-bar__outer {
background-color: transparent;
.el-progress-bar__inner {
background-color: #666;
}
}
}
}
.num {
width: 25%;
text-align: right;
}
}
}
}
.indexBtn {
left: 0;
width: 680rpx;
bottom: calc(10% - 64rpx);
height: 60rpx;
// position: absolute;
background-color: #fff;
display: flex;
margin: 0 auto;
border-radius: 10rpx;
overflow: hidden;
border: 1px solid #6C8FF8;
span {
text-align: center;
display: block;
line-height: 60rpx;
height: 60rpx;
color: #6C8FF8;
border-right: none;
flex: 1;
font-size: 26rpx;
}
.active {
cursor: pointer;
color: #fff;
background: #6C8FF8;
}
}
.pos-box {
position: absolute;
}
.macd-box {
top: 57.2%;
left: 3%;
color: #333;
font-size: 14px;
}
.nodata {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.nodtx {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
text-align: center;
font-size: 16px;
color: #fc985a;
display: flex;
height: 100%;
}
}
}
.nodImg {
background-image: url("~@/static/images/noData.png");
}
.macd-box {
top: 67.2%;
left: 8%;
color: #333;
font-size: 14px;
}
.label {
margin-right: 5px;
}
.val {
margin-right: 10px;
}
.MACD {
color: #FF7786;
}
.DEA {
color: #39afe6;
}
.DIF {
color: #ffab42;
}
.D {
color: #7499E4;
}
.J {
color: #FF7786;
}
.K {
color: #339900;
}
</style>
ChartK子组件代码:
<template>
<div ref="myChart" :style="{height:'calc(100vh - '+(80+708)+'rpx)'}" class="chart-area no-drag" />
</template>
<script>
/* eslint-disable indent*/
import {
formatNumUnit
} from './numberFormat'
import * as echarts from '@/common/echarts/echarts.min.js'
function splitData(rawData) {
const categoryData = []
const values = []
const volumes = []
const MACD = []
const DEA = []
const DIF = []
const D = []
const J = []
const K = []
const MA5 = []
const MA10 = []
const MA20 = []
const MA30 = []
for (let i = 0; i < rawData.length; i++) {
categoryData.push([rawData[i].slice(0, 1)[0]])
values.push(rawData[i].slice(1, 14))
volumes.push([i, rawData[i][5], rawData[i][1] > rawData[i][2] ? 1 : -1])
MACD.push([i, rawData[i][8], rawData[i][8] < 0 ? 1 : -1])
DEA.push(rawData[i][9])
DIF.push(rawData[i][10])
D.push(rawData[i][11])
J.push(rawData[i][12])
K.push(rawData[i][13])
MA5.push(rawData[i][14])
MA10.push(rawData[i][15])
MA20.push(rawData[i][16])
MA30.push(rawData[i][17])
}
return {
categoryData: categoryData,
values: values,
volumes: volumes,
MACD: MACD,
DEA: DEA,
DIF: DIF,
D: D,
J: J,
K: K,
MA5,
MA10,
MA20,
MA30
}
}
const upColor = '#ec0000'
const downColor = '#00da3c'
// ma 颜色
const ma5Color = '#39afe6'
const ma10Color = '#da6ee8'
const ma20Color = '#ffab42'
const ma30Color = '#00940b'
export default {
name: 'ChartK',
props: {
// 指标 1:成交量 2.MACD 3.KDJ
currentIndex: {
type: Number,
default: 1
},
// 折线数据 时间 开盘价 收盘价 最低值 最高值 总量
dataList: {
type: Array,
default: () => []
},
// 小数位数
digit: {
type: Number,
default: () => 2
},
// 当前选择的K线周期 1:日K 2:周K 3:月K 4:5min 5:30min 6:60min
currentTab: {
type: Number,
default: () => 1
},
// 品种id 1表示股票 2表示基金 3是债券 11是债券逆回购
securityType: {
type: String,
default: '1'
},
// 当前特征线
curline: {
type: String,
default: ''
},
// 当前特征线
range: {
type: Number,
default: 70
},
// 前置横坐标,用于历史回放
xaxis: {
type: Array,
default: () => []
},
// 买入&&卖出标识数据
markData: {
type: Array,
default: () => []
},
predata: { // 预设数据用于计算MA30
type: Array,
default: () => []
},
// 刷新图表
isfresh: {
type: Boolean,
default: false
}
},
data() {
this.myChart = null
return {
dataListTemp: [], // 备份dataList
start: 0,
end: 0,
dataZoomY: null // 保存dataZoomY信息
}
},
watch: {
currentIndex(newValue, oldValue) {
if (newValue != oldValue) {
this.drawLine()
}
},
dataList: {
handler(newValue, oldValue) {
this.drawLine()
},
deep: true
},
curline: {
handler(n) {
this.drawLine()
}
},
markData: {
handler(newValue, oldValue) {
this.drawLine()
},
deep: true
},
isfresh: {
handler(n) {
n && this.handleResize()
}
}
},
mounted() {
this.init()
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize, false)
},
methods: {
init() {
// 基于准备好的dom,初始化echarts实例
this.myChart = echarts.init(this.$refs.myChart)
this.myChart.getZr().on('click', this.handleEchartsClick)
this.myChart.on('dataZoom', (event) => {
if (event.batch) {
event = event.batch[0]
this.dataZoomY = event
} else {
const {
dataZoomId
} = event
if (!dataZoomId) {
return
}
this.dataZoomY = event
}
})
this.drawLine()
},
drawLine() {
// 备份datalist
this.dataListTemp = JSON.parse(JSON.stringify(this.dataList))
// 处理datalist数据
const chartOption = this.getChartOption()
// 绘制图表
this.myChart.setOption(chartOption, true)
},
getChartOption() {
const _this = this
const data = splitData(this.dataList)
this.$emit('getHoverData', this.dataList[this.dataList.length - 1])
const MA5 = data.MA5
const MA10 = data.MA10
const MA20 = data.MA20
const MA30 = data.MA30
this.$emit('getma', 'MA5', MA5[MA5.length - 1])
this.$emit('getma', 'MA10', MA10[MA10.length - 1])
this.$emit('getma', 'MA20', MA20[MA20.length - 1])
this.$emit('getma', 'MA30', MA30[MA30.length - 1])
let ln = this.dataList.length
this.start = ln >= 30 ? ln-30 : 0
this.end = ln
const option = {
color: ['#7499E4', '#FF7786', '#339900'],
animation: false,
legend: {
show: false
},
grid: [{
left: '15%',
right: '5%',
top: '6.25%',
height: '30.63%'
},
{
left: '15%',
right: '5%',
bottom: '7%',
height: '25.1%'
}
],
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(38, 43, 81, 0.5)',
textStyle: {
color: '#fff',
fontSize: 12
},
axisPointer: {
type: 'line',
lineStyle: {
// type: "dotted",
color: '#rgba(153, 153, 153, 0.5)',
width: 1
}
},
extraCssText: 'text-align: left;',
formatter: function(params) {
_this.setHoverData(params)
var str = ''
let param = {}
for (const i in params) {
if (params[i].seriesName == 'k线') {
param = params[i]
}
if (params[i].seriesName == 'MA5') {
_this.$emit('getma', 'MA5', params[i].value)
}
if (params[i].seriesName == 'MA10') {
_this.$emit('getma', 'MA10', params[i].value)
}
if (params[i].seriesName == 'MA20') {
_this.$emit('getma', 'MA20', params[i].value)
}
if (params[i].seriesName == 'MA30') {
_this.$emit('getma', 'MA30', params[i].value)
}
}
if (param.data && param.data.length > 1) {
str =
'开盘:' +
_this.toDecimal(param.data[1], _this.digit, true) +
'<br />' +
'收盘:' +
_this.toDecimal(param.data[2], _this.digit, true) +
'<br />' +
'最低:' +
_this.toDecimal(param.data[3], _this.digit, true) +
'<br />' +
'最高:' +
_this.toDecimal(param.data[4], _this.digit, true) +
'<br />' +
'涨跌幅:' +
(param.data[7] > 0 ? '+' + param.data[7] : param.data[7]) + '%' +
'<br />' +
'成交量(手):' +
formatNumUnit(param.data[5]) +
'<br />' +
'成交额:' +
formatNumUnit(param.data[6])
return str
} else {
return ''
}
}
},
axisPointer: {
link: {
xAxisIndex: 'all'
}, // 绑定两个图
label: {
backgroundColor: '#777'
}
},
visualMap: {
show: false,
seriesIndex: 1,
dimension: 2,
pieces: [{
value: 1,
color: downColor
}, {
value: -1,
color: upColor
}]
},
xAxis: [{
type: 'category',
// interval: 1,
splitNumber: 10,
boundaryGap: ['20%', '20%'],
axisLabel: {
show: false,
color: '#999'
},
// 轴线样式
axisLine: {
show: false,
lineStyle: {
color: '#ccc'
}
},
data: data.categoryData
}, {
type: 'category',
gridIndex: 1,
// 轴线样式
axisLine: {
show: true,
lineStyle: {
color: '#ccc'
}
},
// 坐标轴刻度
axisTick: {
show: true
},
// 坐标轴指示器
axisPointer: {
label: {
show: true
}
},
data: data.categoryData
}],
yAxis: [{
type: 'value',
gridIndex: 0,
scale: true,
splitNumber: 5,
// 坐标轴刻度
axisTick: {
show: false
},
// 标签
axisLabel: {
interval: true,
textStyle: {
color: '#999'
},
formatter: function(value) {
return _this.toDecimal(value, _this.digit, true)
}
},
// 轴线样式
axisLine: {
show: false
},
// 坐标轴在 grid 区域中的分隔线
splitLine: {
lineStyle: {
color: 'rgba(100,100,100,0.2)'
}
}
},
{
type: 'value',
gridIndex: 1,
scale: true,
// 坐标轴刻度
axisTick: {
show: false
},
// 标签
axisLabel: {
interval: true,
textStyle: {
color: '#999'
},
formatter: function(value) {
return formatNumUnit(value)
}
},
// 轴线样式
axisLine: {
show: false
},
// 坐标轴在 grid 区域中的分隔线
splitLine: {
show: false
}
}
],
series: [{
name: 'k线',
type: 'candlestick',
barMaxWidth: 20,
itemStyle: {
normal: {
color: upColor,
color0: downColor,
borderColor: upColor,
borderColor0: downColor
}
},
xAxisIndex: 0,
yAxisIndex: 0,
data: data.values,
lineStyle: {
width: 1
},
label: {
normal: {
show: false
}
}
}],
dataZoom: []
}
option.tooltip.axisPointer.type = 'cross'
option.xAxis[0].axisLabel = {
formatter: function(param) {
return ``
}
}
option.xAxis[1].axisLabel = {
formatter: function(param) {
return ``
}
}
option.xAxis[0].axisPointer = {}
option.xAxis[0].axisPointer.label = {
backgroundColor: 'none',
formatter: function(param) {
return ``
}
}
option.xAxis[1].axisPointer = {}
option.xAxis[1].axisPointer.label = {
backgroundColor: 'none',
formatter: function(param) {
return ``
}
}
option.grid[0].height = '45.63%'
option.dataZoom = [{
id: 'dataZoomX',
type: 'inside',
xAxisIndex: [0, 1],
startValue: this.start,
endValue: this.end - 1,
minValueSpan: 10,
maxValueSpan: 50,
height: 0,
}, ]
if (this.markData.length) {
option.series[0] = {
...option.series[0],
markPoint: {
label: {
normal: {
color: '#fff',
fontSize: 8,
padding: [0, 0, 6, 0],
formatter: function(param) {
const {
coord
} = param.data
// return ``
return `${coord[3]}`
}
}
},
data: this.markData
}
}
}
/** ** */
if (this.currentIndex == 1) {
option.series[1] = {
name: '交易量',
type: 'bar',
barMaxWidth: 20,
xAxisIndex: 1,
yAxisIndex: 1,
data: data.volumes
}
} else if (this.currentIndex == 2) {
option.series[1] = {
name: 'MACD',
type: 'bar',
xAxisIndex: 1,
barMaxWidth: 20,
yAxisIndex: 1,
data: data.MACD,
showSymbol: false
}
option.series.push({
name: 'DEA',
type: 'line',
xAxisIndex: 1,
yAxisIndex: 1,
data: data.DEA,
showSymbol: false
})
option.series.push({
name: 'DIF',
type: 'line',
xAxisIndex: 1,
yAxisIndex: 1,
data: data.DIF,
showSymbol: false
})
} else if (this.currentIndex == 3) {
option.series[1] = {
name: 'D',
type: 'line',
xAxisIndex: 1,
yAxisIndex: 1,
data: data.D,
showSymbol: false
}
option.series.push({
name: 'J',
type: 'line',
xAxisIndex: 1,
yAxisIndex: 1,
data: data.J,
showSymbol: false
})
option.series.push({
name: 'K',
type: 'line',
xAxisIndex: 1,
yAxisIndex: 1,
data: data.K,
showSymbol: false
})
}
option.series.push({
name: 'MA5',
type: 'line',
data: MA5,
smooth: true,
symbol: 'none', // 隐藏选中时有小圆点
xAxisIndex: 0,
yAxisIndex: 0,
lineStyle: {
normal: {
opacity: 0.8,
color: ma5Color,
width: 1
}
},
itemStyle: {
color: ma5Color
}
})
option.series.push({
name: 'MA10',
type: 'line',
data: MA10,
smooth: true,
symbol: 'none',
xAxisIndex: 0,
yAxisIndex: 0,
lineStyle: { // 标线的样式
normal: {
opacity: 0.8,
color: ma10Color,
width: 1
}
},
itemStyle: {
color: ma10Color
}
})
option.series.push({
name: 'MA20',
type: 'line',
data: MA20,
smooth: true,
xAxisIndex: 0,
yAxisIndex: 0,
symbol: 'none',
lineStyle: {
normal: {
opacity: 0.8,
width: 1,
color: ma20Color
}
},
itemStyle: {
normal: {
color: ma20Color
}
}
})
option.series.push({
name: 'MA30',
type: 'line',
data: MA30,
smooth: true,
symbol: 'none',
xAxisIndex: 0,
yAxisIndex: 0,
lineStyle: {
normal: {
opacity: 0.8,
width: 1,
color: ma30Color
}
},
itemStyle: {
color: ma30Color
}
})
return option
},
setHoverData(params) {
const param = params.find(function(item) {
return item.componentSubType == 'candlestick'
})
if (param !== undefined) {
this.$emit('getHoverData', param.data)
}
},
handleEchartsClick(params) {
const pointInPixel = [params.offsetX, params.offsetY]
if (this.myChart.containPixel('grid', pointInPixel)) {
const pointInGrid = this.myChart.convertFromPixel({
seriesIndex: 0
}, pointInPixel)
const xIndex = pointInGrid[0] // 索引
const handleIndex = Number(xIndex)
const seriesObj = this.myChart.getOption() // 图表object对象
this.$emit('click', handleIndex)
}
},
handleResize() {
this.myChart.resize()
}
}
}
</script>
b-zdialog弹框组件代码:
<template>
<view v-if="show">
<view class="maskCom" @click="mask"></view>
<view class="dialogCom">
<view class="close" v-if="close" @click="$emit('hide')"></view>
<view class="dialog-hd" v-show="title" v-html="title"></view>
<view class="dialog-bd">
<!-- 用于替换 -->
<slot></slot>
</view>
<view v-if="isfoot">
<view class="dialog-ft" v-if="submitState">
<view class="goLink">{{submitText}}</view>
</view>
<view class="dialog-ft" v-else>
<view class="goLink" :style="cancelBtn?'color:rgba(102, 102, 102, 1)':'color:#7A7A7A'" @click="cancel"
v-text="cancelBtn" v-if="confirm"></view>
<view class="goLink" :style="confirm?'width:50%':'width:100%'" @click="sure" v-text="sureBtn">
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'zdialog',
props: {
data: {
type: [Object, Function]
},
show: Boolean,
confirm: Boolean,
close: Boolean,
isfoot: {
type: Boolean,
default: true
},
title: String,
sureBtn: {
type: String,
default: '确定'
},
cancelBtn: {
type: String,
default: '取消'
},
maskCb: Function,
sureCb: Function,
cancelCb: Function,
submitState: Boolean, // 设置提交状态
submitText: {
type: String,
default: '提交中...'
} // 设置提交中文案
},
methods: {
mask() {
// 设置提交状态
this.$emit('subHide')
if (this.maskCb) {
this.maskCb();
} else {
this.$emit('hide')
}
},
sure() {
if (this.sureCb) {
this.sureCb();
} else {
this.$emit('hide')
}
},
cancel() {
if (this.cancelCb) {
this.cancelCb();
} else {
this.$emit('hide')
}
}
}
}
</script>
<style lang="scss">
.maskCom {
position: fixed;
left: 0;
top: 0;
z-index: 998;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
}
.dialogCom {
position: fixed;
left: 70rpx !important;
right: 70rpx !important;
top: 50%;
z-index: 999;
border-radius: 16rpx;
background-color: #fff;
transform: translate(0, -55%);
-webkit-transform: translate(0, -55%);
padding-top: .5rem;
padding: 20rpx 30rpx 10rpx;
.close {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 50rpx;
height: 50rpx;
z-index: 2;
background: url('../../static/images/game/close-pop-icon.png') no-repeat;
background-size: 100%;
}
}
.dialog-hd {
position: relative;
font-size: 36rpx;
padding: 30rpx 0 20rpx;
text-align: center;
color: $uni-text-color;
}
.dialog-bd {
padding: 0 30rpx 15rpx;
line-height: 1.5;
font-size: 30rpx;
text-align: justify;
// max-height: 6.6rem;
overflow-y: auto;
color: #404040;
text-align: center;
p {
line-height: 1.5;
}
}
.dialog-ft {
padding: 26rpx 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-top: 1rpx solid #F0F0F0;
// height: 66rpx;
line-height: 66rpx;
font-size: 36rpx;
color: $uni-text-color-grey;
.goLink {
text-align: center;
height: 66rpx;
line-height: 66rpx;
color: $uni-text-color-orange;
display: block;
width: 50%;
}
.goLink:nth-child(2) {
border-left: 1rpx solid #DFE1E6;
display: block;
}
}
</style>
numberFormat.js文件方法:
/**
* [toDecimal 转化数字为几位小数,四舍五入或者截取多于位数]
* @param {[Number]} num [需转化的数字]
* @param {Number} [digit=2] [需保留或截取的位数]
* @param {Boolean} [isRround=true] [true四舍五入,false截取多于小数]
* @return {[Number, String]} [转换后的结果]
*/
// import {BigNumber} from 'bignumber.js';
export function toDecimal(num, digit = 2, isRround = true) {
if (num === null || num === undefined) {
return ''
}
// return new BigNumber(num).toFormat(digit);
// 负数处理四舍五入,先转成正数
let sign = ''
if (num < 0) {
sign = '-'
}
num = Math.abs(num).toString()
// 出现了科学计数法
if (num.indexOf('e-') != -1) {
num = noKeXueJiShuFa(num)
}
const numArr = num.split('.')
if (numArr.length > 1) {
// 有小数时的处理
if (numArr[1].length <= digit) {
// 小数位数少于需保留位数的时候
return sign + Number(num).toFixed(digit)
} else {
// 小数位数多于需保留位数的时候
if (isRround) {
// 四舍五入处理
num = numArr[1][digit] >= 5 ? parseInt(noKeXueJiShuFa(num * Math.pow(10, digit))) + 1 : parseInt(noKeXueJiShuFa(num * Math.pow(10, digit)))
return sign + (num / Math.pow(10, digit)).toFixed(digit)
} else {
// 去除多于小数
return sign + `${numArr[0]}.${numArr[1].substring(0, digit)}`
}
}
} else {
// 无小数时的处理
return sign + Number(num).toFixed(digit)
}
}
function noKeXueJiShuFa(num) {
if (String(num).indexOf('e-') != -1) {
num = '0' + String(Number(num) + 1).substr(1)
}
return num
}
export function formatNumUnit(value, fixedLen = 2, num = 10000) {
if (value == '--') {
return value
}
var result = {}
var k = 10000
var sizes = ['', '万', '亿', '万亿']
var i
if (value < num) {
result.value = round(Number(value), fixedLen)
result.unit = ''
} else {
i = Math.floor(Math.log(value) / Math.log(k))
result.value = round(value / Math.pow(k, i), fixedLen)
result.unit = sizes[i]
}
return result.value + result.unit
}
// 保留fixedLen位小数,最后不补0
export function round(value, fixedLen = 2) {
return parseFloat(value.toFixed(fixedLen))
}
dateFormat.js文件方法:
/**
* [dateFormat 时间格式化]
* @param {[Number,String]} [timestamp=Date.now()] [需转化的时间戳或一个能格式化的时间字符串]
* @param {[String]} [format='yyyy-M-d'] [需转化的时间格式]
* yyyy年,月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q),两个字符表示按2位字符输入
* 举例:
* dateFormat(Date.now(), "yyyy-MM-dd hh:mm:ss") ==> 2017-04-25 16:06:06
* dateFormat(Date.now(), "yyyy-M-d h:m:s q") ==> 2017-4-25 16:6:6 2
* @return {[String]} [格式化时间字符串]
*/
export default function dateFormat(timestamp = Date.now(), format = 'yyyy-M-d') {
return getFormatDate(getJsonDate(timestamp), format)
}
// 生成时间对象
export function getJsonDate(timestamp) {
let time = new Date(getCorrectTime(timestamp))
let jsonDate = {
y: time.getFullYear(), //年
M: time.getMonth() + 1, //月
d: time.getDate(), //日
h: time.getHours(), //时
m: time.getMinutes(), //分
s: time.getSeconds(), //秒
q: Math.floor((time.getMonth() + 3) / 3) //季度
}
return jsonDate
}
// 生成格式化时间字符串
export function getFormatDate(jsonDate, format) {
let dateStr = format.replace(/yyyy/g, jsonDate.y)
Object.keys(jsonDate).forEach(e => {
// 时间格式化操作
let double = e.repeat(2)
let num = jsonDate[e]
if (format.includes(double)) {
// 如果需要两位的格式化数据,则转换为两位数
dateStr = dateStr.replace(double, getTwoDigit(num))
} else {
dateStr = dateStr.replace(e, num)
}
})
return dateStr
}
/*
* 获取两个时间之间相差多少天及时分秒
* 举例:
* 2017-12-1 23:20与2017-4-19 9:55相差时间
* getDiffDay('2017-12-1 23:24','2017-4-19 9:55')
* 226天13时29分
*/
export function getDiffDay(original, minuend = Date.now()) {
// 获得时间差值
let diffTime = new Date(getCorrectTime(original)).getTime() - new Date(getCorrectTime(minuend)).getTime()
// 一天的毫秒数
let daySeconds = 24 * 3600 * 1000
// 相差天数
let diffDay = parseInt(diffTime / 1000 / 3600 / 24)
// 剩余相差小时
let diffHours = parseInt((diffTime - diffDay * daySeconds) / 1000 / 3600)
// 剩余相差分钟
let diffMinutes = parseInt((diffTime - diffDay * daySeconds - diffHours * 3600 * 1000) / 1000 / 60)
// 剩余相差秒
let diffSeconds = parseInt((diffTime - diffDay * daySeconds - diffHours * 3600 * 1000 - diffMinutes * 60 * 1000) / 1000)
let diffDayStr = `${concatTime(diffDay, '天')}${concatTime(diffHours, '时')}${concatTime(diffMinutes, '分')}${concatTime(diffSeconds, '秒')}`
return diffTime >= 0
? diffDayStr
: `-${diffDayStr}`
function concatTime(num, type) {
return `${Math.abs(num)}${type}`
}
}
/**
* [getMoveDay 获取当前日期的前几天或者后几天的日期]
* @param {[Number]} AddDayCount [前几天或者后几天,AddDayCount为负数时为前几天的日期,AddDayCount为正数时为后几天的日期]
* @param {Boolean} [isTwoDigit=true] [日期是否强制2位输出]
* @return {[String]} [计算后的日志,以yyyy-M-d或者yyyy-MM-dd的形式输出]
*/
export function getMoveDay(AddDayCount, isTwoDigit = true) {
let dd = new Date()
dd.setDate(dd.getDate() + AddDayCount)
if (isTwoDigit) {
// 强制以2位数输出
return `${getTwoDigit(dd.getFullYear())}-${getTwoDigit(dd.getMonth() + 1)}-${getTwoDigit(dd.getDate())}`
} else {
return `${dd.getFullYear()}-${dd.getMonth() + 1}-${dd.getDate()}`
}
}
// 数字如果为一位数,前面加0
function getTwoDigit(num) {
return Number(num) < 10
? `0${num}`
: num
}
// 对于传入时间数据的转换
function getCorrectTime(timestamp) {
if (isNaN(timestamp)) {
// ios日期格式兼容处理
timestamp = timestamp.toString().includes('-')
? (timestamp.replace(/-/g, '/')).slice(0,19)
: timestamp
} else {
timestamp = Number(timestamp)
}
return timestamp
}