feat(web-admin): viewport-fill list height for CRM menu pages

Wire contract, quote, customer, product, and price-letter lists to useListTableViewportHeight.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-09-01 19:00:02 +08:00
1 parent 7f8cd18b4d
commit 6f7461d009
5 files changed
+88 -11

No files matched your search

@@ -156,10 +156,13 @@
<div class="list-body list-wrap"> <!-- 操作按钮栏 + 数据表格 -->
<div ref="contractTableWrapRef" class="contract-table-wrap">
<ListTable table-key="crm-contract"
<ListTable
ref="contractTableRef"
table-key="crm-contract"
v-loading="loading"
class="list-table"
stripe
:height="tableHeight"
:data="contractList"
row-key="id"
@selection-change="handleSelectionChange"
@@ -465,7 +468,9 @@
/>
<el-table-column v-if="isColumnVisible('remark')" label="备注" align="center" prop="remark" min-width="200" show-overflow-tooltip />
</ListTable>
</div>
<div ref="contractBelowRef">
<ListColumnStatsRow
ref="contractStatsRowRef"
storage-key="crm-contract-column-stats"
@@ -474,7 +479,6 @@
:table-wrap-el="contractTableWrapRef"
:get-cell-value="getContractStatCellValue"
/>
</div>
<div class="pagination list-pager-bar">
<div class="list-pager-selected">
@@ -492,6 +496,7 @@
@current-change="getList"
/>
</div>
</div>
</div>
</section>
@@ -526,7 +531,7 @@
</template>
<script setup lang="ts" name="ContractListPage">
import { ref, reactive, computed, onMounted, watch } from 'vue';
import { ref, reactive, computed, onMounted, watch, nextTick } from 'vue';
import {
listContract,
getContract as getContractDetail,
@@ -576,6 +581,7 @@ import ListExportDropdown from '@/components/business/list-export-dropdown/index
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { useListTableViewportHeight } from '@/composables/useListTableViewportHeight'
import { buildStatsRowColumns } from '@/utils/list-column-stats'
const props = withDefaults(
@@ -1027,8 +1033,16 @@ const CONTRACT_MONEY_STAT_PROPS = new Set([
])
const contractTableWrapRef = ref<HTMLElement | null>(null)
const contractBelowRef = ref<HTMLElement | null>(null)
const contractTableRef = ref<any>(null)
const contractStatsRowRef = ref<{ resync?: () => void } | null>(null)
const { tableHeight, recalc: recalcTableHeight } = useListTableViewportHeight({
tableEl: contractTableWrapRef,
pagerEl: contractBelowRef,
tableRef: contractTableRef
})
const contractStatsRowColumns = computed(() =>
buildStatsRowColumns({
leadingSpacers: 2,
@@ -1341,6 +1355,7 @@ onMounted(() => {
} else {
handleViewChange()
}
nextTick(() => recalcTableHeight())
})
watch(formDialogVisible, (visible) => {
+18 -2
View File
@@ -183,8 +183,12 @@
<!-- 数据表格 -->
<div class="list-body list-wrap">
<ListTable table-key="crm-customer"
<div ref="customerTableWrapRef" class="list-table-viewport-anchor">
<ListTable
ref="customerTableRef"
table-key="crm-customer"
class="list-table"
:height="tableHeight"
:data="customerList"
v-loading="loading"
stripe
@@ -615,7 +619,8 @@
show-overflow-tooltip
/>
</ListTable>
<div class="pagination list-pager-bar">
</div>
<div ref="customerPagerRef" class="pagination list-pager-bar">
<div class="list-pager-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
@@ -693,6 +698,7 @@ import ListExportDropdown from '@/components/business/list-export-dropdown/index
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { useListTableViewportHeight } from '@/composables/useListTableViewportHeight'
const dictStore = useDictStore()
@@ -789,6 +795,15 @@ const {
]
})
const customerTableWrapRef = ref<HTMLElement | null>(null)
const customerPagerRef = ref<HTMLElement | null>(null)
const customerTableRef = ref<any>(null)
const { tableHeight, recalc: recalcTableHeight } = useListTableViewportHeight({
tableEl: customerTableWrapRef,
pagerEl: customerPagerRef,
tableRef: customerTableRef
})
const customerExportColumns = computed(() => {
const cols = [...visibleColumns.value]
if (!cols.includes('customerName')) cols.unshift('customerName')
@@ -1025,6 +1040,7 @@ watchDebounced(
onMounted(() => {
getList()
nextTick(() => recalcTableHeight())
})
</script>
+18 -2
View File
@@ -132,8 +132,12 @@
</div>
<div class="list-body list-wrap">
<ListTable table-key="crm-price-letter"
<div ref="priceLetterTableWrapRef" class="list-table-viewport-anchor">
<ListTable
ref="priceLetterTableRef"
table-key="crm-price-letter"
class="list-table"
:height="tableHeight"
:data="tableData"
v-loading="loading"
stripe
@@ -239,8 +243,9 @@
header-align="center"
/>
</ListTable>
</div>
<div class="pagination list-pager-bar">
<div ref="priceLetterPagerRef" class="pagination list-pager-bar">
<div class="list-pager-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
@@ -612,6 +617,7 @@
} from '@/utils/crm/price-letter-template'
import { useDictStore } from '@/store/modules/dict'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { useListTableViewportHeight } from '@/composables/useListTableViewportHeight'
import { formatMoneyDisplay } from '@/utils/money-format'
import { $modal } from '@/utils/modal'
import { fetchProductOptions } from '@/api/crm/product'
@@ -661,6 +667,15 @@
resetToDefault
} = usePersistedListColumns('crm-price-letter-list-columns', listColumnDefs)
const priceLetterTableWrapRef = ref<HTMLElement | null>(null)
const priceLetterPagerRef = ref<HTMLElement | null>(null)
const priceLetterTableRef = ref<any>(null)
const { tableHeight, recalc: recalcTableHeight } = useListTableViewportHeight({
tableEl: priceLetterTableWrapRef,
pagerEl: priceLetterPagerRef,
tableRef: priceLetterTableRef
})
const formDialogVisible = ref(false)
const isEdit = ref(false)
const submitLoading = ref(false)
@@ -1363,6 +1378,7 @@ ${preparedHtml}
onMounted(() => {
getList()
searchProducts('')
nextTick(() => recalcTableHeight())
})
// 筛选区变化时清空简易检索并查询(勿把 quickSearch 放进依赖)
+17 -2
View File
@@ -128,9 +128,12 @@
<!-- 数据表格 -->
<div class="list-body list-wrap">
<div ref="productTableWrapRef" class="list-table-viewport-anchor">
<ListTable
ref="productTableRef"
table-key="crm-product"
class="list-table"
:height="tableHeight"
:data="productList"
v-loading="loading"
stripe
@@ -268,10 +271,11 @@
</template>
</el-table-column>
</ListTable>
</div>
</div>
<!-- 分页 -->
<div class="pagination list-pager-bar">
<div ref="productPagerRef" class="pagination list-pager-bar">
<div class="list-pager-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
@@ -392,7 +396,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { UploadFile } from 'element-plus'
import {
@@ -429,6 +433,7 @@
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { useListTableViewportHeight } from '@/composables/useListTableViewportHeight'
import {
fetchCategoryTree,
fetchProductList,
@@ -487,6 +492,15 @@
const { visibleColumns, checkAll, isIndeterminate, isColumnVisible, resetToDefault } =
usePersistedListColumns('crm-product-list-columns-v2', listColumnDefs)
const productTableWrapRef = ref<HTMLElement | null>(null)
const productPagerRef = ref<HTMLElement | null>(null)
const productTableRef = ref<any>(null)
const { tableHeight, recalc: recalcTableHeight } = useListTableViewportHeight({
tableEl: productTableWrapRef,
pagerEl: productPagerRef,
tableRef: productTableRef
})
function getProductExportFilterParams() {
const quickKeyword = quickSearchKeyword.value?.trim()
return {
@@ -815,6 +829,7 @@
onMounted(() => {
getCategoryOptions()
getList()
nextTick(() => recalcTableHeight())
})
</script>
@@ -133,10 +133,13 @@
<div class="list-body list-wrap">
<div ref="quoteTableWrapRef" class="quote-table-wrap">
<ListTable table-key="crm-quote"
<ListTable
ref="quoteTableRef"
table-key="crm-quote"
v-loading="loading"
class="list-table"
stripe
:height="tableHeight"
:data="quoteList"
row-key="id"
@selection-change="handleSelectionChange"
@@ -389,7 +392,9 @@
/>
<el-table-column v-if="isColumnVisible('remark')" label="备注" align="center" prop="remark" min-width="200" show-overflow-tooltip />
</ListTable>
</div>
<div ref="quoteBelowRef">
<div
ref="quoteStatsScrollRef"
class="quote-list-stats-row"
@@ -439,7 +444,6 @@
</div>
</div>
</div>
</div>
<div class="pagination list-pager-bar">
<div class="list-pager-selected">
@@ -457,6 +461,7 @@
@current-change="getList"
/>
</div>
</div>
</div>
</section>
@@ -878,6 +883,7 @@ import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortc
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { useListTableViewportHeight } from '@/composables/useListTableViewportHeight'
import {
COLUMN_STAT_METHOD_OPTIONS,
QUOTE_MONEY_STAT_PROPS,
@@ -1971,6 +1977,14 @@ function pickColumnStat(prop: string, method: ColumnStatMethod | '') {
}
const quoteTableWrapRef = ref<HTMLElement | null>(null)
const quoteBelowRef = ref<HTMLElement | null>(null)
const quoteTableRef = ref<any>(null)
const { tableHeight, recalc: recalcTableHeight } = useListTableViewportHeight({
tableEl: quoteTableWrapRef,
pagerEl: quoteBelowRef,
tableRef: quoteTableRef
})
const quoteStatsScrollRef = ref<HTMLElement | null>(null)
let quoteStatsScrollSyncing = false
let quoteHeaderResizeObserver: ResizeObserver | null = null
@@ -3053,6 +3067,7 @@ onMounted(() => {
setTimeout(() => scheduleSyncStatsColumnWidths(), 150);
setTimeout(() => scheduleSyncStatsColumnWidths(), 400);
window.addEventListener('resize', scheduleSyncStatsColumnWidths);
nextTick(() => recalcTableHeight());
});
onUnmounted(() => {