汇率转换功能实现
在本节中,我们将实现在线工具箱的汇率转换功能。主要包括以下功能点:
- 实时汇率查询
- 多币种支持
- 汇率计算器
- 历史汇率走势图
页面设计
页面布局
<script setup lang="ts">import { ref, computed } from 'vue'import type { Currency } from '~/types'
// 币种列表const currencies = ref<Currency[]>([ { code: 'CNY', name: '人民币' }, { code: 'USD', name: '美元' }, { code: 'EUR', name: '欧元' }, { code: 'JPY', name: '日元' }, { code: 'GBP', name: '英镑' }, // 更多币种...])
// 表单数据const form = ref({ amount: 100, fromCurrency: 'CNY', toCurrency: 'USD'})
// 转换结果const result = ref<number | null>(null)</script>
<template> <div class="container mx-auto px-4 py-8"> <h1 class="text-3xl font-bold mb-8">汇率转换</h1>
<!-- 转换表单 --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <!-- 金额输入 --> <div class="form-control"> <label class="label"> <span class="label-text">金额</span> </label> <input v-model="form.amount" type="number" class="input input-bordered" min="0" step="0.01" /> </div>
<!-- 源币种选择 --> <div class="form-control"> <label class="label"> <span class="label-text">从</span> </label> <select v-model="form.fromCurrency" class="select select-bordered" > <option v-for="currency in currencies" :key="currency.code" :value="currency.code" > {{ currency.name }} ({{ currency.code }}) </option> </select> </div>
<!-- 目标币种选择 --> <div class="form-control"> <label class="label"> <span class="label-text">到</span> </label> <select v-model="form.toCurrency" class="select select-bordered" > <option v-for="currency in currencies" :key="currency.code" :value="currency.code" > {{ currency.name }} ({{ currency.code }}) </option> </select> </div> </div>
<!-- 转换按钮 --> <div class="mt-6"> <button class="btn btn-primary" @click="handleConvert" > 转换 </button> </div>
<!-- 转换结果 --> <div v-if="result" class="mt-6"> <div class="text-xl"> {{ form.amount }} {{ form.fromCurrency }} = {{ result.toFixed(2) }} {{ form.toCurrency }} </div> </div> </div> </div>
<!-- 历史走势图 --> <div class="mt-8"> <h2 class="text-2xl font-bold mb-4">历史走势</h2> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <CurrencyChart :from-currency="form.fromCurrency" :to-currency="form.toCurrency" /> </div> </div> </div> </div></template>
类型定义
export interface Currency { code: string name: string}
export interface ExchangeRate { from: string to: string rate: number timestamp: number}
export interface HistoricalRate { date: string rate: number}
API 封装
import { ref } from 'vue'import type { ExchangeRate, HistoricalRate } from '~/types'
export function useCurrency() { const loading = ref(false) const error = ref<string | null>(null)
// 获取实时汇率 async function getExchangeRate( fromCurrency: string, toCurrency: string ): Promise<ExchangeRate | null> { loading.value = true error.value = null
try { // 调用聚合数据汇率 API const response = await fetch(`/api/currency/rate?from=${fromCurrency}&to=${toCurrency}`) const data = await response.json()
if (!data.success) { throw new Error(data.message) }
return { from: fromCurrency, to: toCurrency, rate: data.result.rate, timestamp: data.result.timestamp } } catch (e) { error.value = e.message return null } finally { loading.value = false } }
// 获取历史汇率数据 async function getHistoricalRates( fromCurrency: string, toCurrency: string, days: number = 30 ): Promise<HistoricalRate[]> { loading.value = true error.value = null
try { const response = await fetch( `/api/currency/history?from=${fromCurrency}&to=${toCurrency}&days=${days}` ) const data = await response.json()
if (!data.success) { throw new Error(data.message) }
return data.result.rates } catch (e) { error.value = e.message return [] } finally { loading.value = false } }
return { loading, error, getExchangeRate, getHistoricalRates }}
汇率图表组件
<script setup lang="ts">import { ref, onMounted, watch } from 'vue'import { Line } from 'vue-chartjs'import type { HistoricalRate } from '~/types'
const props = defineProps<{ fromCurrency: string toCurrency: string}>()
const { getHistoricalRates } = useCurrency()const chartData = ref<HistoricalRate[]>([])
async function loadChartData() { chartData.value = await getHistoricalRates( props.fromCurrency, props.toCurrency )}
// 监听币种变化,重新加载数据watch( [() => props.fromCurrency, () => props.toCurrency], () => loadChartData(), { immediate: true })
// 图表配置const chartConfig = computed(() => ({ type: 'line', data: { labels: chartData.value.map(item => item.date), datasets: [ { label: `${props.fromCurrency}/${props.toCurrency} 汇率`, data: chartData.value.map(item => item.rate), borderColor: '#3b82f6', tension: 0.1 } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } }, scales: { y: { beginAtZero: false } } }}))</script>
<template> <div class="h-[400px]"> <Line v-if="chartData.length > 0" :data="chartConfig.data" :options="chartConfig.options" /> <div v-else class="flex items-center justify-center h-full" > <span class="loading loading-spinner loading-lg"></span> </div> </div></template>
后端 API 实现
import { defineEventHandler } from 'h3'
export default defineEventHandler(async (event) => { const query = getQuery(event) const { from, to } = query
if (!from || !to) { return { success: false, message: '请提供源币种和目标币种' } }
try { // 调用聚合数据 API const response = await fetch( `http://apis.juhe.cn/fapig/currency/exchange?from=${from}&to=${to}&key=${process.env.JUHE_API_KEY}` ) const data = await response.json()
if (data.error_code !== 0) { throw new Error(data.reason) }
return { success: true, result: { rate: data.result.rate, timestamp: data.result.timestamp } } } catch (error) { return { success: false, message: error.message } }})
使用说明
- 安装依赖
# 安装 Chart.js 和 Vue Chart.jsnpm install chart.js vue-chartjs
- 环境变量配置
在项目根目录创建 .env
文件:
JUHE_API_KEY=你的聚合数据API密钥
- 功能测试
- 启动开发服务器:
npm run dev
- 访问
http://localhost:3000/tools/currency
- 输入金额,选择币种进行转换
- 查看历史汇率走势图
优化建议
-
缓存优化
- 使用 localStorage 缓存最近查询的汇率
- 设置合理的缓存过期时间
-
性能优化
- 使用防抖处理频繁的汇率查询
- 按需加载 Chart.js
-
用户体验
- 添加常用币种收藏功能
- 支持快捷键操作
- 添加汇率提醒功能
下一步
完成汇率转换功能后,我们将继续开发:
- 二维码生成工具
- 图片处理功能
- 更多实用工具