本节作者:@小符 这一讲我们来实现 Wtfswap 的 Swap 部分的 UI。 设计稿如下所示: ui Swap 页面是 Wtfswap 的核心页面,先来拆分下 Swap 页面,Swap 页面首先引入了 Layout 的头部,然后在页面中间实现了 Swap UI;搭建 Swap 页面需要多个组件, 主要使用 Card 组件完成整个框架的搭建。这一讲课完整的代码可以在这里获取。 修改 , 先引入 组件搭建基础框架: 然后新建 文件,初始化样式: 在 中使用样式: 这样就搭建好了基础的 Swap 页面布局,然后在此布局上进行细化。 swap1 接下来使用其他组件完善页面,选择器使用 Antd Design Web3 的 TokenSelect 组件,然后搭配 , , , 等组件。
本节作者:@小符
这一讲我们来实现 Wtfswap 的 Swap 部分的 UI。
设计稿如下所示:

Swap 页面是 Wtfswap 的核心页面,先来拆分下 Swap 页面,Swap 页面首先引入了 Layout 的头部,然后在页面中间实现了 Swap UI;搭建 Swap 页面需要多个组件, 主要使用 Card 组件完成整个框架的搭建。这一讲课完整的代码可以在这里获取。
修改 pages/wtfswap/index.tsx , 先引入 Card 组件搭建基础框架:
+ import { Card } from 'antd'; import WtfLayout from "@/components/WtfLayout"; export default function Wtfswap() { - return <WtfLayout>Wtfswap</WtfLayout>; + return ( + <WtfLayout> + <Card title="Swap"> + <Card></Card> + <Card></Card> + </Card> + </WtfLayout> + ); }
然后新建 pages/wtfswap/swap.module.css 文件,初始化样式:
.swapCard { width: 600px; margin: 0 auto; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
在 index.tsx 中使用样式:
import { Card } from 'antd'; import WtfLayout from "@/components/WtfLayout"; + import styles from './swap.module.css'; export default function Wtfswap() { return ( <WtfLayout> - <Card title="Swap"> + <Card title="Swap" className={styles.swapCard}> <Card></Card> <Card></Card> </Card> </WtfLayout> ); }
这样就搭建好了基础的 Swap 页面布局,然后在此布局上进行细化。

接下来使用其他组件完善页面,选择器使用 Antd Design Web3 的 TokenSelect 组件,然后搭配 Button,Space,Input,Typography 等组件。
- import { Card } from 'antd'; + import { Card, Input, Button, Space, Typography } from 'antd'; + import { TokenSelect } from '@ant-design/web3'; + import { SwapOutlined } from '@ant-design/icons'; import WtfLayout from "@/components/WtfLayout"; import styles from './swap.module.css'; + const { Text } = Typography; export default function Wtfswap() { return ( <WtfLayout> <Card title="Swap" className={styles.swapCard}> <Card> + <Input + variant="borderless" + type="number" + addonAfter={ + <TokenSelect /> + } + /> + <Space> + <Text type="secondary"> + $ 0.0 + </Text> + <Space> + <Text type="secondary"> + Balance: 0 + </Text> + <Button size="small" type="link"> + Max + </Button> + </Space> + </Space> </Card> + <Space> + <Button + shape="circle" + icon={<SwapOutlined />} + /> + </Space> <Card> + <Input + variant="borderless" + type="number" + addonAfter={ + <TokenSelect /> + } + /> + <Space> + <Text type="secondary"> + $ 0.0 + </Text> + <Text type="secondary"> + Balance: 0 + </Text> + </Space> </Card> + <Button type="primary" size="large" block> + Swap + </Button> </Card> </WtfLayout> ); }

现在的 Swap 页面已经初具雏形了,接下来定义可以选择的代币列表和 Swap 的 TokenA 和 TokenB,可以从 @ant-design/web3-assets/tokens 引入需要使用的 Token:
+ import React, { useState } from 'react'; import { Card, Input, Button, Space, Typography } from 'antd'; - import { TokenSelect } from '@ant-design/web3'; + import { TokenSelect, type Token } from '@ant-design/web3'; + import { ETH, USDT, USDC } from '@ant-design/web3-assets/tokens'; import { SwapOutlined } from '@ant-design/icons'; // ... export default function Wtfswap() { + const [tokenA, setTokenA] = useState<Token>(ETH); + const [tokenB, setTokenB] = useState<Token>(USDT); + const [amountA, setAmountA] = useState(0); + const [amountB, setAmountB] = useState(0); + const [optionsA, setOptionsA] = useState<Token[]>([ETH, USDT, USDC]); + const [optionsB, setOptionsB] = useState<Token[]>([USDT, ETH, USDC]); // ... <Input variant="borderless" + value={amountA} type="number" addonAfter={ - <TokenSelect /> + <TokenSelect value={tokenA} onChange={setTokenA} options={optionsA} /> } /> // ... <Input variant="borderless" + value={amountB} type="number" addonAfter={ - <TokenSelect /> + <TokenSelect value={tokenB} onChange={setTokenB} options={optionsB} /> } /> // ...

现在,为 Input 组件和 Button 组件添加函数:
AmountA。其他的功能在后续章节实现。TokenA:TokenB 代币对,以及对应的值 AmountA:AmountB。// ... + const handleAmountAChange = (e: any) => { + setAmountA(parseFloat(e.target.value)); + // 后续章节实现 + }; + + const handleSwitch = () => { + setTokenA(tokenB); + setTokenB(tokenA); + setAmountA(amountB); + setAmountB(amountA); + }; // ... <Input variant="borderless" value={amountA} type="number" + onChange={(e) => handleAmountAChange(e)} addonAfter={ <TokenSelect value={tokenA} onChange={setTokenA} options={optionsA} /> } /> // ... <Space className={styles.switchBtn}> <Button shape="circle" icon={<SwapOutlined />} + onClick={handleSwitch} /> </Space> // ...
然后为 Max 按钮添加函数 handleMax,UI 阶段先不实现功能。
// ... + const handleMax = () => { + }; // ... - <Button size="small" type="link"> + <Button size="small" onClick={handleMax} type="link"> Max </Button> // ...
以上基本实现了 Swap 页面,接下来优化下样式,使其匹配设计稿的内容,编辑 pages/wtfswap/swap.module.css 文件:
.swapCard { width: 600px; margin: 0 auto; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } + .switchBtn { + font-size: '20px'; + display: flex; + justify-content: center; + margin: 16px 0; + } + + .swapSpace { + width: 99%; + justify-content: space-between; + } + + .swapBtn { + margin-top: 10px; + }
在 index.tsx 引入新添加的样式:
import React, { useState } from 'react'; import { Card, Input, Button, Space, Typography } from 'antd'; import { TokenSelect, type Token } from '@ant-design/web3'; import { ETH, USDT, USDC } from '@ant-design/web3-assets/tokens'; import { SwapOutlined } from '@ant-design/icons'; import WtfLayout from "@/components/WtfLayout"; import styles from "./swap.module.css"; const { Text } = Typography; export default function Wtfswap() { const [tokenA, setTokenA] = useState<Token>(ETH); const [tokenB, setTokenB] = useState<Token>(USDT); const [amountA, setAmountA] = useState(0); const [amountB, setAmountB] = useState(0); const [optionsA, setOptionsA] = useState<Token[]>([ETH, USDT, USDC]);; const [optionsB, setOptionsB] = useState<Token[]>([USDT, ETH, USDC]);; const handleAmountAChange = (e: any) => { setAmountA(parseFloat(e.target.value)); }; const handleSwitch = () => { setTokenA(tokenB); setTokenB(tokenA); setAmountA(amountB); setAmountB(amountA); }; const handleMax = () => { }; return ( <WtfLayout> <Card title="Swap" className={styles.swapCard}> <Card> <Input variant="borderless" value={amountA} type="number" onChange={(e) => handleAmountAChange(e)} addonAfter={ <TokenSelect value={tokenA} onChange={setTokenA} options={optionsA} /> } /> - <Space> + <Space className={styles.swapSpace}> <Text type="secondary"> $ 0.0 </Text> <Space> <Text type="secondary"> Balance: 0 </Text> <Button size="small" onClick={handleMax} type="link"> Max </Button> </Space> </Space> </Card> - <Space> + <Space className={styles.switchBtn}> <Button shape="circle" icon={<SwapOutlined />} onClick={handleSwitch} /> </Space> <Card> <Input variant="borderless" value={amountB} type="number" addonAfter={ <TokenSelect value={tokenB} onChange={setTokenB} options={optionsB} /> } /> - <Space> + <Space className={styles.swapSpace}> <Text type="secondary"> $ 0.0 </Text> <Text type="secondary"> Balance: 0 </Text> </Space> </Card> - <Button type="primary" size="large" block > + <Button type="primary" size="large" block className={styles.swapBtn}> Swap </Button> </Card> </WtfLayout> ); }

这样,我们就实现了 Swap 部分的 UI 开发,为了简化教程,有的细节并没有完全按照设计稿实现。页面样式使用了 CSSModules 实现,你也可以使用自己熟悉的方案。本节的要点主要是使用了 Antd Design Web3 提供的 TokenSelect 组件,方便我们快速集成 Token 到选择器中。