tvChart.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. <template>
  2. <view
  3. :prop="config"
  4. :change:prop="tvchart.changeConfig"
  5. id="tradingview_10798345"
  6. ></view>
  7. </template>
  8. <script>
  9. import { mapState } from "vuex";
  10. import Option from "@/api/option";
  11. export default {
  12. props: {
  13. // 商品
  14. serveSymbolName: {
  15. required: true,
  16. type: String,
  17. },
  18. // 精度
  19. serveInterval: {
  20. required: true,
  21. type: [String, Number],
  22. },
  23. // 精度组
  24. serveResolutions: {
  25. default: () => ["5", "15", "30", "60", "1D", "1W", "1M"],
  26. required: false,
  27. type: Array,
  28. },
  29. contract: {
  30. default: false,
  31. type: Boolean,
  32. required: false,
  33. },
  34. },
  35. watch: {
  36. serveInterval(n) {
  37. this.config = {
  38. type: "changeInterval",
  39. interval: n,
  40. _v: Math.random(),
  41. };
  42. },
  43. serveSymbolName(n) {
  44. this.config = {
  45. type: "changeSymbol",
  46. symbolName: n,
  47. _v: Math.random(),
  48. };
  49. },
  50. },
  51. data() {
  52. return {
  53. config: {},
  54. socketMsg: "",
  55. };
  56. },
  57. computed: {
  58. ...mapState({
  59. tlang: "lang",
  60. tTheme: "theme",
  61. webSocket: "ws",
  62. webSocket1: "ws1",
  63. }),
  64. ws() {
  65. return this.contract ? this.webSocket1 : this.webSocket;
  66. },
  67. },
  68. methods: {
  69. initConfig() {
  70. return {
  71. type: "initChart",
  72. symbolName: this.serveSymbolName,
  73. interval: this.serveInterval,
  74. resolutions: this.serveResolutions,
  75. theme: this.tTheme,
  76. lang: this.tlang,
  77. contract: this.contract,
  78. };
  79. },
  80. // 接受图表的通知
  81. onchartEmit(obj) {
  82. switch (obj.type) {
  83. case "changeSymbol":
  84. this.getData(obj.data, obj.funName);
  85. break;
  86. case "changeInterval":
  87. this.$emit("changeInterval", obj.resolution);
  88. break;
  89. case "sub":
  90. this.toSub(obj.data);
  91. break;
  92. case "unsub":
  93. this.toUnSub(obj.data);
  94. break;
  95. default:
  96. break;
  97. }
  98. },
  99. // 订阅
  100. toSub(data) {
  101. this.socketMsg = data.msg;
  102. this.ws.send(data);
  103. },
  104. // 取消订阅
  105. toUnSub(data) {
  106. this.ws.send(data);
  107. },
  108. getData(data, name) {
  109. Option.getKline(data).then((res) => {
  110. this.config = {};
  111. setTimeout(() => {
  112. this.config = {
  113. type: "changeList",
  114. funName: name,
  115. data: res,
  116. _v: Math.random(),
  117. };
  118. }, 60);
  119. });
  120. },
  121. socketGetData() {
  122. this.ws.on("message", (res) => {
  123. this.config = {
  124. type: "addPoint",
  125. data: res,
  126. };
  127. });
  128. },
  129. },
  130. created() {
  131. this.config = this.initConfig();
  132. this.socketGetData();
  133. },
  134. destroyed() {
  135. this.toUnSub(this.socketMsg);
  136. },
  137. };
  138. </script>
  139. <script module="tvchart" lang="renderjs">
  140. import Datafeed from "@/plugins/datafeed.js";
  141. import tvStyle from "@/plugins/tvStyle.js";
  142. import { $get } from "@/api/serve/webaxios.js";
  143. let exchangeAjax = {
  144. getKline:'/option/getKline',
  145. getSymbol(name) {
  146. return name.split("/").join("").toLowerCase();
  147. },
  148. msg:'Kline_'
  149. };
  150. let contractAjax = {
  151. getKline:'/contract/getKline',
  152. getSymbol(name) {
  153. return name.split("/")[0];
  154. },
  155. msg:'swapKline_'
  156. };
  157. export default {
  158. data() {
  159. return {
  160. datafeed: undefined,
  161. page: 1,
  162. onRealtimeCallback: undefined,
  163. TView: undefined,
  164. interval: 5,
  165. symbolName: '',
  166. theme: 'light',
  167. lang: 'cn',
  168. resolutions: ["5", "15", "30", "60", "1D", "1W", "1M"],
  169. isLoad:false,
  170. funMap:{},
  171. webContract:false
  172. };
  173. },
  174. computed: {
  175. symbol() {
  176. return this.symbolName;
  177. },
  178. msg() {
  179. return `${this.ajaxTv.msg}${this.ajaxTv.getSymbol(this.symbol)}_${this.resolution(this.interval)}`;
  180. },
  181. // 图表语言映射
  182. chartLang() {
  183. switch (this.lang) {
  184. case "cn":
  185. return 'zh';
  186. case "tw":
  187. return 'zh_TW';
  188. case "tr":
  189. return 'tr';
  190. case "en":
  191. return 'en';
  192. default:
  193. return 'en';
  194. }
  195. },
  196. ajaxTv() {
  197. return this.webContract ? contractAjax : exchangeAjax;
  198. },
  199. },
  200. watch: {
  201. symbol(n) {
  202. if (this.TView) this.TView.setSymbol(n, this.interval);
  203. },
  204. msg(n, o) {
  205. this.page = 1;
  206. if (o) {
  207. this.unSub(o);
  208. }
  209. },
  210. interval(n) {
  211. if (this.TView) this.TView.activeChart().setResolution(n);
  212. },
  213. },
  214. methods: {
  215. getMap(data) {
  216. return {
  217. time: data.id * 1000,
  218. close: data.close*1,
  219. open: data.open*1,
  220. high: data.high*1,
  221. low: data.low*1,
  222. volume: data.vol*1,
  223. };
  224. },
  225. // 获取传给后台的精度
  226. resolution(resolution) {
  227. let T = "";
  228. if (isNaN(resolution * 1)) {
  229. T = resolution
  230. .replace("D", "day")
  231. .replace("W", "week")
  232. .replace("M", "mon");
  233. } else {
  234. if (resolution > 60) {
  235. T = Math.floor(resolution / 60) + "hours";
  236. } else {
  237. T = resolution + "min";
  238. }
  239. }
  240. return T;
  241. },
  242. // 获取数据
  243. getBars(
  244. symbolInfo,
  245. resolution,
  246. rangeStartDate,
  247. rangeEndDate,
  248. onLoadedCallback
  249. ) {
  250. let page = this.page > 3 ? 3 : this.page;
  251. let data = {
  252. symbol: this.ajaxTv.getSymbol(symbolInfo.name),
  253. period: this.resolution(resolution),
  254. form: rangeStartDate,
  255. to: rangeEndDate,
  256. size: page * 200,
  257. };
  258. this.page++;
  259. // this.$emit("changeInterval", resolution);
  260. // 不存在商品
  261. if (!this.symbol) {
  262. onLoadedCallback([]);
  263. return;
  264. }
  265. this.isLoad = true
  266. // 获取商品
  267. $get(this.ajaxTv.getKline,data).then(res=>{
  268. this.isLoad = false
  269. this.$ownerInstance.callMethod('onchartEmit', {
  270. type: 'changeInterval',
  271. resolution
  272. })
  273. let arr = res.data.data.map((item) => {
  274. return this.getMap(item);
  275. });
  276. onLoadedCallback(arr);
  277. this.sub();
  278. setTimeout(() => {
  279. onLoadedCallback([]);
  280. }, 60);
  281. })
  282. },
  283. // 订阅消息
  284. sub() {
  285. this.$ownerInstance.callMethod('onchartEmit', {
  286. type: 'sub',
  287. data:{
  288. cmd: "sub",
  289. msg: this.msg
  290. }
  291. })
  292. },
  293. // 取消订阅
  294. unSub(name) {
  295. this.$ownerInstance.callMethod('onchartEmit', {
  296. type: 'unsub',
  297. data:{
  298. cmd: "unsub",
  299. msg: name,
  300. }
  301. })
  302. },
  303. // tradingview触发单条数据订阅
  304. subscribeBars(
  305. symbolInfo,
  306. resolution,
  307. onRealtimeCallback,
  308. subscriberUID,
  309. onResetCacheNeededCallback
  310. ) {
  311. this.onRealtimeCallback = onRealtimeCallback;
  312. if (!this.symbol) {
  313. setTimeout(() => {
  314. onResetCacheNeededCallback();
  315. }, 100);
  316. }
  317. },
  318. initDataFeed() {
  319. this.datafeed = new Datafeed(this);
  320. },
  321. initTradingView() {
  322. let TradingView = window.TradingView;
  323. this.TView = new TradingView.widget({
  324. fullscreen: false,
  325. autosize: true,
  326. interval: this.interval,
  327. timezone: "Asia/Shanghai",
  328. theme: "Dark", // 自定义主题
  329. // style: "1",
  330. library_path: "./static/chart_main/",
  331. datafeed: this.datafeed,
  332. // datafeed: {},
  333. locale: this.chartLang,
  334. // toolbar_bg: this.theme == "light" ? "#fff" : "#2b2b37",
  335. toolbar_bg: this.theme == "light" ? "#fff" : "#292944",
  336. enable_publishing: false,
  337. withdateranges: false,
  338. hide_side_toolbar: false,
  339. allow_symbol_change: true,
  340. show_popup_button: true,
  341. hideideas: true,
  342. studies_overrides: {},
  343. container_id: "tradingview_10798345",
  344. disabled_features: [
  345. "header_symbol_search",
  346. "header_compare",
  347. "control_bar",
  348. "main_series_scale_menu",
  349. "volume_force_overlay",
  350. "header_resolutions",
  351. "legend_context_menu",
  352. "symbol_search_hot_key",
  353. "symbol_info",
  354. // "edit_buttons_in_legend",
  355. "pane_context_menu",
  356. ],
  357. overrides: tvStyle[this.theme],
  358. custom_css_url: this.theme == "light" ? "light-chart.css" : "chart.css",
  359. });
  360. this.TView.onChartReady(() => {
  361. this.TView.chart().createStudy("MACD", false, false);
  362. });
  363. },
  364. // 初始化
  365. initPage() {
  366. this.initDataFeed();
  367. this.initTradingView();
  368. // this.socketGetData();
  369. },
  370. changeConfig(n, o) {
  371. this.onserveEmit(n)
  372. },
  373. // 接受应用通知
  374. onserveEmit(obj) {
  375. switch (obj.type) {
  376. // 变更商品
  377. case 'changeSymbol':
  378. this.symbolName = obj.symbolName
  379. break;
  380. // 变更精度
  381. case 'changeInterval':
  382. if(this.isLoad) return;
  383. this.interval = obj.interval;
  384. break;
  385. // 变更精度组
  386. case 'resetResolutions':
  387. this.resolutions = obj.resolutions
  388. break;
  389. // 初始化图表
  390. case 'initChart':
  391. this.symbolName = obj.symbolName
  392. this.interval = obj.interval
  393. this.resolutions = obj.resolutions
  394. this.theme = obj.theme
  395. this.lang = obj.lang
  396. this.webContract = obj.contract;
  397. if(localStorage.tvTheme!=this.theme){
  398. localStorage.removeItem('tradingview.chartproperties')
  399. }
  400. localStorage.setItem('tvTheme',this.theme)
  401. break;
  402. // 追加节点
  403. case 'addPoint':
  404. let { data, sub } = obj.data;
  405. if (sub == this.msg&&this.onRealtimeCallback) {
  406. this.onRealtimeCallback(this.getMap(data));
  407. }
  408. break;
  409. // 销毁
  410. case 'destroyed':
  411. // this.unSub(this.msg);
  412. break;
  413. default:
  414. break;
  415. }
  416. }
  417. },
  418. mounted() {
  419. this.onserveEmit(this.config)
  420. if (typeof echarts === 'function') {
  421. this.initPage()
  422. } else {
  423. const script = document.createElement('script')
  424. script.src = './static/chart_main/charting_library.min.js'
  425. script.onload = this.initPage.bind(this)
  426. document.head.appendChild(script)
  427. }
  428. },
  429. destroyed() {
  430. console.log('unSub')
  431. this.unSub(this.msg);
  432. },
  433. };
  434. </script>