| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- <template>
- <div class="mtp-echats-kline">
- <div class="mtp-echats-kline__container main">
- <ul class="legend">
- <li class="legend-item">开: {{klineDetail ? klineDetail.open : '--'}}</li>
- <li class="legend-item">收: {{klineDetail ? klineDetail.close : '--'}}</li>
- <li class="legend-item">高: {{klineDetail ? klineDetail.highest : '--'}}</li>
- <li class="legend-item">低: {{klineDetail ? klineDetail.lowest : '--'}}</li>
- <li class="legend-item">MA5: {{klineDetail ? klineDetail.ma5 : '--'}}</li>
- <li class="legend-item">MA10: {{klineDetail ? klineDetail.ma10 : '--'}}</li>
- <li class="legend-item">MA15: {{klineDetail ? klineDetail.ma15 : '--'}}</li>
- </ul>
- <mtp-echarts :option="klineOption" :empty="showEmpty" v-model:index="dataIndex" v-model:loading="loading" @ready="mainReady" />
- </div>
- <template v-if="showIndicator">
- <div class="mtp-echats-kline__container indicator">
- <!-- MACD -->
- <section class="section" v-if="activeSeriesType === SeriesType.MACD">
- <ul class="legend">
- <li class="legend-item">MACD: {{macdDetail ? macdDetail.macd : '--'}}</li>
- <li class="legend-item">DIF: {{macdDetail ? macdDetail.dif : '--'}}</li>
- <li class="legend-item">DEA: {{macdDetail ? macdDetail.dea : '--'}}</li>
- </ul>
- <mtp-echarts :option="macdOption" :empty="showEmpty" v-model:index="dataIndex" v-model:loading="loading" @ready="indicatorReady" />
- </section>
- <!-- VOL -->
- <section class="section" v-if="activeSeriesType === SeriesType.VOL">
- <ul class="legend">
- <li class="legend-item">VOL: {{volDetail ? volDetail.vol : '--'}}</li>
- </ul>
- <mtp-echarts :option="volOption" :empty="showEmpty" v-model:index="dataIndex" v-model:loading="loading" @ready="indicatorReady" />
- </section>
- <!-- KDJ -->
- <section class="section" v-if="activeSeriesType === SeriesType.KDJ">
- <ul class="legend">
- <li class="legend-item">K: {{kdjDetail ? kdjDetail.k : '--'}}</li>
- <li class="legend-item">D: {{kdjDetail ? kdjDetail.d : '--'}}</li>
- <li class="legend-item">J: {{kdjDetail ? kdjDetail.j : '--'}}</li>
- </ul>
- <mtp-echarts :option="kdjOption" :empty="showEmpty" v-model:index="dataIndex" v-model:loading="loading" @ready="indicatorReady" />
- </section>
- <!-- CCI -->
- <section class="section" v-if="activeSeriesType === SeriesType.CCI">
- <ul class="legend">
- <li class="legend-item">CCI: {{cciDetail ? cciDetail.cci : '--'}}</li>
- </ul>
- <mtp-echarts :option="cciOption" :empty="showEmpty" v-model:index="dataIndex" v-model:loading="loading" @ready="indicatorReady" />
- </section>
- </div>
- <mtp-tabbar theme="menu" :data="tabs" @change="tabChange" />
- </template>
- </div>
- </template>
- <script lang="ts">
- import { defineComponent, ref, PropType, watch, computed } from 'vue'
- import { QueryHistoryDatas, QueryQuoteDayRsp } from '@/services/go/quote/interface';
- import { QueryHistoryDatas as queryHistoryDatas } from '@/services/go/quote';
- import { getQuoteDayInfoByCode } from '@/services/bus/goods';
- import { throttle } from '@/utils/time'
- import { CycleType, SeriesType } from './type'
- import { useDataset } from './dataset'
- import { useOptions } from './options'
- import moment from 'moment';
- import * as echarts from 'echarts'
- import MtpEcharts from '../echarts-base/index.vue'
- import MtpTabbar from '../../tabbar/index.vue'
- import { Tabbar } from '../../tabbar/type'
- export default defineComponent({
- components: {
- MtpEcharts,
- MtpTabbar,
- },
- props: {
- goodscode: {
- type: String,
- default: '',
- },
- // 周期类型
- cycleType: {
- type: Number as PropType<CycleType>,
- default: CycleType.minutes,
- },
- // 是否显示指标
- showIndicator: {
- type: Boolean,
- default: true,
- },
- },
- setup(props) {
- const loading = ref(false),
- showEmpty = ref(false),
- dataIndex = ref(0), // 当前数据索引值
- activeSeriesType = ref(SeriesType.MACD), // 当前选中的指标
- chartGroup = new Map<string, echarts.ECharts>(), // 图表联动实例组
- quote = ref<QueryQuoteDayRsp>(getQuoteDayInfoByCode(props.goodscode)!); // 商品实时行情
- const { klineData, macdData, volData, kdjData, cciData, handleData, calcIndicator } = useDataset();
- const { klineOption, macdOption, volOption, kdjOption, cciOption, initOptions, updateOptions } = useOptions(klineData, macdData, volData, kdjData, cciData);
- const klineDetail = computed(() => klineData.source[dataIndex.value]);
- const macdDetail = computed(() => macdData.source[dataIndex.value]);
- const volDetail = computed(() => volData.source[dataIndex.value]);
- const kdjDetail = computed(() => kdjData.source[dataIndex.value]);
- const cciDetail = computed(() => cciData.source[dataIndex.value]);
- const tabs: Tabbar[] = [
- { label: 'MACD', value: SeriesType.MACD },
- { label: 'VOL', value: SeriesType.VOL },
- { label: 'KDJ', value: SeriesType.KDJ },
- { label: 'CCI', value: SeriesType.CCI },
- ]
- const mainReady = (chart: echarts.ECharts) => {
- chartGroup.set('main', chart);
- initData();
- }
- const indicatorReady = (chart: echarts.ECharts) => {
- chartGroup.delete('indicator');
- chartGroup.set('indicator', chart);
- echarts.connect([...chartGroup.values()]); // 图表联动
- }
- // 初始化数据
- const initData = () => {
- showEmpty.value = false;
- loading.value = true;
- dataIndex.value = -1;
- klineData.source = [];
- macdData.source = [];
- volData.source = [];
- kdjData.source = [];
- cciData.source = [];
- const params: QueryHistoryDatas = {
- cycleType: props.cycleType,
- goodsCode: props.goodscode.toUpperCase(),
- count: 1440,
- };
- // 查询历史数据
- queryHistoryDatas(params).then((res) => {
- if (res.length) {
- dataIndex.value = res.length - 1;
- // 日期升序排序
- const data = res.sort((a, b) => moment(a.ts).valueOf() - moment(b.ts).valueOf());
- handleData(data, () => initOptions());
- } else {
- showEmpty.value = true;
- }
- }).catch((err) => {
- console.error(err);
- showEmpty.value = true;
- }).finally(() => {
- loading.value = false;
- });
- }
- // 指标切换
- const tabChange = (item: Tabbar<SeriesType>) => {
- activeSeriesType.value = item.value;
- setTimeout(() => {
- initOptions();
- }, 0);
- }
- // 获取周期毫秒数
- const getCycleMilliseconds = () => {
- const milliseconds = 60 * 1000; // 一分钟毫秒数
- switch (props.cycleType) {
- case CycleType.minutes5: {
- return milliseconds * 5;
- }
- case CycleType.minutes30: {
- return milliseconds * 30;
- }
- case CycleType.minutes60: {
- return milliseconds * 60;
- }
- case CycleType.hours2: {
- return milliseconds * 2 * 60;
- }
- case CycleType.Hours4: {
- return milliseconds * 4 * 60;
- }
- case CycleType.days: {
- return milliseconds * 24 * 60;
- }
- default: {
- return milliseconds;
- }
- }
- }
- // 更新图表数据
- const updateChartData = () => {
- const { source } = klineData,
- lastIndex = source.length - 1, // 历史行情最后索引位置
- cycleMilliseconds = getCycleMilliseconds(),
- newTime = moment(quote.value.lasttime), // 实时行情最新时间
- last = quote.value.last; // 实时行情最新价
- const oldTime = lastIndex === -1 ? newTime : moment(source[lastIndex].date); // 历史行情最后时间
- const diffTime = newTime.valueOf() - oldTime.valueOf(); // 计算时间差
- if (diffTime > cycleMilliseconds * 2) {
- // 时间间隔超过两个周期,重新请求历史数据
- } else {
- // 判断时间差是否大于周期时间
- if (lastIndex === -1 || diffTime > cycleMilliseconds) {
- oldTime.add(cycleMilliseconds, 'ms');
- const newDate = oldTime.format('YYYY-MM-DD HH:mm:ss');
- // 新增K线数据
- klineData.source.push({
- date: newDate,
- open: last,
- close: last,
- lowest: last,
- highest: last,
- ma5: '-',
- ma10: '-',
- ma15: '-',
- });
- // 新增MACD数据
- macdData.source.push({
- date: newDate,
- ema12: 0,
- ema26: 0,
- dif: 0,
- dea: 0,
- macd: 0,
- })
- // 新增VOL数据
- volData.source.push({
- date: newDate,
- vol: 0,
- })
- // 新增KDJ数据
- kdjData.source.push({
- date: newDate,
- k: '-',
- d: '-',
- j: '-',
- })
- // 新增CCI数据
- cciData.source.push({
- date: newDate,
- cci: '-',
- })
- } else {
- // 更新列表中最后一条记录的数据
- const item = source[lastIndex];
- if (item.lowest > last) {
- item.lowest = last; // 更新最低价
- }
- if (item.highest < last) {
- item.highest = last; // 更新最高价
- }
- item.close = last; // 更新收盘价
- }
- // 更新各种指标
- calcIndicator(lastIndex === -1 ? 0 : lastIndex);
- // 延迟图表更新,减少卡顿
- throttle(() => {
- updateOptions();
- }, 1000)
- }
- }
- // 监听行情推送
- watch(() => quote.value.last, () => {
- if (!loading.value) {
- updateChartData();
- }
- })
- // 监听周期选择变化
- watch(() => props.cycleType, () => initData());
- return {
- SeriesType,
- loading,
- showEmpty,
- dataIndex,
- tabs,
- activeSeriesType,
- klineData,
- klineOption,
- macdOption,
- volOption,
- kdjOption,
- cciOption,
- klineDetail,
- macdDetail,
- volDetail,
- kdjDetail,
- cciDetail,
- tabChange,
- mainReady,
- indicatorReady,
- }
- }
- })
- </script>
- <style lang="less">
- @import './index.less';
- </style>
|