feat: init

This commit is contained in:
2026-01-16 13:01:40 +07:00
commit 5b6477559f
34 changed files with 13714 additions and 0 deletions

6
.browserslistrc Normal file
View File

@@ -0,0 +1,6 @@
Chrome >=79
ChromeAndroid >=79
Firefox >=70
Edge >=79
Safari >=14
iOS >=14

3
.env.development Normal file
View File

@@ -0,0 +1,3 @@
VITE_API_URL=https://riwa-api.riwsan1.com
VITE_TRADINGVIEW_LIBRARY_URL=https://dev.riwsan1.com
# VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com

3
.env.production Normal file
View File

@@ -0,0 +1,3 @@
VITE_API_URL=https://riwa-api.riwsan1.com
VITE_TRADINGVIEW_LIBRARY_URL=https://dev.riwsan1.com
# VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com

3
.env.test Normal file
View File

@@ -0,0 +1,3 @@
VITE_API_URL=http://192.168.1.7:9527
VITE_TRADINGVIEW_LIBRARY_URL=https://dev.riwsan1.com
# VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com

117
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,117 @@
Riwa-Ionic 项目提示词
你是一个专业的移动应用开发助手,负责协助开发基于 Ionic + Vue 3 + TypeScript + Capacitor 的跨平台移动应用项目。
项目参考:
欧易OKX 移动端应用
样式简洁现代,符合移动端用户体验
使用 Ionic 组件库,确保跨平台一致性
集成 Capacitor 以调用原生功能(摄像头、文件系统等)
开发工作流:
如果我向你提一个新需求或一个 bug 修复请求,你有不懂的地方,先主动提问,直到你完全理解需求为止。
项目技术栈
核心框架:
Vue 3.5.25 (Composition API with <script lang="ts" setup>)
Ionic Vue 8.7.11 (移动端 UI 框架)
TypeScript 5.9.3
Capacitor 8.0.0 (原生功能桥接)
TailwindCSS 4.1.18 (实用优先的 CSS 框架)
TradingView Charting Library CL v29.4.0 (金融图表)
原生能力:
@capacitor/ios 8.0.0 (iOS 平台支持)
@capacitor/app 8.0.0 (应用生命周期)
@capacitor/haptics 8.0.0 (触觉反馈)
@capacitor/keyboard 8.0.0 (键盘控制)
@capacitor/status-bar 8.0.0 (状态栏控制)
API 集成:
@elysiajs/eden 1.4.5 (类型安全的 API 客户端)
开发规范
1. 代码风格
使用 Composition API 和 <script setup lang="ts"> 语法
遵循 @antfu/eslint-config 规则
使用 TypeScript 严格模式,避免 any 类型
组件命名使用 PascalCase文件名使用 PascalCase 或 kebab-case
使用 @/ 作为 src 目录的别名
使用 #/ 作为类型定义的别名
颜色主题使用ion-color 变量,支持深色模式和浅色模式
2. 组件结构
使用 Ionic 组件库IonPage, IonHeader, IonContent, IonToolbar 等)
页面组件放在 views 目录
可复用组件放在 components 目录
每个页面组件必须包裹在 <IonPage> 中
3. 路由配置
使用 @ionic/vue-router 创建路由
采用嵌套路由结构,主要页面在 /layout 下
懒加载页面组件以优化性能
路由配置在 router/index.ts
支持路由守卫 (router/guard.ts)
4. 样式管理
TailwindCSS 4.x 作为主要样式框架,集成 Ionic CSS Variables
支持深色模式和浅色模式切换
TailwindCSS 配置扩展了 Ionic 颜色变量
主题文件theme/variables.css, theme/ionic.css
5. 自动导入配置
Vue 组合式函数自动导入composables、utils、store 目录
Vue 生态自动导入vue、vue-router、@vueuse/core、vue-i18n、pinia
组件自动导入IonicResolver、@iconify 图标
支持目录作为命名空间
6. 原生功能集成
通过 Capacitor 插件访问原生功能
配置文件capacitor.config.ts
iOS 构建目录ios
使用前检查平台兼容性
7. API 集成模式
使用 @elysiajs/eden 进行类型安全的 API 调用
项目专用 API 类型定义:@riwa/api-types
API 配置集中在 src/api/index.ts
枚举定义统一在 src/api/enum.ts
8. 认证系统
集成 better-auth 提供现代认证功能
认证逻辑封装在 src/auth/index.ts
支持登录、注册组件复用
认证状态管理通过 useAuth composable
9. 图表集成
使用 TradingView Charting Library 集成金融图表
图表组件封装在 src/tradingview/index.tsx
TradingView 官方文档https://www.tradingview.com/charting-library-docs/latest/getting_started/
开发任务指引
当收到开发任务时,请:
分析需求:明确是页面开发、组件开发、功能集成还是原生能力调用
选择合适的 Ionic 组件:优先使用 Ionic 提供的 UI 组件
遵循 Vue 3 最佳实践:使用 Composition API、响应式 API
类型安全:为所有函数、组件 props、emit 事件定义 TypeScript 类型
移动端优化:考虑触摸交互、性能优化、响应式布局
测试覆盖:为关键功能编写单元测试
代码检查:确保代码通过 ESLint 检查
注意事项
项目使用 pnpm 作为包管理器,不要使用 npm 或 yarn
支持 iOS 平台,确保新功能在 iOS 上可用
遵循 Ionic 设计规范,保持移动端原生体验
使用 ionicons 和 @iconify 图标库(自动导入支持)
使用 Vite 的快速热更新开发体验
API 类型来自私有包 @riwa/api-types需要特定访问权限
表单验证使用 vee-validate + yup 组合
Capacitor 配置本地开发服务器地址为 http://localhost:5173
国际化支持中文和英文,配置文件在 src/locales/
样式使用 TailwindCSS 4.x + Ionic CSS Variables 混合模式
函数风格使用function关键字定义一般不要使用箭头函数
如果有任何不明确的地方,随时提问以获取更多信息
如果代码有错误,请先执行 pnpm run lint:fix + 修改/新创建的文件路径来自动修复代码问题
如果有工具链文档地址,请参考相关文档进行开发
外部资源文档:
Ionic Vue 文档https://ionicframework.com/docs/
Vue 3 文档https://cn.vuejs.org/
TypeScript 文档https://www.typescriptlang.org/
Capacitor 文档https://capacitorjs.com/docs/getting-started
TailwindCSS 文档https://tailwindcss.com/docs/installation/using-vite
TradingView Charting Library 文档https://www.tradingview.com/widget-docs/getting-started/#getting-started
PWA 文档https://vite-pwa-org.netlify.app/

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# Specifies intentionally untracked files to ignore when using Git
# http://git-scm.com/docs/gitignore
*~
*.sw[mnpcod]
.tmp
*.tmp
*.tmp.*
*.sublime-project
*.sublime-workspace
.DS_Store
Thumbs.db
UserInterfaceState.xcuserstate
$RECYCLE.BIN/
*.log
log.txt
npm-debug.log*
/.idea
/.ionic
/.sass-cache
/.sourcemaps
/.versions
/.vscode/*
!/.vscode/extensions.json
/coverage
/dist
/dist-test
/dev-dist
/node_modules
/platforms
/plugins
/www

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
ignore-workspace-root-check=true
link-workspace-packages=true
trust-checks=false

6
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"recommendations": [
"Webnative.webnative",
"Vue.volar"
]
}

629
auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,629 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
const asyncComputed: typeof import('@vueuse/core').asyncComputed
const autoResetRef: typeof import('@vueuse/core').autoResetRef
const computed: typeof import('vue').computed
const computedAsync: typeof import('@vueuse/core').computedAsync
const computedEager: typeof import('@vueuse/core').computedEager
const computedInject: typeof import('@vueuse/core').computedInject
const computedWithControl: typeof import('@vueuse/core').computedWithControl
const controlledComputed: typeof import('@vueuse/core').controlledComputed
const controlledRef: typeof import('@vueuse/core').controlledRef
const createApp: typeof import('vue').createApp
const createEventHook: typeof import('@vueuse/core').createEventHook
const createGlobalState: typeof import('@vueuse/core').createGlobalState
const createInjectionState: typeof import('@vueuse/core').createInjectionState
const createPinia: typeof import('pinia').createPinia
const createReactiveFn: typeof import('@vueuse/core').createReactiveFn
const createRef: typeof import('@vueuse/core').createRef
const createReusableTemplate: typeof import('@vueuse/core').createReusableTemplate
const createSharedComposable: typeof import('@vueuse/core').createSharedComposable
const createTemplatePromise: typeof import('@vueuse/core').createTemplatePromise
const createUnrefFn: typeof import('@vueuse/core').createUnrefFn
const customRef: typeof import('vue').customRef
const debouncedRef: typeof import('@vueuse/core').debouncedRef
const debouncedWatch: typeof import('@vueuse/core').debouncedWatch
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const defineStore: typeof import('pinia').defineStore
const eagerComputed: typeof import('@vueuse/core').eagerComputed
const effectScope: typeof import('vue').effectScope
const extendRef: typeof import('@vueuse/core').extendRef
const getActivePinia: typeof import('pinia').getActivePinia
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const ignorableWatch: typeof import('@vueuse/core').ignorableWatch
const inject: typeof import('vue').inject
const injectLocal: typeof import('@vueuse/core').injectLocal
const isDefined: typeof import('@vueuse/core').isDefined
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 makeDestructurable: typeof import('@vueuse/core').makeDestructurable
const mapActions: typeof import('pinia').mapActions
const mapGetters: typeof import('pinia').mapGetters
const mapState: typeof import('pinia').mapState
const mapStores: typeof import('pinia').mapStores
const mapWritableState: typeof import('pinia').mapWritableState
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onClickOutside: typeof import('@vueuse/core').onClickOutside
const onDeactivated: typeof import('vue').onDeactivated
const onElementRemoval: typeof import('@vueuse/core').onElementRemoval
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onKeyStroke: typeof import('@vueuse/core').onKeyStroke
const onLongPress: typeof import('@vueuse/core').onLongPress
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onStartTyping: typeof import('@vueuse/core').onStartTyping
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const pausableWatch: typeof import('@vueuse/core').pausableWatch
const provide: typeof import('vue').provide
const provideLocal: typeof import('@vueuse/core').provideLocal
const reactify: typeof import('@vueuse/core').reactify
const reactifyObject: typeof import('@vueuse/core').reactifyObject
const reactive: typeof import('vue').reactive
const reactiveComputed: typeof import('@vueuse/core').reactiveComputed
const reactiveOmit: typeof import('@vueuse/core').reactiveOmit
const reactivePick: typeof import('@vueuse/core').reactivePick
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const refAutoReset: typeof import('@vueuse/core').refAutoReset
const refDebounced: typeof import('@vueuse/core').refDebounced
const refDefault: typeof import('@vueuse/core').refDefault
const refManualReset: typeof import('@vueuse/core').refManualReset
const refThrottled: typeof import('@vueuse/core').refThrottled
const refWithControl: typeof import('@vueuse/core').refWithControl
const resolveComponent: typeof import('vue').resolveComponent
const resolveRef: typeof import('@vueuse/core').resolveRef
const setActivePinia: typeof import('pinia').setActivePinia
const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const storeToRefs: typeof import('pinia').storeToRefs
const syncRef: typeof import('@vueuse/core').syncRef
const syncRefs: typeof import('@vueuse/core').syncRefs
const templateRef: typeof import('@vueuse/core').templateRef
const throttledRef: typeof import('@vueuse/core').throttledRef
const throttledWatch: typeof import('@vueuse/core').throttledWatch
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 triggerRef: typeof import('vue').triggerRef
const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount
const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount
const tryOnMounted: typeof import('@vueuse/core').tryOnMounted
const tryOnScopeDispose: typeof import('@vueuse/core').tryOnScopeDispose
const tryOnUnmounted: typeof import('@vueuse/core').tryOnUnmounted
const unref: typeof import('vue').unref
const unrefElement: typeof import('@vueuse/core').unrefElement
const until: typeof import('@vueuse/core').until
const useActiveElement: typeof import('@vueuse/core').useActiveElement
const useAnimate: typeof import('@vueuse/core').useAnimate
const useArrayDifference: typeof import('@vueuse/core').useArrayDifference
const useArrayEvery: typeof import('@vueuse/core').useArrayEvery
const useArrayFilter: typeof import('@vueuse/core').useArrayFilter
const useArrayFind: typeof import('@vueuse/core').useArrayFind
const useArrayFindIndex: typeof import('@vueuse/core').useArrayFindIndex
const useArrayFindLast: typeof import('@vueuse/core').useArrayFindLast
const useArrayIncludes: typeof import('@vueuse/core').useArrayIncludes
const useArrayJoin: typeof import('@vueuse/core').useArrayJoin
const useArrayMap: typeof import('@vueuse/core').useArrayMap
const useArrayReduce: typeof import('@vueuse/core').useArrayReduce
const useArraySome: typeof import('@vueuse/core').useArraySome
const useArrayUnique: typeof import('@vueuse/core').useArrayUnique
const useAsyncQueue: typeof import('@vueuse/core').useAsyncQueue
const useAsyncState: typeof import('@vueuse/core').useAsyncState
const useAttrs: typeof import('vue').useAttrs
const useBase64: typeof import('@vueuse/core').useBase64
const useBattery: typeof import('@vueuse/core').useBattery
const useBluetooth: typeof import('@vueuse/core').useBluetooth
const useBreakpoints: typeof import('@vueuse/core').useBreakpoints
const useBroadcastChannel: typeof import('@vueuse/core').useBroadcastChannel
const useBrowserLocation: typeof import('@vueuse/core').useBrowserLocation
const useCached: typeof import('@vueuse/core').useCached
const useClipboard: typeof import('@vueuse/core').useClipboard
const useClipboardItems: typeof import('@vueuse/core').useClipboardItems
const useCloned: typeof import('@vueuse/core').useCloned
const useColorMode: typeof import('@vueuse/core').useColorMode
const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog
const useCountdown: typeof import('@vueuse/core').useCountdown
const useCounter: typeof import('@vueuse/core').useCounter
const useCssModule: typeof import('vue').useCssModule
const useCssVar: typeof import('@vueuse/core').useCssVar
const useCssVars: typeof import('vue').useCssVars
const useCurrentElement: typeof import('@vueuse/core').useCurrentElement
const useCycleList: typeof import('@vueuse/core').useCycleList
const useDark: typeof import('@vueuse/core').useDark
const useDateFormat: typeof import('@vueuse/core').useDateFormat
const useDebounce: typeof import('@vueuse/core').useDebounce
const useDebounceFn: typeof import('@vueuse/core').useDebounceFn
const useDebouncedRefHistory: typeof import('@vueuse/core').useDebouncedRefHistory
const useDeviceMotion: typeof import('@vueuse/core').useDeviceMotion
const useDeviceOrientation: typeof import('@vueuse/core').useDeviceOrientation
const useDevicePixelRatio: typeof import('@vueuse/core').useDevicePixelRatio
const useDevicesList: typeof import('@vueuse/core').useDevicesList
const useDisplayMedia: typeof import('@vueuse/core').useDisplayMedia
const useDocumentVisibility: typeof import('@vueuse/core').useDocumentVisibility
const useDraggable: typeof import('@vueuse/core').useDraggable
const useDropZone: typeof import('@vueuse/core').useDropZone
const useElementBounding: typeof import('@vueuse/core').useElementBounding
const useElementByPoint: typeof import('@vueuse/core').useElementByPoint
const useElementHover: typeof import('@vueuse/core').useElementHover
const useElementSize: typeof import('@vueuse/core').useElementSize
const useElementVisibility: typeof import('@vueuse/core').useElementVisibility
const useEventBus: typeof import('@vueuse/core').useEventBus
const useEventListener: typeof import('@vueuse/core').useEventListener
const useEventSource: typeof import('@vueuse/core').useEventSource
const useEyeDropper: typeof import('@vueuse/core').useEyeDropper
const useFavicon: typeof import('@vueuse/core').useFavicon
const useFetch: typeof import('@vueuse/core').useFetch
const useFileDialog: typeof import('@vueuse/core').useFileDialog
const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess
const useFocus: typeof import('@vueuse/core').useFocus
const useFocusWithin: typeof import('@vueuse/core').useFocusWithin
const useFps: typeof import('@vueuse/core').useFps
const useFullscreen: typeof import('@vueuse/core').useFullscreen
const useGamepad: typeof import('@vueuse/core').useGamepad
const useGeolocation: typeof import('@vueuse/core').useGeolocation
const useI18n: typeof import('vue-i18n').useI18n
const useId: typeof import('vue').useId
const useIdle: typeof import('@vueuse/core').useIdle
const useImage: typeof import('@vueuse/core').useImage
const useInfiniteScroll: typeof import('@vueuse/core').useInfiniteScroll
const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver
const useInterval: typeof import('@vueuse/core').useInterval
const useIntervalFn: typeof import('@vueuse/core').useIntervalFn
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 useMagicKeys: typeof import('@vueuse/core').useMagicKeys
const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory
const useMediaControls: typeof import('@vueuse/core').useMediaControls
const useMediaQuery: typeof import('@vueuse/core').useMediaQuery
const useMemoize: typeof import('@vueuse/core').useMemoize
const useMemory: typeof import('@vueuse/core').useMemory
const useModel: typeof import('vue').useModel
const useMounted: typeof import('@vueuse/core').useMounted
const useMouse: typeof import('@vueuse/core').useMouse
const useMouseInElement: typeof import('@vueuse/core').useMouseInElement
const useMousePressed: typeof import('@vueuse/core').useMousePressed
const useMutationObserver: typeof import('@vueuse/core').useMutationObserver
const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage
const useNetwork: typeof import('@vueuse/core').useNetwork
const useNow: typeof import('@vueuse/core').useNow
const useObjectUrl: typeof import('@vueuse/core').useObjectUrl
const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination
const useOnline: typeof import('@vueuse/core').useOnline
const usePageLeave: typeof import('@vueuse/core').usePageLeave
const useParallax: typeof import('@vueuse/core').useParallax
const useParentElement: typeof import('@vueuse/core').useParentElement
const usePerformanceObserver: typeof import('@vueuse/core').usePerformanceObserver
const usePermission: typeof import('@vueuse/core').usePermission
const usePointer: typeof import('@vueuse/core').usePointer
const usePointerLock: typeof import('@vueuse/core').usePointerLock
const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe
const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme
const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast
const usePreferredDark: typeof import('@vueuse/core').usePreferredDark
const usePreferredLanguages: typeof import('@vueuse/core').usePreferredLanguages
const usePreferredReducedMotion: typeof import('@vueuse/core').usePreferredReducedMotion
const usePreferredReducedTransparency: typeof import('@vueuse/core').usePreferredReducedTransparency
const usePrevious: typeof import('@vueuse/core').usePrevious
const useRafFn: typeof import('@vueuse/core').useRafFn
const useRefHistory: typeof import('@vueuse/core').useRefHistory
const useResizeObserver: typeof import('@vueuse/core').useResizeObserver
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
const useSSRWidth: typeof import('@vueuse/core').useSSRWidth
const useScreenOrientation: typeof import('@vueuse/core').useScreenOrientation
const useScreenSafeArea: typeof import('@vueuse/core').useScreenSafeArea
const useScriptTag: typeof import('@vueuse/core').useScriptTag
const useScroll: typeof import('@vueuse/core').useScroll
const useScrollLock: typeof import('@vueuse/core').useScrollLock
const useSessionStorage: typeof import('@vueuse/core').useSessionStorage
const useShare: typeof import('@vueuse/core').useShare
const useSlots: typeof import('vue').useSlots
const useSorted: typeof import('@vueuse/core').useSorted
const useSpeechRecognition: typeof import('@vueuse/core').useSpeechRecognition
const useSpeechSynthesis: typeof import('@vueuse/core').useSpeechSynthesis
const useStepper: typeof import('@vueuse/core').useStepper
const useStorage: typeof import('@vueuse/core').useStorage
const useStorageAsync: typeof import('@vueuse/core').useStorageAsync
const useStyleTag: typeof import('@vueuse/core').useStyleTag
const useSupported: typeof import('@vueuse/core').useSupported
const useSwipe: typeof import('@vueuse/core').useSwipe
const useTemplateRef: typeof import('vue').useTemplateRef
const useTemplateRefsList: typeof import('@vueuse/core').useTemplateRefsList
const useTextDirection: typeof import('@vueuse/core').useTextDirection
const useTextSelection: typeof import('@vueuse/core').useTextSelection
const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize
const useThrottle: typeof import('@vueuse/core').useThrottle
const useThrottleFn: typeof import('@vueuse/core').useThrottleFn
const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory
const useTimeAgo: typeof import('@vueuse/core').useTimeAgo
const useTimeAgoIntl: typeof import('@vueuse/core').useTimeAgoIntl
const useTimeout: typeof import('@vueuse/core').useTimeout
const useTimeoutFn: typeof import('@vueuse/core').useTimeoutFn
const useTimeoutPoll: typeof import('@vueuse/core').useTimeoutPoll
const useTimestamp: typeof import('@vueuse/core').useTimestamp
const useTitle: typeof import('@vueuse/core').useTitle
const useToNumber: typeof import('@vueuse/core').useToNumber
const useToString: typeof import('@vueuse/core').useToString
const useToggle: typeof import('@vueuse/core').useToggle
const useTransition: typeof import('@vueuse/core').useTransition
const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams
const useUserMedia: typeof import('@vueuse/core').useUserMedia
const useVModel: typeof import('@vueuse/core').useVModel
const useVModels: typeof import('@vueuse/core').useVModels
const useVibrate: typeof import('@vueuse/core').useVibrate
const useVirtualList: typeof import('@vueuse/core').useVirtualList
const useWakeLock: typeof import('@vueuse/core').useWakeLock
const useWebNotification: typeof import('@vueuse/core').useWebNotification
const useWebSocket: typeof import('@vueuse/core').useWebSocket
const useWebWorker: typeof import('@vueuse/core').useWebWorker
const useWebWorkerFn: typeof import('@vueuse/core').useWebWorkerFn
const useWindowFocus: typeof import('@vueuse/core').useWindowFocus
const useWindowScroll: typeof import('@vueuse/core').useWindowScroll
const useWindowSize: typeof import('@vueuse/core').useWindowSize
const watch: typeof import('vue').watch
const watchArray: typeof import('@vueuse/core').watchArray
const watchAtMost: typeof import('@vueuse/core').watchAtMost
const watchDebounced: typeof import('@vueuse/core').watchDebounced
const watchDeep: typeof import('@vueuse/core').watchDeep
const watchEffect: typeof import('vue').watchEffect
const watchIgnorable: typeof import('@vueuse/core').watchIgnorable
const watchImmediate: typeof import('@vueuse/core').watchImmediate
const watchOnce: typeof import('@vueuse/core').watchOnce
const watchPausable: typeof import('@vueuse/core').watchPausable
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
const watchThrottled: typeof import('@vueuse/core').watchThrottled
const watchTriggerable: typeof import('@vueuse/core').watchTriggerable
const watchWithFilter: typeof import('@vueuse/core').watchWithFilter
const whenever: typeof import('@vueuse/core').whenever
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createRef: UnwrapRef<typeof import('@vueuse/core')['createRef']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly isShallow: UnwrapRef<typeof import('vue')['isShallow']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onElementRemoval: UnwrapRef<typeof import('@vueuse/core')['onElementRemoval']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
readonly refManualReset: UnwrapRef<typeof import('@vueuse/core')['refManualReset']>
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
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 toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useCountdown: UnwrapRef<typeof import('@vueuse/core')['useCountdown']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useId: UnwrapRef<typeof import('vue')['useId']>
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useSSRWidth: UnwrapRef<typeof import('@vueuse/core')['useSSRWidth']>
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
readonly useTimeAgoIntl: UnwrapRef<typeof import('@vueuse/core')['useTimeAgoIntl']>
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
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 useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
}
}

19
capacitor.config.ts Normal file
View File

@@ -0,0 +1,19 @@
import type { CapacitorConfig } from "@capacitor/cli";
const config: CapacitorConfig = {
appId: "financial.ionic.app",
appName: "financial-ionic",
webDir: "dist",
server: {
url: "http://192.168.1.6:5173", // Vite默认端口
cleartext: true, // 允许HTTP连接
},
plugins: {
Keyboard: {
resize: "ionic", // 使用ionic模式让ion-app自动调整大小
resizeOnFullScreen: true, // Android全屏模式下也要调整
},
},
};
export default config;

52
components.d.ts vendored Normal file
View File

@@ -0,0 +1,52 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import { GlobalComponents } from 'vue'
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
IonApp: typeof import('@ionic/vue')['IonApp']
IonButton: typeof import('@ionic/vue')['IonButton']
IonContent: typeof import('@ionic/vue')['IonContent']
IonHeader: typeof import('@ionic/vue')['IonHeader']
IonIcon: typeof import('@ionic/vue')['IonIcon']
IonLabel: typeof import('@ionic/vue')['IonLabel']
IonPage: typeof import('@ionic/vue')['IonPage']
IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
IonTabBar: typeof import('@ionic/vue')['IonTabBar']
IonTabButton: typeof import('@ionic/vue')['IonTabButton']
IonTabs: typeof import('@ionic/vue')['IonTabs']
IonTitle: typeof import('@ionic/vue')['IonTitle']
IonToolbar: typeof import('@ionic/vue')['IonToolbar']
LayoutDefault: typeof import('./src/components/layout/default.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}
// For TSX support
declare global {
const IonApp: typeof import('@ionic/vue')['IonApp']
const IonButton: typeof import('@ionic/vue')['IonButton']
const IonContent: typeof import('@ionic/vue')['IonContent']
const IonHeader: typeof import('@ionic/vue')['IonHeader']
const IonIcon: typeof import('@ionic/vue')['IonIcon']
const IonLabel: typeof import('@ionic/vue')['IonLabel']
const IonPage: typeof import('@ionic/vue')['IonPage']
const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
const IonTabBar: typeof import('@ionic/vue')['IonTabBar']
const IonTabButton: typeof import('@ionic/vue')['IonTabButton']
const IonTabs: typeof import('@ionic/vue')['IonTabs']
const IonTitle: typeof import('@ionic/vue')['IonTitle']
const IonToolbar: typeof import('@ionic/vue')['IonToolbar']
const LayoutDefault: typeof import('./src/components/layout/default.vue')['default']
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']
}

39
eslint.config.mjs Normal file
View File

@@ -0,0 +1,39 @@
import process from "node:process";
import antfu from "@antfu/eslint-config";
export default antfu({
vue: true,
typescript: true,
jsonc: true,
gitignore: true,
ignores: [
"ios",
"android",
"dist",
"coverage",
"node_modules",
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
],
stylistic: {
semi: true,
quotes: "double",
},
formatters: {
css: "prettier",
},
parserOptions: {
ecmaVersion: 2020,
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-unused-vars": "off",
"unused-imports/no-unused-vars": "off",
"unused-imports/no-unused-imports": "off",
"vue/no-deprecated-slot-attribute": "off",
"@typescript-eslint/no-explicit-any": "off",
"prefer-promise-reject-errors": "off",
"no-async-promise-executor": "off",
},
});

35
index.html Normal file
View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Financial</title>
<base href="/" />
<meta name="color-scheme" content="light dark" />
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<link rel="shortcut icon" type="image/svg" href="/favicon.svg" />
<!-- add to homescreen for ios -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Financial" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<!-- 防止 iOS Safari 工具栏显示 -->
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

7
ionic.config.json Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "financial-ionic",
"integrations": {
"capacitor": {}
},
"type": "vue-vite"
}

109
package.json Normal file
View File

@@ -0,0 +1,109 @@
{
"name": "financial-app",
"type": "module",
"version": "0.0.1",
"private": true,
"description": "An Ionic project",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"run:ios": "ionic capacitor run ios -l --external",
"run:android": "ionic capacitor run android -l --external",
"deploy:cloudflare": "wrangler pages deploy dist --project-name=financial-app --branch=main",
"test:build": "vite build -m test --outDir dist-test",
"test:preview": "vite preview --port 6173 -m test --outDir dist-test",
"lint": "eslint",
"lint:fix": "eslint --fix"
},
"dependencies": {
"@capacitor-community/barcode-scanner": "catalog:",
"@capacitor/app": "catalog:",
"@capacitor/barcode-scanner": "catalog:",
"@capacitor/camera": "catalog:",
"@capacitor/clipboard": "catalog:",
"@capacitor/core": "catalog:",
"@capacitor/file-transfer": "catalog:",
"@capacitor/filesystem": "catalog:",
"@capacitor/haptics": "catalog:",
"@capacitor/ios": "catalog:",
"@capacitor/keyboard": "catalog:",
"@capacitor/share": "catalog:",
"@capacitor/status-bar": "catalog:",
"@elysiajs/eden": "catalog:",
"@ionic/vue": "catalog:",
"@ionic/vue-router": "catalog:",
"@tailwindcss/vite": "catalog:",
"@vee-validate/zod": "catalog:",
"@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:",
"@vueuse/router": "catalog:",
"better-auth": "catalog:",
"dayjs": "catalog:",
"ethers": "catalog:",
"html2canvas": "catalog:",
"ionicons": "catalog:",
"jsqr": "catalog:",
"lightweight-charts": "catalog:",
"lodash-es": "catalog:",
"markdown-it": "catalog:",
"pinia": "catalog:",
"qr-scanner-wechat": "catalog:",
"qrcode": "catalog:",
"tailwindcss": "catalog:",
"vconsole": "catalog:",
"vee-validate": "catalog:",
"vue": "catalog:",
"vue-i18n": "catalog:",
"vue-router": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@capacitor/cli": "catalog:",
"@cloudflare/workers-types": "catalog:",
"@iconify-json/bx": "catalog:",
"@iconify-json/circle-flags": "catalog:",
"@iconify-json/cryptocurrency-color": "catalog:",
"@iconify-json/ic": "catalog:",
"@iconify-json/icon-park-outline": "catalog:",
"@iconify-json/material-icon-theme": "catalog:",
"@iconify-json/material-symbols": "catalog:",
"@iconify-json/prime": "catalog:",
"@iconify-json/solar": "catalog:",
"@iconify-json/tdesign": "catalog:",
"@iconify/vue": "catalog:",
"@ionic/cli": "catalog:",
"@types/lodash-es": "catalog:",
"@types/node": "catalog:",
"@vitejs/plugin-legacy": "catalog:",
"@vitejs/plugin-vue": "catalog:",
"@vitejs/plugin-vue-jsx": "catalog:",
"@vue/eslint-config-typescript": "catalog:",
"@vue/test-utils": "catalog:",
"dotenv": "catalog:",
"eslint": "catalog:",
"eslint-plugin-format": "catalog:",
"eslint-plugin-vue": "catalog:",
"jiti": "catalog:",
"lint-staged": "catalog:",
"simple-git-hooks": "catalog:",
"terser": "catalog:",
"typescript": "catalog:",
"unplugin-auto-import": "catalog:",
"unplugin-icons": "catalog:",
"unplugin-vue-components": "catalog:",
"vite": "catalog:",
"vite-plugin-pwa": "catalog:",
"vitest": "catalog:",
"vue-tsc": "catalog:",
"workbox-window": "catalog:",
"wrangler": "catalog:"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
}
}

12071
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

95
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,95 @@
packages:
- packages/*
catalog:
'@antfu/eslint-config': ^6.6.1
'@capacitor-community/barcode-scanner': ^4.0.1
'@capacitor-mlkit/barcode-scanning': ^8.0.0
'@capacitor/app': 8.0.0
'@capacitor/barcode-scanner': ^3.0.0
'@capacitor/camera': ^8.0.0
'@capacitor/cli': 8.0.0
'@capacitor/clipboard': ^8.0.0
'@capacitor/core': 8.0.0
'@capacitor/file-transfer': ^2.0.0
'@capacitor/filesystem': ^8.0.0
'@capacitor/haptics': 8.0.0
'@capacitor/ios': ^8.0.0
'@capacitor/keyboard': 8.0.0
'@capacitor/share': ^8.0.0
'@capacitor/status-bar': 8.0.0
'@cloudflare/workers-types': ^4.20260113.0
'@elysiajs/eden': ^1.4.5
'@iconify-json/bx': ^1.2.2
'@iconify-json/circle-flags': ^1.2.10
'@iconify-json/cryptocurrency-color': ^1.2.4
'@iconify-json/ic': ^1.2.4
'@iconify-json/icon-park-outline': ^1.2.4
'@iconify-json/material-icon-theme': ^1.2.44
'@iconify-json/material-symbols': ^1.2.50
'@iconify-json/prime': ^1.2.4
'@iconify-json/solar': ^1.2.5
'@iconify-json/tdesign': ^1.2.11
'@iconify/vue': ^5.0.0
'@ionic/cli': ^7.2.1
'@ionic/vue': ^8.7.11
'@ionic/vue-router': ^8.7.11
'@tailwindcss/vite': ^4.1.18
'@types/lodash-es': ^4.17.12
'@types/node': ^24.10.2
'@types/qrcode': ^1.5.6
'@vee-validate/zod': ^4.15.1
'@vitejs/plugin-basic-ssl': ^2.1.3
'@vitejs/plugin-legacy': ^7.2.1
'@vitejs/plugin-vue': ^6.0.2
'@vitejs/plugin-vue-jsx': ^5.1.2
'@vue/eslint-config-typescript': ^14.6.0
'@vue/test-utils': ^2.4.6
'@vueuse/core': ^14.1.0
'@vueuse/integrations': ^14.1.0
'@vueuse/router': ^14.1.0
better-auth: ^1.4.6
cypress: ^15.7.1
dayjs: ^1.11.19
dotenv: ^17.2.3
eslint: ^9.39.1
eslint-plugin-format: ^1.1.0
eslint-plugin-vue: ^10.6.2
ethers: ^6.16.0
html2canvas: ^1.4.1
ionicons: ^8.0.13
jiti: ^2.6.1
jsdom: ^27.3.0
jsqr: ^1.4.0
lightweight-charts: ^5.1.0
lint-staged: ^16.2.7
lodash-es: ^4.17.21
markdown-it: ^14.1.0
pinia: ^3.0.4
qr-scanner-wechat: ^0.1.3
qrcode: ^1.5.4
simple-git-hooks: ^2.13.1
tailwindcss: ^4.1.18
terser: ^5.44.1
typescript: ~5.9.3
unplugin-auto-import: ^20.3.0
unplugin-icons: ^22.5.0
unplugin-vue-components: ^30.0.0
vconsole: ^3.15.1
vee-validate: ^4.15.1
vite: ^7.2.7
vite-plugin-pwa: ^1.2.0
vitest: ^4.0.15
vue: ^3.5.25
vue-i18n: ^11.2.2
vue-router: ^4.6.3
vue-tsc: ^3.1.8
workbox-window: ^7.4.0
wrangler: ^4.54.0
zod: ^3.24.1
catalogMode: prefer
shellEmulator: true
trustPolicy: off

10
public/favicon.svg Normal file
View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2563eb;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="100" height="100" rx="20" fill="url(#grad)"/>
<text x="50" y="70" font-family="Arial, sans-serif" font-size="60" font-weight="bold" fill="white" text-anchor="middle">R</text>
</svg>

After

Width:  |  Height:  |  Size: 510 B

10
src/App.vue Normal file
View File

@@ -0,0 +1,10 @@
<script setup lang="ts">
</script>
<template>
<IonApp>
<Suspense>
<IonRouterOutlet />
</Suspense>
</IonApp>
</template>

View File

@@ -0,0 +1,47 @@
<script setup lang="ts">
import { cellular, chatboxEllipses, compass, personCircle, swapHorizontal } from "ionicons/icons";
const { t } = useI18n();
</script>
<template>
<ion-page>
<ion-tabs>
<ion-router-outlet />
<ion-tab-bar slot="bottom">
<ion-tab-button tab="home" href="/layout/home">
<div class="flex-col-center gap-1">
<ion-icon aria-hidden="true" :icon="compass" class="icon" />
<ion-label>{{ t('tabs.home') }}</ion-label>
</div>
</ion-tab-button>
<ion-tab-button tab="notify" href="/layout/notify">
<div class="flex-col-center gap-1">
<ion-icon aria-hidden="true" :icon="chatboxEllipses" class="icon" />
<ion-label>{{ t('tabs.notify') }}</ion-label>
</div>
</ion-tab-button>
<ion-tab-button tab="user" href="/layout/user">
<div class="flex-col-center gap-1">
<ion-icon aria-hidden="true" :icon="personCircle" class="icon" />
<ion-label>{{ t('tabs.user') }}</ion-label>
</div>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
</ion-page>
</template>
<style scoped>
ion-tab-bar {
height: 60px;
--background: var(--ion-background-color);
box-shadow: 0px 0px 12px rgba(45, 213, 90, 0.21);
padding-bottom: var(--ion-safe-area-bottom);
}
.icon {
font-size: 1.5rem;
}
</style>

15
src/locales/index.ts Normal file
View File

@@ -0,0 +1,15 @@
import { createI18n } from "vue-i18n";
import zhCN from "./zh-CN.json";
export type MessageSchema = typeof zhCN;
const i18n = createI18n<MessageSchema, "zh-CN">({
legacy: false,
locale: "zh-CN",
fallbackLocale: "zh-CN",
messages: {
"zh-CN": zhCN,
},
});
export { i18n };

10
src/locales/zh-CN.json Normal file
View File

@@ -0,0 +1,10 @@
{
"tabs": {
"home": "首页",
"notify": "通知",
"user": "我的"
},
"home": {
"title": "首页"
}
}

84
src/main.ts Normal file
View File

@@ -0,0 +1,84 @@
import { IonicVue } from "@ionic/vue";
import { createPinia } from "pinia";
import VConsole from "vconsole";
import { useRegisterSW } from "virtual:pwa-register/vue";
import { createApp } from "vue";
import App from "./App.vue";
import { i18n } from "./locales";
import { router } from "./router";
/* Core CSS required for Ionic components to work properly */
import "@ionic/vue/css/core.css";
/* Basic CSS for apps built with Ionic */
import "@ionic/vue/css/normalize.css";
import "@ionic/vue/css/structure.css";
import "@ionic/vue/css/typography.css";
/* Optional CSS utils that can be commented out */
import "@ionic/vue/css/padding.css";
import "@ionic/vue/css/float-elements.css";
import "@ionic/vue/css/text-alignment.css";
import "@ionic/vue/css/text-transformation.css";
import "@ionic/vue/css/flex-utils.css";
/**
* Ionic Dark Mode
* -----------------------------------------------------
* For more info, please see:
* https://ionicframework.com/docs/theming/dark-mode
*/
import "@ionic/vue/css/display.css";
// import "@ionic/vue/css/palettes/dark.system.css";
// import "@ionic/vue/css/palettes/dark.always.css";
// import "@ionic/vue/css/palettes/dark.class.css";
import "@/theme/index.css";
// 注册 PWA Service Worker使用 Vue 3 Composition API
const { offlineReady, needRefresh, updateServiceWorker } = useRegisterSW({
onRegistered(registration) {
console.log("[PWA] Service Worker registered:", registration);
},
onRegisterError(error) {
console.error("[PWA] Service Worker registration failed:", error);
},
immediate: true,
});
// 监听 PWA 状态
watch(offlineReady, (ready) => {
if (ready) {
console.log("[PWA] App ready to work offline.");
}
});
watch(needRefresh, (refresh) => {
if (refresh) {
console.log("[PWA] New content available, please refresh.");
}
});
if (import.meta.env.DEV) {
const vConsole = new VConsole();
globalThis.vConsole = vConsole;
console.log("VConsole is enabled in development mode.");
}
const pinia = createPinia();
const app = createApp(App)
.use(IonicVue, {
backButtonText: "返回",
mode: "ios",
statusTap: true,
swipeBackEnabled: true,
// rippleEffect: true,
// animated: false,
})
.use(pinia)
.use(router)
.use(i18n);
router.isReady().then(() => {
app.mount("#app");
});

18
src/router/auth.ts Normal file
View File

@@ -0,0 +1,18 @@
import type { RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
path: "/auth/login",
component: () => import("@/views/auth/login/index.vue"),
},
{
path: "/auth/signup",
component: () => import("@/views/auth/signup/index.vue"),
},
{
path: "/auth/term",
component: () => import("@/views/auth/term.vue"),
},
];
export default routes;

7
src/router/guard.ts Normal file
View File

@@ -0,0 +1,7 @@
import type { Router } from "vue-router";
export function createRouterGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
next();
});
}

35
src/router/index.ts Normal file
View File

@@ -0,0 +1,35 @@
import type { RouteRecordRaw } from "vue-router";
import { createRouter, createWebHistory } from "@ionic/vue-router";
import authRoutes from "./auth";
import { createRouterGuard } from "./guard";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
redirect: "/layout/home",
},
{
path: "/:pathMatch(.*)*",
redirect: "/layout/home",
},
// ...authRoutes,
{
path: "/layout",
component: () => import("@/components/layout/default.vue"),
children: [
{
path: "home",
component: () => import("@/views/home/index.vue"),
},
],
},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
createRouterGuard(router);
export { router };

3
src/theme/index.css Normal file
View File

@@ -0,0 +1,3 @@
@import "tailwindcss";
@config "../../tailwind.config.ts";
@import "./ionic.css"

4
src/theme/ionic.css Normal file
View File

@@ -0,0 +1,4 @@
.ion-toolbar {
--background: var(--ion-color-primary-contrast);
--min-height: 50px;
}

19
src/views/home/index.vue Normal file
View File

@@ -0,0 +1,19 @@
<script lang='ts' setup>
</script>
<template>
<ion-page>
<ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar">
<ion-button slot="start" fill="clear" class="z-1" @click="$router.push('/global-menu')">
<IconParkOutlineApplicationMenu slot="icon-only" />
</ion-button>
<ion-title>{{ $t('home.title') }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true" class="ion-padding" />
</ion-page>
</template>
<style lang='css' scoped></style>

34
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,34 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pwa/client" />
/// <reference types="unplugin-icons/types/vue" />
/// <reference types="@cloudflare/workers-types" />
import type { MessageSchema } from "@/locales";
import {
DefineDateTimeFormat,
DefineLocaleMessage,
DefineNumberFormat,
} from "vue-i18n";
import "vue-router";
export {};
declare module "vue-i18n" {
// define the locale messages schema
export interface DefineLocaleMessage extends MessageSchema {
}
// define the datetime format schema
export interface DefineDateTimeFormat {
}
// define the number format schema
export interface DefineNumberFormat {
}
}
declare module "vue-router" {
interface RouteMeta {
requiresAuth: ? boolean;
}
}

47
tailwind.config.ts Normal file
View File

@@ -0,0 +1,47 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
primary: "var(--ion-color-primary)",
secondary: "var(--ion-color-secondary)",
tertiary: "var(--ion-color-tertiary)",
success: "var(--ion-color-success)",
warning: "var(--ion-color-warning)",
danger: "var(--ion-color-danger)",
light: "var(--ion-color-light)",
medium: "var(--ion-color-medium)",
dark: "var(--ion-color-dark)",
faint: "var(--ion-color-faint)",
},
},
},
plugins: [
function ({ addUtilities }) {
addUtilities({
".flex-center": {
"display": "flex",
"align-items": "center",
"justify-content": "center",
},
".flex-col-center": {
"display": "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "center",
},
".scrollbar-hide": {
"-ms-overflow-style": "none",
"scrollbar-width": "none",
"&::-webkit-scrollbar": {
display: "none",
},
},
});
},
],
};
export default config;

24
tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"lib": ["ESNext", "DOM", "WebWorker"],
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "bundler",
"paths": {
"@/*": ["./src/*"],
"#/*": ["./types/*"]
},
"resolveJsonModule": true,
"types": ["./components"],
"strict": true,
"noImplicitAny": false,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true
},
"references": [{ "path": "./tsconfig.node.json" }],
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "components.d.ts", "auto-imports.d.ts", "types/*.d.ts"]
}

14
tsconfig.node.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "bundler",
"paths": {
"#build/ui": [
"./node_modules/.nuxt-ui/ui"
]
},
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts", "tailwind.config.ts", "scripts/**/*.ts"]
}

102
vite.config.ts Normal file
View File

@@ -0,0 +1,102 @@
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import tailwindcss from "@tailwindcss/vite";
import legacy from "@vitejs/plugin-legacy";
import vue from "@vitejs/plugin-vue";
import jsx from "@vitejs/plugin-vue-jsx";
import dotenv from "dotenv";
import autoImport from "unplugin-auto-import/vite";
import iconsResolver from "unplugin-icons/resolver";
import icons from "unplugin-icons/vite";
import { IonicResolver } from "unplugin-vue-components/resolvers";
import components from "unplugin-vue-components/vite";
import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";
const packageJson = JSON.parse(fs.readFileSync("./package.json", "utf-8"));
const appVersion = packageJson.version;
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
dotenv.config({ path: `.env.${mode}` });
return {
plugins: [
vue(),
jsx(),
legacy(),
icons({
autoInstall: true,
}),
tailwindcss(),
autoImport({
dirs: ["src/composables", "src/utils", "src/store"],
imports: ["vue", "vue-router", "@vueuse/core", "vue-i18n", "pinia"],
resolvers: [IonicResolver()],
vueTemplate: true,
}),
components({
directoryAsNamespace: true,
resolvers: [IonicResolver(), iconsResolver({ prefix: "i" })],
}),
VitePWA({
registerType: "autoUpdate",
injectRegister: "auto",
includeAssets: ["favicon.svg"],
devOptions: {
enabled: true,
type: "module",
},
manifest: {
name: "Financial",
short_name: "Financial",
description: "Financial",
theme_color: "#ffffff",
background_color: "#ffffff",
display: "standalone",
orientation: "portrait",
scope: "/",
start_url: "/",
id: "/",
prefer_related_applications: false,
icons: [
{
src: "/favicon.svg",
sizes: "any",
type: "image/svg+xml",
purpose: "any maskable",
},
],
},
workbox: {
globPatterns: ["**/*.{js,css,html,ico,png,svg,jpg,woff2}"],
navigateFallback: "/index.html",
navigateFallbackDenylist: [/^\/api/],
cleanupOutdatedCaches: true,
clientsClaim: true,
skipWaiting: true,
maximumFileSizeToCacheInBytes: 3 * 1024 * 1024, // 3 MB
},
}),
],
define: {
__APP_VERSION__: JSON.stringify(appVersion),
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
host: true,
proxy: {
"/api": {
target: process.env.VITE_API_URL,
changeOrigin: true,
ws: true,
},
},
},
};
});