Browse Source

图表优化

li.shaoyi 3 năm trước cách đây
mục cha
commit
bd07bdb461

+ 0 - 50
src/common/components/echart/echart-base/index.less

@@ -1,50 +0,0 @@
-.echart {
-    display       : flex;
-    flex-direction: column;
-    position      : relative;
-    overflow      : hidden;
-
-    &__empty {
-        flex           : 1;
-        display        : flex;
-        justify-content: center;
-        align-items    : center;
-        color          : #7A8A94;
-    }
-
-    &__container {
-        flex: 1;
-
-        .tooltip {
-            &-title {
-                text-align: left;
-            }
-
-            &-item {
-                display    : flex;
-                align-items: center;
-
-                span:first-child {
-                    display       : table-cell;
-                    width         : 40px;
-                    text-align    : left;
-                    vertical-align: middle;
-                }
-
-                span:last-child {
-                    width     : 100px;
-                    text-align: right;
-                }
-
-                i {
-                    display      : inline-block;
-                    font-size    : 0;
-                    margin-right : 8px;
-                    border-radius: 50%;
-                    width        : 7px;
-                    height       : 7px;
-                }
-            }
-        }
-    }
-}

+ 0 - 75
src/common/components/echart/echart-base/index.vue

@@ -1,75 +0,0 @@
-<template>
-    <div ref="chartElement" class="echart" :style="{ width: width, height: height }">
-        <template v-if="empty">
-            <div class="echart__empty">暂无数据</div>
-        </template>
-        <template v-else>
-            <div class="echart__container" v-for="i in options.length" :key="i"></div>
-        </template>
-    </div>
-</template>
-
-<script lang="ts">
-import { defineComponent, ref, onMounted, PropType, watch, watchEffect } from 'vue';
-import { EChartsOption } from 'echarts';
-import { useEchart } from './setup';
-
-export default defineComponent({
-    name: 'Echart',
-    props: {
-        // 图表宽度
-        width: {
-            type: String,
-            default: '100%',
-        },
-        // 图表高度
-        height: {
-            type: String,
-            default: '100%',
-        },
-        // 图表配置项,支持多图表(待完善...)
-        options: {
-            type: Array as PropType<EChartsOption[]>,
-            default: [],
-        },
-        loading: {
-            type: Boolean,
-            default: true,
-        },
-        empty: {
-            type: Boolean,
-            default: false,
-        },
-    },
-    setup(props) {
-        // 图表容器元素
-        const chartElement = ref<HTMLElement>();
-
-        onMounted(() => {
-            const { setOptions, showLoading } = useEchart(chartElement.value!);
-
-            watchEffect(() => {
-                showLoading(props.loading);
-            });
-
-            watch(
-                () => props.options,
-                (val) => {
-                    if (!props.loading && !props.empty) {
-                        // 设置图表配置项
-                        setOptions(val);
-                    }
-                }
-            );
-        });
-
-        return {
-            chartElement,
-        };
-    },
-});
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>

+ 0 - 153
src/common/components/echart/echart-base/setup.ts

@@ -1,153 +0,0 @@
-import { onActivated, onDeactivated, onUnmounted, SetupContext, onMounted } from "vue";
-import { _debounce } from "@/utils/time"
-import { getTheme, ThemeEnum } from '@/common/config/theme';
-import * as echarts from 'echarts'
-
-// 创建 iframe 对象
-const createIframe = (parent: HTMLElement): HTMLIFrameElement => {
-    const iframe = document.createElement('iframe');
-    // 设置 iframe 样式
-    iframe.style.cssText = 'position: absolute; z-index: -1000; left: 0; top: 0; width: 100%; height: 100%; border: 0; visibility: hidden; pointer-events: none;';
-    // 添加 iframe 节点
-    parent.appendChild(iframe);
-
-    return iframe;
-}
-
-export function useEchart(el: HTMLElement) {
-    const iframe = createIframe(el);
-    const echartMap: echarts.ECharts[] = [];
-
-    el.querySelectorAll('.echart__container').forEach((item) => {
-        // 初始化图表
-        const echart = echarts.init(item as HTMLElement);
-        echartMap.push(echart);
-    })
-
-    // 显示加载动画
-    const showLoading = (loading: boolean) => {
-        if (loading) {
-            echartMap.forEach((echart) => {
-                echart.clear(); // 清空图表
-                const theme = getTheme(); // 当前主题
-                switch (theme.value) {
-                    case ThemeEnum.default:
-                    case ThemeEnum.dark:
-                        echart.showLoading({
-                            text: '加载中...',
-                            textColor: '#fff',
-                            color: 'rgba(255, 255, 255, 0.75)',
-                            maskColor: 'transparent',
-                        });
-                        break;
-                    default:
-                        echart.showLoading({
-                            text: '加载中...',
-                            maskColor: 'transparent',
-                        });
-                }
-            })
-        }
-    };
-
-    // 图表配置项
-    const setOptions = (options: echarts.EChartsOption[], notMerge = false) => {
-        options.forEach((opt, i) => {
-            echartMap[i].setOption(opt, notMerge);
-            echartMap[i].hideLoading();
-        });
-    };
-
-    const onresize = () => {
-        // 防抖函数待优化,同时调用多次timeoutId可能相同,导致后面的覆盖前面
-        _debounce(() => {
-            // 重置图表大小
-            echartMap.forEach((echart) => {
-                echart.resize && echart.resize();
-            })
-        }, 50);
-    };
-
-    // 键盘上下左右键控制图表
-    const onKeyup = (e: KeyboardEvent) => {
-        // echartMap.forEach((chart) => {
-        //     const datazoom = chart.getOption().dataZoom as echarts.DataZoomComponentOption[];
-        //     if (datazoom && datazoom.length) {
-        //         const start = datazoom[0].start!;
-        //         const end = datazoom[0].end!;
-        //         switch (e.key) {
-        //             case 'ArrowLeft':
-        //                 if (start > 0) {
-        //                     chart.setOption({
-        //                         dataZoom: [
-        //                             {
-        //                                 start: start - 1,
-        //                                 end: end - 1,
-        //                             }
-        //                         ]
-        //                     });
-        //                 }
-        //                 break;
-        //             case 'ArrowUp':
-        //                 console.log('上');
-        //                 break;
-        //             case 'ArrowRight':
-        //                 if (end < 100) {
-        //                     chart.setOption({
-        //                         dataZoom: [
-        //                             {
-        //                                 start: start + 1,
-        //                                 end: end + 1,
-        //                             }
-        //                         ]
-        //                     });
-        //                 }
-        //                 break;
-        //             case 'ArrowDown':
-        //                 console.log('下');
-        //                 break;
-        //             default:
-        //                 console.log(e);
-        //         }
-        //     }
-        // });
-    };
-
-    const addEventListener = () => {
-        // 监听 iframe 变化重置图表尺寸
-        iframe.contentWindow?.addEventListener('resize', onresize);
-        // 监听键盘事件
-        document.addEventListener('keyup', onKeyup);
-    }
-
-    const removeEventListener = () => {
-        // 移除 iframe 监听事件
-        iframe.contentWindow?.removeEventListener('resize', onresize);
-        // 移除键盘监听事件
-        document.removeEventListener('keyup', onKeyup);
-    }
-
-    addEventListener();
-
-    onUnmounted(() => {
-        echartMap.forEach((echart) => {
-            echart.dispose();
-        })
-        removeEventListener();
-    })
-
-    // 针对 keepalive 缓存组件
-    onActivated(() => {
-        addEventListener();
-    })
-
-    // 针对 keepalive 缓存组件
-    onDeactivated(() => {
-        removeEventListener();
-    })
-
-    return {
-        setOptions,
-        showLoading,
-    }
-}

+ 0 - 356
src/common/components/echart/echart-kline/index.vue

@@ -1,356 +0,0 @@
-<template>
-  <echart-base :options="[options]" :empty="isEmpty" v-model:loading="loading"></echart-base>
-</template>
-
-<script lang="ts">
-import { defineComponent, PropType, ref, watch, watchEffect } from 'vue';
-import { QueryHistoryDatasRsp, QueryQuoteDayRsp, QueryHistoryDatas, CycleType } from '@/services/go/quote/interface';
-import { QueryHistoryDatas as queryHistoryDatas } from '@/services/go/quote';
-import { _debounce } from '@/utils/time';
-import EchartBase from '../echart-base/index.vue';
-import { handleEchart, Source } from './setup';
-import moment from 'moment';
-
-export default defineComponent({
-  name: 'EchartKline',
-  components: {
-    EchartBase,
-  },
-  props: {
-    // 实时行情数据
-    quoteData: {
-      type: Object as PropType<QueryQuoteDayRsp>,
-      required: true,
-    },
-    // 周期类型
-    cycleType: {
-      type: Number as PropType<CycleType>,
-      required: true,
-    },
-    // 指标类型
-    seriesType: {
-      type: String,
-      default: 'MACD',
-    },
-  },
-  setup(props) {
-    const loading = ref(true);
-    const isEmpty = ref(false);
-    const historyIndexs: number[] = []; // 行情历史数据中所有非补充数据的索引位置(用于计算均线)
-    const { chartData, options, updateOptions, initOptions } = handleEchart();
-
-    // 处理图表数据
-    const handleData = (rawData: QueryHistoryDatasRsp[]): void => {
-      const { source } = chartData.value;
-      source.length = 0;
-      historyIndexs.length = 0;
-
-      rawData.forEach((item, index) => {
-        const { o, c, h, l, ts, tv } = item;
-        source.push({
-          date: moment(ts).format('YYYY-MM-DD HH:mm:ss'),
-          open: o,
-          close: c,
-          lowest: l,
-          highest: h,
-          ma5: '-',
-          ma10: '-',
-          ma15: '-',
-          vol: tv,
-          macd: '-',
-          dif: '-',
-          dea: '-',
-          k: '-',
-          d: '-',
-          j: '-',
-          cci: '-',
-        });
-        if (!item.f) historyIndexs.push(index); // 排除补充数据
-      });
-
-      calcMA('ma5', 5);
-      calcMA('ma10', 10);
-      calcMA('ma15', 15);
-      calcMACD();
-      calcKDJ();
-      clacCCI();
-    };
-
-    // 计算平均线
-    const calcMA = (key: keyof Source, count: number) => {
-      const { source } = chartData.value;
-      let result: Source[keyof Source] = '-';
-
-      if (source.length >= count) {
-        // 均线起始位置
-        const startIndex = historyIndexs[count - 1];
-        for (let i = 0; i < source.length; i++) {
-          if (startIndex !== undefined && i > startIndex) {
-            const j = historyIndexs.findIndex((val) => val === i);
-            // 判断是否补充数据
-            if (j === -1) {
-              result = source[i - 1][key]; // 取上个平均值
-            } else {
-              // 向后取MA数
-              const maIndexs = historyIndexs.slice(j - (count - 1), j + 1);
-              // 计算总价
-              const total = maIndexs.reduce((sum, val) => sum + source[val].close, 0);
-              // 计算均线
-              result = (total / count).toFixed(2);
-            }
-          }
-          (<typeof result>source[i][key]) = result;
-        }
-      }
-    };
-
-    // 计算EMA
-    const calcEMA = (close: number[], n: number) => {
-      const ema: number[] = [],
-        a = 2 / (n + 1); // 平滑系数
-      for (let i = 0; i < close.length; i++) {
-        if (i === 0) {
-          //第一个EMA(n)是前n个收盘价代数平均
-          const result = close.slice(0, n).reduce((sum, val) => sum + val, 0) / n;
-          ema.push(result);
-        } else {
-          // EMA(n) = α × Close + (1 - α) × EMA(n - 1)
-          const result = a * close[i] + (1 - a) * ema[i - 1];
-          ema.push(result);
-        }
-      }
-      return ema;
-    };
-
-    // 计算DEA
-    const calcDEA = (dif: number[]) => {
-      return calcEMA(dif, 9);
-    };
-
-    // 计算DIF
-    const calcDIF = (close: number[]) => {
-      const dif: number[] = [],
-        emaShort = calcEMA(close, 12),
-        emaLong = calcEMA(close, 26);
-
-      for (let i = 0; i < close.length; i++) {
-        const result = emaShort[i] - emaLong[i];
-        dif.push(result);
-      }
-      return dif;
-    };
-
-    // 计算MACD
-    const calcMACD = () => {
-      const { source } = chartData.value,
-        close = source.map((item) => item.close),
-        dif = calcDIF(close),
-        dea = calcDEA(dif);
-
-      for (let i = 0; i < source.length; i++) {
-        source[i].dif = dif[i].toFixed(2);
-        source[i].dea = dea[i].toFixed(2);
-        source[i].macd = ((dif[i] - dea[i]) * 2).toFixed(2);
-      }
-    };
-
-    // 计算KDJ
-    const calcKDJ = () => {
-      const { source } = chartData.value;
-      for (let i = 0; i < source.length; i++) {
-        const item = source[i];
-        if (i < 8) {
-          item.k = '-';
-          item.d = '-';
-          item.j = '-';
-        } else {
-          let rsv = 50; // 如果最低价等于最高价,RSV默认值为50
-          if (item.lowest !== item.highest) {
-            const n9 = source.slice(i - 8, i + 1).map((item) => item.close), // 取前9个收盘价
-              max = Math.max(...n9),
-              min = Math.min(...n9);
-            // 计算RSV
-            rsv = ((item.close - min) / (max - min)) * 100;
-          }
-
-          const yestK = Number(source[i - 1].k); // 取前一日K值
-          const yestD = Number(source[i - 1].d); // 取前一日D值
-
-          if (isNaN(yestK) || isNaN(yestD)) {
-            // 如果前一日的K值或D值不存在则默认值为50
-            item.k = '50';
-            item.d = '50';
-            item.j = '50';
-          } else {
-            const k = (2 / 3) * yestK + (1 / 3) * rsv,
-              d = (2 / 3) * yestD + (1 / 3) * yestK,
-              j = 3 * k - 2 * d;
-
-            item.k = k.toFixed(2);
-            item.d = d.toFixed(2);
-            item.j = j.toFixed(2);
-          }
-        }
-      }
-    };
-
-    // 计算CCI
-    const clacCCI = () => {
-      const { source } = chartData.value;
-      for (let i = 0; i < source.length; i++) {
-        const item = source[i];
-        if (i < 13) {
-          item.cci = '-';
-        } else {
-          const tp = (item.close + item.lowest + item.highest) / 3, // (收盘价 + 最低价 + 最高价) / 3
-            n14 = source.slice(i - 13, i + 1), // 取前14条数据
-            ma = n14.reduce((sum, e) => sum + (e.close + e.lowest + e.highest) / 3, 0) / 14, // 计算前14条数据的(TP)价总和÷N
-            md = n14.reduce((sum, e) => sum + Math.abs(ma - (e.close + e.lowest + e.highest) / 3), 0) / 14, // 计算前14条数据的(MA-TP)价总和÷N
-            result = (tp - ma) / md / 0.015;
-
-          item.cci = result.toFixed(2);
-        }
-      }
-    };
-
-    // 更新图表K线数据
-    const updateChartData = () => {
-      const { source } = chartData.value,
-        lastIndex = source.length - 1, // 历史行情最后索引位置
-        lastTime = moment(source[lastIndex].date), // 历史行情最后时间
-        newTime = moment(props.quoteData.lasttime), // 实时行情最新时间
-        newPrice = props.quoteData.last; // 实时行情最新价
-
-      let cycleMilliseconds = 60 * 1000; // 周期毫秒数
-
-      switch (props.cycleType) {
-        case CycleType.minutes5:
-          cycleMilliseconds *= 5;
-          break;
-        case CycleType.minutes30:
-          cycleMilliseconds *= 30;
-          break;
-        case CycleType.minutes60:
-          cycleMilliseconds *= 60;
-          break;
-        case CycleType.hours2:
-          cycleMilliseconds *= 2 * 60;
-          break;
-        case CycleType.Hours4:
-          cycleMilliseconds *= 4 * 60;
-          break;
-        case CycleType.days:
-          cycleMilliseconds *= 24 * 60;
-          break;
-      }
-
-      const diffTime = newTime.valueOf() - lastTime.valueOf(); // 计算时间差
-
-      if (diffTime > cycleMilliseconds * 2) {
-        // 时间间隔超过两个周期,重新请求历史数据
-      } else {
-        // 判断时间差是否大于周期时间
-        if (diffTime > cycleMilliseconds) {
-          lastTime.add(cycleMilliseconds, 'ms');
-          // 添加历史行情
-          source.push({
-            date: lastTime.format('YYYY-MM-DD HH:mm:ss'),
-            open: newPrice,
-            close: newPrice,
-            lowest: newPrice,
-            highest: newPrice,
-            ma5: '-',
-            ma10: '-',
-            ma15: '-',
-            vol: 0,
-            macd: '-',
-            dif: '-',
-            dea: '-',
-            k: '-',
-            d: '-',
-            j: '-',
-            cci: '-',
-          });
-          historyIndexs.push(lastIndex + 1); // 添加历史行情索引
-        } else {
-          const lastData = source[lastIndex];
-          if (lastData.lowest > newPrice) {
-            lastData.lowest = newPrice; //更新最低价
-          }
-          if (lastData.highest < newPrice) {
-            lastData.highest = newPrice; //更新最高价
-          }
-          lastData.close = newPrice; //更新收盘价
-        }
-
-        calcMA('ma5', 5);
-        calcMA('ma10', 10);
-        calcMA('ma15', 15);
-        calcMACD();
-        calcKDJ();
-        clacCCI();
-
-        // 延迟图表更新,减少卡顿
-        _debounce(() => {
-          updateOptions(props.seriesType);
-        }, 1000);
-      }
-    };
-
-    // 监听行情最新价推送
-    watch(
-      () => props.quoteData.last,
-      () => {
-        if (!loading.value) {
-          updateChartData();
-        }
-      }
-    );
-
-    // 监听指标类型
-    watch(
-      () => props.seriesType,
-      (val) => {
-        if (!loading.value) {
-          updateOptions(val);
-        }
-      }
-    );
-
-    // 监听周期选择变化
-    watchEffect(() => {
-      loading.value = true;
-      const params: QueryHistoryDatas = {
-        cycleType: props.cycleType,
-        goodsCode: props.quoteData.goodscode.toUpperCase(),
-        count: 1440,
-      };
-      // 查询K线数据
-      queryHistoryDatas(params)
-        .then((res) => {
-          if (res.length) {
-            isEmpty.value = false;
-            // 日期升序排序
-            const kdata = res.sort((a, b) => moment(a.ts).valueOf() - moment(b.ts).valueOf());
-            handleData(kdata);
-          } else {
-            isEmpty.value = true;
-          }
-          initOptions(props.seriesType);
-        })
-        .catch(() => {
-          isEmpty.value = true;
-        })
-        .finally(() => {
-          loading.value = false;
-        });
-    });
-
-    return {
-      loading,
-      isEmpty,
-      options,
-    };
-  },
-});
-</script>

+ 0 - 536
src/common/components/echart/echart-kline/setup.ts

@@ -1,536 +0,0 @@
-import { ref, watch } from "vue";
-import { getTheme, ThemeEnum } from '@/common/config/theme';
-import { deepMerge } from '@/utils/objHandle'
-import { EChartsOption } from 'echarts';
-import moment from 'moment';
-
-// 命名待优化
-type Colors = {
-    backgroundColor: string, // 图表背景颜色
-    axisPointerLabelColor: string,
-    legendTextColor: string,
-    xAxisLineColor: string
-    yAxisLineColor: string,
-    seriesMarkLabelColor: string,
-    seriesMarkLineColor: string,
-    upColor: string,
-    downColor: string,
-    equalColor: string,
-}
-
-export type Source = {
-    date: string, // xAxis数据,必须是第一个属性
-    open: number,
-    close: number,
-    lowest: number,
-    highest: number,
-    ma5: string,
-    ma10: string,
-    ma15: string,
-    vol: number,
-    macd: string,
-    dif: string,
-    dea: string,
-    k: string,
-    d: string,
-    j: string,
-    cci: string,
-}
-
-// 图表数据
-type ChartData = {
-    source: Source[],
-}
-
-export function handleEchart() {
-    const options = ref<EChartsOption>();
-    // 当前主题
-    const theme = getTheme();
-    // 图表数据
-    const chartData = ref<ChartData>({
-        source: []
-    });
-
-    // 初始化图表配置
-    const initOptions = (seriesType: string) => {
-        const { source } = chartData.value;
-        const option: EChartsOption = {
-            dataset: {
-                dimensions: ['date', 'open', 'close', 'lowest', 'highest', 'ma5', 'ma10', 'ma15', 'vol', 'macd', 'dif', 'dea', 'k', 'd', 'j', 'cci'],
-                source
-            },
-            animation: false,
-            axisPointer: {
-                link: [
-                    {
-                        xAxisIndex: 'all'
-                    }
-                ],
-            },
-            legend: {
-                //图例控件,点击图例控制哪些系列不显示
-                type: 'scroll',
-                data: ['MA5', 'MA10', 'MA15'],
-                selected: {
-                    VOL: seriesType === 'VOL',
-                    MACD: seriesType === 'MACD',
-                    DIF: seriesType === 'MACD',
-                    DEA: seriesType === 'MACD',
-                    K: seriesType === 'KDJ',
-                    D: seriesType === 'KDJ',
-                    J: seriesType === 'KDJ',
-                    CCI: seriesType === 'CCI',
-                },
-                itemWidth: 14,
-                itemHeight: 2,
-                left: '5%',
-                top: 0,
-                textStyle: {
-                    fontSize: 12,
-                },
-            },
-            // 悬浮框
-            tooltip: {
-                trigger: 'axis',
-                axisPointer: {
-                    type: 'cross',
-                },
-                backgroundColor: 'rgba(255,255,255,.95)',
-                borderWidth: 1,
-                borderRadius: 3,
-                textStyle: {
-                    color: '#4d535c',
-                },
-                confine: true,
-                className: 'tooltip',
-                formatter: (params: any) => {
-                    let result = '';
-                    params.forEach((item: any, index: number) => {
-                        if (index === 0) {
-                            result += '<div class="tooltip-title">' + moment(item.data.date).format('YYYY-MM-DD HH:mm') + '</div>';
-                        }
-                        if (item.seriesType === 'candlestick') {
-                            result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>开盘</span><span>' + item.data.open + '</span></div>';
-                            result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>收盘</span><span>' + item.data.close + '</span></div>';
-                            result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>最低</span><span>' + item.data.lowest + '</span></div>';
-                            result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>最高</span><span>' + item.data.highest + '</span></div>';
-                        } else {
-                            const key = item.dimensionNames[item.encode.y[0]];
-                            result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>' + item.seriesName + '</span><span>' + item.data[key] + '</span></div>';
-                        }
-                    })
-                    return result;
-                },
-            },
-            grid: [
-                // K线
-                {
-                    top: '8%',
-                    left: '8%',
-                    right: '8%',
-                    height: '55%',
-                },
-                // MACD、VOL、KDJ
-                {
-                    top: '73%',
-                    left: '8%',
-                    right: '8%',
-                    height: '20%',
-                }
-            ],
-            xAxis: [
-                // K线时间轴
-                {
-                    type: 'category',
-                    axisLabel: {
-                        formatter: (val: any) => moment(val).format('YYYY/MM/DD'),
-                    },
-                    splitLine: {
-                        show: true,
-                    },
-                },
-                // MACD、VOL、KDJ时间轴
-                {
-                    type: 'category',
-                    gridIndex: 1,
-                    axisLabel: {
-                        show: false,
-                        formatter: (val: any) => moment(val).format('YYYY/MM/DD'),
-                    },
-                    axisLine: {
-                        show: false,
-                    },
-                    axisTick: {
-                        show: false,
-                    },
-                    splitLine: {
-                        show: true,
-                    },
-                }
-            ],
-            yAxis: [
-                {
-                    scale: true,
-                },
-                {
-                    scale: true,
-                    gridIndex: 1,
-                }
-            ],
-            dataZoom: [
-                {
-                    type: 'inside',
-                    xAxisIndex: [0, 1],
-                    startValue: source.length - 120, // 起始显示K线条数(最新120条)
-                    endValue: source.length,
-                    minValueSpan: 60, // 限制窗口缩放显示最少数据条数
-                    maxValueSpan: 400, // 限制窗口缩放显示最大数据条数
-                },
-                {
-                    show: false,
-                    type: 'slider',
-                    xAxisIndex: [0, 1],
-                },
-            ],
-            series: [
-                {
-                    name: 'K线',
-                    type: 'candlestick',
-                    // Y轴数据
-                    markLine: {
-                        animation: false,
-                        // 标线两端图标
-                        symbol: 'none',
-                        // 标线标签样式
-                        label: {
-                            fontWeight: 'bold',
-                            position: 'end',
-                        },
-                        // 标线样式
-                        lineStyle: {
-                            type: 'dashed',
-                        },
-                        data: [
-                            {
-                                // 当前价
-                                yAxis: source.length ? source[source.length - 1].close : 0,
-                            },
-                        ],
-                    },
-                },
-                {
-                    name: 'MA5',
-                    type: 'line',
-                    sampling: 'average', //折线图在数据量远大于像素点时候的降采样策略,开启后可以有效的优化图表的绘制效率
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-                {
-                    name: 'MA10',
-                    type: 'line',
-                    sampling: 'average',
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-                {
-                    name: 'MA15',
-                    type: 'line',
-                    sampling: 'average',
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-                {
-                    name: 'VOL',
-                    type: 'bar',
-                    sampling: 'average',
-                    xAxisIndex: 1,
-                    yAxisIndex: 1,
-                    barWidth: '60%',
-                },
-                {
-                    name: 'MACD',
-                    type: 'bar',
-                    sampling: 'average',
-                    xAxisIndex: 1,
-                    yAxisIndex: 1,
-                    barWidth: '20%',
-                },
-                {
-                    name: 'DIF',
-                    type: 'line',
-                    sampling: 'average',
-                    xAxisIndex: 1,
-                    yAxisIndex: 1,
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-                {
-                    name: 'DEA',
-                    type: 'line',
-                    sampling: 'average',
-                    xAxisIndex: 1,
-                    yAxisIndex: 1,
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-                {
-                    name: 'K',
-                    type: 'line',
-                    sampling: 'average',
-                    xAxisIndex: 1,
-                    yAxisIndex: 1,
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-                {
-                    name: 'D',
-                    type: 'line',
-                    sampling: 'average',
-                    xAxisIndex: 1,
-                    yAxisIndex: 1,
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-                {
-                    name: 'J',
-                    type: 'line',
-                    sampling: 'average',
-                    xAxisIndex: 1,
-                    yAxisIndex: 1,
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-                {
-                    name: 'CCI',
-                    type: 'line',
-                    sampling: 'average',
-                    xAxisIndex: 1,
-                    yAxisIndex: 1,
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-            ],
-        };
-
-        options.value = deepMerge(option, getColors(theme.value));
-    };
-
-    // 动态更新数据
-    const updateOptions = (seriesType: string) => {
-        const { source } = chartData.value;
-        if (source.length) {
-            options.value = {
-                legend: {
-                    selected: {
-                        VOL: seriesType === 'VOL',
-                        MACD: seriesType === 'MACD',
-                        DIF: seriesType === 'MACD',
-                        DEA: seriesType === 'MACD',
-                        K: seriesType === 'KDJ',
-                        D: seriesType === 'KDJ',
-                        J: seriesType === 'KDJ',
-                        CCI: seriesType === 'CCI',
-                    },
-                },
-                dataset: {
-                    source
-                },
-                series: [
-                    {
-                        name: 'K线',
-                        markLine: {
-                            data: [
-                                {
-                                    yAxis: source[source.length - 1].close,
-                                },
-                            ],
-                        },
-                    },
-                ],
-            }
-        }
-    };
-
-    // 设置图表样式
-    const setColors = (colors: Colors): EChartsOption => {
-        return {
-            // 图表背景颜色
-            backgroundColor: colors.backgroundColor,
-            axisPointer: {
-                label: {
-                    color: colors.axisPointerLabelColor,
-                },
-            },
-            legend: {
-                textStyle: {
-                    color: colors.legendTextColor,
-                },
-            },
-            xAxis: [
-                {
-                    splitLine: {
-                        lineStyle: {
-                            // 坐标分隔线颜色
-                            color: colors.xAxisLineColor,
-                        },
-                    },
-                },
-                {
-                    splitLine: {
-                        lineStyle: {
-                            // 坐标分隔线颜色
-                            color: colors.xAxisLineColor,
-                        },
-                    },
-                }
-            ],
-            yAxis: [
-                {
-                    splitLine: {
-                        lineStyle: {
-                            // 坐标分隔线颜色
-                            color: colors.xAxisLineColor,
-                        },
-                    },
-                },
-                {
-                    splitLine: {
-                        lineStyle: {
-                            // 坐标分隔线颜色
-                            color: colors.xAxisLineColor,
-                        },
-                    },
-                }
-            ],
-            series: [
-                {
-                    name: 'K线',
-                    markLine: {
-                        // 标线标签样式
-                        label: {
-                            color: colors.seriesMarkLabelColor,
-                        },
-                        // 标线样式
-                        lineStyle: {
-                            color: colors.seriesMarkLineColor,
-                        },
-                    },
-                },
-                {
-                    name: 'MA5',
-                },
-                {
-                    name: 'MA10',
-                },
-                {
-                    name: 'MA15',
-                },
-                {
-                    name: 'VOL',
-                    itemStyle: {
-                        color: (params: any) => {
-                            // 判断收盘价是否高于或等于开盘价
-                            if (params.data.close >= params.data.open) {
-                                return colors.upColor;
-                            } else {
-                                return colors.downColor;
-                            }
-                        },
-                    }
-                },
-                {
-                    name: 'MACD',
-                    itemStyle: {
-                        color: (params: any) => {
-                            if (params.data.macd > 0) {
-                                return colors.upColor;
-                            } else {
-                                return colors.downColor;
-                            }
-                        },
-                    }
-                },
-            ]
-        }
-    }
-
-    // 获取图表样式配置
-    const getColors = (theme: ThemeEnum) => {
-        switch (theme) {
-            case ThemeEnum.default:
-            case ThemeEnum.dark:
-                return setColors({
-                    backgroundColor: 'transparent',
-                    axisPointerLabelColor: '#fff',
-                    legendTextColor: '#0e99e2',
-                    xAxisLineColor: '#171B1D',
-                    yAxisLineColor: '#171B1D',
-                    seriesMarkLabelColor: '#3C454B',
-                    seriesMarkLineColor: '#33393D',
-                    upColor: '#eb5454',
-                    downColor: '#47b262',
-                    equalColor: '#fff',
-                });
-            case ThemeEnum.light:
-                return setColors({
-                    backgroundColor: 'transparent',
-                    axisPointerLabelColor: '#fff',
-                    legendTextColor: '#FC9618',
-                    xAxisLineColor: '#DAE5EC',
-                    yAxisLineColor: '#DAE5EC',
-                    seriesMarkLabelColor: '#ACB8C0',
-                    seriesMarkLineColor: '#ACB8C0',
-                    upColor: '#eb5454',
-                    downColor: '#47b262',
-                    equalColor: '#333',
-                });
-        }
-    }
-
-    watch(theme, (val) => {
-        options.value = getColors(val);
-    });
-
-    return {
-        chartData,
-        options,
-        initOptions,
-        updateOptions,
-    }
-}

+ 0 - 205
src/common/components/echart/echart-timeline/index.vue

@@ -1,205 +0,0 @@
-<template>
-  <echart-base :options="[options]" :empty="isEmpty" v-model:loading="loading"></echart-base>
-</template>
-
-<script lang="ts">
-import { defineComponent, ref, watch, PropType, onMounted, computed } from 'vue';
-import { QueryTSDataRsp, QueryQuoteDayRsp } from '@/services/go/quote/interface';
-import { QueryTSData } from '@/services/go/quote';
-import { _debounce, getRangeTime } from '@/utils/time';
-import { toDecimalFull } from '@/utils/number';
-import EchartBase from '../echart-base/index.vue';
-import { handleEchart } from './setup';
-import moment from 'moment';
-
-export default defineComponent({
-  name: 'EchartTime',
-  emits: ['change'],
-  components: {
-    EchartBase,
-  },
-  props: {
-    // 实时行情数据
-    quoteData: {
-      type: Object as PropType<QueryQuoteDayRsp>,
-      required: true,
-    },
-  },
-  setup(props, { emit }) {
-    const loading = ref(false);
-    const isEmpty = ref(false);
-    const historyIndexs: number[] = []; // 行情历史数据中所有非补充数据的索引位置(用于计算均线)
-    const { chartData, options, updateOptions, initOptions } = handleEchart();
-
-    // 处理图表数据
-    const handleData = (rawData: QueryTSDataRsp): void => {
-      historyIndexs.length = 0; // 清空数据
-      const datas: number[] = [],
-        times: string[] = [],
-        xAxisTimes: string[] = [],
-        yestclose = rawData.preSettle,
-        decimal = rawData.decimalPlace;
-
-      // 历史行情日期
-      rawData.historyDatas.forEach((item, index) => {
-        const { c, ts } = item;
-        datas.push(c);
-        times.push(moment(ts).format('YYYY-MM-DD HH:mm:ss'));
-        if (!item.f) historyIndexs.push(index);
-      });
-
-      // 时间轴(开盘交易时间)
-      rawData.runSteps.forEach((item) => {
-        const { start, end } = item;
-        const rangeTime = getRangeTime(start, end, 'HH:mm', 'm');
-        xAxisTimes.push(...rangeTime);
-      });
-
-      chartData.value = {
-        yestclose,
-        decimal,
-        rawTime: times,
-        ...calcDataLine(datas, yestclose),
-        source: {
-          time: xAxisTimes,
-          data: datas,
-          ma5: calcMA(datas, 5, decimal),
-        },
-      };
-    };
-
-    // 计算图表最高低指标线
-    const calcDataLine = (datas: number[], yestclose: number) => {
-      let max = Math.max(...datas); // 取历史行情最高价
-      let min = Math.min(...datas); // 取历史行情最低价
-
-      const last = datas[datas.length - 1], // 历史行情最后收盘价
-        a = yestclose - min, // 计算收盘价和最低价的差值
-        b = max - yestclose; // 计算收盘价和最高价的差值
-
-      // 比较差值大小
-      if (a > b) {
-        max = yestclose + a;
-        if (last > max) {
-          const c = last - max;
-          max += c;
-          min -= c;
-        }
-      } else {
-        min = min - (b - a);
-        if (min > last) {
-          const c = min - last;
-          max += c;
-          min -= c;
-        }
-      }
-
-      return {
-        max: max,
-        min: min,
-      };
-    };
-
-    // 计算平均线
-    const calcMA = (data: number[], count: number, decimal: number) => {
-      const result: string[] = [];
-      if (data.length >= count) {
-        // 均线起始位置
-        const startIndex = historyIndexs[count - 1];
-        for (let i = 0; i < data.length; i++) {
-          if (startIndex === undefined || i < startIndex) {
-            result.push('-');
-          } else {
-            const j = historyIndexs.findIndex((val) => val === i);
-            // 判断是否补充数据
-            if (j === -1) {
-              // 取上个平均值
-              result.push(result[result.length - 1]);
-            } else {
-              // 向后取MA数
-              const maIndexs = historyIndexs.slice(j - (count - 1), j + 1);
-              // 计算总价
-              const total = maIndexs.reduce((sum, val) => sum + data[val], 0);
-              // 计算均线
-              const ma = toDecimalFull(total / count, decimal);
-              result.push(ma);
-            }
-          }
-        }
-      }
-      return result;
-    };
-
-    // 更新分时数据
-    const updateChartData = () => {
-      const { source, rawTime } = chartData.value,
-        lastIndex = source.data.length - 1, // 历史行情最后索引位置
-        lastTime = moment(rawTime[rawTime.length - 1]), // 历史行情最后时间
-        newTime = moment(props.quoteData.lasttime), // 实时行情最新时间
-        newPrice = props.quoteData.last; // 实时行情最新价
-
-      const cycleMilliseconds = 60 * 1000; // 周期毫秒数
-
-      const diffTime = newTime.valueOf() - lastTime.valueOf(); // 计算时间差
-      // 判断时间差是否大于周期时间
-      if (diffTime > cycleMilliseconds) {
-        lastTime.add(cycleMilliseconds, 'ms');
-        rawTime.push(lastTime.format('YYYY-MM-DD HH:mm:ss')); // 添加历史行情时间
-        source.data.push(newPrice); // 添加历史行情数据
-        historyIndexs.push(lastIndex + 1); // 添加历史行情索引
-      } else {
-        source.data[lastIndex] = newPrice; // 更新历史行情数据
-      }
-
-      source.ma5 = calcMA(source.data, 5, chartData.value.decimal);
-
-      const { min, max } = calcDataLine(source.data, chartData.value.yestclose);
-      chartData.value.min = min;
-      chartData.value.max = max;
-
-      // 延迟图表更新,减少卡顿
-      _debounce(() => {
-        updateOptions();
-      }, 1000);
-    };
-
-    onMounted(() => {
-      loading.value = true;
-      // 查询分时数据
-      QueryTSData(props.quoteData.goodscode)
-        .then((res) => {
-          if (res.historyDatas.length) {
-            isEmpty.value = false;
-            handleData(res);
-            // 调用父级函数查询tik数据 (不合理的逻辑处理,待优化)
-            emit('change', res.startTime, res.endTime);
-          } else {
-            isEmpty.value = true;
-          }
-          initOptions();
-        })
-        .catch(() => {
-          isEmpty.value = true;
-        })
-        .finally(() => {
-          loading.value = false;
-        });
-    });
-
-    watch(
-      () => props.quoteData.last,
-      () => {
-        if (!loading.value) {
-          updateChartData();
-        }
-      }
-    );
-
-    return {
-      loading,
-      isEmpty,
-      options,
-    };
-  },
-});
-</script>

+ 0 - 399
src/common/components/echart/echart-timeline/setup.ts

@@ -1,399 +0,0 @@
-import { ref, watch } from "vue";
-import { toDecimalFull } from '@/utils/number';
-import { getTheme, ThemeEnum } from '@/common/config/theme';
-import { deepMerge } from '@/utils/objHandle'
-import { EChartsOption } from 'echarts';
-import * as echarts from 'echarts';
-import moment from 'moment';
-
-// 命名待优化
-type Colors = {
-    backgroundColor: string, // 图表背景颜色
-    axisPointerLabelColor: string,
-    legendTextColor: string,
-    xAxisLineColor: string
-    yAxisLineColor: string,
-    seriesMarkLabelColor: string,
-    seriesMarkLineColor: string,
-    upColor: string,
-    downColor: string,
-    seriesLineColor: string,
-    seriesAreaGradients: echarts.LinearGradientObject,
-}
-
-// 图表数据
-export type ChartData = {
-    yestclose: number, // 昨日收盘价
-    decimal: number, // 保留小数位
-    min: number, // yAxis最低指标线
-    max: number, // yAxis最高指标线
-    rawTime: string[], // 原始时间数据
-    source: {
-        time: string[], // xAxis数据,必须是第一个属性
-        data: number[], // series数据
-        ma5: string[],
-    },
-}
-
-export function handleEchart() {
-    const options = ref<EChartsOption>({});
-    // 当前主题
-    const theme = getTheme();
-    // 图表数据
-    const chartData = ref<ChartData>({
-        yestclose: 0,
-        decimal: 0,
-        min: 0,
-        max: 0,
-        rawTime: [],
-        source: {
-            time: [],
-            data: [],
-            ma5: [],
-        }
-    });
-
-    // 计算涨跌幅百分比,涨跌幅=(今日收盘价-昨日收盘价)/昨日收盘价*100%
-    const calcRatio = (val: number) => {
-        const yestclose = chartData.value.yestclose;
-        const num = Number(val);
-
-        if (isNaN(num)) {
-            return "-";
-        }
-        if (yestclose > 0) {
-            const result = (num - yestclose) / yestclose * 100;
-            return toDecimalFull(result) + '%';
-        }
-        return '0%';
-    }
-
-    // 初始化图表配置
-    const initOptions = () => {
-        const { yestclose, min, max, decimal, source, rawTime } = chartData.value;
-        const option: EChartsOption = {
-            dataset: {
-                source,
-            },
-            animation: false,
-            legend: {
-                //图例控件,点击图例控制哪些系列不显示
-                type: 'scroll',
-                itemWidth: 14,
-                itemHeight: 2,
-                left: '5%',
-                top: 0,
-                textStyle: {
-                    fontSize: 12,
-                },
-            },
-            // 悬浮框
-            tooltip: {
-                trigger: 'axis',
-                axisPointer: {
-                    type: 'cross',
-                },
-                backgroundColor: 'rgba(255,255,255,.9)',
-                borderWidth: 1,
-                borderRadius: 3,
-                textStyle: {
-                    color: '#4d535c',
-                },
-                confine: true,
-                formatter: (params: any) => {
-                    let result = '';
-                    params.forEach((item: any, index: number) => {
-                        // item.value = [time, data, ma5]
-                        if (item.value[1] !== undefined) {
-                            if (index === 0) {
-                                result += '<div class="tooltip-title">' + moment(rawTime[item.dataIndex]).format('YYYY-MM-DD HH:mm') + '</div>';
-                                result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>收盘</span><span>' + item.value[1] + '</span></div>';
-                            }
-                            if (index === 1) {
-                                result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>均价</span><span>' + item.value[2] + '</span></div>';
-                                result += '<div class="tooltip-item"><span><i></i>涨幅</span><span>' + calcRatio(item.value[2]) + '</span></div>';
-                            }
-                        }
-                    })
-                    return result;
-                },
-            },
-            grid: {
-                top: '8%',
-                left: '8%',
-                right: '8%',
-                bottom: '8%',
-            },
-            xAxis: {
-                type: 'category',
-                splitLine: {
-                    // 坐标分隔线
-                    show: true,
-                },
-                axisLabel: {
-                    showMinLabel: true,
-                    showMaxLabel: true,
-                }
-            },
-            yAxis: [
-                // Y轴左侧数值标签
-                {
-                    id: 'leftPrice',
-                    scale: true,
-                    min: min,
-                    max: max,
-                    axisLabel: {
-                        formatter: (val: number) => toDecimalFull(val, decimal),
-                    }
-                },
-                // Y轴右侧涨跌幅标签
-                {
-                    id: 'rightRatio',
-                    scale: true,
-                    min: min,
-                    max: max,
-                    splitLine: {
-                        show: false,
-                    },
-                    axisLabel: {
-                        formatter: (val: number) => calcRatio(val),
-                    }
-                },
-            ],
-            // series 中不指定 yAxisId 或 yAxisIndex 默认关联 yAxis 第一个配置,xAxis 配置同理
-            series: [
-                {
-                    name: '分时',
-                    type: 'line',
-                    yAxisId: 'leftPrice',
-                    smooth: true,
-                    symbol: 'none', //中时有小圆点
-                    lineStyle: {
-                        opacity: 0.8,
-                        width: 1,
-                    },
-                    markLine: {
-                        // 标线两端图标
-                        symbol: 'none',
-                        // 标线标签样式
-                        data: [
-                            {
-                                // 当前价
-                                yAxis: source.data[source.data.length - 1] ?? 0,
-                            },
-                            {
-                                // 昨结价
-                                yAxis: yestclose,
-                                lineStyle: {
-                                    color: '#666',
-                                },
-                            },
-                        ],
-                    },
-                },
-                {
-                    name: '均价',
-                    type: 'line',
-                    smooth: true,
-                    symbol: 'none',
-                    lineStyle: {
-                        width: 1,
-                        opacity: 0.8,
-                    },
-                },
-            ],
-        };
-
-        options.value = deepMerge(option, getColors(theme.value));
-    };
-
-    // 动态更新数据
-    const updateOptions = () => {
-        const { source, min, max, yestclose } = chartData.value;
-        if (source.data.length) {
-            options.value = {
-                dataset: {
-                    source
-                },
-                series: [
-                    {
-                        name: '分时',
-                        markLine: {
-                            data: [
-                                {
-                                    yAxis: source.data[source.data.length - 1],
-                                },
-                                {
-                                    // 昨结价
-                                    yAxis: yestclose,
-                                    lineStyle: {
-                                        color: '#666',
-                                    },
-                                },
-                            ],
-                        },
-                    },
-                ],
-                yAxis: [
-                    {
-                        min: min,
-                        max: max,
-                    },
-                    {
-                        min: min,
-                        max: max,
-                    },
-                ],
-            };
-        }
-    };
-
-    // 设置图表样式
-    const setColors = (colors: Colors): EChartsOption => {
-        const { yestclose } = chartData.value;
-        return {
-            // 图表背景颜色
-            backgroundColor: colors.backgroundColor,
-            axisPointer: {
-                label: {
-                    color: colors.axisPointerLabelColor,
-                },
-            },
-            legend: {
-                textStyle: {
-                    color: colors.legendTextColor,
-                },
-            },
-            xAxis: {
-                splitLine: {
-                    lineStyle: {
-                        // 坐标分隔线颜色
-                        color: colors.xAxisLineColor,
-                    },
-                },
-            },
-            yAxis: [
-                // Y轴左侧标签
-                {
-                    id: 'leftPrice',
-                    axisLabel: {
-                        color: (val: any) => {
-                            if (val > yestclose) return colors.upColor;
-                            if (val < yestclose) return colors.downColor;
-                            return '#3C454B';
-                        },
-                    },
-                    splitLine: {
-                        lineStyle: {
-                            // 坐标分隔线颜色
-                            color: colors.yAxisLineColor,
-                        },
-                    },
-                },
-                // Y轴右侧标签
-                {
-                    id: 'rightRatio',
-                    axisLabel: {
-                        color: (val: any) => {
-                            if (val > yestclose) return colors.upColor;
-                            if (val < yestclose) return colors.downColor;
-                            return '#3C454B';
-                        },
-                    },
-                },
-            ],
-            series: [
-                {
-                    lineStyle: {
-                        color: colors.seriesLineColor,
-                    },
-                    areaStyle: {
-                        color: colors.seriesAreaGradients,
-                        shadowColor: 'rgba(0, 0, 0, 0.1)',
-                        shadowBlur: 10,
-                    },
-                    markLine: {
-                        // 标线标签样式
-                        label: {
-                            color: colors.seriesMarkLabelColor,
-                        },
-                        // 标线样式
-                        lineStyle: {
-                            color: colors.seriesMarkLineColor,
-                        },
-                    },
-                }
-            ]
-        }
-    }
-
-    // 获取图表样式配置
-    const getColors = (theme: ThemeEnum) => {
-        switch (theme) {
-            case ThemeEnum.default:
-            case ThemeEnum.dark:
-                return setColors({
-                    backgroundColor: 'transparent',
-                    axisPointerLabelColor: '#fff',
-                    legendTextColor: '#0e99e2',
-                    xAxisLineColor: '#171B1D',
-                    yAxisLineColor: '#171B1D',
-                    seriesMarkLabelColor: '#3C454B',
-                    seriesMarkLineColor: '#33393D',
-                    upColor: '#FF2B2B',
-                    downColor: '#1FF195',
-                    seriesLineColor: '#39afe6',
-                    seriesAreaGradients: new echarts.graphic.LinearGradient(0, 0, 0, 1,
-                        [
-                            {
-                                offset: 0,
-                                color: 'rgba(0, 136, 212, 0.7)',
-                            },
-                            {
-                                offset: 0.8,
-                                color: 'rgba(0, 136, 212, 0.02)',
-                            },
-                        ],
-                        false
-                    ),
-                });
-            case ThemeEnum.light:
-                return setColors({
-                    backgroundColor: 'transparent',
-                    axisPointerLabelColor: '#fff',
-                    legendTextColor: '#FC9618',
-                    xAxisLineColor: '#DAE5EC',
-                    yAxisLineColor: '#DAE5EC',
-                    seriesMarkLabelColor: '#666',
-                    seriesMarkLineColor: '#ACB8C0',
-                    upColor: '#FF2B2B',
-                    downColor: '#00A843',
-                    seriesLineColor: '#3864d7',
-                    seriesAreaGradients: new echarts.graphic.LinearGradient(0, 0, 0, 1,
-                        [
-                            {
-                                offset: 0,
-                                color: 'rgba(0, 136, 212, 0.3)',
-                            },
-                            {
-                                offset: 1,
-                                color: 'rgba(0, 136, 212, 0.3)',
-                            },
-                        ],
-                        false
-                    ),
-                });
-        }
-    }
-
-    watch(theme, (val) => {
-        options.value = getColors(val);
-    });
-
-    return {
-        chartData,
-        options,
-        initOptions,
-        updateOptions,
-    }
-}

+ 0 - 7
src/common/components/echart/index.ts

@@ -1,7 +0,0 @@
-import EchartKline from './echart-kline/index.vue';
-import EchartTime from './echart-timeline/index.vue';
-
-export {
-    EchartKline,
-    EchartTime,
-}

+ 2 - 1
src/common/components/echarts/echarts-kline/dataset.ts

@@ -37,7 +37,7 @@ export function useDataset() {
     }
 
     // 处理行情数据
-    const handleData = (rawData: QueryHistoryDatasRsp[]) => {
+    const handleData = (rawData: QueryHistoryDatasRsp[], onReady?: () => void) => {
         invalidData.length = 0;
         for (let i = 0; i < rawData.length; i++) {
             const { o, c, h, l, ts, f, tv } = rawData[i];
@@ -84,6 +84,7 @@ export function useDataset() {
         }
         // 计算各种指标
         calcIndicator();
+        onReady && onReady();
     }
 
     // 计算MA

+ 1 - 2
src/common/components/echarts/echarts-kline/index.vue

@@ -145,8 +145,7 @@ export default defineComponent({
           dataIndex.value = res.length - 1;
           // 日期升序排序
           const data = res.sort((a, b) => moment(a.ts).valueOf() - moment(b.ts).valueOf());
-          handleData(data);
-          initOptions();
+          handleData(data, () => initOptions(updateChartData));
         } else {
           showEmpty.value = true;
         }

+ 2 - 1
src/common/components/echarts/echarts-kline/options.ts

@@ -302,12 +302,13 @@ export function useOptions(klineData: Dataset<Candlestick>, macdData: Dataset<MA
         }
     }
 
-    const initOptions = () => {
+    const initOptions = (onReady?: () => void) => {
         setKlineOption();
         setMacdOption();
         setVolOption();
         setKdjOption();
         setCciOption();
+        onReady && onReady();
     }
 
     // 动态更新数据

+ 6 - 4
src/common/components/echarts/echarts-timeline/dataset.ts

@@ -24,7 +24,7 @@ export function useDataset() {
     }
 
     // 处理行情数据
-    const handleData = (rawData: QueryTSDataRsp) => {
+    const handleData = (rawData: QueryTSDataRsp, onReady?: () => void) => {
         const { date, close, ma5 } = state.dataset.source;
         invalidData.length = 0;
         state.decimal = rawData.decimalPlace;
@@ -49,6 +49,7 @@ export function useDataset() {
         }
         // 计算各种指标
         calcIndicator();
+        onReady && onReady();
     }
 
     // 计算MA
@@ -84,6 +85,7 @@ export function useDataset() {
     // 计算图表最高低指标线
     const calcMarkLine = () => {
         const { close } = state.dataset.source;
+        const point = Math.pow(10, -state.decimal) * 10; // 图表上下保留10个报价点数
         let max = Math.max(...close); // 取历史行情最高价
         let min = Math.min(...close); // 取历史行情最低价
 
@@ -108,10 +110,10 @@ export function useDataset() {
             }
         }
 
-        state.maxMark = max + min * 0.01;
-        state.minMark = min - min * 0.01;
+        state.maxMark = max + point;
+        state.minMark = min - point;
         state.interval = (state.maxMark - state.minMark) / 6;
-    };
+    }
 
     // 计算各种指标
     const calcIndicator = (startIndex = 0) => {

+ 1 - 2
src/common/components/echarts/echarts-timeline/index.vue

@@ -60,8 +60,7 @@ export default defineComponent({
       QueryTSData(props.goodscode).then((res) => {
         if (res.historyDatas.length) {
           dataIndex.value = res.historyDatas.length - 1;
-          handleData(res);
-          initOptions();
+          handleData(res, () => initOptions(updateChartData));
         } else {
           showEmpty.value = true;
         }

+ 3 - 1
src/common/components/echarts/echarts-timeline/options.ts

@@ -63,7 +63,7 @@ export function useOptions(state: State) {
         }
     }
 
-    const initOptions = () => {
+    const initOptions = (onReady?: () => void) => {
         const colors = getColors();
         const { dataset, maxMark, minMark, interval, yestClose, decimal } = state;
 
@@ -187,6 +187,8 @@ export function useOptions(state: State) {
                 },
             ],
         }
+
+        onReady && onReady();
     }
 
     // 动态更新数据

+ 0 - 54
src/views/market/market-spot/components/goods-chart/chart/index.less

@@ -1,9 +1,6 @@
 .chart-container {
     [theme='light'] & {
         --bgcolor            : #fff;
-        --tab-border-color   : #dae5ec;
-        --tab-checked-color  : #0866b8;
-        --tab-checked-bgcolor: #d4e0ff;
         --slider-border-color: #b2c4dd;
         --slider-bgcolor     : #edf2f7;
         --slider-button-color: #b2c4dd;
@@ -19,57 +16,6 @@
     height          : calc(100% - 41px);
     background-color: var(--bgcolor, #0e0e0f);
 
-    .chart-content {
-        display       : flex;
-        flex-direction: column;
-        flex          : 1;
-        height        : 100%;
-        padding       : 10px 0;
-
-        &__main {
-            flex: 1;
-        }
-
-        &__header {
-            padding      : 4px 4%;
-            margin-bottom: 10px;
-        }
-
-        &__tabs {
-            &:last-child:not(:first-child) {
-                margin-left: 20px;
-            }
-
-            display         : inline-block;
-            background-color: transparent;
-            line-height     : normal;
-            border-bottom   : 0;
-            border          : 1px solid var(--tab-border-color, #22292c);
-            border-radius   : 1.02px;
-            border-right    : 0;
-            overflow        : hidden;
-            margin-top      : 0;
-
-            .ant-menu-item {
-                height       : 22px;
-                line-height  : 22px;
-                color        : #7a8a94;
-                border-bottom: 0;
-                border-right : 1px solid var(--tab-border-color, #22292c);
-                margin-top   : 0;
-                top          : 0;
-                padding      : 0 16px;
-
-                &-active,
-                &-selected {
-                    color           : var(--tab-checked-color, #0866b8);
-                    background-color: var(--tab-checked-bgcolor, #0e2f4c);
-                    border-bottom   : 0;
-                }
-            }
-        }
-    }
-
     .chart-slider {
         display         : flex;
         justify-content : center;

+ 22 - 129
src/views/market/market-spot/components/goods-chart/chart/index.vue

@@ -1,43 +1,8 @@
 <template>
   <!-- 交易图表  -->
   <div class="chart-container">
-    <div class="chart-content">
-      <div class="chart-content__header">
-        <a-menu
-          class="chart-content__tabs"
-          v-model:selectedKeys="activeCycleName"
-          mode="horizontal"
-          @click="changeCycleType"
-        >
-          <a-menu-item v-for="item in chartType" :key="item.name">{{ item.label }}</a-menu-item>
-        </a-menu>
-        <a-menu
-          class="chart-content__tabs"
-          v-model:selectedKeys="activeSeriesType"
-          mode="horizontal"
-          v-show="activeCycleType !== CycleType.time"
-        >
-          <a-menu-item key="MACD">MACD</a-menu-item>
-          <a-menu-item key="VOL">VOL</a-menu-item>
-          <a-menu-item key="KDJ">KDJ</a-menu-item>
-          <a-menu-item key="CCI">CCI</a-menu-item>
-        </a-menu>
-      </div>
-      <echart-time
-        class="chart-content__main"
-        :quote-data="selectedRow"
-        @change="getHistoryTikDatas"
-        v-if="activeCycleType === CycleType.time"
-      ></echart-time>
-      <echart-kline
-        class="chart-content__main"
-        :quote-data="selectedRow"
-        :cycle-type="activeCycleType"
-        :series-type="activeSeriesType[0]"
-        v-else
-      ></echart-kline>
-      <div class="chart-content__footer"></div>
-    </div>
+    <!-- 图表  -->
+    <mtp-chart :goodscode="selectedRow.goodscode" @ready="getHistoryTikDatas" />
     <div class="chart-slider">
       <div class="chart-slider__button"></div>
     </div>
@@ -48,40 +13,22 @@
           <div class="content content--right">{{ selectedRow.goodsname }}</div>
         </div>
         <div class="chart-tips__last">
-          <div
-            :class="['content content--left', handleQuotePriceColor(selectedRow.last, selectedRow.presettle)]"
-          >{{ handleNoneValue(selectedRow.last) }}</div>
+          <div :class="['content content--left', handleQuotePriceColor(selectedRow.last, selectedRow.presettle)]">{{ handleNoneValue(selectedRow.last) }}</div>
           <div class="content content--right">
-            <span
-              :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)"
-            >{{ quoteChange(selectedRow) }}</span>
-            <span
-              :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)"
-            >{{ quoteAmplitude(selectedRow) }}</span>
+            <span :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)">{{ quoteChange(selectedRow) }}</span>
+            <span :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)">{{ quoteAmplitude(selectedRow) }}</span>
           </div>
         </div>
         <div class="chart-tips__volume">
           <a-row>
             <a-col :span="8">卖一</a-col>
-            <a-col
-              :class="handleQuotePriceColor(selectedRow.ask, selectedRow.presettle)"
-              :span="8"
-            >{{ handleNoneValue(selectedRow.ask) }}</a-col>
-            <a-col
-              :class="handleQuotePriceColor(selectedRow.ask, selectedRow.presettle)"
-              :span="8"
-            >{{ handleNoneValue(selectedRow.askvolume) }}</a-col>
+            <a-col :class="handleQuotePriceColor(selectedRow.ask, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.ask) }}</a-col>
+            <a-col :class="handleQuotePriceColor(selectedRow.ask, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.askvolume) }}</a-col>
           </a-row>
           <a-row>
             <a-col :span="8">买一</a-col>
-            <a-col
-              :class="handleQuotePriceColor(selectedRow.bid, selectedRow.presettle)"
-              :span="8"
-            >{{ handleNoneValue(selectedRow.bid) }}</a-col>
-            <a-col
-              :class="handleQuotePriceColor(selectedRow.bid, selectedRow.presettle)"
-              :span="8"
-            >{{ handleNoneValue(selectedRow.bidvolume) }}</a-col>
+            <a-col :class="handleQuotePriceColor(selectedRow.bid, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.bid) }}</a-col>
+            <a-col :class="handleQuotePriceColor(selectedRow.bid, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.bidvolume) }}</a-col>
           </a-row>
         </div>
       </div>
@@ -97,62 +44,35 @@
         <div class="row-content">
           <a-row v-for="(item, index) in tradedList" :key="index + '11'">
             <a-col :span="8">{{ formatTime(item.TS, 'hm') }}</a-col>
-            <a-col
-              :class="handleQuotePriceColor(item.PE, selectedRow.presettle)"
-              :span="8"
-            >{{ item.PE }}</a-col>
-            <a-col
-              :class="handleQuotePriceColor(item.Vol, selectedRow.presettle)"
-              :span="8"
-            >{{ item.Vol }}</a-col>
+            <a-col :class="handleQuotePriceColor(item.PE, selectedRow.presettle)" :span="8">{{ item.PE }}</a-col>
+            <a-col :class="handleQuotePriceColor(item.Vol, selectedRow.presettle)" :span="8">{{ item.Vol }}</a-col>
           </a-row>
         </div>
       </div>
       <div class="chart-tips__info">
         <a-row>
           <a-col :span="4">最新</a-col>
-          <a-col
-            :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)"
-            :span="8"
-          >{{ handleNoneValue(selectedRow.last) }}</a-col>
+          <a-col :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.last) }}</a-col>
           <a-col :span="4">均价</a-col>
-          <a-col
-            :class="handleQuotePriceColor(selectedRow.averageprice, selectedRow.presettle)"
-            :span="8"
-          >{{ handleNoneValue(selectedRow.averageprice) }}</a-col>
+          <a-col :class="handleQuotePriceColor(selectedRow.averageprice, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.averageprice) }}</a-col>
         </a-row>
         <a-row>
           <a-col :span="4">涨跌</a-col>
-          <a-col
-            :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)"
-            :span="8"
-          >{{ quoteChange(selectedRow) }}</a-col>
+          <a-col :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)" :span="8">{{ quoteChange(selectedRow) }}</a-col>
           <a-col :span="4">今开</a-col>
-          <a-col
-            :class="handleQuotePriceColor(selectedRow.opened, selectedRow.presettle)"
-            :span="8"
-          >{{ handleNoneValue(selectedRow.opened) }}</a-col>
+          <a-col :class="handleQuotePriceColor(selectedRow.opened, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.opened) }}</a-col>
         </a-row>
         <a-row>
           <a-col :span="4">涨幅</a-col>
-          <a-col
-            :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)"
-            :span="8"
-          >{{ quoteAmplitude(selectedRow) }}</a-col>
+          <a-col :class="handleQuotePriceColor(selectedRow.last, selectedRow.presettle)" :span="8">{{ quoteAmplitude(selectedRow) }}</a-col>
           <a-col :span="4">最高</a-col>
-          <a-col
-            :class="handleQuotePriceColor(selectedRow.highest, selectedRow.presettle)"
-            :span="8"
-          >{{ handleNoneValue(selectedRow.highest) }}</a-col>
+          <a-col :class="handleQuotePriceColor(selectedRow.highest, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.highest) }}</a-col>
         </a-row>
         <a-row>
           <a-col :span="4">总量</a-col>
           <a-col :span="8">{{ handleNoneValue(selectedRow.totalvolume) }}</a-col>
           <a-col :span="4">最低</a-col>
-          <a-col
-            :class="handleQuotePriceColor(selectedRow.lowest, selectedRow.presettle)"
-            :span="8"
-          >{{ handleNoneValue(selectedRow.lowest) }}</a-col>
+          <a-col :class="handleQuotePriceColor(selectedRow.lowest, selectedRow.presettle)" :span="8">{{ handleNoneValue(selectedRow.lowest) }}</a-col>
         </a-row>
         <!-- <a-row>
           <a-col :span="4">金额</a-col>
@@ -202,22 +122,20 @@
 import { defineComponent } from '@/common/export/commonTable';
 import { _closeModal } from '@/common/setup/modal/modal';
 import { PropType, ref, watch } from 'vue';
-import { QueryQuoteDayRsp, QueryHistoryTikDatasRsp, CycleType } from '@/services/go/quote/interface';
+import { QueryQuoteDayRsp, QueryHistoryTikDatasRsp } from '@/services/go/quote/interface';
 import { QueryHistoryTikDatas } from '@/services/go/quote';
 import { formatTime } from '@/common/methods';
 import { changeUnit } from '@/utils/qt/common';
 import { ComponentType } from '@/views/market/market-spot/spot_trade_order_transaction/setup';
-import { EchartKline, EchartTime } from '@/common/components/echart';
 import { handleQuotePriceColor, quoteChange, handleNoneValue, quoteAmplitude } from '@/common/setup/table/tableQuote';
-import { useQueryData } from '@/common/setup/request';
 import moment from 'moment';
+import MtpChart from '@/common/components/echarts/index.vue';
 
 export default defineComponent({
   emits: ['cancel', 'update'],
   name: 'stock-exchange',
   components: {
-    EchartKline,
-    EchartTime,
+    MtpChart
   },
   props: {
     selectedRow: {
@@ -231,31 +149,12 @@ export default defineComponent({
   },
   setup(props, context) {
     const { visible, cancel } = _closeModal(context),
-      activeCycleName = ref<string[]>(['time']),
-      activeCycleType = ref<CycleType>(CycleType.time),
-      activeSeriesType = ref<string[]>(['MACD']),
       tradedList = ref<QueryHistoryTikDatasRsp[]>([]);
 
     function watchMore() {
       context.emit('update', ComponentType.marketContent);
     }
 
-    // 周期类型
-    const chartType = [
-      { label: '分时', name: 'time', value: CycleType.time },
-      { label: '1分钟', name: 'minutes', value: CycleType.minutes },
-      { label: '5分钟', name: 'minutes5', value: CycleType.minutes5 },
-      { label: '30分钟', name: 'minutes30', value: CycleType.minutes30 },
-      { label: '60分钟', name: 'minutes60', value: CycleType.minutes60 },
-      { label: '4小时', name: 'Hours4', value: CycleType.Hours4 },
-      { label: '日 K', name: 'days', value: CycleType.days },
-    ];
-
-    // 切换图表周期类型
-    const changeCycleType = (e: { key: string }) => {
-      activeCycleType.value = chartType.find((item) => item.name === e.key)!.value;
-    };
-
     // Tik列表
     const getHistoryTikDatas = (startTime: string, endTime: string) => {
       if (!tradedList.value.length) {
@@ -300,12 +199,7 @@ export default defineComponent({
     return {
       cancel,
       visible,
-      chartType,
       tradedList,
-      CycleType,
-      activeCycleName,
-      activeCycleType,
-      activeSeriesType,
       changeUnit,
       watchMore,
       formatTime,
@@ -313,7 +207,6 @@ export default defineComponent({
       handleNoneValue,
       quoteAmplitude,
       handleQuotePriceColor,
-      changeCycleType,
       getHistoryTikDatas,
     };
   },
@@ -321,5 +214,5 @@ export default defineComponent({
 </script>
 
 <style lang="less">
-@import "./index.less";
+@import './index.less';
 </style>

+ 1 - 0
src/views/market/market-spot/goods-chart/index.vue

@@ -128,6 +128,7 @@ export default defineComponent({
     display: inline-flex;
     align-items: center;
     line-height: 1;
+    color: #acb8c0;
 
     .valNums {
         display: inline-flex;