ProjectTonyStack项目-前端终止
This commit is contained in:
455
c-ProjectTonyStack/2-AI优化设计/1-页面/api_binance_account.md
Normal file
455
c-ProjectTonyStack/2-AI优化设计/1-页面/api_binance_account.md
Normal file
@@ -0,0 +1,455 @@
|
||||
**背景 (Context):**
|
||||
|
||||
你是一名资深前端架构师,严格遵循我们团队定义的 `vue3-typescript-stye.md` 开发规范。现在,你需要基于一份初步的产品需求 `mask_binance_account.md`,设计并实现一个功能完整、体验卓越、代码健壮的币安账户信息页面 (`BinanceAccountView.vue`)。
|
||||
|
||||
**核心指令 (Core Directive):**
|
||||
|
||||
请为 `BinanceAccountView.vue` 页面提供一个完整、高质量、可直接用于生产环境的代码实现方案。方案必须包含Pinia状态管理、API层封装、组件模板和逻辑脚本的全部细节,并严格遵循下述所有设计规范。
|
||||
|
||||
-----
|
||||
|
||||
### **第一部分:架构与状态管理设计 (Pinia Store)**
|
||||
|
||||
为了实现清晰的数据流和逻辑分离,我们将为该页面创建一个专属的Pinia Store:`@/store/binanceAccount.ts`。
|
||||
|
||||
**1. 类型定义 (Type Definitions):**
|
||||
|
||||
首先,在 `@/types/binance.d.ts` 中定义所有相关的数据结构,确保类型安全。
|
||||
|
||||
```typescript
|
||||
// @/types/binance.d.ts
|
||||
|
||||
// API标准响应体 (遵循规范)
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number;
|
||||
status: number;
|
||||
timestamp: string;
|
||||
data: T;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// 币种余额
|
||||
export interface Balance {
|
||||
asset: string;
|
||||
free: string;
|
||||
locked: string;
|
||||
}
|
||||
|
||||
// 费率
|
||||
export interface CommissionRates {
|
||||
maker: string;
|
||||
taker: string;
|
||||
buyer: string;
|
||||
seller: string;
|
||||
}
|
||||
|
||||
// 账户完整信息
|
||||
export interface AccountInfo {
|
||||
makerCommission: int64;
|
||||
takerCommission: int64;
|
||||
buyerCommission: int64;
|
||||
sellerCommission: int64;
|
||||
commissionRates: CommissionRates;
|
||||
canTrade: boolean;
|
||||
canWithdraw: boolean;
|
||||
canDeposit: boolean;
|
||||
updateTime: uint64;
|
||||
accountType: string;
|
||||
balances: Balance[];
|
||||
permissions: string[];
|
||||
uid: int64;
|
||||
}
|
||||
|
||||
// 币种价格
|
||||
export interface TickerPrice {
|
||||
symbol: string;
|
||||
price: string;
|
||||
}
|
||||
|
||||
// 资产详情表格行项目 (优化设计)
|
||||
// 将账户余额、实时价格和其他计算属性聚合到一个对象中,便于表格渲染
|
||||
export interface AssetDetail extends Balance {
|
||||
icon: string; // 币种图标 (通过组件动态获取)
|
||||
price: number;
|
||||
totalAmount: number; // free + locked
|
||||
totalValue: number; // price * totalAmount
|
||||
}
|
||||
```
|
||||
|
||||
**2. Pinia Store (`useBinanceAccountStore`):**
|
||||
|
||||
```typescript
|
||||
// @/store/binanceAccount.ts
|
||||
import { defineStore } from 'pinia';
|
||||
import { getAccountInfo, getSupportedSymbols, updateSupportedSymbols, getTickerPrices } from '@/api/modules/binance';
|
||||
import type { AccountInfo, AssetDetail, Balance, TickerPrice } from '@/types/binance';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
export const useBinanceAccountStore = defineStore('binanceAccount', () => {
|
||||
// === State ===
|
||||
const accountInfo = ref<AccountInfo | null>(null);
|
||||
const supportedSymbols = ref<string[]>([]);
|
||||
const assetPrices = ref<Map<string, number>>(new Map()); // 使用Map优化查询性能
|
||||
const isLoading = ref<boolean>(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
// === Getters (Computed) ===
|
||||
const assetDetails = computed<AssetDetail[]>(() => {
|
||||
if (!accountInfo.value?.balances) return [];
|
||||
|
||||
// 聚合余额、价格和计算属性
|
||||
return accountInfo.value.balances
|
||||
.map(balance => {
|
||||
const price = assetPrices.value.get(`${balance.asset}USDT`) ?? 0;
|
||||
const freeAmount = parseFloat(balance.free);
|
||||
const lockedAmount = parseFloat(balance.locked);
|
||||
const totalAmount = freeAmount + lockedAmount;
|
||||
|
||||
return {
|
||||
...balance,
|
||||
price,
|
||||
totalAmount,
|
||||
totalValue: totalAmount * price,
|
||||
};
|
||||
})
|
||||
.filter(asset => asset.totalAmount > 0); // 过滤掉数量为0的资产
|
||||
});
|
||||
|
||||
const totalAssetValue = computed<number>(() => {
|
||||
return assetDetails.value.reduce((sum, asset) => sum + asset.totalValue, 0);
|
||||
});
|
||||
|
||||
// === Actions ===
|
||||
async function fetchInitialData() {
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
// 并行获取账户信息和支持的交易对,提升加载速度
|
||||
const [info, symbols] = await Promise.all([
|
||||
getAccountInfo(),
|
||||
getSupportedSymbols(),
|
||||
]);
|
||||
accountInfo.value = info;
|
||||
supportedSymbols.value = symbols;
|
||||
|
||||
// 获取资产后,根据资产列表获取价格
|
||||
if (info.balances && info.balances.length > 0) {
|
||||
await fetchAllTickerPrices();
|
||||
}
|
||||
} catch (e: any) {
|
||||
error.value = e.message || '获取账户数据失败';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAllTickerPrices() {
|
||||
if (!accountInfo.value?.balances) return;
|
||||
const symbolsToFetch = accountInfo.value.balances.map(b => `${b.asset}USDT`);
|
||||
if (symbolsToFetch.length === 0) return;
|
||||
|
||||
const prices = await getTickerPrices(symbolsToFetch);
|
||||
const priceMap = new Map<string, number>();
|
||||
prices.forEach(p => priceMap.set(p.symbol, parseFloat(p.price)));
|
||||
assetPrices.value = priceMap;
|
||||
}
|
||||
|
||||
// 优化点:采用单一接口进行全量更新,保证数据事务性
|
||||
async function saveSupportedSymbols(symbols: string[]) {
|
||||
// 这里可以添加乐观更新的UI逻辑
|
||||
await updateSupportedSymbols(symbols);
|
||||
supportedSymbols.value = symbols; // 更新成功后同步状态
|
||||
}
|
||||
|
||||
return {
|
||||
accountInfo,
|
||||
supportedSymbols,
|
||||
isLoading,
|
||||
error,
|
||||
assetDetails,
|
||||
totalAssetValue,
|
||||
fetchInitialData,
|
||||
fetchAllTickerPrices,
|
||||
saveSupportedSymbols,
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### **第二部分:API层设计 (`@/api/modules/binance.ts`)**
|
||||
|
||||
所有API请求必须通过此模块进行封装,并提供完整的类型定义。
|
||||
|
||||
```typescript
|
||||
// @/api/modules/binance.ts
|
||||
import request from '@/api';
|
||||
import type { ApiResponse, AccountInfo, TickerPrice } from '@/types/binance';
|
||||
|
||||
// 获取账户信息
|
||||
export function getAccountInfo(): Promise<AccountInfo> {
|
||||
return request.get<ApiResponse<AccountInfo>>('/api/binance/account/info').then(res => res.data);
|
||||
}
|
||||
|
||||
// 获取账户支持的交易对
|
||||
export function getSupportedSymbols(): Promise<string[]> {
|
||||
return request.get<ApiResponse<string[]>>('/api/binance/account/symbols/get').then(res => res.data);
|
||||
}
|
||||
|
||||
// 优化设计:将 add 和 delete 合并为一个 update 接口,实现原子化操作
|
||||
// 后端需要提供一个接收字符串数组的全量更新接口
|
||||
export function updateSupportedSymbols(symbols: string[]): Promise<void> {
|
||||
return request.post<ApiResponse<void>>('/api/binance/account/symbols/update', { symbols }).then(res => res.data);
|
||||
}
|
||||
|
||||
// 获取指定交易对的实时价格
|
||||
export function getTickerPrices(symbols: string[]): Promise<TickerPrice[]> {
|
||||
return request.post<ApiResponse<TickerPrice[]>>('/api/binance/market/ticker/price', { symbols }).then(res => res.data);
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### **第三部分:页面组件实现 (`BinanceAccountView.vue`)**
|
||||
|
||||
这是最终呈现给用户的视图,需要包含完整的加载、错误、空状态处理,并提供流畅的交互体验。
|
||||
|
||||
#### **`<script setup lang="ts">`**
|
||||
|
||||
```typescript
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { useBinanceAccountStore } from '@/store/binanceAccount';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import CryptoIcon from '@/components/CryptoIcon.vue';
|
||||
import CurrencySelector from '@/components/CurrencySelector.vue'; // 假设该组件存在
|
||||
|
||||
// 1. 初始化Store和状态
|
||||
const binanceStore = useBinanceAccountStore();
|
||||
const {
|
||||
accountInfo,
|
||||
supportedSymbols,
|
||||
isLoading,
|
||||
error,
|
||||
assetDetails,
|
||||
totalAssetValue,
|
||||
} = storeToRefs(binanceStore);
|
||||
|
||||
// 2. 本地UI状态
|
||||
const isEditingSymbols = ref(false);
|
||||
const localSymbols = ref<string[]>([]); // 用于编辑时的临时状态,避免直接修改store
|
||||
const filterText = ref(''); // 资产表格筛选文本
|
||||
|
||||
// 3. 生命周期钩子
|
||||
onMounted(() => {
|
||||
binanceStore.fetchInitialData();
|
||||
});
|
||||
|
||||
// 4. 交互逻辑
|
||||
watch(supportedSymbols, (newVal) => {
|
||||
// 当store中的数据变化时(例如,初始加载),同步到本地
|
||||
localSymbols.value = [...newVal];
|
||||
});
|
||||
|
||||
function handleEditSymbols() {
|
||||
isEditingSymbols.value = true;
|
||||
localSymbols.value = [...supportedSymbols.value]; // 进入编辑模式,拷贝一份数据
|
||||
}
|
||||
|
||||
async function handleSaveSymbols() {
|
||||
await binanceStore.saveSupportedSymbols(localSymbols.value);
|
||||
isEditingSymbols.value = false;
|
||||
}
|
||||
|
||||
function handleCancelEdit() {
|
||||
isEditingSymbols.value = false;
|
||||
localSymbols.value = [...supportedSymbols.value]; // 取消编辑,恢复原始数据
|
||||
}
|
||||
|
||||
function addSymbol(symbol: string) {
|
||||
if (!localSymbols.value.includes(symbol)) {
|
||||
localSymbols.value.push(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
function removeSymbol(symbol: string) {
|
||||
localSymbols.value = localSymbols.value.filter(s => s !== symbol);
|
||||
}
|
||||
|
||||
// 5. 表格数据与表头
|
||||
const headers = [
|
||||
{ title: '', key: 'icon', sortable: false, width: '60px' },
|
||||
{ title: '名称', key: 'asset', align: 'start' },
|
||||
{ title: '实时价格 (USDT)', key: 'price', align: 'end' },
|
||||
{ title: '总数量', key: 'totalAmount', align: 'end' },
|
||||
{ title: '可使用数量', key: 'free', align: 'end' },
|
||||
{ title: '冻结数量', key: 'locked', align: 'end' },
|
||||
{ title: '币种总计 (USDT)', key: 'totalValue', align: 'end' },
|
||||
];
|
||||
|
||||
const filteredAssetDetails = computed(() => {
|
||||
if (!filterText.value) {
|
||||
return assetDetails.value;
|
||||
}
|
||||
const query = filterText.value.toUpperCase();
|
||||
return assetDetails.value.filter(asset => asset.asset.toUpperCase().includes(query));
|
||||
});
|
||||
|
||||
// 格式化函数
|
||||
const formatNumber = (value: number) => value.toLocaleString('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
```
|
||||
|
||||
#### **`<template>`**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="d-flex justify-end align-center">
|
||||
<span class="text-h6 font-weight-bold">欢迎,{{ accountInfo?.uid ?? '...' }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="isLoading">
|
||||
<v-col>
|
||||
<v-skeleton-loader type="card, table"></v-skeleton-loader>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-else-if="error">
|
||||
<v-col>
|
||||
<v-alert type="error" closable title="加载失败">{{ error }}</v-alert>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-else-if="accountInfo">
|
||||
<v-col cols="12" md="4">
|
||||
<v-card class="mb-4" title="账户信息">
|
||||
<v-list density="compact">
|
||||
<v-list-item title="账户UID" :subtitle="accountInfo.uid"></v-list-item>
|
||||
<v-list-item title="账户类型" :subtitle="accountInfo.accountType"></v-list-item>
|
||||
<v-list-item title="能否交易" :subtitle="accountInfo.canTrade ? '是' : '否'"></v-list-item>
|
||||
<v-list-item title="能否提款" :subtitle="accountInfo.canWithdraw ? '是' : '否'"></v-list-item>
|
||||
<v-list-item title="能否充值" :subtitle="accountInfo.canDeposit ? '是' : '否'"></v-list-item>
|
||||
<v-list-item title="Maker费率" :subtitle="accountInfo.commissionRates.maker"></v-list-item>
|
||||
<v-list-item title="Taker费率" :subtitle="accountInfo.commissionRates.taker"></v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
|
||||
<v-card>
|
||||
<v-card-title class="d-flex justify-space-between align-center">
|
||||
支持的交易对
|
||||
<div v-if="!isEditingSymbols">
|
||||
<v-btn icon="mdi-pencil" variant="text" size="small" @click="handleEditSymbols"></v-btn>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-btn color="primary" variant="tonal" size="small" @click="handleSaveSymbols">保存</v-btn>
|
||||
<v-btn variant="text" size="small" @click="handleCancelEdit">取消</v-btn>
|
||||
</div>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div class="d-flex flex-wrap ga-2">
|
||||
<v-chip
|
||||
v-for="symbol in localSymbols"
|
||||
:key="symbol"
|
||||
:closable="isEditingSymbols"
|
||||
@click:close="removeSymbol(symbol)"
|
||||
label
|
||||
variant="outlined"
|
||||
>
|
||||
<template #prepend>
|
||||
<CryptoIcon :symbol="symbol.replace('USDT', '')" size="20" />
|
||||
</template>
|
||||
{{ symbol }}
|
||||
</v-chip>
|
||||
<CurrencySelector v-if="isEditingSymbols" @select="addSymbol">
|
||||
<template #activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-plus" variant="tonal" size="small"></v-btn>
|
||||
</template>
|
||||
</CurrencySelector>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="8">
|
||||
<v-card>
|
||||
<v-toolbar flat>
|
||||
<v-text-field
|
||||
v-model="filterText"
|
||||
label="筛选币种"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="ma-2"
|
||||
></v--text-field>
|
||||
<v-btn icon="mdi-refresh" @click="binanceStore.fetchAllTickerPrices"></v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<div class="px-4">
|
||||
<span class="text-subtitle-1">总资产: </span>
|
||||
<span class="text-h6 font-weight-bold">{{ formatNumber(totalAssetValue) }} USDT</span>
|
||||
</div>
|
||||
</v-toolbar>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="filteredAssetDetails"
|
||||
:loading="isLoading"
|
||||
item-value="asset"
|
||||
density="compact"
|
||||
>
|
||||
<template #item.icon="{ item }">
|
||||
<CryptoIcon :symbol="item.raw.asset" />
|
||||
</template>
|
||||
<template #item.price="{ value }">{{ formatNumber(value) }}</template>
|
||||
<template #item.totalAmount="{ value }">{{ formatNumber(value) }}</template>
|
||||
<template #item.free="{ value }">{{ formatNumber(parseFloat(value)) }}</template>
|
||||
<template #item.locked="{ value }">{{ formatNumber(parseFloat(value)) }}</template>
|
||||
<template #item.totalValue="{ value }">
|
||||
<span class="font-weight-bold">{{ formatNumber(value) }}</span>
|
||||
</template>
|
||||
<template #no-data>
|
||||
<v-alert type="info" class="ma-4">暂无资产数据</v-alert>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### **总结:设计优化点**
|
||||
|
||||
1. **架构层面:**
|
||||
|
||||
* 引入了 **Pinia** 进行中心化状态管理,将数据、业务逻辑与视图彻底分离,提高了代码的可维护性和可测试性。
|
||||
* 定义了清晰的 **TypeScript 类型** (`AccountInfo`, `AssetDetail`等),贯穿API、Store和组件,实现端到端的类型安全。
|
||||
|
||||
2. **API与数据流:**
|
||||
|
||||
* **并行请求**:`fetchInitialData` 中使用 `Promise.all` 并行获取账户信息和交易对,缩短了首屏加载时间。
|
||||
* **原子化更新**:将原始设计中的 `add` 和 `delete` 接口合并为单一的 `update` 接口,这使得前端的 "保存" 操作成为一个原子事务,避免了多次请求可能导致的部分成功或失败,保证了数据一致性。
|
||||
* **数据聚合**:在Pinia的Getter (`assetDetails`) 中,将分散的余额数据 (`balances`) 和价格数据 (`assetPrices`) 聚合成统一的视图模型,简化了组件层的渲染逻辑。
|
||||
|
||||
3. **用户体验 (UX):**
|
||||
|
||||
* **完整的状态反馈**:全面设计了 **加载中 (Loading)**、**错误 (Error)** 和 **空数据 (No Data)** 状态的UI展示,使用 `v-skeleton-loader`、`v-alert` 等组件提供清晰的用户反馈。
|
||||
* **优化的编辑模式**:修正了原始设计中 "立即调用接口" 的问题。交易对的增删操作现在在本地临时状态 (`localSymbols`) 中进行,用户可以自由修改,只有点击 "保存" 按钮时才发起一次API请求。增加了 "取消" 按钮,允许用户放弃修改,极大提升了操作的容错性和友好性。
|
||||
* **即时筛选与刷新**:资产表格提供了客户端筛选功能和手动刷新价格的按钮,增强了数据的可操作性。
|
||||
|
||||
4. **代码健壮性:**
|
||||
|
||||
* **防御性编程**:在 `computed` 属性和模板中,对可能为空的 `accountInfo.value` 进行了安全检查。
|
||||
* **数据格式化**:对所有数值显示进行了统一的格式化处理,保证了UI的一致性。
|
||||
* **性能考量**:使用 `Map` 结构存储币种价格,使得在聚合 `assetDetails` 时查找价格的时间复杂度从O(N)降低到O(1),在资产数量较多时性能更优。
|
||||
|
||||
这份设计方案将一个初步的想法,转化为了一个专业、严谨且可直接投入生产的工程实现蓝图。
|
||||
136
c-ProjectTonyStack/2-AI优化设计/1-页面/api_binance_order.md
Normal file
136
c-ProjectTonyStack/2-AI优化设计/1-页面/api_binance_order.md
Normal file
@@ -0,0 +1,136 @@
|
||||
**背景 (Context):**
|
||||
|
||||
你是一名资深前端架构师,严格遵循 `vue3-typescript-stye.md` 中定义的所有开发规范。你的任务是为一个企业级加密货币交易分析平台,开发一个功能完备的“交易订单”查询与分析页面。
|
||||
|
||||
**核心指令 (Core Directive):**
|
||||
|
||||
请根据以下详细设计方案,生成完整的、生产环境级别的Vue 3、TypeScript和Vuetify 3代码。你需要创建所有必要的Pinia Store、API模块、页面和组件。
|
||||
|
||||
---
|
||||
|
||||
### **功能模块一:Pinia状态管理 (`src/store/binanceOrder.ts`)**
|
||||
|
||||
为“交易订单”页面创建一个专属的Pinia store,用于统一管理所有状态。
|
||||
|
||||
1. **Store ID:** `'binanceOrder'`
|
||||
|
||||
2. **State (`BinanceOrderState`):**
|
||||
* `trades`: `Ref<Trade[]>` - 存储从API获取的原始订单列表,默认为 `[]`。
|
||||
* `filters`: `Reactive<OrderFilters>` - 存储所有查询和筛选条件。
|
||||
* `symbol`: `string | null` - 交易对,如 'BTCUSDT'。
|
||||
* `startTime`: `number | null` - 查询起始时间戳。
|
||||
* `endTime`: `number | null` - 查询结束时间戳。
|
||||
* `side`: `'BUY' | 'SELL' | 'ALL'` - 交易方向,默认 'ALL'。
|
||||
* `timeRangeSelection`: `string | null` - 用于高亮显示快捷时间段按钮,如 '3m', '1y'。
|
||||
* `pagination`: `Reactive<Pagination>` - 分页状态。
|
||||
* `page`: `number` - 当前页码,默认 1。
|
||||
* `itemsPerPage`: `number` - 每页数量,默认 20。
|
||||
* `totalItems`: `number` - 总条目数,默认 0。
|
||||
* `isLoading`: `Ref<boolean>` - 页面级加载状态,控制骨架屏或加载指示器。
|
||||
* `error`: `Ref<string | null>` - 存储API请求错误信息。
|
||||
|
||||
3. **Getters:**
|
||||
* `displayTrades`: 计算属性,根据 `filters.side` 和 `pagination` 从 `trades` 派生出当前页面应显示的订单列表。
|
||||
* `timelineData`: 计算属性,将 `trades` 转换为时间轴组件所需的数据结构 `[{timestamp: number, side: 'BUY' | 'SELL'}]`。
|
||||
* `isDefaultDateRange`: 计算属性,判断当前`startTime`是否为初始默认值 `2025-01-01 08:00:00`。
|
||||
|
||||
4. **Actions:**
|
||||
* `fetchTrades()`:
|
||||
* 设置 `isLoading` 为 `true`,清空 `error`。
|
||||
* 调用API模块获取订单数据,请求参数基于 `filters` state(`symbol`, `startTime`, `endTime`),`limit` 固定为 `1000`。
|
||||
* 成功后,更新 `trades` 和 `pagination.totalItems`。
|
||||
* 失败后,填充 `error` 状态。
|
||||
* 最后设置 `isLoading` 为 `false`。
|
||||
* `setFilter(payload: Partial<OrderFilters>)`: 更新 `filters` 对象。
|
||||
* `setPage(page: number)`: 更新 `pagination.page`。
|
||||
* `setItemsPerPage(size: number)`: 更新 `pagination.itemsPerPage` 并将 `page` 重置为 1。
|
||||
* `applyTimeRange(range: '3m' | '1m' | '1y' | '3y')`:
|
||||
* 根据`range`计算新的 `startTime` (以当前时间为`endTime`)。
|
||||
* 更新 `filters.startTime`, `filters.endTime` 和 `filters.timeRangeSelection`。
|
||||
* `resetTimeRange()`: 恢复`startTime`和`endTime`至默认值,并清空`timeRangeSelection`。
|
||||
* `exportToCSV()`:
|
||||
* 实现一个将 `trades` state中的数据转换为CSV格式并触发浏览器下载的逻辑。
|
||||
* CSV列应包括:`Time (CST)`, `Symbol`, `Side`, `Price`, `Quantity`, `QuoteQty`, `Commission`, `CommissionAsset`。
|
||||
|
||||
---
|
||||
|
||||
### **功能模块二:API层 (`src/api/modules/binance.ts` & `src/types/binance.d.ts`)**
|
||||
|
||||
1. **类型定义 (`binance.d.ts`):**
|
||||
* `MyTradesRequest`: 定义API请求参数类型。
|
||||
* `Trade`: 定义单笔订单的数据结构,所有字段必须有精确类型。
|
||||
|
||||
2. **API函数 (`binance.ts`):**
|
||||
* 创建一个 `getMyTrades(params: MyTradesRequest): Promise<ApiResponse<Trade[]>>` 函数。
|
||||
* 该函数通过封装的Axios实例,向 `POST /api/binance/order/trades` 发送请求。
|
||||
|
||||
---
|
||||
|
||||
### **功能模块三:页面与组件实现**
|
||||
|
||||
#### **1. 主页面 (`src/pages/BinanceOrder.vue`)**
|
||||
|
||||
* **布局:**
|
||||
* 使用 `v-container(fluid)` 作为根元素。
|
||||
* 顶部应用栏 (`v-app-bar`) 的 `v-toolbar-title` 显示 "交易订单"。
|
||||
* 页面主体使用 `v-row` 和 `v-col` 组织布局。
|
||||
* **逻辑 (`<script setup lang="ts">`):**
|
||||
* 实例化 `binanceOrder` store。
|
||||
* 使用 `onMounted` 触发一次初始数据加载 `store.fetchTrades()`。
|
||||
* 将store的state和actions传递给子组件。
|
||||
* 使用 `v-if="store.isLoading"` 显示一个覆盖整个内容区的 `v-progress-linear` 加载指示器。
|
||||
* 监听 `store.error`,当其有值时,通过全局Snackbar服务显示错误信息。
|
||||
|
||||
#### **2. 订单查询栏组件 (`src/components/binance/OrderFilterBar.vue`)**
|
||||
|
||||
* **UI:** 使用 `v-card` 和 `v-card-text` 构建。
|
||||
* **交易对选择:** 使用 `CurrencySelector` 全局组件。
|
||||
* **时间选择:**
|
||||
* 使用两个 `v-text-field` (label: "开始时间", "结束时间") 并配合 `v-menu` 和 `v-date-picker` + 时间选择器实现。`v-model` 双向绑定到 store中的 `filters.startTime` 和 `filters.endTime`(需要转换器)。
|
||||
* **快捷时间段:** 使用 `v-chip-group` 或 `v-btn-toggle` 展示“最近三个月”、“最近一个月”、“最近一年”、“最近三年”。点击时调用 `store.applyTimeRange(range)`。`v-model` 绑定到`store.filters.timeRangeSelection`以实现状态高亮。
|
||||
* **重置时间:** 提供一个 `v-btn` (icon: `mdi-restore`),`@click`="store.resetTimeRange()"`,仅在 `!isDefaultDateRange` 时可见。
|
||||
* **操作按钮:**
|
||||
* `v-btn` "查询",`@click="store.fetchTrades()"`,颜色为 `primary`。
|
||||
* `v-btn` "导出CSV",`@click="store.exportToCSV()"`,颜色为 `secondary`。
|
||||
* **响应式:** 在 `xs` 断点下,所有表单项垂直堆叠。
|
||||
|
||||
#### **3. 订单时间轴组件 (`src/components/binance/OrderTimeline.vue`)**
|
||||
|
||||
* **技术选型:** 自定义、响应式的SVG组件。
|
||||
* **Props:**
|
||||
* `data: {timestamp: number, side: 'BUY' | 'SELL'}[]` - 从 `store.timelineData` getter 传入。
|
||||
* **Events:**
|
||||
* `@range-selected="(startTime: number, endTime: number) => void"` - 当用户通过拖拽(brushing)选择一个范围后触发。
|
||||
* `@dot-clicked="(trade: Trade) => void"` - 当用户点击某个订单点时触发。
|
||||
* **功能:**
|
||||
* 根据`data`中的时间戳范围,动态计算X轴的比例尺。
|
||||
* 在SVG上渲染代表每个订单的圆点(`<circle>`),`BUY`为绿色,`SELL`为红色。
|
||||
* 实现鼠标悬停在圆点上时显示Tooltip(使用Vuetify的 `v-tooltip`),展示订单关键信息。
|
||||
* 支持鼠标按下并拖拽以创建一个矩形选区(brush),释放鼠标后,发出 `@range-selected` 事件。
|
||||
* 整个SVG的尺寸应能响应容器宽度变化。
|
||||
|
||||
#### **4. 订单列表区组件 (`src/components/binance/OrderDataTable.vue`)**
|
||||
|
||||
* **UI:**
|
||||
* 使用 Vuetify 的 `v-data-table-server` 组件,因为它能更好地处理潜在的大量数据和分页逻辑。
|
||||
* **Props & State绑定:**
|
||||
* `:items`: `store.displayTrades`
|
||||
* `:headers`: 定义表头,包括时间、交易对、方向、价格、数量、成交额、手续费等。
|
||||
* `:loading`: `store.isLoading`
|
||||
* `:page`: `store.pagination.page`
|
||||
* `:items-per-page`: `store.pagination.itemsPerPage`
|
||||
* `:items-length`: `store.pagination.totalItems`
|
||||
* `@update:page`: `(p) => store.setPage(p)`
|
||||
* `@update:items-per-page`: `(size) => store.setItemsPerPage(size)`
|
||||
* **功能:**
|
||||
* **表头:** 在 `v-data-table` 上方使用 `v-toolbar` 添加筛选控件。
|
||||
* **方向筛选:** `v-btn-toggle` (选项: 全部, 买入, 卖出),`v-model` 绑定 `store.filters.side`。
|
||||
* **单元格渲染:**
|
||||
* **方向:** 使用 `v-chip` 根据买卖方向显示不同颜色(`success` for BUY, `error` for SELL)。
|
||||
* **时间:** 格式化为 `YYYY-MM-DD HH:mm:ss`。
|
||||
* **空状态:** 当 `store.trades.length === 0 && !store.isLoading` 时,在表格中显示 "暂无数据" 的提示。
|
||||
* **分页选项:** `items-per-page-options` 设置为 `[20, 50, 100]`。
|
||||
|
||||
**总结指令:**
|
||||
|
||||
请严格按照上述四个模块的设计,生成所有代码文件,并确保它们完全符合 `vue3-typescript-stye.md` 中定义的每一条规范,包括但不限于:完全的TypeScript、组合式API、Vuetify 3组件和主题变量的使用、Pinia的最佳实践以及健壮的API数据处理。
|
||||
165
c-ProjectTonyStack/2-AI优化设计/2-组件/api_crypto_icon.md
Normal file
165
c-ProjectTonyStack/2-AI优化设计/2-组件/api_crypto_icon.md
Normal file
@@ -0,0 +1,165 @@
|
||||
**SUBJECT:** **企业级加密货币图标系统(Crypto Icon System)详细设计与实现指令**
|
||||
|
||||
**背景 (Context):**
|
||||
|
||||
你将为我们的企业级SaaS应用构建一个高性能、类型安全且完全符合设计规范的加密货币图标系统。此系统不仅需要展示单个货币图标,还需优雅地处理交易对的堆叠图标样式。该模块必须严格遵守项目 `vue3-typescript-stye.md` 中定义的所有架构原则、编码规范和技术选型。
|
||||
|
||||
**核心指令 (Core Directive):**
|
||||
|
||||
请根据以下详细设计,实现一个完整的、可复用的、生产级的加密货币图标系统。该系统主要由一个核心的组合式函数(Composable)用于数据管理,以及一个UI组件用于渲染构成。
|
||||
|
||||
-----
|
||||
|
||||
### **1. 体系结构与设计哲学 (Architecture & Philosophy)**
|
||||
|
||||
此图标系统旨在将图标数据管理与UI渲染完全分离,以实现最大程度的内聚和可复用性。
|
||||
|
||||
* **数据层 (Data Layer):** 通过一个**组合式函数 `useCrypto.ts`** 进行管理。该函数在应用启动时加载并索引一次静态图标数据,提供高效、响应式的查询接口供任何组件使用。数据是静态的,在构建时生成,确保了运行时的高性能和零API依赖。
|
||||
* **表现层 (Presentation Layer):** 一个**单一职责组件 `CryptoIcon.vue`** 负责根据传入的标识符(单个货币或交易对)从数据层获取SVG数据并完成渲染。该组件将完全兼容Vuetify的尺寸、响应式和主题系统。
|
||||
|
||||
-----
|
||||
|
||||
### **2. 数据管理模块 (`composables/useCrypto.ts`)**
|
||||
|
||||
这是系统的核心逻辑,负责图标数据的加载、索引和查询。
|
||||
|
||||
**2.1. 数据源与生成 (Data Source & Generation)**
|
||||
|
||||
* **数据来源:**
|
||||
* **元数据:** 使用python3构建脚本,放置于`ProjectTonyStack/TonyMask/scripts/`中,用于从[TradingView](https://tradingview.com/markets/cryptocurrencies/prices-all/) 或者从稳定、公开的加密货币API(例如 CoinGecko 或 CoinMarketCap 的免费API)通过手动执行,获取前1000名加密货币的图标数据。
|
||||
* **SVG图标:** 不要使用开源库 **`cryptocurrency-icons`**。该库的内容过时,无法满足需求,请设计爬虫脚本爬取svg图标数据。
|
||||
* **数据静态化:**
|
||||
* 该脚本将整合后的元数据和SVG内容生成一个静态的 `crypto-data.ts` 文件,存放于 `src/assets/` 目录下。此文件将导出一个类型为 `CryptoCurrencyInfo[]` 的常量数组。这种方式避免了客户端的运行时API请求,极大地提升了性能和稳定性。
|
||||
* 对于未在 `cryptocurrency-icons` 库中找到的图标,脚本应自动关联一个预定义的“未知货币”SVG图标。
|
||||
|
||||
**2.2. 数据结构定义 (`types/crypto.d.ts`)**
|
||||
|
||||
```typescript
|
||||
// src/types/crypto.d.ts
|
||||
|
||||
/**
|
||||
* @interface CryptoCurrencyInfo
|
||||
* @description 定义了单个加密货币的完整信息结构。
|
||||
*/
|
||||
export interface CryptoCurrencyInfo {
|
||||
/** 货币的唯一ID (e.g., "bitcoin") */
|
||||
id: string;
|
||||
/** 货币简称/符号 (e.g., "BTC") */
|
||||
symbol: string;
|
||||
/** 货币全称 (e.g., "Bitcoin") */
|
||||
name: string;
|
||||
/** 货币全称的大写形式 (e.g., "BITCOIN") */
|
||||
nameUpperCase: string;
|
||||
/** 货币的SVG图标原始内容 (作为字符串) */
|
||||
svg: string;
|
||||
/** 市值排名 */
|
||||
marketCapRank: number;
|
||||
}
|
||||
```
|
||||
|
||||
**2.3. Composable 实现 (`useCrypto.ts`)**
|
||||
|
||||
* **状态管理:**
|
||||
|
||||
* 使用 `shallowRef` 来存储从 `crypto-data.ts` 导入的完整货币列表,因为该数据在应用生命周期内是只读的,无需深度响应。
|
||||
* 在首次调用时,一次性构建多个 `Map` 对象用于快速索引,并将其存储在模块作用域的常量中,实现单例模式,避免重复计算。
|
||||
|
||||
* **高效查询机制:**
|
||||
|
||||
* **O(1) 查找:** 创建三个独立的 `Map` 来满足不同的查询需求:
|
||||
1. `symbolMap: Map<string, CryptoCurrencyInfo>` (通过简称查询, e.g., 'BTC')
|
||||
2. `nameMap: Map<string, CryptoCurrencyInfo>` (通过全称小写查询, e.g., 'bitcoin')
|
||||
3. `idMap: Map<string, CryptoCurrencyInfo>` (通过ID查询, e.g., 'bitcoin')
|
||||
* **查询函数 `findCryptoInfo`:**
|
||||
* 设计一个签名清晰的函数 `const findCryptoInfo = (identifier: string): CryptoCurrencyInfo | undefined => { ... }`。
|
||||
* 该函数内部将尝试按以下顺序和逻辑进行高效查找:
|
||||
1. 将输入 `identifier` 转换为小写。
|
||||
2. 优先在 `symbolMap` 中查找。
|
||||
3. 若未找到,则在 `nameMap` 中查找。
|
||||
4. 若仍未找到,则在 `idMap` 中查找。
|
||||
5. 返回找到的 `CryptoCurrencyInfo` 对象,否则返回 `undefined`。
|
||||
* **交易对解析函数 `parseTradingPair`:**
|
||||
* 设计一个函数 `const parseTradingPair = (pair: string): { base: CryptoCurrencyInfo | undefined, quote: CryptoCurrencyInfo | undefined } => { ... }`。
|
||||
* 该函数负责解析常见的交易对格式(如 `BTCUSDT`, `ETHUSDT`)。它会尝试从已知的稳定币后缀(`USDT`, `USDC`, `BUSD`, `DAI`等)分割基础货币和计价货币,然后分别调用 `findCryptoInfo` 进行查找。
|
||||
|
||||
-----
|
||||
|
||||
### **3. UI组件 (`components/CryptoIcon.vue`)**
|
||||
|
||||
此组件是图标的最终渲染载体。
|
||||
|
||||
**3.1. 组件API设计 (Props)**
|
||||
|
||||
```typescript
|
||||
// CryptoIcon.vue
|
||||
import type { CryptoCurrencyInfo } from '@/types/crypto.d.ts';
|
||||
|
||||
interface Props {
|
||||
/** 单个货币标识符 (简称、全称或ID),与`pair`属性互斥 */
|
||||
symbol?: string;
|
||||
/** 交易对字符串 (e.g., "BTCUSDT"),与`symbol`属性互斥 */
|
||||
pair?: string;
|
||||
/**
|
||||
* 图标尺寸,可以是数字(px)或Vuetify预设的尺寸字符串
|
||||
* @default 24
|
||||
*/
|
||||
size?: string | number;
|
||||
/**
|
||||
* 是否仅显示图标,不显示旁边的名称
|
||||
* @default false
|
||||
*/
|
||||
iconOnly?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
* 组件逻辑中必须包含对 `symbol` 和 `pair` 互斥性的校验。
|
||||
|
||||
**3.2. 渲染逻辑 (`<template>`)**
|
||||
|
||||
* **根元素:** 使用 `div` 作为容器,并应用 `d-inline-flex align-center` 以确保图标和文字垂直对齐。
|
||||
* **图标容器:**
|
||||
* **单个图标:** 直接渲染一个 `div`,使用 `v-html` 绑定从 `useCrypto` composable中查找到的SVG字符串。
|
||||
* **交易对图标:**
|
||||
* 创建一个相对定位的容器 `div` (`position: relative`)。
|
||||
* 内部渲染两个绝对定位的 `div`,分别用于基础货币和计价货币。
|
||||
* **样式:** 仿照TradingView,基础货币图标(左)尺寸为100%,计价货币图标(右下)尺寸约为60%,通过 `bottom: -10%; right: -10%;` 进行偏移,并设置更高的 `z-index` 和一个小的白色边框(使用Vuetify主题变量 `rgb(var(--v-theme-surface))`)以创建堆叠效果。
|
||||
* **健壮性:**
|
||||
* 当 `findCryptoInfo` 或 `parseTradingPair` 返回 `undefined` 时,必须优雅地降级,显示预定义的“未知货币”图标。
|
||||
* 使用可选链 (`?.`) 和空值合并运算符 (`??`) 安全地访问数据。
|
||||
* **名称展示:**
|
||||
* 当 `iconOnly` 为 `false` 时,在图标右侧渲染货币全称大写 (`nameUpperCase`)。使用 `v-if` 控制其显示。
|
||||
* 使用 `v-text` 绑定名称,并应用Vuetify的排版类 (`text-subtitle-2` 或类似)。
|
||||
|
||||
**3.3. 样式与主题 (Styling & Theming)**
|
||||
|
||||
* **严禁硬编码颜色。** SVG内部的 `fill` 或 `stroke` 属性,如果需要跟随主题变化(例如在单色图标版本中),应设置为 `currentColor`。
|
||||
* 组件的所有样式必须能完美适配**明亮 (Light)** 和**黑暗 (Dark)** 两种主题。边框、背景等颜色必须使用Vuetify主题变量,如 `border-color: rgb(var(--v-theme-surface));`。
|
||||
|
||||
**3.4. 无障碍 (Accessibility - a11y)**
|
||||
|
||||
* 图标的根元素需添加 `role="img"`。
|
||||
* 动态绑定 `aria-label` 属性,其内容为货币的全称(例如 `aria-label="Bitcoin"`)或交易对的名称(例如 `aria-label="Bitcoin / Tether"`),为屏幕阅读器用户提供清晰的上下文。
|
||||
|
||||
-----
|
||||
|
||||
### **4. 项目结构整合 (Directory Structure)**
|
||||
|
||||
```
|
||||
src/
|
||||
├── assets/
|
||||
│ └── crypto-data.ts # (构建时生成) 静态图标数据
|
||||
├── components/
|
||||
│ └── CryptoIcon.vue # UI渲染组件
|
||||
├── composables/
|
||||
│ └── useCrypto.ts # 数据逻辑与查询核心
|
||||
├── types/
|
||||
│ └── crypto.d.ts # TypeScript类型定义
|
||||
scripts/
|
||||
└── generate-crypto-data.ts # 用于在构建时生成数据的Node.js脚本
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### **5. 最终实现指令 (Actionable Prompt for Implementation)**
|
||||
|
||||
现在,请严格遵循以上这份详尽的架构设计方案,完成`CryptoIcon.vue`组件、`useCrypto.ts`组合式函数、`crypto.d.ts`类型定义文件以及`generate-crypto-data.ts`构建脚本的编码工作。确保最终产出的代码是生产级的,完全符合`vue3-typescript-stye.md`中定义的所有规范,特别是在TypeScript强类型、组合式API、Vuetify 3集成、主题适配和代码健壮性方面达到典范水平。
|
||||
243
c-ProjectTonyStack/2-AI优化设计/2-组件/api_currency_selector.md
Normal file
243
c-ProjectTonyStack/2-AI优化设计/2-组件/api_currency_selector.md
Normal file
@@ -0,0 +1,243 @@
|
||||
**角色**: 你是一位资深前端架构师,精通 Vue 3、TypeScript 和 Vuetify 3,严格遵循 Material Design 3 设计哲学和企业级代码规范。
|
||||
|
||||
**任务**: 根据以下详细设计方案,创建一个名为 `CurrencySelector` 的通用、高性能、主题自适应的加密货币/交易对选择器组件。
|
||||
|
||||
-----
|
||||
|
||||
#### **1. 组件核心概述 (Component Overview)**
|
||||
|
||||
`CurrencySelector` 是一个基于 Vuetify 3 的高级表单控件。它旨在提供一个功能强大且用户友好的界面,用于从一个可配置的数据源中单选或多选加密货币或交易对。组件内部将封装数据加载、搜索过滤、状态管理以及复杂的UI渲染逻辑,同时对外暴露简洁、类型安全的API。
|
||||
|
||||
-----
|
||||
|
||||
#### **2. 核心技术栈与规范 (Core Tech Stack & Specifications)**
|
||||
|
||||
* **框架 (Framework):** Vue 3.x
|
||||
* **脚本 (Script):** `<script setup lang="ts">` (组合式API)
|
||||
* **语言 (Language):** TypeScript (严格模式,**严禁 `any`** )
|
||||
* **UI库 (UI Library):** Vuetify 3.x
|
||||
* **状态管理 (State Management):** Pinia
|
||||
* **设计规范 (Design System):** Google Material Design 3 (MD3)
|
||||
* **强制遵循**: `vue3-typescript-stye.md` 中的所有规范。
|
||||
|
||||
-----
|
||||
|
||||
#### **3. 组件API设计 (Props & Emits)**
|
||||
|
||||
为了实现组件的高度可复用性和灵活性,其API必须经过精心设计,并提供完整的TypeScript类型定义。
|
||||
|
||||
##### **3.1 类型定义 (Type Definitions)**
|
||||
|
||||
首先,在 `@/types/currency.d.ts` 中定义核心数据结构:
|
||||
|
||||
```typescript
|
||||
// @/types/currency.d.ts
|
||||
|
||||
/**
|
||||
* @interface CurrencyItem
|
||||
* @description 代表一个可选择的币种或交易对的基础数据结构
|
||||
*/
|
||||
export interface CurrencyItem {
|
||||
id: string | number; // 唯一标识符
|
||||
name: string; // 完整名称, e.g., "Bitcoin"
|
||||
symbol: string; // 简称/符号, e.g., "BTC"
|
||||
pair?: string; // 交易对名称, e.g., "BTCUSDT"
|
||||
iconUrl?: string; // 图标的URL地址
|
||||
}
|
||||
```
|
||||
|
||||
##### **3.2 Props**
|
||||
|
||||
```typescript
|
||||
// CurrencySelector.vue
|
||||
|
||||
import type { CurrencyItem } from '@/types/currency';
|
||||
|
||||
interface Props {
|
||||
// v-model 绑定值,支持单选或多选
|
||||
modelValue: string | number | (string | number)[] | null;
|
||||
|
||||
// 选项列表数据源
|
||||
items: CurrencyItem[];
|
||||
|
||||
// 是否支持多选
|
||||
multiple?: boolean;
|
||||
|
||||
// 占位符文本
|
||||
placeholder?: string;
|
||||
|
||||
// 是否处于加载状态
|
||||
loading?: boolean;
|
||||
|
||||
// 是否禁用
|
||||
disabled?: boolean;
|
||||
|
||||
// 返回对象的属性,决定了 modelValue 的值
|
||||
itemValue?: keyof CurrencyItem; // 默认为 'id'
|
||||
|
||||
// 自定义清除按钮图标
|
||||
clearable?: boolean;
|
||||
clearIcon?: string;
|
||||
|
||||
// 错误信息和状态
|
||||
error?: boolean;
|
||||
errorMessages?: string | string[];
|
||||
|
||||
// Vuetify v-field 的 density 和 variant 属性
|
||||
density?: 'default' | 'comfortable' | 'compact';
|
||||
variant?: 'outlined' | 'filled' | 'underlined' | 'solo' | 'plain';
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
multiple: false,
|
||||
placeholder: '点击选择币种',
|
||||
loading: false,
|
||||
disabled: false,
|
||||
itemValue: 'id',
|
||||
clearable: true,
|
||||
density: 'compact',
|
||||
variant: 'outlined'
|
||||
});
|
||||
```
|
||||
|
||||
##### **3.3 Emits**
|
||||
|
||||
```typescript
|
||||
// CurrencySelector.vue
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string | number | (string | number)[] | null): void;
|
||||
}>();
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
#### **4. 内部实现与架构决策 (Internal Implementation & Architecture)**
|
||||
|
||||
##### **4.1 基础组件选用**
|
||||
|
||||
* **核心**: 使用 Vuetify 的 `VAutocomplete` 组件作为基础。它原生支持搜索、单选/多选、异步加载以及丰富的插槽,能完美满足需求。**不要手动实现下拉菜单的显示/隐藏逻辑和层级管理**,`VAutocomplete` 内置的 `v-menu` 会自动处理。
|
||||
|
||||
##### **4.2 UI/UX 实现细节**
|
||||
|
||||
* **已选项渲染 (胶囊按钮)**:
|
||||
|
||||
* 利用 `VAutocomplete` 的 `#selection` 插槽。
|
||||
* 在插槽内,使用 `VChip` 组件来渲染已选项,以实现“胶囊按钮”效果。
|
||||
* `VChip` 应包含币种图标 (`VAvatar` 或 `VIcon`)、简称 (`item.raw.symbol` 或 `item.raw.pair`),并自带删除按钮 (`closable=true`)。
|
||||
* `VChip` 的颜色应使用主题变量,例如 `color="primary"`, 使其与输入框背景色区分。
|
||||
|
||||
* **下拉选项渲染**:
|
||||
|
||||
* 利用 `VAutocomplete` 的 `#item` 插槽。
|
||||
* 在插槽内,使用 `VListItem` 组件。
|
||||
* `VListItem` 包含 `VListItemAvatar` (用于图标) 和 `VListItemTitle` (用于显示简称或交易对名称)。
|
||||
|
||||
* **主题适配 (Theming)**:
|
||||
|
||||
* **严禁**任何硬编码的颜色值。
|
||||
* 组件所有背景、文字、边框颜色**必须**使用 Vuetify 的主题系统。例如,`VChip` 的颜色应设置为 `primary` 或 `secondary` 等主题色,输入框的背景将自动适应主题。
|
||||
* 币种图标本身是图片,无需颜色变换,符合原始要求。
|
||||
|
||||
* **滚动与显示数量**:
|
||||
|
||||
* `VAutocomplete` 的下拉菜单默认就是可滚动的。
|
||||
* 要控制显示高度,可以通过 CSS `var(--v-overlay-max-height)` 变量或为 `menu-props` 设置样式 `{ maxHeight: '300px' }` 来实现,而非通过参数配置固定的`n`个选项。这更符合响应式设计。
|
||||
|
||||
##### **4.3 逻辑与功能**
|
||||
|
||||
* **搜索功能**:
|
||||
|
||||
* `VAutocomplete` 自带文本搜索功能。
|
||||
* 为了更精准地匹配 `name`, `symbol`, `pair`,可以提供一个自定义的 `custom-filter` 函数。
|
||||
```typescript
|
||||
const currencyFilter = (itemTitle: string, queryText: string, item: { raw: CurrencyItem }): boolean => {
|
||||
const query = queryText.toLowerCase();
|
||||
const { name, symbol, pair } = item.raw;
|
||||
return (
|
||||
name.toLowerCase().includes(query) ||
|
||||
symbol.toLowerCase().includes(query) ||
|
||||
(pair && pair.toLowerCase().includes(query))
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
* **数据管理**:
|
||||
|
||||
* 如果币种列表是全局共享且不常变动的,**推荐**使用 Pinia Store (`@/store/currency.ts`) 进行管理。
|
||||
* Store 应包含 state (存储列表)、getters (用于快速查找) 和 actions (用于异步获取数据并缓存)。
|
||||
* 组件在 `onMounted` 时,检查 Store 中是否已有数据,若无则 dispatch action 获取。
|
||||
|
||||
* **响应式设计**:
|
||||
|
||||
* 组件的尺寸不应由内部 prop 控制。应遵循 Vuetify 的栅格系统,将其放置在 `VCol` 中,由父级布局决定其宽度。这保证了组件在不同屏幕尺寸下的适应性。
|
||||
|
||||
-----
|
||||
|
||||
#### **5. 非功能性需求 (Non-Functional Requirements)**
|
||||
|
||||
* **性能 (Performance)**:
|
||||
|
||||
* 对于超大数据量的选项列表 (e.g., \> 1000),应考虑使用 `VVirtualScroll` 组件包裹在下拉菜单中,以提高渲染性能。
|
||||
* 搜索输入的 debounce 由 `VAutocomplete` 内置处理。
|
||||
|
||||
* **可访问性 (Accessibility - A11Y)**:
|
||||
|
||||
* 通过使用 `VAutocomplete` 和 `VChip` 等标准 Vuetify 组件,大部分 WAI-ARIA 属性会自动得到支持。
|
||||
* 确保所有可交互元素都有清晰的焦点状态,并支持键盘导航。
|
||||
|
||||
* **错误处理 (Error Handling)**:
|
||||
|
||||
* 当 `loading` 为 `true` 时,显示 `VProgressLinear` 或 `VProgressCircular`。
|
||||
* 当 `items` 数组为空时,应在下拉菜单中显示提示信息,如“无匹配数据”。
|
||||
* 当接收到 `error` prop 时,`VAutocomplete` 边框应变为错误颜色,并显示 `error-messages`。
|
||||
|
||||
-----
|
||||
|
||||
#### **6. 示例用法 (Example Usage)**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<CurrencySelector
|
||||
v-model="selectedSingle"
|
||||
:items="currencyStore.list"
|
||||
:loading="currencyStore.loading"
|
||||
label="选择单个币种"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<CurrencySelector
|
||||
v-model="selectedMultiple"
|
||||
:items="currencyStore.list"
|
||||
:loading="currencyStore.loading"
|
||||
label="选择多个交易对"
|
||||
multiple
|
||||
placeholder="搜索并选择交易对"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useCurrencyStore } from '@/store/currency';
|
||||
import CurrencySelector from '@/components/CurrencySelector.vue';
|
||||
|
||||
const currencyStore = useCurrencyStore();
|
||||
|
||||
const selectedSingle = ref<string | null>(null);
|
||||
const selectedMultiple = ref<string[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
currencyStore.fetchCurrencies();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
**最终指令**: 请根据以上这份详尽、严谨的架构设计方案,生成 `CurrencySelector.vue` 组件的完整代码。代码必须是生产级别的,并体现出卓越的工程实践。
|
||||
69
c-ProjectTonyStack/2-AI优化设计/2-组件/api_mask_layout.md
Normal file
69
c-ProjectTonyStack/2-AI优化设计/2-组件/api_mask_layout.md
Normal file
@@ -0,0 +1,69 @@
|
||||
**角色 (Role):** 你是一位精通 Vue 3、TypeScript 和 Vuetify 的资深前端架构师。你的任务是构建基础布局框架。
|
||||
|
||||
**背景 (Context):** 你必须严格遵守所提供的 `vue3-typescript-stye.md` 风格指南中的所有原则。最终目标是产出一个健壮、可维护、生产就绪的应用外壳,它将作为未来所有页面的实现蓝图。此布局必须是完全响应式、支持主题切换,并完全基于 Vue 3 组合式 API 和 Vuetify 3 组件构建。
|
||||
|
||||
**核心任务 (Core Task):**
|
||||
|
||||
在 `src/layouts/` 目录下,创建一个名为 `Default.vue` 的主应用布局组件。该组件需实现一个经典的三段式结构:一个持久化的导航抽屉、一个顶部应用栏和一个用于承载路由视图的主内容区。所有与布局 UI 相关的状态(例如,侧边栏的收缩状态、当前的主题)必须在一个专用的 Pinia store 中进行集中管理。
|
||||
|
||||
---
|
||||
|
||||
### **1. 状态管理:布局 Store (Pinia)**
|
||||
|
||||
在创建组件之前,首先定义状态管理模块。
|
||||
|
||||
* **文件位置:** `src/store/layout.ts`
|
||||
* **Store 名称:** `useLayoutStore`
|
||||
* **State:**
|
||||
* `isSidebarMini: Ref<boolean>`: 管理导航抽屉的收缩/展开状态,默认为 `false`。
|
||||
* `theme: Ref<'light' | 'dark'>`: 存储当前的主题名称,默认为 `'light'`。
|
||||
* **Actions:**
|
||||
* `toggleSidebar()`: 用于翻转 `isSidebarMini`布尔值的 action。
|
||||
* `setTheme(newTheme: 'light' | 'dark')`: 用于更新 `theme` 状态的 action。
|
||||
|
||||
**约束:** 所有的 state 属性和函数签名都必须拥有精确的 TypeScript 类型。
|
||||
|
||||
### **2. 组件拆解: `Default.vue`**
|
||||
|
||||
#### **a. Script 部分 (`<script setup lang="ts">`)**
|
||||
|
||||
* **导入:**
|
||||
* 从 Vue 和 Vue Router 中导入必要的模块 (`ref`, `watch`, `useRoute`)。
|
||||
* 从 `@/store/layout.ts` 中导入 `useLayoutStore`。
|
||||
* 从 Vuetify 中导入 `useTheme` 和 `useDisplay` 组合式函数。
|
||||
* **Store 实例化:**
|
||||
* 实例化布局 store: `const layoutStore = useLayoutStore();`
|
||||
* 实例化 Vuetify theme 组合式函数: `const theme = useTheme();`
|
||||
* 实例化 Vuetify display 组合式函数: `const display = useDisplay();`
|
||||
* **响应式逻辑:**
|
||||
* 为导航抽屉的可见性创建一个本地 `ref`,`const drawer = ref(true);`。
|
||||
* 使用 `watch` 监听 `useDisplay` 返回的 `display.mobile`。如果视口变为移动端 (`display.mobile.value` 为 `true`),则默认将导航抽屉设置为临时隐藏 (`drawer.value = false`)。
|
||||
* 实现一个 `toggleTheme()` 函数,该函数调用 Pinia store 中的 `setTheme` action,并通过 `theme.global.name.value = layoutStore.theme;` 来更新 Vuetify 的主题。此函数将绑定到一个 UI 控件上。
|
||||
|
||||
#### **b. 模板部分 (`<template>`)**
|
||||
|
||||
* **根元素:** 使用 `<v-layout>` 组件作为最外层包裹容器。
|
||||
* **导航抽屉 (`<v-navigation-drawer>`):**
|
||||
* 将其可见性与 `drawer` ref 进行双向绑定 (`v-model="drawer"`)。
|
||||
* 将其“图标模式”状态与 Pinia store 中的 `isSidebarMini` 进行绑定 (`:rail="layoutStore.isSidebarMini"`)。
|
||||
* 实现一个占位用的 `<v-list>`,包含数个 `<v-list-item>` 用于导航。确保其中一个项目拥有 `active` 属性,以展示单页应用中高亮当前路由的样式。
|
||||
* 在抽屉底部,添加一个 `<template v-slot:append>` 区块。在其中放置一个 `<v-btn>` 用于切换主题。该按钮的图标应根据当前主题变化(例如,暗色主题下为 'mdi-weather-sunny',明亮主题下为 'mdi-weather-night')。将其 `@click` 事件绑定到 `toggleTheme()` 方法。
|
||||
* **应用栏 (`<v-app-bar>`):**
|
||||
* 包含一个 `<v-app-bar-nav-icon>` 按钮,用于切换 `drawer` ref 的值 (`@click="drawer = !drawer"`)。
|
||||
* 包含一个 `<v-toolbar-title>`,用于动态显示当前页面的标题占位符。
|
||||
* 在最右侧添加一个按钮,其图标为 `mdi-dots-vertical`。
|
||||
* **主内容区 (`<v-main>`):**
|
||||
* 主内容区必须是流式的,能自适应剩余空间。
|
||||
* 在 `<v-main>` 内部,渲染 `<router-view />` 组件,使得该布局能够根据当前路由托管不同的页面组件。
|
||||
* **页面 Header:** 在 `<v-main>` 内部,但在 `<router-view>` 之前,添加一个容器(例如,一个带有特定 class 的 `div`)作为页面 Header。在此处显示当前路由的名称作为占位符(例如,使用 `useRoute().name`)。
|
||||
|
||||
### **3. 样式与主题**
|
||||
|
||||
* **禁止硬编码样式:** 绝不使用任何硬编码的颜色值 (例如 `#FFF`, `rgb(0,0,0)`)。
|
||||
* **Vuetify 主题变量:** 所有的自定义样式(如有)必须使用 Vuetify 的主题系统。例如,使用 CSS 变量 `background-color: rgb(var(--v-theme-surface));` 或 SASS 函数 `color: v-theme(on-surface);`。
|
||||
* **主题响应:** 整个布局,包括所有文本、图标和表面,都必须在明亮和暗色主题之间无缝切换,不能有任何视觉瑕疵。
|
||||
|
||||
### **4. 响应式设计**
|
||||
|
||||
* **移动端:** 在移动端视口(由 `useDisplay` 判断),导航抽屉应默认关闭,并以临时/模态模式运行。`<v-app-bar-nav-icon>` 成为打开它的主要方式。
|
||||
* **平板/桌面端:** 在更大的屏幕上,导航抽屉应默认可见。用于切换其 `rail` 状态(完整模式与图标模式)的按钮应位于 `<v-app-bar>` 中。为此,在 `<v-app-bar-nav-icon>` 之后添加一个 `<v-btn icon="mdi-menu" @click.stop="layoutStore.toggleSidebar()"></v-btn>`。
|
||||
6
c-ProjectTonyStack/2-AI优化设计/2-组件/prompt_crypto_icon_.md
Normal file
6
c-ProjectTonyStack/2-AI优化设计/2-组件/prompt_crypto_icon_.md
Normal file
@@ -0,0 +1,6 @@
|
||||
不要使用开源库 **`cryptocurrency-icons`**。该库的内容过时,无法满足需求。使用python3构建脚本,放置于`ProjectTonyStack/TonyMask/scripts/`中,用于从[TradingView](https://tradingview.com/markets/cryptocurrencies/prices-all/) 爬取排名前100的加密货币信息,包含吗svg图标,全名称,简称,大写名称,排名等.该脚本将整合后的元数据和SVG内容生成一个静态的 `crypto-data.ts` 文件,存放于 `src/assets/` 目录下。此文件将导出一个类型为 `CryptoCurrencyInfo[]` 的常量数组。
|
||||
请根据上述的要求,设计一个爬取的python3脚本,注意是在无浏览器环境运行脚本
|
||||
|
||||
|
||||
包含加密货币信息的网页保存在[tradingview-save-704.html](ProjectTonyStack/TonyMask/scripts/tradingview-save-704.html)包含svg图标,加密货币简称,加密货币全称, 请实现一个python3脚本解析其中包含的信息.并且调用CoinGecko API获取加密货币的大写名称,描述,市场排名信息,注意是在无浏览器环境运行脚本
|
||||
该脚本将整合后的元数据和SVG内容生成一个静态的 `crypto-data.ts` 文件,存放于 `src/assets/` 目录下。此文件将导出一个类型为 `CryptoCurrencyInfo[]` 的常量数组。
|
||||
12
c-ProjectTonyStack/2-AI优化设计/2-组件/prompt_currency_selector.md
Normal file
12
c-ProjectTonyStack/2-AI优化设计/2-组件/prompt_currency_selector.md
Normal file
@@ -0,0 +1,12 @@
|
||||
请遵守前端设计规范@/root/ProjectTonyStack/gemini_cli_prompt/TonyMaskPrompt/vue3-typescript-stye.md,针对@/root/ProjectTonyStack/TonyMask/src/components/CurrencySelector.vue 进行如下的修改
|
||||
1 修改图标加载方式,当打开页面后需要即刻展示图标
|
||||
2 当选中币种之后,图标和名称显示不正常,请修改此bug
|
||||
1 当前显示的是object Object 请修改为胶囊按钮状显示图标及名称
|
||||
3 下拉选项,增大图标和名称之间的间隔
|
||||
|
||||
|
||||
请继续@/root/ProjectTonyStack/TonyMask/src/components/CurrencySelector.vue 进行如下的修改
|
||||
1 选中币种需要用胶囊按钮包含展示图标以及名称,现在无图标
|
||||
2 选中币种需要有删除按键,点击取消选择,现在没有
|
||||
3 输入框有删除按键,点击删除全部选中
|
||||
4 输入栏,默认高度可以增高一些
|
||||
74
c-ProjectTonyStack/2-AI优化设计/3-工具/api_utils_time_exchange.md
Normal file
74
c-ProjectTonyStack/2-AI优化设计/3-工具/api_utils_time_exchange.md
Normal file
@@ -0,0 +1,74 @@
|
||||
**背景 (Context):**
|
||||
|
||||
你是一名资深前端架构师,负责为我们的 Vue 3 + TypeScript + Vuetify 3 项目构建基础工具库。该项目要求极高的代码质量、健壮性、可维护性和开发效率。当前任务是设计并实现一个专业、可靠且覆盖全面的时间处理工具模块。
|
||||
W
|
||||
**核心指令 (Core Directive):**
|
||||
|
||||
请严格遵循 `vue3-typescript-stye.md` 中定义的所有规范,创建一个位于 `src/utils/time.ts` 的时间工具模块。此模块必须基于业界公认的最佳实践,封装所有与时间相关的操作,确保前端应用在处理时间数据时的一致性、准确性和健朗性。放弃手动计算时间的原始思路,全面拥抱成熟的第三方库来保证专业性。
|
||||
|
||||
---
|
||||
|
||||
### **技术架构与设计规范 (Technical Architecture & Design Specification)**
|
||||
|
||||
**1. 核心依赖 (Core Dependency):**
|
||||
|
||||
* **唯一指定库:** 项目中所有的时间/日期操作**必须**使用 `date-fns` 和 `date-fns-tz` 库。严禁手动进行复杂的日期计算(如月份加减、年份推算),以规避闰年、月份天数不均等边界问题。
|
||||
* **安装命令:** 使用 pnpm 进行安装:`pnpm add date-fns date-fns-tz`。
|
||||
|
||||
**2. 模块设计与常量定义 (Module Design & Constants):**
|
||||
|
||||
* **文件路径:** `src/utils/time.ts`
|
||||
* **核心常量:**
|
||||
* `API_DATETIME_FORMAT`: 定义一个字符串常量,值为 `"yyyy-MM-dd HH:mm:ss"`,用于统一前后端接口的时间格式。
|
||||
* `TIMEZONE`: 定义一个字符串常量,值为 `"Asia/Shanghai"`,代表所有时间转换的目标时区(东八区)。
|
||||
|
||||
**3. 函数接口定义 (Function Interface Definitions):**
|
||||
|
||||
模块需要导出以下经过精心设计的函数,所有函数必须包含完整的 JSDoc 注释,并拥有精确的 TypeScript 类型签名。
|
||||
|
||||
* **`formatDate(date: Date | number, formatStr: string = API_DATETIME_FORMAT): string`**
|
||||
* **描述:** 将给定的 `Date` 对象或时间戳(`number`)格式化为指定格式的字符串。
|
||||
* **核心逻辑:** 使用 `date-fns-tz` 的 `formatInTimeZone` 函数,确保输出的字符串始终基于我们定义的 `TIMEZONE` (`Asia/Shanghai`)。
|
||||
* **参数:**
|
||||
* `date`: 必选,要格式化的 `Date` 对象或时间戳。
|
||||
* `formatStr`: 可选,目标格式字符串,默认为 `API_DATETIME_FORMAT`。
|
||||
* **返回值:** `string` - 格式化后的时间字符串。
|
||||
|
||||
* **`parseDate(dateStr: string, formatStr: string = API_DATETIME_FORMAT): Date`**
|
||||
* **描述:** 将符合标准格式的时间字符串解析为 `Date` 对象。
|
||||
* **核心逻辑:** 使用 `date-fns` 的 `parse` 函数进行解析。必须健壮地处理无效的输入字符串,如果解析失败,应返回一个无效的 `Date` 对象 (可以通过 `isValid` from `date-fns` 进行检查)。
|
||||
* **参数:**
|
||||
* `dateStr`: 必选,要解析的时间字符串,如 "2025-01-01 08:00:00"。
|
||||
* `formatStr`: 可选,源字符串的格式,默认为 `API_DATETIME_FORMAT`。
|
||||
* **返回值:** `Date` - 解析后的 `Date` 对象。
|
||||
|
||||
* **`getRelativeDate(options: { years?: number; months?: number; days?: number }, fromDate: Date = new Date()): Date`**
|
||||
* **描述:** 一个通用的、获取相对日期的核心函数。可根据当前时间或指定时间,推算过去或未来的某个时间点。
|
||||
* **核心逻辑:** 组合使用 `date-fns` 的 `add` 或 `sub` 系列函数(如 `subYears`, `subMonths`, `subDays`)来实现。操作必须是不可变的(immutable),即不修改原始 `fromDate` 对象。
|
||||
* **参数:**
|
||||
* `options`: 必选,一个包含 `years?`, `months?`, `days?` 的对象,值为负数表示过去,正数表示未来。
|
||||
* `fromDate`: 可选,计算的基准日期,默认为当前时间 `new Date()`。
|
||||
* **返回值:** `Date` - 计算得出的新 `Date` 对象。
|
||||
|
||||
* **便捷函数 (Convenience Functions):**
|
||||
* **描述:** 基于 `getRelativeDate` 和 `formatDate` 封装一系列便捷函数,以满足业务中的常见需求。这些函数使业务代码更具可读性。
|
||||
* **函数列表:**
|
||||
* `getMonthAgo(fromDate?: Date): string` - 获取一个月前的时间字符串。
|
||||
* `getThreeMonthsAgo(fromDate?: Date): string` - 获取三个月前的时间字符串。
|
||||
* `getYearAgo(fromDate?: Date): string` - 获取一年前的时间字符串。
|
||||
* `getThreeYearsAgo(fromDate?: Date): string` - 获取三年前的时间字符串。
|
||||
* **实现细节:** 每个函数内部调用 `getRelativeDate` 计算出 `Date` 对象,然后调用 `formatDate` 格式化为标准 API 字符串。例如,`getMonthAgo` 内部应调用 `getRelativeDate({ months: -1 }, fromDate)`。
|
||||
|
||||
**4. 非功能性需求 (Non-Functional Requirements):**
|
||||
|
||||
* **代码健壮性:**
|
||||
* 所有函数在接收参数时,应考虑 `null` 或 `undefined` 的可能性,并进行适当的防御性编程。
|
||||
* `parseDate` 函数是与外部数据交互的关键入口,其对异常输入的处理能力至关重要。
|
||||
* **性能:**
|
||||
* **必须** 采用 Tree-shaking 友好的方式从 `date-fns` 和 `date-fns-tz` 导入函数,例如:`import { format } from 'date-fns'`,而不是 `import * as dateFns from 'date-fns'`。
|
||||
* **不可变性 (Immutability):**
|
||||
* 所有对 `Date` 对象的操作都不能直接修改原始对象,而应返回一个新的 `Date` 实例。`date-fns` 默认遵循此模式,需确保在实现中维持这一特性。
|
||||
|
||||
**5. 最终产出 (Final Output):**
|
||||
|
||||
请根据以上所有规范,生成 `src/utils/time.ts` 文件的完整 TypeScript 代码。代码必须是生产级别的,包含完整的 JSDoc 注释、精确的类型定义,并体现出卓越的工程实践。
|
||||
9
c-ProjectTonyStack/2-AI优化设计/3-工具/prompt_utils_time.md
Normal file
9
c-ProjectTonyStack/2-AI优化设计/3-工具/prompt_utils_time.md
Normal file
@@ -0,0 +1,9 @@
|
||||
gemini -m gemini-2.5-flash
|
||||
|
||||
|
||||
统一使用[time.ts](TonyMask/src/utils/time.ts)中预定义的方法,修改[binanceOrder.ts](TonyMask/src/store/binanceOrder.ts),修改其中的时间获取方法,修改startTime为string类型
|
||||
|
||||
|
||||
针对@/root/ProjectTonyStack/TonyMask/src/components/binance/OrderDataTable.vue 进行如下的修改
|
||||
1 使用[time.ts](TonyMask/src/utils/time.ts)中预定义的方法修改时间处理方法,表格中统一使用字符串展示时间
|
||||
2 @/root/ProjectTonyStack/TonyMask/tmp/binance_order_response.json 是实际的订单返回数据,修改页面能够正确的处理并显示订单数据,目前无法显示
|
||||
Reference in New Issue
Block a user