**背景 (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)，在资产数量较多时性能更优。

这份设计方案将一个初步的想法，转化为了一个专业、严谨且可直接投入生产的工程实现蓝图。