From 5cf5052a518e7f9e3c44a25753997700ae111b80 Mon Sep 17 00:00:00 2001 From: Seven Date: Thu, 18 Dec 2025 02:48:34 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BA=A4=E6=98=93?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E5=8A=9F=E8=83=BD=EF=BC=8C=E9=9B=86=E6=88=90?= =?UTF-8?q?=20lightweight-charts=20=E5=BA=93=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auto-imports.d.ts | 43 ++---- package.json | 1 + pnpm-lock.yaml | 15 ++ src/composables/useTradingView.ts | 134 ++++++++++++++++++ src/utils/helper.ts | 5 + src/utils/index.ts | 1 + src/utils/is.ts | 7 + .../issue/issuing-apply/issue-period.vue | 1 - src/views/market/index.vue | 38 +++++ 9 files changed, 212 insertions(+), 33 deletions(-) create mode 100644 src/composables/useTradingView.ts create mode 100644 src/utils/is.ts diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 0270976..1d66dfe 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -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 readonly injectLocal: UnwrapRef readonly isDefined: UnwrapRef + readonly isFunction: UnwrapRef + readonly isPromise: UnwrapRef readonly isProxy: UnwrapRef readonly isReactive: UnwrapRef readonly isReadonly: UnwrapRef @@ -474,6 +451,7 @@ declare module 'vue' { readonly templateRef: UnwrapRef readonly throttledRef: UnwrapRef readonly throttledWatch: UnwrapRef + readonly timeToLocal: UnwrapRef readonly toRaw: UnwrapRef readonly toReactive: UnwrapRef readonly toRef: UnwrapRef @@ -645,6 +623,7 @@ declare module 'vue' { readonly useToNumber: UnwrapRef readonly useToString: UnwrapRef readonly useToggle: UnwrapRef + readonly useTradingView: UnwrapRef readonly useTransition: UnwrapRef readonly useUrlSearchParams: UnwrapRef readonly useUserMedia: UnwrapRef diff --git a/package.json b/package.json index 370c66e..1046f31 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b1e030..a551d68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/composables/useTradingView.ts b/src/composables/useTradingView.ts new file mode 100644 index 0000000..9cea067 --- /dev/null +++ b/src/composables/useTradingView.ts @@ -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; + +export type TradingViewData = (() => Awaitable) | MaybeRefOrGetter; + +export interface TradingViewOptions { + theme?: "light" | "dark"; + type?: T; + data?: TradingViewData; + weightChartOptions?: MaybeRefOrGetter; + autosize?: boolean; +} + +const initializeOptions: Required = { + 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, options?: TradingViewOptions) { + const opts: Required = mergeWith(initializeOptions, options); + const chart = ref(null); + const chartSeriesDefinition = getChartSeriesDefinition(opts.type); + const chartEl = ref(null); + const series = ref | 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 }; +} diff --git a/src/utils/helper.ts b/src/utils/helper.ts index db504f7..02b0314 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -9,3 +9,8 @@ export function formatBalance(amount: MaybeRefOrGetter, 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; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 7cd62fb..7ad319f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,3 @@ export * from "./ionic-helper"; +export * from "./is"; export * from "./pattern"; diff --git a/src/utils/is.ts b/src/utils/is.ts new file mode 100644 index 0000000..c90126c --- /dev/null +++ b/src/utils/is.ts @@ -0,0 +1,7 @@ +export function isPromise(val: unknown): val is Promise { + return !!val && typeof (val as Promise).then === "function"; +} + +export function isFunction(val: unknown): val is (...args: any[]) => T { + return typeof val === "function"; +} diff --git a/src/views/issue/issuing-apply/issue-period.vue b/src/views/issue/issuing-apply/issue-period.vue index 5b36c7d..287df60 100644 --- a/src/views/issue/issuing-apply/issue-period.vue +++ b/src/views/issue/issuing-apply/issue-period.vue @@ -45,7 +45,6 @@ const schema = toTypedSchema(yup.object({ })); function handleSubmit(values: GenericObject) { - debugger; emit("submit", values.editions); } diff --git a/src/views/market/index.vue b/src/views/market/index.vue index 7261a00..be664f2 100644 --- a/src/views/market/index.vue +++ b/src/views/market/index.vue @@ -11,6 +11,43 @@ const [query] = useResetRef({ const { data, refresh } = safeClient(() => client.api.rwa.subscription.available_editions.get({ query: query.value, })); +const tradingViewContainer = useTemplateRef("tradingViewContainer"); + +const tradingViewData = ref([{ 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, () => { +
Futures Market Content