diff --git a/package.json b/package.json index c867733..f748ab9 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "@ant-design/nextjs-registry": "^1.0.2", "@ant-design/pro-components": "^2.8.2", "antd": "^5.22.6", + "axios": "^1.7.9", + "jotai": "^2.11.0", "next": "15.1.2", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7deff8..1aed473 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,12 @@ importers: antd: specifier: ^5.22.6 version: 5.22.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + axios: + specifier: ^1.7.9 + version: 1.7.9 + jotai: + specifier: ^2.11.0 + version: 2.11.0(@types/react@19.0.2)(react@19.0.0) next: specifier: 15.1.2 version: 15.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -822,6 +828,9 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -830,6 +839,9 @@ packages: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -922,6 +934,10 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -1011,6 +1027,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1265,6 +1285,15 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -1272,6 +1301,10 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1531,6 +1564,18 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jotai@2.11.0: + resolution: {integrity: sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1923,6 +1968,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -3603,12 +3651,22 @@ snapshots: ast-types-flow@0.0.8: {} + asynckit@0.4.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 axe-core@4.10.2: {} + axios@1.7.9: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.1.0: {} balanced-match@1.0.2: {} @@ -3711,6 +3769,10 @@ snapshots: color-string: 1.9.1 optional: true + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@2.20.3: {} commander@4.1.1: {} @@ -3794,6 +3856,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + dequal@2.0.3: {} detect-libc@2.0.3: @@ -4184,6 +4248,8 @@ snapshots: flatted@3.3.2: {} + follow-redirects@1.15.9: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -4193,6 +4259,12 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true @@ -4459,6 +4531,11 @@ snapshots: jiti@1.21.7: {} + jotai@2.11.0(@types/react@19.0.2)(react@19.0.0): + optionalDependencies: + '@types/react': 19.0.2 + react: 19.0.0 + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -4818,6 +4895,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-from-env@1.1.0: {} + prr@1.0.1: optional: true diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..33e5273 --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,105 @@ +import axios, { AxiosInstance, AxiosRequestConfig } from "axios"; + +/** + * API客户端类 + * 封装了基于axios的HTTP请求方法,提供统一的接口调用方式 + */ +export class ApiClient { + private client: AxiosInstance; + + // constructor(baseURL: string = process.env.NEXT_PUBLIC_API_BASE_URL || 'https://api.example.com') { + constructor(baseURL: string = " http://192.168.31.65:6600") { + this.client = axios.create({ + baseURL, + timeout: 10000, // 请求超时时间:10秒 + headers: { + "Content-Type": "application/json", + }, + }); + + // 请求拦截器 + this.client.interceptors.request.use( + (config) => { + // 在这里添加认证token + const token = localStorage.getItem("token"); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + }, + ); + + // 响应拦截器 + this.client.interceptors.response.use( + (response) => response, + (error) => { + // 在这里处理常见错误 + if (error.response?.status === 401) { + // 处理未授权访问 + console.error("未授权访问"); + } + return Promise.reject(error); + }, + ); + } + + /** + * 发送GET请求 + * @param url 请求地址 + * @param config 请求配置 + * @returns 返回请求数据 + */ + async get(url: string, config?: AxiosRequestConfig): Promise { + const response = await this.client.get(url, config); + return response.data; + } + + /** + * 发送POST请求 + * @param url 请求地址 + * @param data 请求数据 + * @param config 请求配置 + * @returns 返回请求数据 + */ + async post( + url: string, + data?: any, + config?: AxiosRequestConfig, + ): Promise { + const response = await this.client.post(url, data, config); + return response.data; + } + + /** + * 发送PUT请求 + * @param url 请求地址 + * @param data 请求数据 + * @param config 请求配置 + * @returns 返回请求数据 + */ + async put( + url: string, + data?: any, + config?: AxiosRequestConfig, + ): Promise { + const response = await this.client.put(url, data, config); + return response.data; + } + + /** + * 发送DELETE请求 + * @param url 请求地址 + * @param config 请求配置 + * @returns 返回请求数据 + */ + async delete(url: string, config?: AxiosRequestConfig): Promise { + const response = await this.client.delete(url, config); + return response.data; + } +} + +// 导出API客户端实例 +export const apiClient = new ApiClient(); diff --git a/src/api/services/index.ts b/src/api/services/index.ts new file mode 100644 index 0000000..192bc1f --- /dev/null +++ b/src/api/services/index.ts @@ -0,0 +1,7 @@ +/** + * 服务模块导出文件 + * 统一导出所有服务类,方便其他模块引用 + */ + +export * from './userService'; +export * from './transactionService'; diff --git a/src/api/services/transactionService.ts b/src/api/services/transactionService.ts new file mode 100644 index 0000000..16a4a1d --- /dev/null +++ b/src/api/services/transactionService.ts @@ -0,0 +1,44 @@ +import { apiClient } from '../client'; +import { ApiResponse, Transaction, CreateTransactionRequest } from '../types'; + +/** + * 交易服务类 - 处理所有与交易相关的API请求 + */ +export class TransactionService { + // 交易API的基础路径 + private static readonly BASE_PATH = '/transactions'; + + /** + * 创建新交易 + * @param data 创建交易所需的数据 + * @returns 返回创建的交易信息 + */ + static async createTransaction(data: CreateTransactionRequest): Promise> { + return apiClient.post>(this.BASE_PATH, data); + } + + /** + * 获取指定ID的交易详情 + * @param id 交易ID + * @returns 返回交易详细信息 + */ + static async getTransaction(id: string): Promise> { + return apiClient.get>(`${this.BASE_PATH}/${id}`); + } + + /** + * 获取交易列表 + * @param params 查询参数对象 + * @param params.page 页码 + * @param params.limit 每页数量 + * @param params.status 交易状态过滤 + * @returns 返回交易列表数据 + */ + static async listTransactions(params?: { + page?: number; + limit?: number; + status?: Transaction['status']; + }): Promise> { + return apiClient.get>(this.BASE_PATH, { params }); + } +} diff --git a/src/api/services/userService.ts b/src/api/services/userService.ts new file mode 100644 index 0000000..1ee78b5 --- /dev/null +++ b/src/api/services/userService.ts @@ -0,0 +1,58 @@ +import { apiClient } from '../client'; +import { ApiResponse, LoginRequest, LoginResponse, User } from '../types'; + +/** + * 用户服务类 - 处理所有与用户相关的API请求 + */ +export class UserService { + // 用户API的基础路径 + private static readonly BASE_PATH = '/users'; + + /** + * 用户登录 + * @param credentials 登录凭证,包含邮箱和密码 + * @returns 返回登录响应,包含用户token和用户信息 + */ + static async login(credentials: LoginRequest): Promise> { + return apiClient.post>(`${this.BASE_PATH}/login`, credentials); + } + + /** + * 获取当前登录用户信息 + * @returns 返回当前用户的详细信息 + */ + static async getCurrentUser(): Promise> { + return apiClient.get>(`${this.BASE_PATH}/me`); + } + + /** + * 更新用户资料 + * @param userId 用户ID + * @param data 需要更新的用户数据 + * @returns 返回更新后的用户信息 + */ + static async updateProfile(userId: string, data: Partial): Promise> { + return apiClient.put>(`${this.BASE_PATH}/${userId}`, data); + } +} + +// 使用示例: +/* +try { + // 尝试用户登录 + const response = await UserService.login({ + email: 'user@example.com', + password: 'password123' + }); + + if (response.success) { + const { token, user } = response.data; + // 将token存储在localStorage中 + localStorage.setItem('token', token); + // 处理用户数据 + console.log('登录用户:', user); + } +} catch (error) { + console.error('登录失败:', error); +} +*/ diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..1f35ce4 --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,39 @@ +// Common API response wrapper +export interface ApiResponse { + success: boolean; + data: T; + message?: string; +} + +// User related types +export interface User { + id: string; + username: string; + email: string; + createdAt: string; +} + +export interface LoginRequest { + email: string; + password: string; +} + +export interface LoginResponse { + token: string; + user: User; +} + +// Transaction related types +export interface Transaction { + id: string; + amount: number; + currency: string; + status: 'pending' | 'completed' | 'failed'; + timestamp: string; +} + +export interface CreateTransactionRequest { + amount: number; + currency: string; + recipientAddress: string; +} diff --git a/src/app/home/assets/components/AssetListCard.tsx b/src/app/home/assets/components/AssetListCard.tsx index cc52e76..49ab456 100644 --- a/src/app/home/assets/components/AssetListCard.tsx +++ b/src/app/home/assets/components/AssetListCard.tsx @@ -63,6 +63,7 @@ const AssetListCard: React.FC = () => { return ( 资产列表 diff --git a/src/app/home/assets/components/ResourceCard.tsx b/src/app/home/assets/components/ResourceCard.tsx index 837c70a..06fd78d 100644 --- a/src/app/home/assets/components/ResourceCard.tsx +++ b/src/app/home/assets/components/ResourceCard.tsx @@ -6,6 +6,7 @@ import Link from "antd/lib/typography/Link"; const ResourceCard: React.FC = () => { return ( { return ( 最新转账 @@ -18,22 +19,24 @@ const TransferCard: React.FC = () => { } hoverable > - -
- - 隐藏 <$0.5 通证 - - - -
- - - -
+
+ +
+ + 隐藏 <$0.5 通证 + + + +
+ + + +
+
); }; diff --git a/src/app/home/assets/components/VoteCard.tsx b/src/app/home/assets/components/VoteCard.tsx index 1c98ef2..0f8998f 100644 --- a/src/app/home/assets/components/VoteCard.tsx +++ b/src/app/home/assets/components/VoteCard.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Card, Progress, Row, Space, Tooltip, Typography } from "antd"; +import { Button, Card, Progress, Row, Space, Tooltip, Typography } from "antd"; import { AppstoreOutlined, QuestionCircleOutlined } from "@ant-design/icons"; import Link from "antd/lib/typography/Link"; @@ -8,6 +8,7 @@ const { Title } = Typography; const VoteCard: React.FC = () => { return ( { } hoverable > - -
- 投票权 - - - -
-
0.0
-
- - -
投票权APY
-
0.0%
-
- -
投票权奖励
-
0TRX
-
- - - - +
+ +
+ 投票权 + + + +
+
0.0
+
+ + +
投票权APY
+
0.0%
+
+ +
投票权奖励
+
0TRX
+
+ + + + +
); }; diff --git a/src/app/home/assets/components/WalletCard.tsx b/src/app/home/assets/components/WalletCard.tsx index e0b231d..dd9de09 100644 --- a/src/app/home/assets/components/WalletCard.tsx +++ b/src/app/home/assets/components/WalletCard.tsx @@ -1,28 +1,150 @@ -import React from "react"; -import { Card, Space, Typography } from "antd"; -import { WalletOutlined } from "@ant-design/icons"; +import React, { useState } from "react"; +import { + Card, + Space, + Typography, + Modal, + Input, + Select, + Button, + QRCode, + message, +} from "antd"; +import { + WalletOutlined, + QrcodeOutlined, + TransactionOutlined, +} from "@ant-design/icons"; const { Title } = Typography; +const { TextArea } = Input; const WalletCard: React.FC = () => { + const [isTransferModalOpen, setIsTransferModalOpen] = useState(false); + const [isReceiveModalOpen, setIsReceiveModalOpen] = useState(false); + const [receiverAddress, setReceiverAddress] = useState(""); + const [selectedToken, setSelectedToken] = useState("TRX"); + const [transferNote, setTransferNote] = useState(""); + + // 示例钱包地址 + const walletAddress = "TRx1234567890abcdefghijklmnopqrstuvwxyz"; + + const handleTransfer = () => { + if (!receiverAddress) { + message.error("请输入接收账户地址"); + return; + } + // 这里添加转账逻辑 + message.success("转账请求已提交"); + setIsTransferModalOpen(false); + // 重置表单 + setReceiverAddress(""); + setSelectedToken("TRX"); + setTransferNote(""); + }; + + const handleSaveQRCode = () => { + message.success("二维码已保存"); + }; + return ( - - 钱包资产 - - } - hoverable - > -
- 0.00 TRX -

总资产价值

-
- - - - -
+ <> + + 钱包资产 + + } + hoverable + style={{ minHeight: "240px" }} + > +
+ 0.00 TRX +

总资产价值

+
+ + + + +
+ + {/* 转账弹窗 */} + setIsTransferModalOpen(false)} + centered + > + +
+

接收账户

+ setReceiverAddress(e.target.value)} + /> +
+
+

选择通证

+