zhb 11 месяцев назад
Родитель
Сommit
b6a1da87f1
57 измененных файлов с 7992 добавлено и 1727 удалено
  1. 2 0
      .eslintrc-auto-import.json
  2. 5 266
      README.md
  3. 6 5
      package.json
  4. 25 8
      src/App.vue
  5. 118 58
      src/api/ucard.ts
  6. 1 1
      src/api/upload.ts
  7. 28 23
      src/api/user.ts
  8. 76 4
      src/assets/scss/global/global.scss
  9. 1 0
      src/assets/scss/global/vant.scss
  10. 2 4
      src/auto-imports.d.ts
  11. 3 0
      src/components.d.ts
  12. 35 11
      src/components/ApplyCard.vue
  13. 73 75
      src/components/CurrencySelect.vue
  14. 97 96
      src/components/CustomTabbar.vue
  15. 38 0
      src/components/EmptyState.vue
  16. 394 0
      src/components/RemitInput.vue
  17. 393 0
      src/components/VirtualCard.vue
  18. 5 1
      src/composables/config.ts
  19. 15 3
      src/composables/fetch.ts
  20. 36 37
      src/config/index.ts
  21. 42 0
      src/i18n/index.ts
  22. 610 0
      src/i18n/locales/cn.ts
  23. 610 0
      src/i18n/locales/de.ts
  24. 610 0
      src/i18n/locales/en.ts
  25. 610 0
      src/i18n/locales/zh.ts
  26. 0 191
      src/locales/cn.ts
  27. 0 50
      src/locales/de.ts
  28. 0 50
      src/locales/en.ts
  29. 0 63
      src/locales/zh.ts
  30. 4 2
      src/main.ts
  31. 0 21
      src/plugin/i18n.ts
  32. 25 1
      src/router/index.ts
  33. 2 0
      src/stores/pinia.types.ts
  34. 11 1
      src/stores/use-global-store.ts
  35. 25 0
      src/stores/use-user-store.ts
  36. 0 1
      src/views/activate-card.vue
  37. 420 0
      src/views/card-activation.vue
  38. 107 58
      src/views/card-recharge.vue
  39. 16 7
      src/views/card-transaction-detail.vue
  40. 389 96
      src/views/cards.vue
  41. 17 6
      src/views/change-pay-password.vue
  42. 1176 0
      src/views/eur-remit.vue
  43. 175 0
      src/views/eur.vue
  44. 80 80
      src/views/finance.vue
  45. 13 4
      src/views/find-password.vue
  46. 87 88
      src/views/forget-pay-password.vue
  47. 19 7
      src/views/freeze-card.vue
  48. 150 61
      src/views/home.vue
  49. 695 0
      src/views/improve-info.vue
  50. 215 145
      src/views/kyc.vue
  51. 1 1
      src/views/language.vue
  52. 94 41
      src/views/login.vue
  53. 43 9
      src/views/mine.vue
  54. 14 90
      src/views/reset-password.vue
  55. 64 62
      src/views/select-card.vue
  56. 310 0
      src/views/transfer-detail.vue
  57. 5 0
      vite.config.build.ts

+ 2 - 0
.eslintrc-auto-import.json

@@ -1,6 +1,7 @@
 {
 {
   "globals": {
   "globals": {
     "$api": true,
     "$api": true,
+    "AccountInfo": true,
     "CLIENT": true,
     "CLIENT": true,
     "Component": true,
     "Component": true,
     "ComponentPublicInstance": true,
     "ComponentPublicInstance": true,
@@ -114,6 +115,7 @@
     "refDefault": true,
     "refDefault": true,
     "refThrottled": true,
     "refThrottled": true,
     "refWithControl": true,
     "refWithControl": true,
+    "rememberPassword": true,
     "resolveComponent": true,
     "resolveComponent": true,
     "resolveRef": true,
     "resolveRef": true,
     "resolveUnref": true,
     "resolveUnref": true,

+ 5 - 266
README.md

@@ -1,281 +1,20 @@
-# Vue3 项目模板
-
-vue3 H5端脚手架, 包含技术栈(Vue3 + Vant + Pinia + Vite + TS + Unocss)
-
-## Variations
-
--   [vite-nuxt3](https://github.com/lincenying/vite-nuxt3) - Nuxt3 + Vite 入门模板
--   [vite-uniapp-vue3](https://github.com/lincenying/vite-uniapp-vue3) - Uniapp3 + Vite 入门模板
--   [vite-react-mobx-ssr](https://github.com/lincenying/vite-react-mobx-ssr) - React + Mobx + Vite + SSR 入门模板
--   [vite-react-mobx](https://github.com/lincenying/vite-react-mobx) - React + Mobx + Vite 入门模板
--   [vite-react-redux](https://github.com/lincenying/vite-react-redux) - React + Redux + Vite 入门模板
--   [vite-vue3-h5-ssr](https://github.com/lincenying/vite-vue3-h5-ssr) - Vue3 + Vant + Vite + SSR 入门模板
--   [vite-vue3-h5](https://github.com/lincenying/vite-vue3-h5) - Vue3 + Vant + Vite 入门模板
--   [vite-vue3-admin](https://github.com/lincenying/vite-vue3-admin) - Vue3 + ElementPlus + Vite 管理后台入
-
 ## 使用
 ## 使用
 
 
 ```bash
 ```bash
-npx degit lincenying/vite-vue3-h5 my-h5-app
-cd my-h5-app
-pnpm i # 如果你没有安装 pnpm,请运行:npm install -g pnpm
+npm i
 ```
 ```
 
 
 ### 开发环境
 ### 开发环境
 
 
 ```bash
 ```bash
-pnpm serve
+npm serve
 ```
 ```
 
 
 ### 生产环境
 ### 生产环境
 
 
 ```bash
 ```bash
 # 测试环境
 # 测试环境
-pnpm build:test
-# 预发布环境
-pnpm build:staging
+npm build:test
 # 生产环境
 # 生产环境
-pnpm build
-```
-
-### 生产环境预览
-
-```bash
-pnpm start
-```
-
-### Lint 和修复文件
-
-```bash
-pnpm lint # eslint检测不修复
-pnpm lint:fix # eslint检测并修复
-pnpm lint:ts # ts 类型检测
-pnpm lint:css # css 检测并修复
-
-```
-
-## 环境变量
-
-预留4套环境变量, 具体参数可查看根目录的 `.env.xxx`, 其中 `development` 为开发环境, `test, staging, production` 依次为 `测试环境, 预发布环境, 正式环境`
-根据自己需要, 启动/编译不同的环境
-
-## Rem 适配
-
-设计稿相关参数配置见: `src/design.config.ts`, 按UI给的设计稿, 修改即可
-
-设计稿宽度: `designWidth`
-设计稿高度: `designHeight`
-字号: `fontSize`
-
-字号大小, 尽量配合Ui库, 比如默认使用的vant UI组件库就是设计稿宽度为375, rootfontsize为37.5
-如果你的设计稿是750的, 方法有2
-1: 设计稿宽度设置为750, 然后字号设置成75, 然后css代码的宽高按设计稿中实际的书写, 然后在postcss插件, 针对性判断vant的字号改成37.5( vite.config.css.ts 里已做了适配vant组件库)
-2: 设计稿宽度设置为375, 然后字号设置成37.5, 然后css代码的宽高按设计稿中实际尺寸/2书写, 也可以将设计稿尺寸调整到375后, 按375的实际尺寸书写
-
-一般项目中有3类代码的单位需要转换, 分别是自己写的css代码, 组件库或者其他第三方插件带的css代码, 使用unocss写的原子化css
-具体插件配置详见: `vite.config.css.ts`
-
-## 自动引入UI库组件/项目组件/函数等
-
-项目已经配置了`unplugin-auto-import`和`unplugin-vue-components`
-前者能自动引入vue, vue-router, vueuse等提供的方法, 而无需一遍遍的`import`
-后者能自动引入UI组件, 及项目被定义的组件, 也不用一遍遍的`import`
-详细配置见: `vite.config.components.ts`
-相关文档见:
-https://github.com/antfu/unplugin-auto-import#readme
-https://github.com/antfu/unplugin-vue-components#readme
-
-## Pinia 状态管理
-
-vue 官方出品的, 比vuex更好用的状态管理
-使用方法:
-在pinia文件夹下,新建一个ts文件, 如: `use-global-store.ts`
-里面代码如下:
-
-```ts
-import type { GlobalState } from './pinia.types'
-
-const useStore = defineStore('globalStore', () => {
-    const state: GlobalState = reactive({
-        globalLoading: true,
-        routerLoading: false,
-    })
-
-    const setGlobalLoading = (payload: boolean) => {
-        state.globalLoading = payload
-    }
-    const setRouterLoading = (payload: boolean) => {
-        state.routerLoading = payload
-    }
-
-    return {
-        ...toRefs(state),
-        setGlobalLoading,
-        setRouterLoading,
-    }
-})
-
-export default useStore
-```
-
-那么在需要用到该状态管理的地方, 只需要
-
-```ts
-const userStore = useGlobalStore()
-userStore.setGlobalLoading(true)
-```
-
-即可, 因为配置了`unplugin-auto-import`, 所以根本无需要`import`, 你只需要直接把文件名改成驼峰的方式, 直接当函数使用即可
-注意: 直接用文件名当函数名, 只有代码是用`export default`导出时可用, 如果是用`export const xxx`, `export function xxx {}` 这样导出的, 那么直接使用xxx作为方法名即可
-具体可以看`src/auto-imports.d.ts`为你生成了那些方法, 这里的方法都可以直接使用, 而无需`import`
-
-## 路由
-
-放在`views`文件夹下的`vue`文件, 都会自动加入路由中, 子文件夹里的则不会, 根据你自己的使用情况, 可以修改`src/router/index.ts`以适配
-是使用`hash`还是`history`模式, 也可以在上面的文件中修改
-
-## Api封装
-
-`src/api/index.ts`封装了`get, post, put, delete`4中常用的方法, 分别对应4种method, 而`$api`为全局方法, 可以在任何`.vue`页面, 直接使用`$api.get/post/put/delete`
-接口默认判断code=200为正常返回, 如果后端接口不是用code作为判断, 那么需要在`src/api/index.ts`做对应修改
-如:
-
-```ts
-let detail: NullAble<Article> = null
-async function getDetail() {
-    const { code, data } = await $api.get<Article>('article/detail', {})
-    if (code === 200) {
-        detail = data
-    }
-}
-
-getDetail()
-```
-
-## 列表封装: useLists
-
-在`src/composables/index.ts`中封装了`useLists`方法, 让你所有上拉加载, 下拉刷新几行代码就搞定, 如:
-
-```ts
-const apiConfig = {
-    api: {
-        method: 'get',
-        url: 'article/lists',
-        config: { per_page: 20, tab: '' },
-    },
-}
-const { api, page, config, dataList, getList, onRefresh } = useLists<Article>(apiConfig)
-```
-
-如上面代码, 只需要将接口相关参数传入接口, 返回的参数中, `api`为传入的接口相关参数(响应式的Ref数据), `page`为当前页数(响应式的Ref数据),
-`config`为加载状态(响应式的Ref数据), `dataList`为数据列表(响应式的Ref数据), `getList`为请求列表的方法, `onRefresh`为刷新数据的方法
-
-模板代码为:
-
-```html
-<template>
-    <van-pull-refresh v-model="config.isLoading" @refresh="onRefresh">
-        <van-list
-            :loading="config.loading"
-            :immediate-check="false"
-            :finished="config.finished"
-            :error="config.error"
-            loading-text="努力加载中"
-            finished-text="我也是有底线的"
-            error-text="请求失败,点击重新加载"
-            @load="getList"
-        >
-            <van-cell v-for="(item, index) in dataList" :key="`${index}_${item.c_id}`" :title="item.c_title" is-link :to="`/home/detail?id=${item.c_id}`" />
-        </van-list>
-    </van-pull-refresh>
-</template>
-```
-
-如果你需要做搜索, 或者切换分类什么的, 也很简单, 假设我现在需要修改tab的值,然后重新请求接口, 那么只需要:
-
-```ts
-function changeTab(tab: string) {
-    api.value.config.tab = tab
-    page.value = 1
-    getList()
-}
-```
-
-先更新api里的相关参数/充值page数为1, 然后重新请求接口即可
-
-上面`config`返回的参数如下:
-
-```json
-{
-  // 下拉刷新 ==>
-  "isLoading": false,
-  "isRefresh": false,
-  // <==下拉刷新
-  // 滚动加载 ==>
-  "loadStatus": "loadmore", // 'loadmore' | 'nomore' | 'loading'
-  "isLock": false, // 请求过程中锁定, 防止重复请求
-  "loading": false, // 加载状态 (直接判断loadStatus==='loading'也可以)
-  "error": false, // 接口是否报错
-  "finished": false // 列表是否已加载完成, 即是否到最后一页 (直接判断loadStatus==='nomore'也可以)
-  // <==滚动加载
-}
-```
-
-接口默认返回数据接口为:
-
-```json
-{
-  "code": 200,
-  "data": {
-    "total": 992,
-    "per_page": 20,
-    "current_page": 1,
-    "last_page": 50,
-    "data": []
-  }
-}
-```
-
-如果你用的接口返回数据结构不一样, 需要在`src/composables/index.ts`稍做修改
-
-## 开发环境配置proxy跨域
-
-```
-{
-    server: {
-        port: 7771,
-        proxy: {
-            '/api': {
-                target: 'https://php.mmxiaowu.com',
-                changeOrigin: true,
-                rewrite: (path: string) => path.replace(/^\/api/, '/api'),
-            },
-        },
-    },
-}
-```
-
-详见: `vite.config.build.ts`
-
-## Mock
-
-在`mock`文件夹, 创建ts文件, 按mock规则添加接口即可, 详情见: `mock/module-index.ts`
-相关文档见:
-https://github.com/anncwb/vite-plugin-mock/tree/master/#readme
-
-## Unocss
-
-unocss是一个及时/按需/原子化的css引擎, 项目中也做了相关配置, 可直接使用
-配置见:
-https://github.com/lincenying/unocss-base-config/blob/main/src/uno.h5.config.ts
-官方文档见:
-https://unocss.dev/
-
-## eslint/stylelint/prettierrc/vue-tsc
-
-根目录下的`eslint.config.ts`、`stylelint.config.js`、`.prettier`内置了 lint 规则,帮助你规范地开发代码,有助于提高团队的代码质量和协作性,可以根据团队的规则进行修改
-注意: `prettier`只在编辑器层面, 在`eslint`中并没有添加`prettier`插件
-
-## License
-
-[MIT]
+npm build
+```

+ 6 - 5
package.json

@@ -1,17 +1,17 @@
 {
 {
-  "name": "vite-vue3-h5",
+  "name": "CWG",
   "type": "module",
   "type": "module",
-  "version": "3.0.0",
+  "version": "1.0.0",
   "private": true,
   "private": true,
   "packageManager": "pnpm@10.11.0",
   "packageManager": "pnpm@10.11.0",
-  "author": "lincenying <lincenying@qq.com>",
+  "author": "CWG <>",
   "scripts": {
   "scripts": {
     "prepare": "npx simple-git-hooks",
     "prepare": "npx simple-git-hooks",
     "del": "sh delete-ts.sh",
     "del": "sh delete-ts.sh",
     "serve": "cross-env DEBUG=vite:transform vite",
     "serve": "cross-env DEBUG=vite:transform vite",
     "build:test": "vite build --mode test",
     "build:test": "vite build --mode test",
     "build:staging": "vite build --mode staging",
     "build:staging": "vite build --mode staging",
-    "build": "vite build",
+    "build": "vite build --mode production",
     "start": "vite preview",
     "start": "vite preview",
     "lint": "eslint .",
     "lint": "eslint .",
     "lint:fix": "eslint . --fix",
     "lint:fix": "eslint . --fix",
@@ -24,10 +24,12 @@
     "@vueuse/core": "^13.1.0",
     "@vueuse/core": "^13.1.0",
     "axios": "^1.9.0",
     "axios": "^1.9.0",
     "crypto-js": "^4.0.0",
     "crypto-js": "^4.0.0",
+    "dayjs": "^1.11.13",
     "default-passive-events": "^2.0.0",
     "default-passive-events": "^2.0.0",
     "lodash": "^4.17.21",
     "lodash": "^4.17.21",
     "md5": "^2.3.0",
     "md5": "^2.3.0",
     "pinia": "^3.0.2",
     "pinia": "^3.0.2",
+    "pinyin-pro": "^3.26.0",
     "qs": "^6.14.0",
     "qs": "^6.14.0",
     "store2": "^2.14.4",
     "store2": "^2.14.4",
     "unhead": "^2.0.8",
     "unhead": "^2.0.8",
@@ -52,7 +54,6 @@
     "@vue-macros/volar": "^3.0.0-beta.12",
     "@vue-macros/volar": "^3.0.0-beta.12",
     "cross-env": "^7.0.3",
     "cross-env": "^7.0.3",
     "eslint": "^9.26.0",
     "eslint": "^9.26.0",
-    "eslint-plugin-format": "^1.0.1",
     "lint-staged": "^16.0.0",
     "lint-staged": "^16.0.0",
     "mockjs": "^1.1.0",
     "mockjs": "^1.1.0",
     "postcss": "^8.5.3",
     "postcss": "^8.5.3",

+ 25 - 8
src/App.vue

@@ -15,17 +15,24 @@
         </router-view>
         </router-view>
         <CustomTabbar v-if="routeIsTab" />
         <CustomTabbar v-if="routeIsTab" />
         <div v-if="globalLoading" class="global-loading">
         <div v-if="globalLoading" class="global-loading">
-            <van-loading type="spinner" size="32px" />
+            <van-loading type="spinner" size="32px" color="var(--main-yellow)" />
         </div>
         </div>
         <div v-if="routerLoading" class="router-loading">
         <div v-if="routerLoading" class="router-loading">
-            <van-loading type="spinner" size="32px" color="#1989fa" />
+            <van-loading type="spinner" size="32px" color="var(--main-yellow)" />
+        </div>
+        <div v-if="requestLoading" class="request-loading">
+            <van-loading type="spinner" size="32px" color="var(--main-yellow)" />
+            <span class="loading-text">{{ t('common.loading') }}</span>
+        </div>
+        <div v-if="fullScreenLoading" class="full-screen-loading">
+            <van-loading type="spinner" size="32px" color="var(--main-yellow)" />
+            <span class="loading-text">{{ t('common.loading') }}</span>
         </div>
         </div>
     </div>
     </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import CustomTabbar from './components/CustomTabbar.vue'
 import CustomTabbar from './components/CustomTabbar.vue'
-import { computed } from 'vue'
 import PageHeader from './components/PageHeader.vue'
 import PageHeader from './components/PageHeader.vue'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 defineOptions({
 defineOptions({
@@ -33,10 +40,10 @@ defineOptions({
 })
 })
 const { t } = useI18n()
 const { t } = useI18n()
 const { route, globalStore } = useGlobal()
 const { route, globalStore } = useGlobal()
-const { globalLoading, routerLoading } = storeToRefs(globalStore)
-setTimeout(() => {
-    globalStore.setGlobalLoading(false)
-}, 200)
+const { globalLoading, routerLoading, requestLoading, fullScreenLoading } = storeToRefs(globalStore)
+// setTimeout(() => {
+//     globalStore.setGlobalLoading(false)
+// }, 200)
 const cacheComponents = $ref('HomeRouter,ListsRouter,AboutRouter')
 const cacheComponents = $ref('HomeRouter,ListsRouter,AboutRouter')
 let transitionName = $ref('fade')
 let transitionName = $ref('fade')
 let metaIndex = $ref<number>(route.meta.index as number)
 let metaIndex = $ref<number>(route.meta.index as number)
@@ -86,9 +93,19 @@ const headerTitle = computed(() => {
     font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
     font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
 }
 }
 .page {
 .page {
-    padding: 0 20px 80px 20px;
+    padding: 0 20px 120px 20px;
     box-sizing: border-box;
     box-sizing: border-box;
     min-height: 100vh;
     min-height: 100vh;
     background: var(--main-bg);
     background: var(--main-bg);
 }
 }
+.request-loading {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    z-index: 9999;
+    background: rgba(0, 0, 0, 1);
+    padding: 12px;
+    border-radius: 8px;
+}
 </style>
 </style>

+ 118 - 58
src/api/ucard.ts

@@ -1,5 +1,4 @@
 import { $api } from '@/composables/fetch'
 import { $api } from '@/composables/fetch'
-
 // 基础响应接口
 // 基础响应接口
 export interface BaseResponse<T = any> {
 export interface BaseResponse<T = any> {
     code: number;
     code: number;
@@ -104,8 +103,6 @@ export interface TransferInfo {
 export interface TransferParams {
 export interface TransferParams {
     cardId: string;
     cardId: string;
     uniqueId: string;
     uniqueId: string;
-    cardNo: string;
-    amount: number;
     [key: string]: any;
     [key: string]: any;
 }
 }
 
 
@@ -119,160 +116,223 @@ export interface RateInfo {
 // 国家城市相关接口
 // 国家城市相关接口
 export interface CountryCityInfo {
 export interface CountryCityInfo {
     code: string;
     code: string;
-    name: string;
-    cities?: Array<{
-        code: string;
-        name: string;
-    }>;
+    cnName: string;
+    enName: string;
+}
+
+// 代付校验参数
+export interface ucardValidateParams{
+    bankId: number;
+    uniqueId: string;
+    originOrderNo: string;
+    amount: number;
+    postscript: string;
+    relationship: string;
+    sourceFunds: string;
+    payPurpose: string;
+    payerType: string;
+    payerLastName: string;
+    payerFirstName: string;
+    payerIdNo: string;
+    payerIdNoType: string;
+    payerIdCountry: string;
+    payerBirthday: string;
+    payerNationalityCountry: string;
+    payerMobile: string;
+    payerCountryCode: string;
+    payerCityCode: string;
+    payerAddress: string;
+    payerPostCode: string;
+    payerOccupation: string;
+    benAccountNum: string;
+    benAccountName: string;
+    benCountryCode: string;
+    benCityCode: string;
+    benAddress: string;
+    benPostCode: string;
+    benTransBankSwift: string;
+    benLastName: string;
+    benFirstName: string;
+    benNationalityCountry: string;
+    benIdNoType: string;
+    benIdNo: string;
+    benIdExpirationDate: string;
+    benBirthday: string;
+    benMobileCode: string;
+    benMobile: string;
+    benBankAccountType: string;
+    benBankCode: string;
+    payer: ucardValidateParams;
+    payee: ucardValidateParams;
 }
 }
 
 
+
 export const ucardApi = {
 export const ucardApi = {
     //获取卡片类型列表
     //获取卡片类型列表
     cardTypesList(params: CardTypeParams = { page: { current: 1, row: 10 } }): Promise<BaseResponse<PageResponse<CardType>>> {
     cardTypesList(params: CardTypeParams = { page: { current: 1, row: 10 } }): Promise<BaseResponse<PageResponse<CardType>>> {
-        return $api.post("/ucard/card/types/page", params);
+        return $api.post("/ucard/api/card/types/page", { ...params});
     },
     },
     // 更新卡片类型列表
     // 更新卡片类型列表
     updateCardTypes(params: Partial<CardType> = {}): Promise<BaseResponse> {
     updateCardTypes(params: Partial<CardType> = {}): Promise<BaseResponse> {
-        return $api.post("/ucard/card/types", params);
+        return $api.post("/ucard/api/card/types", { ...params });
     },
     },
     // 商户用户分页列表
     // 商户用户分页列表
     merchantList(params: MerchantParams = { page: { current: 1, row: 10 } }): Promise<BaseResponse<PageResponse<MerchantUser>>> {
     merchantList(params: MerchantParams = { page: { current: 1, row: 10 } }): Promise<BaseResponse<PageResponse<MerchantUser>>> {
-        return $api.post("/ucard/merchant/user/page", params);
+        return $api.post("/ucard/api/merchant/user/page", { ...params });
     },
     },
     // 查询商户账户信息
     // 查询商户账户信息
     merchantAccount(params: { merchantId: string }): Promise<BaseResponse<MerchantAccount>> {
     merchantAccount(params: { merchantId: string }): Promise<BaseResponse<MerchantAccount>> {
-        return $api.post("/ucard/merchant/account", params);
+        return $api.post("/ucard/api/merchant/account", { ...params });
     },
     },
     // 商户注册选择用户列表
     // 商户注册选择用户列表
     merchantSearch(params: { keyword: string }): Promise<BaseResponse<MerchantUser[]>> {
     merchantSearch(params: { keyword: string }): Promise<BaseResponse<MerchantUser[]>> {
-        return $api.post("/custom/search/ucard", params);
+        return $api.post("/custom/search/ucard", { ...params });
     },
     },
     // 商户用户注册
     // 商户用户注册
     merchantRegister(params: Partial<MerchantUser>): Promise<BaseResponse> {
     merchantRegister(params: Partial<MerchantUser>): Promise<BaseResponse> {
-        return $api.post("/ucard/merchant/user/register", params);
+        return $api.post("/ucard/api/merchant/user/register", { ...params });
     },
     },
     // 更新商户用户信息
     // 更新商户用户信息
     merchantUpdate(params: Partial<MerchantUser>): Promise<BaseResponse> {
     merchantUpdate(params: Partial<MerchantUser>): Promise<BaseResponse> {
-        return $api.post("/ucard/merchant/user/update", params);
+        return $api.post("/ucard/api/merchant/user/update", { ...params });
     },
     },
     // 上传KYC附件
     // 上传KYC附件
     kycUpload(params: FormData): Promise<BaseResponse<{ url: string }>> {
     kycUpload(params: FormData): Promise<BaseResponse<{ url: string }>> {
-        return $api.post("/ucard/merchant/kyc/upload", params);
+        return $api.post("/ucard/api/merchant/kyc/upload", { ...params });
     },
     },
     // 提交KYC认证
     // 提交KYC认证
     kycSubmit(params: KycParams): Promise<BaseResponse> {
     kycSubmit(params: KycParams): Promise<BaseResponse> {
-        return $api.post("/ucard/merchant/kyc/submit", params);
+        return $api.post("/ucard/api/merchant/kyc/submit", { ...params });
     },
     },
     // 查询KYC认证状态
     // 查询KYC认证状态
     kycStatus(params: { merchantId: string }): Promise<BaseResponse<KycInfo>> {
     kycStatus(params: { merchantId: string }): Promise<BaseResponse<KycInfo>> {
-        return $api.post("/ucard/merchant/kyc/status", params);
+        return $api.post("/ucard/api/merchant/kyc/status", { ...params });
     },
     },
     // 获取卡片申请列表
     // 获取卡片申请列表
     applyList(params: PageParams): Promise<BaseResponse<PageResponse<PageParams>>> {
     applyList(params: PageParams): Promise<BaseResponse<PageResponse<PageParams>>> {
-        return $api.post("/ucard/card/apply/page", params);
+        return $api.post("/ucard/api/card/apply/page", { ...params });
     },
     },
     // 获取卡片列表
     // 获取卡片列表
     cardList(params: PageParams): Promise<BaseResponse<PageResponse<PageParams>>> {
     cardList(params: PageParams): Promise<BaseResponse<PageResponse<PageParams>>> {
-        return $api.post("/ucard/card/page", params);
+        return $api.post("/ucard/api/card/list", { ...params });
     },
     },
     // 充值记录分页查询
     // 充值记录分页查询
     rechargeList(params: TransactionParams): Promise<BaseResponse<PageResponse<TransactionInfo>>> {
     rechargeList(params: TransactionParams): Promise<BaseResponse<PageResponse<TransactionInfo>>> {
-        return $api.post("/ucard/card/recharge/page", params);
+        return $api.post("/ucard/api/card/recharge/page", { ...params });
     },
     },
     // 查询交易记录分页列表
     // 查询交易记录分页列表
     transactionsList(params: TransactionParams): Promise<BaseResponse<PageResponse<TransactionInfo>>> {
     transactionsList(params: TransactionParams): Promise<BaseResponse<PageResponse<TransactionInfo>>> {
-        return $api.post("/ucard/card/transac/page", params);
+        return $api.post("/ucard/api/card/transac/page", { ...params });
     },
     },
     // 用户订单分页查询
     // 用户订单分页查询
     transferList(params: TransactionParams): Promise<BaseResponse<PageResponse<TransferInfo>>> {
     transferList(params: TransactionParams): Promise<BaseResponse<PageResponse<TransferInfo>>> {
-        return $api.post("/ucard/transfer/page", params);
+        return $api.post("/ucard/api/transfer/page", { ...params });
     },
     },
     // 文件上传
     // 文件上传
     ucardUpload(params: FormData): Promise<BaseResponse<{ url: string }>> {
     ucardUpload(params: FormData): Promise<BaseResponse<{ url: string }>> {
-        return $api.post("/ucard/upload/file", params);
+        return $api.post("/ucard/api/upload/file", { ...params });
     },
     },
     // 申请开卡
     // 申请开卡
     ucardApply(params: CardApplyParams): Promise<BaseResponse> {
     ucardApply(params: CardApplyParams): Promise<BaseResponse> {
-        return $api.post("/ucard/card/apply", params);
+        return $api.post("/ucard/api/card/apply", { ...params });
     },
     },
     // kyc列表
     // kyc列表
     kycList(params: PageParams): Promise<BaseResponse<PageResponse<KycInfo>>> {
     kycList(params: PageParams): Promise<BaseResponse<PageResponse<KycInfo>>> {
-        return $api.post("/ucard/merchant/kyc/page", params);
+        return $api.post("/ucard/api/merchant/kyc/page", { ...params });
+    },
+    // 获取kyc卡片类型列表
+    cardKycTypesList(params: PageParams): Promise<BaseResponse<PageResponse<CardType>>> {
+        return $api.post("/ucard/api/card/types/list", { ...params });
     },
     },
     // 查询开卡进度
     // 查询开卡进度
     ucardApplyProgress(params: { applyId: string }): Promise<BaseResponse<{ status: number; message: string }>> {
     ucardApplyProgress(params: { applyId: string }): Promise<BaseResponse<{ status: number; message: string }>> {
-        return $api.post("/ucard/card/apply/progress", params);
+        return $api.post("/ucard/api/card/apply/progress", { ...params });
     },
     },
     // 银行卡激活
     // 银行卡激活
-    ucardActivate(params: { cardId: string; password: string }): Promise<BaseResponse> {
-        return $api.post("/ucard/card/activate", params);
+    ucardActivate(params: { cardNo: string; uniqueId: string, password:string }): Promise<BaseResponse> {
+        return $api.post("/ucard/api/card/activate", { ...params });
     },
     },
     // 查询充值预估到账金额
     // 查询充值预估到账金额
     ucardRechargeEstimate(params: { amount: number; currency: string }): Promise<BaseResponse<{ estimatedAmount: number }>> {
     ucardRechargeEstimate(params: { amount: number; currency: string }): Promise<BaseResponse<{ estimatedAmount: number }>> {
-        return $api.post("/ucard/card/recharge/estimate", params);
+        return $api.post("/ucard/api/card/recharge/estimate", { ...params });
     },
     },
     // 银行卡充值
     // 银行卡充值
-    ucardRecharge(params: { cardId: string; amount: number; currency: string }): Promise<BaseResponse> {
-        return $api.post("/ucard/card/recharge", params);
+    ucardRecharge(params: { cardNo: string; amount: number; currency: string,uniqueId:string }): Promise<BaseResponse> {
+        return $api.post("/ucard/api/card/recharge", { ...params });
     },
     },
     // 查询充值订单
     // 查询充值订单
     ucardRechargeOrder(params: { orderId: string }): Promise<BaseResponse<TransactionInfo>> {
     ucardRechargeOrder(params: { orderId: string }): Promise<BaseResponse<TransactionInfo>> {
-        return $api.post("/ucard/card/recharge/order", params);
+        return $api.post("/ucard/api/card/recharge/order", { ...params });
     },
     },
     // 查询卡片余额
     // 查询卡片余额
     ucardBalance(params: { cardNo: string; uniqueId: string }): Promise<BaseResponse<{ balance: number; currency: string }>> {
     ucardBalance(params: { cardNo: string; uniqueId: string }): Promise<BaseResponse<{ balance: number; currency: string }>> {
-        return $api.post("/ucard/card/balance", params);
+        return $api.post("/ucard/api/card/balance", { ...params });
     },
     },
     // 找回密码
     // 找回密码
-    ucardResetPassword(params: { cardId: string; newPassword: string }): Promise<BaseResponse> {
-        return $api.post("/ucard/card/password/reset", params);
+    ucardResetPassword(params: { cardNo: string; newPassword: string }): Promise<BaseResponse> {
+        return $api.post("/ucard/api/card/password/reset", { ...params });
     },
     },
     // 冻结卡片
     // 冻结卡片
-    ucardFreeze(params: { cardId: string }): Promise<BaseResponse> {
-        return $api.post("/ucard/card/freeze", params);
+    ucardFreeze(params: { cardNo: string; uniqueId: string }): Promise<BaseResponse> {
+        return $api.post("/ucard/api/card/freeze", { ...params });
     },
     },
     // 解冻卡片
     // 解冻卡片
-    ucardUnfreeze(params: { cardId: string }): Promise<BaseResponse> {
-        return $api.post("/ucard/card/unfreeze", params);
+    ucardUnfreeze(params: { cardNo: string; uniqueId: string }): Promise<BaseResponse> {
+        return $api.post("/ucard/api/card/unfreeze", { ...params });
     },
     },
     // 查询速汇银行及相关配置
     // 查询速汇银行及相关配置
-    ucardBanks(params: { country: string }): Promise<BaseResponse<Array<{ bankCode: string; bankName: string }>>> {
-        return $api.post("/ucard/transfer/banks", params);
+    ucardBanks(params: {}): Promise<BaseResponse<Array<{ bankCode: string; bankName: string }>>> {
+        return $api.post("/ucard/api/transfer/banks", { ...params });
     },
     },
     // 查询法币汇率
     // 查询法币汇率
     ucardRate(params: { fromCurrency: string; toCurrency: string }): Promise<BaseResponse<RateInfo>> {
     ucardRate(params: { fromCurrency: string; toCurrency: string }): Promise<BaseResponse<RateInfo>> {
-        return $api.post("/ucard/transfer/rate", params);
+        return $api.post("/ucard/api/transfer/rate", { ...params });
     },
     },
     // 代付校验
     // 代付校验
-    ucardValidate(params: TransferParams): Promise<BaseResponse<{ valid: boolean; message: string }>> {
-        return $api.post("/ucard/transfer/validate", params);
+    ucardValidate(params: ucardValidateParams): Promise<BaseResponse<{ valid: boolean; message: string }>> {
+        return $api.post("/ucard/api/transfer/validate", { ...params });
     },
     },
     // 代付付款人校验
     // 代付付款人校验
-    ucardValidatePayer(params: { payerId: string }): Promise<BaseResponse<{ valid: boolean; message: string }>> {
-        return $api.post("/ucard/transfer/validate/payer", params);
+    ucardValidatePayer(params: ucardValidateParams): Promise<BaseResponse<{ valid: boolean; message: string }>> {
+        return $api.post("/ucard/api/transfer/validate/payer", { ...params });
     },
     },
     // 代付收款人校验
     // 代付收款人校验
-    ucardValidatePayee(params: { payeeId: string }): Promise<BaseResponse<{ valid: boolean; message: string }>> {
-        return $api.post("/ucard/transfer/validate/payee", params);
+    ucardValidatePayee(params: ucardValidateParams): Promise<BaseResponse<{ valid: boolean; message: string }>> {
+        return $api.post("/ucard/api/transfer/validate/payee", { ...params });
     },
     },
     // 代付
     // 代付
-    ucardTransfer(params: TransferParams): Promise<BaseResponse<{ orderId: string }>> {
-        return $api.post("/ucard/transfer", params);
+    ucardTransfer(params: ucardValidateParams): Promise<BaseResponse<{ orderId: string }>> {
+        return $api.post("/ucard/api/transfer", { ...params });
     },
     },
     // 提交调单信息或文件
     // 提交调单信息或文件
     ucardDispute(params: { orderId: string; reason: string; files?: FormData }): Promise<BaseResponse> {
     ucardDispute(params: { orderId: string; reason: string; files?: FormData }): Promise<BaseResponse> {
-        return $api.post("/ucard/transfer/dispute", params);
+        return $api.post("/ucard/api/transfer/dispute", { ...params });
     },
     },
     // 查询订单结果
     // 查询订单结果
     ucardResult(params: { orderId: string }): Promise<BaseResponse<{ status: number; message: string }>> {
     ucardResult(params: { orderId: string }): Promise<BaseResponse<{ status: number; message: string }>> {
-        return $api.post("/ucard/transfer/order/result", params);
+        return $api.post("/ucard/api/transfer/order/result", { ...params });
     },
     },
     // 国家城市
     // 国家城市
-    ucardCountryCity(params: { country?: string }): Promise<BaseResponse<CountryCityInfo[]>> {
-        return $api.post("/ucard/card/country", params);
+    ucardCountryCity(params: { code?: string }): Promise<BaseResponse<CountryCityInfo[]>> {
+        return $api.post("/ucard/api/card/country", { ...params });
     },
     },
     // 手机区号获取
     // 手机区号获取
-    countryGet(params: { keyword?: string }): Promise<BaseResponse<Array<{ code: string; name: string }>>> {
-        return $api.post("/country/get", params);
+    countryGet(): Promise<BaseResponse<Array<{ code: string; name: string,enName:string,callingCode:string }>>> {
+        return $api.post("/country/get",{});
+    },
+    // 获取卡片详情
+    cardSingle(params: { id: string }): Promise<BaseResponse<CardInfo>> {
+        return $api.post("/ucard/api/card/single", { ...params });
+    },
+    // 获取卡片交易详情
+    cardTransacSingle(params: { id: string }): Promise<BaseResponse<TransactionInfo>> {
+        return $api.post("/ucard/api/card/transac/single", { ...params });
     },
     },
-}
+    ucardOrderDetail: (params: { orderNo: string }) => {
+        return $api.get('/api/ucard/order/detail', { params })
+    },
+    // 速汇转账详情
+    ucardTransferSingle(params: { id: string }): Promise<BaseResponse<TransferInfo>> {
+        return $api.post('/ucard/api/transfer/single', params)
+    },
+}

+ 1 - 1
src/api/upload.ts

@@ -26,6 +26,6 @@ export interface ResetPasswordParams {
 export const uploadApi = {
 export const uploadApi = {
   // 登录
   // 登录
   uploadFile: (file: File) => {
   uploadFile: (file: File) => {
-    return $api.uploadFile<string>('/ucard/upload/file', file)
+    return $api.uploadFile<string>('/ucard/api/upload/file', file)
   }
   }
 }
 }

+ 28 - 23
src/api/user.ts

@@ -2,38 +2,43 @@ import { $api } from '@/composables/fetch'
 import type { UserInfo } from '@/stores/use-user-store'
 import type { UserInfo } from '@/stores/use-user-store'
 
 
 export interface LoginParams {
 export interface LoginParams {
-  loginName: string
-  password: string
-  emailCode: string
+    loginName: string
+    password: string
 }
 }
 export interface TokenInfo {
 export interface TokenInfo {
-  data: string
+    data: string
 }
 }
 
 
 export interface RegisterParams extends LoginParams {
 export interface RegisterParams extends LoginParams {
-  email: string
-  phone: string
+    email: string
+    phone: string
 }
 }
 
 
 export interface ResetPasswordParams {
 export interface ResetPasswordParams {
-  email: string
-  code: string
-  newPassword: string
+    email: string
+    code: string
+    newPassword: string
 }
 }
 
 
 export const userApi = {
 export const userApi = {
-  // 登录
-  login: (params: LoginParams) => {
-    return $api.post<string>('/user/login', params)
-  },
-
-  // 重置密码
-  resetPassword: (params: ResetPasswordParams) => {
-    return $api.post('/user/reset-password', params)
-  },
-
-  // 获取用户信息
-  getUserInfo: () => {
-    return $api.post<UserInfo>('/user/info')
-  },
+    // 登录
+    login: (params: LoginParams) => {
+        return $api.post<string>('/custom/login', params)
+    },
+    // 获取用户信息
+    getUserInfo: () => {
+        return $api.post<UserInfo>('/custom/info')
+    },
+    //退出登录
+    logout: () => {
+        return $api.post('/custom/logout', {})
+    },
+    //忘记密码
+    updatePassword: (params: { email: string }) => {
+        return $api.post('/custom/update/password/send/email', params)
+    },
+    //用户余额
+    walletBalance: (params= {}) => {
+        return $api.post('/custom/get/balance', params)
+    }
 }
 }

+ 76 - 4
src/assets/scss/global/global.scss

@@ -19,6 +19,7 @@
     --gray: #aaa;
     --gray: #aaa;
     --border: #333;
     --border: #333;
     --black: #000;
     --black: #000;
+    --font-size-10: 10px;
     --font-size-12: 12px;
     --font-size-12: 12px;
     --font-size-13: 13px;
     --font-size-13: 13px;
     --font-size-14: 14px;
     --font-size-14: 14px;
@@ -37,7 +38,7 @@
 
 
 html {
 html {
     --body-width: #{$vmDesignWidth}px;
     --body-width: #{$vmDesignWidth}px;
-    --tabbar-height: 50px;
+    --tabbar-height: 80px;
 
 
     font-size: math.div($vmFontSize, $vmDesignWidth) * 100vw;
     font-size: math.div($vmFontSize, $vmDesignWidth) * 100vw;
 
 
@@ -109,7 +110,7 @@ body {
     align-items: center;
     align-items: center;
     width: 100vw;
     width: 100vw;
     height: 100vh;
     height: 100vh;
-    background: rgba(255, 255, 255, 0.1);
+    background: var(--main-bg);
 }
 }
 
 
 .router-loading {
 .router-loading {
@@ -122,7 +123,78 @@ body {
     align-items: center;
     align-items: center;
     width: 100vw;
     width: 100vw;
     height: 100vh;
     height: 100vh;
-    background: rgba(255, 255, 255, 0.1);
+    background: var(--main-bg);
+}
+
+.full-screen-loading {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 1001;
+    width: 100vw;
+    height: 100vh;
+    background: var(--main-bg);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.request-loading,
+.full-screen-loading {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    z-index: 1001;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    padding: 16px 24px;
+    background: rgba(0, 0, 0, 0.88);
+    border-radius: 12px;
+    backdrop-filter: blur(4px);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+    animation: fadeIn 0.2s ease-in-out;
+
+    .van-loading {
+        margin-bottom: 8px;
+    }
+
+    .loading-text {
+        color: #fff;
+        font-size: 14px;
+        font-weight: 500;
+        letter-spacing: 0.5px;
+        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+    }
+
+    &::after {
+        content: '';
+        position: absolute;
+        top: -1px;
+        left: -1px;
+        right: -1px;
+        bottom: -1px;
+        border-radius: 12px;
+        background: linear-gradient(45deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
+        z-index: -1;
+    }
+}
+.full-screen-loading {
+    width: 100vw;
+    height: 100vh;
+}
+
+@keyframes fadeIn {
+    from {
+        opacity: 0;
+        transform: translate(-50%, -48%);
+    }
+    to {
+        opacity: 1;
+        transform: translate(-50%, -50%);
+    }
 }
 }
 
 
 .load-more {
 .load-more {
@@ -246,4 +318,4 @@ body {
     border-width: 1PX;
     border-width: 1PX;
 }
 }
 
 
-/* end--Retina 屏幕下的 1px 边框--end */
+/* end--Retina 屏幕下的 1px 边框--end */

+ 1 - 0
src/assets/scss/global/vant.scss

@@ -46,3 +46,4 @@
         }
         }
     }
     }
 }
 }
+

+ 2 - 4
src/auto-imports.d.ts

@@ -6,12 +6,10 @@
 // biome-ignore lint: disable
 // biome-ignore lint: disable
 export {}
 export {}
 declare global {
 declare global {
-  const $Api: typeof import('./api/index')['$Api']
   const $api: typeof import('./composables/fetch')['$api']
   const $api: typeof import('./composables/fetch')['$api']
   const CLIENT: typeof import('./composables/config')['CLIENT']
   const CLIENT: typeof import('./composables/config')['CLIENT']
   const EffectScope: typeof import('vue')['EffectScope']
   const EffectScope: typeof import('vue')['EffectScope']
   const UTC2Date: typeof import('@lincy/utils')['UTC2Date']
   const UTC2Date: typeof import('@lincy/utils')['UTC2Date']
-  const UseTabLists: typeof import('./composables/index')['UseTabLists']
   const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
   const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
   const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
   const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
   const closeToast: typeof import('vant')['closeToast']
   const closeToast: typeof import('vant')['closeToast']
@@ -275,7 +273,6 @@ declare global {
   const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
   const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
   const useScroll: typeof import('@vueuse/core')['useScroll']
   const useScroll: typeof import('@vueuse/core')['useScroll']
   const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
   const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
-  const useSeoMeta: typeof import('@vueuse/head')['useSeoMeta']
   const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
   const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
   const useShare: typeof import('@vueuse/core')['useShare']
   const useShare: typeof import('@vueuse/core')['useShare']
   const useSlots: typeof import('vue')['useSlots']
   const useSlots: typeof import('vue')['useSlots']
@@ -349,7 +346,7 @@ declare global {
   export type { GlobalState } from './stores/pinia.types'
   export type { GlobalState } from './stores/pinia.types'
   import('./stores/pinia.types')
   import('./stores/pinia.types')
   // @ts-ignore
   // @ts-ignore
-  export type { UserInfo } from './stores/use-user-store'
+  export type { UserInfo, AccountInfo } from './stores/use-user-store'
   import('./stores/use-user-store')
   import('./stores/use-user-store')
 }
 }
 
 
@@ -454,6 +451,7 @@ declare module 'vue' {
     readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
     readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
     readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
     readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
     readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
     readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
+    readonly rememberPassword: UnwrapRef<typeof import('./composables/config')['rememberPassword']>
     readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
     readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
     readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
     readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
     readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
     readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>

+ 3 - 0
src/components.d.ts

@@ -12,9 +12,11 @@ declare module 'vue' {
     CurrencySelect: typeof import('./components/CurrencySelect.vue')['default']
     CurrencySelect: typeof import('./components/CurrencySelect.vue')['default']
     CustomTabbar: typeof import('./components/CustomTabbar.vue')['default']
     CustomTabbar: typeof import('./components/CustomTabbar.vue')['default']
     EmptyComponents: typeof import('./components/empty-components.vue')['default']
     EmptyComponents: typeof import('./components/empty-components.vue')['default']
+    EmptyState: typeof import('./components/EmptyState.vue')['default']
     ImgList: typeof import('./components/img-list.vue')['default']
     ImgList: typeof import('./components/img-list.vue')['default']
     JsxComponents: typeof import('./components/jsx-components.tsx')['default']
     JsxComponents: typeof import('./components/jsx-components.tsx')['default']
     PageHeader: typeof import('./components/PageHeader.vue')['default']
     PageHeader: typeof import('./components/PageHeader.vue')['default']
+    RemitInput: typeof import('./components/RemitInput.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     RouterView: typeof import('vue-router')['RouterView']
     SelectCard: typeof import('./views/select-card.vue')['default']
     SelectCard: typeof import('./views/select-card.vue')['default']
@@ -49,5 +51,6 @@ declare module 'vue' {
     VanTabbarItem: typeof import('vant/es')['TabbarItem']
     VanTabbarItem: typeof import('vant/es')['TabbarItem']
     VanTabs: typeof import('vant/es')['Tabs']
     VanTabs: typeof import('vant/es')['Tabs']
     VanUploader: typeof import('vant/es')['Uploader']
     VanUploader: typeof import('vant/es')['Uploader']
+    VirtualCard: typeof import('./components/VirtualCard.vue')['default']
   }
   }
 }
 }

+ 35 - 11
src/components/ApplyCard.vue

@@ -21,32 +21,62 @@
         <button class="apply-btn" @click="handleApply">申请银行卡</button>
         <button class="apply-btn" @click="handleApply">申请银行卡</button>
         <button class="activate-btn" @click="handleActivate">已收到卡片? 立即激活</button>
         <button class="activate-btn" @click="handleActivate">已收到卡片? 立即激活</button>
         <div class="apply-card-footer">
         <div class="apply-card-footer">
-            <div class="apply-card-empty">
+            <EmptyState v-if="cardList.length === 0" icon="i-mdi-receipt-text-outline" text="暂无卡片" />
+            <!-- <div class="apply-card-empty">
+                <svg
+                    t="1749207046076"
+                    class="icon"
+                    viewBox="0 0 1024 1024"
+                    version="1.1"
+                    xmlns="http://www.w3.org/2000/svg"
+                    p-id="2492"
+                    width="48"
+                    height="48"
+                >
+                    <path
+                        d="M283.473171 71.180488h283.47317c16.234146 0 28.721951-12.487805 28.721952-28.721951 0-16.234146-12.487805-28.721951-28.721952-28.721952H283.473171c-16.234146 0-28.721951 12.487805-28.721951 28.721952 0 16.234146 12.487805 28.721951 28.721951 28.721951z"
+                        fill="rgb(6 255 139)"
+                        p-id="2493"
+                    ></path>
+                    <path
+                        d="M709.307317 826.692683l-3.746341 3.746341c-6.243902 6.243902-14.985366 9.990244-23.72683 9.990244s-18.731707-3.746341-24.975609-11.239024c-6.243902-7.492683-8.741463-16.234146-8.741464-24.97561V799.219512c8.741463-78.673171 29.970732-154.84878 62.439025-226.029268L849.170732 274.731707V71.180488c0-31.219512-24.97561-57.443902-56.195122-57.443903h-109.892683c-6.243902 0-12.487805 4.995122-13.736586 11.239025l-21.229268 81.170731c-3.746341 12.487805-14.985366 21.229268-27.473171 21.229269H228.526829c-12.487805 0-23.726829-8.741463-27.47317-21.229269L179.82439 24.97561c-1.24878-6.243902-7.492683-11.239024-13.736585-11.239025H56.195122C24.97561 14.985366 0 39.960976 0 71.180488v881.639024c0 31.219512 24.97561 57.443902 56.195122 57.443903h735.531707c31.219512 0 56.195122-24.97561 56.195122-57.443903V613.15122l-8.741463 18.731707c-31.219512 71.180488-74.926829 137.365854-129.873171 194.809756zM196.058537 333.42439h455.804878c14.985366 0 27.473171 12.487805 27.47317 27.473171 0 14.985366-12.487805 27.473171-27.47317 27.473171H196.058537c-14.985366 0-27.473171-12.487805-27.473171-27.473171 1.24878-14.985366 12.487805-27.473171 27.473171-27.473171zM524.487805 847.921951H189.814634c-14.985366 0-27.473171-12.487805-27.473171-27.473171s12.487805-27.473171 27.473171-27.47317H524.487805c14.985366 0 27.473171 12.487805 27.473171 27.47317s-12.487805 27.473171-27.473171 27.473171z m68.682927-229.77561H196.058537c-14.985366 0-27.473171-12.487805-27.473171-27.47317 0-14.985366 12.487805-27.473171 27.473171-27.473171h397.112195c14.985366 0 27.473171 12.487805 27.47317 27.473171 0 14.985366-12.487805 27.473171-27.47317 27.47317z"
+                        fill="rgb(6 255 139)"
+                        p-id="2494"
+                    ></path>
+                    <path
+                        d="M1000.273171 109.892683c-19.980488-9.990244-44.956098 0-53.697561 19.980488L738.029268 584.429268c-31.219512 68.682927-51.2 142.360976-59.941463 217.287805v4.995122c0 2.497561 3.746341 4.995122 4.995122 2.497561l3.746341-3.746341c51.2-54.946341 93.658537-118.634146 124.878049-187.317074l208.546342-454.556097c9.990244-19.980488 0-43.707317-19.980488-53.697561z"
+                        fill="rgb(6 255 139)"
+                        p-id="2495"
+                    ></path>
+                </svg>
                 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
                 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
                     <rect x="8" y="12" width="32" height="24" rx="4" fill="#232323" stroke="rgb(6 255 139)" stroke-width="2" />
                     <rect x="8" y="12" width="32" height="24" rx="4" fill="#232323" stroke="rgb(6 255 139)" stroke-width="2" />
                     <rect x="20" y="20" width="8" height="2" rx="1" fill="rgb(6 255 139)" />
                     <rect x="20" y="20" width="8" height="2" rx="1" fill="rgb(6 255 139)" />
                     <circle cx="34" cy="34" r="6" fill="rgb(6 255 139)" />
                     <circle cx="34" cy="34" r="6" fill="rgb(6 255 139)" />
                     <rect x="32" y="32" width="4" height="2" rx="1" fill="#232323" />
                     <rect x="32" y="32" width="4" height="2" rx="1" fill="#232323" />
                 </svg>
                 </svg>
+
                 <div class="empty-text">暂无卡片</div>
                 <div class="empty-text">暂无卡片</div>
-            </div>
+            </div> -->
         </div>
         </div>
     </div>
     </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
-
+import EmptyState from '@/components/EmptyState.vue'
+const cardList = ref<any[]>([])
 const router = useRouter()
 const router = useRouter()
 
 
 const handleActivate = () => {
 const handleActivate = () => {
+    return
     router.push('/activate/card')
     router.push('/activate/card')
 }
 }
 
 
 const handleApply = () => {
 const handleApply = () => {
+    return
     router.push('/select/card')
     router.push('/select/card')
 }
 }
-
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
@@ -126,13 +156,7 @@ const handleApply = () => {
 .step-dash {
 .step-dash {
     flex: 1;
     flex: 1;
     height: 2px;
     height: 2px;
-    background: repeating-linear-gradient(
-        to right,
-        var(--main-yellow),
-        var(--main-yellow) 6px,
-        transparent 6px,
-        transparent 12px
-    );
+    background: repeating-linear-gradient(to right, var(--main-yellow), var(--main-yellow) 6px, transparent 6px, transparent 12px);
     min-width: 18px;
     min-width: 18px;
     position: relative;
     position: relative;
     top: -16px;
     top: -16px;

+ 73 - 75
src/components/CurrencySelect.vue

@@ -1,82 +1,80 @@
 <template>
 <template>
-  <van-popup v-model:show="show" round position="bottom" :style="{ background: 'transparent', boxShadow: 'none' }">
-    <div class="currency-mask">
-      <div class="currency-select">
-        <div
-          v-for="item in options"
-          :key="item.value"
-          class="currency-item"
-          @click="select(item)"
-        >
-          {{ item.label }}
+    <van-popup v-model:show="show" round position="bottom" :style="{ background: 'transparent', boxShadow: 'none' }">
+      <div class="currency-mask">
+        <div class="currency-select">
+          <div
+            v-for="item in options"
+            :key="item.value"
+            class="currency-item"
+            @click="select(item)"
+          >
+            {{ item.label }}
+          </div>
+          <div class="cancel-btn" @click="close">取消</div>
         </div>
         </div>
-        <div class="cancel-btn" @click="close">取消</div>
       </div>
       </div>
-    </div>
-  </van-popup>
-</template>
+    </van-popup>
+  </template>
 
 
-<script setup lang="ts">
-import { ref, watch } from 'vue'
+  <script setup lang="ts">
+  const props = defineProps<{
+    modelValue: boolean
+    options: Array<{ label: string; value: string }>
+  }>()
+  const emit = defineEmits(['update:modelValue', 'select'])
 
 
-const props = defineProps<{
-  modelValue: boolean
-  options: Array<{ label: string; value: string }>
-}>()
-const emit = defineEmits(['update:modelValue', 'select'])
+  const show = ref(props.modelValue)
+  watch(() => props.modelValue, val => (show.value = val))
+  watch(show, val => emit('update:modelValue', val))
 
 
-const show = ref(props.modelValue)
-watch(() => props.modelValue, val => (show.value = val))
-watch(show, val => emit('update:modelValue', val))
+  function select(item: { label: string; value: string }) {
+    emit('select', item)
+    show.value = false
+  }
+  function close() {
+    show.value = false
+  }
+  </script>
 
 
-function select(item: { label: string; value: string }) {
-  emit('select', item)
-  show.value = false
-}
-function close() {
-  show.value = false
-}
-</script>
-
-<style scoped>
-.currency-mask {
-  position: fixed;
-  inset: 0;
-  background: rgba(0, 0, 0, 0.45);
-  display: flex;
-  align-items: flex-end;
-  justify-content: center;
-  z-index: 1;
-}
-.currency-select {
-  width: 100vw;
-  padding: 0 0 22px 0;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  z-index: 2;
-}
-.currency-item {
-  width: 90vw;
-  padding: 20px 0;
-  text-align: center;
-  font-size: 22px;
-  background: #eaff00;
-  color: #111;
-  margin-bottom: 16px;
-  border-radius: 20px;
-  font-weight: 500;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
-}
-.cancel-btn {
-  width: 90vw;
-  padding: 20px 0;
-  text-align: center;
-  color: #111;
-  background: #eaff00;
-  border-radius: 20px;
-  font-weight: bold;
-  font-size: 22px;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
-}
-</style>
+  <style scoped>
+  .currency-mask {
+    position: fixed;
+    inset: 0;
+    background: rgba(0, 0, 0, 0.45);
+    display: flex;
+    align-items: flex-end;
+    justify-content: center;
+    z-index: 1;
+  }
+  .currency-select {
+    width: 100vw;
+    padding: 0 0 22px 0;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    z-index: 2;
+  }
+  .currency-item {
+    width: 90vw;
+    padding: 20px 0;
+    text-align: center;
+    font-size: 22px;
+    background: var(--main-yellow);
+    color: #111;
+    margin-bottom: 16px;
+    border-radius: 20px;
+    font-weight: 500;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
+  }
+  .cancel-btn {
+    width: 90vw;
+    padding: 20px 0;
+    text-align: center;
+    color: #111;
+    background: var(--main-yellow);
+    border-radius: 20px;
+    font-weight: bold;
+    font-size: 22px;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
+  }
+  </style>

+ 97 - 96
src/components/CustomTabbar.vue

@@ -1,103 +1,104 @@
 <template>
 <template>
-  <nav class="custom-tabbar">
-    <div
-      v-for="item in tabs"
-      :key="item.path"
-      :class="['tabbar-item', { active: route.path === item.path }]"
-      @click="onTabClick(item.path)"
-    >
-      <span class="tabbar-icon">
-        <van-icon :name="route.path === item.path ? item.iconActive : item.icon" />
-      </span>
-      <span class="tabbar-label">{{ item.label }}</span>
-    </div>
-  </nav>
-</template>
+    <nav class="custom-tabbar">
+      <div
+        v-for="item in tabs"
+        :key="item.path"
+        :class="['tabbar-item', { active: route.path === item.path }]"
+        @click="onTabClick(item.path)"
+      >
+        <span class="tabbar-icon">
+          <van-icon :name="route.path === item.path ? item.iconActive : item.icon" />
+        </span>
+        <span class="tabbar-label">{{ item.label }}</span>
+      </div>
+    </nav>
+  </template>
 
 
-<script setup lang="ts">
-import { useI18n } from 'vue-i18n'
-import { useRouter, useRoute } from 'vue-router'
-import { Icon as VanIcon } from 'vant'
+  <script setup lang="ts">
+  import { useI18n } from 'vue-i18n'
+  import { useRouter, useRoute } from 'vue-router'
+  import { Icon as VanIcon } from 'vant'
 
 
-const { t } = useI18n()
-const router = useRouter()
-const route = useRoute()
+  const { t } = useI18n()
+  const router = useRouter()
+  const route = useRoute()
 
 
-const tabs = [
-  {
-    label: t('tabs.wallet'),
-    path: '/',
-    icon: 'balance-o',
-    iconActive: 'balance-o',
-  },
-  {
-    label: t('tabs.cards'),
-    path: '/cards',
-    icon: 'credit-pay',
-    iconActive: 'credit-pay',
-  },
-//   {
-//     label: t('tabs.finance'),
-//     path: '/finance',
-//     icon: 'gold-coin-o',
-//     iconActive: 'gold-coin',
-//   },
-  {
-    label: t('tabs.mine'),
-    path: '/mine',
-    icon: 'user-o',
-    iconActive: 'user',
-  },
-]
+  const tabs = [
+    {
+      label: t('tabs.wallet'),
+      path: '/',
+      icon: 'balance-o',
+      iconActive: 'balance-o',
+    },
+    {
+      label: t('tabs.cards'),
+      path: '/cards',
+      icon: 'credit-pay',
+      iconActive: 'credit-pay',
+    },
+  //   {
+  //     label: t('tabs.finance'),
+  //     path: '/finance',
+  //     icon: 'gold-coin-o',
+  //     iconActive: 'gold-coin',
+  //   },
+    {
+      label: t('tabs.mine'),
+      path: '/mine',
+      icon: 'user-o',
+      iconActive: 'user',
+    },
+  ]
 
 
-function onTabClick(path: string) {
-  if (route.path !== path) {
-    router.replace(path)
+  function onTabClick(path: string) {
+    if (route.path !== path) {
+      router.replace(path)
+    }
   }
   }
-}
-</script>
+  </script>
 
 
-<style scoped>
-.custom-tabbar {
-  position: fixed;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  height: 60px;
-  background: #181a1b;
-  border-radius: 18px 18px 0 0;
-  display: flex;
-  justify-content: space-around;
-  align-items: center;
-  box-shadow: 0 -2px 16px rgba(0,0,0,0.12);
-  z-index: 100;
-  margin: 0 8px 8px 8px;
-}
-.tabbar-item {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  color: var(--white);
-  font-size: var(--font-size-12);
-  border-radius: 12px;
-  padding: 8px 0;
-  cursor: pointer;
-}
-.tabbar-item.active {
-  color: var(--main-yellow);
-}
-.tabbar-icon {
-  margin-bottom: 4px;
-  font-size: var(--font-size-24);
-}
-.tabbar-icon-active {
-  color: var(--main-yellow);
-}
-.tabbar-label {
-  font-weight: 500;
-  line-height: 1.2;
-  letter-spacing: 1px;
-}
-</style>
+  <style scoped>
+  .custom-tabbar {
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    height: 60px;
+    background: #181a1b;
+    border-radius: 18px 18px 0 0;
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+    box-shadow: 0 -2px 16px rgba(0,0,0,0.12);
+    z-index: 100;
+    margin: 0 8px 0px 8px;
+    padding-bottom: 8px;
+  }
+  .tabbar-item {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    color: var(--white);
+    font-size: var(--font-size-12);
+    border-radius: 12px;
+    padding: 8px 0;
+    cursor: pointer;
+  }
+  .tabbar-item.active {
+    color: var(--main-yellow);
+  }
+  .tabbar-icon {
+    margin-bottom: 4px;
+    font-size: var(--font-size-24);
+  }
+  .tabbar-icon-active {
+    color: var(--main-yellow);
+  }
+  .tabbar-label {
+    font-weight: 500;
+    line-height: 1.2;
+    letter-spacing: 1px;
+  }
+  </style>

+ 38 - 0
src/components/EmptyState.vue

@@ -0,0 +1,38 @@
+<template>
+    <div class="empty-state">
+        <i :class="icon"></i>
+        <p>{{ text }}</p>
+    </div>
+</template>
+
+<script setup lang="ts">
+withDefaults(defineProps<{
+    icon?: string
+    text?: string
+}>(), {
+    icon: 'i-mdi-receipt-text-outline',
+    text: '暂无数据'
+})
+</script>
+
+<style scoped lang="scss">
+.empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 40px 0;
+    color: var(--gray);
+
+    i {
+        font-size: 48px;
+        margin-bottom: 16px;
+        opacity: 0.5;
+        color: var(--main-yellow);
+    }
+
+    p {
+        font-size: var(--font-size-14);
+    }
+}
+</style>

+ 394 - 0
src/components/RemitInput.vue

@@ -0,0 +1,394 @@
+<template>
+    <div class="form-group">
+        <label class="form-label" v-if="label"
+            ><span>{{ required ? '*' : '' }}</span> {{ label }}
+        </label>
+        <template v-if="type === 'text'">
+            <van-field
+                class="form-input"
+                v-model="inputValueDoc"
+                :placeholder="placeholder"
+                :readonly="readonly"
+                :disabled="disabled"
+                :clearable="clearable"
+                :rules="[...rules]"
+                :maxlength="maxlength"
+                autocomplete="off"
+                :error-message="errorMessage"
+                @blur="handleBlur"
+                @focus="handleFocus"
+                @clear="handleClear"
+            />
+        </template>
+        <template v-if="type === 'number'">
+            <van-field
+                class="form-input"
+                v-model="inputValueDoc"
+                :placeholder="placeholder"
+                :readonly="readonly"
+                :disabled="disabled"
+                :clearable="clearable"
+                autocomplete="off"
+                :rules="[...rules]"
+                :maxlength="maxlength"
+                :error-message="errorMessage"
+                @blur="handleBlur"
+                @focus="handleFocus"
+                @clear="handleClear"
+            />
+        </template>
+        <template v-if="type === 'select'">
+            <van-field
+                class="form-input"
+                v-model="inputValueDoc"
+                :placeholder="placeholder"
+                :readonly="true"
+                :disabled="disabled"
+                :clearable="clearable"
+                :rules="[...rules]"
+                :error-message="errorMessage"
+                is-link
+                @click="!disabled && (showPicker = true)"
+                @clear="handleClear"
+            />
+            <van-popup v-model:show="showPicker" destroy-on-close round position="bottom">
+                <van-field
+                    v-if="showSearch"
+                    class="form-input search-field"
+                    v-model="searchText"
+                    :clearable="clearable"
+                    autocomplete="off"
+                    :placeholder="t('eur-remit.search')"
+                    clearable
+                />
+                <van-picker :columns="filteredColumns" v-model="selectedValue" :loading="loading" @cancel="showPicker = false" @confirm="onConfirm" />
+            </van-popup>
+        </template>
+        <template v-if="type === 'date'">
+            <van-field
+                class="form-input"
+                v-model="inputValueDoc"
+                :placeholder="placeholder"
+                :readonly="true"
+                :disabled="disabled"
+                :rules="[...rules]"
+                :clearable="clearable"
+                :error-message="errorMessage"
+                is-link
+                @click="!disabled && (showPicker = true)"
+                @clear="handleClear"
+            />
+            <van-calendar
+                v-model:show="showPicker"
+                :show-mark="false"
+                :title="t('cards.selectDateRange')"
+                :subtitle="t('cards.selectDate')"
+                color="var(--main-yellow)"
+                :min-date="minDate"
+                :max-date="maxDate"
+                :show-confirm="false"
+                @cancel="showPicker = false"
+                @confirm="onDateConfirm"
+            />
+        </template>
+        <template v-if="type === 'upload'">
+            <van-field name="uploader" class="form-input uploader" :rules="[...rules]">
+                <template #input>
+                    <van-uploader v-model="uploader" :max-count="1" :after-read="afterRead" />
+                </template>
+            </van-field>
+        </template>
+    </div>
+</template>
+
+<script setup>
+import dayjs from 'dayjs'
+import { useI18n } from 'vue-i18n'
+import { uploadApi } from '@/api/upload'
+import { showLoadingToast, closeToast, showToast } from 'vant'
+const { t } = useI18n()
+const isUploading = ref(false)
+const uploader = ref([])
+const afterRead = async (file) => {
+    isUploading.value = true
+    showLoadingToast({
+        message: '上传中...',
+        forbidClick: true,
+    })
+    const result = await uploadApi.uploadFile(file.file)
+    closeToast()
+    inputValueDoc.value = result.data
+    showToast('上传成功')
+}
+
+const props = defineProps({
+    type: { type: String, default: 'text', validator: (v) => ['text', 'number', 'select', 'date'].includes(v) },
+    label: String,
+    fkey: String,
+    showSearch: { type: Boolean, default: false },
+    value: { type: [String, Number] },
+    placeholder: { type: String, default: '请输入' },
+    disabled: Boolean,
+    readonly: Boolean,
+    required: Boolean,
+    clearable: { type: Boolean, default: true },
+    columns: { type: Array, default: () => [] },
+    rules: { type: Array, default: () => [] },
+    maxlength: Number,
+    errorMessage: String,
+    minDate: { type: Date, default: () => new Date(1920, 0, 1) },
+    maxDate: { type: Date, default: () => new Date(2050, 11, 31) },
+    dateFormatter: { type: Function, default: (type, val) => val },
+    displayFormatter: { type: Function, default: (val) => dayjs(val).format('YYYY-MM-DD') },
+})
+const emit = defineEmits(['update:value', 'blur', 'focus', 'clear', 'confirm', 'change'])
+const inputValueDoc = ref('')
+const selectedValue = ref([])
+const inputValue = ref('')
+const showPicker = ref(false)
+const loading = ref(false)
+const searchText = ref('')
+
+const filteredColumns = computed(() => {
+    if (!searchText.value) {
+        return props.columns
+    }
+    return props.columns.filter((item) => item.text.toLowerCase().includes(searchText.value.toLowerCase()))
+})
+
+watch(
+    () => inputValueDoc.value,
+    (newVal) => {
+        if (!newVal) {
+            return
+        }
+        if (props.type === 'text' || props.type === 'number') {
+            inputValue.value = newVal
+            emit('update:value', newVal)
+            emit('change', { value: newVal, key: props.fkey })
+        } else if (props.type === 'select') {
+            const matched = props.columns.find((opt) => opt.text === newVal)
+            inputValue.value = matched?.value || ''
+            emit('update:value', matched?.value || '')
+            emit('change', { value: matched?.value || '', key: props.fkey })
+        } else if (props.type === 'date') {
+            inputValue.value = newVal
+            emit('update:value', newVal)
+            emit('change', { value: newVal, key: props.fkey })
+        } else if (props.type === 'upload') {
+            inputValue.value = newVal
+            emit('update:value', newVal)
+            emit('change', { value: newVal, key: props.fkey })
+        }
+    },
+)
+
+watch(
+    () => props.value,
+    (newVal) => {
+        if (!newVal) {
+            return
+        }
+        if (props.type === 'date') {
+            inputValueDoc.value = newVal ? dayjs(newVal).format('YYYY-MM-DD') : ''
+            inputValue.value = newVal
+        } else if (props.type === 'select') {
+            const matched = props.columns.find((opt) => opt.value === newVal)
+            inputValueDoc.value = matched?.text || ''
+            inputValue.value = matched?.value || ''
+            selectedValue.value = [matched?.value]
+        } else if(props.type === 'upload'){
+            uploader.value=[{url:props.value}]
+            inputValueDoc.value = props.value
+        } else {
+            inputValueDoc.value = newVal
+            inputValue.value = newVal
+        }
+    },
+    { immediate: true },
+)
+
+const handleBlur = (event) => {
+    emit('blur', event)
+}
+
+const handleFocus = (event) => {
+    emit('focus', event)
+}
+
+const handleClear = () => {
+    inputValueDoc.value = ''
+    inputValue.value = ''
+    emit('update:value', '')
+    emit('clear')
+}
+
+const onConfirm = (value) => {
+    const selectedValue = value.selectedValues?.[0]
+    const selectedText = value.selectedOptions?.[0]?.text || ''
+    inputValueDoc.value = selectedText
+    searchText.value = ''
+    showPicker.value = false
+    emit('update:value', selectedValue)
+    emit('change', selectedValue)
+}
+
+const onDateConfirm = (value) => {
+    const formatted = dayjs(value).format('YYYY-MM-DD')
+    inputValueDoc.value = formatted
+    showPicker.value = false
+    emit('update:value', formatted)
+}
+</script>
+
+<style scoped lang="scss">
+.form-group {
+    width: 100%;
+    margin-bottom: 18px;
+}
+
+.form-label {
+    color: #fff;
+    font-size: 15px;
+    margin-bottom: 8px;
+    display: block;
+    span {
+        color: red;
+    }
+}
+
+.form-input {
+    width: 100%;
+    background: #181818 !important;
+    border: 1px solid var(--main-yellow) !important;
+    border-radius: 10px !important;
+    color: #fff !important;
+    font-size: 16px !important;
+    margin-top: 4px;
+
+    ::v-deep .van-field__control {
+        color: #fff !important;
+        &::placeholder {
+            color: #bdbdbd !important;
+        }
+    }
+}
+.search-field {
+    background: #181818 !important;
+    border: none !important;
+    padding-left: 20px !important;
+}
+.uploader {
+    border: none !important;
+    background: transparent !important;
+    padding-left: 0;
+}
+::v-deep .van-popup {
+    background: #181818 !important;
+}
+::v-deep .van-field__right-icon {
+    color: var(--main-yellow) !important;
+}
+::v-deep .van-field {
+    background: var(--action-bg);
+}
+::v-deep .van-uploader__upload {
+    background: #181818;
+}
+::v-deep .van-calendar {
+    background: var(--action-bg);
+}
+::v-deep .van-picker {
+    background: var(--action-bg);
+}
+::v-deep .van-picker__columns {
+    background: var(--action-bg);
+}
+::v-deep .van-calendar__month-mark {
+    display: none;
+}
+::v-deep .van-calendar__header-subtitle {
+    color: var(--white);
+}
+::v-deep .van-calendar__header-title {
+    color: var(--white);
+}
+::v-deep .van-calendar__month-title {
+    color: var(--main-yellow);
+}
+::v-deep {
+    .van-picker {
+        background: #181818;
+
+        .van-picker__toolbar {
+            background: #181818;
+            border-bottom: 1px solid #333;
+            height: 44px;
+            padding: 0 16px;
+
+            .van-picker__title {
+                color: #fff;
+                font-size: var(--font-size-16);
+                font-weight: 500;
+            }
+
+            .van-picker__cancel,
+            .van-picker__confirm {
+                color: var(--main-yellow);
+                font-size: var(--font-size-14);
+                padding: 0 8px;
+                height: 28px;
+                line-height: 28px;
+                border-radius: 14px;
+                transition: all 0.3s ease;
+
+                &:active {
+                    opacity: 0.8;
+                    background: rgba(255, 193, 7, 0.1);
+                }
+            }
+        }
+
+        .van-picker-column {
+            color: #fff;
+
+            .van-picker-column__item {
+                color: #fff;
+                font-size: var(--font-size-14);
+                padding: 0 16px;
+                height: 44px;
+                line-height: 44px;
+                transition: all 0.3s ease;
+
+                &--selected {
+                    color: var(--main-yellow);
+                    font-weight: 500;
+                    font-size: var(--font-size-16);
+                }
+
+                &:active {
+                    background: rgba(255, 255, 255, 0.05);
+                }
+            }
+
+            .van-picker-column__wrapper {
+                &::after {
+                    border-color: #333;
+                }
+            }
+        }
+
+        .van-picker__mask {
+            background-image: linear-gradient(180deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4)),
+                linear-gradient(0deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4));
+        }
+
+        .van-picker__indicator {
+            height: 44px;
+            background: rgba(255, 193, 7, 0.05);
+            border-top: 1px solid rgba(255, 193, 7, 0.1);
+            border-bottom: 1px solid rgba(255, 193, 7, 0.1);
+        }
+    }
+}
+</style>

+ 393 - 0
src/components/VirtualCard.vue

@@ -0,0 +1,393 @@
+<template>
+    <div class="apply-card-content">
+        <div class="apply-card-steps">
+            <div class="steps-top">
+                <div class="step">
+                    <div class="step-circle">1</div>
+                    <div class="step-label">上传KYC附件</div>
+                </div>
+                <div class="step-dash"></div>
+                <div class="step">
+                    <div class="step-circle">2</div>
+                    <div class="step-label">提交KYC认证</div>
+                </div>
+                <div class="step-dash"></div>
+                <div class="step">
+                    <div class="step-circle">3</div>
+                    <div class="step-label">申请开卡</div>
+                </div>
+            </div>
+        </div>
+        <button class="apply-btn" @click="handleApply(1, {})" v-if="isShowBtn">申请银行卡</button>
+        <div class="apply-card-footer">
+            <div class="kyc-list-title">KYC认证</div>
+            <div v-if="kycList.length > 0">
+                <div class="kyc-list">
+                    <div class="kyc-item" v-for="item in kycList" :key="item.id">
+                        <div class="kyc-icon">
+                            <div class="kyc-icon-inner">
+                                <i class="i-mdi-card-account-details-outline"></i>
+                            </div>
+                        </div>
+                        <div class="kyc-left">
+                            <div class="kyc-type">{{ item.cardName }}</div>
+                            <div class="kyc-desc">{{ statusMap[item.kycStatus] }}</div>
+                        </div>
+                        <div class="kyc-right">
+                            <button class="apply-btn" v-if="item.kycStatus === 2 && item.applyStatus === null" @click="handleApply(2, item)">
+                                开卡
+                            </button>
+                            <button class="apply-btn" v-if="[null,-1, 1, 3].includes(item.kycStatus)" @click="handleApply(3, item)">重新认证</button>
+                            <div v-if="item.applyStatus === 0" class="processing-status">处理中</div>
+                            <div v-if="item.applyStatus === 1" class="success-status">开卡成功</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <EmptyState v-if="kycList.length === 0" icon="i-mdi-receipt-text-outline" text="暂无卡片" />
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from 'vue-router'
+import EmptyState from '@/components/EmptyState.vue'
+import { ucardApi } from '@/api/ucard'
+import { useI18n } from 'vue-i18n'
+import useUserStore from '@/stores/use-user-store'
+const userStore = useUserStore()
+const userInfo = computed(() => userStore.userInfo)
+const router = useRouter()
+const { t } = useI18n()
+interface KycItem {
+    id: number
+    cardName: string
+    kycStatus: number | null
+    applyStatus: number | null
+    cardTypeId: number
+    uniqueId: string
+}
+
+const statusMap: Record<string, string> = {
+    null: t('kyc.statusDesc'),
+    '-1': t('kyc.statusDesc'),
+    '1': t('kyc.statusDesc2'),
+    '2': t('kyc.statusDesc3'),
+    '3': t('kyc.statusDesc4'),
+}
+const kycList = ref<KycItem[]>([])
+const isShowBtn = ref(false)
+async function getKycList() {
+    const res = await ucardApi.cardKycTypesList({ page: { current: 1, row: 10 } })
+    if (res.code === 200) {
+       let a =  res.data.filter((i)=> {
+            return i.kycStatus !== null
+        })
+       let b =  res.data.filter((i)=> {
+            return i.kycStatus == null
+        })
+        if(b.length != 0){
+            isShowBtn.value = true
+        }else {
+            isShowBtn.value = false
+        }
+        kycList.value = a
+    }
+}
+
+const ucardApply = async (item: any) => {
+    console.log(1111,item);
+
+    const res = await ucardApi.ucardApply({
+        cardTypeId: item.cardTypeId,
+        uniqueId: userInfo.value.customInfo.uniqueId,
+    })
+    if (res.code === 200) {
+        showToast(t('improve-info.kycSuccess'))
+        // router.push('/select/card')
+    }
+}
+const handleApply = (type: number, item: any) => {
+    if (type == 1) {
+        router.push('/select/card')
+    } else if (type == 2) {
+        ucardApply(item)
+    } else if (type == 3) {
+        router.push({
+            path: '/kyc',
+            query: {
+                cardTypeId: item.cardTypeId,
+                type: 0,
+            },
+        })
+    }
+}
+
+
+onMounted(() => {
+    getKycList()
+})
+</script>
+
+<style scoped>
+.apply-card-header {
+    font-size: 20px;
+    color: #fff;
+    font-weight: 600;
+    margin-bottom: 18px;
+}
+.apply-card-img {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-bottom: 18px;
+}
+.apply-card-visual {
+    position: relative;
+    width: 90px;
+    height: 60px;
+    background: transparent;
+    border-radius: 12px;
+    box-shadow: 0 2px 8px rgba(214, 255, 0, 0.1);
+}
+.chip {
+    position: absolute;
+    right: 12px;
+    top: 22px;
+    width: 22px;
+    height: 16px;
+    background: #eaeaea;
+    border-radius: 4px;
+    border: 1.5px solid #bbb;
+}
+.apply-card-steps {
+    width: 100%;
+    margin-bottom: 52px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+.steps-top {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    position: relative;
+    max-width: 600px;
+    margin: 0 auto;
+}
+.step {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    position: relative;
+    z-index: 2;
+    flex: 0 0 auto;
+}
+.step-circle {
+    width: 28px;
+    height: 28px;
+    background: #232323;
+    color: var(--main-yellow);
+    border-radius: 50%;
+    font-size: 14px;
+    font-weight: bold;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border: 2px solid var(--main-yellow);
+    margin-bottom: 8px;
+    transition: all 0.3s ease;
+}
+.step-circle:hover {
+    transform: scale(1.1);
+    box-shadow: 0 0 10px rgba(73, 247, 166, 0.3);
+}
+.step-dash {
+    flex: 1;
+    height: 2px;
+    background: repeating-linear-gradient(to right, var(--main-yellow), var(--main-yellow) 6px, transparent 6px, transparent 12px);
+    min-width: 18px;
+    position: relative;
+    top: -16px;
+}
+.step-label {
+    color: var(--main-yellow);
+    font-size: 14px;
+    font-weight: 500;
+    text-align: center;
+    line-height: 2;
+}
+.apply-btn {
+    width: 90%;
+    max-width: 320px;
+    background: var(--main-yellow);
+    color: #232323;
+    border: none;
+    border-radius: 24px;
+    font-size: 17px;
+    font-weight: 600;
+    padding: 12px 0;
+    margin: 0 auto 14px;
+    display: block;
+    box-shadow: 0 2px 8px rgba(214, 255, 0, 0.15);
+    cursor: pointer;
+    transition: background 0.2s;
+}
+.apply-btn:hover {
+    background: var(--main-yellow-dark);
+}
+.activate-btn {
+    width: 90%;
+    max-width: 320px;
+    background: transparent;
+    color: var(--main-yellow);
+    border: 1.5px solid var(--main-yellow);
+    border-radius: 24px;
+    font-size: 16px;
+    font-weight: 500;
+    padding: 10px 0;
+    margin: 0 auto 18px;
+    display: block;
+    cursor: pointer;
+    transition: background 0.2s, color 0.2s;
+}
+.activate-btn:hover {
+    background: var(--main-yellow);
+    color: #232323;
+}
+.apply-card-footer {
+    width: 100%;
+    margin-top: 34px;
+}
+.apply-card-empty {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 6px;
+}
+.empty-text {
+    color: var(--main-yellow);
+    font-size: 15px;
+    margin-top: 2px;
+}
+.kyc-list {
+    background: var(--action-bg);
+    border-radius: 16px;
+    margin-bottom: 16px;
+    padding: 16px 12px;
+    width: 100%;
+    max-width: 340px;
+}
+
+.kyc-item {
+    display: flex;
+    align-items: center;
+    padding: 12px 0;
+    border-bottom: 1px solid var(--border, #333);
+    font-size: 16px;
+}
+
+.kyc-item:last-child {
+    border-bottom: none;
+}
+
+.kyc-icon {
+    width: 40px;
+    height: 40px;
+    display: flex;
+    background: var(--main-bg);
+    box-shadow: 0 4px 12px rgba(214, 255, 0, 0.1);
+    border-radius: 8px;
+    margin-right: 12px;
+    align-items: center;
+    justify-content: center;
+}
+
+.kyc-icon-inner {
+    width: 22px;
+    height: 22px;
+    background: var(--white);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.kyc-icon-inner i {
+    color: #000;
+    width: 18px;
+    height: 18px;
+    font-size: var(--font-size-16);
+    border-radius: 50%;
+}
+
+.kyc-left {
+    flex: 1;
+    min-width: 0;
+}
+
+.kyc-right {
+    width: 80px;
+    text-align: right;
+    margin-left: 6px;
+    .apply-btn {
+        padding: 10px 0;
+        font-size: var(--font-size-14);
+        font-weight: 500;
+    }
+}
+
+.kyc-type {
+    color: var(--white);
+    font-size: 15px;
+    margin-bottom: 4px;
+}
+
+.kyc-desc {
+    color: #bdbdbd;
+    font-size: 13px;
+}
+
+.kyc-amount {
+    font-size: 15px;
+    color: var(--main-yellow);
+    margin-bottom: 4px;
+}
+
+.kyc-date {
+    color: #bdbdbd;
+    font-size: 13px;
+}
+
+.kyc-list-title {
+    font-size: var(--font-size-16);
+    color: var(--main-yellow);
+    margin-bottom: 12px;
+    font-weight: 500;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    &::before {
+        content: '';
+        display: inline-block;
+        width: 4px;
+        height: 16px;
+        background: var(--main-yellow);
+        border-radius: 2px;
+    }
+}
+
+.processing-status {
+    color: var(--main-yellow);
+    font-size: 14px;
+    text-align: center;
+}
+
+.success-status {
+    color: #4caf50;
+    font-size: 14px;
+    text-align: center;
+}
+</style>

+ 5 - 1
src/composables/config.ts

@@ -3,5 +3,9 @@ const config = {}
 export default config
 export default config
 
 
 export const userToken = useStorage('user-token', '')
 export const userToken = useStorage('user-token', '')
-export const lang = useStorage('lang', '')
+export const lang = useStorage('lang', 'cn', localStorage, {
+    deep: true,
+    writeDefaults: true
+})
 export const CLIENT = useStorage('CLIENT', '')
 export const CLIENT = useStorage('CLIENT', '')
+export const rememberPassword = useStorage('rememberPassword', 'false')

+ 15 - 3
src/composables/fetch.ts

@@ -2,6 +2,8 @@ import type { AxiosHeaders, AxiosRequestConfig, AxiosResponse } from 'axios'
 import axios from 'axios'
 import axios from 'axios'
 import { showToast } from 'vant'
 import { showToast } from 'vant'
 import { userToken, lang, CLIENT } from './config'
 import { userToken, lang, CLIENT } from './config'
+import useGlobalStore from '@/stores/use-global-store'
+import useUserStore from '@/stores/use-user-store'
 import config from '@/config'
 import config from '@/config'
 const { Host85 } = config
 const { Host85 } = config
 
 
@@ -51,6 +53,8 @@ axios.interceptors.request.use(
 axios.interceptors.response.use(
 axios.interceptors.response.use(
     response => response,
     response => response,
     (error) => {
     (error) => {
+        console.log(error,11);
+
         const response = {} as AxiosResponse
         const response = {} as AxiosResponse
         response.config = error.config
         response.config = error.config
         response.data = null
         response.data = null
@@ -76,12 +80,14 @@ function checkStatus(response: AxiosResponse): ResponseData<any> {
     return {
     return {
         code: -404,
         code: -404,
         data: response.statusText || response.toString(),
         data: response.statusText || response.toString(),
-        msg: `接口返回数据错误, 错误代码: ${response.status || '未知'}`,
+        msg: `接口返回数据错误`,
     }
     }
 }
 }
 
 
 function checkCodeFn(data: ResponseData<any>) {
 function checkCodeFn(data: ResponseData<any>) {
     const code = [0, 200, 1000]
     const code = [0, 200, 1000]
+    const globalStore = useGlobalStore()
+    globalStore.setRequestLoading(false)
     if (data.code === 401 || data.code === 600) {
     if (data.code === 401 || data.code === 600) {
         userToken.value = ''
         userToken.value = ''
         const pathname = encodeURIComponent(window.location.pathname)
         const pathname = encodeURIComponent(window.location.pathname)
@@ -183,6 +189,10 @@ const api: ApiType = {
         return xhr
         return xhr
     },
     },
     async $RESTful<T = any>(url: string, method = 'get', data?: Objable, header?: Objable) {
     async $RESTful<T = any>(url: string, method = 'get', data?: Objable, header?: Objable) {
+        const globalStore = useGlobalStore()
+        if (!globalStore.fullScreenLoading && !globalStore.requestLoading) {
+            globalStore.setRequestLoading(true)
+        }
         url = Host85 + url
         url = Host85 + url
         const config: AxiosRequestConfig = {
         const config: AxiosRequestConfig = {
             ...baseConfig,
             ...baseConfig,
@@ -202,12 +212,14 @@ const api: ApiType = {
         if (CLIENT.value) {
         if (CLIENT.value) {
             (config.headers as AxiosHeaders)['CLIENT'] = `${CLIENT.value}`
             (config.headers as AxiosHeaders)['CLIENT'] = `${CLIENT.value}`
         }
         }
+        const userStore = useUserStore()
+        const cId = userStore.userInfo?.customInfo?.cId
 
 
         if (method === 'get') {
         if (method === 'get') {
-            config.params = data
+            config.params = { ...data, cId }
         }
         }
         else {
         else {
-            config.data = data
+            config.data = { ...data, cId }
         }
         }
 
 
         if (url.includes('NoTimeout')) {
         if (url.includes('NoTimeout')) {

+ 36 - 37
src/config/index.ts

@@ -3,46 +3,45 @@ let ho = window.location.host.split('.')[1];
 const c = import.meta.env.VITE_APP_ENV
 const c = import.meta.env.VITE_APP_ENV
 let Host00, Host85
 let Host00, Host85
 switch (c) {
 switch (c) {
-  // 测试环境
-  case 'test':
-    Host00=ht + "//testsecure." + ho + ".com"
-    Host85=ht + "//testad." + ho + ".com"
-    break;
+    // 测试环境
+    case 'test':
+        Host00 = ht + "//testsecure." + ho + ".com"
+        Host85 = ht + "//testucard." + ho + ".com"
+        break;
     // 生产环境
     // 生产环境
-  case 'production':
-    Host00=ht + "//secure." + ho + ".com"
-    Host85=ht + "//ad." + ho + ".com"
-    break;
-
-  default:
-    // 开发环境
-    Host00="https://testsecure.6cd7e0f0b52.com"
-    Host85="https://testad.6cd7e0f0b52.com"
-    // Host00="http://103.214.175.29:8000"
-    // Host85="http://103.214.175.29:8500"
-    break;
+    case 'production':
+        Host00 = ht + "//secure." + ho + ".com"
+        Host85 = ht + "//ucard." + ho + ".com"
+        break;
+    default:
+        // 开发环境
+        Host00 = "https://testsecure.6cd7e0f0b52.com"
+        // Host85= "https://testad.6cd7e0f0b52.com"
+        Host85 = "http://103.214.175.29:8700"
+        // Host85= "http://192.168.0.33:8700"
+        break;
 }
 }
 const config = {
 const config = {
-  Host00,
-  Host85,
-  Host80: ht + "//secure." + ho + ".com",
-  Code: {
-    StatusOK: 200,
-    StatusFail: 400,
-    StatusSessionExpire: 600,
-    StatusSNotFound: 404,
-  },
-  Pattern: {
-    Email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
-    Phone: /^1[3-9]\d{9}$/,
-    Password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,16}$/,
-    Tel: /^[0][1-9]{2,3}-[0-9]{5,10}$/,
-    Num: /\d/,
-    NonNegInt: /^[0-9]+?$/, //非负整数
-    PosInt: /^[1-9]\d*$/, //正整数
-    nonnegative: /^[0-9]+([.]{1}[0-9]{1,2})?$/, //非负数(最多两位小数)
-    englishName: /^[^\u4e00-\u9fa5]+$/,
-  }
+    Host00,
+    Host85,
+    Host80: ht + "//secure." + ho + ".com",
+    Code: {
+        StatusOK: 200,
+        StatusFail: 400,
+        StatusSessionExpire: 600,
+        StatusSNotFound: 404,
+    },
+    Pattern: {
+        Email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
+        Phone: /^1[3-9]\d{9}$/,
+        Password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,16}$/,
+        Tel: /^[0][1-9]{2,3}-[0-9]{5,10}$/,
+        Num: /\d/,
+        NonNegInt: /^[0-9]+?$/, //非负整数
+        PosInt: /^[1-9]\d*$/, //正整数
+        nonnegative: /^[0-9]+([.]{1}[0-9]{1,2})?$/, //非负数(最多两位小数)
+        englishName: /^[^\u4e00-\u9fa5]+$/,
+    }
 }
 }
 
 
 export default config
 export default config

+ 42 - 0
src/i18n/index.ts

@@ -0,0 +1,42 @@
+import { createI18n } from 'vue-i18n'
+import { lang } from '@/composables/config'
+import cn from '@/i18n/locales/cn'
+import zh from '@/i18n/locales/zh'
+import en from '@/i18n/locales/en'
+import de from '@/i18n/locales/de'
+
+const messages = {
+  cn,
+  zh,
+  en,
+  de
+}
+
+type LocaleType = 'cn' | 'en' | 'zh' | 'de'
+// 确保在初始化时获取正确的语言设置
+const savedLang = (localStorage.getItem('lang') as LocaleType) || 'cn'
+
+const i18n = createI18n({
+  legacy: false,
+  locale: savedLang,
+  fallbackLocale: 'en',
+  messages,
+  silentTranslationWarn: true,
+  silentFallbackWarn: true,
+  runtimeOnly: false,
+  compositionOnly: true,
+  // 添加全局注入
+  globalInjection: true
+})
+console.log(i18n, 1000)
+
+// 监听语言变化
+watch(lang, (newLang) => {
+  i18n.global.locale.value = newLang as LocaleType
+  localStorage.setItem('lang', newLang)
+})
+
+const localesList = ['cn', 'en', 'zh', 'de'] as const
+// const localesList = ['en','cn','zh', 'de','es','ar','id','ms','th','vi','ko','pt','fa','tr']
+
+export { i18n, localesList }

+ 610 - 0
src/i18n/locales/cn.ts

@@ -0,0 +1,610 @@
+export default {
+    common: {
+        confirm: '确认',
+        cancel: '取消',
+        loading: '加载中...',
+        success: '成功',
+        fail: '失败',
+        input: '请输入',
+        choose: '请选择'
+    },
+    tabs: {
+        wallet: '钱包',
+        cards: '卡片',
+        finance: '金融',
+        mine: '我的'
+    },
+    wallet: {
+        title: '我的钱包',
+        balance: '余额',
+        transactions: '交易记录',
+    },
+    cards: {
+        title: '我的卡片',
+        balance: '余额',
+        transactions: '交易记录',
+        cardNo: '卡号',
+        recharge: '卡片充值',
+        findPassword: '找回密码',
+        freezeCard: '冻结卡片',
+        unfreezeCard: '解冻卡片',
+        selectCard: '选择卡片',
+        currency: '币种',
+        amount: '金额',
+        selectDateRange: '选择日期',
+        selectDate: '选择日期1',
+        start: '开始',
+        end: '结束'
+    },
+    finance: {
+        title: '金融',
+    },
+    mine: {
+        title: '我的',
+        logout: '确认退出登录吗?',
+    },
+    language: {
+        title: '语言设置',
+        selectLang: '选择语言',
+        cn: '中文简体',
+        zh: '中文繁体',
+        en: 'English',
+        de: 'German',
+        es: 'Spanish',
+        ar: 'Arabic',
+        id: 'Indonesian',
+        ms: 'Malay',
+        th: 'Thai',
+        vi: 'Vietnamese',
+        i1: '语言设置',
+        i2: '设置支付密码',
+        i3: '个人信息',
+        i4: '购卡记录',
+        i5: '关于PayouCard',
+        i6: '退出登录',
+    },
+    login: {
+        title: '登录',
+        username: '用户名',
+        password: '密码',
+        rememberPassword: '记住密码',
+        forgotPassword: '忘记密码?',
+    },
+    'card-transaction-detail': {
+        title: '交易详情',
+        currency: '币种',
+        amount: '交易金额',
+        type: '交易类型',
+        consume: '消费',
+        desc: '交易描述',
+        status: '订单状态',
+        time: '时间',
+    },
+    'pay-password': {
+        title: '支付密码',
+        change: '修改支付密码',
+        forget: '忘记支付密码',
+    },
+    'change-pay-password': {
+        title: '修改支付密码',
+        desc: '向您手机157***1673发送验证码',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'forget-pay-password': {
+        title: '忘记支付密码',
+        desc: '向您手机157***1673发送验证码',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'find-password': {
+        title: '找回密码',
+        desc: '请上传签名照片',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'freeze-card': {
+        title: '冻结卡片',
+        desc: '请上传签名照片',
+        tip: '支持PNG、JPG格式。',
+        nextStep: '确定',
+        formatError: '仅支持PNG、JPG格式',
+        needUpload: '请上传签名照片',
+        submitted: '已提交'
+    },
+    'card-recharge': {
+        title: '卡片充值',
+        currency: '币种',
+        cardNo: '卡号',
+        cardNoPlaceholder: '请输入卡号',
+        amount: '金额',
+        amountPlaceholder: '请输入金额,最低金额10',
+        walletBalance: '钱包余额',
+        all: '全部',
+        recharge: '充值',
+        fee: '手续费',
+        records: '充值记录',
+        walletPay: '钱包支付',
+        rechargeSuccess: '充值成功',
+        rechargePending: '充值中',
+        rechargeFailed: '充值失败',
+    },
+    'activate-card': {
+        title: '卡片激活',
+        desc: '请输入您的卡片信息进行激活',
+        cardNo: '卡号',
+        cardNoPlaceholder: '请输入卡号',
+        expireDate: '有效期',
+        expireDatePlaceholder: '请输入有效期',
+        cvv: 'CVV',
+        cvvPlaceholder: '请输入CVV',
+        password: '交易密码',
+        passwordPlaceholder: '请输入交易密码',
+        activate: '立即激活',
+    },
+    'select-card': {
+        title: '提交',
+        selectCard: '选择你的卡片',
+        desc: '我们为全球各地用户提供不同的卡片选择,包括实体卡和虚拟卡。',
+        physicalCard: '实体卡',
+        price: '价格:',
+        kycRequire: 'KYC认证:',
+        kycRequiredDesc: '需要',
+        kycRequiredDesc2: '不需要',
+        rechargeFee: '充值费用:',
+        atmFee: 'ATM取现:',
+        atmFeeDesc: 'Free in Europe (other 2%)',
+        atmFeeDesc2: 'Free in Europe (other 2%)',
+        spendFee: '消费手续费:',
+        annualFee: '年费:',
+        annualFeeDesc: 'Free',
+        usageArea: '使用地区:',
+        free: '免费',
+        submit: '提交',
+        masterCard: 'MasterCard 实体卡',
+        visaVirtualCard: 'Visa 虚拟卡(至珍卡)',
+        visaPhysicalCard: 'Visa 实体卡',
+        visaVirtualCard2: 'Visa 虚拟卡(畅游卡)',
+        masterCardDesc: '该卡支持全球线上和线下消费,ATM机取现;支持绑定PayPal/微信/支付宝等场景。',
+        visaVirtualCardDesc: '支持Apple Pay,Google Pay,微信,支付宝场景使用,无单笔消费限额,支持超大额消费。(卡片激活预计1-72小时完成)。',
+        visaPhysicalCardDesc: '该卡片能力强,支持和覆盖全球全平台的线上线下消费,ATM取现等功能(卡片激活时间预计需要48小时)。',
+        visaVirtualCard2Desc: '该卡片支持和覆盖全球全平台的线上消费功能。最擅长商户: Apple,Facebook,Walmart,PAYPAL',
+        status: '状态',
+        statusDesc: '可用',
+        statusDesc2: '不可用',
+        currency: '币种',
+        needPhotoForActiveCard: '激活卡片时是否需要手持护照和银行卡照片',
+        needPhotoForActiveCardDesc: '需要',
+        needPhotoForActiveCardDesc2: '不需要',
+        needPhotoForOperateCard: '冻结、解冻和找回密码时是否需要提供用户签名照',
+        needPhotoForOperateCardDesc: '需要',
+        needPhotoForOperateCardDesc2: '不需要',
+    },
+    kyc: {
+        title: 'KYC认证',
+        desc: '请填写您的身份信息以完成认证',
+        type: '证件类型',
+        i1: 'EEA文件证照',
+        i2: '手持照片',
+        data: '证件照片',
+        firstName: '名',
+        lastName: '姓',
+        address: '邮寄地址',
+        email: '邮箱',
+        phone: '手机号',
+        phoneCode: '区号',
+        postalCode: '邮政编码',
+        submit: '提交',
+        kycSuccess: '身份认证成功',
+        kycFail: '身份认证失败',
+        step1: '身份认证',
+        step2: '支付',
+        step3: '邮寄',
+        step1Desc: '请填写您的身份信息以完成认证',
+        step2Desc: '请填写您的支付信息以完成支付',
+        step3Desc: '请填写您的邮寄信息以完成邮寄',
+        step4Desc: '请填写您的支付信息以完成支付',
+        kycFailDesc: '请填写完整的身份认证信息',
+        emailError: '请输入正确的邮箱格式',
+        phoneError: '请输入正确的手机号格式',
+        postalCodeError: '请输入正确的邮政编码格式',
+        amountError: '请输入正确的金额',
+        currencyError: '请选择正确的币种',
+        shippingAddressError: '请输入正确的邮寄地址',
+        amount: '金额',
+        currency: '币种',
+        shippingAddress: '邮寄地址',
+        next: '下一步',
+        submitKyc: '提交认证',
+        country: '选择区号',
+        status: '状态',
+        statusDesc: '不存在',
+        statusDesc2: '审核中',
+        statusDesc3: '认证成功',
+        statusDesc4: '拒绝',
+
+    },
+    'eur-remit': {
+        title: '全球速汇',
+        available: '可速汇余额:',
+        country: '国家和地区',
+        currency: '币种',
+        amount: '金额',
+        amountPlaceholder: '请输入欧元金额,最低金额100',
+        note: '附言',
+        notePlaceholder: '请输入Note(附言码)',
+        select: '请选择',
+        next: '下一步',
+        "item1":"收款银行:",
+        "item2":"交易货币:",
+        "item3":"目标货币代码:",
+        "item4":"目标国家代码:",
+        "item5":"汇率:",
+        "item6":"基本信息",
+        "item7":"收款银行ID:",
+        "item8":"用户唯一ID:",
+        "item9":"速汇金额:",
+        "item10":"附言:",
+        "item11":"收付款人关系:",
+        "item12":"兄弟",
+        "item13":"姐夫/妹夫",
+        "item14":"堂/表兄弟姐妹",
+        "item15":"女儿",
+        "item16":"父亲",
+        "item17":"岳父/公公",
+        "item18":"朋友",
+        "item19":"祖父",
+        "item20":"祖母",
+        "item21":"丈夫",
+        "item22":"母亲",
+        "item23":"岳母/婆婆",
+        "item24":"侄子/外甥",
+        "item25":"侄女/外甥女",
+        "item26":"本人",
+        "item27":"姐妹",
+        "item28":"兄嫂/弟妹",
+        "item29":"儿子",
+        "item30":"叔伯/舅姑",
+        "item31":"妻子",
+        "item32":"其他",
+        "item33":"资金来源:",
+        "item34":"现金",
+        "item35":"商业",
+        "item36":"礼物",
+        "item37":"工资",
+        "item38":"彩票",
+        "item39":"储蓄",
+        "item40":"其他",
+        "item41":"付款目的:",
+        "item42":"购买货物或服务",
+        "item43":"运费和运输成本",
+        "item44":"教育费用",
+        "item45":"移民投资",
+        "item46":"慈善捐赠",
+        "item47":"家庭支持",
+        "item48":"股息或利息支付",
+        "item49":"国际贸易",
+        "item50":"付款人信息",
+        "item51":"付款人类型:",
+        "item52":"个人",
+        "item53":"付款人姓:",
+        "item54":"付款人名:",
+        "item55":"付款人证件号码:",
+        "item56":"付款人证件类型:",
+        "item57":"护照",
+        "item58":"身份证",
+        "item59":"证件颁发国家:",
+        "item60":"请选择证件颁发国家",
+        "item61":"出生日期:",
+        "item62":"国籍:",
+        "item63":"请选择国籍",
+        "item64":"手机号码:",
+        "item65":"居住国家:",
+        "item66":"请选择居住国家",
+        "item67":"居住城市代码:",
+        "item68":"请选择居住城市",
+        "item69":"地址:",
+        "item70":"邮编:",
+        "item71":"职业:",
+        "item72":"校验付款人",
+        "item73":"收款人信息",
+        "item74":"收款人银行ID:",
+        "item75":"收款人账号:",
+        "item76":"收款人户名:",
+        "item77":"收款人居住国家:",
+        "item78":"请选择收款人居住国家",
+        "item79":"收款人居住城市:",
+        "item80":"请选择收款人居住城市",
+        "item81":"收款人地址:",
+        "item82":"收款人邮编:",
+        "item83":"收款人姓:",
+        "item84":"收款人名:",
+        "item85":"收款人银行编码:",
+        "item86":"收款人国籍:",
+        "item87":"请选择收款人国籍",
+        "item88":"收款人证件类型:",
+        "item89":"护照",
+        "item90":"身份证",
+        "item91":"收款人证件号码:",
+        "item92":"证件有效期:",
+        "item93":"出生日期:",
+        "item94":"手机区号:",
+        "item95":"请选择区号",
+        "item96":"手机号码:",
+        "item97":"银行账户类型:",
+        "item98":"支票账户",
+        "item99":"储蓄账户",
+        "item100":"定期存款账户",
+        "item101":"其它账户",
+        "item102":"校验收款人",
+        "item103":"校验付款人",
+        "item104":"确认信息",
+        "item105":"提交代付",
+        "item106":"订单号:",
+        "item107":"商户原始订单号:",
+        "item108":"支付状态:",
+        text1: '提交调单信息或文件',
+        text2: '订单号:',
+        text3: '请输入PayouCard订单号',
+        text4: '(PayouCard订单号,必填)',
+        text5: '订单信息',
+        text6: '请提供订单相关信息',
+        text7: '请选择信息类型',
+        text8: '信息类型:',
+        text9: '请选择信息类型',
+        text10: '请输入信息内容',
+        text11: '信息内容:',
+        text12: '请输入信息内容',
+        text13: '(不支持中文字符,最多200个字符)',
+        text14: '添加订单信息',
+        text15: '订单文件',
+        text16: '请提供订单相关文件',
+        text17: '请选择文件类型',
+        text18: '文件类型:',
+        text19: '请选择文件类型',
+        text20: '请上传文件',
+        text21: '文件:',
+        text22: '选择文件',
+        text23: '删除',
+        text24: '(每个文件不能超过2M,只支持图片格式',
+        ms: '最低金额100EUR,最多2位小数',
+        ms3: '请输入银行ID',
+        ms4: '请输入用户唯一ID',
+        ms5: '请输入商户订单号',
+        ms6: '请输入速汇金额',
+        ms7: '请输入附言',
+        ms8: '长度在5到64个字符之间',
+        ms9: '请输入付款人姓',
+        ms10: '长度在2到50个字符之间',
+        ms10_1: '请输入付款人名',
+        ms11: '请输入付款人证件号码',
+        ms12: '长度在6到18个字符之间',
+        ms13: '请选择付款人证件类型',
+        ms14: '请输入证件颁发国家',
+        ms15: '2位国家代码',
+        ms16: '请选择出生日期',
+        ms17: '请输入国籍',
+        ms18: '请输入手机号码',
+        ms19: '例如:+37012345678,长度在8到15个字符之间',
+        ms20: '请输入居住国家',
+        ms21: '2位国家代码',
+        ms22: '请输入居住城市代码',
+        ms23: '请输入地址',
+        ms24: '长度在10到100个字符之间',
+        ms25: '请输入邮编',
+        ms26: '长度在3到9个字符之间',
+        ms27: '请输入职业',
+        ms28: '长度在3到20个字符之间',
+        ms29: '请输入收款人账号',
+        ms30: '长度在2到48个字符之间',
+        ms31: '请输入收款人户名',
+        ms32: '长度在1到100个字符之间',
+        ms33: '长度在2到60个字符之间',
+        ms34: '长度在2到60个字符之间',
+        ms35: '请输入银行ID',
+        ms36: '请输入收款人账号',
+        ms37: '长度在2到48个字符之间',
+        ms38: '请输入收款人户名',
+        ms39: '长度在1到100个字符之间',
+        ms40: '请输入订单号',
+        ms41: '收款人ID',
+        ms42: '收款人电话',
+        ms43: '收款人银行',
+        ms44: '收款人开户行',
+        ms45: '收款人开户行',
+        ms46: '收款人地址',
+        ms47: '收款人出生日期',
+        ms48: '支付证明',
+        ms49: '银行流水',
+        ms50: '交易合同',
+        ms51: '交易发票',
+        ms52: '订单截图',
+        ms53: '身份证明',
+        ms54: '文件上传成功',
+        ms55: '文件上传失败',
+        ms56: '只能上传JPG/PNG格式的图片!',
+        ms57: '图片大小不能超过2MB',
+        ms58: '请完善表单信息',
+        ms59: '至少需要提供一项订单信息或文件',
+        ms60: '调单信息提交成功',
+        ms61: '提交失败',
+        ms62: '未知错误',
+        ms63: '上传失败',
+        ms64: '查询法币汇率失败',
+        ms65: '查询失败',
+        ms66: '支持银行:',
+        ms67: '最低金额100EUR',
+        ms68: '不支持中文字符',
+        ms69: '字符长度为',
+        ms70: '只支持英文',
+        ms71: '获取区号失败',
+        ms72: '收款人校验通过',
+        ms73: '付款人校验通过',
+        ms74: '整体校验通过',
+        ms75: '代付校验通过',
+        prev: '上一步',
+        submit: '提交',
+        mobileFormat: '请输入正确的手机号格式',
+        mobileCodeFormat: '请输入正确的区号格式',
+        postalCodeFormat: '请输入正确的邮政编码格式',
+        amountFormat: '请输入正确的金额格式',
+        currencyFormat: '请选择正确的币种',
+        shippingAddressFormat: '请输入正确的邮寄地址',
+        amountError: '请输入正确的金额',
+        currencyError: '请选择正确的币种',
+        shippingAddressError: '请输入正确的邮寄地址',
+        required:'',
+        requiredFieldsMissing:'请填写完整信息',
+        item95_1:'',
+        item95_2:'',
+        orderNo:'订单号',
+        paymentSuccess:'支付成功',
+        paymentFailed:'支付失败',
+        paymentError:'支付失败',
+        validateError:'校验失败',
+        validateFailed:'校验失败',
+        validateSuccess:'校验成功',
+        search:'搜索选项',
+    },
+    eur: {
+        title: 'EUR',
+        globalRemit: '全球速汇',
+        transactionRecord: '速汇记录',
+        eurTopup: 'eur充值'
+
+    },
+    'remit-success':{
+        title:'支付成功',
+        orderNo:'订单号',
+        paymentSuccess:'支付成功',
+        paymentFailed:'支付失败',
+        paymentError:'支付失败',
+        validateError:'校验失败',
+        validateFailed:'校验失败',
+        validateSuccess:'校验成功',
+        successTitle:'支付成功',
+        viewDetail:'查看详情',
+        backToHome:'返回首页',
+    },
+    'transfer-detail': {
+        title: '交易详情',
+        step1: '速汇申请',
+        step2: '处理中',
+        step3: '已成功',
+        amount: '金额',
+        currency: '币种',
+        status: '订单状态',
+        withdrawn: '已速汇',
+        time: '时间',
+        relationship: '收付款人关系',
+        relationship1: '兄弟',
+        relationship2: '姐夫/妹夫',
+        relationship3: '堂/表兄弟姐妹',
+        relationship4: '女儿',
+        relationship5: '父亲',
+        relationship6: '岳父/公公',
+        sourceFunds: '资金来源',
+        sourceFunds1: '现金',
+        sourceFunds2: '商业',
+        sourceFunds3: '礼物',
+        sourceFunds4: '工资',
+        sourceFunds5: '彩票',
+        sourceFunds6: '储蓄',
+        orderNo: '订单号',
+        mobile: '手机号码',
+        email: '邮箱',
+        country: '居住国家',
+        city: '居住城市',
+        address: '居住地址',
+        postalCode: '邮政编码',
+        occupation: '职业',
+        accountName: '收款人户名',
+        accountNumber: '收款人账号',
+        B1: '待处理',
+        B2: '支付中',
+        B3: '支付成功',
+        B4: '支付失败',
+        B6: '退款',
+        B11: '调单_待提交',
+        B12: '调单_待审核',
+        B13: '调单_审核通过',
+        B15: '调单_审核拒绝',
+        addTime: '创建时间',
+        postscript: '附言',
+        payPurpose: '付款目的',
+        name: '姓名',
+    },
+    remit: {
+        title: 'USDT',
+        available: 'USDT余额',
+        amount: 'USDT金额',
+        amountPlaceholder: '请输入USDT金额',
+        currency: 'USDT',
+        buy: '购买',
+        sell: '卖出',
+        recharge: '充值',
+        withdraw: '提现',
+        record: '交易记录',
+        item1: '购买',
+        item2: '卖出',
+        item3: '链上充值',
+        item4: '链上提现',
+    },
+    'reset-password': {
+        title: '重置密码',
+        email: '邮箱',
+        emailPlaceholder: '请输入邮箱',
+        emailError: '请输入正确的邮箱格式',
+        emailRequired: '请输入邮箱',
+        remember: '记起密码了?',
+        login: '返回登录',
+        reset: '重置密码',
+        resetSuccess: '',
+    },
+    "card-activation":{
+        title:'卡片激活',
+        cardNo: '银行卡号',
+        activePhoto: '手持护照和银行卡照图片',
+        activePhotoRequired: '请上传手持护照和银行卡照图片',
+        submit:'激活',
+        submitRequired:'激活成功',
+
+    },
+    "improve-info":{
+        title: '完善信息',
+        "p3": "请输入信息",
+        "p4": "区号",
+        "p5": "请选择区号",
+        "p6": "手机号",
+        "p7": "邮箱",
+        "p8": "姓",
+        "p9": "名",
+        "p10": "生日",
+        "p11": "国籍",
+        "p12": "请选择国籍",
+        "p13": "性别",
+        "p14": "请选择",
+        "p15": "男",
+        "p16": "女",
+        "p17": "国家",
+        "p18": "请选择国家",
+        "p19": "城市",
+        "p20": "请选择城市",
+        "p21": "详细地址",
+        "p22": "邮编",
+        "p23": "证件类型",
+        "p24": "请选择证件类型",
+        "p25": "身份证",
+        "p26": "护照",
+        "p27": "证件号",
+        "p28": "证件到期",
+        "p29": "证件照片",
+        "p30": "人脸照片",
+        "p31": "扩展字段",
+        "p32": "取消",
+        "p33": "提交",
+    }
+}

+ 610 - 0
src/i18n/locales/de.ts

@@ -0,0 +1,610 @@
+export default {
+    common: {
+        confirm: '确认',
+        cancel: '取消',
+        loading: '加载中...',
+        success: '成功',
+        fail: '失败',
+        input: '请输入',
+        choose: '请选择'
+    },
+    tabs: {
+        wallet: '钱包',
+        cards: '卡片',
+        finance: '金融',
+        mine: '我的'
+    },
+    wallet: {
+        title: '我的钱包',
+        balance: '余额',
+        transactions: '交易记录',
+    },
+    cards: {
+        title: '我的卡片',
+        balance: '余额',
+        transactions: '交易记录',
+        cardNo: '卡号',
+        recharge: '卡片充值',
+        findPassword: '找回密码',
+        freezeCard: '冻结卡片',
+        unfreezeCard: '解冻卡片',
+        selectCard: '选择卡片',
+        currency: '币种',
+        amount: '金额',
+        selectDateRange: '选择日期',
+        selectDate: '选择日期1',
+        start: '开始',
+        end: '结束'
+    },
+    finance: {
+        title: '金融',
+    },
+    mine: {
+        title: '我的',
+        logout: '确认退出登录吗?',
+    },
+    language: {
+        title: '语言设置',
+        selectLang: '选择语言',
+        cn: '中文简体',
+        zh: '中文繁体',
+        en: 'English',
+        de: 'German',
+        es: 'Spanish',
+        ar: 'Arabic',
+        id: 'Indonesian',
+        ms: 'Malay',
+        th: 'Thai',
+        vi: 'Vietnamese',
+        i1: '语言设置',
+        i2: '设置支付密码',
+        i3: '个人信息',
+        i4: '购卡记录',
+        i5: '关于PayouCard',
+        i6: '退出登录',
+    },
+    login: {
+        title: '登录',
+        username: '用户名',
+        password: '密码',
+        rememberPassword: '记住密码',
+        forgotPassword: '忘记密码?',
+    },
+    'card-transaction-detail': {
+        title: '交易详情',
+        currency: '币种',
+        amount: '交易金额',
+        type: '交易类型',
+        consume: '消费',
+        desc: '交易描述',
+        status: '订单状态',
+        time: '时间',
+    },
+    'pay-password': {
+        title: '支付密码',
+        change: '修改支付密码',
+        forget: '忘记支付密码',
+    },
+    'change-pay-password': {
+        title: '修改支付密码',
+        desc: '向您手机157***1673发送验证码',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'forget-pay-password': {
+        title: '忘记支付密码',
+        desc: '向您手机157***1673发送验证码',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'find-password': {
+        title: '找回密码',
+        desc: '请上传签名照片',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'freeze-card': {
+        title: '冻结卡片',
+        desc: '请上传签名照片',
+        tip: '支持PNG、JPG格式。',
+        nextStep: '确定',
+        formatError: '仅支持PNG、JPG格式',
+        needUpload: '请上传签名照片',
+        submitted: '已提交'
+    },
+    'card-recharge': {
+        title: '卡片充值',
+        currency: '币种',
+        cardNo: '卡号',
+        cardNoPlaceholder: '请输入卡号',
+        amount: '金额',
+        amountPlaceholder: '请输入金额,最低金额10',
+        walletBalance: '钱包余额',
+        all: '全部',
+        recharge: '充值',
+        fee: '手续费',
+        records: '充值记录',
+        walletPay: '钱包支付',
+        rechargeSuccess: '充值成功',
+        rechargePending: '充值中',
+        rechargeFailed: '充值失败',
+    },
+    'activate-card': {
+        title: '卡片激活',
+        desc: '请输入您的卡片信息进行激活',
+        cardNo: '卡号',
+        cardNoPlaceholder: '请输入卡号',
+        expireDate: '有效期',
+        expireDatePlaceholder: '请输入有效期',
+        cvv: 'CVV',
+        cvvPlaceholder: '请输入CVV',
+        password: '交易密码',
+        passwordPlaceholder: '请输入交易密码',
+        activate: '立即激活',
+    },
+    'select-card': {
+        title: '提交',
+        selectCard: '选择你的卡片',
+        desc: '我们为全球各地用户提供不同的卡片选择,包括实体卡和虚拟卡。',
+        physicalCard: '实体卡',
+        price: '价格:',
+        kycRequire: 'KYC认证:',
+        kycRequiredDesc: '需要',
+        kycRequiredDesc2: '不需要',
+        rechargeFee: '充值费用:',
+        atmFee: 'ATM取现:',
+        atmFeeDesc: 'Free in Europe (other 2%)',
+        atmFeeDesc2: 'Free in Europe (other 2%)',
+        spendFee: '消费手续费:',
+        annualFee: '年费:',
+        annualFeeDesc: 'Free',
+        usageArea: '使用地区:',
+        free: '免费',
+        submit: '提交',
+        masterCard: 'MasterCard 实体卡',
+        visaVirtualCard: 'Visa 虚拟卡(至珍卡)',
+        visaPhysicalCard: 'Visa 实体卡',
+        visaVirtualCard2: 'Visa 虚拟卡(畅游卡)',
+        masterCardDesc: '该卡支持全球线上和线下消费,ATM机取现;支持绑定PayPal/微信/支付宝等场景。',
+        visaVirtualCardDesc: '支持Apple Pay,Google Pay,微信,支付宝场景使用,无单笔消费限额,支持超大额消费。(卡片激活预计1-72小时完成)。',
+        visaPhysicalCardDesc: '该卡片能力强,支持和覆盖全球全平台的线上线下消费,ATM取现等功能(卡片激活时间预计需要48小时)。',
+        visaVirtualCard2Desc: '该卡片支持和覆盖全球全平台的线上消费功能。最擅长商户: Apple,Facebook,Walmart,PAYPAL',
+        status: '状态',
+        statusDesc: '可用',
+        statusDesc2: '不可用',
+        currency: '币种',
+        needPhotoForActiveCard: '激活卡片时是否需要手持护照和银行卡照片',
+        needPhotoForActiveCardDesc: '需要',
+        needPhotoForActiveCardDesc2: '不需要',
+        needPhotoForOperateCard: '冻结、解冻和找回密码时是否需要提供用户签名照',
+        needPhotoForOperateCardDesc: '需要',
+        needPhotoForOperateCardDesc2: '不需要',
+    },
+    kyc: {
+        title: 'KYC认证',
+        desc: '请填写您的身份信息以完成认证',
+        type: '证件类型',
+        i1: 'EEA文件证照',
+        i2: '手持照片',
+        data: '证件照片',
+        firstName: '名',
+        lastName: '姓',
+        address: '邮寄地址',
+        email: '邮箱',
+        phone: '手机号',
+        phoneCode: '区号',
+        postalCode: '邮政编码',
+        submit: '提交',
+        kycSuccess: '身份认证成功',
+        kycFail: '身份认证失败',
+        step1: '身份认证',
+        step2: '支付',
+        step3: '邮寄',
+        step1Desc: '请填写您的身份信息以完成认证',
+        step2Desc: '请填写您的支付信息以完成支付',
+        step3Desc: '请填写您的邮寄信息以完成邮寄',
+        step4Desc: '请填写您的支付信息以完成支付',
+        kycFailDesc: '请填写完整的身份认证信息',
+        emailError: '请输入正确的邮箱格式',
+        phoneError: '请输入正确的手机号格式',
+        postalCodeError: '请输入正确的邮政编码格式',
+        amountError: '请输入正确的金额',
+        currencyError: '请选择正确的币种',
+        shippingAddressError: '请输入正确的邮寄地址',
+        amount: '金额',
+        currency: '币种',
+        shippingAddress: '邮寄地址',
+        next: '下一步',
+        submitKyc: '提交认证',
+        country: '选择区号',
+        status: '状态',
+        statusDesc: '不存在',
+        statusDesc2: '审核中',
+        statusDesc3: '认证成功',
+        statusDesc4: '拒绝',
+
+    },
+    'eur-remit': {
+        title: '全球速汇',
+        available: '可速汇余额:',
+        country: '国家和地区',
+        currency: '币种',
+        amount: '金额',
+        amountPlaceholder: '请输入欧元金额,最低金额100',
+        note: '附言',
+        notePlaceholder: '请输入Note(附言码)',
+        select: '请选择',
+        next: '下一步',
+        "item1":"收款银行:",
+        "item2":"交易货币:",
+        "item3":"目标货币代码:",
+        "item4":"目标国家代码:",
+        "item5":"汇率:",
+        "item6":"基本信息",
+        "item7":"收款银行ID:",
+        "item8":"用户唯一ID:",
+        "item9":"速汇金额:",
+        "item10":"附言:",
+        "item11":"收付款人关系:",
+        "item12":"兄弟",
+        "item13":"姐夫/妹夫",
+        "item14":"堂/表兄弟姐妹",
+        "item15":"女儿",
+        "item16":"父亲",
+        "item17":"岳父/公公",
+        "item18":"朋友",
+        "item19":"祖父",
+        "item20":"祖母",
+        "item21":"丈夫",
+        "item22":"母亲",
+        "item23":"岳母/婆婆",
+        "item24":"侄子/外甥",
+        "item25":"侄女/外甥女",
+        "item26":"本人",
+        "item27":"姐妹",
+        "item28":"兄嫂/弟妹",
+        "item29":"儿子",
+        "item30":"叔伯/舅姑",
+        "item31":"妻子",
+        "item32":"其他",
+        "item33":"资金来源:",
+        "item34":"现金",
+        "item35":"商业",
+        "item36":"礼物",
+        "item37":"工资",
+        "item38":"彩票",
+        "item39":"储蓄",
+        "item40":"其他",
+        "item41":"付款目的:",
+        "item42":"购买货物或服务",
+        "item43":"运费和运输成本",
+        "item44":"教育费用",
+        "item45":"移民投资",
+        "item46":"慈善捐赠",
+        "item47":"家庭支持",
+        "item48":"股息或利息支付",
+        "item49":"国际贸易",
+        "item50":"付款人信息",
+        "item51":"付款人类型:",
+        "item52":"个人",
+        "item53":"付款人姓:",
+        "item54":"付款人名:",
+        "item55":"付款人证件号码:",
+        "item56":"付款人证件类型:",
+        "item57":"护照",
+        "item58":"身份证",
+        "item59":"证件颁发国家:",
+        "item60":"请选择证件颁发国家",
+        "item61":"出生日期:",
+        "item62":"国籍:",
+        "item63":"请选择国籍",
+        "item64":"手机号码:",
+        "item65":"居住国家:",
+        "item66":"请选择居住国家",
+        "item67":"居住城市代码:",
+        "item68":"请选择居住城市",
+        "item69":"地址:",
+        "item70":"邮编:",
+        "item71":"职业:",
+        "item72":"校验付款人",
+        "item73":"收款人信息",
+        "item74":"收款人银行ID:",
+        "item75":"收款人账号:",
+        "item76":"收款人户名:",
+        "item77":"收款人居住国家:",
+        "item78":"请选择收款人居住国家",
+        "item79":"收款人居住城市:",
+        "item80":"请选择收款人居住城市",
+        "item81":"收款人地址:",
+        "item82":"收款人邮编:",
+        "item83":"收款人姓:",
+        "item84":"收款人名:",
+        "item85":"收款人银行编码:",
+        "item86":"收款人国籍:",
+        "item87":"请选择收款人国籍",
+        "item88":"收款人证件类型:",
+        "item89":"护照",
+        "item90":"身份证",
+        "item91":"收款人证件号码:",
+        "item92":"证件有效期:",
+        "item93":"出生日期:",
+        "item94":"手机区号:",
+        "item95":"请选择区号",
+        "item96":"手机号码:",
+        "item97":"银行账户类型:",
+        "item98":"支票账户",
+        "item99":"储蓄账户",
+        "item100":"定期存款账户",
+        "item101":"其它账户",
+        "item102":"校验收款人",
+        "item103":"校验付款人",
+        "item104":"确认信息",
+        "item105":"提交代付",
+        "item106":"订单号:",
+        "item107":"商户原始订单号:",
+        "item108":"支付状态:",
+        text1: '提交调单信息或文件',
+        text2: '订单号:',
+        text3: '请输入PayouCard订单号',
+        text4: '(PayouCard订单号,必填)',
+        text5: '订单信息',
+        text6: '请提供订单相关信息',
+        text7: '请选择信息类型',
+        text8: '信息类型:',
+        text9: '请选择信息类型',
+        text10: '请输入信息内容',
+        text11: '信息内容:',
+        text12: '请输入信息内容',
+        text13: '(不支持中文字符,最多200个字符)',
+        text14: '添加订单信息',
+        text15: '订单文件',
+        text16: '请提供订单相关文件',
+        text17: '请选择文件类型',
+        text18: '文件类型:',
+        text19: '请选择文件类型',
+        text20: '请上传文件',
+        text21: '文件:',
+        text22: '选择文件',
+        text23: '删除',
+        text24: '(每个文件不能超过2M,只支持图片格式',
+        ms: '最低金额100EUR,最多2位小数',
+        ms3: '请输入银行ID',
+        ms4: '请输入用户唯一ID',
+        ms5: '请输入商户订单号',
+        ms6: '请输入速汇金额',
+        ms7: '请输入附言',
+        ms8: '长度在5到64个字符之间',
+        ms9: '请输入付款人姓',
+        ms10: '长度在2到50个字符之间',
+        ms10_1: '请输入付款人名',
+        ms11: '请输入付款人证件号码',
+        ms12: '长度在6到18个字符之间',
+        ms13: '请选择付款人证件类型',
+        ms14: '请输入证件颁发国家',
+        ms15: '2位国家代码',
+        ms16: '请选择出生日期',
+        ms17: '请输入国籍',
+        ms18: '请输入手机号码',
+        ms19: '例如:+37012345678,长度在8到15个字符之间',
+        ms20: '请输入居住国家',
+        ms21: '2位国家代码',
+        ms22: '请输入居住城市代码',
+        ms23: '请输入地址',
+        ms24: '长度在10到100个字符之间',
+        ms25: '请输入邮编',
+        ms26: '长度在3到9个字符之间',
+        ms27: '请输入职业',
+        ms28: '长度在3到20个字符之间',
+        ms29: '请输入收款人账号',
+        ms30: '长度在2到48个字符之间',
+        ms31: '请输入收款人户名',
+        ms32: '长度在1到100个字符之间',
+        ms33: '长度在2到60个字符之间',
+        ms34: '长度在2到60个字符之间',
+        ms35: '请输入银行ID',
+        ms36: '请输入收款人账号',
+        ms37: '长度在2到48个字符之间',
+        ms38: '请输入收款人户名',
+        ms39: '长度在1到100个字符之间',
+        ms40: '请输入订单号',
+        ms41: '收款人ID',
+        ms42: '收款人电话',
+        ms43: '收款人银行',
+        ms44: '收款人开户行',
+        ms45: '收款人开户行',
+        ms46: '收款人地址',
+        ms47: '收款人出生日期',
+        ms48: '支付证明',
+        ms49: '银行流水',
+        ms50: '交易合同',
+        ms51: '交易发票',
+        ms52: '订单截图',
+        ms53: '身份证明',
+        ms54: '文件上传成功',
+        ms55: '文件上传失败',
+        ms56: '只能上传JPG/PNG格式的图片!',
+        ms57: '图片大小不能超过2MB',
+        ms58: '请完善表单信息',
+        ms59: '至少需要提供一项订单信息或文件',
+        ms60: '调单信息提交成功',
+        ms61: '提交失败',
+        ms62: '未知错误',
+        ms63: '上传失败',
+        ms64: '查询法币汇率失败',
+        ms65: '查询失败',
+        ms66: '支持银行:',
+        ms67: '最低金额100EUR',
+        ms68: '不支持中文字符',
+        ms69: '字符长度为',
+        ms70: '只支持英文',
+        ms71: '获取区号失败',
+        ms72: '收款人校验通过',
+        ms73: '付款人校验通过',
+        ms74: '整体校验通过',
+        ms75: '代付校验通过',
+        prev: '上一步',
+        submit: '提交',
+        mobileFormat: '请输入正确的手机号格式',
+        mobileCodeFormat: '请输入正确的区号格式',
+        postalCodeFormat: '请输入正确的邮政编码格式',
+        amountFormat: '请输入正确的金额格式',
+        currencyFormat: '请选择正确的币种',
+        shippingAddressFormat: '请输入正确的邮寄地址',
+        amountError: '请输入正确的金额',
+        currencyError: '请选择正确的币种',
+        shippingAddressError: '请输入正确的邮寄地址',
+        required:'',
+        requiredFieldsMissing:'请填写完整信息',
+        item95_1:'',
+        item95_2:'',
+        orderNo:'订单号',
+        paymentSuccess:'支付成功',
+        paymentFailed:'支付失败',
+        paymentError:'支付失败',
+        validateError:'校验失败',
+        validateFailed:'校验失败',
+        validateSuccess:'校验成功',
+        search:'搜索选项',
+    },
+    eur: {
+        title: 'EUR',
+        globalRemit: '全球速汇',
+        transactionRecord: '速汇记录',
+        eurTopup: 'eur充值'
+
+    },
+    'remit-success':{
+        title:'支付成功',
+        orderNo:'订单号',
+        paymentSuccess:'支付成功',
+        paymentFailed:'支付失败',
+        paymentError:'支付失败',
+        validateError:'校验失败',
+        validateFailed:'校验失败',
+        validateSuccess:'校验成功',
+        successTitle:'支付成功',
+        viewDetail:'查看详情',
+        backToHome:'返回首页',
+    },
+    'transfer-detail': {
+        title: '交易详情',
+        step1: '速汇申请',
+        step2: '处理中',
+        step3: '已成功',
+        amount: '金额',
+        currency: '币种',
+        status: '订单状态',
+        withdrawn: '已速汇',
+        time: '时间',
+        relationship: '收付款人关系',
+        relationship1: '兄弟',
+        relationship2: '姐夫/妹夫',
+        relationship3: '堂/表兄弟姐妹',
+        relationship4: '女儿',
+        relationship5: '父亲',
+        relationship6: '岳父/公公',
+        sourceFunds: '资金来源',
+        sourceFunds1: '现金',
+        sourceFunds2: '商业',
+        sourceFunds3: '礼物',
+        sourceFunds4: '工资',
+        sourceFunds5: '彩票',
+        sourceFunds6: '储蓄',
+        orderNo: '订单号',
+        mobile: '手机号码',
+        email: '邮箱',
+        country: '居住国家',
+        city: '居住城市',
+        address: '居住地址',
+        postalCode: '邮政编码',
+        occupation: '职业',
+        accountName: '收款人户名',
+        accountNumber: '收款人账号',
+        B1: '待处理',
+        B2: '支付中',
+        B3: '支付成功',
+        B4: '支付失败',
+        B6: '退款',
+        B11: '调单_待提交',
+        B12: '调单_待审核',
+        B13: '调单_审核通过',
+        B15: '调单_审核拒绝',
+        addTime: '创建时间',
+        postscript: '附言',
+        payPurpose: '付款目的',
+        name: '姓名',
+    },
+    remit: {
+        title: 'USDT',
+        available: 'USDT余额',
+        amount: 'USDT金额',
+        amountPlaceholder: '请输入USDT金额',
+        currency: 'USDT',
+        buy: '购买',
+        sell: '卖出',
+        recharge: '充值',
+        withdraw: '提现',
+        record: '交易记录',
+        item1: '购买',
+        item2: '卖出',
+        item3: '链上充值',
+        item4: '链上提现',
+    },
+    'reset-password': {
+        title: '重置密码',
+        email: '邮箱',
+        emailPlaceholder: '请输入邮箱',
+        emailError: '请输入正确的邮箱格式',
+        emailRequired: '请输入邮箱',
+        remember: '记起密码了?',
+        login: '返回登录',
+        reset: '重置密码',
+        resetSuccess: '',
+    },
+    "card-activation":{
+        title:'卡片激活',
+        cardNo: '银行卡号',
+        activePhoto: '手持护照和银行卡照图片',
+        activePhotoRequired: '请上传手持护照和银行卡照图片',
+        submit:'激活',
+        submitRequired:'激活成功',
+
+    },
+    "improve-info":{
+        title: '完善信息',
+        "p3": "请输入信息",
+        "p4": "区号",
+        "p5": "请选择区号",
+        "p6": "手机号",
+        "p7": "邮箱",
+        "p8": "姓",
+        "p9": "名",
+        "p10": "生日",
+        "p11": "国籍",
+        "p12": "请选择国籍",
+        "p13": "性别",
+        "p14": "请选择",
+        "p15": "男",
+        "p16": "女",
+        "p17": "国家",
+        "p18": "请选择国家",
+        "p19": "城市",
+        "p20": "请选择城市",
+        "p21": "详细地址",
+        "p22": "邮编",
+        "p23": "证件类型",
+        "p24": "请选择证件类型",
+        "p25": "身份证",
+        "p26": "护照",
+        "p27": "证件号",
+        "p28": "证件到期",
+        "p29": "证件照片",
+        "p30": "人脸照片",
+        "p31": "扩展字段",
+        "p32": "取消",
+        "p33": "提交",
+    }
+}

+ 610 - 0
src/i18n/locales/en.ts

@@ -0,0 +1,610 @@
+export default {
+    common: {
+        confirm: '确认',
+        cancel: '取消',
+        loading: '加载中...',
+        success: '成功',
+        fail: '失败',
+        input: '请输入',
+        choose: '请选择'
+    },
+    tabs: {
+        wallet: '钱包',
+        cards: '卡片',
+        finance: '金融',
+        mine: '我的'
+    },
+    wallet: {
+        title: '我的钱包',
+        balance: '余额',
+        transactions: '交易记录',
+    },
+    cards: {
+        title: '我的卡片',
+        balance: '余额',
+        transactions: '交易记录',
+        cardNo: '卡号',
+        recharge: '卡片充值',
+        findPassword: '找回密码',
+        freezeCard: '冻结卡片',
+        unfreezeCard: '解冻卡片',
+        selectCard: '选择卡片',
+        currency: '币种',
+        amount: '金额',
+        selectDateRange: '选择日期',
+        selectDate: '选择日期1',
+        start: '开始',
+        end: '结束'
+    },
+    finance: {
+        title: '金融',
+    },
+    mine: {
+        title: '我的',
+        logout: '确认退出登录吗?',
+    },
+    language: {
+        title: '语言设置',
+        selectLang: '选择语言',
+        cn: '中文简体',
+        zh: '中文繁体',
+        en: 'English',
+        de: 'German',
+        es: 'Spanish',
+        ar: 'Arabic',
+        id: 'Indonesian',
+        ms: 'Malay',
+        th: 'Thai',
+        vi: 'Vietnamese',
+        i1: '语言设置',
+        i2: '设置支付密码',
+        i3: '个人信息',
+        i4: '购卡记录',
+        i5: '关于PayouCard',
+        i6: '退出登录',
+    },
+    login: {
+        title: '登录',
+        username: '用户名',
+        password: '密码',
+        rememberPassword: '记住密码',
+        forgotPassword: '忘记密码?',
+    },
+    'card-transaction-detail': {
+        title: '交易详情',
+        currency: '币种',
+        amount: '交易金额',
+        type: '交易类型',
+        consume: '消费',
+        desc: '交易描述',
+        status: '订单状态',
+        time: '时间',
+    },
+    'pay-password': {
+        title: '支付密码',
+        change: '修改支付密码',
+        forget: '忘记支付密码',
+    },
+    'change-pay-password': {
+        title: '修改支付密码',
+        desc: '向您手机157***1673发送验证码',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'forget-pay-password': {
+        title: '忘记支付密码',
+        desc: '向您手机157***1673发送验证码',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'find-password': {
+        title: '找回密码',
+        desc: '请上传签名照片',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'freeze-card': {
+        title: '冻结卡片',
+        desc: '请上传签名照片',
+        tip: '支持PNG、JPG格式。',
+        nextStep: '确定',
+        formatError: '仅支持PNG、JPG格式',
+        needUpload: '请上传签名照片',
+        submitted: '已提交'
+    },
+    'card-recharge': {
+        title: '卡片充值',
+        currency: '币种',
+        cardNo: '卡号',
+        cardNoPlaceholder: '请输入卡号',
+        amount: '金额',
+        amountPlaceholder: '请输入金额,最低金额10',
+        walletBalance: '钱包余额',
+        all: '全部',
+        recharge: '充值',
+        fee: '手续费',
+        records: '充值记录',
+        walletPay: '钱包支付',
+        rechargeSuccess: '充值成功',
+        rechargePending: '充值中',
+        rechargeFailed: '充值失败',
+    },
+    'activate-card': {
+        title: '卡片激活',
+        desc: '请输入您的卡片信息进行激活',
+        cardNo: '卡号',
+        cardNoPlaceholder: '请输入卡号',
+        expireDate: '有效期',
+        expireDatePlaceholder: '请输入有效期',
+        cvv: 'CVV',
+        cvvPlaceholder: '请输入CVV',
+        password: '交易密码',
+        passwordPlaceholder: '请输入交易密码',
+        activate: '立即激活',
+    },
+    'select-card': {
+        title: '提交',
+        selectCard: '选择你的卡片',
+        desc: '我们为全球各地用户提供不同的卡片选择,包括实体卡和虚拟卡。',
+        physicalCard: '实体卡',
+        price: '价格:',
+        kycRequire: 'KYC认证:',
+        kycRequiredDesc: '需要',
+        kycRequiredDesc2: '不需要',
+        rechargeFee: '充值费用:',
+        atmFee: 'ATM取现:',
+        atmFeeDesc: 'Free in Europe (other 2%)',
+        atmFeeDesc2: 'Free in Europe (other 2%)',
+        spendFee: '消费手续费:',
+        annualFee: '年费:',
+        annualFeeDesc: 'Free',
+        usageArea: '使用地区:',
+        free: '免费',
+        submit: '提交',
+        masterCard: 'MasterCard 实体卡',
+        visaVirtualCard: 'Visa 虚拟卡(至珍卡)',
+        visaPhysicalCard: 'Visa 实体卡',
+        visaVirtualCard2: 'Visa 虚拟卡(畅游卡)',
+        masterCardDesc: '该卡支持全球线上和线下消费,ATM机取现;支持绑定PayPal/微信/支付宝等场景。',
+        visaVirtualCardDesc: '支持Apple Pay,Google Pay,微信,支付宝场景使用,无单笔消费限额,支持超大额消费。(卡片激活预计1-72小时完成)。',
+        visaPhysicalCardDesc: '该卡片能力强,支持和覆盖全球全平台的线上线下消费,ATM取现等功能(卡片激活时间预计需要48小时)。',
+        visaVirtualCard2Desc: '该卡片支持和覆盖全球全平台的线上消费功能。最擅长商户: Apple,Facebook,Walmart,PAYPAL',
+        status: '状态',
+        statusDesc: '可用',
+        statusDesc2: '不可用',
+        currency: '币种',
+        needPhotoForActiveCard: '激活卡片时是否需要手持护照和银行卡照片',
+        needPhotoForActiveCardDesc: '需要',
+        needPhotoForActiveCardDesc2: '不需要',
+        needPhotoForOperateCard: '冻结、解冻和找回密码时是否需要提供用户签名照',
+        needPhotoForOperateCardDesc: '需要',
+        needPhotoForOperateCardDesc2: '不需要',
+    },
+    kyc: {
+        title: 'KYC认证',
+        desc: '请填写您的身份信息以完成认证',
+        type: '证件类型',
+        i1: 'EEA文件证照',
+        i2: '手持照片',
+        data: '证件照片',
+        firstName: '名',
+        lastName: '姓',
+        address: '邮寄地址',
+        email: '邮箱',
+        phone: '手机号',
+        phoneCode: '区号',
+        postalCode: '邮政编码',
+        submit: '提交',
+        kycSuccess: '身份认证成功',
+        kycFail: '身份认证失败',
+        step1: '身份认证',
+        step2: '支付',
+        step3: '邮寄',
+        step1Desc: '请填写您的身份信息以完成认证',
+        step2Desc: '请填写您的支付信息以完成支付',
+        step3Desc: '请填写您的邮寄信息以完成邮寄',
+        step4Desc: '请填写您的支付信息以完成支付',
+        kycFailDesc: '请填写完整的身份认证信息',
+        emailError: '请输入正确的邮箱格式',
+        phoneError: '请输入正确的手机号格式',
+        postalCodeError: '请输入正确的邮政编码格式',
+        amountError: '请输入正确的金额',
+        currencyError: '请选择正确的币种',
+        shippingAddressError: '请输入正确的邮寄地址',
+        amount: '金额',
+        currency: '币种',
+        shippingAddress: '邮寄地址',
+        next: '下一步',
+        submitKyc: '提交认证',
+        country: '选择区号',
+        status: '状态',
+        statusDesc: '不存在',
+        statusDesc2: '审核中',
+        statusDesc3: '认证成功',
+        statusDesc4: '拒绝',
+
+    },
+    'eur-remit': {
+        title: '全球速汇',
+        available: '可速汇余额:',
+        country: '国家和地区',
+        currency: '币种',
+        amount: '金额',
+        amountPlaceholder: '请输入欧元金额,最低金额100',
+        note: '附言',
+        notePlaceholder: '请输入Note(附言码)',
+        select: '请选择',
+        next: '下一步',
+        "item1":"收款银行:",
+        "item2":"交易货币:",
+        "item3":"目标货币代码:",
+        "item4":"目标国家代码:",
+        "item5":"汇率:",
+        "item6":"基本信息",
+        "item7":"收款银行ID:",
+        "item8":"用户唯一ID:",
+        "item9":"速汇金额:",
+        "item10":"附言:",
+        "item11":"收付款人关系:",
+        "item12":"兄弟",
+        "item13":"姐夫/妹夫",
+        "item14":"堂/表兄弟姐妹",
+        "item15":"女儿",
+        "item16":"父亲",
+        "item17":"岳父/公公",
+        "item18":"朋友",
+        "item19":"祖父",
+        "item20":"祖母",
+        "item21":"丈夫",
+        "item22":"母亲",
+        "item23":"岳母/婆婆",
+        "item24":"侄子/外甥",
+        "item25":"侄女/外甥女",
+        "item26":"本人",
+        "item27":"姐妹",
+        "item28":"兄嫂/弟妹",
+        "item29":"儿子",
+        "item30":"叔伯/舅姑",
+        "item31":"妻子",
+        "item32":"其他",
+        "item33":"资金来源:",
+        "item34":"现金",
+        "item35":"商业",
+        "item36":"礼物",
+        "item37":"工资",
+        "item38":"彩票",
+        "item39":"储蓄",
+        "item40":"其他",
+        "item41":"付款目的:",
+        "item42":"购买货物或服务",
+        "item43":"运费和运输成本",
+        "item44":"教育费用",
+        "item45":"移民投资",
+        "item46":"慈善捐赠",
+        "item47":"家庭支持",
+        "item48":"股息或利息支付",
+        "item49":"国际贸易",
+        "item50":"付款人信息",
+        "item51":"付款人类型:",
+        "item52":"个人",
+        "item53":"付款人姓:",
+        "item54":"付款人名:",
+        "item55":"付款人证件号码:",
+        "item56":"付款人证件类型:",
+        "item57":"护照",
+        "item58":"身份证",
+        "item59":"证件颁发国家:",
+        "item60":"请选择证件颁发国家",
+        "item61":"出生日期:",
+        "item62":"国籍:",
+        "item63":"请选择国籍",
+        "item64":"手机号码:",
+        "item65":"居住国家:",
+        "item66":"请选择居住国家",
+        "item67":"居住城市代码:",
+        "item68":"请选择居住城市",
+        "item69":"地址:",
+        "item70":"邮编:",
+        "item71":"职业:",
+        "item72":"校验付款人",
+        "item73":"收款人信息",
+        "item74":"收款人银行ID:",
+        "item75":"收款人账号:",
+        "item76":"收款人户名:",
+        "item77":"收款人居住国家:",
+        "item78":"请选择收款人居住国家",
+        "item79":"收款人居住城市:",
+        "item80":"请选择收款人居住城市",
+        "item81":"收款人地址:",
+        "item82":"收款人邮编:",
+        "item83":"收款人姓:",
+        "item84":"收款人名:",
+        "item85":"收款人银行编码:",
+        "item86":"收款人国籍:",
+        "item87":"请选择收款人国籍",
+        "item88":"收款人证件类型:",
+        "item89":"护照",
+        "item90":"身份证",
+        "item91":"收款人证件号码:",
+        "item92":"证件有效期:",
+        "item93":"出生日期:",
+        "item94":"手机区号:",
+        "item95":"请选择区号",
+        "item96":"手机号码:",
+        "item97":"银行账户类型:",
+        "item98":"支票账户",
+        "item99":"储蓄账户",
+        "item100":"定期存款账户",
+        "item101":"其它账户",
+        "item102":"校验收款人",
+        "item103":"校验付款人",
+        "item104":"确认信息",
+        "item105":"提交代付",
+        "item106":"订单号:",
+        "item107":"商户原始订单号:",
+        "item108":"支付状态:",
+        text1: '提交调单信息或文件',
+        text2: '订单号:',
+        text3: '请输入PayouCard订单号',
+        text4: '(PayouCard订单号,必填)',
+        text5: '订单信息',
+        text6: '请提供订单相关信息',
+        text7: '请选择信息类型',
+        text8: '信息类型:',
+        text9: '请选择信息类型',
+        text10: '请输入信息内容',
+        text11: '信息内容:',
+        text12: '请输入信息内容',
+        text13: '(不支持中文字符,最多200个字符)',
+        text14: '添加订单信息',
+        text15: '订单文件',
+        text16: '请提供订单相关文件',
+        text17: '请选择文件类型',
+        text18: '文件类型:',
+        text19: '请选择文件类型',
+        text20: '请上传文件',
+        text21: '文件:',
+        text22: '选择文件',
+        text23: '删除',
+        text24: '(每个文件不能超过2M,只支持图片格式',
+        ms: '最低金额100EUR,最多2位小数',
+        ms3: '请输入银行ID',
+        ms4: '请输入用户唯一ID',
+        ms5: '请输入商户订单号',
+        ms6: '请输入速汇金额',
+        ms7: '请输入附言',
+        ms8: '长度在5到64个字符之间',
+        ms9: '请输入付款人姓',
+        ms10: '长度在2到50个字符之间',
+        ms10_1: '请输入付款人名',
+        ms11: '请输入付款人证件号码',
+        ms12: '长度在6到18个字符之间',
+        ms13: '请选择付款人证件类型',
+        ms14: '请输入证件颁发国家',
+        ms15: '2位国家代码',
+        ms16: '请选择出生日期',
+        ms17: '请输入国籍',
+        ms18: '请输入手机号码',
+        ms19: '例如:+37012345678,长度在8到15个字符之间',
+        ms20: '请输入居住国家',
+        ms21: '2位国家代码',
+        ms22: '请输入居住城市代码',
+        ms23: '请输入地址',
+        ms24: '长度在10到100个字符之间',
+        ms25: '请输入邮编',
+        ms26: '长度在3到9个字符之间',
+        ms27: '请输入职业',
+        ms28: '长度在3到20个字符之间',
+        ms29: '请输入收款人账号',
+        ms30: '长度在2到48个字符之间',
+        ms31: '请输入收款人户名',
+        ms32: '长度在1到100个字符之间',
+        ms33: '长度在2到60个字符之间',
+        ms34: '长度在2到60个字符之间',
+        ms35: '请输入银行ID',
+        ms36: '请输入收款人账号',
+        ms37: '长度在2到48个字符之间',
+        ms38: '请输入收款人户名',
+        ms39: '长度在1到100个字符之间',
+        ms40: '请输入订单号',
+        ms41: '收款人ID',
+        ms42: '收款人电话',
+        ms43: '收款人银行',
+        ms44: '收款人开户行',
+        ms45: '收款人开户行',
+        ms46: '收款人地址',
+        ms47: '收款人出生日期',
+        ms48: '支付证明',
+        ms49: '银行流水',
+        ms50: '交易合同',
+        ms51: '交易发票',
+        ms52: '订单截图',
+        ms53: '身份证明',
+        ms54: '文件上传成功',
+        ms55: '文件上传失败',
+        ms56: '只能上传JPG/PNG格式的图片!',
+        ms57: '图片大小不能超过2MB',
+        ms58: '请完善表单信息',
+        ms59: '至少需要提供一项订单信息或文件',
+        ms60: '调单信息提交成功',
+        ms61: '提交失败',
+        ms62: '未知错误',
+        ms63: '上传失败',
+        ms64: '查询法币汇率失败',
+        ms65: '查询失败',
+        ms66: '支持银行:',
+        ms67: '最低金额100EUR',
+        ms68: '不支持中文字符',
+        ms69: '字符长度为',
+        ms70: '只支持英文',
+        ms71: '获取区号失败',
+        ms72: '收款人校验通过',
+        ms73: '付款人校验通过',
+        ms74: '整体校验通过',
+        ms75: '代付校验通过',
+        prev: '上一步',
+        submit: '提交',
+        mobileFormat: '请输入正确的手机号格式',
+        mobileCodeFormat: '请输入正确的区号格式',
+        postalCodeFormat: '请输入正确的邮政编码格式',
+        amountFormat: '请输入正确的金额格式',
+        currencyFormat: '请选择正确的币种',
+        shippingAddressFormat: '请输入正确的邮寄地址',
+        amountError: '请输入正确的金额',
+        currencyError: '请选择正确的币种',
+        shippingAddressError: '请输入正确的邮寄地址',
+        required:'',
+        requiredFieldsMissing:'请填写完整信息',
+        item95_1:'',
+        item95_2:'',
+        orderNo:'订单号',
+        paymentSuccess:'支付成功',
+        paymentFailed:'支付失败',
+        paymentError:'支付失败',
+        validateError:'校验失败',
+        validateFailed:'校验失败',
+        validateSuccess:'校验成功',
+        search:'搜索选项',
+    },
+    eur: {
+        title: 'EUR',
+        globalRemit: '全球速汇',
+        transactionRecord: '速汇记录',
+        eurTopup: 'eur充值'
+
+    },
+    'remit-success':{
+        title:'支付成功',
+        orderNo:'订单号',
+        paymentSuccess:'支付成功',
+        paymentFailed:'支付失败',
+        paymentError:'支付失败',
+        validateError:'校验失败',
+        validateFailed:'校验失败',
+        validateSuccess:'校验成功',
+        successTitle:'支付成功',
+        viewDetail:'查看详情',
+        backToHome:'返回首页',
+    },
+    'transfer-detail': {
+        title: '交易详情',
+        step1: '速汇申请',
+        step2: '处理中',
+        step3: '已成功',
+        amount: '金额',
+        currency: '币种',
+        status: '订单状态',
+        withdrawn: '已速汇',
+        time: '时间',
+        relationship: '收付款人关系',
+        relationship1: '兄弟',
+        relationship2: '姐夫/妹夫',
+        relationship3: '堂/表兄弟姐妹',
+        relationship4: '女儿',
+        relationship5: '父亲',
+        relationship6: '岳父/公公',
+        sourceFunds: '资金来源',
+        sourceFunds1: '现金',
+        sourceFunds2: '商业',
+        sourceFunds3: '礼物',
+        sourceFunds4: '工资',
+        sourceFunds5: '彩票',
+        sourceFunds6: '储蓄',
+        orderNo: '订单号',
+        mobile: '手机号码',
+        email: '邮箱',
+        country: '居住国家',
+        city: '居住城市',
+        address: '居住地址',
+        postalCode: '邮政编码',
+        occupation: '职业',
+        accountName: '收款人户名',
+        accountNumber: '收款人账号',
+        B1: '待处理',
+        B2: '支付中',
+        B3: '支付成功',
+        B4: '支付失败',
+        B6: '退款',
+        B11: '调单_待提交',
+        B12: '调单_待审核',
+        B13: '调单_审核通过',
+        B15: '调单_审核拒绝',
+        addTime: '创建时间',
+        postscript: '附言',
+        payPurpose: '付款目的',
+        name: '姓名',
+    },
+    remit: {
+        title: 'USDT',
+        available: 'USDT余额',
+        amount: 'USDT金额',
+        amountPlaceholder: '请输入USDT金额',
+        currency: 'USDT',
+        buy: '购买',
+        sell: '卖出',
+        recharge: '充值',
+        withdraw: '提现',
+        record: '交易记录',
+        item1: '购买',
+        item2: '卖出',
+        item3: '链上充值',
+        item4: '链上提现',
+    },
+    'reset-password': {
+        title: '重置密码',
+        email: '邮箱',
+        emailPlaceholder: '请输入邮箱',
+        emailError: '请输入正确的邮箱格式',
+        emailRequired: '请输入邮箱',
+        remember: '记起密码了?',
+        login: '返回登录',
+        reset: '重置密码',
+        resetSuccess: '',
+    },
+    "card-activation":{
+        title:'卡片激活',
+        cardNo: '银行卡号',
+        activePhoto: '手持护照和银行卡照图片',
+        activePhotoRequired: '请上传手持护照和银行卡照图片',
+        submit:'激活',
+        submitRequired:'激活成功',
+
+    },
+    "improve-info":{
+        title: '完善信息',
+        "p3": "请输入信息",
+        "p4": "区号",
+        "p5": "请选择区号",
+        "p6": "手机号",
+        "p7": "邮箱",
+        "p8": "姓",
+        "p9": "名",
+        "p10": "生日",
+        "p11": "国籍",
+        "p12": "请选择国籍",
+        "p13": "性别",
+        "p14": "请选择",
+        "p15": "男",
+        "p16": "女",
+        "p17": "国家",
+        "p18": "请选择国家",
+        "p19": "城市",
+        "p20": "请选择城市",
+        "p21": "详细地址",
+        "p22": "邮编",
+        "p23": "证件类型",
+        "p24": "请选择证件类型",
+        "p25": "身份证",
+        "p26": "护照",
+        "p27": "证件号",
+        "p28": "证件到期",
+        "p29": "证件照片",
+        "p30": "人脸照片",
+        "p31": "扩展字段",
+        "p32": "取消",
+        "p33": "提交",
+    }
+}

+ 610 - 0
src/i18n/locales/zh.ts

@@ -0,0 +1,610 @@
+export default {
+    common: {
+        confirm: '确认',
+        cancel: '取消',
+        loading: '加载中...',
+        success: '成功',
+        fail: '失败',
+        input: '请输入',
+        choose: '请选择'
+    },
+    tabs: {
+        wallet: '钱包',
+        cards: '卡片',
+        finance: '金融',
+        mine: '我的'
+    },
+    wallet: {
+        title: '我的钱包',
+        balance: '余额',
+        transactions: '交易记录',
+    },
+    cards: {
+        title: '我的卡片',
+        balance: '余额',
+        transactions: '交易记录',
+        cardNo: '卡号',
+        recharge: '卡片充值',
+        findPassword: '找回密码',
+        freezeCard: '冻结卡片',
+        unfreezeCard: '解冻卡片',
+        selectCard: '选择卡片',
+        currency: '币种',
+        amount: '金额',
+        selectDateRange: '选择日期',
+        selectDate: '选择日期1',
+        start: '开始',
+        end: '结束'
+    },
+    finance: {
+        title: '金融',
+    },
+    mine: {
+        title: '我的',
+        logout: '确认退出登录吗?',
+    },
+    language: {
+        title: '语言设置',
+        selectLang: '选择语言',
+        cn: '中文简体',
+        zh: '中文繁体',
+        en: 'English',
+        de: 'German',
+        es: 'Spanish',
+        ar: 'Arabic',
+        id: 'Indonesian',
+        ms: 'Malay',
+        th: 'Thai',
+        vi: 'Vietnamese',
+        i1: '语言设置',
+        i2: '设置支付密码',
+        i3: '个人信息',
+        i4: '购卡记录',
+        i5: '关于PayouCard',
+        i6: '退出登录',
+    },
+    login: {
+        title: '登录',
+        username: '用户名',
+        password: '密码',
+        rememberPassword: '记住密码',
+        forgotPassword: '忘记密码?',
+    },
+    'card-transaction-detail': {
+        title: '交易详情',
+        currency: '币种',
+        amount: '交易金额',
+        type: '交易类型',
+        consume: '消费',
+        desc: '交易描述',
+        status: '订单状态',
+        time: '时间',
+    },
+    'pay-password': {
+        title: '支付密码',
+        change: '修改支付密码',
+        forget: '忘记支付密码',
+    },
+    'change-pay-password': {
+        title: '修改支付密码',
+        desc: '向您手机157***1673发送验证码',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'forget-pay-password': {
+        title: '忘记支付密码',
+        desc: '向您手机157***1673发送验证码',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'find-password': {
+        title: '找回密码',
+        desc: '请上传签名照片',
+        input: '请输入验证码',
+        getCode: '获取验证码',
+        nextStep: '下一步',
+    },
+    'freeze-card': {
+        title: '冻结卡片',
+        desc: '请上传签名照片',
+        tip: '支持PNG、JPG格式。',
+        nextStep: '确定',
+        formatError: '仅支持PNG、JPG格式',
+        needUpload: '请上传签名照片',
+        submitted: '已提交'
+    },
+    'card-recharge': {
+        title: '卡片充值',
+        currency: '币种',
+        cardNo: '卡号',
+        cardNoPlaceholder: '请输入卡号',
+        amount: '金额',
+        amountPlaceholder: '请输入金额,最低金额10',
+        walletBalance: '钱包余额',
+        all: '全部',
+        recharge: '充值',
+        fee: '手续费',
+        records: '充值记录',
+        walletPay: '钱包支付',
+        rechargeSuccess: '充值成功',
+        rechargePending: '充值中',
+        rechargeFailed: '充值失败',
+    },
+    'activate-card': {
+        title: '卡片激活',
+        desc: '请输入您的卡片信息进行激活',
+        cardNo: '卡号',
+        cardNoPlaceholder: '请输入卡号',
+        expireDate: '有效期',
+        expireDatePlaceholder: '请输入有效期',
+        cvv: 'CVV',
+        cvvPlaceholder: '请输入CVV',
+        password: '交易密码',
+        passwordPlaceholder: '请输入交易密码',
+        activate: '立即激活',
+    },
+    'select-card': {
+        title: '提交',
+        selectCard: '选择你的卡片',
+        desc: '我们为全球各地用户提供不同的卡片选择,包括实体卡和虚拟卡。',
+        physicalCard: '实体卡',
+        price: '价格:',
+        kycRequire: 'KYC认证:',
+        kycRequiredDesc: '需要',
+        kycRequiredDesc2: '不需要',
+        rechargeFee: '充值费用:',
+        atmFee: 'ATM取现:',
+        atmFeeDesc: 'Free in Europe (other 2%)',
+        atmFeeDesc2: 'Free in Europe (other 2%)',
+        spendFee: '消费手续费:',
+        annualFee: '年费:',
+        annualFeeDesc: 'Free',
+        usageArea: '使用地区:',
+        free: '免费',
+        submit: '提交',
+        masterCard: 'MasterCard 实体卡',
+        visaVirtualCard: 'Visa 虚拟卡(至珍卡)',
+        visaPhysicalCard: 'Visa 实体卡',
+        visaVirtualCard2: 'Visa 虚拟卡(畅游卡)',
+        masterCardDesc: '该卡支持全球线上和线下消费,ATM机取现;支持绑定PayPal/微信/支付宝等场景。',
+        visaVirtualCardDesc: '支持Apple Pay,Google Pay,微信,支付宝场景使用,无单笔消费限额,支持超大额消费。(卡片激活预计1-72小时完成)。',
+        visaPhysicalCardDesc: '该卡片能力强,支持和覆盖全球全平台的线上线下消费,ATM取现等功能(卡片激活时间预计需要48小时)。',
+        visaVirtualCard2Desc: '该卡片支持和覆盖全球全平台的线上消费功能。最擅长商户: Apple,Facebook,Walmart,PAYPAL',
+        status: '状态',
+        statusDesc: '可用',
+        statusDesc2: '不可用',
+        currency: '币种',
+        needPhotoForActiveCard: '激活卡片时是否需要手持护照和银行卡照片',
+        needPhotoForActiveCardDesc: '需要',
+        needPhotoForActiveCardDesc2: '不需要',
+        needPhotoForOperateCard: '冻结、解冻和找回密码时是否需要提供用户签名照',
+        needPhotoForOperateCardDesc: '需要',
+        needPhotoForOperateCardDesc2: '不需要',
+    },
+    kyc: {
+        title: 'KYC认证',
+        desc: '请填写您的身份信息以完成认证',
+        type: '证件类型',
+        i1: 'EEA文件证照',
+        i2: '手持照片',
+        data: '证件照片',
+        firstName: '名',
+        lastName: '姓',
+        address: '邮寄地址',
+        email: '邮箱',
+        phone: '手机号',
+        phoneCode: '区号',
+        postalCode: '邮政编码',
+        submit: '提交',
+        kycSuccess: '身份认证成功',
+        kycFail: '身份认证失败',
+        step1: '身份认证',
+        step2: '支付',
+        step3: '邮寄',
+        step1Desc: '请填写您的身份信息以完成认证',
+        step2Desc: '请填写您的支付信息以完成支付',
+        step3Desc: '请填写您的邮寄信息以完成邮寄',
+        step4Desc: '请填写您的支付信息以完成支付',
+        kycFailDesc: '请填写完整的身份认证信息',
+        emailError: '请输入正确的邮箱格式',
+        phoneError: '请输入正确的手机号格式',
+        postalCodeError: '请输入正确的邮政编码格式',
+        amountError: '请输入正确的金额',
+        currencyError: '请选择正确的币种',
+        shippingAddressError: '请输入正确的邮寄地址',
+        amount: '金额',
+        currency: '币种',
+        shippingAddress: '邮寄地址',
+        next: '下一步',
+        submitKyc: '提交认证',
+        country: '选择区号',
+        status: '状态',
+        statusDesc: '不存在',
+        statusDesc2: '审核中',
+        statusDesc3: '认证成功',
+        statusDesc4: '拒绝',
+
+    },
+    'eur-remit': {
+        title: '全球速汇',
+        available: '可速汇余额:',
+        country: '国家和地区',
+        currency: '币种',
+        amount: '金额',
+        amountPlaceholder: '请输入欧元金额,最低金额100',
+        note: '附言',
+        notePlaceholder: '请输入Note(附言码)',
+        select: '请选择',
+        next: '下一步',
+        "item1":"收款银行:",
+        "item2":"交易货币:",
+        "item3":"目标货币代码:",
+        "item4":"目标国家代码:",
+        "item5":"汇率:",
+        "item6":"基本信息",
+        "item7":"收款银行ID:",
+        "item8":"用户唯一ID:",
+        "item9":"速汇金额:",
+        "item10":"附言:",
+        "item11":"收付款人关系:",
+        "item12":"兄弟",
+        "item13":"姐夫/妹夫",
+        "item14":"堂/表兄弟姐妹",
+        "item15":"女儿",
+        "item16":"父亲",
+        "item17":"岳父/公公",
+        "item18":"朋友",
+        "item19":"祖父",
+        "item20":"祖母",
+        "item21":"丈夫",
+        "item22":"母亲",
+        "item23":"岳母/婆婆",
+        "item24":"侄子/外甥",
+        "item25":"侄女/外甥女",
+        "item26":"本人",
+        "item27":"姐妹",
+        "item28":"兄嫂/弟妹",
+        "item29":"儿子",
+        "item30":"叔伯/舅姑",
+        "item31":"妻子",
+        "item32":"其他",
+        "item33":"资金来源:",
+        "item34":"现金",
+        "item35":"商业",
+        "item36":"礼物",
+        "item37":"工资",
+        "item38":"彩票",
+        "item39":"储蓄",
+        "item40":"其他",
+        "item41":"付款目的:",
+        "item42":"购买货物或服务",
+        "item43":"运费和运输成本",
+        "item44":"教育费用",
+        "item45":"移民投资",
+        "item46":"慈善捐赠",
+        "item47":"家庭支持",
+        "item48":"股息或利息支付",
+        "item49":"国际贸易",
+        "item50":"付款人信息",
+        "item51":"付款人类型:",
+        "item52":"个人",
+        "item53":"付款人姓:",
+        "item54":"付款人名:",
+        "item55":"付款人证件号码:",
+        "item56":"付款人证件类型:",
+        "item57":"护照",
+        "item58":"身份证",
+        "item59":"证件颁发国家:",
+        "item60":"请选择证件颁发国家",
+        "item61":"出生日期:",
+        "item62":"国籍:",
+        "item63":"请选择国籍",
+        "item64":"手机号码:",
+        "item65":"居住国家:",
+        "item66":"请选择居住国家",
+        "item67":"居住城市代码:",
+        "item68":"请选择居住城市",
+        "item69":"地址:",
+        "item70":"邮编:",
+        "item71":"职业:",
+        "item72":"校验付款人",
+        "item73":"收款人信息",
+        "item74":"收款人银行ID:",
+        "item75":"收款人账号:",
+        "item76":"收款人户名:",
+        "item77":"收款人居住国家:",
+        "item78":"请选择收款人居住国家",
+        "item79":"收款人居住城市:",
+        "item80":"请选择收款人居住城市",
+        "item81":"收款人地址:",
+        "item82":"收款人邮编:",
+        "item83":"收款人姓:",
+        "item84":"收款人名:",
+        "item85":"收款人银行编码:",
+        "item86":"收款人国籍:",
+        "item87":"请选择收款人国籍",
+        "item88":"收款人证件类型:",
+        "item89":"护照",
+        "item90":"身份证",
+        "item91":"收款人证件号码:",
+        "item92":"证件有效期:",
+        "item93":"出生日期:",
+        "item94":"手机区号:",
+        "item95":"请选择区号",
+        "item96":"手机号码:",
+        "item97":"银行账户类型:",
+        "item98":"支票账户",
+        "item99":"储蓄账户",
+        "item100":"定期存款账户",
+        "item101":"其它账户",
+        "item102":"校验收款人",
+        "item103":"校验付款人",
+        "item104":"确认信息",
+        "item105":"提交代付",
+        "item106":"订单号:",
+        "item107":"商户原始订单号:",
+        "item108":"支付状态:",
+        text1: '提交调单信息或文件',
+        text2: '订单号:',
+        text3: '请输入PayouCard订单号',
+        text4: '(PayouCard订单号,必填)',
+        text5: '订单信息',
+        text6: '请提供订单相关信息',
+        text7: '请选择信息类型',
+        text8: '信息类型:',
+        text9: '请选择信息类型',
+        text10: '请输入信息内容',
+        text11: '信息内容:',
+        text12: '请输入信息内容',
+        text13: '(不支持中文字符,最多200个字符)',
+        text14: '添加订单信息',
+        text15: '订单文件',
+        text16: '请提供订单相关文件',
+        text17: '请选择文件类型',
+        text18: '文件类型:',
+        text19: '请选择文件类型',
+        text20: '请上传文件',
+        text21: '文件:',
+        text22: '选择文件',
+        text23: '删除',
+        text24: '(每个文件不能超过2M,只支持图片格式',
+        ms: '最低金额100EUR,最多2位小数',
+        ms3: '请输入银行ID',
+        ms4: '请输入用户唯一ID',
+        ms5: '请输入商户订单号',
+        ms6: '请输入速汇金额',
+        ms7: '请输入附言',
+        ms8: '长度在5到64个字符之间',
+        ms9: '请输入付款人姓',
+        ms10: '长度在2到50个字符之间',
+        ms10_1: '请输入付款人名',
+        ms11: '请输入付款人证件号码',
+        ms12: '长度在6到18个字符之间',
+        ms13: '请选择付款人证件类型',
+        ms14: '请输入证件颁发国家',
+        ms15: '2位国家代码',
+        ms16: '请选择出生日期',
+        ms17: '请输入国籍',
+        ms18: '请输入手机号码',
+        ms19: '例如:+37012345678,长度在8到15个字符之间',
+        ms20: '请输入居住国家',
+        ms21: '2位国家代码',
+        ms22: '请输入居住城市代码',
+        ms23: '请输入地址',
+        ms24: '长度在10到100个字符之间',
+        ms25: '请输入邮编',
+        ms26: '长度在3到9个字符之间',
+        ms27: '请输入职业',
+        ms28: '长度在3到20个字符之间',
+        ms29: '请输入收款人账号',
+        ms30: '长度在2到48个字符之间',
+        ms31: '请输入收款人户名',
+        ms32: '长度在1到100个字符之间',
+        ms33: '长度在2到60个字符之间',
+        ms34: '长度在2到60个字符之间',
+        ms35: '请输入银行ID',
+        ms36: '请输入收款人账号',
+        ms37: '长度在2到48个字符之间',
+        ms38: '请输入收款人户名',
+        ms39: '长度在1到100个字符之间',
+        ms40: '请输入订单号',
+        ms41: '收款人ID',
+        ms42: '收款人电话',
+        ms43: '收款人银行',
+        ms44: '收款人开户行',
+        ms45: '收款人开户行',
+        ms46: '收款人地址',
+        ms47: '收款人出生日期',
+        ms48: '支付证明',
+        ms49: '银行流水',
+        ms50: '交易合同',
+        ms51: '交易发票',
+        ms52: '订单截图',
+        ms53: '身份证明',
+        ms54: '文件上传成功',
+        ms55: '文件上传失败',
+        ms56: '只能上传JPG/PNG格式的图片!',
+        ms57: '图片大小不能超过2MB',
+        ms58: '请完善表单信息',
+        ms59: '至少需要提供一项订单信息或文件',
+        ms60: '调单信息提交成功',
+        ms61: '提交失败',
+        ms62: '未知错误',
+        ms63: '上传失败',
+        ms64: '查询法币汇率失败',
+        ms65: '查询失败',
+        ms66: '支持银行:',
+        ms67: '最低金额100EUR',
+        ms68: '不支持中文字符',
+        ms69: '字符长度为',
+        ms70: '只支持英文',
+        ms71: '获取区号失败',
+        ms72: '收款人校验通过',
+        ms73: '付款人校验通过',
+        ms74: '整体校验通过',
+        ms75: '代付校验通过',
+        prev: '上一步',
+        submit: '提交',
+        mobileFormat: '请输入正确的手机号格式',
+        mobileCodeFormat: '请输入正确的区号格式',
+        postalCodeFormat: '请输入正确的邮政编码格式',
+        amountFormat: '请输入正确的金额格式',
+        currencyFormat: '请选择正确的币种',
+        shippingAddressFormat: '请输入正确的邮寄地址',
+        amountError: '请输入正确的金额',
+        currencyError: '请选择正确的币种',
+        shippingAddressError: '请输入正确的邮寄地址',
+        required:'',
+        requiredFieldsMissing:'请填写完整信息',
+        item95_1:'',
+        item95_2:'',
+        orderNo:'订单号',
+        paymentSuccess:'支付成功',
+        paymentFailed:'支付失败',
+        paymentError:'支付失败',
+        validateError:'校验失败',
+        validateFailed:'校验失败',
+        validateSuccess:'校验成功',
+        search:'搜索选项',
+    },
+    eur: {
+        title: 'EUR',
+        globalRemit: '全球速汇',
+        transactionRecord: '速汇记录',
+        eurTopup: 'eur充值'
+
+    },
+    'remit-success':{
+        title:'支付成功',
+        orderNo:'订单号',
+        paymentSuccess:'支付成功',
+        paymentFailed:'支付失败',
+        paymentError:'支付失败',
+        validateError:'校验失败',
+        validateFailed:'校验失败',
+        validateSuccess:'校验成功',
+        successTitle:'支付成功',
+        viewDetail:'查看详情',
+        backToHome:'返回首页',
+    },
+    'transfer-detail': {
+        title: '交易详情',
+        step1: '速汇申请',
+        step2: '处理中',
+        step3: '已成功',
+        amount: '金额',
+        currency: '币种',
+        status: '订单状态',
+        withdrawn: '已速汇',
+        time: '时间',
+        relationship: '收付款人关系',
+        relationship1: '兄弟',
+        relationship2: '姐夫/妹夫',
+        relationship3: '堂/表兄弟姐妹',
+        relationship4: '女儿',
+        relationship5: '父亲',
+        relationship6: '岳父/公公',
+        sourceFunds: '资金来源',
+        sourceFunds1: '现金',
+        sourceFunds2: '商业',
+        sourceFunds3: '礼物',
+        sourceFunds4: '工资',
+        sourceFunds5: '彩票',
+        sourceFunds6: '储蓄',
+        orderNo: '订单号',
+        mobile: '手机号码',
+        email: '邮箱',
+        country: '居住国家',
+        city: '居住城市',
+        address: '居住地址',
+        postalCode: '邮政编码',
+        occupation: '职业',
+        accountName: '收款人户名',
+        accountNumber: '收款人账号',
+        B1: '待处理',
+        B2: '支付中',
+        B3: '支付成功',
+        B4: '支付失败',
+        B6: '退款',
+        B11: '调单_待提交',
+        B12: '调单_待审核',
+        B13: '调单_审核通过',
+        B15: '调单_审核拒绝',
+        addTime: '创建时间',
+        postscript: '附言',
+        payPurpose: '付款目的',
+        name: '姓名',
+    },
+    remit: {
+        title: 'USDT',
+        available: 'USDT余额',
+        amount: 'USDT金额',
+        amountPlaceholder: '请输入USDT金额',
+        currency: 'USDT',
+        buy: '购买',
+        sell: '卖出',
+        recharge: '充值',
+        withdraw: '提现',
+        record: '交易记录',
+        item1: '购买',
+        item2: '卖出',
+        item3: '链上充值',
+        item4: '链上提现',
+    },
+    'reset-password': {
+        title: '重置密码',
+        email: '邮箱',
+        emailPlaceholder: '请输入邮箱',
+        emailError: '请输入正确的邮箱格式',
+        emailRequired: '请输入邮箱',
+        remember: '记起密码了?',
+        login: '返回登录',
+        reset: '重置密码',
+        resetSuccess: '',
+    },
+    "card-activation":{
+        title:'卡片激活',
+        cardNo: '银行卡号',
+        activePhoto: '手持护照和银行卡照图片',
+        activePhotoRequired: '请上传手持护照和银行卡照图片',
+        submit:'激活',
+        submitRequired:'激活成功',
+
+    },
+    "improve-info":{
+        title: '完善信息',
+        "p3": "请输入信息",
+        "p4": "区号",
+        "p5": "请选择区号",
+        "p6": "手机号",
+        "p7": "邮箱",
+        "p8": "姓",
+        "p9": "名",
+        "p10": "生日",
+        "p11": "国籍",
+        "p12": "请选择国籍",
+        "p13": "性别",
+        "p14": "请选择",
+        "p15": "男",
+        "p16": "女",
+        "p17": "国家",
+        "p18": "请选择国家",
+        "p19": "城市",
+        "p20": "请选择城市",
+        "p21": "详细地址",
+        "p22": "邮编",
+        "p23": "证件类型",
+        "p24": "请选择证件类型",
+        "p25": "身份证",
+        "p26": "护照",
+        "p27": "证件号",
+        "p28": "证件到期",
+        "p29": "证件照片",
+        "p30": "人脸照片",
+        "p31": "扩展字段",
+        "p32": "取消",
+        "p33": "提交",
+    }
+}

+ 0 - 191
src/locales/cn.ts

@@ -1,191 +0,0 @@
-export default {
-  common: {
-    confirm: '确认',
-    cancel: '取消',
-    loading: '加载中...',
-    success: '成功',
-    fail: '失败'
-  },
-  tabs: {
-    wallet: '钱包',
-    cards: '卡片',
-    finance: '金融',
-    mine: '我的'
-  },
-  wallet: {
-    title: '我的钱包',
-    balance: '余额',
-    transactions: '交易记录',
-  },
-  cards: {
-    title: '我的卡片',
-    balance: '余额',
-    transactions: '交易记录',
-  },
-  finance: {
-    title: '金融',
-  },
-  mine: {
-    title: '我的',
-  },
-  language: {
-    title: '语言设置',
-    selectLang: '选择语言',
-    cn: '中文简体',
-    zh: '中文繁体',
-    en: 'English',
-    de: 'German',
-    es: 'Spanish',
-    ar: 'Arabic',
-    id: 'Indonesian',
-    ms: 'Malay',
-    th: 'Thai',
-    vi: 'Vietnamese',
-    i1: '语言设置',
-    i2: '设置支付密码',
-    i3: '安全设置',
-    i4: '购卡记录',
-    i5: '关于PayouCard',
-    i6: '退出登录',
-  },
-  login: {
-    title: '登录',
-    username: '用户名',
-    password: '密码',
-    rememberPassword: '记住密码',
-    forgotPassword: '忘记密码?',
-  },
-  "card-transaction-detail": {
-    title: '交易详情',
-    currency: '币种',
-    amount: '交易金额',
-    type: '交易类型',
-    consume: '消费',
-    desc: '交易描述',
-    status: '订单状态',
-    time: '时间',
-  },
-  "pay-password": {
-    title: '支付密码',
-    change: '修改支付密码',
-    forget: '忘记支付密码',
-  },
-  "change-pay-password": {
-    title: '修改支付密码',
-    desc: '向您手机157***1673发送验证码',
-    input: '请输入验证码',
-    getCode: '获取验证码',
-    nextStep: '下一步',
-  },
-  "forget-pay-password": {
-    title: '忘记支付密码',
-    desc: '向您手机157***1673发送验证码',
-    input: '请输入验证码',
-    getCode: '获取验证码',
-    nextStep: '下一步',
-  },
-  "find-password": {
-    title: '找回密码',
-    desc: '请上传签名照片',
-    input: '请输入验证码',
-    getCode: '获取验证码',
-    nextStep: '下一步',
-  },
-  "freeze-card": {
-    title: "冻结卡片",
-    desc: "请上传签名照片",
-    tip: "支持PNG、JPG格式。",
-    nextStep: "确定",
-    formatError: "仅支持PNG、JPG格式",
-    needUpload: "请上传签名照片",
-    submitted: "已提交"
-  },
-  "card-recharge": {
-    title: '卡片充值',
-    currency: '币种',
-    cardNo: '卡号',
-    cardNoPlaceholder: '请输入卡号',
-    amount: '金额',
-    amountPlaceholder: '请输入金额,最低金额10',
-    walletBalance: '钱包余额',
-    all: '全部',
-    recharge: '充值',
-    fee: '手续费',
-    records: '交易记录',
-    walletPay: '钱包支付',
-    rechargeSuccess: '充值成功',
-  },
-  "activate-card": {
-    title: '卡片激活',
-    desc: '请输入您的卡片信息进行激活',
-    cardNo: '卡号',
-    cardNoPlaceholder: '请输入卡号',
-    expireDate: '有效期',
-    expireDatePlaceholder: '请输入有效期',
-    cvv: 'CVV',
-    cvvPlaceholder: '请输入CVV',
-    password: '交易密码',
-    passwordPlaceholder: '请输入交易密码',
-    activate: '立即激活',
-  },
-  "select-card": {
-    title: '提交',
-    selectCard: "选择你的卡片",
-    desc: "我们为全球各地用户提供不同的卡片选择,包括实体卡和虚拟卡。",
-    physicalCard: "实体卡",
-    price: "价格:",
-    kycRequired: "KYC认证:",
-    kycRequiredDesc: "需要",
-    kycRequiredDesc2: "不需要",
-    rechargeFee: "充值费用:",
-    atmFee: "ATM取现:",
-    atmFeeDesc: "Free in Europe (other 2%)",
-    atmFeeDesc2: "Free in Europe (other 2%)",
-    spendFee: "消费手续费:",
-    annualFee: "年费:",
-    annualFeeDesc: "Free",
-    usageArea: "使用地区:",
-    free: "免费",
-    submit: "提交",
-    masterCard: "MasterCard 实体卡",
-    visaVirtualCard: "Visa 虚拟卡(至珍卡)",
-    visaPhysicalCard: "Visa 实体卡",
-    visaVirtualCard2: "Visa 虚拟卡(畅游卡)",
-    masterCardDesc: "该卡支持全球线上和线下消费,ATM机取现;支持绑定PayPal/微信/支付宝等场景。",
-    visaVirtualCardDesc: "支持Apple Pay,Google Pay,微信,支付宝场景使用,无单笔消费限额,支持超大额消费。(卡片激活预计1-72小时完成)。",
-    visaPhysicalCardDesc: "该卡片能力强,支持和覆盖全球全平台的线上线下消费,ATM取现等功能(卡片激活时间预计需要48小时)。",
-    visaVirtualCard2Desc: "该卡片支持和覆盖全球全平台的线上消费功能。最擅长商户: Apple,Facebook,Walmart,PAYPAL",
-  },
-  kyc: {
-    title: '身份认证',
-    desc: '请填写您的身份信息以完成认证',
-    firstName: '名',
-    lastName: '姓',
-    address: '邮寄地址',
-    email: '邮箱',
-    phone: '手机号',
-    phoneCode: '区号',
-    postalCode: '邮政编码',
-    submit: '提交',
-    kycSuccess: '身份认证成功',
-    kycFail: '身份认证失败',
-    step1: '身份认证',
-    step2: '支付',
-    step3: '邮寄',
-    step1Desc: '请填写您的身份信息以完成认证',
-    step2Desc: '请填写您的支付信息以完成支付',
-    step3Desc: '请填写您的邮寄信息以完成邮寄',
-    step4Desc: '请填写您的支付信息以完成支付',
-    kycFailDesc: '请填写完整的身份认证信息',
-    emailError: '请输入正确的邮箱格式',
-    phoneError: '请输入正确的手机号格式',
-    postalCodeError: '请输入正确的邮政编码格式',
-    amountError: '请输入正确的金额',
-    currencyError: '请选择正确的币种',
-    shippingAddressError: '请输入正确的邮寄地址',
-    amount: '金额',
-    currency: '币种',
-    shippingAddress: '邮寄地址',
-    next: '下一步',
-  },
-}

+ 0 - 50
src/locales/de.ts

@@ -1,50 +0,0 @@
-export default {
-    common: {
-      confirm: '确认',
-      cancel: '取消',
-      loading: '加载中...',
-      success: '成功',
-      fail: '失败'
-    },
-    tabs: {
-      wallet: '钱包',
-      cards: '卡片',
-      finance: '金融',
-      mine: '我的'
-    },
-    wallet: {
-      title: '我的钱包',
-      balance: '余额',
-      transactions: '交易记录',
-    },
-    cards: {
-      title: '我的卡片',
-      balance: '余额',
-      transactions: '交易记录',
-    },
-    finance: {
-      title: '金融',
-    },
-    mine: {
-      title: '我的',
-    },
-    language: {
-      title: '语言设置',
-      selectLang: '选择语言',
-      cn: '中文简体',
-      zh: '中文繁体',
-      en: 'English',
-      de: 'German',
-      es: 'Spanish',
-      ar: 'Arabic',
-      id: 'Indonesian',
-      ms: 'Malay',
-      th: 'Thai',
-      vi: 'Vietnamese',
-      i1: '语言设置',
-      i2: '设置支付密码',
-      i3: '安全设置',
-      i4: '购卡记录',
-      i5: '关于PayouCard',
-    },
-  }

+ 0 - 50
src/locales/en.ts

@@ -1,50 +0,0 @@
-export default {
-    common: {
-      confirm: '确认',
-      cancel: '取消',
-      loading: '加载中...',
-      success: '成功',
-      fail: '失败'
-    },
-    tabs: {
-      wallet: '钱包',
-      cards: '卡片',
-      finance: '金融',
-      mine: '我的'
-    },
-    wallet: {
-      title: '我的钱包',
-      balance: '余额',
-      transactions: '交易记录',
-    },
-    cards: {
-      title: '我的卡片',
-      balance: '余额',
-      transactions: '交易记录',
-    },
-    finance: {
-      title: '金融',
-    },
-    mine: {
-      title: '我的',
-    },
-    language: {
-      title: '语言设置',
-      selectLang: '选择语言',
-      cn: '中文简体',
-      zh: '中文繁体',
-      en: 'English',
-      de: 'German',
-      es: 'Spanish',
-      ar: 'Arabic',
-      id: 'Indonesian',
-      ms: 'Malay',
-      th: 'Thai',
-      vi: 'Vietnamese',
-      i1: '语言设置',
-      i2: '设置支付密码',
-      i3: '安全设置',
-      i4: '购卡记录',
-      i5: '关于PayouCard',
-    },
-  }

+ 0 - 63
src/locales/zh.ts

@@ -1,63 +0,0 @@
-export default {
-    common: {
-      confirm: '确认',
-      cancel: '取消',
-      loading: '加载中...',
-      success: '成功',
-      fail: '失败'
-    },
-    tabs: {
-      wallet: '钱包',
-      cards: '卡片',
-      finance: '金融',
-      mine: '我的'
-    },
-    wallet: {
-      title: '我的钱包',
-      balance: '余额',
-      transactions: '交易记录',
-    },
-    cards: {
-      title: '我的卡片',
-      balance: '余额',
-      transactions: '交易记录',
-    },
-    finance: {
-      title: '金融',
-    },
-    mine: {
-      title: '我的',
-    },
-    language: {
-      title: '语言设置',
-      selectLang: '选择语言',
-      cn: '中文简体',
-      zh: '中文繁体',
-      en: 'English',
-      de: 'German',
-      es: 'Spanish',
-      ar: 'Arabic',
-      id: 'Indonesian',
-      ms: 'Malay',
-      th: 'Thai',
-      vi: 'Vietnamese',
-      i1: '语言设置',
-      i2: '设置支付密码',
-      i3: '安全设置',
-      i4: '购卡记录',
-      i5: '关于PayouCard',
-    },
-    "card-recharge": {
-      currency: '币种',
-      account: '账户',
-      amount: '金额',
-      amountPlaceholder: '请输入金额,最低金额10',
-      walletBalance: '钱包余额',
-      all: '全部',
-      recharge: '充值',
-      fee: '手续费',
-      records: '交易记录',
-      walletPay: '钱包支付',
-      rechargeSuccess: '充值成功',
-    },
-  }

+ 4 - 2
src/main.ts

@@ -8,7 +8,7 @@ import App from './App.vue'
 import global from './plugin/global'
 import global from './plugin/global'
 import vant from './plugin/vant'
 import vant from './plugin/vant'
 import router from './router'
 import router from './router'
-import {i18n} from './plugin/i18n'
+import {i18n} from '@/i18n'
 
 
 import 'uno.css'
 import 'uno.css'
 import 'vue-cropper/dist/index.css'
 import 'vue-cropper/dist/index.css'
@@ -24,4 +24,6 @@ const app = createApp(App)
 const store = createPinia()
 const store = createPinia()
 const head = createHead()
 const head = createHead()
 
 
-app.use(store).use(router).use(head).use(vant).use(global).use(i18n).mount('#app')
+app.use(i18n).use(store).use(router).use(head).use(vant).use(global)
+
+app.mount('#app')

+ 0 - 21
src/plugin/i18n.ts

@@ -1,21 +0,0 @@
-import { createI18n } from 'vue-i18n'
-import cn from '../locales/cn'
-import zh from '../locales/zh'
-import en from '../locales/en'
-import de from '../locales/de'
-
-const i18n = createI18n({
-  legacy: false, // 使用 Composition API 模式
-  locale: localStorage.getItem('language') || 'cn', // 默认语言
-  fallbackLocale: 'en', // 备用语言
-  messages: {
-    'zh': zh,
-    'en': en,
-    'de': de,
-    'cn': cn
-  }
-})
-const localesList = ['cn','en','zh', 'de']
-// const localesList = ['en','cn','zh', 'de','es','ar','id','ms','th','vi','ko','pt','fa','tr']
-
-export {i18n,localesList}

+ 25 - 1
src/router/index.ts

@@ -1,5 +1,6 @@
 import type { RouteRecordRaw } from 'vue-router'
 import type { RouteRecordRaw } from 'vue-router'
-
+import { createRouter, createWebHashHistory } from 'vue-router'
+import { userToken } from '@/composables/config'
 const pages = import.meta.glob('../views/*.vue')
 const pages = import.meta.glob('../views/*.vue')
 
 
 let routes: Array<RouteRecordRaw> = []
 let routes: Array<RouteRecordRaw> = []
@@ -23,4 +24,27 @@ const router = createRouter({
     routes,
     routes,
 })
 })
 
 
+// 白名单路由
+const whiteList = ['/login', '/language', '/reset/password']
+
+// 路由守卫
+router.beforeEach((to, from, next) => {
+    // 获取登录状态
+    const isLoggedIn = userToken.value // 假设使用token来判断登录状态
+
+    if (isLoggedIn) {
+        // 已登录状态
+        next()
+    } else {
+        // 未登录状态
+        if (whiteList.includes(to.path)) {
+            // 在白名单中,允许访问
+            next()
+        } else {
+            // 不在白名单中,重定向到登录页
+            next('/login')
+        }
+    }
+})
+
 export default router
 export default router

+ 2 - 0
src/stores/pinia.types.ts

@@ -1,6 +1,8 @@
 export interface GlobalState {
 export interface GlobalState {
     globalLoading: boolean
     globalLoading: boolean
     routerLoading: boolean
     routerLoading: boolean
+    requestLoading: boolean
+    fullScreenLoading: boolean
     ISLocal: boolean
     ISLocal: boolean
     ISDEV: boolean
     ISDEV: boolean
     ISPRE: boolean
     ISPRE: boolean

+ 11 - 1
src/stores/use-global-store.ts

@@ -2,8 +2,10 @@ import type { GlobalState } from './pinia.types'
 
 
 const useStore = defineStore('globalStore', () => {
 const useStore = defineStore('globalStore', () => {
     const state: GlobalState = reactive({
     const state: GlobalState = reactive({
-        globalLoading: true,
+        globalLoading: false,
         routerLoading: false,
         routerLoading: false,
+        requestLoading: false,
+        fullScreenLoading: false,
         ISLocal: import.meta.env.VITE_APP_ENV === 'development',
         ISLocal: import.meta.env.VITE_APP_ENV === 'development',
         ISDEV: import.meta.env.VITE_APP_ENV === 'development',
         ISDEV: import.meta.env.VITE_APP_ENV === 'development',
         ISPRE: import.meta.env.VITE_APP_ENV === 'pre-release',
         ISPRE: import.meta.env.VITE_APP_ENV === 'pre-release',
@@ -17,11 +19,19 @@ const useStore = defineStore('globalStore', () => {
     const setRouterLoading = (payload: boolean) => {
     const setRouterLoading = (payload: boolean) => {
         state.routerLoading = payload
         state.routerLoading = payload
     }
     }
+    const setRequestLoading = (payload: boolean) => {
+        state.requestLoading = payload
+    }
+    const setFullScreenLoading = (payload: boolean) => {
+        state.fullScreenLoading = payload
+    }
 
 
     return {
     return {
         ...toRefs(state),
         ...toRefs(state),
         setGlobalLoading,
         setGlobalLoading,
         setRouterLoading,
         setRouterLoading,
+        setRequestLoading,
+        setFullScreenLoading,
     }
     }
 })
 })
 
 

+ 25 - 0
src/stores/use-user-store.ts

@@ -10,9 +10,16 @@ export interface UserInfo {
   email: string
   email: string
   phone: string
   phone: string
 }
 }
+export interface AccountInfo {
+  loginName: string
+  password: string
+  rememberPassword: boolean
+}
 const STORAGE_KEY = 'user'
 const STORAGE_KEY = 'user'
+const ACCOUNT_INFO_KEY = 'accountInfo'
 const useUserStore = defineStore('userStore', () => {
 const useUserStore = defineStore('userStore', () => {
   const userInfo = ref<UserInfo | null>(null)
   const userInfo = ref<UserInfo | null>(null)
+  const accountInfo = ref<AccountInfo | null>(null)
   const isLoggedIn = ref(false)
   const isLoggedIn = ref(false)
   const initUserInfo = () => {
   const initUserInfo = () => {
     const encryptedInfo = ls.get(STORAGE_KEY)
     const encryptedInfo = ls.get(STORAGE_KEY)
@@ -24,12 +31,26 @@ const useUserStore = defineStore('userStore', () => {
       }
       }
     }
     }
   }
   }
+  const initAccountInfo = () => {
+    const encryptedInfo = ls.get(ACCOUNT_INFO_KEY)
+    if (encryptedInfo) {
+      const decryptedInfo = crypt.decrypt(encryptedInfo)
+      if (decryptedInfo) {
+        accountInfo.value = JSON.parse(decryptedInfo)
+      }
+    }
+  }
   const saveUserInfo = (info: UserInfo) => {
   const saveUserInfo = (info: UserInfo) => {
     userInfo.value = info
     userInfo.value = info
     isLoggedIn.value = true
     isLoggedIn.value = true
     const encryptedInfo = crypt.encrypt(JSON.stringify(info))
     const encryptedInfo = crypt.encrypt(JSON.stringify(info))
     ls.set(STORAGE_KEY, encryptedInfo)
     ls.set(STORAGE_KEY, encryptedInfo)
   }
   }
+  const saveAccountInfo = (info: AccountInfo) => {
+    accountInfo.value = info
+    const encryptedInfo = crypt.encrypt(JSON.stringify(info))
+    ls.set(ACCOUNT_INFO_KEY, encryptedInfo)
+  }
   const clearUserInfo = () => {
   const clearUserInfo = () => {
     userInfo.value = null
     userInfo.value = null
     isLoggedIn.value = false
     isLoggedIn.value = false
@@ -37,10 +58,14 @@ const useUserStore = defineStore('userStore', () => {
     ls.remove(STORAGE_KEY)
     ls.remove(STORAGE_KEY)
   }
   }
   initUserInfo()
   initUserInfo()
+  initAccountInfo()
   return {
   return {
     userInfo,
     userInfo,
+    accountInfo,
     isLoggedIn,
     isLoggedIn,
     saveUserInfo,
     saveUserInfo,
+    initAccountInfo,
+    saveAccountInfo,
     clearUserInfo,
     clearUserInfo,
   }
   }
 })
 })

+ 0 - 1
src/views/activate-card.vue

@@ -70,7 +70,6 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref } from 'vue'
 import { showToast, showLoadingToast } from 'vant'
 import { showToast, showLoadingToast } from 'vant'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'

+ 420 - 0
src/views/card-activation.vue

@@ -0,0 +1,420 @@
+<template>
+    <div class="page kyc-page" v-if="form.cardNo">
+        <div class="kyc-form">
+            <remit-input v-model:value="form.cardNo" fkey="cardNo" :readonly="true" :label="t('card-activation.cardNo')" />
+            <remit-input
+                v-model:value="form.activePhoto"
+                type="upload"
+                :required="true"
+                fkey="activePhoto"
+                :label="t('card-activation.activePhoto')"
+                :rules="[{ required: true, message: t('card-activation.activePhoto') }]"
+                @change="handleChange"
+            />
+
+            <div class="kyc-button">
+                <van-button type="primary" block @click="handleNext1">{{ t('card-activation.submit') }}</van-button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+import { ucardApi } from '@/api/ucard'
+import { showToast } from 'vant'
+const route = useRoute()
+const { t } = useI18n()
+const router = useRouter()
+const form = ref({})
+
+const { id } = route.query as { id: string }
+const formData = ref<typeof form.value>({ id } as any)
+const getCardInfo = async () => {
+    if (!id) return
+    try {
+        const res = await ucardApi.cardSingle({ id })
+        if (res.code === 200 && res.data) {
+            form.value = res.data
+        }
+    } catch (error) {
+        showToast(error || String(error))
+    }
+}
+
+const handleNext1 = async () => {
+    formData.value = { ...form.value, ...formData.value }
+    if (!formData.value.activePhoto) {
+        showToast(t('card-activation.activePhotoRequired'))
+        return
+    }
+
+    try {
+        const res = await ucardApi.ucardActivate(formData.value)
+        if (res.code === 200) {
+            showToast(t('card-activation.submitRequired'))
+            router.back()
+        } else {
+            showToast(res.msg)
+        }
+    } catch (error) {
+        showToast('error')
+    }
+}
+
+onMounted(() => {
+    getCardInfo()
+})
+const handleChange = (value: any) => {
+    formData.value = { ...formData.value, [value.key]: value.value }
+}
+</script>
+
+<style scoped lang="scss">
+.kyc-page {
+    min-height: 100vh;
+    background: linear-gradient(135deg, #232323 0%, #2a2a2a 100%);
+    color: #fff;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 32px 12px;
+}
+.kyc-header {
+    width: 100%;
+    text-align: center;
+    margin-bottom: 24px;
+    h2 {
+        font-size: var(--font-size-18);
+        color: var(--main-yellow);
+        margin-bottom: 8px;
+    }
+    p {
+        color: #bbb;
+        font-size: var(--font-size-12);
+        line-height: 1.5;
+    }
+}
+.kyc-form {
+    width: 100%;
+    max-width: 350px;
+    border-radius: 20px;
+    padding: 28px 18px 18px 18px;
+    display: flex;
+    flex-direction: column;
+    gap: 18px;
+}
+
+.form-item {
+    margin-bottom: 0;
+    ::v-deep {
+        .van-field {
+            background: #181818;
+            border-radius: 12px;
+            padding: 12px 16px;
+            transition: all 0.3s ease;
+
+            &:focus-within {
+                background: #1f1f1f;
+            }
+
+            .van-field__label {
+                color: #bbb;
+                font-size: var(--font-size-14);
+                width: 80px;
+            }
+
+            .van-field__control {
+                color: #fff;
+                font-size: var(--font-size-14);
+                min-height: 20px;
+                line-height: 20px;
+
+                &::placeholder {
+                    color: #666;
+                    font-size: var(--font-size-14);
+                }
+
+                &::-webkit-input-placeholder {
+                    color: #666;
+                }
+
+                &::-moz-placeholder {
+                    color: #666;
+                    opacity: 1;
+                }
+            }
+
+            .van-field__right-icon {
+                color: var(--main-yellow);
+                margin-left: 8px;
+            }
+
+            .van-field__error-message {
+                color: #ff4d4f;
+                font-size: var(--font-size-12);
+                margin-top: 4px;
+            }
+        }
+    }
+}
+.kyc-button {
+    margin-top: 18px;
+    ::v-deep {
+        .van-button {
+            height: 44px;
+            border-radius: 24px;
+            background: var(--main-yellow);
+            border: none;
+            color: #232323;
+            font-size: var(--font-size-16);
+            font-weight: bold;
+
+            &:active {
+                opacity: 0.9;
+            }
+        }
+    }
+}
+.apply-card-steps {
+    width: 100%;
+    max-width: 350px;
+    margin-bottom: 32px;
+
+    .steps-top {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 0 12px;
+    }
+
+    .step {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 8px;
+
+        .step-circle {
+            width: 32px;
+            height: 32px;
+            border-radius: 50%;
+            background: var(--action-bg, #181818);
+            border: 2px solid #444;
+            color: #444;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: var(--font-size-16);
+            font-weight: bold;
+            transition: all 0.3s ease;
+        }
+
+        .step-label {
+            font-size: var(--font-size-14);
+            color: #444;
+            transition: all 0.3s ease;
+        }
+
+        &.active {
+            .step-circle {
+                background: var(--main-yellow);
+                border-color: var(--main-yellow);
+                color: #232323;
+            }
+
+            .step-label {
+                color: var(--main-yellow);
+            }
+        }
+    }
+
+    .step-dash {
+        flex: 1;
+        height: 2px;
+        margin: 0 8px;
+        position: relative;
+        top: -16px;
+        transition: all 0.3s ease;
+        background-image: linear-gradient(to right, #444 50%, transparent 50%);
+        background-size: 8px 2px;
+        background-repeat: repeat-x;
+
+        &.active {
+            background-image: linear-gradient(to right, var(--main-yellow) 50%, transparent 50%);
+        }
+    }
+}
+
+.phone-input {
+    display: flex;
+    align-items: center;
+    background: #181818;
+    border-radius: 12px;
+    padding: 0 16px;
+    height: 44px;
+    transition: all 0.3s ease;
+
+    &:focus-within {
+        background: #1f1f1f;
+    }
+
+    .phone-code {
+        width: 20%;
+        ::v-deep .van-cell {
+            &::after {
+                border-bottom: 0;
+            }
+            .van-icon {
+                font-size: var(--font-size-14);
+                line-height: 20px;
+            }
+        }
+        ::v-deep {
+            .van-field {
+                background: transparent;
+                padding-left: 0;
+                padding-right: 0;
+
+                .van-field__control {
+                    color: var(--white);
+                    text-align: center;
+                    padding: 0;
+                }
+            }
+        }
+    }
+    .phone-code-active {
+        ::v-deep .van-cell {
+            .van-icon {
+                color: var(--white);
+            }
+        }
+    }
+
+    .phone-divider {
+        width: 1px;
+        height: 20px;
+        background: #333;
+        margin: 0 12px;
+        transition: all 0.3s ease;
+    }
+
+    .phone-number {
+        flex: 1;
+        ::v-deep {
+            .van-field {
+                background: transparent;
+                padding-left: 10px;
+
+                .van-field__control {
+                    color: #fff;
+                    text-align: left;
+                    padding: 0;
+
+                    &::placeholder {
+                        color: #666;
+                        text-align: left;
+                    }
+                }
+            }
+        }
+    }
+}
+
+@keyframes slideDown {
+    from {
+        opacity: 0;
+        transform: translateY(-10px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+@keyframes fadeIn {
+    from {
+        opacity: 0;
+    }
+    to {
+        opacity: 0.2;
+    }
+}
+
+::v-deep {
+    .van-picker {
+        background: #181818;
+
+        .van-picker__toolbar {
+            background: #181818;
+            border-bottom: 1px solid #333;
+            height: 44px;
+            padding: 0 16px;
+
+            .van-picker__title {
+                color: #fff;
+                font-size: var(--font-size-16);
+                font-weight: 500;
+            }
+
+            .van-picker__cancel,
+            .van-picker__confirm {
+                color: var(--main-yellow);
+                font-size: var(--font-size-14);
+                padding: 0 8px;
+                height: 28px;
+                line-height: 28px;
+                border-radius: 14px;
+                transition: all 0.3s ease;
+
+                &:active {
+                    opacity: 0.8;
+                    background: rgba(255, 193, 7, 0.1);
+                }
+            }
+        }
+
+        .van-picker-column {
+            color: #fff;
+
+            .van-picker-column__item {
+                color: #fff;
+                font-size: var(--font-size-14);
+                padding: 0 16px;
+                height: 44px;
+                line-height: 44px;
+                transition: all 0.3s ease;
+
+                &--selected {
+                    color: var(--main-yellow);
+                    font-weight: 500;
+                    font-size: var(--font-size-16);
+                }
+
+                &:active {
+                    background: rgba(255, 255, 255, 0.05);
+                }
+            }
+
+            .van-picker-column__wrapper {
+                &::after {
+                    border-color: #333;
+                }
+            }
+        }
+
+        .van-picker__mask {
+            background-image: linear-gradient(180deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4)),
+                linear-gradient(0deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4));
+        }
+
+        .van-picker__indicator {
+            height: 44px;
+            background: rgba(255, 193, 7, 0.05);
+            border-top: 1px solid rgba(255, 193, 7, 0.1);
+            border-bottom: 1px solid rgba(255, 193, 7, 0.1);
+        }
+    }
+}
+</style>

+ 107 - 58
src/views/card-recharge.vue

@@ -4,7 +4,7 @@
             <div class="form-group">
             <div class="form-group">
                 <label class="form-label">{{ t('card-recharge.currency') }}</label>
                 <label class="form-label">{{ t('card-recharge.currency') }}</label>
                 <div class="form-select" @click="showCurrency = true" style="cursor: pointer">
                 <div class="form-select" @click="showCurrency = true" style="cursor: pointer">
-                    {{ currency }}
+                    {{ currencyT }}
                 </div>
                 </div>
             </div>
             </div>
             <div class="form-group">
             <div class="form-group">
@@ -25,91 +25,104 @@
         </div>
         </div>
         <div class="transactions">
         <div class="transactions">
             <div class="trans-title">{{ t('card-recharge.records') }}</div>
             <div class="trans-title">{{ t('card-recharge.records') }}</div>
-            <div v-for="t in transactions" :key="t.time + t.amount" class="transaction">
+            <div v-for="i in transactions" :key="i.id" class="transaction">
+                <div class="trans-icon">
+                    <div class="trans-icon-inner">$</div>
+                </div>
                 <div class="trans-left">
                 <div class="trans-left">
-                    <div class="trans-type">{{ t.type }}</div>
+                    <div class="trans-type">{{ i.typeDesc }}</div>
+                    <div class="trans-desc">{{ statusMap[i.status] }}</div>
                 </div>
                 </div>
                 <div class="trans-right">
                 <div class="trans-right">
-                    <div class="trans-amount">{{ t.amount }} {{ t.currency }}</div>
-                    <div class="trans-date">{{ t.time }}</div>
+                    <div class="trans-amount">{{ i.amount }} {{ i.currency }}</div>
+                    <div class="trans-date">{{ i.time }}</div>
                 </div>
                 </div>
             </div>
             </div>
         </div>
         </div>
-        <CurrencySelect v-model="showCurrency" :options="currencyOptions" @select="onCurrencySelect" />
+        <CurrencySelect v-model="showCurrency" :options="rechargeCurrencyInfoList" @select="onCurrencySelect" />
     </div>
     </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref, computed, onMounted } from 'vue'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import CurrencySelect from '@/components/CurrencySelect.vue'
 import CurrencySelect from '@/components/CurrencySelect.vue'
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
 import { ucardApi } from '@/api/ucard'
 import { ucardApi } from '@/api/ucard'
+import { userApi } from '@/api/user'
 import { showToast } from 'vant'
 import { showToast } from 'vant'
+import { CardInfo } from '@/api/ucard'
+const { t } = useI18n()
 interface Transaction {
 interface Transaction {
-    type: string
+    id: string
+    typeDesc: string
+    remark: string
     amount: number
     amount: number
+    currency: string
+    status: string
     time: string
     time: string
 }
 }
+const statusMap = {
+    '1': t('card-recharge.rechargeSuccess'),
+    '2': t('card-recharge.rechargeFailed'),
+    '3': t('card-recharge.rechargePending'),
+}
 const route = useRoute()
 const route = useRoute()
-const { uniqueId, cardNo } = route.query
-const { t } = useI18n()
+const { id } = route.query as { id: string }
+const cardInfo = ref<CardInfo | null>(null)
+const cardNo = ref('')
 const currency = ref('')
 const currency = ref('')
+const currencyT = ref('')
 const exchangeRate = ref(0)
 const exchangeRate = ref(0)
 const receivedAmount = ref(0)
 const receivedAmount = ref(0)
 const showCurrency = ref(false)
 const showCurrency = ref(false)
-const currencyOptions = ref<{ label: string; value: string }[]>([])
+const rechargeCurrencyInfoList = ref<{ label: string; value: string }[]>([])
 const amount = ref('')
 const amount = ref('')
 const balance = ref(0)
 const balance = ref(0)
 const transactions = ref<Transaction[]>([])
 const transactions = ref<Transaction[]>([])
-// 获取充值记录
-async function getRechargeList() {
+const getCardInfo = async () => {
+    if (!id) return
     try {
     try {
-        const res = await ucardApi.rechargeList({
-            cardNo: cardNo as string,
-            // currency: currency.value,
-            page: { current: 1, row: 10 },
-        })
+        const res = await ucardApi.cardSingle({ id })
         if (res.code === 200 && res.data) {
         if (res.code === 200 && res.data) {
-            transactions.value = res.data
+            cardInfo.value = res.data
+            cardNo.value = cardInfo.value?.cardNo || ''
+            rechargeCurrencyInfoList.value = cardInfo.value?.rechargeCurrencyInfoList.map((item: { currency: string }) => ({
+                label: item.currency,
+                value: item.currency,
+            }))
+            currencyT.value = cardInfo.value?.rechargeCurrencyInfoList[0].currency || ''
+            if (cardInfo.value?.cardNo) {
+                getBalance()
+            }
         }
         }
     } catch (error) {
     } catch (error) {
-        showToast(error)
+        showToast(error || String(error))
     }
     }
 }
 }
-// 获取卡片余额
-async function getCardBalance() {
+
+// 获取充值记录
+async function getRechargeList() {
     try {
     try {
-        const res = await ucardApi.ucardBalance({
-            cardNo: cardNo as string,
-            uniqueId: uniqueId as string,
+        const res = await ucardApi.rechargeList({
+            cardNo: cardInfo.value?.cardNo,
+            currency: currencyT.value,
+            page: { current: 1, row: 10 },
         })
         })
         if (res.code === 200 && res.data) {
         if (res.code === 200 && res.data) {
-            currencyOptions.value = res.data.map((item: { currency: string }) => ({
-                label: item.currency,
-                value: item.currency,
-                ...item,
-            }))
-            currency.value = res.data[0].currency
-            balance.value = res.data[0].amount
-            getRechargeList()
+            transactions.value = res.data
         }
         }
     } catch (error) {
     } catch (error) {
-        console.error('获取卡片余额失败:', error)
+        showToast(error as string)
     }
     }
 }
 }
-onMounted(() => {
-    if (cardNo) {
-        getCardBalance()
-    }
-})
+
+
 const canRecharge = computed(() => Number(amount.value) >= 10 && Number(amount.value) <= balance.value)
 const canRecharge = computed(() => Number(amount.value) >= 10 && Number(amount.value) <= balance.value)
 function useAll() {
 function useAll() {
     amount.value = balance.value.toString()
     amount.value = balance.value.toString()
 }
 }
 function onCurrencySelect(item: { label: string; value: string; amount: number }) {
 function onCurrencySelect(item: { label: string; value: string; amount: number }) {
-    currency.value = item.value
-    balance.value = item.amount
+    currencyT.value = item.value
     getRechargeList()
     getRechargeList()
 }
 }
 // 充值
 // 充值
@@ -117,30 +130,37 @@ async function handleRecharge() {
     estimateRecharge()
     estimateRecharge()
     try {
     try {
         const params = {
         const params = {
-            cardNo: cardNo as string,
-            uniqueId: uniqueId as string,
+            cardNo: cardInfo.value?.cardNo,
+            uniqueId: cardInfo.value?.uniqueId,
             amount: Number(amount.value),
             amount: Number(amount.value),
-            currency: 'USDT',
+            currency: currencyT.value,
         }
         }
         const response = await ucardApi.ucardRecharge(params)
         const response = await ucardApi.ucardRecharge(params)
         if (response.code === 200) {
         if (response.code === 200) {
             showToast('充值请求已提交成功')
             showToast('充值请求已提交成功')
-            const orderId = response.data.orderId
-            return orderId
+            amount.value = ''
+            getRechargeList()
         } else {
         } else {
             showToast(response.msg)
             showToast(response.msg)
         }
         }
     } catch (error) {
     } catch (error) {
-        showToast(error)
+        showToast(error || String(error))
     }
     }
 }
 }
+const getBalance = async () => {
+    try {
+        const res = await userApi.walletBalance()
+        currency.value = "USD"
+        balance.value = res.data || 0
+    } catch (error) {}
+}
 // 预估充值
 // 预估充值
 async function estimateRecharge() {
 async function estimateRecharge() {
     try {
     try {
         const params = {
         const params = {
             amount: Number(amount.value),
             amount: Number(amount.value),
-            cardTypeId: 3,
-            currency: 'USDT',
+            cardTypeId: cardInfo.value?.cardTypeId,
+            currency: currencyT.value,
         }
         }
         const response = await ucardApi.ucardRechargeEstimate(params)
         const response = await ucardApi.ucardRechargeEstimate(params)
         if (response.code === 200) {
         if (response.code === 200) {
@@ -153,6 +173,10 @@ async function estimateRecharge() {
         showToast(error)
         showToast(error)
     }
     }
 }
 }
+onMounted(() => {
+    getCardInfo()
+    getRechargeList()
+})
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
@@ -261,32 +285,57 @@ async function estimateRecharge() {
 .transaction {
 .transaction {
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
-    justify-content: space-between;
-    padding: 8px 0;
+    padding: 12px 0;
     border-bottom: 1px solid var(--border, #333);
     border-bottom: 1px solid var(--border, #333);
     font-size: 16px;
     font-size: 16px;
 }
 }
 .transaction:last-child {
 .transaction:last-child {
     border-bottom: none;
     border-bottom: none;
 }
 }
+.trans-icon {
+    width: 40px;
+    height: 40px;
+    border-radius: 6px;
+    background-color: var(--border);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 12px;
+}
+.trans-icon-inner {
+    width: 24px;
+    height: 24px;
+    background-color: #232323;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+.trans-icon-inner i {
+    color: var(--main-yellow);
+    font-size: 16px;
+}
 .trans-left {
 .trans-left {
-    width: 120px;
+    flex: 1;
+    min-width: 0;
 }
 }
 .trans-right {
 .trans-right {
-    width: 160px;
-    div {
-        text-align: left;
-    }
+    text-align: right;
+    margin-left: 12px;
 }
 }
 .trans-type {
 .trans-type {
     color: #fff;
     color: #fff;
     font-size: 15px;
     font-size: 15px;
-    line-height: 2;
+    margin-bottom: 4px;
+}
+.trans-desc {
+    color: #bdbdbd;
+    font-size: 13px;
 }
 }
 .trans-amount {
 .trans-amount {
     font-size: 16px;
     font-size: 16px;
     color: var(--main-yellow);
     color: var(--main-yellow);
-    line-height: 2;
+    margin-bottom: 4px;
 }
 }
 .trans-date {
 .trans-date {
     color: #bdbdbd;
     color: #bdbdbd;

+ 16 - 7
src/views/card-transaction-detail.vue

@@ -10,27 +10,27 @@
         <div class="detail-card">
         <div class="detail-card">
             <div class="detail-row">
             <div class="detail-row">
                 <span class="label">{{ t('card-transaction-detail.currency') }}</span>
                 <span class="label">{{ t('card-transaction-detail.currency') }}</span>
-                <span class="value">{{ currency }}</span>
+                <span class="value">{{ transactionInfo?.currency }}</span>
             </div>
             </div>
             <div class="detail-row">
             <div class="detail-row">
                 <span class="label">{{ t('card-transaction-detail.amount') }}</span>
                 <span class="label">{{ t('card-transaction-detail.amount') }}</span>
-                <span class="value">{{ amount }}</span>
+                <span class="value">{{ transactionInfo?.amount }}</span>
             </div>
             </div>
             <div class="detail-row">
             <div class="detail-row">
                 <span class="label">{{ t('card-transaction-detail.type') }}</span>
                 <span class="label">{{ t('card-transaction-detail.type') }}</span>
-                <span class="value">{{ type }}</span>
+                <span class="value">{{ transactionInfo?.tradeTypeStr }}</span>
             </div>
             </div>
             <div class="detail-row">
             <div class="detail-row">
                 <span class="label">{{ t('card-transaction-detail.desc') }}</span>
                 <span class="label">{{ t('card-transaction-detail.desc') }}</span>
-                <span class="value">{{ remark }}</span>
+                <span class="value">{{ transactionInfo?.remark }}</span>
             </div>
             </div>
             <div class="detail-row">
             <div class="detail-row">
                 <span class="label">{{ t('card-transaction-detail.status') }}</span>
                 <span class="label">{{ t('card-transaction-detail.status') }}</span>
-                <span class="value success">{{ tradeStatusStr }}</span>
+                <span class="value success">{{ transactionInfo?.tradeStatusStr }}</span>
             </div>
             </div>
             <div class="detail-row">
             <div class="detail-row">
                 <span class="label">{{ t('card-transaction-detail.time') }}</span>
                 <span class="label">{{ t('card-transaction-detail.time') }}</span>
-                <span class="value">{{ date }}</span>
+                <span class="value">{{ transactionInfo?.businessDate }}</span>
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
@@ -39,9 +39,18 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
+import { ucardApi } from '@/api/ucard'
 const { t } = useI18n()
 const { t } = useI18n()
 const route = useRoute()
 const route = useRoute()
-const { id, cardNo, tradeStatusStr, amount, currency, type, remark, date } = route.query
+const { id } = route.query as { id: string }
+const transactionInfo = ref<TransactionInfo | null>(null)
+const getTransactionInfo = async () => {
+    const res = await ucardApi.cardTransacSingle({ id })
+    transactionInfo.value = res.data
+}
+onMounted(() => {
+    getTransactionInfo()
+})
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 389 - 96
src/views/cards.vue

@@ -9,7 +9,7 @@
                     :style="{ transform: `translateX(${(index - currentIndex) * 100 + offsetX}%)` }"
                     :style="{ transform: `translateX(${(index - currentIndex) * 100 + offsetX}%)` }"
                 >
                 >
                     <div
                     <div
-                        v-if="card.id !== 'apply'"
+                        v-if="card.id !== 'apply' && card.id !== 'virtual'"
                         class="card-info"
                         class="card-info"
                         @click.stop="(e: MouseEvent) => toggleCardNo(card.id, e)"
                         @click.stop="(e: MouseEvent) => toggleCardNo(card.id, e)"
                         :class="{ flipping: isFlipping[card.id] }"
                         :class="{ flipping: isFlipping[card.id] }"
@@ -44,6 +44,7 @@
                                 VALID THRU {{ card.expire }}
                                 VALID THRU {{ card.expire }}
                             </div>
                             </div>
                         </div>
                         </div>
+                        <img v-if="card.status != 1" src="https://upload.wikimedia.org/wikipedia/commons/a/a4/Flag_of_the_United_States.svg" class="flags" @click.stop="ucardActivate(card.id)" />
                     </div>
                     </div>
                     <div class="card-info" v-else>
                     <div class="card-info" v-else>
                         <div class="apply-card-img">
                         <div class="apply-card-img">
@@ -64,33 +65,78 @@
                     </div>
                     </div>
                 </div>
                 </div>
             </div>
             </div>
+            <div class="swiper-indicators">
+                <div
+                    v-for="(card, index) in cardList"
+                    :key="card.id"
+                    class="indicator-dot"
+                    :class="{ active: index === currentIndex }"
+                    @click="currentIndex = index"
+                ></div>
+            </div>
         </div>
         </div>
-
-        <template v-if="cardList[currentIndex]?.id !== 'apply'">
+        <div v-if="currentCard.id === 'apply'">
+            <ApplyCard />
+        </div>
+        <div v-else-if="currentCard.id === 'virtual'">
+            <VirtualCard />
+        </div>
+        <template v-else>
             <div class="actions">
             <div class="actions">
                 <button class="action-btn" @click="goToCardRecharge">
                 <button class="action-btn" @click="goToCardRecharge">
                     <i class="i-mdi-credit-card-plus" />
                     <i class="i-mdi-credit-card-plus" />
-                    <span>卡片充值</span>
+                    <span>{{ t('cards.recharge') }}</span>
                 </button>
                 </button>
                 <button class="action-btn" @click="goToFindPassword">
                 <button class="action-btn" @click="goToFindPassword">
                     <i class="i-mdi-lock-reset" />
                     <i class="i-mdi-lock-reset" />
-                    <span>找回密码</span>
+                    <span>{{ t('cards.findPassword') }}</span>
                 </button>
                 </button>
-                <button class="action-btn" @click="goToFreezeCard">
+                <button class="action-btn" @click="goToFreezeCard" v-if="currentCard.freezeStatus === 1">
                     <i class="i-mdi-credit-card-off" />
                     <i class="i-mdi-credit-card-off" />
-                    <span>冻结卡片</span>
+                    <span>{{ t('cards.freezeCard') }}</span>
+                </button>
+                <button class="action-btn" @click="goToFreezeCard" v-if="currentCard.freezeStatus === 2">
+                    <i class="i-mdi-credit-card-check" />
+                    <span>{{ t('cards.unfreezeCard') }}</span>
                 </button>
                 </button>
             </div>
             </div>
-            <div class="balance-wrap">
+
+            <div class="balance-wrap" v-if="balance.length > 0">
+                <div class="balance-content">{{ t('cards.currency') }}</div>
+                <div class="balance-title">{{ t('cards.balance') }}</div>
+            </div>
+            <div class="balance-wrap" v-for="item in balance" :key="item.currency">
                 <div class="currency">
                 <div class="currency">
                     <img src="https://upload.wikimedia.org/wikipedia/commons/a/a4/Flag_of_the_United_States.svg" class="flag" />
                     <img src="https://upload.wikimedia.org/wikipedia/commons/a/a4/Flag_of_the_United_States.svg" class="flag" />
-                    <span>USD</span>
+                    <span>{{ item.currency }}</span>
                 </div>
                 </div>
-                <div class="balance">{{ balance.amount }}</div>
+                <div class="balance">{{ item.amount }}</div>
+            </div>
+            <div class="trans-header">
+                <div class="trans-title">{{ t('cards.transactions') }}</div>
+                <i class="i-mdi-calendar-month-outline" @click="showDatePicker = true" />
+                <van-calendar
+                    v-model:show="showDatePicker"
+                    :min-date="minDate"
+                    :title="t('cards.selectDateRange')"
+                    :subtitle="t('cards.selectDate')"
+                    :max-date="maxDate"
+                    :show-mark="false"
+                    color="var(--main-yellow)"
+                    type="range"
+                    :show-confirm="false"
+                    @cancel="showDatePicker = false"
+                    :formatter="formatter"
+                    @confirm="onConfirmStart"
+                />
             </div>
             </div>
-            <div class="transactions">
-                <div class="trans-title">交易记录</div>
-                <div v-for="t in transactions" :key="t.date + t.amount + t.description" class="transaction" @click="goToTransactionDetail(t)">
+            <div class="transactions" v-if="transactions.length > 0">
+                <div v-for="t in transactions" :key="t.id" class="transaction" @click="goToTransactionDetail(t)">
+                    <div class="trans-icon">
+                        <div class="trans-icon-inner">
+                            <i class="i-mdi-cart-outline"></i>
+                        </div>
+                    </div>
                     <div class="trans-left">
                     <div class="trans-left">
                         <div class="trans-type">{{ t.tradeTypeStr }}</div>
                         <div class="trans-type">{{ t.tradeTypeStr }}</div>
                         <div class="trans-desc">{{ t.remark }}</div>
                         <div class="trans-desc">{{ t.remark }}</div>
@@ -101,41 +147,125 @@
                     </div>
                     </div>
                 </div>
                 </div>
             </div>
             </div>
+            <EmptyState v-else icon="i-mdi-receipt-text-outline" text="暂无交易记录" />
         </template>
         </template>
-        <div v-else>
-            <ApplyCard />
-        </div>
     </div>
     </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import ApplyCard from '@/components/ApplyCard.vue'
 import ApplyCard from '@/components/ApplyCard.vue'
-const balance = {
-    currency: 'USD',
-    amount: 340.05,
-}
-const transactions = ref<TransactionInfo[]>([])
-import { ref, watch } from 'vue'
+import VirtualCard from '@/components/VirtualCard.vue'
+import EmptyState from '@/components/EmptyState.vue'
+import { useI18n } from 'vue-i18n'
 import { ucardApi } from '@/api/ucard'
 import { ucardApi } from '@/api/ucard'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
 import type { CardInfo, TransactionInfo } from '@/api/ucard'
 import type { CardInfo, TransactionInfo } from '@/api/ucard'
+import { showToast } from 'vant'
+import dayjs from 'dayjs'
 const router = useRouter()
 const router = useRouter()
+const { t } = useI18n()
+const currentCard = ref<CardInfo | null>({})
+const balance = ref<{ balance: number; currency: string }[]>([])
+
+const globalStore = useGlobalStore()
+const transactions = ref<TransactionInfo[]>([])
 const cardList = ref<CardInfo[]>([])
 const cardList = ref<CardInfo[]>([])
 const showCardNo = ref<{ [key: string]: boolean }>({})
 const showCardNo = ref<{ [key: string]: boolean }>({})
 const isFlipping = ref<{ [key: string]: boolean }>({})
 const isFlipping = ref<{ [key: string]: boolean }>({})
-const currentIndex = ref(6)
+const currentIndex = ref(0)
 const startX = ref(0)
 const startX = ref(0)
 const offsetX = ref(0)
 const offsetX = ref(0)
 const isDragging = ref(false)
 const isDragging = ref(false)
-// 获取卡片列表
-const getCardList = async () => {
-    const res = await ucardApi.cardList({
-        page: {
-            current: 1,
-            row: 10,
+const showDatePicker = ref(false)
+const dateRange = ref<[string, string] | undefined>(undefined)
+dateRange.value = ['', '']
+const minDate = new Date(new Date().getFullYear() - 10, 0, 1)
+const maxDate = new Date(new Date().getFullYear() + 10, 0, 1)
+const onConfirmStart = (value: [string, string]) => {
+    dateRange.value = ['', '']
+    if (value && value.length === 2) {
+        dateRange.value = value
+    }
+    showDatePicker.value = false
+    handleDateRangeChange()
+}
+
+const formatter = (day: any) => {
+    if (day.type === 'start') {
+        day.bottomInfo = t('cards.start')
+    } else if (day.type === 'end') {
+        day.bottomInfo = t('cards.end')
+    } else {
+        day.bottomInfo = ''
+    }
+    return day
+}
+
+const ucardActivate = async (id: string) => {
+    router.push({
+        path: '/card/activation',
+        query: {
+            id
         },
         },
     })
     })
-    // 手动映射为CardInfo类型
+}
+
+// 获取交易记录
+const getTransactions = async () => {
+    try {
+        transactions.value = []
+        if (!currentCard.value?.cardNo) return
+        const res = await ucardApi.transactionsList({
+            beginDate: dateRange.value?.[0] ? dayjs(dateRange.value?.[0]).format('YYYY-MM-DD') : undefined,
+            endDate: dateRange.value?.[1] ? dayjs(dateRange.value?.[1]).format('YYYY-MM-DD') : undefined,
+            cardNo: currentCard.value?.cardNo,
+            page: {
+                current: 1,
+                row: 10,
+            },
+        })
+        transactions.value = res.data && Array.isArray(res.data) ? res.data : []
+    } catch (error: any) {
+        showToast(error?.message || String(error))
+        transactions.value = []
+    }
+}
+
+const getBalance = async () => {
+    globalStore.setFullScreenLoading(true)
+    try {
+        balance.value = []
+        if (!currentCard.value?.cardNo) return
+        const res = await ucardApi.ucardBalance({
+            cardNo: currentCard.value?.cardNo,
+            uniqueId: currentCard.value?.uniqueId,
+        })
+        if(res.code == 200){
+            balance.value = res.data
+        }else {
+            balance.value = []
+        }
+
+        console.log(balance.value,res,12);
+
+    } catch (error: any) {
+        showToast(error?.message || String(error))
+        balance.value = []
+    } finally {
+        globalStore.setFullScreenLoading(false)
+    }
+}
+
+const handleDateRangeChange = () => {
+    if (currentCard.value) {
+        getTransactions()
+    }
+}
+
+// 获取卡片列表
+const getCardList = async () => {
+    globalStore.setFullScreenLoading(true)
+    const res = await ucardApi.cardList({ page: { current: 1, row: 10 } })
     cardList.value =
     cardList.value =
         res.data && Array.isArray(res.data)
         res.data && Array.isArray(res.data)
             ? res.data.map((item: any) => ({
             ? res.data.map((item: any) => ({
@@ -148,74 +278,81 @@ const getCardList = async () => {
                   ...item,
                   ...item,
               }))
               }))
             : []
             : []
+
+            if(res.data.length == 0){
+                globalStore.setFullScreenLoading(false)
+            }
     cardList.value.forEach((card) => {
     cardList.value.forEach((card) => {
         showCardNo.value[card.id] = false
         showCardNo.value[card.id] = false
         isFlipping.value[card.id] = false
         isFlipping.value[card.id] = false
     })
     })
     // 添加"申请开卡"虚拟卡片
     // 添加"申请开卡"虚拟卡片
     cardList.value.push({
     cardList.value.push({
-        id: 'apply',
+        id: 'virtual',
         firstName: '',
         firstName: '',
         lastName: '',
         lastName: '',
         cardNo: '',
         cardNo: '',
         expire: '',
         expire: '',
         uniqueId: '',
         uniqueId: '',
     })
     })
-    if (cardList.value.length > 1) {
-        getTransactions(cardList.value[currentIndex.value].cardNo)
-    }
-}
-// 获取交易记录
-const getTransactions = async (cardNo: string) => {
-    try {
-        transactions.value = []
-        if (!cardNo) return
-        const res = await ucardApi.transactionsList({
-            cardNo,
-            page: {
-                current: 1,
-                row: 10,
-            },
-        })
-        transactions.value = res.data && Array.isArray(res.data) ? res.data : []
-    } catch (error: any) {
-        showToast(error?.message || String(error))
-        transactions.value = []
+    // cardList.value.push({
+    //     id: 'apply',
+    //     firstName: '',
+    //     lastName: '',
+    //     cardNo: '',
+    //     expire: '',
+    //     uniqueId: '',
+    // })
+
+    if (cardList.value.length > 0) {
+        currentCard.value = cardList.value[currentIndex.value]
+        getTransactions()
+        getBalance()
     }
     }
 }
 }
-watch(currentIndex, (newIndex) => {
-    if (cardList.value[newIndex]) {
-        getTransactions(cardList.value[newIndex].cardNo)
-    }
-})
+
+watch(
+    currentIndex,
+    (newIndex) => {
+        if (cardList.value[newIndex]) {
+            currentCard.value = cardList.value[newIndex]
+            getTransactions()
+            getBalance()
+        }
+    },
+    { immediate: true },
+)
+
+getCardList()
+
 const toggleCardNo = (cardId: string, event: MouseEvent) => {
 const toggleCardNo = (cardId: string, event: MouseEvent) => {
     event.stopPropagation()
     event.stopPropagation()
     isFlipping.value[cardId] = !isFlipping.value[cardId]
     isFlipping.value[cardId] = !isFlipping.value[cardId]
     showCardNo.value[cardId] = !showCardNo.value[cardId]
     showCardNo.value[cardId] = !showCardNo.value[cardId]
 }
 }
-getCardList()
+
 const handleTouchStart = (e: TouchEvent) => {
 const handleTouchStart = (e: TouchEvent) => {
     startX.value = e.touches[0].clientX
     startX.value = e.touches[0].clientX
     isDragging.value = true
     isDragging.value = true
 }
 }
+
 const handleTouchMove = (e: TouchEvent) => {
 const handleTouchMove = (e: TouchEvent) => {
     if (!isDragging.value) return
     if (!isDragging.value) return
     const currentX = e.touches[0].clientX
     const currentX = e.touches[0].clientX
     const diff = currentX - startX.value
     const diff = currentX - startX.value
     const containerWidth = document.querySelector('.swiper-container')?.clientWidth || 0
     const containerWidth = document.querySelector('.swiper-container')?.clientWidth || 0
-    // 添加弹性效果
     if (currentIndex.value === 0 && diff > 0) {
     if (currentIndex.value === 0 && diff > 0) {
-        offsetX.value = (diff / containerWidth) * 30 // 减小边界阻力
+        offsetX.value = (diff / containerWidth) * 30
     } else if (currentIndex.value === cardList.value.length - 1 && diff < 0) {
     } else if (currentIndex.value === cardList.value.length - 1 && diff < 0) {
-        offsetX.value = (diff / containerWidth) * 30 // 减小边界阻力
+        offsetX.value = (diff / containerWidth) * 30
     } else {
     } else {
         offsetX.value = (diff / containerWidth) * 100
         offsetX.value = (diff / containerWidth) * 100
     }
     }
 }
 }
+
 const handleTouchEnd = () => {
 const handleTouchEnd = () => {
     isDragging.value = false
     isDragging.value = false
     if (Math.abs(offsetX.value) > 0.2) {
     if (Math.abs(offsetX.value) > 0.2) {
-        // 降低切换阈值
         if (offsetX.value > 0 && currentIndex.value > 0) {
         if (offsetX.value > 0 && currentIndex.value > 0) {
             currentIndex.value--
             currentIndex.value--
         } else if (offsetX.value < 0 && currentIndex.value < cardList.value.length - 1) {
         } else if (offsetX.value < 0 && currentIndex.value < cardList.value.length - 1) {
@@ -227,51 +364,69 @@ const handleTouchEnd = () => {
     }
     }
     offsetX.value = 0
     offsetX.value = 0
 }
 }
+
 function goToCardRecharge() {
 function goToCardRecharge() {
-    const currentCard = cardList.value[currentIndex.value]
+    if(currentCard.value?.status != 1){
+        showToast('请先激活银行卡')
+        return
+    }
     router.push({
     router.push({
         path: '/card/recharge',
         path: '/card/recharge',
         query: {
         query: {
-            cardId: currentCard.id,
-            uniqueId: currentCard.uniqueId,
-            cardNo: currentCard.cardNo,
+            id: currentCard.value?.id,
         },
         },
     })
     })
 }
 }
+
 function goToFindPassword() {
 function goToFindPassword() {
-    const currentCard = cardList.value[currentIndex.value]
+    if(currentCard.value?.status != 1){
+        showToast('请先激活银行卡')
+        return
+    }
     router.push({
     router.push({
         path: '/find/password',
         path: '/find/password',
         query: {
         query: {
-            cardId: currentCard.id,
-            uniqueId: currentCard.uniqueId,
-            cardNo: currentCard.cardNo,
+            id: currentCard.value?.id,
         },
         },
     })
     })
 }
 }
-function goToFreezeCard() {
-    const currentCard = cardList.value[currentIndex.value]
-    router.push({
-        path: '/freeze/card',
-        query: {
-            cardId: currentCard.id,
-            uniqueId: currentCard.uniqueId,
-            cardNo: currentCard.cardNo,
-        },
-    })
+// 冻结卡片/解冻卡片
+async function goToFreezeCard() {
+    if(currentCard.value?.status != 1){
+        showToast('请先激活银行卡')
+        return
+    }
+    try {
+        switch (currentCard.value?.freezeStatus) {
+            case 1:
+                let res = await ucardApi.ucardFreeze({ cardNo: currentCard.value?.cardNo, uniqueId: currentCard.value?.uniqueId })
+                if (res.code === 200) {
+                    showToast('冻结成功')
+                    getCardList()
+                } else {
+                    showToast(res.msg)
+                }
+                break
+            case 2:
+                let res1 = await ucardApi.ucardUnfreeze({ cardNo: currentCard.value?.cardNo, uniqueId: currentCard.value?.uniqueId })
+                if (res1.code === 200) {
+                    showToast('解冻成功')
+                    getCardList()
+                } else {
+                    showToast(res1.msg)
+                }
+                break
+        }
+    } catch (error) {
+        showToast(error || String(error))
+    }
 }
 }
+
 function goToTransactionDetail(transaction: TransactionInfo) {
 function goToTransactionDetail(transaction: TransactionInfo) {
     router.push({
     router.push({
         path: '/card/transaction/detail',
         path: '/card/transaction/detail',
         query: {
         query: {
             id: transaction.id,
             id: transaction.id,
-            cardNo: cardList.value[currentIndex.value].cardNo,
-            amount: transaction.amount,
-            currency: transaction.currencyTxn,
-            type: transaction.tradeTypeStr,
-            remark: transaction.remark,
-            date: transaction.businessDate,
-            tradeStatusStr: transaction.tradeStatusStr,
         },
         },
     })
     })
 }
 }
@@ -282,7 +437,7 @@ function goToTransactionDetail(transaction: TransactionInfo) {
     position: absolute;
     position: absolute;
     width: 100%;
     width: 100%;
     height: 100%;
     height: 100%;
-    transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); // 优化过渡动画
+    transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
     will-change: transform;
     will-change: transform;
 }
 }
 
 
@@ -429,10 +584,21 @@ function goToTransactionDetail(transaction: TransactionInfo) {
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
     justify-content: space-between;
     justify-content: space-between;
-    margin-bottom: 20px;
+    margin: 30px 0;
     font-size: var(--font-size-14);
     font-size: var(--font-size-14);
 }
 }
 
 
+.balance-content {
+    font-size: var(--font-size-20);
+    color: var(--white);
+    font-weight: bold;
+}
+
+.balance-title {
+    font-size: var(--font-size-12);
+    color: var(--white);
+}
+
 .currency {
 .currency {
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
@@ -449,21 +615,118 @@ function goToTransactionDetail(transaction: TransactionInfo) {
 }
 }
 
 
 .balance {
 .balance {
-    font-size: var(--font-size-16);
+    font-size: var(--font-size-14);
     font-weight: bold;
     font-weight: bold;
-    color: var(--white);
+    color: var(--main-yellow);
 }
 }
 
 
 .transactions {
 .transactions {
-    background: var(--action-bg);
     border-radius: 16px;
     border-radius: 16px;
     margin-bottom: 16px;
     margin-bottom: 16px;
-    padding: 16px 12px;
+    padding: 16px 0;
+}
+.trans-icon {
+    width: 40px;
+    height: 40px;
+    display: flex;
+    background: var(--main-bg);
+    box-shadow: 0 4px 12px rgba(214, 255, 0, 0.1);
+    border-radius: 8px;
+    margin-right: 12px;
+    align-items: center;
+    justify-content: center;
+    .trans-icon-inner {
+        width: 22px;
+        height: 22px;
+        background: var(--white);
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+    }
+    i {
+        color: #000;
+        width: 18px;
+        height: 18px;
+        font-size: var(--font-size-16);
+        border-radius: 50%;
+    }
+}
+
+.trans-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 10px;
+    color: var(--white);
+    i {
+        font-size: var(--font-size-20);
+        cursor: pointer;
+    }
+}
+::v-deep .van-calendar {
+    background: var(--action-bg);
+}
+::v-deep .van-calendar__month-mark {
+    display: none;
+}
+::v-deep .van-calendar__header-subtitle {
+    color: var(--white);
+}
+::v-deep .van-calendar__header-title {
+    color: var(--white);
+}
+::v-deep .van-calendar__month-title {
+    color: var(--main-yellow);
 }
 }
 
 
 .trans-title {
 .trans-title {
     font-size: var(--font-size-16);
     font-size: var(--font-size-16);
     margin-bottom: 10px;
     margin-bottom: 10px;
+}
+
+.date-field {
+    width: 200px;
+
+    :deep(.van-field__control) {
+        color: var(--white);
+    }
+
+    :deep(.van-field__placeholder) {
+        color: var(--gray);
+    }
+}
+
+:deep(.van-popup) {
+    background: var(--action-bg);
+}
+
+:deep(.van-picker__toolbar) {
+    background: var(--action-bg);
+    border-bottom: 1px solid var(--border);
+}
+
+:deep(.van-picker__title) {
+    color: var(--white);
+}
+
+:deep(.van-picker__confirm) {
+    color: var(--main-yellow);
+}
+
+:deep(.van-picker__cancel) {
+    color: var(--gray);
+}
+
+:deep(.van-picker-column) {
+    color: var(--white);
+}
+
+:deep(.van-picker-column__item) {
+    color: var(--white);
+}
+
+:deep(.van-picker-column__item--selected) {
     color: var(--main-yellow);
     color: var(--main-yellow);
 }
 }
 
 
@@ -471,8 +734,8 @@ function goToTransactionDetail(transaction: TransactionInfo) {
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
     justify-content: space-between;
     justify-content: space-between;
-    padding: 8px 0;
-    border-bottom: 1px solid var(--border);
+    padding: 10px 0;
+    /* border-bottom: 1px solid var(--border); */
     font-size: var(--font-size-16);
     font-size: var(--font-size-16);
 }
 }
 
 
@@ -488,7 +751,7 @@ function goToTransactionDetail(transaction: TransactionInfo) {
     width: 100px;
     width: 100px;
 
 
     div {
     div {
-        text-align: left;
+        text-align: right;
     }
     }
 }
 }
 
 
@@ -500,7 +763,7 @@ function goToTransactionDetail(transaction: TransactionInfo) {
 
 
 .trans-desc {
 .trans-desc {
     font-size: var(--font-size-13);
     font-size: var(--font-size-13);
-    color: var(--white);
+    color: var(--gray);
 }
 }
 
 
 .trans-amount {
 .trans-amount {
@@ -518,6 +781,7 @@ function goToTransactionDetail(transaction: TransactionInfo) {
     position: relative;
     position: relative;
     overflow: hidden;
     overflow: hidden;
     margin-bottom: 20px;
     margin-bottom: 20px;
+    padding-bottom: 5px;
 }
 }
 
 
 .swiper-container {
 .swiper-container {
@@ -584,4 +848,33 @@ function goToTransactionDetail(transaction: TransactionInfo) {
         transform: scale(1.2);
         transform: scale(1.2);
     }
     }
 }
 }
+
+.swiper-indicators {
+    display: flex;
+    justify-content: center;
+    margin-top: 16px;
+    gap: 8px;
+}
+
+.indicator-dot {
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    background: var(--action-bg);
+    cursor: pointer;
+    transition: all 0.3s ease;
+
+    &.active {
+        background: var(--main-yellow);
+        transform: scale(1.2);
+    }
+}
+.flags {
+    width: 20px;
+    height: 20px;
+    cursor: pointer;
+    position: absolute;
+    top: 10px;
+    right: 10px;
+}
 </style>
 </style>

+ 17 - 6
src/views/change-pay-password.vue

@@ -3,8 +3,10 @@
         <div class="form-box">
         <div class="form-box">
             <div class="desc">{{ t('change-pay-password.desc') }}</div>
             <div class="desc">{{ t('change-pay-password.desc') }}</div>
             <div class="input-row">
             <div class="input-row">
-                <input class="input" v-model="code" :placeholder="t('change-pay-password.input')" />
-                <button class="code-btn" :disabled="codeBtnDisabled" @click="getCode">{{ t('change-pay-password.getCode') }}</button>
+                <input class="input" type="number" v-model="code" :placeholder="t('change-pay-password.input')" />
+                <button class="code-btn" :disabled="codeBtnDisabled" @click="getCode">
+                    {{ codeBtnDisabled ? `${countdown}s` : t('change-pay-password.getCode') }}
+                </button>
             </div>
             </div>
             <button class="next-btn" @click="nextStep">{{ t('change-pay-password.nextStep') }}</button>
             <button class="next-btn" @click="nextStep">{{ t('change-pay-password.nextStep') }}</button>
         </div>
         </div>
@@ -12,17 +14,25 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref } from 'vue'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 const { t } = useI18n()
 const { t } = useI18n()
 const code = ref('')
 const code = ref('')
 const codeBtnDisabled = ref(false)
 const codeBtnDisabled = ref(false)
+const countdown = ref(60)
+
 function getCode() {
 function getCode() {
     codeBtnDisabled.value = true
     codeBtnDisabled.value = true
-    setTimeout(() => {
-        codeBtnDisabled.value = false
-    }, 60000)
+    countdown.value = 60
+
+    const timer = setInterval(() => {
+        countdown.value--
+        if (countdown.value <= 0) {
+            clearInterval(timer)
+            codeBtnDisabled.value = false
+        }
+    }, 1000)
 }
 }
+
 function nextStep() {
 function nextStep() {
     // 下一步逻辑
     // 下一步逻辑
 }
 }
@@ -73,6 +83,7 @@ function nextStep() {
                 color: #fff;
                 color: #fff;
             }
             }
             .code-btn {
             .code-btn {
+                width: 200px;
                 background: var(--main-yellow);
                 background: var(--main-yellow);
                 color: #232323;
                 color: #232323;
                 border: none;
                 border: none;

+ 1176 - 0
src/views/eur-remit.vue

@@ -0,0 +1,1176 @@
+<template>
+    <div class="page remit-page">
+        <van-form ref="paymentForm" :model="paymentForm" class="payment-form" v-if="targetBankOptions.length > 0 && countryOptions.length > 0">
+            <div class="bank-summary">{{ bankSummary }}</div>
+            <!-- 第一步:基本信息 -->
+            <div v-show="currentStep === 1" class="form-section">
+                <h3 class="section-title">{{ t('eur-remit.item6') }}</h3>
+
+                <remit-input
+                    v-model:value="paymentForm.bankId"
+                    type="select"
+                    fkey="bankId"
+                    :label="t('eur-remit.item7')"
+                    :required="true"
+                    :rules="rules['bankId']"
+                    :columns="targetBankOptions"
+                    :show-search="true"
+                    @change="handleBankChange"
+                    :loading="loadingStates.validateStep1"
+                />
+                <remit-input
+                    v-model:value="paymentForm.amount"
+                    type="number"
+                    fkey="amount"
+                    :label="t('eur-remit.item9')"
+                    @change="handleBankChange"
+                    :required="true"
+                    :rules="rules['amount']"
+                />
+                <remit-input
+                    v-model:value="paymentForm.postscript"
+                    type="text"
+                    fkey="postscript"
+                    :label="t('eur-remit.item10')"
+                    :required="true"
+                    :rules="rules['postscript']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.relationship"
+                    type="select"
+                    fkey="relationship"
+                    :label="t('eur-remit.item11')"
+                    :columns="relationshipOptions"
+                    :loading="loadingStates.validateStep1"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.sourceFunds"
+                    type="select"
+                    fkey="sourceFunds"
+                    :label="t('eur-remit.item33')"
+                    :columns="sourceFundsOptions"
+                    :loading="loadingStates.validateStep1"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payPurpose"
+                    type="select"
+                    fkey="payPurpose"
+                    :label="t('eur-remit.item41')"
+                    :columns="payPurposeOptions"
+                    :loading="loadingStates.validateStep1"
+                    @change="handleBankChange"
+                />
+            </div>
+            <!-- 第二步:付款人信息 -->
+            <div v-show="currentStep === 2" class="form-section">
+                <h3 class="section-title">{{ t('eur-remit.item50') }}</h3>
+                <remit-input
+                    v-model:value="paymentForm.payerType"
+                    type="select"
+                    fkey="payerType"
+                    :label="t('eur-remit.item51')"
+                    :columns="payerTypeOptions"
+                    :loading="loadingStates.validateStep2"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerLastName"
+                    type="text"
+                    fkey="payerLastName"
+                    :label="t('eur-remit.item53')"
+                    :required="true"
+                    :rules="rules['payerLastName']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerFirstName"
+                    type="text"
+                    fkey="payerFirstName"
+                    :label="t('eur-remit.item54')"
+                    :required="true"
+                    :rules="rules['payerFirstName']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerIdNo"
+                    type="text"
+                    fkey="payerIdNo"
+                    :label="t('eur-remit.item55')"
+                    :required="true"
+                    :rules="rules['payerIdNo']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerIdNoType"
+                    type="select"
+                    fkey="payerIdNoType"
+                    :label="t('eur-remit.item56')"
+                    :columns="idNoTypeOptions"
+                    :required="true"
+                    :rules="rules['payerIdNoType']"
+                    :loading="loadingStates.validateStep2"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerIdCountry"
+                    type="select"
+                    fkey="payerIdCountry"
+                    :label="t('eur-remit.item59')"
+                    :show-search="true"
+                    :columns="countryOptions"
+                    :required="true"
+                    :rules="rules['payerIdCountry']"
+                    :loading="loadingStates.validateStep2"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerBirthday"
+                    type="date"
+                    fkey="payerBirthday"
+                    :label="t('eur-remit.item61')"
+                    :required="true"
+                    :rules="rules['payerBirthday']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerNationalityCountry"
+                    type="select"
+                    fkey="payerNationalityCountry"
+                    :show-search="true"
+                    :label="t('eur-remit.item62')"
+                    :columns="countryOptions"
+                    :required="true"
+                    :rules="rules['payerNationalityCountry']"
+                    :loading="loadingStates.validateStep2"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerMobile"
+                    type="text"
+                    fkey="payerMobile"
+                    :label="t('eur-remit.item64')"
+                    :required="true"
+                    :rules="rules['payerMobile']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerCountryCode"
+                    type="select"
+                    fkey="payerCountryCode"
+                    :label="t('eur-remit.item65')"
+                    :show-search="true"
+                    :columns="countryOptions"
+                    :required="true"
+                    :rules="rules['payerCountryCode']"
+                    :loading="loadingStates.validateStep2"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerCityCode"
+                    type="select"
+                    fkey="payerCityCode"
+                    :label="t('eur-remit.item67')"
+                    :show-search="true"
+                    :columns="payerCityList"
+                    :required="true"
+                    :rules="rules['payerCityCode']"
+                    :loading="loadingStates.validateStep2"
+                    :disabled="!paymentForm.payerCountryCode"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerAddress"
+                    type="text"
+                    fkey="payerAddress"
+                    :label="t('eur-remit.item69')"
+                    :required="true"
+                    :rules="rules['payerAddress']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerPostCode"
+                    type="text"
+                    fkey="payerPostCode"
+                    :label="t('eur-remit.item70')"
+                    :required="true"
+                    :rules="rules['payerPostCode']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.payerOccupation"
+                    type="text"
+                    fkey="payerOccupation"
+                    :label="t('eur-remit.item71')"
+                    :required="true"
+                    :rules="rules['payerOccupation']"
+                    @change="handleBankChange"
+                />
+            </div>
+
+            <!-- 第三步:收款人信息 -->
+            <div v-show="currentStep === 3" class="form-section">
+                <h3 class="section-title">{{ t('eur-remit.item73') }}</h3>
+                <remit-input
+                    v-model:value="paymentForm.benAccountNum"
+                    type="text"
+                    fkey="benAccountNum"
+                    :label="t('eur-remit.item75')"
+                    :required="true"
+                    :rules="rules['benAccountNum']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benAccountName"
+                    type="text"
+                    fkey="benAccountName"
+                    :label="t('eur-remit.item76')"
+                    :required="true"
+                    :rules="rules['benAccountName']"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benCountryCode"
+                    type="select"
+                    fkey="benCountryCode"
+                    :label="t('eur-remit.item77')"
+                    :show-search="true"
+                    :columns="countryOptions"
+                    :loading="loadingStates.validateStep3"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benCityCode"
+                    type="select"
+                    fkey="benCityCode"
+                    :label="t('eur-remit.item79')"
+                    :show-search="true"
+                    :columns="payeeCityList"
+                    :loading="loadingStates.validateStep3"
+                    :disabled="!paymentForm.benCountryCode"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benAddress"
+                    type="text"
+                    fkey="benAddress"
+                    :label="t('eur-remit.item81')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benPostCode"
+                    type="text"
+                    fkey="benPostCode"
+                    :label="t('eur-remit.item82')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benLastName"
+                    type="text"
+                    fkey="benLastName"
+                    :label="t('eur-remit.item83')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benFirstName"
+                    type="text"
+                    fkey="benFirstName"
+                    :label="t('eur-remit.item84')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benBankCode"
+                    type="text"
+                    fkey="benBankCode"
+                    :label="t('eur-remit.item85')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benNationalityCountry"
+                    type="select"
+                    fkey="benNationalityCountry"
+                    :label="t('eur-remit.item86')"
+                    :show-search="true"
+                    :columns="countryOptions"
+                    :loading="loadingStates.validateStep3"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benIdNoType"
+                    type="select"
+                    fkey="benIdNoType"
+                    :label="t('eur-remit.item88')"
+                    :columns="idNoTypeOptions"
+                    :loading="loadingStates.validateStep3"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benIdNo"
+                    type="text"
+                    fkey="benIdNo"
+                    :label="t('eur-remit.item91')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benIdExpirationDate"
+                    type="date"
+                    fkey="benIdExpirationDate"
+                    :label="t('eur-remit.item92')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benBirthday"
+                    type="date"
+                    fkey="benBirthday"
+                    :label="t('eur-remit.item93')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benMobileCode"
+                    type="select"
+                    fkey="benMobileCode"
+                    :label="t('eur-remit.item94')"
+                    :show-search="true"
+                    :columns="mobileCodeOptions"
+                    :loading="loadingStates.validateStep3"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benMobile"
+                    type="text"
+                    fkey="benMobile"
+                    :label="t('eur-remit.item96')"
+                    @change="handleBankChange"
+                />
+                <remit-input
+                    v-model:value="paymentForm.benBankAccountType"
+                    type="select"
+                    fkey="benBankAccountType"
+                    :label="t('eur-remit.item97')"
+                    :columns="bankAccountTypeOptions"
+                    :loading="loadingStates.validateStep3"
+                    @change="handleBankChange"
+                />
+            </div>
+
+            <!-- 第四步:确认信息 -->
+            <div v-if="currentStep === 4" class="form-section">
+                <h3 class="section-title">{{ t('eur-remit.item100') }}</h3>
+                <div class="confirm-info">
+                    <div class="info-group">
+                        <h4>{{ t('eur-remit.item6') }}</h4>
+                        <div class="info-item">
+                            <span class="label">{{ t('eur-remit.item7') }}</span>
+                            <span class="value">{{ getBankName(formData.bankId) }}</span>
+                        </div>
+                        <div class="info-item">
+                            <span class="label">{{ t('eur-remit.item9') }}</span>
+                            <span class="value">{{ formData.amount }}</span>
+                        </div>
+                        <div class="info-item">
+                            <span class="label">{{ t('eur-remit.item10') }}</span>
+                            <span class="value">{{ formData.postscript }}</span>
+                        </div>
+                    </div>
+                    <div class="info-group">
+                        <h4>{{ t('eur-remit.item50') }}</h4>
+                        <div class="info-item">
+                            <span class="label">{{ t('eur-remit.item53') }}</span>
+                            <span class="value">{{ formData.payer.payerLastName }} {{ formData.payer.payerFirstName }}</span>
+                        </div>
+                        <div class="info-item">
+                            <span class="label">{{ t('eur-remit.item55') }}</span>
+                            <span class="value">{{ formData.payer.payerIdNo }}</span>
+                        </div>
+                    </div>
+                    <div class="info-group">
+                        <h4>{{ t('eur-remit.item73') }}</h4>
+                        <div class="info-item">
+                            <span class="label">{{ t('eur-remit.item75') }}</span>
+                            <span class="value">{{ formData.payee.benAccountNum }}</span>
+                        </div>
+                        <div class="info-item">
+                            <span class="label">{{ t('eur-remit.item76') }}</span>
+                            <span class="value">{{ formData.payee.benAccountName }}</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 步骤控制按钮 -->
+            <div class="form-actions">
+                <template v-if="currentStep === 1">
+                    <van-button type="warning" block @click="validateStep1" :loading="loadingStates.validateStep1">
+                        {{ t('eur-remit.next') }}
+                    </van-button>
+                </template>
+                <template v-else-if="currentStep === 2">
+                    <van-button plain block @click="currentStep = 1" class="prev-btn">
+                        {{ t('eur-remit.prev') }}
+                    </van-button>
+                    <van-button type="warning" block @click="validateStep2" :loading="loadingStates.validateStep2">
+                        {{ t('eur-remit.next') }}
+                    </van-button>
+                </template>
+                <template v-else-if="currentStep === 3">
+                    <van-button plain block @click="currentStep = 2" class="prev-btn">
+                        {{ t('eur-remit.prev') }}
+                    </van-button>
+                    <van-button type="warning" block @click="validateStep3" :loading="loadingStates.validateStep3">
+                        {{ t('eur-remit.next') }}
+                    </van-button>
+                </template>
+                <template v-else-if="currentStep === 4">
+                    <van-button plain block @click="currentStep = 3" class="prev-btn">
+                        {{ t('eur-remit.prev') }}
+                    </van-button>
+                    <van-button type="warning" block @click="validateStep4" :loading="loadingStates.validateStep4">
+                        {{ t('eur-remit.item104') }}
+                    </van-button>
+                </template>
+            </div>
+            <div class="form-actions">
+                <template v-if="currentStep === 4">
+                    <van-button type="warning" block @click="validateStep5" :loading="loadingStates.validateStep5" :disabled="!isAllValidated">
+                        {{ t('eur-remit.item105') }}
+                    </van-button>
+                </template>
+            </div>
+        </van-form>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+import { showToast } from 'vant'
+import { useRouter } from 'vue-router'
+import { ucardApi } from '@/api/ucard'
+import RemitInput from '@/components/RemitInput.vue'
+import { lang } from '@/composables/config'
+const { t } = useI18n()
+import useUserStore from '@/stores/use-user-store'
+import { ucardValidateParams } from '@/api/ucard'
+const userStore = useUserStore()
+const userInfo = userStore.userInfo?.customInfo
+const router = useRouter()
+
+// 当前步骤
+const currentStep = ref(1)
+
+// 加载状态
+const loadingStates = ref({
+    validateStep1: false,
+    validateStep2: false,
+    validateStep3: false,
+    validateStep4: false,
+    validateStep5: false,
+})
+const isAllValidated = ref(false)
+
+const formData = ref<ucardValidateParams>({uniqueId: userInfo?.uniqueId,} as ucardValidateParams)
+// 表单数据
+const paymentForm = ref({
+    bankId: 2671,
+    uniqueId: userInfo?.uniqueId,
+    originOrderNo: '',
+    amount: 200,
+    postscript: 'testorder',
+    relationship: 'OTHER',
+    sourceFunds: 'SAVINGS',
+    payPurpose: 'INTERNATIONAL_TRADE',
+    payerType: 'INDIVIDUAL',
+    payerLastName: 'zhang',
+    payerFirstName: 'tom',
+    payerIdNo: 'ES125664',
+    payerIdNoType: 'PASSPORT',
+    payerIdCountry: 'SG',
+    payerBirthday: '1990-10-10',
+    payerNationalityCountry: 'SG',
+    payerMobile: '+65012345678',
+    payerCountryCode: 'SG',
+    payerCityCode: 'SG_1',
+    payerAddress: '21 Tampines Ave 1, Singapore 529757',
+    payerPostCode: '999002',
+    payerOccupation: 'worker',
+    benAccountNum: '235486845630',
+    benAccountName: 'wangfei',
+    benCountryCode: 'SG',
+    benCityCode: 'SG_1',
+    benAddress: '21 Tampines Ave 1, Singapore 529757',
+    benPostCode: '999002',
+    benTransBankSwift: '',
+    benLastName: 'zhang',
+    benFirstName: 'alex',
+    benNationalityCountry: 'SG',
+    benIdNoType: 'PASSPORT',
+    benIdNo: '1334924322424',
+    benIdExpirationDate: '2030-01-01',
+    benBirthday: '2001-01-01',
+    benMobileCode: '+67',
+    benMobile: '18326559132',
+    benBankAccountType: 'SAVINGS',
+    benBankCode: '',
+})
+
+// 表单验证规则
+const rules = {
+    bankId: [{ required: true, message: t('eur-remit.ms3') }],
+    amount: [
+        { required: true, message: t('eur-remit.ms6') },
+        { pattern: /\b(?:[1-9]\d{2,}|100)(?:\.\d{1,2})?\b/g, message: t('eur-remit.ms') },
+    ],
+    postscript: [
+        { required: true, message: t('eur-remit.ms7') },
+        { max: 64, min: 5, message: t('eur-remit.ms8') },
+    ],
+    uniqueId: [{ required: true, message: t('eur-remit.ms4') }],
+    // 付款人信息验证规则
+    payerLastName: [
+        { required: true, message: t('eur-remit.ms9') },
+        { max: 50, min: 2, message: t('eur-remit.ms10') },
+    ],
+    payerFirstName: [
+        { required: true, message: t('eur-remit.ms10_1') },
+        { max: 50, min: 2, message: t('eur-remit.ms10') },
+    ],
+    payerIdNo: [
+        { required: true, message: t('eur-remit.ms11') },
+        { max: 18, min: 6, message: t('eur-remit.ms12') },
+    ],
+    payerIdNoType: [{ required: true, message: t('eur-remit.ms13') }],
+    payerIdCountry: [{ required: true, message: t('eur-remit.ms14') }],
+    payerBirthday: [{ required: true, message: t('eur-remit.ms16') }],
+    payerNationalityCountry: [{ required: true, message: t('eur-remit.ms17') }],
+    payerMobile: [
+        { required: true, message: t('eur-remit.ms18') },
+        { pattern: /^\+?\d{8,15}$/, message: t('eur-remit.ms19') },
+    ],
+    payerCountryCode: [{ required: true, message: t('eur-remit.ms20') }],
+    payerCityCode: [{ required: true, message: t('eur-remit.ms22') }],
+    payerAddress: [
+        { required: true, message: t('eur-remit.ms23') },
+        { max: 100, min: 10, message: t('eur-remit.ms24') },
+    ],
+    payerPostCode: [
+        { required: true, message: t('eur-remit.ms25') },
+        { max: 9, min: 3, message: t('eur-remit.ms26') },
+    ],
+    payerOccupation: [
+        { required: true, message: t('eur-remit.ms27') },
+        { max: 10, min: 3, message: t('eur-remit.ms28') },
+    ],
+
+    // 收款人信息验证规则
+    benAccountNum: [
+        { required: true, message: t('eur-remit.ms29') },
+        { max: 48, min: 2, message: t('eur-remit.ms30') },
+    ],
+    benAccountName: [
+        { required: true, message: t('eur-remit.ms31') },
+        { max: 100, min: 1, message: t('eur-remit.ms32') },
+    ],
+}
+// 关系
+const relationshipOptions = ref([
+    { text: t('eur-remit.item26'), value: 'SELF' },
+    { text: t('eur-remit.item21'), value: 'HUSBAND' },
+    { text: t('eur-remit.item31'), value: 'WIFE' },
+    { text: t('eur-remit.item16'), value: 'FATHER' },
+    { text: t('eur-remit.item22'), value: 'MOTHER' },
+    { text: t('eur-remit.item29'), value: 'SON' },
+    { text: t('eur-remit.item15'), value: 'DAUGHTER' },
+    { text: t('eur-remit.item27'), value: 'SISTER' },
+    { text: t('eur-remit.item12'), value: 'BROTHER' },
+    { text: t('eur-remit.item17'), value: 'FATHER_IN_LAW' },
+    { text: t('eur-remit.item23'), value: 'MOTHER_IN_LAW' },
+    { text: t('eur-remit.item28'), value: 'SISTER_IN_LAW' },
+    { text: t('eur-remit.item13'), value: 'BROTHER_IN_LAW' },
+    { text: t('eur-remit.item19'), value: 'GRAND_FATHER' },
+    { text: t('eur-remit.item20'), value: 'GRAND_MOTHER' },
+    { text: t('eur-remit.item24'), value: 'NEPHEW' },
+    { text: t('eur-remit.item25'), value: 'NIECE' },
+    { text: t('eur-remit.item30'), value: 'UNCLE' },
+    { text: t('eur-remit.item14'), value: 'COUSIN' },
+    { text: t('eur-remit.item18'), value: 'FRIEND' },
+    { text: t('eur-remit.item32'), value: 'OTHER' },
+])
+
+// 资金来源
+const sourceFundsOptions = ref([
+    { text: t('eur-remit.item34'), value: 'SAVINGS' },
+    { text: t('eur-remit.item35'), value: 'SALARY' },
+    { text: t('eur-remit.item36'), value: 'INVESTMENT' },
+    { text: t('eur-remit.item37'), value: 'OTHER' },
+])
+// 付款目的
+const payPurposeOptions = ref([
+    { text: t('eur-remit.item42'), value: 'PURCHASE_OF_GOODS_OR_SERVICES' },
+    { text: t('eur-remit.item43'), value: 'FREIGHT_AND_TRANSPORTATION_COSTS' },
+    { text: t('eur-remit.item44'), value: 'EDUCATION_EXPENSES' },
+    { text: t('eur-remit.item45'), value: 'IMMIGRATION_INVESTMENT' },
+    { text: t('eur-remit.item46'), value: 'CHARITABLE_DONATIONS' },
+    { text: t('eur-remit.item47'), value: 'FAMILY_SUPPORT' },
+    { text: t('eur-remit.item48'), value: 'DIVIDEND_OR_INTEREST_PAYMENTS' },
+    { text: t('eur-remit.item49'), value: 'INTERNATIONAL_TRADE' },
+])
+
+// 付款人类型
+const payerTypeOptions = ref([{ text: t('eur-remit.item52'), value: 'INDIVIDUAL' }])
+
+// 证件类型
+const idNoTypeOptions = ref([
+    { text: t('eur-remit.item57'), value: 'PASSPORT' },
+    { text: t('eur-remit.item58'), value: 'EUROPEAN_ID' },
+])
+
+// 手机区号
+const mobileCodeOptions = ref([
+    { text: t('eur-remit.item95'), value: '+65' },
+    { text: t('eur-remit.item95_1'), value: '+86' },
+    { text: t('eur-remit.item95_2'), value: '+1' },
+])
+
+const getMobileCode = async () => {
+    try {
+        const res = await ucardApi.countryGet()
+        mobileCodeOptions.value = res.data.map((item: { callingCode: string; name: string; enName: string }) => ({
+            text: '+' + item.callingCode + ' ' + (lang.value === 'cn' ? item.name : item.enName),
+            value: item.callingCode,
+        }))
+    } catch (error) {
+        showToast(error || String(error))
+    }
+}
+// 银行账户类型
+const bankAccountTypeOptions = ref([
+    { text: t('eur-remit.item98'), value: 'SAVINGS' },
+    { text: t('eur-remit.item99'), value: 'CHECKING' },
+])
+
+const bankConfigList = ref<any[]>([])
+const targetBankOptions = ref<Array<{ text: string; value: number }>>([])
+const bankSummary = ref('')
+const loadBankConfigList = async () => {
+    try {
+        const res = await ucardApi.ucardBanks({})
+        if (res && (res.code === 0 || res.code === 200) && Array.isArray(res.data)) {
+            const allBanks = res.data.flatMap((item: any) => item?.bankList || [])
+            bankConfigList.value = allBanks
+            const options = allBanks.map((bank) => ({
+                text: bank.bankName + '(' + bank.bankId + ')',
+                value: bank.bankId,
+            }))
+
+            targetBankOptions.value = options
+            const names = allBanks.map((b) => String(b.bankName || '')).filter(Boolean)
+            bankSummary.value = `支持银行:${names.slice(0, 5).join('、')}${names.length > 5 ? ' 等' + names.length + '家' : ''}`
+        }
+    } catch (error) {
+        bankConfigList.value = []
+        targetBankOptions.value = []
+        bankSummary.value = ''
+    }
+}
+
+// 国家选项
+const countryOptions = ref<Array<{ text: string; value: string }>>([])
+const payerCityList = ref<Array<{ text: string; value: string }>>([])
+const payeeCityList = ref<Array<{ text: string; value: string }>>([])
+// 获取国家列表
+const getCountryListForSelect = async () => {
+    try {
+        const res = await ucardApi.ucardCountryCity({})
+        if (res.code === 200 || res.code === 0) {
+            countryOptions.value = res.data.map((item: any) => ({
+                text: lang.value === 'cn' ? item.cnName : item.enName,
+                value: item.code,
+            }))
+        }
+    } catch (error) {
+        countryOptions.value = []
+    }
+}
+// 获取城市列表
+const getCityListForSelect = async (countryCode: string, type: 'payer' | 'payee' = 'payer') => {
+    try {
+        const res = await ucardApi.ucardCountryCity({ code: countryCode })
+        if (res.code === 200 || res.code === 0) {
+            const cityList = res.data.map((item: any) => ({
+                text: lang.value === 'cn' ? item.cnName : item.enName,
+                value: item.code,
+            }))
+
+            if (type === 'payer') {
+                payerCityList.value = cityList
+            } else {
+                payeeCityList.value = cityList
+            }
+        }
+    } catch (error) {
+        if (type === 'payer') {
+            payerCityList.value = []
+        } else {
+            payeeCityList.value = []
+        }
+    }
+}
+
+// API 调用函数
+interface ApiResponse {
+    success: boolean
+    message?: string
+    data?: any
+}
+const validateBasicInfo = async (data: ucardValidateParams): Promise<ApiResponse> => {
+    try {
+        const res = await ucardApi.ucardValidate({ ...data })
+        if (res.code === 200 || res.code === 0) {
+            return {
+                ...res.data,
+                success: true,
+            }
+        }
+        return {
+            success: false,
+            message: res.msg || t('eur-remit.validateError'),
+        }
+    } catch (error: any) {
+        return {
+            success: false,
+            message: error.response?.data?.message || t('eur-remit.validateError'),
+        }
+    }
+}
+const validatePayerInfo = async (data: ucardValidateParams): Promise<ApiResponse> => {
+    try {
+        const res = await ucardApi.ucardValidatePayer({ ...data })
+        if (res.code === 200 || res.code === 0) {
+            return {
+                ...res.data,
+                success: true,
+            }
+        }
+        return {
+            success: false,
+            message: res.msg || t('eur-remit.validateError'),
+        }
+    } catch (error: any) {
+        return {
+            success: false,
+            message: error.response?.data?.message || t('eur-remit.validateError'),
+        }
+    }
+}
+const validatePayeeInfo = async (data: ucardValidateParams): Promise<ApiResponse> => {
+    try {
+        const res = await ucardApi.ucardValidatePayee({ ...data })
+        if (res.code === 200 || res.code === 0) {
+            return {
+                ...res.data,
+                success: true,
+            }
+        }
+        return {
+            success: false,
+            message: res.msg || t('eur-remit.validateError'),
+        }
+    } catch (error: any) {
+        return {
+            success: false,
+            message: error.response?.data?.message || t('eur-remit.validateError'),
+        }
+    }
+}
+const executePayment = async (data: ucardValidateParams): Promise<ApiResponse> => {
+    try {
+        const res = await ucardApi.ucardTransfer({ ...data })
+        if (res.code === 200 || res.code === 0) {
+            return {
+                ...res,
+                success: true,
+            }
+        }
+        return {
+            success: false,
+            message: res.msg || t('eur-remit.paymentError'),
+        }
+    } catch (error: any) {
+        return {
+            success: false,
+            message: error.response?.data?.message || t('eur-remit.paymentError'),
+        }
+    }
+}
+
+// 获取银行名称
+const getBankName = (bankId: number) => {
+    const bank = bankConfigList.value.find((b) => b.bankId === bankId)
+    return bank ? bank.bankName : ''
+}
+
+// 第一步校验 - 基本信息完整性
+const validateStep1 = async () => {
+    loadingStates.value.validateStep1 = true
+    try {
+        // 检查必填字段
+        const requiredFields = ['bankId', 'amount', 'postscript', 'uniqueId'] as const
+        const missingFields = requiredFields.filter((field) => !formData.value[field])
+        if (missingFields.length > 0) {
+            showToast(t('eur-remit.requiredFieldsMissing'))
+            return
+        }
+        // 检查金额格式
+        if (!/^\d+(\.\d{1,2})?$/.test(String(formData.value.amount))) {
+            showToast(t('eur-remit.amountFormat'))
+            return
+        }
+        currentStep.value = 2
+    } catch (error) {
+        showToast(t('eur-remit.validateError'))
+    } finally {
+        loadingStates.value.validateStep1 = false
+    }
+}
+
+// 第二步校验 - 付款人信息
+const validateStep2 = async () => {
+    loadingStates.value.validateStep2 = true
+    try {
+        const requiredFields = [
+            'payerLastName',
+            'payerFirstName',
+            'payerIdNo',
+            'payerIdNoType',
+            'payerIdCountry',
+            'payerBirthday',
+            'payerNationalityCountry',
+            'payerMobile',
+            'payerCountryCode',
+            'payerCityCode',
+            'payerAddress',
+            'payerPostCode',
+            'payerOccupation',
+        ] as const
+        const missingFields = requiredFields.filter((field) => !formData.value.payer[field])
+
+        if (missingFields.length > 0) {
+            showToast(t('eur-remit.requiredFieldsMissing'))
+            return
+        }
+        const response = await validatePayerInfo(formData.value.payer)
+        if (response.success) {
+            currentStep.value = 3
+        } else {
+            showToast(response.message || t('eur-remit.validateFailed'))
+            return
+        }
+    } catch (error) {
+        showToast(t('eur-remit.validateError'))
+        return
+    } finally {
+        loadingStates.value.validateStep2 = false
+    }
+}
+
+// 第三步校验 - 收款人信息
+const validateStep3 = async () => {
+    loadingStates.value.validateStep3 = true
+    try {
+        const requiredFields = ['benAccountNum', 'benAccountName'] as const
+        const missingFields = requiredFields.filter((field) => !formData.value.payee[field])
+        if (missingFields.length > 0) {
+            showToast(t('eur-remit.requiredFieldsMissing'))
+            return
+        }
+        const response = await validatePayeeInfo(formData.value.payee)
+        if (response.success) {
+            currentStep.value = 4
+            isAllValidated.value = false
+        } else {
+            showToast(response.message || t('eur-remit.validateFailed'))
+            return
+        }
+    } catch (error) {
+        showToast(t('eur-remit.validateError'))
+        return
+    } finally {
+        loadingStates.value.validateStep3 = false
+    }
+}
+
+// 第四步校验 校验所有信息
+const validateStep4 = async () => {
+    loadingStates.value.validateStep4 = true
+    try {
+        // 最终校验所有信息
+        const finalValidation = await validateBasicInfo(formData.value)
+        if (!finalValidation.success) {
+            showToast(finalValidation.message || t('eur-remit.validateFailed'))
+            return
+        }
+        isAllValidated.value = true
+    } catch (error) {
+        showToast(t('eur-remit.validateError'))
+        return
+    } finally {
+        loadingStates.value.validateStep4 = false
+    }
+}
+
+// 第五步校验 - 最终确认并提交
+const validateStep5 = async () => {
+    loadingStates.value.validateStep5 = true
+    try {
+        const payResponse = await executePayment(formData.value)
+        if (payResponse.success) {
+            showToast(t('eur-remit.paymentSuccess'))
+            // 跳转到成功页面
+            router.push({
+                path: '/remit/success',
+                query: {
+                    orderNo: payResponse.data?.orderNo,
+                },
+            })
+        } else {
+            showToast(payResponse.message || t('eur-remit.paymentFailed'))
+        }
+    } catch (error) {
+        showToast(t('eur-remit.validateError'))
+    } finally {
+        loadingStates.value.validateStep5 = false
+    }
+}
+
+const handleBankChange = (value: any) => {
+    const payerKeys = [
+        'payerType',
+        'payerLastName',
+        'payerFirstName',
+        'payerIdNo',
+        'payerIdNoType',
+        'payerIdCountry',
+        'payerBirthday',
+        'payerNationalityCountry',
+        'payerMobile',
+        'payerCountryCode',
+        'payerCityCode',
+        'payerAddress',
+        'payerPostCode',
+        'payerOccupation',
+    ]
+
+    const payeeKeys = [
+        'bankId',
+        'benAccountNum',
+        'benAccountName',
+        'benCountryCode',
+        'benCityCode',
+        'benAddress',
+        'benPostCode',
+        'benTransBankSwift',
+        'benLastName',
+        'benFirstName',
+        'benNationalityCountry',
+        'benIdNoType',
+        'benIdNo',
+        'benIdExpirationDate',
+        'benBirthday',
+        'benMobileCode',
+        'benMobile',
+        'benBankAccountType',
+        'benBankCode',
+    ]
+    if (value.key === 'bankId') {
+        formData.value.bankId = value.value
+    }
+    if (payerKeys.includes(value.key)) {
+        formData.value.payer = { ...formData.value.payer, [value.key]: value.value }
+    } else if (payeeKeys.includes(value.key)) {
+        formData.value.payee = { ...formData.value.payee, [value.key]: value.value }
+    } else {
+        formData.value = { ...formData.value, [value.key]: value.value }
+    }
+    if (value.key === 'payerCountryCode') {
+        getCityListForSelect(value.value, 'payer')
+        formData.value.payer.payerCityCode = ''
+    } else if (value.key === 'benCountryCode') {
+        formData.value.payee.benCityCode = ''
+        getCityListForSelect(value.value, 'payee')
+    }
+}
+
+onMounted(() => {
+    loadBankConfigList()
+    getCountryListForSelect()
+    getMobileCode()
+    if (paymentForm.value.payerCountryCode) {
+        getCityListForSelect(paymentForm.value.payerCountryCode, 'payer')
+    }
+    if (paymentForm.value.benCountryCode) {
+        getCityListForSelect(paymentForm.value.benCountryCode, 'payee')
+    }
+})
+</script>
+
+<style scoped lang="scss">
+.remit-page {
+    min-height: 100vh;
+    background: var(--action-bg);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding-top: 32px;
+}
+
+.remit-form {
+    width: 100%;
+    max-width: 340px;
+    margin: 0 auto 24px auto;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    background: rgba(24, 24, 24, 0.92);
+    border-radius: 20px;
+    box-shadow: 0 4px 32px 0 rgba(214, 255, 0, 0.08);
+    padding: 32px 20px 24px 20px;
+}
+
+.form-group {
+    width: 100%;
+    margin-bottom: 18px;
+}
+
+.form-label {
+    color: var(--white);
+    font-size: 15px;
+    margin-bottom: 8px;
+    display: block;
+}
+
+.balance-info {
+    width: 100%;
+    color: #bdbdbd;
+    font-size: 14px;
+    margin-bottom: 10px;
+    display: flex;
+    align-items: center;
+}
+
+.balance-value {
+    color: var(--main-yellow);
+    font-weight: bold;
+    margin: 0 8px;
+}
+
+.confirm-btn {
+    width: 100%;
+    height: 44px;
+    background: linear-gradient(90deg, var(--main-yellow) 0%, var(--main-yellow-dark) 100%);
+    color: #232323;
+    border: none;
+    border-radius: 22px;
+    font-size: 18px;
+    font-weight: bold;
+    margin-top: 18px;
+    cursor: pointer;
+    box-shadow: 0 2px 12px 0 rgba(73, 247, 166, 0.1);
+    letter-spacing: 2px;
+    transition: background 0.2s, box-shadow 0.2s;
+
+    &:active {
+        opacity: 0.9;
+    }
+}
+
+.payment-form {
+    width: 100%;
+    max-width: 340px;
+    margin: 0 auto 24px auto;
+    background: rgba(24, 24, 24, 0.92);
+    border-radius: 20px;
+    box-shadow: 0 4px 32px 0 rgba(214, 255, 0, 0.08);
+    padding: 32px 20px 24px 20px;
+
+    ::v-deep .van-field {
+        background: transparent;
+        padding: 10px;
+        margin-bottom: 8px;
+
+        &__label {
+            color: var(--white);
+            font-size: 15px;
+            width: 120px;
+        }
+
+        &__control {
+            color: var(--white);
+            &::placeholder {
+                color: #bdbdbd;
+            }
+        }
+
+        &__right-icon {
+            color: var(--main-yellow);
+        }
+    }
+}
+
+.form-section {
+    margin: 24px 0;
+}
+
+.section-title {
+    color: var(--main-yellow);
+    font-size: 16px;
+    margin-bottom: 16px;
+}
+
+.form-actions {
+    margin: 16px 0 32px;
+    display: flex;
+    gap: 12px;
+
+    .prev-btn {
+        flex: 1;
+        border-color: var(--main-yellow);
+        color: var(--main-yellow);
+    }
+
+    .van-button {
+        flex: 2;
+    }
+}
+
+.confirm-info {
+    padding: 16px;
+    border-radius: 12px;
+    margin-bottom: 24px;
+
+    .info-group {
+        margin-bottom: 20px;
+
+        h4 {
+            color: var(--main-yellow);
+            font-size: 16px;
+            margin-bottom: 12px;
+        }
+    }
+
+    .info-item {
+        display: flex;
+        margin-bottom: 8px;
+        color: var(--white);
+
+        .label {
+            width: 120px;
+            color: #bdbdbd;
+        }
+
+        .value {
+            flex: 1;
+        }
+    }
+}
+</style>

+ 175 - 0
src/views/eur.vue

@@ -0,0 +1,175 @@
+<template>
+    <div class="page">
+        <div class="eur-balance-card">
+            <div class="balance-row">
+                <img src="https://upload.wikimedia.org/wikipedia/commons/b/b7/Flag_of_Europe.svg" class="flag" />
+                <span class="amount">0</span>
+                <span class="unit">EUR</span>
+            </div>
+            <div class="eur-actions">
+                <button class="action-btn" @click="goToGlobalRemit">{{ t('eur.globalRemit') }}</button>
+            </div>
+            <div class="balance-chart">
+                <img src="../assets/img/remit.png" alt="chart" class="chart-img" />
+            </div>
+        </div>
+        <div class="record-title">{{ t('eur.transactionRecord') }}</div>
+        <div class="record-list" v-if="transferList.length > 0">
+            <div class="record-item" v-for="item in transferList" :key="item.id" @click="goToTransferDetail(item.id)">
+                <i class="i-mdi-swap-horizontal"></i>
+                <!-- <i class="i-mdi-reload"></i> -->
+                <span>{{ t('eur.globalRemit') }}</span>
+                <div>
+
+                </div>
+                <div class="record-amount-time">
+                    <div class="record-amount">{{ item.amount }}</div>
+                    <div class="record-time">{{ item.addTime }}</div>
+                </div>
+
+            </div>
+        </div>
+        <div class="record-list" v-else>
+            <EmptyState icon="i-mdi-receipt-text-outline" text="暂无交易记录" />
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+import { ucardApi } from '../api/ucard'
+import EmptyState from '../components/EmptyState.vue'
+import { TransferInfo } from '../api/ucard'
+const { t } = useI18n()
+const router = useRouter()
+const goToGlobalRemit = () => {
+    router.push('/eur/remit')
+}
+const transferList = ref<TransferInfo[]>([])
+const getTransferList = async () => {
+    try {
+        const res = await ucardApi.transferList({ page: { current: 1, row: 100 } })
+        if (res.code === 200) {
+            transferList.value = res.data
+        } else {
+            transferList.value = []
+        }
+    } catch (error) {
+        showToast(error || String(error))
+    }
+}
+const goToTransferDetail = (id: string) => {
+    router.push(`/transfer/detail?id=${id}`)
+}
+onMounted(() => {
+    getTransferList()
+})
+</script>
+
+<style scoped lang="scss">
+.eur-balance-card {
+    position: relative;
+    background: var(--action-bg);
+    border-radius: 16px;
+    padding: 45px 16px;
+    margin-bottom: 24px;
+    .balance-row {
+        position: relative;
+        z-index: 1;
+        display: flex;
+        align-items: center;
+        .flag {
+            width: 24px;
+            height: 24px;
+            border-radius: 50%;
+        }
+        .amount {
+            font-size: var(--font-size-18);
+            color: var(--white);
+            margin-left: 16px;
+        }
+        .unit {
+            margin-left: 4px;
+            font-size: var(--font-size-10);
+            color: var(--white);
+        }
+    }
+    .eur-actions {
+        position: relative;
+        top: 24px;
+        z-index: 1;
+        margin-top: 16px;
+        display: flex;
+        gap: 12px;
+        .action-btn {
+            width: 100%;
+            background: var(--main-yellow);
+            color: #232323;
+            border-radius: 16px;
+            padding: 12px 24px;
+            font-size: var(--font-size-14);
+            font-weight: 600;
+            cursor: pointer;
+            border: none;
+        }
+    }
+    .balance-chart {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        background: #222;
+        border-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        .chart-img {
+            width: 100%;
+            height: 100%;
+        }
+    }
+}
+.record-title {
+    font-size: var(--font-size-16);
+    margin-bottom: 12px;
+    color: var(--main-yellow);
+}
+.record-list {
+    .record-item {
+        display: flex;
+        align-items: center;
+        background: var(--action-bg);
+        border-radius: 12px;
+        padding: 12px 16px;
+        margin-bottom: 10px;
+        .i-mdi-reload,
+        .i-mdi-swap-horizontal {
+            font-size: 20px;
+            margin-right: 10px;
+            color: var(--main-yellow);
+        }
+        span {
+            margin-right: 12px;
+            font-size: var(--font-size-14);
+        }
+        .record-amount {
+            color: var(--main-yellow);
+            font-weight: 600;
+            margin-bottom: 15px;
+        }
+        .record-time {
+            color: #aaa;
+            font-size: var(--font-size-12);
+        }
+        .record-amount-time {
+            flex: 1;
+            div {
+                text-align: right;
+            }
+        }
+    }
+}
+</style>

+ 80 - 80
src/views/finance.vue

@@ -1,86 +1,86 @@
 <template>
 <template>
-  <div class="page">
-    <div class="balance-wrap">
-      <div class="currency">
-        <img src="https://upload.wikimedia.org/wikipedia/commons/a/a4/Flag_of_the_United_States.svg" class="flag" />
-        <span>USD</span>
+    <div class="page">
+      <div class="balance-wrap">
+        <div class="currency">
+          <img src="https://upload.wikimedia.org/wikipedia/commons/a/a4/Flag_of_the_United_States.svg" class="flag" />
+          <span>USD</span>
+        </div>
+        <div class="balance">340.05</div>
       </div>
       </div>
-      <div class="balance">340.05</div>
-    </div>
-    <div class="transactions">
-      <div class="trans-title">交易记录</div>
-      <div class="transaction">
-        <div class="trans-type">消费</div>
-        <div class="trans-desc">FEE RETAIL</div>
-        <div class="trans-amount">0.15 USD</div>
-        <div class="trans-date">2025-06-01</div>
-      </div>
-      <div class="transaction">
-        <div class="trans-type">消费</div>
-        <div class="trans-desc">CURSOR, AI POWERED</div>
-        <div class="trans-amount">20 USD</div>
-        <div class="trans-date">2025-06-01</div>
+      <div class="transactions">
+        <div class="trans-title">交易记录</div>
+        <div class="transaction">
+          <div class="trans-type">消费</div>
+          <div class="trans-desc">FEE RETAIL</div>
+          <div class="trans-amount">0.15 USD</div>
+          <div class="trans-date">2025-06-01</div>
+        </div>
+        <div class="transaction">
+          <div class="trans-type">消费</div>
+          <div class="trans-desc">CURSOR, AI POWERED</div>
+          <div class="trans-amount">20 USD</div>
+          <div class="trans-date">2025-06-01</div>
+        </div>
       </div>
       </div>
     </div>
     </div>
-  </div>
-</template>
+  </template>
 
 
-<script setup lang="ts">
-</script>
+  <script setup lang="ts">
+  </script>
 
 
-<style scoped lang="scss">
-.balance-wrap {
-  display: flex;
-  align-items: center;
-  justify-content: flex-start;
-  margin: 0 16px 20px 16px;
-  font-size: var(--font-size-18);
-}
-.currency {
-  display: flex;
-  align-items: center;
-  margin-right: 12px;
-}
-.flag {
-  width: 24px;
-  height: 24px;
-  border-radius: 50%;
-  margin-right: 6px;
-}
-.balance {
-  font-size: var(--font-size-22);
-  font-weight: bold;
-  color: var(--main-yellow);
-}
-.transactions {
-  background: var(--action-bg);
-  border-radius: 16px;
-  margin: 0 16px 16px 16px;
-  padding: 16px 12px;
-}
-.trans-title {
-  font-size: var(--font-size-16);
-  margin-bottom: 10px;
-  color: var(--main-yellow);
-}
-.transaction {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 8px 0;
-  border-bottom: 1px solid var(--border);
-  font-size: var(--font-size-15);
-}
-.transaction:last-child {
-  border-bottom: none;
-}
-.trans-type {
-  color: var(--main-yellow);
-  width: 40px;
-}
-.trans-desc {
-  flex: 1;
-  color: var(--white);
-  margin-left: 10px;
-}
-</style>
+  <style scoped lang="scss">
+  .balance-wrap {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    margin: 0 16px 20px 16px;
+    font-size: var(--font-size-18);
+  }
+  .currency {
+    display: flex;
+    align-items: center;
+    margin-right: 12px;
+  }
+  .flag {
+    width: 24px;
+    height: 24px;
+    border-radius: 50%;
+    margin-right: 6px;
+  }
+  .balance {
+    font-size: var(--font-size-22);
+    font-weight: bold;
+    color: var(--main-yellow);
+  }
+  .transactions {
+    background: var(--action-bg);
+    border-radius: 16px;
+    margin: 0 16px 16px 16px;
+    padding: 16px 12px;
+  }
+  .trans-title {
+    font-size: var(--font-size-16);
+    margin-bottom: 10px;
+    color: var(--main-yellow);
+  }
+  .transaction {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 8px 0;
+    border-bottom: 1px solid var(--border);
+    font-size: var(--font-size-15);
+  }
+  .transaction:last-child {
+    border-bottom: none;
+  }
+  .trans-type {
+    color: var(--main-yellow);
+    width: 40px;
+  }
+  .trans-desc {
+    flex: 1;
+    color: var(--white);
+    margin-left: 10px;
+  }
+  </style>

+ 13 - 4
src/views/find-password.vue

@@ -16,14 +16,16 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref } from 'vue'
 import { showToast } from 'vant'
 import { showToast } from 'vant'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import { $Api } from '@/api'
 import { $Api } from '@/api'
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
+import { ucardApi } from '@/api/ucard'
+import type { CardInfo } from '@/api/ucard'
 const { t } = useI18n()
 const { t } = useI18n()
 const route = useRoute()
 const route = useRoute()
-const { uniqueId, cardNo } = route.query as { uniqueId: string; cardNo: string }
+const { id } = route.query as { id: string }
+const cardInfo = ref<CardInfo | null>(null)
 const file = ref<File | null>(null)
 const file = ref<File | null>(null)
 const previewUrl = ref('')
 const previewUrl = ref('')
 const signaturePhoto = ref('')
 const signaturePhoto = ref('')
@@ -31,6 +33,14 @@ const fileInput = ref<HTMLInputElement | null>(null)
 function triggerFileInput() {
 function triggerFileInput() {
     fileInput.value?.click()
     fileInput.value?.click()
 }
 }
+const getCardInfo = async () => {
+    const res = await ucardApi.cardSingle({ id })
+    cardInfo.value = res.data
+}
+onMounted(() => {
+    getCardInfo()
+})
+
 function handleFileChange(e: Event) {
 function handleFileChange(e: Event) {
     const target = e.target as HTMLInputElement
     const target = e.target as HTMLInputElement
     if (target.files && target.files[0]) {
     if (target.files && target.files[0]) {
@@ -62,8 +72,7 @@ async function handleConfirm() {
         try {
         try {
             await $Api.ucard.ucardResetPassword({
             await $Api.ucard.ucardResetPassword({
                 signaturePhoto: result.data,
                 signaturePhoto: result.data,
-                uniqueId,
-                cardNo,
+                ...cardInfo.value,
             })
             })
             showToast('重置密码成功')
             showToast('重置密码成功')
         } catch (error) {
         } catch (error) {

+ 87 - 88
src/views/forget-pay-password.vue

@@ -1,104 +1,103 @@
 <template>
 <template>
-  <div class="page forget-pay-password-page">
-    <div class="form-box">
-      <div class="desc">{{t('forget-pay-password.desc')}}</div>
-      <div class="input-row">
-        <input class="input" v-model="code" :placeholder="t('forget-pay-password.input')" />
-        <button class="code-btn" :disabled="codeBtnDisabled" @click="getCode">{{t('forget-pay-password.getCode')}}</button>
+    <div class="page forget-pay-password-page">
+      <div class="form-box">
+        <div class="desc">{{t('forget-pay-password.desc')}}</div>
+        <div class="input-row">
+          <input class="input" v-model="code" :placeholder="t('forget-pay-password.input')" />
+          <button class="code-btn" :disabled="codeBtnDisabled" @click="getCode">{{t('forget-pay-password.getCode')}}</button>
+        </div>
+        <button class="next-btn" @click="nextStep">{{t('forget-pay-password.nextStep')}}</button>
       </div>
       </div>
-      <button class="next-btn" @click="nextStep">{{t('forget-pay-password.nextStep')}}</button>
     </div>
     </div>
-  </div>
-</template>
+  </template>
 
 
-<script setup lang="ts">
-import { ref } from 'vue'
-import { useI18n } from 'vue-i18n'
-const { t } = useI18n()
-const code = ref('')
-const codeBtnDisabled = ref(false)
-function getCode() {
-  codeBtnDisabled.value = true
-  setTimeout(() => {
-    codeBtnDisabled.value = false
-  }, 60000)
-}
-function nextStep() {
-}
-</script>
-
-<style scoped lang="scss">
-.forget-pay-password-page {
-  min-height: 100vh;
-  background: linear-gradient(135deg, #232323 0%, #2a2a2a 100%);
-  padding: 0;
-  .header {
-    display: flex;
-    align-items: center;
-    padding: 24px 0 18px 12px;
-    .back-icon {
-      font-size: 22px;
-      color: var(--main-yellow);
-      margin-right: 10px;
-      cursor: pointer;
-    }
-    .title {
-      font-size: 18px;
-      color: #fff;
-      font-weight: bold;
-    }
+  <script setup lang="ts">
+  import { useI18n } from 'vue-i18n'
+  const { t } = useI18n()
+  const code = ref('')
+  const codeBtnDisabled = ref(false)
+  function getCode() {
+    codeBtnDisabled.value = true
+    setTimeout(() => {
+      codeBtnDisabled.value = false
+    }, 60000)
   }
   }
-  .form-box {
-    margin: 60px 18px 0 18px;
-    display: flex;
-    flex-direction: column;
-    gap: 32px;
-    .desc {
-      color: #fff;
-      font-size: 15px;
-      margin-bottom: 18px;
+  function nextStep() {
+  }
+  </script>
+
+  <style scoped lang="scss">
+  .forget-pay-password-page {
+    min-height: 100vh;
+    background: linear-gradient(135deg, #232323 0%, #2a2a2a 100%);
+    padding: 0;
+    .header {
+      display: flex;
+      align-items: center;
+      padding: 24px 0 18px 12px;
+      .back-icon {
+        font-size: 22px;
+        color: var(--main-yellow);
+        margin-right: 10px;
+        cursor: pointer;
+      }
+      .title {
+        font-size: 18px;
+        color: #fff;
+        font-weight: bold;
+      }
     }
     }
-    .input-row {
+    .form-box {
+      margin: 60px 18px 0 18px;
       display: flex;
       display: flex;
-      gap: 12px;
-      .input {
-        flex: 1;
-        height: 44px;
-        border-radius: 8px;
-        border: none;
-        padding: 0 12px;
-        font-size: 16px;
-        background: #181818;
+      flex-direction: column;
+      gap: 32px;
+      .desc {
         color: #fff;
         color: #fff;
+        font-size: 15px;
+        margin-bottom: 18px;
       }
       }
-      .code-btn {
+      .input-row {
+        display: flex;
+        gap: 12px;
+        .input {
+          flex: 1;
+          height: 44px;
+          border-radius: 8px;
+          border: none;
+          padding: 0 12px;
+          font-size: 16px;
+          background: #181818;
+          color: #fff;
+        }
+        .code-btn {
+          background: var(--main-yellow);
+          color: #232323;
+          border: none;
+          border-radius: 8px;
+          padding: 0 18px;
+          font-size: 15px;
+          height: 44px;
+          cursor: pointer;
+          font-weight: bold;
+        }
+        .code-btn:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
+      }
+      .next-btn {
+        width: 100%;
+        height: 48px;
         background: var(--main-yellow);
         background: var(--main-yellow);
         color: #232323;
         color: #232323;
         border: none;
         border: none;
-        border-radius: 8px;
-        padding: 0 18px;
-        font-size: 15px;
-        height: 44px;
-        cursor: pointer;
+        border-radius: 24px;
+        font-size: 18px;
         font-weight: bold;
         font-weight: bold;
+        margin-top: 24px;
+        cursor: pointer;
       }
       }
-      .code-btn:disabled {
-        opacity: 0.5;
-        cursor: not-allowed;
-      }
-    }
-    .next-btn {
-      width: 100%;
-      height: 48px;
-      background: var(--main-yellow);
-      color: #232323;
-      border: none;
-      border-radius: 24px;
-      font-size: 18px;
-      font-weight: bold;
-      margin-top: 24px;
-      cursor: pointer;
     }
     }
   }
   }
-}
-</style>
+  </style>

+ 19 - 7
src/views/freeze-card.vue

@@ -16,16 +16,25 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref } from 'vue'
 import { showToast } from 'vant'
 import { showToast } from 'vant'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
+import { useRoute } from 'vue-router'
+import { ucardApi } from '@/api/ucard'
+import type { CardInfo } from '@/api/ucard'
 const { t } = useI18n()
 const { t } = useI18n()
+const route = useRoute()
+const { id } = route.query as { id: string }
+const cardInfo = ref<CardInfo | null>(null)
 const file = ref<File | null>(null)
 const file = ref<File | null>(null)
 const previewUrl = ref('')
 const previewUrl = ref('')
 const fileInput = ref<HTMLInputElement | null>(null)
 const fileInput = ref<HTMLInputElement | null>(null)
 function triggerFileInput() {
 function triggerFileInput() {
     fileInput.value?.click()
     fileInput.value?.click()
 }
 }
+const getCardInfo = async () => {
+    const res = await ucardApi.cardSingle({ id })
+    cardInfo.value = res.data
+}
 function handleFileChange(e: Event) {
 function handleFileChange(e: Event) {
     const target = e.target as HTMLInputElement
     const target = e.target as HTMLInputElement
     if (target.files && target.files[0]) {
     if (target.files && target.files[0]) {
@@ -39,14 +48,17 @@ function handleFileChange(e: Event) {
     }
     }
 }
 }
 
 
-function handleConfirm() {
-    if (!file.value) {
-        showToast(t('freeze-card.needUpload'))
-        return
+async function handleConfirm() {
+    try {
+        await ucardApi.ucardFreeze({ ...cardInfo.value })
+        showToast('冻结成功')
+    } catch (error) {
+        showToast(error?.message || String(error))
     }
     }
-    // 这里可以添加上传逻辑
-    showToast(t('freeze-card.submitted'))
 }
 }
+onMounted(() => {
+    getCardInfo()
+})
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 150 - 61
src/views/home.vue

@@ -10,57 +10,100 @@
                 <div class="card-yellow"></div>
                 <div class="card-yellow"></div>
             </div>
             </div>
         </div>
         </div>
-        <div class="wallet-balance-card">
+        <!-- <div class="wallet-balance-card" @click="handleGlobalRemit">
             <div class="balance-row">
             <div class="balance-row">
                 <img src="https://upload.wikimedia.org/wikipedia/commons/b/b7/Flag_of_Europe.svg" class="flag" />
                 <img src="https://upload.wikimedia.org/wikipedia/commons/b/b7/Flag_of_Europe.svg" class="flag" />
-                <span class="currency">EUR</span>
+
                 <span class="amount">0</span>
                 <span class="amount">0</span>
                 <span class="unit">EUR</span>
                 <span class="unit">EUR</span>
-                <button class="quick-btn">全球速汇</button>
             </div>
             </div>
-        </div>
-        <div class="wallet-balance-card">
+            <div class="wallet-actions">
+                <button class="action-btn">{{ t('eur.globalRemit') }}</button>
+            </div>
+            <i class="i-mdi-trending-up"></i>
+        </div> -->
+        <div class="wallet-balance-card" @click="handleRemit" v-for="item in currencyList" :key="item.name" >
             <div class="balance-row">
             <div class="balance-row">
                 <img src="https://cryptologos.cc/logos/tether-usdt-logo.png" class="flag" />
                 <img src="https://cryptologos.cc/logos/tether-usdt-logo.png" class="flag" />
-                <span class="currency">USDT</span>
-                <span class="amount">0.0088</span>
-                <span class="unit">USDT</span>
+                <span class="amount">{{ item.amount }}</span>
+                <span class="unit">{{ item.name }}</span>
             </div>
             </div>
-            <div class="wallet-actions">
+            <!-- <div class="wallet-actions">
                 <button class="action-btn">提现</button>
                 <button class="action-btn">提现</button>
                 <button class="action-btn">充值</button>
                 <button class="action-btn">充值</button>
                 <button class="action-btn">购买</button>
                 <button class="action-btn">购买</button>
                 <button class="action-btn">卖出</button>
                 <button class="action-btn">卖出</button>
-            </div>
+            </div> -->
+            <i class="i-mdi-trending-up"></i>
         </div>
         </div>
         <div class="currency-list">
         <div class="currency-list">
-            <div class="currency-title">币种</div>
-            <div class="currency-item">
-                <img src="https://upload.wikimedia.org/wikipedia/commons/b/b7/Flag_of_Europe.svg" class="flag" />
-                <span>EUR</span>
-                <span class="currency-amount">0</span>
+            <div class="currency-title">
+                <span>币种</span>
+                <span>金额</span>
             </div>
             </div>
-            <div class="currency-item">
-                <img src="https://cryptologos.cc/logos/tether-usdt-logo.png" class="flag" />
-                <span>USDT</span>
-                <span class="currency-amount">0.0088</span>
+            <div class="currency-item" v-for="item in currencyList" :key="item.name">
+                <img :src="item.flag" class="flag" />
+                <span class="currency">{{ item.name }}</span>
+                <div class="currency-amount">
+                    {{ item.amount }}
+                    <!-- <i class="i-mdi-call-made"></i> -->
+                    <i class="i-mdi-arrow-right-thin"></i>
+                </div>
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
 </template>
 </template>
 
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+import { userApi } from '@/api/user'
+const { t } = useI18n()
+const router = useRouter()
+const currencyList = ref([
+    // {
+    //     name: 'EUR',
+    //     amount: 0,
+    //     flag: 'https://upload.wikimedia.org/wikipedia/commons/b/b7/Flag_of_Europe.svg'
+    // },
+])
+const getBalance = async () => {
+    try {
+        const res = await userApi.walletBalance()
+        currencyList.value.push({
+            name: 'USD',
+            amount: res.data || 0,
+            flag: 'https://cryptologos.cc/logos/tether-usdt-logo.png',
+        })
+    } catch (error) {}
+}
+const handleGlobalRemit = () => {
+    router.push('/eur')
+}
+const handleRemit = () => {
+    router.push('/remit')
+}
+
+onMounted(() => {
+    getBalance()
+})
+</script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
 .wallet-banner {
 .wallet-banner {
     background: linear-gradient(135deg, #232323 0%, #2a2a2a 100%);
     background: linear-gradient(135deg, #232323 0%, #2a2a2a 100%);
     border-radius: 16px;
     border-radius: 16px;
     margin-bottom: 20px;
     margin-bottom: 20px;
-    padding: 18px 20px 18px 20px;
+    padding: 24px 20px;
     color: #fff;
     color: #fff;
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
     justify-content: space-between;
     justify-content: space-between;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+    transition: transform 0.3s ease;
+    &:hover {
+        transform: translateY(-2px);
+    }
     .banner-content {
     .banner-content {
         flex: 1;
         flex: 1;
         .banner-title {
         .banner-title {
@@ -78,14 +121,18 @@
     .banner-cards {
     .banner-cards {
         display: flex;
         display: flex;
         align-items: center;
         align-items: center;
-        gap: 8px;
-        margin-left: 12px;
+        gap: 12px;
+        margin-left: 16px;
         .card-black,
         .card-black,
         .card-yellow {
         .card-yellow {
             width: 48px;
             width: 48px;
             height: 68px;
             height: 68px;
             border-radius: 8px;
             border-radius: 8px;
-            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+            transition: transform 0.3s ease;
+            &:hover {
+                transform: translateY(-4px) rotate(2deg);
+            }
         }
         }
         .card-black {
         .card-black {
             background: #111;
             background: #111;
@@ -96,62 +143,71 @@
     }
     }
 }
 }
 .wallet-balance-card {
 .wallet-balance-card {
+    position: relative;
     background: var(--action-bg);
     background: var(--action-bg);
     border-radius: 16px;
     border-radius: 16px;
     margin-bottom: 16px;
     margin-bottom: 16px;
-    padding: 16px 12px;
+    padding: 20px 16px;
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+    transition: all 0.3s ease;
+    i {
+        position: absolute;
+        right: 16px;
+        top: 16px;
+        font-size: var(--font-size-20);
+        color: var(--white);
+        margin-left: auto;
+        cursor: pointer;
+        width: 20px;
+        height: 20px;
+        display: inline-block;
+    }
+    &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
+    }
     .balance-row {
     .balance-row {
         display: flex;
         display: flex;
         align-items: center;
         align-items: center;
-        gap: 8px;
         .flag {
         .flag {
             width: 24px;
             width: 24px;
             height: 24px;
             height: 24px;
             border-radius: 50%;
             border-radius: 50%;
         }
         }
-        .currency {
-            font-size: 16px;
-            color: var(--main-yellow);
-            margin-right: 4px;
-        }
         .amount {
         .amount {
-            font-size: 18px;
-            font-weight: bold;
+            font-size: var(--font-size-18);
             color: var(--white);
             color: var(--white);
-            margin: 0 4px;
+            margin-left: 16px;
         }
         }
         .unit {
         .unit {
-            font-size: 14px;
-            color: var(--main-yellow);
-            margin-right: 8px;
-        }
-        .quick-btn {
-            background: var(--main-yellow);
-            color: #232323;
-            border: none;
-            border-radius: 8px;
-            padding: 4px 12px;
-            font-size: 13px;
-            margin-left: auto;
-            cursor: pointer;
+            padding-top: 8px;
+            margin-left: 4px;
+            font-size: var(--font-size-10);
+            color: var(--white);
         }
         }
     }
     }
     .wallet-actions {
     .wallet-actions {
         display: flex;
         display: flex;
-        justify-content: space-between;
-        margin-top: 10px;
+        margin-top: 16px;
+        gap: 12px;
         .action-btn {
         .action-btn {
             background: var(--main-bg);
             background: var(--main-bg);
             color: var(--main-yellow);
             color: var(--main-yellow);
             border: 1px solid var(--main-yellow);
             border: 1px solid var(--main-yellow);
-            border-radius: 8px;
-            padding: 6px 14px;
-            font-size: 14px;
+            border-radius: 16px;
+            padding: 2px 12px;
+            line-height: 18px;
+            font-size: var(--font-size-12);
+            font-weight: 400;
             cursor: pointer;
             cursor: pointer;
-            transition: all 0.2s;
+            transition: all 0.3s ease;
             &:hover {
             &:hover {
                 background: var(--main-yellow);
                 background: var(--main-yellow);
                 color: #232323;
                 color: #232323;
+                transform: translateY(-2px);
+            }
+            &:active {
+                transform: translateY(0);
             }
             }
         }
         }
     }
     }
@@ -159,18 +215,27 @@
 .currency-list {
 .currency-list {
     margin-bottom: 16px;
     margin-bottom: 16px;
     .currency-title {
     .currency-title {
-        font-size: 15px;
-        color: var(--main-yellow);
-        margin-bottom: 8px;
+        font-size: var(--font-size-16);
+        color: var(--white);
+        margin-bottom: 12px;
+        display: flex;
+        justify-content: space-between;
+        padding: 0 4px;
     }
     }
     .currency-item {
     .currency-item {
         display: flex;
         display: flex;
         align-items: center;
         align-items: center;
         justify-content: space-between;
         justify-content: space-between;
         background: var(--action-bg);
         background: var(--action-bg);
-        border-radius: 10px;
-        padding: 10px 12px;
-        margin-bottom: 8px;
+        border-radius: 12px;
+        padding: 14px 16px;
+        margin-bottom: 10px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+        transition: all 0.3s ease;
+        &:hover {
+            transform: translateX(4px);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
+        }
         .flag {
         .flag {
             width: 22px;
             width: 22px;
             height: 22px;
             height: 22px;
@@ -178,12 +243,36 @@
             margin-right: 8px;
             margin-right: 8px;
         }
         }
         span {
         span {
-            font-size: 15px;
+            font-size: var(--font-size-15);
             color: var(--white);
             color: var(--white);
         }
         }
         .currency-amount {
         .currency-amount {
+            width: 150px;
             color: var(--main-yellow);
             color: var(--main-yellow);
-            font-weight: bold;
+            text-align: right;
+            font-weight: 600;
+            font-size: var(--font-size-16);
+            line-height: 20px;
+            display: flex;
+            align-items: center;
+            justify-content: flex-end;
+            i {
+                margin-left: 14px;
+                transition: transform 0.3s ease;
+                font-size: var(--font-size-16);
+                line-height: 20px;
+                color: var(--white);
+                cursor: pointer;
+                font-weight: 600;
+                width: 20px;
+                display: inline-block;
+                height: 20px;
+            }
+        }
+        .currency {
+            color: var(--white);
+            width: 100px;
+            text-align: left;
         }
         }
     }
     }
 }
 }

+ 695 - 0
src/views/improve-info.vue

@@ -0,0 +1,695 @@
+<template>
+    <div class="page">
+        <div class="kyc-form" v-if="isShow">
+            <remit-input
+                v-model:value="infoForm.lastName"
+                fkey="lastName"
+                :required="true"
+                :label="t('improve-info.p8')"
+                :rules="rules['lastName']"
+                @change="handleChange"
+            />
+            <remit-input
+                v-model:value="infoForm.firstName"
+                fkey="firstName"
+                :required="true"
+                :label="t('improve-info.p9')"
+                :rules="rules['firstName']"
+                @change="handleChange"
+            />
+
+            <remit-input
+                v-model:value="infoForm.email"
+                fkey="email"
+                :label="t('improve-info.p7')"
+                :required="true"
+                :rules="rules['email']"
+                @change="handleChange"
+            />
+            <remit-input
+                v-model:value="infoForm.areaCode"
+                fkey="areaCode"
+                :required="true"
+                type="select"
+                :show-search="true"
+                :columns="phoneCodes"
+                :label="t('improve-info.p4')"
+                :rules="rules['areaCode']"
+                @change="handleChange"
+            />
+            <remit-input
+                v-model:value="infoForm.mobile"
+                fkey="mobile"
+                :label="t('improve-info.p6')"
+                :required="true"
+                :rules="rules['mobile']"
+                @change="handleChange"
+            />
+            <remit-input
+                v-model:value="infoForm.sex"
+                fkey="sex"
+                type="select"
+                :columns="sexOptions"
+                :label="t('improve-info.p13')"
+                @change="handleChange"
+            />
+            <remit-input v-model:value="infoForm.birthday" type="date" fkey="birthday" :label="t('improve-info.p10')" @change="handleChange" />
+            <remit-input
+                v-model:value="infoForm.nationality"
+                fkey="nationality"
+                type="select"
+                :show-search="true"
+                :columns="countryOptions"
+                :label="t('improve-info.p17')"
+                @change="handleChange"
+            />
+            <remit-input
+                v-model:value="infoForm.town"
+                fkey="town"
+                :show-search="true"
+                type="select"
+                :columns="cityOptions"
+                :label="t('improve-info.p19')"
+                @change="handleChange"
+            />
+            <remit-input
+                v-model:value="infoForm.address"
+                fkey="address"
+                :label="t('improve-info.p21')"
+                :required="true"
+                :rules="rules['address']"
+                @change="handleChange"
+            />
+            <remit-input v-model:value="infoForm.postCode" fkey="postCode" :label="t('improve-info.p22')" @change="handleChange" />
+            <remit-input
+                v-model:value="infoForm.idType"
+                fkey="idType"
+                type="select"
+                :columns="idTypeOptions"
+                :label="t('improve-info.p23')"
+                @change="handleChange"
+            />
+            <remit-input v-model:value="infoForm.idNo" fkey="idNo" :label="t('improve-info.p27')" @change="handleChange" />
+            <remit-input
+                v-model:value="infoForm.idNoExpiryDate"
+                type="date"
+                fkey="idNoExpiryDate"
+                :label="t('improve-info.p28')"
+                @change="handleChange"
+            />
+            <remit-input v-model:value="infoForm.idPicture" type="upload" fkey="idPicture" :label="t('improve-info.p29')" @change="handleChange" />
+            <remit-input
+                v-model:value="infoForm.facePicture"
+                type="upload"
+                fkey="facePicture"
+                :label="t('improve-info.p30')"
+                @change="handleChange"
+            />
+            <!-- <div class="form-item phone-input">
+                <div :class="{ 'phone-code': true, 'phone-code-active': form.phoneCode }">
+                    <van-field
+                        is-link
+                        readonly
+                        @click="showPicker = true"
+                        v-model="form.phoneCode"
+                        :placeholder="t('improve-info.phoneCode')"
+                        :rules="[{ required: true, message: t('improve-info.phoneCode') }]"
+                        autocomplete="off"
+                    />
+                    <van-popup v-model:show="showPicker" destroy-on-close round position="bottom">
+                        <van-picker
+                            v-model="form.phoneCode1"
+                            :visible-option-num="8"
+                            show-toolbar
+                            :title="t('improve-info.country')"
+                            :columns="[phoneCodes]"
+                            :cancel-button-text="t('common.cancel')"
+                            :confirm-button-text="t('common.confirm')"
+                            @cancel="showPicker = false"
+                            @confirm="onConfirm"
+                        />
+                    </van-popup>
+                </div>
+                <div class="phone-divider"></div>
+                <div class="phone-number">
+                    <van-field
+                        v-model="form.phone"
+                        :placeholder="t('improve-info.phone')"
+                        :rules="[{ required: true, message: t('improve-info.phone') }]"
+                        autocomplete="off"
+                    />
+                </div>
+            </div> -->
+
+            <div class="kyc-button">
+                <van-button type="primary" block @click="kycSubmit">{{ t('improve-info.p33') }}</van-button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { pinyin } from 'pinyin-pro'
+import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+import { showToast } from 'vant'
+import { ucardApi } from '@/api/ucard'
+import { userApi } from '@/api/user'
+import useUserStore from '@/stores/use-user-store'
+const userStore = useUserStore()
+const userInfo = computed(() => userStore.userInfo)
+const { t } = useI18n()
+const router = useRouter()
+
+const sexOptions = ref([
+    { text: t('improve-info.p15'), value: 1 },
+    { text: t('improve-info.p16'), value: 2 },
+])
+const idTypeOptions = ref([
+    { text: t('improve-info.p25'), value: 'EUROPEAN_ID' },
+    { text: t('improve-info.p26'), value: 'PASSPORT' },
+])
+// 表单验证规则
+const rules = {
+    email: [{ required: true, message: t('improve-info.p3') }],
+    lastName: [{ required: true, message: t('improve-info.p3') }],
+    firstName: [{ required: true, message: t('improve-info.p3') }],
+    address: [{ required: true, message: t('improve-info.p3') }],
+    mobile: [{ required: true, message: t('improve-info.p3') }],
+    areaCode: [{ required: true, message: t('improve-info.p5') }],
+}
+const infoForm = ref({
+    areaCode: undefined,
+    mobile: undefined,
+    idNo: undefined,
+    sex: undefined,
+    birthday: undefined,
+    nationality: undefined,
+    town: undefined,
+    postCode: undefined,
+    idType: undefined,
+    idNoExpiryDate: undefined,
+    idPicture: undefined,
+    facePicture: undefined,
+    email: undefined,
+    lastName: undefined,
+    firstName: undefined,
+    address: undefined,
+    cId: undefined,
+})
+const formData = ref<typeof infoForm.value>({} as any)
+const isShow = ref(false)
+
+// 国家选项
+const countryOptions = ref<Array<{ text: string; value: string }>>([])
+const cityOptions = ref<Array<{ text: string; value: string }>>([])
+const phoneCodes = ref([])
+// 获取国家列表
+const getCountryListForSelect = async () => {
+    try {
+        const res = await ucardApi.ucardCountryCity({})
+        if (res.code === 200 || res.code === 0) {
+            countryOptions.value = res.data.map((item: any) => ({
+                text: lang.value === 'cn' ? item.cnName : item.enName,
+                value: item.code,
+            }))
+        }
+    } catch (error) {
+        countryOptions.value = []
+    }
+}
+// 获取城市列表
+const getCityListForSelect = async (countryCode: string) => {
+    try {
+        const res = await ucardApi.ucardCountryCity({ code: countryCode })
+        if (res.code === 200 || res.code === 0) {
+            const cityList = res.data.map((item: any) => ({
+                text: lang.value === 'cn' ? item.cnName : item.enName,
+                value: item.code,
+            }))
+            cityOptions.value = cityList
+            isShow.value = true
+        }
+    } catch (error) {
+        cityOptions.value = []
+    }
+}
+const getCountry = async () => {
+    try {
+        const res = await ucardApi.countryGet()
+        phoneCodes.value = res.data.map((item: { callingCode: string; name: string; enName: string }) => ({
+            text: lang.value === 'cn' ? item.name + ' + ' + item.callingCode : item.enName + ' + ' + item.callingCode,
+            value: item.callingCode,
+        }))
+    } catch (error) {
+        showToast(error || String(error))
+    }
+}
+const kycSubmit = async () => {
+    const requiredFields = ['email', 'lastName', 'firstName', 'address', 'mobile', 'areaCode'] as const
+    const missingFields = requiredFields.filter((field) => !formData.value[field])
+    if (missingFields.length > 0) {
+        showToast(t('eur-remit.requiredFieldsMissing'))
+        return
+    }
+    let res
+
+    if (formData.value.uniqueId) {
+        res = await ucardApi.merchantUpdate(formData.value as any)
+        if (res.code === 200) {
+            showToast(t('improve-info.kycSuccess'))
+        }
+    } else {
+        res = await ucardApi.merchantRegister(formData.value as any)
+        if (res.code === 200) {
+            showToast(t('improve-info.kycSuccess'))
+            userInfo.value.customInfo.uniqueId = res.data
+            console.log(userInfo.value)
+            userStore.saveUserInfo(userInfo.value)
+            router.push('/')
+        }
+    }
+}
+const handleChange = (value: any) => {
+    formData.value = { ...formData.value, [value.key]: value.value }
+    if (value.key === 'nationality') {
+        getCityListForSelect(value.value)
+        formData.value.town = ''
+    }
+}
+const containsChinese = (str: string) => /[\u4e00-\u9fa5]/.test(str)
+const convertToPinyin = (value: string) => (containsChinese(value) ? pinyin(value, { toneType: 'none', type: 'capitalize' }) : value)
+const getInfoForm = () => {
+    const customInfo = userInfo.value?.customInfo || {}
+    const fields = [
+        'areaCode',
+        'mobile',
+        'idNo',
+        'sex',
+        'birthday',
+        'nationality',
+        'town',
+        'postCode',
+        'idType',
+        'idNoExpiryDate',
+        'idPicture',
+        'facePicture',
+        'email',
+        'lastName',
+        'firstName',
+        'address',
+        'cId',
+        'uniqueId',
+    ]
+
+    const data: Record<string, any> = {}
+    for (const key of fields) {
+        data[key] = customInfo[key] ?? undefined
+    }
+    data.lastName = convertToPinyin(data.lastName)
+    data.firstName = convertToPinyin(data.firstName)
+    infoForm.value = { ...data }
+    formData.value = { ...formData.value, ...data }
+}
+const getUserInfo = async () => {
+    if (!userToken.value) {
+        showToast('请先登录')
+        return
+    }
+    try {
+        const res = await userApi.getUserInfo()
+
+        if (res.code === 200) {
+            if (res.data.customInfo.uniqueId) {
+                router.push('/')
+            } else {
+                router.push('/improve/info')
+            }
+        } else {
+            showToast(res.msg || '登录失败')
+        }
+    } catch (error: any) {
+        showToast(error.message || '登录失败')
+    } finally {
+    }
+}
+onMounted(() => {
+    getInfoForm()
+    isShow.value = false
+    getCountryListForSelect()
+    getCountry()
+    if (infoForm.value.nationality) {
+        getCityListForSelect(infoForm.value.nationality)
+    } else {
+        isShow.value = true
+    }
+})
+</script>
+
+<style scoped lang="scss">
+.kyc-page {
+    min-height: 100vh;
+    background: linear-gradient(135deg, #232323 0%, #2a2a2a 100%);
+    color: #fff;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 32px 12px;
+}
+.kyc-header {
+    width: 100%;
+    text-align: center;
+    margin-bottom: 24px;
+    h2 {
+        font-size: var(--font-size-18);
+        color: var(--main-yellow);
+        margin-bottom: 8px;
+    }
+    p {
+        color: #bbb;
+        font-size: var(--font-size-12);
+        line-height: 1.5;
+    }
+}
+.kyc-form {
+    width: 100%;
+    max-width: 350px;
+    border-radius: 20px;
+    padding: 28px 18px 18px 18px;
+    display: flex;
+    flex-direction: column;
+    gap: 18px;
+}
+
+.form-item {
+    margin-bottom: 0;
+    ::v-deep {
+        .van-field {
+            background: #181818;
+            border-radius: 12px;
+            padding: 12px 16px;
+            transition: all 0.3s ease;
+
+            &:focus-within {
+                background: #1f1f1f;
+            }
+
+            .van-field__label {
+                color: #bbb;
+                font-size: var(--font-size-14);
+                width: 80px;
+            }
+
+            .van-field__control {
+                color: #fff;
+                font-size: var(--font-size-14);
+                min-height: 20px;
+                line-height: 20px;
+
+                &::placeholder {
+                    color: #666;
+                    font-size: var(--font-size-14);
+                }
+
+                &::-webkit-input-placeholder {
+                    color: #666;
+                }
+
+                &::-moz-placeholder {
+                    color: #666;
+                    opacity: 1;
+                }
+            }
+
+            .van-field__right-icon {
+                color: var(--main-yellow);
+                margin-left: 8px;
+            }
+
+            .van-field__error-message {
+                color: #ff4d4f;
+                font-size: var(--font-size-12);
+                margin-top: 4px;
+            }
+        }
+    }
+}
+.kyc-button {
+    margin-top: 18px;
+    ::v-deep {
+        .van-button {
+            height: 44px;
+            border-radius: 24px;
+            background: var(--main-yellow);
+            border: none;
+            color: #232323;
+            font-size: var(--font-size-16);
+            font-weight: bold;
+
+            &:active {
+                opacity: 0.9;
+            }
+        }
+    }
+}
+.apply-card-steps {
+    width: 100%;
+    max-width: 350px;
+    margin-bottom: 32px;
+
+    .steps-top {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 0 12px;
+    }
+
+    .step {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 8px;
+
+        .step-circle {
+            width: 32px;
+            height: 32px;
+            border-radius: 50%;
+            background: var(--action-bg, #181818);
+            border: 2px solid #444;
+            color: #444;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: var(--font-size-16);
+            font-weight: bold;
+            transition: all 0.3s ease;
+        }
+
+        .step-label {
+            font-size: var(--font-size-14);
+            color: #444;
+            transition: all 0.3s ease;
+        }
+
+        &.active {
+            .step-circle {
+                background: var(--main-yellow);
+                border-color: var(--main-yellow);
+                color: #232323;
+            }
+
+            .step-label {
+                color: var(--main-yellow);
+            }
+        }
+    }
+
+    .step-dash {
+        flex: 1;
+        height: 2px;
+        margin: 0 8px;
+        position: relative;
+        top: -16px;
+        transition: all 0.3s ease;
+        background-image: linear-gradient(to right, #444 50%, transparent 50%);
+        background-size: 8px 2px;
+        background-repeat: repeat-x;
+
+        &.active {
+            background-image: linear-gradient(to right, var(--main-yellow) 50%, transparent 50%);
+        }
+    }
+}
+
+.phone-input {
+    display: flex;
+    align-items: center;
+    background: #181818;
+    border-radius: 12px;
+    padding: 0 16px;
+    height: 44px;
+    transition: all 0.3s ease;
+
+    &:focus-within {
+        background: #1f1f1f;
+    }
+
+    .phone-code {
+        width: 20%;
+        ::v-deep .van-cell {
+            &::after {
+                border-bottom: 0;
+            }
+            .van-icon {
+                font-size: var(--font-size-14);
+                line-height: 20px;
+            }
+        }
+        ::v-deep {
+            .van-field {
+                background: transparent;
+                padding-left: 0;
+                padding-right: 0;
+
+                .van-field__control {
+                    color: var(--white);
+                    text-align: center;
+                    padding: 0;
+                }
+            }
+        }
+    }
+    .phone-code-active {
+        ::v-deep .van-cell {
+            .van-icon {
+                color: var(--white);
+            }
+        }
+    }
+
+    .phone-divider {
+        width: 1px;
+        height: 20px;
+        background: #333;
+        margin: 0 12px;
+        transition: all 0.3s ease;
+    }
+
+    .phone-number {
+        flex: 1;
+        ::v-deep {
+            .van-field {
+                background: transparent;
+                padding-left: 10px;
+
+                .van-field__control {
+                    color: #fff;
+                    text-align: left;
+                    padding: 0;
+
+                    &::placeholder {
+                        color: #666;
+                        text-align: left;
+                    }
+                }
+            }
+        }
+    }
+}
+
+@keyframes slideDown {
+    from {
+        opacity: 0;
+        transform: translateY(-10px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+@keyframes fadeIn {
+    from {
+        opacity: 0;
+    }
+    to {
+        opacity: 0.2;
+    }
+}
+
+::v-deep {
+    .van-picker {
+        background: #181818;
+
+        .van-picker__toolbar {
+            background: #181818;
+            border-bottom: 1px solid #333;
+            height: 44px;
+            padding: 0 16px;
+
+            .van-picker__title {
+                color: #fff;
+                font-size: var(--font-size-16);
+                font-weight: 500;
+            }
+
+            .van-picker__cancel,
+            .van-picker__confirm {
+                color: var(--main-yellow);
+                font-size: var(--font-size-14);
+                padding: 0 8px;
+                height: 28px;
+                line-height: 28px;
+                border-radius: 14px;
+                transition: all 0.3s ease;
+
+                &:active {
+                    opacity: 0.8;
+                    background: rgba(255, 193, 7, 0.1);
+                }
+            }
+        }
+
+        .van-picker-column {
+            color: #fff;
+
+            .van-picker-column__item {
+                color: #fff;
+                font-size: var(--font-size-14);
+                padding: 0 16px;
+                height: 44px;
+                line-height: 44px;
+                transition: all 0.3s ease;
+
+                &--selected {
+                    color: var(--main-yellow);
+                    font-weight: 500;
+                    font-size: var(--font-size-16);
+                }
+
+                &:active {
+                    background: rgba(255, 255, 255, 0.05);
+                }
+            }
+
+            .van-picker-column__wrapper {
+                &::after {
+                    border-color: #333;
+                }
+            }
+        }
+
+        .van-picker__mask {
+            background-image: linear-gradient(180deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4)),
+                linear-gradient(0deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4));
+        }
+
+        .van-picker__indicator {
+            height: 44px;
+            background: rgba(255, 193, 7, 0.05);
+            border-top: 1px solid rgba(255, 193, 7, 0.1);
+            border-bottom: 1px solid rgba(255, 193, 7, 0.1);
+        }
+    }
+}
+</style>

+ 215 - 145
src/views/kyc.vue

@@ -1,155 +1,206 @@
 <template>
 <template>
     <div class="page kyc-page">
     <div class="page kyc-page">
-        <div class="apply-card-steps">
-            <div class="steps-top">
-                <div class="step" :class="{ active: currentStep >= 1 }">
-                    <div class="step-circle">1</div>
-                    <div class="step-label">{{ t('kyc.step1') }}</div>
+        <template v-if="type == '1'">
+            <div class="apply-card-steps">
+                <div class="steps-top">
+                    <div class="step" :class="{ active: currentStep >= 1 }">
+                        <div class="step-circle">1</div>
+                        <div class="step-label">{{ t('kyc.step1') }}</div>
+                    </div>
+                    <div class="step-dash" :class="{ active: currentStep >= 2 }"></div>
+                    <div class="step" :class="{ active: currentStep >= 2 }">
+                        <div class="step-circle">2</div>
+                        <div class="step-label">{{ t('kyc.step2') }}</div>
+                    </div>
+                    <div class="step-dash" :class="{ active: currentStep >= 3 }"></div>
+                    <div class="step" :class="{ active: currentStep >= 3 }">
+                        <div class="step-circle">3</div>
+                        <div class="step-label">{{ t('kyc.step3') }}</div>
+                    </div>
                 </div>
                 </div>
-                <div class="step-dash" :class="{ active: currentStep >= 2 }"></div>
-                <div class="step" :class="{ active: currentStep >= 2 }">
-                    <div class="step-circle">2</div>
-                    <div class="step-label">{{ t('kyc.step2') }}</div>
+            </div>
+            <div class="kyc-form" v-show="currentStep === 1">
+                <div class="form-item">
+                    <van-field
+                        v-model="form.firstName"
+                        :placeholder="t('kyc.firstName')"
+                        :rules="[{ required: true, message: t('kyc.firstName') }]"
+                        autocomplete="off"
+                    />
                 </div>
                 </div>
-                <div class="step-dash" :class="{ active: currentStep >= 3 }"></div>
-                <div class="step" :class="{ active: currentStep >= 3 }">
-                    <div class="step-circle">3</div>
-                    <div class="step-label">{{ t('kyc.step3') }}</div>
+                <div class="form-item">
+                    <van-field
+                        v-model="form.lastName"
+                        :placeholder="t('kyc.lastName')"
+                        :rules="[{ required: true, message: t('kyc.lastName') }]"
+                        autocomplete="off"
+                    />
                 </div>
                 </div>
-            </div>
-        </div>
-        <div class="kyc-form" v-show="currentStep === 1">
-            <div class="form-item">
-                <van-field
-                    v-model="form.firstName"
-                    :placeholder="t('kyc.firstName')"
-                    :rules="[{ required: true, message: t('kyc.firstName') }]"
-                    autocomplete="off"
-                />
-            </div>
-            <div class="form-item">
-                <van-field
-                    v-model="form.lastName"
-                    :placeholder="t('kyc.lastName')"
-                    :rules="[{ required: true, message: t('kyc.lastName') }]"
-                    autocomplete="off"
-                />
-            </div>
-            <div class="form-item">
-                <van-field
-                    v-model="form.address"
-                    :placeholder="t('kyc.address')"
-                    :rules="[{ required: true, message: t('kyc.address') }]"
-                    autocomplete="off"
-                />
-            </div>
-            <div class="form-item">
-                <van-field
-                    v-model="form.email"
-                    :placeholder="t('kyc.email')"
-                    :rules="[{ required: true, message: t('kyc.email') }]"
-                    autocomplete="off"
-                />
-            </div>
-            <div class="form-item phone-input">
-                <div :class="{ 'phone-code': true, 'phone-code-active': form.phoneCode }">
+                <div class="form-item">
                     <van-field
                     <van-field
-                        is-link
-                        readonly
-                        @click="showPicker = true"
-                        v-model="form.phoneCode"
-                        :placeholder="t('kyc.phoneCode')"
-                        :rules="[{ required: true, message: t('kyc.phoneCode') }]"
+                        v-model="form.address"
+                        :placeholder="t('kyc.address')"
+                        :rules="[{ required: true, message: t('kyc.address') }]"
                         autocomplete="off"
                         autocomplete="off"
                     />
                     />
-                    <van-popup v-model:show="showPicker" destroy-on-close round position="bottom">
-                        <van-picker
-                            v-model="form.phoneCode1"
-                            :visible-option-num="8"
-                            show-toolbar
-                            title="选择区号"
-                            :columns="[phoneCodes]"
-                            @cancel="showPicker = false"
-                            @confirm="onConfirm"
-                        />
-                    </van-popup>
                 </div>
                 </div>
-                <div class="phone-divider"></div>
-                <div class="phone-number">
+                <div class="form-item">
                     <van-field
                     <van-field
-                        v-model="form.phone"
-                        :placeholder="t('kyc.phone')"
-                        :rules="[{ required: true, message: t('kyc.phone') }]"
+                        v-model="form.email"
+                        :placeholder="t('kyc.email')"
+                        :rules="[{ required: true, message: t('kyc.email') }]"
                         autocomplete="off"
                         autocomplete="off"
                     />
                     />
                 </div>
                 </div>
+                <div class="form-item phone-input">
+                    <div :class="{ 'phone-code': true, 'phone-code-active': form.phoneCode }">
+                        <van-field
+                            is-link
+                            readonly
+                            @click="showPicker = true"
+                            v-model="form.phoneCode"
+                            :placeholder="t('kyc.phoneCode')"
+                            :rules="[{ required: true, message: t('kyc.phoneCode') }]"
+                            autocomplete="off"
+                        />
+                        <van-popup v-model:show="showPicker" destroy-on-close round position="bottom">
+                            <van-picker
+                                v-model="form.phoneCode1"
+                                :visible-option-num="8"
+                                show-toolbar
+                                :title="t('kyc.country')"
+                                :columns="[phoneCodes]"
+                                :cancel-button-text="t('common.cancel')"
+                                :confirm-button-text="t('common.confirm')"
+                                @cancel="showPicker = false"
+                                @confirm="onConfirm"
+                            />
+                        </van-popup>
+                    </div>
+                    <div class="phone-divider"></div>
+                    <div class="phone-number">
+                        <van-field
+                            v-model="form.phone"
+                            :placeholder="t('kyc.phone')"
+                            :rules="[{ required: true, message: t('kyc.phone') }]"
+                            autocomplete="off"
+                        />
+                    </div>
+                </div>
+                <div class="kyc-button">
+                    <van-button type="primary" block @click="handleNext">{{ t('kyc.next') }}</van-button>
+                </div>
             </div>
             </div>
-            <div class="kyc-button">
-                <van-button type="primary" block @click="handleNext">{{ t('kyc.next') }}</van-button>
-            </div>
-        </div>
-        <div class="kyc-form" v-show="currentStep === 2">
-            <div class="form-item">
-                <van-field
-                    v-model="form.amount"
-                    type="number"
-                    :placeholder="t('kyc.amount')"
-                    :rules="[{ required: true, message: t('kyc.amount') }]"
-                    autocomplete="off"
-                />
-            </div>
-            <div class="form-item">
-                <van-field
-                    v-model="form.currency"
-                    :placeholder="t('kyc.currency')"
-                    :rules="[{ required: true, message: t('kyc.currency') }]"
-                    autocomplete="off"
-                />
+            <div class="kyc-form" v-show="currentStep === 2">
+                <div class="form-item">
+                    <van-field
+                        v-model="form.amount"
+                        type="number"
+                        :placeholder="t('kyc.amount')"
+                        :rules="[{ required: true, message: t('kyc.amount') }]"
+                        autocomplete="off"
+                    />
+                </div>
+                <div class="form-item">
+                    <van-field
+                        v-model="form.currency"
+                        :placeholder="t('kyc.currency')"
+                        :rules="[{ required: true, message: t('kyc.currency') }]"
+                        autocomplete="off"
+                    />
+                </div>
+                <div class="kyc-button">
+                    <van-button type="primary" block @click="handleNext">{{ t('kyc.next') }}</van-button>
+                </div>
             </div>
             </div>
-            <div class="kyc-button">
-                <van-button type="primary" block @click="handleNext">{{ t('kyc.next') }}</van-button>
+            <div class="kyc-form" v-show="currentStep === 3">
+                <div class="form-item">
+                    <van-field
+                        v-model="form.shippingAddress"
+                        :placeholder="t('kyc.shippingAddress')"
+                        :rules="[{ required: true, message: t('kyc.shippingAddress') }]"
+                        autocomplete="off"
+                    />
+                </div>
+                <div class="form-item">
+                    <van-field
+                        v-model="form.postalCode"
+                        :placeholder="t('kyc.postalCode')"
+                        :rules="[{ required: true, message: t('kyc.postalCode') }]"
+                        autocomplete="off"
+                    />
+                </div>
+                <div class="kyc-button">
+                    <van-button type="primary" block @click="handleSubmit">{{ t('kyc.submit') }}</van-button>
+                </div>
             </div>
             </div>
-        </div>
-        <div class="kyc-form" v-show="currentStep === 3">
-            <div class="form-item">
-                <van-field
-                    v-model="form.shippingAddress"
-                    :placeholder="t('kyc.shippingAddress')"
-                    :rules="[{ required: true, message: t('kyc.shippingAddress') }]"
-                    autocomplete="off"
+        </template>
+        <template v-else>
+            <div class="kyc-form">
+                <remit-input
+                    v-model:value="kycForm.type"
+                    type="select"
+                    fkey="type"
+                    :label="t('kyc.type')"
+                    :rules="[{ required: true, message: t('kyc.type') }]"
+                    @change="handleChange"
+                    :columns="typeOptions"
                 />
                 />
-            </div>
-            <div class="form-item">
-                <van-field
-                    v-model="form.postalCode"
-                    :placeholder="t('kyc.postalCode')"
-                    :rules="[{ required: true, message: t('kyc.postalCode') }]"
-                    autocomplete="off"
+                <remit-input
+                    v-model:value="kycForm.data"
+                    type="upload"
+                    fkey="data"
+                    :label="t('kyc.data')"
+                    :rules="[{ required: true, message: t('kyc.data') }]"
+                    @change="handleChange"
+                    :columns="typeOptions"
                 />
                 />
+
+                <div class="kyc-button">
+                    <van-button type="primary" block @click="handleNext1">{{ t('kyc.submitKyc') }}</van-button>
+                </div>
             </div>
             </div>
-            <div class="kyc-button">
-                <van-button type="primary" block @click="handleSubmit">{{ t('kyc.submit') }}</van-button>
-            </div>
-        </div>
+        </template>
     </div>
     </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref } from 'vue'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import { showToast } from 'vant'
 import { showToast } from 'vant'
-
+import { ucardApi } from '@/api/ucard'
+import useUserStore from '@/stores/use-user-store'
+const userStore = useUserStore()
+const userInfo = computed(() => userStore.userInfo)
+const route = useRoute()
 const { t } = useI18n()
 const { t } = useI18n()
 const router = useRouter()
 const router = useRouter()
+const typeOptions = ref([
+    { text: t('kyc.i1'), value: '1' },
+    { text: t('kyc.i2'), value: '2' },
+])
+const kycForm = ref({
+    type: '',
+    data: '',
+    idNo: '',
+    idNoType: '',
+    idNoCountry: '',
+    idNoExpireDate: '',
+    idNoIssueDate: '',
+    idNoIssuePlace: '',
+    firstName: '',
+    lastName: '',
+    address: '',
+    email: '',
+    phone: '',
+    phoneCode: '',
+    phoneCode1: [],
+    uniqueId: '',
+})
+const { cardTypeId, type } = route.query as { cardTypeId: string; type: string }
+const formData = ref<typeof kycForm.value>({ cardTypeId: cardTypeId, type: type } as any)
 const currentStep = ref(1)
 const currentStep = ref(1)
 
 
-const showPicker = ref(false)
-const onConfirm = (value: any) => {
-    form.value.phoneCode = value.selectedValues[0]
-    showPicker.value = false
-}
-
 const form = ref({
 const form = ref({
     // 第一步
     // 第一步
     firstName: '',
     firstName: '',
@@ -168,28 +219,7 @@ const form = ref({
 })
 })
 
 
 // 区号选项
 // 区号选项
-const phoneCodes = [
-    { text: '中国 +86', value: '+86' },
-    { text: '美国 +1', value: '+1' },
-    { text: '英国 +44', value: '+44' },
-    { text: '日本 +81', value: '+81' },
-    { text: '韩国 +82', value: '+82' },
-    { text: '新加坡 +65', value: '+65' },
-    { text: '澳大利亚 +61', value: '+61' },
-    { text: '香港 +852', value: '+852' },
-    { text: '澳门 +853', value: '+853' },
-    { text: '台湾 +886', value: '+886' },
-    { text: '柬埔寨 +855', value: '+855' },
-    { text: '老挝 +856', value: '+856' },
-    { text: '缅甸 +95', value: '+95' },
-    { text: '泰国 +66', value: '+66' },
-    { text: '越南 +84', value: '+84' },
-    { text: '马来西亚 +60', value: '+60' },
-    { text: '菲律宾 +63', value: '+63' },
-    { text: '印度尼西亚 +62', value: '+62' },
-    { text: '印度 +91', value: '+91' },
-    { text: '巴基斯坦 +92', value: '+92' },
-]
+const phoneCodes = ref([])
 
 
 // 验证规则
 // 验证规则
 const validations = {
 const validations = {
@@ -211,6 +241,47 @@ const validations = {
     },
     },
 }
 }
 
 
+const showPicker = ref(false)
+const getCountry = async () => {
+    try {
+        const res = await ucardApi.countryGet()
+        phoneCodes.value = res.data.map((item: { code: string; name: string }) => ({
+            text: item.name + ' +' + item.code,
+            value: item.code,
+        }))
+    } catch (error) {
+        showToast(error || String(error))
+    }
+}
+const handleNext1 = async () => {
+    if (formData.value.type && formData.value.data) {
+        formData.value.uniqueId = userInfo.value?.customInfo?.uniqueId
+        const res = await ucardApi.kycUpload(formData.value as any)
+        if (res.code === 200) {
+            kycSubmit()
+        }
+    }
+}
+const kycSubmit = async () => {
+    const res = await ucardApi.kycSubmit(formData.value as any)
+    if (res.code === 200) {
+        // router.push('/kyc/success')
+        showToast(t('kyc.kycSuccess'))
+    }
+}
+onMounted(() => {
+    getCountry()
+})
+const handleChange = (value: any) => {
+    formData.value = { ...formData.value, [value.key]: value.value }
+}
+
+const onConfirm = (value: any) => {
+    form.value.phoneCode = value.selectedValues[0]
+    showPicker.value = false
+}
+
+
 function validateStep(step: number): boolean {
 function validateStep(step: number): boolean {
     switch (step) {
     switch (step) {
         case 1:
         case 1:
@@ -268,7 +339,6 @@ function handleSubmit() {
     }
     }
 
 
     // 提交所有表单数据
     // 提交所有表单数据
-    console.log('提交表单数据:', form.value)
     router.push('/kyc/success')
     router.push('/kyc/success')
 }
 }
 </script>
 </script>

+ 1 - 1
src/views/language.vue

@@ -11,7 +11,7 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import { ref } from 'vue'
 import { ref } from 'vue'
-import { localesList } from '@/plugin/i18n'
+import { localesList } from '@/i18n'
 import { lang } from '@/composables/config'
 import { lang } from '@/composables/config'
 const { locale, t } = useI18n()
 const { locale, t } = useI18n()
 const currentLang = ref(lang.value || locale.value)
 const currentLang = ref(lang.value || locale.value)

+ 94 - 41
src/views/login.vue

@@ -1,5 +1,8 @@
 <template>
 <template>
     <div class="page">
     <div class="page">
+        <div class="language-switch" @click="handleLanguageSwitch">
+            <i class="i-mdi-web"></i>
+        </div>
         <div class="login-container">
         <div class="login-container">
             <div class="login-header">
             <div class="login-header">
                 <h2>欢迎登录</h2>
                 <h2>欢迎登录</h2>
@@ -8,7 +11,7 @@
 
 
             <div class="login-form">
             <div class="login-form">
                 <div class="form-item">
                 <div class="form-item">
-                    <van-field v-model="loginName" placeholder="请输入账号" :rules="[{ required: true, message: '请输入账号' }]">
+                    <van-field v-model="loginName" autocomplete="off" placeholder="请输入账号" :rules="[{ required: true, message: '请输入账号' }]">
                         <template #left-icon>
                         <template #left-icon>
                             <van-icon name="user-o" />
                             <van-icon name="user-o" />
                         </template>
                         </template>
@@ -16,10 +19,13 @@
                 </div>
                 </div>
 
 
                 <div class="form-item">
                 <div class="form-item">
-                    <van-field v-model="password" type="password" placeholder="请输入密码" :rules="[{ required: true, message: '请输入密码' }]">
+                    <van-field v-model="password" :type="showPassword ? 'text' : 'password'" autocomplete="off" placeholder="请输入密码" :rules="[{ required: true, message: '请输入密码' }]">
                         <template #left-icon>
                         <template #left-icon>
                             <van-icon name="lock" />
                             <van-icon name="lock" />
                         </template>
                         </template>
+                        <template #right-icon>
+                            <van-icon name="eye-o" @click="showPassword = !showPassword" />
+                        </template>
                     </van-field>
                     </van-field>
                 </div>
                 </div>
 
 
@@ -48,64 +54,50 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-defineOptions({ name: 'LoginRouter' })
-import { ref, onMounted } from 'vue'
-import { showToast, showLoadingToast } from 'vant'
+import { showToast } from 'vant'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
 import { userApi } from '@/api/user'
 import { userApi } from '@/api/user'
 import useUserStore from '@/stores/use-user-store'
 import useUserStore from '@/stores/use-user-store'
-import ls from 'store2'
 import { userToken } from '@/composables/config'
 import { userToken } from '@/composables/config'
 const router = useRouter()
 const router = useRouter()
 const userStore = useUserStore()
 const userStore = useUserStore()
-const loginName = ref('admin1')
-const password = ref('Cwg123456')
+const loginName = ref('')
+const password = ref('')
 const rememberPassword = ref(false)
 const rememberPassword = ref(false)
 const loading = ref(false)
 const loading = ref(false)
-onMounted(() => {
-    const savedCredentials = ls.get('savedCredentials')
-    if (savedCredentials) {
-        loginName.value = savedCredentials.loginName
-        password.value = savedCredentials.password
-        rememberPassword.value = true
-    }
-})
-
+const showPassword = ref(false)
 const handleLogin = async () => {
 const handleLogin = async () => {
     if (!loginName.value || !password.value) {
     if (!loginName.value || !password.value) {
         showToast('请输入账号和密码')
         showToast('请输入账号和密码')
         return
         return
     }
     }
-    loading.value = true
-    const loadingToast = showLoadingToast({
-        message: '登录中...',
-        forbidClick: true,
-    })
+    if (rememberPassword.value) {
+        userStore.saveAccountInfo({
+            loginName: loginName.value,
+            password: password.value,
+            rememberPassword: true,
+        })
+    }else {
+        userStore.saveAccountInfo({
+            loginName: '',
+            password: '',
+            rememberPassword: false,
+        })
+    }
     try {
     try {
         const res = await userApi.login({
         const res = await userApi.login({
             loginName: loginName.value,
             loginName: loginName.value,
-            password: password.value,
-            emailCode: '12',
+            password: password.value
         })
         })
-        userToken.value = res.data
-        await new Promise((resolve) => setTimeout(resolve, 100))
-        if (rememberPassword.value) {
-            ls.set('savedCredentials', {
-                loginName: loginName.value,
-                password: password.value,
-            })
+        if (res.code === 200) {
+            userToken.value = res.data
+            getUserInfo()
         } else {
         } else {
-            ls.remove('savedCredentials')
+            showToast(res.msg || '登录失败')
         }
         }
-        showToast('登录成功')
-        getUserInfo()
-    } catch (error: any) {
-        showToast(error.message || '登录失败')
-    } finally {
-        loading.value = false
-        loadingToast.close()
-    }
+    } catch (error) {}
 }
 }
+
 const getUserInfo = async () => {
 const getUserInfo = async () => {
     if (!userToken.value) {
     if (!userToken.value) {
         showToast('请先登录')
         showToast('请先登录')
@@ -114,7 +106,16 @@ const getUserInfo = async () => {
     try {
     try {
         const res = await userApi.getUserInfo()
         const res = await userApi.getUserInfo()
         userStore.saveUserInfo(res.data)
         userStore.saveUserInfo(res.data)
-        router.push('/')
+        if (res.code === 200) {
+            if(res.data.customInfo.uniqueId){
+                router.push('/')
+            }else {
+                router.push('/improve/info')
+            }
+            showToast('登录成功')
+        } else {
+            showToast(res.msg || '登录失败')
+        }
     } catch (error: any) {
     } catch (error: any) {
         showToast(error.message || '登录失败')
         showToast(error.message || '登录失败')
     } finally {
     } finally {
@@ -125,10 +126,49 @@ const getUserInfo = async () => {
 const handleForgotPassword = () => {
 const handleForgotPassword = () => {
     router.push('/reset/password')
     router.push('/reset/password')
 }
 }
+const handleLanguageSwitch = () => {
+    router.push('/language')
+}
+
+onMounted(() => {
+    const accountInfo = userStore.accountInfo
+    if (accountInfo?.rememberPassword) {
+        loginName.value = accountInfo?.loginName || ''
+        password.value = accountInfo?.password || ''
+        rememberPassword.value = accountInfo?.rememberPassword || false
+    }
+})
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
 ::v-deep {
 ::v-deep {
+    .language-switch {
+        width: 40px;
+        height: 40px;
+        position: fixed;
+        top: 4px;
+        right: 10px;
+        cursor: pointer;
+        color: var(--main-yellow);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        z-index: 100;
+        i {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 20px;
+            height: 20px;
+            font-size: 14px;
+            color: var(--main-yellow);
+        }
+
+        &:hover {
+            opacity: 0.8;
+        }
+    }
+
     .login-container {
     .login-container {
         padding: 40px 0;
         padding: 40px 0;
     }
     }
@@ -291,4 +331,17 @@ const handleForgotPassword = () => {
         }
         }
     }
     }
 }
 }
+
+.code-dialog {
+    padding: 20px;
+
+    .van-field {
+        margin-bottom: 20px;
+    }
+}
+::v-deep .van-checkbox__icon--checked .van-icon-success {
+    border-color: var(--main-yellow) !important;
+    background: var(--main-yellow) !important;
+    color: var(--black) !important;
+}
 </style>
 </style>

+ 43 - 9
src/views/mine.vue

@@ -3,8 +3,13 @@
         <div class="user-card">
         <div class="user-card">
             <div class="avatar"></div>
             <div class="avatar"></div>
             <div class="user-info">
             <div class="user-info">
-                <div class="hello">{{ userInfo?.name }}</div>
-                <div class="phone">{{ userInfo?.email }}</div>
+                <div class="hello" v-if="lang === 'en'">
+                    {{ userInfo?.customInfo?.nameEn }}
+                </div>
+                <div class="hello" v-else>
+                    {{ userInfo?.customInfo?.lastName }} {{ userInfo?.customInfo?.firstName }}
+                </div>
+                <div class="phone">{{ userInfo?.customInfo?.email }}</div>
             </div>
             </div>
         </div>
         </div>
         <div class="group">
         <div class="group">
@@ -22,7 +27,7 @@
                 <i class="i-mdi-lock-reset"></i>
                 <i class="i-mdi-lock-reset"></i>
                 <span>{{ t('language.i2') }}</span>
                 <span>{{ t('language.i2') }}</span>
             </div>
             </div>
-            <div class="group-item">
+            <div class="group-item" @click="$router.push('/improve/info')">
                 <i class="i-mdi-shield-lock-outline"></i>
                 <i class="i-mdi-shield-lock-outline"></i>
                 <span>{{ t('language.i3') }}</span>
                 <span>{{ t('language.i3') }}</span>
             </div>
             </div>
@@ -35,25 +40,48 @@
             </div>
             </div>
         </div>
         </div>
         <div class="group">
         <div class="group">
-            <div class="group-item" @click="handleLogout">
+            <div class="group-item" @click="showLogoutPopup = true">
                 <i class="i-mdi-logout"></i>
                 <i class="i-mdi-logout"></i>
                 <span>{{ t('language.i6') }}</span>
                 <span>{{ t('language.i6') }}</span>
             </div>
             </div>
         </div>
         </div>
+
+        <van-dialog
+            v-model:show="showLogoutPopup"
+            :title="t('mine.logout')"
+            show-cancel-button
+            :before-close="handleLogout"
+            :close-on-click-overlay="false"
+        >
+        </van-dialog>
     </div>
     </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
-const { t } = useI18n()
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
-const router = useRouter()
+import { userApi } from '@/api/user'
+import { lang } from '@/composables/config'
 import useUserStore from '@/stores/use-user-store'
 import useUserStore from '@/stores/use-user-store'
+const router = useRouter()
+const { t } = useI18n()
 const userStore = useUserStore()
 const userStore = useUserStore()
 const userInfo = userStore.userInfo
 const userInfo = userStore.userInfo
-const handleLogout = () => {
-    userStore.clearUserInfo()
-    router.push('/login')
+const showLogoutPopup = ref(false)
+
+const handleLogout = async (action: string) => {
+    if (action === 'confirm') {
+        try {
+            const res = await userApi.logout()
+            if (res.code === 200) {
+                userStore.clearUserInfo()
+                router.push('/login')
+            }
+        } catch (error) {
+            showToast(error.message || '退出登录失败')
+        }
+    }
+    showLogoutPopup.value = false
 }
 }
 </script>
 </script>
 
 
@@ -115,4 +143,10 @@ i{
     color: var(--main-yellow);
     color: var(--main-yellow);
     font-size: 15px;
     font-size: 15px;
 }
 }
+.code-dialog {
+    display: flex;
+    justify-content: center;
+    gap: 12px;
+    padding: 16px;
+}
 </style>
 </style>

+ 14 - 90
src/views/reset-password.vue

@@ -1,15 +1,11 @@
 <template>
 <template>
     <div class="page">
     <div class="page">
         <div class="reset-container">
         <div class="reset-container">
-            <div class="reset-header">
-                <h2>重置密码</h2>
-                <p>请输入您的邮箱和验证码</p>
-            </div>
-
             <div class="reset-form">
             <div class="reset-form">
                 <div class="form-item">
                 <div class="form-item">
                     <van-field
                     <van-field
                         v-model="form.email"
                         v-model="form.email"
+                        autocomplete="off"
                         placeholder="请输入邮箱"
                         placeholder="请输入邮箱"
                         :rules="[
                         :rules="[
                             { required: true, message: '请输入邮箱' },
                             { required: true, message: '请输入邮箱' },
@@ -21,120 +17,48 @@
                         </template>
                         </template>
                     </van-field>
                     </van-field>
                 </div>
                 </div>
-
-                <div class="form-item">
-                    <van-field v-model="form.code" placeholder="请输入验证码" :rules="[{ required: true, message: '请输入验证码' }]">
-                        <template #left-icon>
-                            <van-icon name="shield-o" />
-                        </template>
-                        <template #button>
-                            <van-button size="small" type="primary" :disabled="!!countdown" @click="handleSendCode">
-                                {{ countdown ? `${countdown}s后重试` : '获取验证码' }}
-                            </van-button>
-                        </template>
-                    </van-field>
-                </div>
-
-                <div class="form-item">
-                    <van-field
-                        v-model="form.newPassword"
-                        type="password"
-                        placeholder="请输入新密码"
-                        :rules="[
-                            { required: true, message: '请输入新密码' },
-                            { pattern: config.Pattern.Password, message: '密码必须包含大小写字母和数字,长度8-16位' },
-                        ]"
-                    >
-                        <template #left-icon>
-                            <van-icon name="lock" />
-                        </template>
-                    </van-field>
-                </div>
-
                 <div class="reset-button">
                 <div class="reset-button">
-                    <van-button type="primary" block :loading="loading" @click="handleReset"> 重置密码 </van-button>
+                    <van-button type="primary" block :loading="loading" @click="handleReset"> {{ t('reset-password.reset') }} </van-button>
+                </div>
+                <div class="login-link">
+                    {{ t('reset-password.remember') }}<span @click="handleLogin">{{ t('reset-password.login') }}</span>
                 </div>
                 </div>
-
-                <div class="login-link">记起密码了?<span @click="handleLogin">返回登录</span></div>
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-defineOptions({
-    name: 'ResetPasswordRouter',
-})
-
-import { ref } from 'vue'
-import { showToast, showLoadingToast } from 'vant'
+import { showToast } from 'vant'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
 import { userApi } from '@/api/user'
 import { userApi } from '@/api/user'
 import config from '@/config'
 import config from '@/config'
-
+const { t } = useI18n()
 const router = useRouter()
 const router = useRouter()
 const loading = ref(false)
 const loading = ref(false)
-const countdown = ref(0)
-
-const form = ref({
-    email: '',
-    code: '',
-    newPassword: '',
-})
-
-const startCountdown = () => {
-    countdown.value = 60
-    const timer = setInterval(() => {
-        countdown.value--
-        if (countdown.value <= 0) {
-            clearInterval(timer)
-        }
-    }, 1000)
-}
-
-const handleSendCode = async () => {
+const form = ref({ email: '' })
+const handleReset = async () => {
     if (!form.value.email) {
     if (!form.value.email) {
-        showToast('请输入邮箱')
+        showToast(t('reset-password.emailRequired'))
         return
         return
     }
     }
-
     if (!config.Pattern.Email.test(form.value.email)) {
     if (!config.Pattern.Email.test(form.value.email)) {
-        showToast('请输入正确的邮箱格式')
-        return
-    }
-
-    try {
-        await userApi.sendVerifyCode(form.value.email)
-        showToast('验证码已发送')
-        startCountdown()
-    } catch (error: any) {
-        showToast(error.message || '发送失败')
-    }
-}
-
-const handleReset = async () => {
-    if (!form.value.email || !form.value.code || !form.value.newPassword) {
-        showToast('请填写完整信息')
+        showToast(t('reset-password.emailError'))
         return
         return
     }
     }
 
 
     loading.value = true
     loading.value = true
-    const loadingToast = showLoadingToast({
-        message: '重置中...',
-        forbidClick: true,
-    })
     try {
     try {
-        await userApi.resetPassword(form.value)
-        showToast('密码重置成功')
+        const res = await userApi.updatePassword(form.value)
+        showToast(res.msg)
         router.push('/login')
         router.push('/login')
     } catch (error: any) {
     } catch (error: any) {
         showToast(error.message || '重置失败')
         showToast(error.message || '重置失败')
     } finally {
     } finally {
         loading.value = false
         loading.value = false
-        loadingToast.close()
     }
     }
 }
 }
-
 const handleLogin = () => {
 const handleLogin = () => {
     router.push('/login')
     router.push('/login')
 }
 }

+ 64 - 62
src/views/select-card.vue

@@ -3,18 +3,38 @@
         <div class="select-card-header">{{ t('select-card.selectCard') }}</div>
         <div class="select-card-header">{{ t('select-card.selectCard') }}</div>
         <div class="select-card-desc">{{ t('select-card.desc') }}</div>
         <div class="select-card-desc">{{ t('select-card.desc') }}</div>
         <div class="cards-container">
         <div class="cards-container">
-            <van-swipe class="cards-wrapper" :show-indicators="false" :loop="false" :duration="300">
+            <van-swipe class="cards-wrapper" :show-indicators="false" :loop="false" :duration="300" @change="(index) => (currentCardIndex = index)">
                 <van-swipe-item v-for="(card, index) in cards" :key="index" class="card-box">
                 <van-swipe-item v-for="(card, index) in cards" :key="index" class="card-box">
                     <div class="card-visual">
                     <div class="card-visual">
                         <div class="card-chip"></div>
                         <div class="card-chip"></div>
                     </div>
                     </div>
                     <div class="card-info">
                     <div class="card-info">
-                        <div class="card-title">{{ card.title }}</div>
-                        <div class="card-content">{{ card.content }}</div>
+                        <div class="card-title">{{ card.cardName }}</div>
+                        <div class="card-content">{{ card.cardDesc }}</div>
                         <ul class="card-list">
                         <ul class="card-list">
-                            <li v-for="(item, key) in card.details" :key="key">
-                                <span>{{ t(`select-card.${key}`) }}</span>
-                                <span>{{ item }}</span>
+                            <li>
+                                <span class="label">{{ t(`select-card.kycRequire`) }}</span>
+                                <span>{{ card.kycRequire ? t('select-card.kycRequiredDesc') : t('select-card.kycRequiredDesc2') }}</span>
+                            </li>
+                            <li>
+                                <span class="label">{{ t('select-card.currency') }}</span>
+                                <span>{{ card.rechargeCurrencyInfoList[0].currency }}</span>
+                            </li>
+                            <li>
+                                <span class="label">{{ t('select-card.needPhotoForActiveCard') }}</span>
+                                <span>{{
+                                    card.needPhotoForActiveCard
+                                        ? t('select-card.needPhotoForActiveCardDesc')
+                                        : t('select-card.needPhotoForActiveCardDesc2')
+                                }}</span>
+                            </li>
+                            <li>
+                                <span class="label">{{ t('select-card.needPhotoForOperateCard') }}</span>
+                                <span>{{ card.kycRequire ? t('select-card.kycRequiredDesc') : t('select-card.kycRequiredDesc2') }}</span>
+                            </li>
+                            <li>
+                                <span class="label">{{ t('select-card.status') }}</span>
+                                <span>{{ card.status ? t('select-card.statusDesc') : t('select-card.statusDesc2') }}</span>
                             </li>
                             </li>
                         </ul>
                         </ul>
                     </div>
                     </div>
@@ -27,69 +47,46 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
-
+import { ucardApi, CardType } from '@/api/ucard'
 const router = useRouter()
 const router = useRouter()
 const { t } = useI18n()
 const { t } = useI18n()
+const cards = ref<CardType[]>([])
+const currentCardIndex = ref(0)
 
 
-const cards = [
-    {
-        title: t('select-card.masterCard'),
-        content: t('select-card.masterCardDesc'),
-        details: {
-            price: '99 USDT',
-            kycRequired: t('select-card.kycRequiredDesc'),
-            rechargeFee: '1.8%',
-            atmFee: t('select-card.atmFeeDesc'),
-            spendFee: t('select-card.spendFeeDesc'),
-            annualFee: '12 EUR',
-            usageArea: 'Worldwide',
-        },
-    },
-    {
-        title: t('select-card.visaVirtualCard'),
-        content: t('select-card.visaVirtualCardDesc'),
-        details: {
-            price: '29 USDT',
-            kycRequired: t('select-card.kycRequiredDesc2'),
-            rechargeFee: '2%',
-            atmFee: '0%',
-            spendFee: '0%',
-            annualFee: t('select-card.annualFeeDesc'),
-            usageArea: 'Worldwide',
-        },
-    },
-    {
-        title: t('select-card.visaPhysicalCard'),
-        content: t('select-card.visaPhysicalCardDesc'),
-        details: {
-            price: '239 USDT',
-            kycRequired: t('select-card.kycRequiredDesc'),
-            rechargeFee: '2.99%',
-            atmFee: t('select-card.atmFeeDesc'),
-            spendFee: t('select-card.spendFeeDesc'),
-            annualFee: t('select-card.annualFeeDesc'),
-            usageArea: 'Worldwide',
-        },
-    },
-    {
-        title: t('select-card.visaVirtualCard2'),
-        content: t('select-card.visaVirtualCard2Desc'),
-        details: {
-            price: '10 USDT',
-            kycRequired: t('select-card.kycRequiredDesc2'),
-            rechargeFee: '2%',
-            atmFee: '2%',
-            spendFee: '2%',
-            annualFee: t('select-card.annualFeeDesc'),
-            usageArea: 'Worldwide',
-        },
-    },
-]
+const getCardTypes = async () => {
+    try {
+        const res = await ucardApi.cardKycTypesList({
+            page: {
+                current: 1,
+                row: 100,
+            },
+        })
+        if (res.code === 200 && res.data) {
+            let a = res.data.filter((i) => {
+                return i.kycStatus == null
+            })
+            cards.value = a
+        }
+    } catch (error) {
+        showToast(error || String(error))
+    }
+}
 
 
 const handleSubmit = () => {
 const handleSubmit = () => {
-    router.push('/kyc')
+    const selectedCard = cards.value[currentCardIndex.value]
+    router.push({
+        path: '/kyc',
+        query: {
+            cardTypeId: selectedCard.cardTypeId,
+            type: selectedCard.type,
+        },
+    })
 }
 }
+onMounted(() => {
+    getCardTypes()
+})
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
@@ -246,6 +243,11 @@ const handleSubmit = () => {
     font-weight: bold;
     font-weight: bold;
 }
 }
 
 
+.label {
+    width: 160px;
+    line-height: 1.5;
+}
+
 .card-list li:hover {
 .card-list li:hover {
     background: rgba(73, 247, 166, 0.1);
     background: rgba(73, 247, 166, 0.1);
     border-radius: 6px;
     border-radius: 6px;

+ 310 - 0
src/views/transfer-detail.vue

@@ -0,0 +1,310 @@
+<template>
+    <div class="page">
+        <div class="amount-section">
+            <span class="amount">{{ detail?.amount }}</span>
+            <span class="unit">{{ detail?.currency }}</span>
+        </div>
+        <div class="progress-section">
+            <div class="steps-vertical">
+                <div class="step" :class="{ active: step >= 1 }">
+                    <div class="step-circle">{{ step >= 1 ? '✓' : '1' }}</div>
+                    <div class="step-label">{{ t('transfer-detail.step1') }}</div>
+                    <div class="step-time" v-if="step >= 1">{{ detail?.time }}</div>
+                </div>
+                <div class="step-dash" :class="{ active: step >= 2 }"></div>
+                <div class="step" :class="{ active: step >= 2 }">
+                    <div class="step-circle">{{ step >= 2 ? '✓' : '2' }}</div>
+                    <div class="step-label">{{ t('transfer-detail.step2') }}</div>
+                </div>
+                <div class="step-dash" :class="{ active: step >= 3 }"></div>
+                <div class="step" :class="{ active: step >= 3 }">
+                    <div class="step-circle">{{ step >= 3 ? '✓' : '3' }}</div>
+                    <div class="step-label">{{ getStatus(detail?.status) }}</div>
+                </div>
+            </div>
+        </div>
+        <div class="info-list">
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.orderNo') }}</span>
+                <span class="info-item-value">{{ detail?.orderNo }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.amount') }}</span>
+                <span class="info-item-value">{{ detail?.amount }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.currency') }}</span>
+                <span class="info-item-value">{{ detail?.currency || '--' }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.relationship') }}</span>
+                <span class="info-item-value">{{ relationshipOptions.find((item) => item.value === detail?.relationship)?.text || '--' }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.sourceFunds') }}</span>
+                <span class="info-item-value">{{ sourceFundsOptions.find((item) => item.value === detail?.sourceFunds)?.text || '--' }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.mobile') }}</span>
+                <span class="info-item-value">{{ detail?.mobile || '--' }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.email') }}</span>
+                <span class="info-item-value">{{ detail?.email || '--' }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.country') }}</span>
+                <span class="info-item-value">{{ detail?.country || '--' }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.status') }}</span>
+                <span class="info-item-value">{{ getStatus(detail?.status,true) }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.addTime') }}</span>
+                <span class="info-item-value">{{ detail?.addTime || '--' }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.payPurpose') }}</span>
+                <span class="info-item-value">{{ payPurposeOptions.find((item) => item.value === detail?.payPurpose)?.text || '--' }}</span>
+            </div>
+            <div class="info-item">
+                <span class="info-item-label">{{ t('transfer-detail.postscript') }}</span>
+                <span class="info-item-value">{{ detail?.postscript || '--' }}</span>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+import { useRoute } from 'vue-router'
+import { ucardApi,TransferInfo } from '@/api/ucard'
+const { t } = useI18n()
+const route = useRoute()
+const { id } = route.query as { id: string }
+const detail = ref<TransferInfo | null>(null)
+const step = ref(3)
+const payPurposeOptions = ref([
+    { text: t('eur-remit.item42'), value: 'PURCHASE_OF_GOODS_OR_SERVICES' },
+    { text: t('eur-remit.item43'), value: 'FREIGHT_AND_TRANSPORTATION_COSTS' },
+    { text: t('eur-remit.item44'), value: 'EDUCATION_EXPENSES' },
+    { text: t('eur-remit.item45'), value: 'IMMIGRATION_INVESTMENT' },
+    { text: t('eur-remit.item46'), value: 'CHARITABLE_DONATIONS' },
+    { text: t('eur-remit.item47'), value: 'FAMILY_SUPPORT' },
+    { text: t('eur-remit.item48'), value: 'DIVIDEND_OR_INTEREST_PAYMENTS' },
+    { text: t('eur-remit.item49'), value: 'INTERNATIONAL_TRADE' },
+])
+const getTransferDetail = async () => {
+    const res = await ucardApi.ucardTransferSingle({ id })
+    res.data.currency = 'EUR'
+    switch (res.data.status) {
+        case 'B1':
+            step.value = 1
+            break
+        case 'B2':
+            step.value = 2
+            break
+        case 'B3':
+            step.value = 3
+            break
+        case 'B4':
+            step.value = 3
+            break
+        case 'B6':
+            step.value = 3
+            break
+        case 'B11':
+            step.value = 6
+            break
+        case 'B12':
+            step.value = 7
+            break
+        case 'B13':
+            step.value = 8
+            break
+        case 'B15':
+            step.value = 9
+            break
+    }
+    detail.value = res.data
+}
+const getStatus = (status: string,isShowStep:boolean = false) => {
+    if (isShowStep) {
+        return t('transfer-detail.' + status)
+    }
+    if (step.value >= 3) {
+        switch (status) {
+            case 'B1':
+                return t('transfer-detail.B1')
+            case 'B2':
+                return t('transfer-detail.B2')
+            case 'B3':
+                return t('transfer-detail.B3')
+            case 'B4':
+                return t('transfer-detail.B4')
+            case 'B6':
+                return t('transfer-detail.B6')
+            case 'B11':
+                return t('transfer-detail.B11')
+            case 'B12':
+                return t('transfer-detail.B12')
+            case 'B13':
+                return t('transfer-detail.B13')
+            case 'B15':
+                return t('transfer-detail.B15')
+            default:
+                return '--'
+        }
+    } else {
+        return t('transfer-detail.step3')
+    }
+}
+const sourceFundsOptions = ref([
+    { text: t('eur-remit.item34'), value: 'SAVINGS' },
+    { text: t('eur-remit.item35'), value: 'SALARY' },
+    { text: t('eur-remit.item36'), value: 'INVESTMENT' },
+    { text: t('eur-remit.item37'), value: 'OTHER' },
+])
+const relationshipOptions = ref([
+    { text: t('eur-remit.item26'), value: 'SELF' },
+    { text: t('eur-remit.item21'), value: 'HUSBAND' },
+    { text: t('eur-remit.item31'), value: 'WIFE' },
+    { text: t('eur-remit.item16'), value: 'FATHER' },
+    { text: t('eur-remit.item22'), value: 'MOTHER' },
+    { text: t('eur-remit.item29'), value: 'SON' },
+    { text: t('eur-remit.item15'), value: 'DAUGHTER' },
+    { text: t('eur-remit.item27'), value: 'SISTER' },
+    { text: t('eur-remit.item12'), value: 'BROTHER' },
+    { text: t('eur-remit.item17'), value: 'FATHER_IN_LAW' },
+    { text: t('eur-remit.item23'), value: 'MOTHER_IN_LAW' },
+    { text: t('eur-remit.item28'), value: 'SISTER_IN_LAW' },
+    { text: t('eur-remit.item13'), value: 'BROTHER_IN_LAW' },
+    { text: t('eur-remit.item19'), value: 'GRAND_FATHER' },
+    { text: t('eur-remit.item20'), value: 'GRAND_MOTHER' },
+    { text: t('eur-remit.item24'), value: 'NEPHEW' },
+    { text: t('eur-remit.item25'), value: 'NIECE' },
+    { text: t('eur-remit.item30'), value: 'UNCLE' },
+    { text: t('eur-remit.item14'), value: 'COUSIN' },
+    { text: t('eur-remit.item18'), value: 'FRIEND' },
+    { text: t('eur-remit.item32'), value: 'OTHER' },
+])
+onMounted(() => {
+    getTransferDetail()
+})
+</script>
+
+<style scoped lang="scss">
+.header {
+    display: flex;
+    align-items: center;
+    padding: 20px 0 10px 0;
+}
+.back-btn {
+    font-size: 22px;
+    cursor: pointer;
+    margin-right: 10px;
+}
+.active {
+    .step-circle {
+        background: var(--main-yellow);
+        color: var(--black);
+    }
+    .step-label {
+        color: var(--main-yellow);
+    }
+}
+.title {
+    font-size: 20px;
+    font-weight: bold;
+}
+.amount-section {
+    text-align: center;
+    margin: 20px 0;
+}
+.amount {
+    font-size: 48px;
+    color: #eaff00;
+    font-weight: bold;
+}
+.unit {
+    font-size: 18px;
+    margin-left: 8px;
+}
+.progress-section {
+    margin: 30px auto;
+    max-width: 600px;
+    padding: 0 20px;
+}
+.steps-vertical {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+.step {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    color: #888;
+    position: relative;
+}
+.step.active {
+    color: #eaff00;
+}
+.step-circle {
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
+    background: #222;
+    color: #eaff00;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-weight: bold;
+    margin-bottom: 8px;
+}
+.step-label {
+    font-size: 14px;
+    margin-bottom: 4px;
+}
+.step-time {
+    font-size: 12px;
+    color: #666;
+}
+.step-dash {
+    width: 2px;
+    height: 20px;
+    background: #333;
+    margin: 4px 0;
+}
+.step-dash.active {
+    background: #eaff00;
+}
+.info-list {
+    background: #232323;
+    border-radius: 12px;
+    padding: 18px 16px;
+    margin-top: 20px;
+}
+.info-item-label {
+    color: #888;
+}
+.info-item-value {
+    color: #fff;
+}
+.info-item {
+    display: flex;
+    justify-content: space-between;
+    padding: 8px 0;
+    border-bottom: 1px solid #333;
+    font-size: 15px;
+    color: #fff;
+}
+.info-item:last-child {
+    border-bottom: none;
+}
+.mono {
+    font-family: monospace;
+    word-break: break-all;
+}
+</style>

+ 5 - 0
vite.config.build.ts

@@ -25,6 +25,10 @@ const config: { server: ServerOptions, build: BuildOptions } = {
                     if (id.includes('__uno.css')) {
                     if (id.includes('__uno.css')) {
                         return 'unocss'
                         return 'unocss'
                     }
                     }
+                    // 将i18n相关文件打包到单独的chunk
+                    if (id.includes('i18n/locales')) {
+                        return 'i18n'
+                    }
                 },
                 },
             },
             },
             external: /\.\/static.*/,
             external: /\.\/static.*/,
@@ -32,6 +36,7 @@ const config: { server: ServerOptions, build: BuildOptions } = {
     },
     },
     server: {
     server: {
         port: 7771,
         port: 7771,
+        host: true,
         proxy: {
         proxy: {
             '/api': {
             '/api': {
                 target: 'https://php.mmxiaowu.com',
                 target: 'https://php.mmxiaowu.com',