Skip to content

实例 ​

本节目标:理解“实例”的核心思路,并能把它用于实际开发或面试表达。 一、在useDynamicHeight中onMounted中得到渲染后的高度后发起首次请求; 二、watch中没用使用immediate:true,这样,只有等高度变化后才会重新请求;

<template> <div class="w-full h-full bg-white p-4 rounded-lg overflow-hidden flex flex-col"> <!-- 头部:标题 + 刷新 / 关闭 --> <div class="flex items-center justify-between mb-2"> <div class="text-xs text-[#363636] font-medium leading-3">交易品种</div> <div class="flex items-center space-x-2"> <SvgIcon @click="refresh" :class="['cursor-pointer hover:text-primary', isRefreshing ? 'animate-custom-spin' : '']" name="icon-restart" :width="14" :height="14" /> <span class="cursor-pointer w-4 h-4 flex justify-center items-center" @click="handleClose"> <span class="absolute w-3 h-0.5 bg-gray-500 rounded-sm transform rotate-45" /> <span class="absolute w-3 h-0.5 bg-gray-500 rounded-sm transform -rotate-45" /> </span> </div> </div> <!-- 搜索栏 + 下拉选择 --> <SymbolSearch v-model:keyword="keyword" v-model:selectedType="selectedType" :type-loading="typeLoading" :type-list="typeList" @search="refresh" /> <!-- 表格 --> <div ref="containerRef" class="flex-1 overflow-hidden"> <MVirtualizedTable :loading="listLoading" :columns="_tradeCategoryColumns" :data-source="symbolList" :table-height="tableWrapHeight" :row-height="tableRowHeight" @on-row-click="handleRowClick" @visible-data-updated="handleVisibleDataUpdated" > <template #bodyCell="{ record, column }"> <template v-if="column.key === 'ask'"> <div class="price-wrap" v-socket:[askArg(record)]="socketMessage" /> </template> <template v-else-if="column.key === 'bid'"> <div class="price-wrap" v-socket:[bidArg(record)]="socketMessage" /> </template> <template v-else-if="column.key === 'actions'"> <SvgIcon @click="toggleFavorite(record as ForexSymbol)" :class="[ 'cursor-pointer hover:text-primary', (record as ForexSymbol).is_favorite ? 'text-warning' : '', ]" :name="(record as ForexSymbol).is_favorite ? 'icon-star-fill' : 'icon-star'" :width="20" :height="21" /> </template> </template> </MVirtualizedTable> </div> </div> </template> <script setup lang="ts"> import { computed, watch, toRef, ref, inject, type Ref } from 'vue' import { useI18n } from 'vue-i18n' import { MVirtualizedTable } from '@/components' import SvgIcon from '@/components/svg-icon/SvgIcon.vue' import SymbolSearch from '../_parts/SymbolSearch.vue' import { useSymbols } from '../../_composables/useSymbols' import { useDynamicHeight } from '@/composables' import type { TradingInstrument, ForexSymbol } from '@/types' import type { IAccount } from '@/store' import { socketMessageKey } from '@/constants' import { tradeCategoryColumns } from './settings' import { debounce } from 'lodash' import { parseNumber } from '@/utils' const tableRowHeight = 32 const minPageSize = 10 const extraRows = 5 const props = withDefaults( defineProps<{ accountInfo: IAccount showTable?: boolean }>(), { showTable: true, }, ) const pageSize = ref(30) const emit = defineEmits(['select-symbol', 'all-symbol-loaded', 'visible-data-updated', 'close']) const socketMessage = inject<Ref<string>>(socketMessageKey) ?? ref('') const { t } = useI18n() const { containerRef, height: tableWrapHeight } = useDynamicHeight({ offset: 60, unit: 'px', initialHeight: '500px', minHeight: 100, debounceDelay: 50, onFirstUpdate: (newHeight) => { pageSize.value = calculatePageSize(newHeight) debouncedFetch() }, }) const calculatePageSize = (heightValue: string | number) => { const parsedHeight = parseNumber(heightValue) if (parsedHeight === null) return minPageSize const visibleRows = Math.ceil(parsedHeight / tableRowHeight) return Math.max(minPageSize, visibleRows + extraRows) } const { keyword, selectedType, typeList, typeLoading, symbolList, listLoading, isRefreshing, refresh, toggleFavorite, fetchSymbols, } = useSymbols(toRef(props, 'accountInfo'), pageSize, { autoFetch: false }) const debouncedFetch = debounce(() => { fetchSymbols(pageSize.value) }, 300) const _tradeCategoryColumns = computed(() => tradeCategoryColumns.map((c) => ({ ...c, title: t(c.title) })), ) watch(tableWrapHeight, (newHeight, oldHeight) => { const newHeightValue = parseNumber(newHeight) if (newHeightValue === null) { console.warn('Invalid new height, skipping fetch:', newHeight) return } // 如果旧高度有效,检查高度变化是否足以影响 pageSize const oldHeightValue = parseNumber(oldHeight) if (oldHeightValue !== null) { const heightDiff = Math.abs(newHeightValue - oldHeightValue) if (heightDiff < tableRowHeight) { // 高度变化小于一行高度,不会影响可见行数,跳过 return } } const newPageSize = calculatePageSize(newHeightValue) if (newPageSize !== pageSize.value) { pageSize.value = newPageSize debouncedFetch() } }) const handleClose = () => emit('close') const askArg = (r: ForexSymbol) => ${r.symbol}-0-${r.digits} const bidArg = (r: ForexSymbol) => ${r.symbol}-1-${r.digits} function handleRowClick(record: TradingInstrument, index: number) { emit('select-symbol', record, index) } function handleVisibleDataUpdated(tableData: TradingInstrument[]) { emit( 'visible-data-updated', tableData.map((i) => i.symbol), ) } </script> <style scoped> .animate-custom-spin { animation: spin 0.5s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } </style>

本站总访问 — 次