feat: 添加交易视图功能,集成 lightweight-charts 库,优化数据展示
This commit is contained in:
134
src/composables/useTradingView.ts
Normal file
134
src/composables/useTradingView.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import type { Awaitable } from "@vueuse/core";
|
||||
import type { ChartOptions, DeepPartial, IChartApi, ISeriesApi, OhlcData, SeriesOptionsMap, SeriesType } from "lightweight-charts";
|
||||
import { AreaSeries, BarSeries, BaselineSeries, CandlestickSeries, ColorType, createChart, HistogramSeries, LineSeries } from "lightweight-charts";
|
||||
import { mergeWith } from "lodash-es";
|
||||
|
||||
export type Series = "Area" | "Bar" | "Baseline" | "Candlestick" | "Histogram" | "Line";
|
||||
|
||||
export type TData = OhlcData;
|
||||
|
||||
export type WeightChartOptions = DeepPartial<ChartOptions>;
|
||||
|
||||
export type TradingViewData = (() => Awaitable<TData[]>) | MaybeRefOrGetter<TData[]>;
|
||||
|
||||
export interface TradingViewOptions<T = Series> {
|
||||
theme?: "light" | "dark";
|
||||
type?: T;
|
||||
data?: TradingViewData;
|
||||
weightChartOptions?: MaybeRefOrGetter<WeightChartOptions>;
|
||||
autosize?: boolean;
|
||||
}
|
||||
|
||||
const initializeOptions: Required<TradingViewOptions> = {
|
||||
theme: "dark",
|
||||
type: "Candlestick",
|
||||
data: [],
|
||||
weightChartOptions: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
layout: {
|
||||
textColor: "white",
|
||||
background: { type: ColorType.Solid, color: "#000000" },
|
||||
},
|
||||
},
|
||||
autosize: true,
|
||||
};
|
||||
|
||||
function getChartSeriesDefinition(type: Series) {
|
||||
switch (type) {
|
||||
case "Area":
|
||||
return AreaSeries;
|
||||
case "Bar":
|
||||
return BarSeries;
|
||||
case "Baseline":
|
||||
return BaselineSeries;
|
||||
case "Candlestick":
|
||||
return CandlestickSeries;
|
||||
case "Histogram":
|
||||
return HistogramSeries;
|
||||
case "Line":
|
||||
return LineSeries;
|
||||
default:
|
||||
return CandlestickSeries;
|
||||
}
|
||||
}
|
||||
|
||||
export function useTradingView(target: MaybeRefOrGetter<HTMLElement | null>, options?: TradingViewOptions) {
|
||||
const opts: Required<TradingViewOptions> = mergeWith(initializeOptions, options);
|
||||
const chart = ref<IChartApi | null>(null);
|
||||
const chartSeriesDefinition = getChartSeriesDefinition(opts.type);
|
||||
const chartEl = ref<HTMLElement | null>(null);
|
||||
const series = ref<ISeriesApi<SeriesType> | null>(null);
|
||||
|
||||
function fitContent() {
|
||||
chart.value?.timeScale().fitContent();
|
||||
}
|
||||
|
||||
function resize() {
|
||||
if (!chart.value || !chartEl.value)
|
||||
return;
|
||||
const dimensions = chartEl.value.getBoundingClientRect();
|
||||
chart.value.resize(dimensions.width, dimensions.height);
|
||||
}
|
||||
|
||||
function setData(data: TradingViewData) {
|
||||
if (isRef(data)) {
|
||||
series.value?.setData(toValue(data));
|
||||
}
|
||||
else if (isFunction(data)) {
|
||||
const result = data();
|
||||
if (isPromise(result)) {
|
||||
result.then((data) => {
|
||||
series.value?.setData(data);
|
||||
fitContent();
|
||||
}).catch((err) => {
|
||||
console.error("Failed to load trading view data:", err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
series.value?.setData(result);
|
||||
}
|
||||
}
|
||||
else {
|
||||
series.value?.setData(toValue(data));
|
||||
}
|
||||
}
|
||||
|
||||
tryOnMounted(() => {
|
||||
const el = unrefElement(target);
|
||||
if (!el)
|
||||
return;
|
||||
chartEl.value = el;
|
||||
chart.value = createChart(el, toValue(opts.weightChartOptions));
|
||||
series.value = chart.value.addSeries(chartSeriesDefinition);
|
||||
setData(opts.data);
|
||||
fitContent();
|
||||
|
||||
if (isRef(opts.data)) {
|
||||
watch(opts.data, (newData) => {
|
||||
setData(newData);
|
||||
}, { deep: true });
|
||||
}
|
||||
|
||||
if (opts.autosize) {
|
||||
useTimeoutFn(() => {
|
||||
resize();
|
||||
}, 0);
|
||||
window.addEventListener("resize", resize);
|
||||
}
|
||||
});
|
||||
|
||||
tryOnUnmounted(() => {
|
||||
chart.value?.remove();
|
||||
chart.value = null;
|
||||
window.removeEventListener("resize", resize);
|
||||
});
|
||||
|
||||
watch(() => toValue(opts.weightChartOptions), (newOptions) => {
|
||||
if (!chart.value)
|
||||
return;
|
||||
chart.value.applyOptions(newOptions);
|
||||
}, { deep: true });
|
||||
|
||||
return { chart, fitContent, resize };
|
||||
}
|
||||
@@ -9,3 +9,8 @@ export function formatBalance(amount: MaybeRefOrGetter<number | string>, locale:
|
||||
|
||||
return value.toLocaleString(locale, { minimumFractionDigits: 0, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
export function timeToLocal(originalTime: number) {
|
||||
const d = new Date(originalTime * 1000);
|
||||
return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()) / 1000;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./ionic-helper";
|
||||
export * from "./is";
|
||||
export * from "./pattern";
|
||||
|
||||
7
src/utils/is.ts
Normal file
7
src/utils/is.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function isPromise<T = any>(val: unknown): val is Promise<T> {
|
||||
return !!val && typeof (val as Promise<T>).then === "function";
|
||||
}
|
||||
|
||||
export function isFunction<T = any>(val: unknown): val is (...args: any[]) => T {
|
||||
return typeof val === "function";
|
||||
}
|
||||
@@ -45,7 +45,6 @@ const schema = toTypedSchema(yup.object({
|
||||
}));
|
||||
|
||||
function handleSubmit(values: GenericObject) {
|
||||
debugger;
|
||||
emit("submit", values.editions);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -11,6 +11,43 @@ const [query] = useResetRef<AvailableSubscriptionBody>({
|
||||
const { data, refresh } = safeClient(() => client.api.rwa.subscription.available_editions.get({
|
||||
query: query.value,
|
||||
}));
|
||||
const tradingViewContainer = useTemplateRef<HTMLDivElement>("tradingViewContainer");
|
||||
|
||||
const tradingViewData = ref<any[]>([{ open: 10, high: 10.63, low: 9.49, close: 9.55, time: 1642427876 }, { open: 9.55, high: 10.30, low: 9.42, close: 9.94, time: 1642514276 }, { open: 9.94, high: 10.17, low: 9.92, close: 9.78, time: 1642600676 }, { open: 9.78, high: 10.59, low: 9.18, close: 9.51, time: 1642687076 }, { open: 9.51, high: 10.46, low: 9.10, close: 10.17, time: 1642773476 }, { open: 10.17, high: 10.96, low: 10.16, close: 10.47, time: 1642859876 }, { open: 10.47, high: 11.39, low: 10.40, close: 10.81, time: 1642946276 }, { open: 10.81, high: 11.60, low: 10.30, close: 10.75, time: 1643032676 }, { open: 10.75, high: 11.60, low: 10.49, close: 10.93, time: 1643119076 }, { open: 10.93, high: 11.53, low: 10.76, close: 10.96, time: 1643205476 }]);
|
||||
|
||||
setInterval(() => {
|
||||
const lastData = tradingViewData.value[tradingViewData.value.length - 1];
|
||||
const lastClose = lastData.close;
|
||||
const lastTime = lastData.time;
|
||||
|
||||
const changePercent = (Math.random() - 0.5) * 0.04;
|
||||
const open = lastClose;
|
||||
const close = open * (1 + changePercent);
|
||||
|
||||
const volatility = Math.random() * 0.02;
|
||||
const high = Math.max(open, close) * (1 + volatility);
|
||||
const low = Math.min(open, close) * (1 - volatility);
|
||||
|
||||
const newTime = lastTime + 60;
|
||||
|
||||
const newData = {
|
||||
open: Number(open.toFixed(2)),
|
||||
high: Number(high.toFixed(2)),
|
||||
low: Number(low.toFixed(2)),
|
||||
close: Number(close.toFixed(2)),
|
||||
time: newTime,
|
||||
};
|
||||
|
||||
tradingViewData.value.push(newData);
|
||||
|
||||
if (tradingViewData.value.length > 50) {
|
||||
tradingViewData.value.shift();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const { resize } = useTradingView(tradingViewContainer, {
|
||||
data: tradingViewData,
|
||||
});
|
||||
|
||||
watch(query, () => {
|
||||
refresh();
|
||||
@@ -32,6 +69,7 @@ watch(query, () => {
|
||||
<ui-tab-pane name="spot" title="Digital Products" class="py-5">
|
||||
<Category v-model="query!.categoryId" />
|
||||
<ion-content :scroll-y="true" />
|
||||
<div ref="tradingViewContainer" class="w-full h-80" />
|
||||
</ui-tab-pane>
|
||||
<ui-tab-pane name="futures" title="Tokenized Products" class="py-5">
|
||||
<div>Futures Market Content</div>
|
||||
|
||||
Reference in New Issue
Block a user