feat: 修改tradingview的websocket请求方式
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
VITE_API_URL=http://192.168.1.3:9528
|
VITE_API_URL=http://192.168.1.8:9528
|
||||||
VITE_TRADINGVIEW_LIBRARY_URL=http://192.168.1.5:6173
|
VITE_TRADINGVIEW_LIBRARY_URL=http://192.168.1.6:6173
|
||||||
VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com
|
# VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
VITE_API_URL=http://192.168.1.3:9527
|
VITE_API_URL=http://192.168.1.8:9527
|
||||||
VITE_TRADINGVIEW_LIBRARY_URL=http://192.168.1.5:6173
|
VITE_TRADINGVIEW_LIBRARY_URL=http://192.168.1.6:6173
|
||||||
VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com
|
# VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"@elysiajs/eden": "^1.4.5",
|
"@elysiajs/eden": "^1.4.5",
|
||||||
"@ionic/vue": "^8.7.11",
|
"@ionic/vue": "^8.7.11",
|
||||||
"@ionic/vue-router": "^8.7.11",
|
"@ionic/vue-router": "^8.7.11",
|
||||||
"@riwa/api-types": "http://192.168.1.3:9527/api/riwa-api-types-0.0.67.tgz",
|
"@riwa/api-types": "http://192.168.1.8:9527/api/riwa-api-types-0.0.75.tgz",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@vee-validate/yup": "^4.15.1",
|
"@vee-validate/yup": "^4.15.1",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
@@ -91,6 +91,7 @@
|
|||||||
"unplugin-icons": "^22.5.0",
|
"unplugin-icons": "^22.5.0",
|
||||||
"unplugin-vue-components": "^30.0.0",
|
"unplugin-vue-components": "^30.0.0",
|
||||||
"vite": "^7.2.7",
|
"vite": "^7.2.7",
|
||||||
|
"vite-plugin-pwa": "^1.2.0",
|
||||||
"vitest": "^4.0.15",
|
"vitest": "^4.0.15",
|
||||||
"vue-tsc": "^3.1.8"
|
"vue-tsc": "^3.1.8"
|
||||||
},
|
},
|
||||||
|
|||||||
1294
pnpm-lock.yaml
generated
1294
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -66,6 +66,8 @@ export type UserWithdrawOrderData = Treaty.Data<typeof client.api.withdraw.get>[
|
|||||||
|
|
||||||
export type UserWithdrawOrderBody = TreatyQuery<typeof client.api.withdraw.get>;
|
export type UserWithdrawOrderBody = TreatyQuery<typeof client.api.withdraw.get>;
|
||||||
|
|
||||||
|
export type MarketDataStreaming = ReturnType<typeof client.api.market_data.streaming.subscribe>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用版本信息
|
* 应用版本信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,189 +12,186 @@ import type {
|
|||||||
SearchSymbolsCallback,
|
SearchSymbolsCallback,
|
||||||
SubscribeBarsCallback,
|
SubscribeBarsCallback,
|
||||||
} from "#/datafeed-api";
|
} from "#/datafeed-api";
|
||||||
|
import type { MarketDataStreaming } from "@/api/types";
|
||||||
const { VITE_TRADINGVIEW_DATA_API_URL } = useEnv();
|
import { client } from "@/api";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义 TradingView Datafeed 实现
|
* 自定义 TradingView Datafeed 实现
|
||||||
* 用于从后端 API 获取 K 线数据
|
* 用于从后端 API 获取 K 线数据
|
||||||
*/
|
*/
|
||||||
export class RWADatafeed extends Datafeeds.UDFCompatibleDatafeed {
|
export class RWADatafeed extends Datafeeds.UDFCompatibleDatafeed {
|
||||||
private apiUrl: string = VITE_TRADINGVIEW_DATA_API_URL;
|
|
||||||
private updateInterval: number;
|
|
||||||
private subscribers: Map<string, { callback: SubscribeBarsCallback; resolution: ResolutionString }> = new Map();
|
private subscribers: Map<string, { callback: SubscribeBarsCallback; resolution: ResolutionString }> = new Map();
|
||||||
|
private wsConnections: Map<string, MarketDataStreaming> = new Map();
|
||||||
|
|
||||||
constructor(apiUrl: string, updateInterval = 1000) {
|
constructor(apiUrl: string) {
|
||||||
super(apiUrl);
|
super(apiUrl);
|
||||||
this.apiUrl = apiUrl;
|
|
||||||
this.updateInterval = updateInterval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* 初始化配置
|
// * 初始化配置
|
||||||
*/
|
// */
|
||||||
onReady(callback: OnReadyCallback): void {
|
// onReady(callback: OnReadyCallback): void {
|
||||||
console.log("[RWADatafeed]: onReady called");
|
// console.log("[RWADatafeed]: onReady called");
|
||||||
|
|
||||||
const config: DatafeedConfiguration = {
|
// const config: DatafeedConfiguration = {
|
||||||
// 支持的交易所
|
// // 支持的交易所
|
||||||
exchanges: [
|
// exchanges: [
|
||||||
{ value: "", name: "All Exchanges", desc: "" },
|
// { value: "", name: "All Exchanges", desc: "" },
|
||||||
],
|
// ],
|
||||||
// 支持的品种类型
|
// // 支持的品种类型
|
||||||
symbols_types: [
|
// symbols_types: [
|
||||||
{ name: "All types", value: "" },
|
// { name: "All types", value: "" },
|
||||||
{ name: "Stock", value: "stock" },
|
// { name: "Stock", value: "stock" },
|
||||||
{ name: "Crypto", value: "crypto" },
|
// { name: "Crypto", value: "crypto" },
|
||||||
],
|
// ],
|
||||||
// 支持的时间周期
|
// // 支持的时间周期
|
||||||
supported_resolutions: [
|
// supported_resolutions: [
|
||||||
"1" as ResolutionString,
|
// "1" as ResolutionString,
|
||||||
"5" as ResolutionString,
|
// "5" as ResolutionString,
|
||||||
"15" as ResolutionString,
|
// "15" as ResolutionString,
|
||||||
"30" as ResolutionString,
|
// "30" as ResolutionString,
|
||||||
"60" as ResolutionString,
|
// "60" as ResolutionString,
|
||||||
"240" as ResolutionString,
|
// "240" as ResolutionString,
|
||||||
"1D" as ResolutionString,
|
// "1D" as ResolutionString,
|
||||||
"1W" as ResolutionString,
|
// "1W" as ResolutionString,
|
||||||
"1M" as ResolutionString,
|
// "1M" as ResolutionString,
|
||||||
],
|
// ],
|
||||||
supports_marks: false,
|
// supports_marks: false,
|
||||||
supports_timescale_marks: false,
|
// supports_timescale_marks: false,
|
||||||
supports_time: true,
|
// supports_time: true,
|
||||||
};
|
// };
|
||||||
|
|
||||||
setTimeout(() => callback(config), 0);
|
// setTimeout(() => callback(config), 0);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* 搜索品种
|
// * 搜索品种
|
||||||
*/
|
// */
|
||||||
searchSymbols(
|
// searchSymbols(
|
||||||
userInput: string,
|
// userInput: string,
|
||||||
exchange: string,
|
// exchange: string,
|
||||||
symbolType: string,
|
// symbolType: string,
|
||||||
onResult: SearchSymbolsCallback,
|
// onResult: SearchSymbolsCallback,
|
||||||
): void {
|
// ): void {
|
||||||
console.log("[RWADatafeed]: searchSymbols", userInput, exchange, symbolType);
|
// console.log("[RWADatafeed]: searchSymbols", userInput, exchange, symbolType);
|
||||||
|
|
||||||
// TODO: 调用后端 API 搜索品种
|
// // TODO: 调用后端 API 搜索品种
|
||||||
// const results = await client.api.market.search.get({ query: { keyword: userInput, exchange, type: symbolType } })
|
// // const results = await client.api.market.search.get({ query: { keyword: userInput, exchange, type: symbolType } })
|
||||||
|
|
||||||
// 模拟搜索结果
|
// // 模拟搜索结果
|
||||||
const results = [
|
// const results = [
|
||||||
{
|
// {
|
||||||
symbol: "AAPL",
|
// symbol: "AAPL",
|
||||||
full_name: "NASDAQ:AAPL",
|
// full_name: "NASDAQ:AAPL",
|
||||||
description: "Apple Inc.",
|
// description: "Apple Inc.",
|
||||||
exchange: "NASDAQ",
|
// exchange: "NASDAQ",
|
||||||
ticker: "AAPL",
|
// ticker: "AAPL",
|
||||||
type: "stock",
|
// type: "stock",
|
||||||
},
|
// },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
onResult(results);
|
// onResult(results);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* 解析品种信息
|
// * 解析品种信息
|
||||||
*/
|
// */
|
||||||
resolveSymbol(
|
// resolveSymbol(
|
||||||
symbolName: string,
|
// symbolName: string,
|
||||||
onResolve: ResolveCallback,
|
// onResolve: ResolveCallback,
|
||||||
onError: (reason: string) => void,
|
// onError: (reason: string) => void,
|
||||||
): void {
|
// ): void {
|
||||||
console.log("[RWADatafeed]: resolveSymbol", symbolName);
|
// console.log("[RWADatafeed]: resolveSymbol", symbolName);
|
||||||
|
|
||||||
// TODO: 调用后端 API 获取品种信息
|
// // TODO: 调用后端 API 获取品种信息
|
||||||
// const symbolInfo = await client.api.market.symbol[symbolName].get()
|
// // const symbolInfo = await client.api.market.symbol[symbolName].get()
|
||||||
|
|
||||||
// 模拟品种信息
|
// // 模拟品种信息
|
||||||
const symbolInfo: LibrarySymbolInfo = {
|
// const symbolInfo: LibrarySymbolInfo = {
|
||||||
name: symbolName,
|
// name: symbolName,
|
||||||
ticker: symbolName,
|
// ticker: symbolName,
|
||||||
description: symbolName,
|
// description: symbolName,
|
||||||
type: "crypto",
|
// type: "crypto",
|
||||||
session: "24x7",
|
// session: "24x7",
|
||||||
exchange: "Binance",
|
// exchange: "Binance",
|
||||||
listed_exchange: "Binance",
|
// listed_exchange: "Binance",
|
||||||
timezone: "Etc/UTC",
|
// timezone: "Etc/UTC",
|
||||||
format: "price",
|
// format: "price",
|
||||||
pricescale: 100,
|
// pricescale: 100,
|
||||||
minmov: 1,
|
// minmov: 1,
|
||||||
has_intraday: true,
|
// has_intraday: true,
|
||||||
has_daily: true,
|
// has_daily: true,
|
||||||
has_weekly_and_monthly: true,
|
// has_weekly_and_monthly: true,
|
||||||
supported_resolutions: [
|
// supported_resolutions: [
|
||||||
"1" as ResolutionString,
|
// "1" as ResolutionString,
|
||||||
"5" as ResolutionString,
|
// "5" as ResolutionString,
|
||||||
"15" as ResolutionString,
|
// "15" as ResolutionString,
|
||||||
"30" as ResolutionString,
|
// "30" as ResolutionString,
|
||||||
"60" as ResolutionString,
|
// "60" as ResolutionString,
|
||||||
"240" as ResolutionString,
|
// "240" as ResolutionString,
|
||||||
"1D" as ResolutionString,
|
// "1D" as ResolutionString,
|
||||||
"1W" as ResolutionString,
|
// "1W" as ResolutionString,
|
||||||
"1M" as ResolutionString,
|
// "1M" as ResolutionString,
|
||||||
],
|
// ],
|
||||||
intraday_multipliers: ["1", "5", "15", "30", "60", "240"],
|
// intraday_multipliers: ["1", "5", "15", "30", "60", "240"],
|
||||||
volume_precision: 2,
|
// volume_precision: 2,
|
||||||
data_status: "streaming",
|
// data_status: "streaming",
|
||||||
};
|
// };
|
||||||
|
|
||||||
setTimeout(() => onResolve(symbolInfo), 0);
|
// setTimeout(() => onResolve(symbolInfo), 0);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* 获取历史 K 线数据
|
// * 获取历史 K 线数据
|
||||||
*/
|
// */
|
||||||
getBars(
|
// getBars(
|
||||||
symbolInfo: LibrarySymbolInfo,
|
// symbolInfo: LibrarySymbolInfo,
|
||||||
resolution: ResolutionString,
|
// resolution: ResolutionString,
|
||||||
periodParams: PeriodParams,
|
// periodParams: PeriodParams,
|
||||||
onResult: HistoryCallback,
|
// onResult: HistoryCallback,
|
||||||
onError: (reason: string) => void,
|
// onError: (reason: string) => void,
|
||||||
): void {
|
// ): void {
|
||||||
const { from, to, firstDataRequest } = periodParams;
|
// const { from, to, firstDataRequest } = periodParams;
|
||||||
console.log("[RWADatafeed]: getBars", {
|
// console.log("[RWADatafeed]: getBars", {
|
||||||
symbol: symbolInfo.name,
|
|
||||||
resolution,
|
|
||||||
from: new Date(from * 1000).toISOString(),
|
|
||||||
to: new Date(to * 1000).toISOString(),
|
|
||||||
firstDataRequest,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: 调用后端 API 获取 K 线数据
|
|
||||||
// client.api.market.kline.get({
|
|
||||||
// query: {
|
|
||||||
// symbol: symbolInfo.name,
|
// symbol: symbolInfo.name,
|
||||||
// resolution,
|
// resolution,
|
||||||
// from,
|
// from: new Date(from * 1000).toISOString(),
|
||||||
// to,
|
// to: new Date(to * 1000).toISOString(),
|
||||||
|
// firstDataRequest,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // TODO: 调用后端 API 获取 K 线数据
|
||||||
|
// // client.api.market.kline.get({
|
||||||
|
// // query: {
|
||||||
|
// // symbol: symbolInfo.name,
|
||||||
|
// // resolution,
|
||||||
|
// // from,
|
||||||
|
// // to,
|
||||||
|
// // }
|
||||||
|
// // }).then(response => {
|
||||||
|
// // const bars = response.data.map(item => ({
|
||||||
|
// // time: item.time * 1000,
|
||||||
|
// // open: item.open,
|
||||||
|
// // high: item.high,
|
||||||
|
// // low: item.low,
|
||||||
|
// // close: item.close,
|
||||||
|
// // volume: item.volume,
|
||||||
|
// // }))
|
||||||
|
// // onResult(bars, { noData: bars.length === 0 })
|
||||||
|
// // }).catch(error => {
|
||||||
|
// // onError(error.message)
|
||||||
|
// // })
|
||||||
|
|
||||||
|
// // 模拟 K 线数据
|
||||||
|
// const bars: Bar[] = this.generateMockBars(from, to, resolution);
|
||||||
|
|
||||||
|
// if (bars.length === 0) {
|
||||||
|
// onResult([], { noData: true });
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// onResult(bars, { noData: false });
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
// }).then(response => {
|
|
||||||
// const bars = response.data.map(item => ({
|
|
||||||
// time: item.time * 1000,
|
|
||||||
// open: item.open,
|
|
||||||
// high: item.high,
|
|
||||||
// low: item.low,
|
|
||||||
// close: item.close,
|
|
||||||
// volume: item.volume,
|
|
||||||
// }))
|
|
||||||
// onResult(bars, { noData: bars.length === 0 })
|
|
||||||
// }).catch(error => {
|
|
||||||
// onError(error.message)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// 模拟 K 线数据
|
|
||||||
const bars: Bar[] = this.generateMockBars(from, to, resolution);
|
|
||||||
|
|
||||||
if (bars.length === 0) {
|
|
||||||
onResult([], { noData: true });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
onResult(bars, { noData: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅实时数据
|
* 订阅实时数据
|
||||||
@@ -203,96 +200,45 @@ export class RWADatafeed extends Datafeeds.UDFCompatibleDatafeed {
|
|||||||
symbolInfo: LibrarySymbolInfo,
|
symbolInfo: LibrarySymbolInfo,
|
||||||
resolution: ResolutionString,
|
resolution: ResolutionString,
|
||||||
onTick: SubscribeBarsCallback,
|
onTick: SubscribeBarsCallback,
|
||||||
listenerGuid: string,
|
subscriberUID: string,
|
||||||
onResetCacheNeededCallback: () => void,
|
onResetCacheNeededCallback: () => void,
|
||||||
): void {
|
): void {
|
||||||
console.log("[RWADatafeed]: subscribeBars", {
|
console.log("[RWADatafeed]: subscribeBars", {
|
||||||
symbol: symbolInfo.name,
|
symbol: symbolInfo.name,
|
||||||
resolution,
|
resolution,
|
||||||
listenerGuid,
|
subscriberUID,
|
||||||
|
});
|
||||||
|
this.subscribers.set(subscriberUID, { callback: onTick, resolution });
|
||||||
|
const ws = client.api.market_data.streaming.subscribe();
|
||||||
|
this.wsConnections.set(subscriberUID, ws);
|
||||||
|
ws.on("open", () => {
|
||||||
|
ws.send({ type: "subscribe", subscriberUID, symbol: symbolInfo.name, resolution });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.subscribers.set(listenerGuid, { callback: onTick, resolution });
|
ws.subscribe((message) => {
|
||||||
|
if (message.data.type !== "bar")
|
||||||
// TODO: 建立 WebSocket 连接获取实时数据
|
return;
|
||||||
// const ws = new WebSocket(`${this.apiUrl}/ws/kline?symbol=${symbolInfo.name}&resolution=${resolution}`)
|
const bar: Bar = {
|
||||||
// ws.onmessage = (event) => {
|
time: message.data.bar.time,
|
||||||
// const data = JSON.parse(event.data)
|
open: message.data.bar.open,
|
||||||
// const bar: Bar = {
|
high: message.data.bar.high,
|
||||||
// time: data.time * 1000,
|
low: message.data.bar.low,
|
||||||
// open: data.open,
|
close: message.data.bar.close,
|
||||||
// high: data.high,
|
volume: message.data.bar.volume,
|
||||||
// low: data.low,
|
};
|
||||||
// close: data.close,
|
onTick(bar);
|
||||||
// volume: data.volume,
|
});
|
||||||
// }
|
|
||||||
// onTick(bar)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消订阅实时数据
|
* 取消订阅实时数据
|
||||||
*/
|
*/
|
||||||
unsubscribeBars(listenerGuid: string): void {
|
unsubscribeBars(subscriberUID: string): void {
|
||||||
console.log("[RWADatafeed]: unsubscribeBars", listenerGuid);
|
console.log("[RWADatafeed]: unsubscribeBars", subscriberUID);
|
||||||
this.subscribers.delete(listenerGuid);
|
const ws = this.wsConnections.get(subscriberUID);
|
||||||
|
if (ws) {
|
||||||
// TODO: 关闭 WebSocket 连接
|
ws.close();
|
||||||
// ws.close()
|
this.wsConnections.delete(subscriberUID);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成模拟 K 线数据(用于测试)
|
|
||||||
*/
|
|
||||||
private generateMockBars(from: number, to: number, resolution: ResolutionString): Bar[] {
|
|
||||||
const bars: Bar[] = [];
|
|
||||||
const interval = this.getIntervalInSeconds(resolution);
|
|
||||||
let currentTime = from * 1000;
|
|
||||||
let lastClose = 100 + Math.random() * 10;
|
|
||||||
|
|
||||||
while (currentTime <= to * 1000) {
|
|
||||||
const open = lastClose;
|
|
||||||
const change = (Math.random() - 0.5) * 5;
|
|
||||||
const close = open + change;
|
|
||||||
const high = Math.max(open, close) + Math.random() * 2;
|
|
||||||
const low = Math.min(open, close) - Math.random() * 2;
|
|
||||||
|
|
||||||
bars.push({
|
|
||||||
time: currentTime,
|
|
||||||
open,
|
|
||||||
high,
|
|
||||||
low,
|
|
||||||
close,
|
|
||||||
volume: Math.random() * 1000000,
|
|
||||||
});
|
|
||||||
|
|
||||||
lastClose = close;
|
|
||||||
currentTime += interval * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bars;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取时间周期对应的秒数
|
|
||||||
*/
|
|
||||||
private getIntervalInSeconds(resolution: ResolutionString): number {
|
|
||||||
const match = resolution.match(/^(\d+)([DWMH]?)$/);
|
|
||||||
if (!match)
|
|
||||||
return 60;
|
|
||||||
|
|
||||||
const value = Number.parseInt(match[1]);
|
|
||||||
const unit = match[2];
|
|
||||||
|
|
||||||
switch (unit) {
|
|
||||||
case "D":
|
|
||||||
return value * 86400;
|
|
||||||
case "W":
|
|
||||||
return value * 604800;
|
|
||||||
case "M":
|
|
||||||
return value * 2592000;
|
|
||||||
default:
|
|
||||||
return value * 60;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import type { CSSProperties } from "vue";
|
|||||||
import { mergeWith } from "lodash-es";
|
import { mergeWith } from "lodash-es";
|
||||||
import { RWADatafeed } from "./datafeed";
|
import { RWADatafeed } from "./datafeed";
|
||||||
|
|
||||||
const { VITE_TRADINGVIEW_LIBRARY_URL, VITE_TRADINGVIEW_DATA_API_URL } = useEnv();
|
const { VITE_TRADINGVIEW_LIBRARY_URL, VITE_API_URL } = useEnv();
|
||||||
|
|
||||||
const datafeed = new RWADatafeed(VITE_TRADINGVIEW_DATA_API_URL, 60000);
|
const datafeed = new RWADatafeed(`${VITE_API_URL}/api/udf`);
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
container: "tradingview_chart_container",
|
container: "tradingview_chart_container",
|
||||||
@@ -15,7 +15,7 @@ const defaultOptions = {
|
|||||||
library_path: `${VITE_TRADINGVIEW_LIBRARY_URL}/charting_library/`,
|
library_path: `${VITE_TRADINGVIEW_LIBRARY_URL}/charting_library/`,
|
||||||
datafeed,
|
datafeed,
|
||||||
interval: "1D" as ResolutionString,
|
interval: "1D" as ResolutionString,
|
||||||
symbol: "AAPL",
|
symbol: "",
|
||||||
debug: true,
|
debug: true,
|
||||||
autosize: true,
|
autosize: true,
|
||||||
// 禁用移动端不友好的功能
|
// 禁用移动端不友好的功能
|
||||||
@@ -71,6 +71,10 @@ const defaultOptions = {
|
|||||||
export const TradingViewChart = defineComponent({
|
export const TradingViewChart = defineComponent({
|
||||||
name: "TradingViewChart",
|
name: "TradingViewChart",
|
||||||
props: {
|
props: {
|
||||||
|
symbol: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
width: {
|
width: {
|
||||||
type: [String, Number] as PropType<string | number>,
|
type: [String, Number] as PropType<string | number>,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -102,6 +106,7 @@ export const TradingViewChart = defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const opts = mergeWith({}, defaultOptions, props.options);
|
const opts = mergeWith({}, defaultOptions, props.options);
|
||||||
|
opts.symbol = props.symbol;
|
||||||
|
|
||||||
const styles = computed(() => {
|
const styles = computed(() => {
|
||||||
const style: CSSProperties = {};
|
const style: CSSProperties = {};
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import TradeWay from "./components/trade-way.vue";
|
|||||||
|
|
||||||
const mode = ref<"buy" | "sell">("buy");
|
const mode = ref<"buy" | "sell">("buy");
|
||||||
const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = {
|
const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = {
|
||||||
symbol: "BTC/USDT",
|
|
||||||
disabled_features: [
|
disabled_features: [
|
||||||
"create_volume_indicator_by_default",
|
"create_volume_indicator_by_default",
|
||||||
],
|
],
|
||||||
@@ -40,7 +39,7 @@ const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = {
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content :fullscreen="true">
|
<ion-content :fullscreen="true">
|
||||||
<TradingViewChart class="mb-5" height="300px" :options="tradingviewOptions" />
|
<TradingViewChart class="mb-5" height="300px" symbol="BTCUSD" :options="tradingviewOptions" />
|
||||||
|
|
||||||
<div class="grid grid-cols-5 px-4">
|
<div class="grid grid-cols-5 px-4">
|
||||||
<div class="col-span-3 space-y-2">
|
<div class="col-span-3 space-y-2">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import icons from "unplugin-icons/vite";
|
|||||||
import { IonicResolver } from "unplugin-vue-components/resolvers";
|
import { IonicResolver } from "unplugin-vue-components/resolvers";
|
||||||
import components from "unplugin-vue-components/vite";
|
import components from "unplugin-vue-components/vite";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
import { generateVersion } from "./scripts/build";
|
import { generateVersion } from "./scripts/build";
|
||||||
|
|
||||||
dotenv.config({ path: `.env.${process.env.NODE_ENV}` });
|
dotenv.config({ path: `.env.${process.env.NODE_ENV}` });
|
||||||
@@ -39,6 +40,46 @@ export default defineConfig({
|
|||||||
directoryAsNamespace: true,
|
directoryAsNamespace: true,
|
||||||
resolvers: [IonicResolver(), iconsResolver({ prefix: "i" })],
|
resolvers: [IonicResolver(), iconsResolver({ prefix: "i" })],
|
||||||
}),
|
}),
|
||||||
|
VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
includeAssets: ["favicon.svg", "apple-touch-icon.png"],
|
||||||
|
manifest: {
|
||||||
|
name: "Riwa",
|
||||||
|
short_name: "Riwa",
|
||||||
|
description: "Riwa Ionic App",
|
||||||
|
theme_color: "#ffffff",
|
||||||
|
background_color: "#ffffff",
|
||||||
|
display: "standalone",
|
||||||
|
orientation: "portrait",
|
||||||
|
scope: "/",
|
||||||
|
start_url: "/",
|
||||||
|
id: "/",
|
||||||
|
prefer_related_applications: false,
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/pwa-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/pwa-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/pwa-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "any maskable",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
workbox: {
|
||||||
|
globPatterns: ["**/*.{js,css,html,ico,png,svg,woff2}"],
|
||||||
|
navigateFallback: "/index.html",
|
||||||
|
navigateFallbackDenylist: [/^\/api/],
|
||||||
|
},
|
||||||
|
}),
|
||||||
generateVersion({
|
generateVersion({
|
||||||
version: appVersion,
|
version: appVersion,
|
||||||
}),
|
}),
|
||||||
@@ -57,6 +98,7 @@ export default defineConfig({
|
|||||||
"/api": {
|
"/api": {
|
||||||
target: process.env.VITE_API_URL,
|
target: process.env.VITE_API_URL,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user