feat: 添加交易视图功能,集成 lightweight-charts 库,优化数据展示

This commit is contained in:
2025-12-18 02:48:34 +07:00
parent 8bf6c06889
commit 5cf5052a51
9 changed files with 212 additions and 33 deletions

43
auto-imports.d.ts vendored
View File

@@ -10,7 +10,6 @@ declare global {
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
const asyncComputed: typeof import('@vueuse/core').asyncComputed
const autoResetRef: typeof import('@vueuse/core').autoResetRef
const avatarGroupInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').avatarGroupInjectionKey
const computed: typeof import('vue').computed
const computedAsync: typeof import('@vueuse/core').computedAsync
const computedEager: typeof import('@vueuse/core').computedEager
@@ -34,23 +33,11 @@ declare global {
const debouncedWatch: typeof import('@vueuse/core').debouncedWatch
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const defineLocale: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/defineLocale.js').defineLocale
const defineShortcuts: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.js').defineShortcuts
const defineStore: typeof import('pinia').defineStore
const eagerComputed: typeof import('@vueuse/core').eagerComputed
const effectScope: typeof import('vue').effectScope
const emailPattern: typeof import('./src/utils/pattern').emailPattern
const extendLocale: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/defineLocale.js').extendLocale
const extendRef: typeof import('@vueuse/core').extendRef
const extractShortcuts: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.js').extractShortcuts
const fieldGroupInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').fieldGroupInjectionKey
const formBusInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formBusInjectionKey
const formErrorsInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formErrorsInjectionKey
const formFieldInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formFieldInjectionKey
const formInputsInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formInputsInjectionKey
const formLoadingInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formLoadingInjectionKey
const formOptionsInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formOptionsInjectionKey
const formStateInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formStateInjectionKey
const formatBalance: typeof import('./src/utils/helper').formatBalance
const getActivePinia: typeof import('pinia').getActivePinia
const getCurrentInstance: typeof import('vue').getCurrentInstance
@@ -60,15 +47,14 @@ declare global {
const ignorableWatch: typeof import('@vueuse/core').ignorableWatch
const inject: typeof import('vue').inject
const injectLocal: typeof import('@vueuse/core').injectLocal
const inputIdInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').inputIdInjectionKey
const isDefined: typeof import('@vueuse/core').isDefined
const isFunction: typeof import('./src/utils/is').isFunction
const isPromise: typeof import('./src/utils/is').isPromise
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const kbdKeysMap: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useKbd.js').kbdKeysMap
const localeContextInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useLocale.js').localeContextInjectionKey
const makeDestructurable: typeof import('@vueuse/core').makeDestructurable
const mapActions: typeof import('pinia').mapActions
const mapGetters: typeof import('pinia').mapGetters
@@ -100,7 +86,6 @@ declare global {
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const pausableWatch: typeof import('@vueuse/core').pausableWatch
const portalTargetInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/usePortal.js').portalTargetInjectionKey
const provide: typeof import('vue').provide
const provideLocal: typeof import('@vueuse/core').provideLocal
const reactify: typeof import('@vueuse/core').reactify
@@ -130,12 +115,12 @@ declare global {
const templateRef: typeof import('@vueuse/core').templateRef
const throttledRef: typeof import('@vueuse/core').throttledRef
const throttledWatch: typeof import('@vueuse/core').throttledWatch
const timeToLocal: typeof import('./src/utils/helper').timeToLocal
const toRaw: typeof import('vue').toRaw
const toReactive: typeof import('@vueuse/core').toReactive
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const toastMaxInjectionKey: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useToast.js').toastMaxInjectionKey
const triggerRef: typeof import('vue').triggerRef
const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount
const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount
@@ -147,7 +132,6 @@ declare global {
const until: typeof import('@vueuse/core').until
const useActiveElement: typeof import('@vueuse/core').useActiveElement
const useAnimate: typeof import('@vueuse/core').useAnimate
const useAppConfig: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/vue/composables/useAppConfig.js').useAppConfig
const useArrayDifference: typeof import('@vueuse/core').useArrayDifference
const useArrayEvery: typeof import('@vueuse/core').useArrayEvery
const useArrayFilter: typeof import('@vueuse/core').useArrayFilter
@@ -164,7 +148,6 @@ declare global {
const useAsyncState: typeof import('@vueuse/core').useAsyncState
const useAttrs: typeof import('vue').useAttrs
const useAuth: typeof import('./src/composables/useAuth').useAuth
const useAvatarGroup: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').useAvatarGroup
const useBase64: typeof import('@vueuse/core').useBase64
const useBattery: typeof import('@vueuse/core').useBattery
const useBluetooth: typeof import('@vueuse/core').useBluetooth
@@ -176,9 +159,7 @@ declare global {
const useClipboardItems: typeof import('@vueuse/core').useClipboardItems
const useCloned: typeof import('@vueuse/core').useCloned
const useColorMode: typeof import('@vueuse/core').useColorMode
const useComponentIcons: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.js').useComponentIcons
const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog
const useContentSearch: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useContentSearch.js').useContentSearch
const useCountdown: typeof import('@vueuse/core').useCountdown
const useCounter: typeof import('@vueuse/core').useCounter
const useCssModule: typeof import('vue').useCssModule
@@ -210,13 +191,10 @@ declare global {
const useEyeDropper: typeof import('@vueuse/core').useEyeDropper
const useFavicon: typeof import('@vueuse/core').useFavicon
const useFetch: typeof import('@vueuse/core').useFetch
const useFieldGroup: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').useFieldGroup
const useFileDialog: typeof import('@vueuse/core').useFileDialog
const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess
const useFileUpload: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload.js').useFileUpload
const useFocus: typeof import('@vueuse/core').useFocus
const useFocusWithin: typeof import('@vueuse/core').useFocusWithin
const useFormField: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').useFormField
const useFps: typeof import('@vueuse/core').useFps
const useFullscreen: typeof import('@vueuse/core').useFullscreen
const useGamepad: typeof import('@vueuse/core').useGamepad
@@ -229,12 +207,10 @@ declare global {
const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver
const useInterval: typeof import('@vueuse/core').useInterval
const useIntervalFn: typeof import('@vueuse/core').useIntervalFn
const useKbd: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useKbd.js').useKbd
const useKeyModifier: typeof import('@vueuse/core').useKeyModifier
const useLastChanged: typeof import('@vueuse/core').useLastChanged
const useLink: typeof import('vue-router').useLink
const useLocalStorage: typeof import('@vueuse/core').useLocalStorage
const useLocale: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useLocale.js').useLocale
const useMagicKeys: typeof import('@vueuse/core').useMagicKeys
const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory
const useMediaControls: typeof import('@vueuse/core').useMediaControls
@@ -253,7 +229,6 @@ declare global {
const useObjectUrl: typeof import('@vueuse/core').useObjectUrl
const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination
const useOnline: typeof import('@vueuse/core').useOnline
const useOverlay: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useOverlay.js').useOverlay
const usePageLeave: typeof import('@vueuse/core').usePageLeave
const useParallax: typeof import('@vueuse/core').useParallax
const useParentElement: typeof import('@vueuse/core').useParentElement
@@ -262,7 +237,6 @@ declare global {
const usePointer: typeof import('@vueuse/core').usePointer
const usePointerLock: typeof import('@vueuse/core').usePointerLock
const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe
const usePortal: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/usePortal.js').usePortal
const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme
const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast
const usePreferredDark: typeof import('@vueuse/core').usePreferredDark
@@ -274,7 +248,6 @@ declare global {
const useRafFn: typeof import('@vueuse/core').useRafFn
const useRefHistory: typeof import('@vueuse/core').useRefHistory
const useResetRef: typeof import('./src/composables/useResetRef').useResetRef
const useResizable: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useResizable.js').useResizable
const useResizeObserver: typeof import('@vueuse/core').useResizeObserver
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
@@ -284,7 +257,6 @@ declare global {
const useScriptTag: typeof import('@vueuse/core').useScriptTag
const useScroll: typeof import('@vueuse/core').useScroll
const useScrollLock: typeof import('@vueuse/core').useScrollLock
const useScrollspy: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useScrollspy.js').useScrollspy
const useSessionStorage: typeof import('@vueuse/core').useSessionStorage
const useShare: typeof import('@vueuse/core').useShare
const useSlots: typeof import('vue').useSlots
@@ -314,8 +286,8 @@ declare global {
const useTitle: typeof import('@vueuse/core').useTitle
const useToNumber: typeof import('@vueuse/core').useToNumber
const useToString: typeof import('@vueuse/core').useToString
const useToast: typeof import('./node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_change-case@5.4.4_embla-carousel@8.6.0_typescript@5_5175cd019aaffaee0a62fe5f1000cc73/node_modules/@nuxt/ui/dist/runtime/composables/useToast.js').useToast
const useToggle: typeof import('@vueuse/core').useToggle
const useTradingView: typeof import('./src/composables/useTradingView').useTradingView
const useTransition: typeof import('@vueuse/core').useTransition
const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams
const useUserMedia: typeof import('@vueuse/core').useUserMedia
@@ -358,6 +330,9 @@ declare global {
export type { QRScanResult } from './src/composables/useQRScanner'
import('./src/composables/useQRScanner')
// @ts-ignore
export type { Series, TData, WeightChartOptions, TradingViewData, TradingViewOptions } from './src/composables/useTradingView'
import('./src/composables/useTradingView')
// @ts-ignore
export type { PageInstance, InputInstance, ModalInstance, FormInstance } from './src/utils/ionic-helper'
import('./src/utils/ionic-helper')
}
@@ -409,6 +384,8 @@ declare module 'vue' {
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isFunction: UnwrapRef<typeof import('./src/utils/is')['isFunction']>
readonly isPromise: UnwrapRef<typeof import('./src/utils/is')['isPromise']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
@@ -474,6 +451,7 @@ declare module 'vue' {
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly timeToLocal: UnwrapRef<typeof import('./src/utils/helper')['timeToLocal']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
@@ -645,6 +623,7 @@ declare module 'vue' {
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTradingView: UnwrapRef<typeof import('./src/composables/useTradingView')['useTradingView']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>

View File

@@ -35,6 +35,7 @@
"@vueuse/router": "^14.1.0",
"better-auth": "^1.4.6",
"ionicons": "^8.0.13",
"lightweight-charts": "^5.1.0",
"lodash-es": "^4.17.21",
"pinia": "^3.0.4",
"tailwindcss": "^4.1.18",

15
pnpm-lock.yaml generated
View File

@@ -65,6 +65,9 @@ importers:
ionicons:
specifier: ^8.0.13
version: 8.0.13
lightweight-charts:
specifier: ^5.1.0
version: 5.1.0
lodash-es:
specifier: ^4.17.21
version: 4.17.21
@@ -2832,6 +2835,9 @@ packages:
resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
engines: {'0': node >=0.6.0}
fancy-canvas@2.1.0:
resolution: {integrity: sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==}
fast-decode-uri-component@1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@@ -3401,6 +3407,9 @@ packages:
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
engines: {node: '>= 12.0.0'}
lightweight-charts@5.1.0:
resolution: {integrity: sha512-jEAYR4ODYeyNZcWUigsoLTl52rbPmgXnvd5FLIv/ZoA/2sSDw63YKnef8n4yhzum7W926yHeFwlm7ididKb7YQ==}
lint-staged@16.2.7:
resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==}
engines: {node: '>=20.17'}
@@ -7954,6 +7963,8 @@ snapshots:
extsprintf@1.3.0: {}
fancy-canvas@2.1.0: {}
fast-decode-uri-component@1.0.1: {}
fast-deep-equal@3.1.3: {}
@@ -8510,6 +8521,10 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.2
lightningcss-win32-x64-msvc: 1.30.2
lightweight-charts@5.1.0:
dependencies:
fancy-canvas: 2.1.0
lint-staged@16.2.7:
dependencies:
commander: 14.0.2

View 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 };
}

View File

@@ -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;
}

View File

@@ -1,2 +1,3 @@
export * from "./ionic-helper";
export * from "./is";
export * from "./pattern";

7
src/utils/is.ts Normal file
View 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";
}

View File

@@ -45,7 +45,6 @@ const schema = toTypedSchema(yup.object({
}));
function handleSubmit(values: GenericObject) {
debugger;
emit("submit", values.editions);
}
</script>

View File

@@ -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>