index.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <template>
  2. <echart-base :options="[options]" :empty="isEmpty" v-model:loading="loading"></echart-base>
  3. </template>
  4. <script lang="ts">
  5. import { defineComponent, ref, watch, PropType, onMounted, computed } from 'vue';
  6. import { QueryTSDataRsp, QueryQuoteDayRsp } from '@/services/go/quote/interface';
  7. import { QueryTSData } from '@/services/go/quote';
  8. import { _debounce, getRangeTime } from '@/utils/time';
  9. import { toDecimalFull } from '@/utils/number';
  10. import EchartBase from '../echart-base/index.vue';
  11. import { handleEchart } from './setup';
  12. import moment from 'moment';
  13. export default defineComponent({
  14. name: 'EchartTime',
  15. emits: ['change'],
  16. components: {
  17. EchartBase,
  18. },
  19. props: {
  20. // 实时行情数据
  21. quoteData: {
  22. type: Object as PropType<QueryQuoteDayRsp>,
  23. required: true,
  24. },
  25. },
  26. setup(props, { emit }) {
  27. const loading = ref(false);
  28. const isEmpty = ref(false);
  29. const historyIndexs: number[] = []; // 行情历史数据中所有非补充数据的索引位置(用于计算均线)
  30. const { chartData, options, updateOptions, initOptions } = handleEchart();
  31. // 处理图表数据
  32. const handleData = (rawData: QueryTSDataRsp): void => {
  33. historyIndexs.length = 0; // 清空数据
  34. const datas: number[] = [],
  35. times: string[] = [],
  36. xAxisTimes: string[] = [],
  37. yestclose = rawData.preSettle,
  38. decimal = rawData.decimalPlace;
  39. // 历史行情日期
  40. rawData.historyDatas.forEach((item, index) => {
  41. const { c, ts } = item;
  42. datas.push(c);
  43. times.push(moment(ts).format('YYYY-MM-DD HH:mm:ss'));
  44. if (!item.f) historyIndexs.push(index);
  45. });
  46. // 时间轴(开盘交易时间)
  47. rawData.runSteps.forEach((item) => {
  48. const { start, end } = item;
  49. const rangeTime = getRangeTime(start, end, 'HH:mm', 'm');
  50. xAxisTimes.push(...rangeTime);
  51. });
  52. chartData.value = {
  53. yestclose,
  54. decimal,
  55. rawTime: times,
  56. ...calcDataLine(datas, yestclose),
  57. source: {
  58. time: xAxisTimes,
  59. data: datas,
  60. ma5: calcMA(datas, 5, decimal),
  61. },
  62. };
  63. };
  64. // 计算图表最高低指标线
  65. const calcDataLine = (datas: number[], yestclose: number) => {
  66. let max = Math.max(...datas); // 取历史行情最高价
  67. let min = Math.min(...datas); // 取历史行情最低价
  68. const last = datas[datas.length - 1], // 历史行情最后收盘价
  69. a = yestclose - min, // 计算收盘价和最低价的差值
  70. b = max - yestclose; // 计算收盘价和最高价的差值
  71. // 比较差值大小
  72. if (a > b) {
  73. max = yestclose + a;
  74. if (last > max) {
  75. const c = last - max;
  76. max += c;
  77. min -= c;
  78. }
  79. } else {
  80. min = min - (b - a);
  81. if (min > last) {
  82. const c = min - last;
  83. max += c;
  84. min -= c;
  85. }
  86. }
  87. return {
  88. max: max + max * 0.01,
  89. min: min - min * 0.01,
  90. };
  91. };
  92. // 计算平均线
  93. const calcMA = (data: number[], count: number, decimal: number) => {
  94. const result: string[] = [];
  95. if (data.length >= count) {
  96. // 均线起始位置
  97. const startIndex = historyIndexs[count - 1];
  98. for (let i = 0; i < data.length; i++) {
  99. if (startIndex === undefined || i < startIndex) {
  100. result.push('-');
  101. } else {
  102. const j = historyIndexs.findIndex((val) => val === i);
  103. // 判断是否补充数据
  104. if (j === -1) {
  105. // 取上个平均值
  106. result.push(result[result.length - 1]);
  107. } else {
  108. // 向后取MA数
  109. const maIndexs = historyIndexs.slice(j - (count - 1), j + 1);
  110. // 计算总价
  111. const total = maIndexs.reduce((sum, val) => sum + data[val], 0);
  112. // 计算均线
  113. const ma = toDecimalFull(total / count, decimal);
  114. result.push(ma);
  115. }
  116. }
  117. }
  118. }
  119. return result;
  120. };
  121. // 更新分时数据
  122. const updateChartData = () => {
  123. const { source, rawTime } = chartData.value,
  124. lastIndex = source.data.length - 1, // 历史行情最后索引位置
  125. lastTime = moment(rawTime[rawTime.length - 1]), // 历史行情最后时间
  126. newTime = moment(props.quoteData.lasttime), // 实时行情最新时间
  127. newPrice = props.quoteData.last; // 实时行情最新价
  128. const cycleMilliseconds = 60 * 1000; // 周期毫秒数
  129. const diffTime = newTime.valueOf() - lastTime.valueOf(); // 计算时间差
  130. // 判断时间差是否大于周期时间
  131. if (diffTime > cycleMilliseconds) {
  132. lastTime.add(cycleMilliseconds, 'ms');
  133. rawTime.push(lastTime.format('YYYY-MM-DD HH:mm:ss')); // 添加历史行情时间
  134. source.data.push(newPrice); // 添加历史行情数据
  135. historyIndexs.push(lastIndex + 1); // 添加历史行情索引
  136. } else {
  137. source.data[lastIndex] = newPrice; // 更新历史行情数据
  138. }
  139. source.ma5 = calcMA(source.data, 5, chartData.value.decimal);
  140. const { min, max } = calcDataLine(source.data, chartData.value.yestclose);
  141. chartData.value.min = min;
  142. chartData.value.max = max;
  143. // 延迟图表更新,减少卡顿
  144. _debounce(() => {
  145. updateOptions();
  146. }, 1000);
  147. };
  148. onMounted(() => {
  149. loading.value = true;
  150. // 查询分时数据
  151. QueryTSData(props.quoteData.goodscode)
  152. .then((res) => {
  153. if (res.historyDatas.length) {
  154. isEmpty.value = false;
  155. handleData(res);
  156. // 调用父级函数查询tik数据 (不合理的逻辑处理,待优化)
  157. emit('change', res.startTime, res.endTime);
  158. } else {
  159. isEmpty.value = true;
  160. }
  161. initOptions();
  162. })
  163. .catch(() => {
  164. isEmpty.value = true;
  165. })
  166. .finally(() => {
  167. loading.value = false;
  168. });
  169. });
  170. watch(
  171. () => props.quoteData.last,
  172. () => {
  173. if (!loading.value) {
  174. updateChartData();
  175. }
  176. }
  177. );
  178. return {
  179. loading,
  180. isEmpty,
  181. options,
  182. };
  183. },
  184. });
  185. </script>