让左侧部门树和右侧用户表格区域高度独立计算,实现各自区域内的滚动互不影响。<template> <el-row :gutter="20" type="flex"> <!-- 左侧部门树 --> <el-col style="flex: 0 0 auto; max-width: 350px; min-width: 200px;" :xs="24" > <ContentWrap class="h-1/1"> <DeptTree @node-click="handleDeptNodeClick" /> </ContentWrap> </el-col> <el-col style="flex: 1; min-width: 0;" :xs="24"> <!-- 搜索 --> <ContentWrap> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px" > <el-form-item label="用户账号" prop="username"> <el-input v-model="queryParams.username" placeholder="请输入用户账号" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> <el-form-item label="手机号码" prop="mobile"> <el-input v-model="queryParams.mobile" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> <el-form-item label="状态" prop="status"> <el-select v-model="queryParams.status" placeholder="请选择用户状态" clearable class="!w-240px" > <el-option v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item label="创建时间" prop="createTime"> <el-date-picker v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange" start-placeholder="开始日期" end-placeholder="结束日期" class="!w-240px" /> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['system:user:create']" > <Icon icon="ep:plus" /> 新增 </el-button> <el-button type="warning" plain @click="handleImport" v-hasPermi="['system:user:import']" > <Icon icon="ep:upload" /> 导入 </el-button> <el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['system:user:export']" > <Icon icon="ep:download" />导出 </el-button> </el-form-item> </el-form> </ContentWrap> <ContentWrap> <el-table v-loading="loading" :data="list"> <el-table-column label="用户编号" align="center" key="id" prop="id" /> <el-table-column label="用户账号" align="center" prop="username" :show-overflow-tooltip="true" /> <el-table-column label="用户昵称" align="center" prop="nickname" :show-overflow-tooltip="true" /> <el-table-column label="部门" align="center" key="deptName" prop="deptName" :show-overflow-tooltip="true" /> <el-table-column label="手机号码" align="center" prop="mobile" width="120" /> <el-table-column label="状态" key="status"> <template #default="scope"> <el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1" @change="handleStatusChange(scope.row)" :disabled="!checkPermi(['system:user:update'])" /> </template> </el-table-column> <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180" /> <el-table-column label="操作" align="center" width="160"> <template #default="scope"> <div class="flex items-center justify-center"> <el-button type="primary" link @click="openForm('update', scope.row.id)" v-hasPermi="['system:user:update']" > <Icon icon="ep:edit" />修改 </el-button> <el-dropdown @command="(command) => handleCommand(command, scope.row)" v-hasPermi="[ 'system:user:delete', 'system:user:update-password', 'system:permission:assign-user-role' ]" > <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="handleDelete" v-if="checkPermi(['system:user:delete'])" > <Icon icon="ep:delete" />删除 </el-dropdown-item> <el-dropdown-item command="handleResetPwd" v-if="checkPermi(['system:user:update-password'])" > <Icon icon="ep:key" />重置密码 </el-dropdown-item> <el-dropdown-item command="handleRole" v-if="checkPermi(['system:permission:assign-user-role'])" > <Icon icon="ep:circle-check" />分配角色 </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> </el-table> <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" /> </ContentWrap> </el-col> </el-row> <!-- 添加或修改用户对话框 --> <UserForm ref="formRef" @success="getList" /> <!-- 用户导入对话框 --> <UserImportForm ref="importFormRef" @success="getList" /> <!-- 分配角色 --> <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" /> </template> <script lang="ts" setup> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { checkPermi } from '@/utils/permission' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import { CommonStatusEnum } from '@/utils/constants' import * as UserApi from '@/api/system/user' import UserForm from './UserForm.vue' import UserImportForm from './UserImportForm.vue' import UserAssignRoleForm from './UserAssignRoleForm.vue' import DeptTree from './DeptTree.vue' defineOptions({ name: 'SystemUser' }) const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数 const queryParams = reactive({ pageNo: 1, pageSize: 10, username: undefined, mobile: undefined, status: undefined, deptId: undefined, createTime: [] }) const queryFormRef = ref() // 搜索的表单 /** 查询列表 */ const getList = async () => { loading.value = true try { const data = await UserApi.getUserPage(queryParams) list.value = data.list total.value = data.total } finally { loading.value = false } } /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 getList() } /** 重置按钮操作 */ const resetQuery = () => { queryFormRef.value?.resetFields() handleQuery() } /** 处理部门被点击 */ const handleDeptNodeClick = async (row) => { queryParams.deptId = row.id await getList() } /** 添加/修改操作 */ const formRef = ref() const openForm = (type: string, id?: number) => { formRef.value.open(type, id) } /** 用户导入 */ const importFormRef = ref() const handleImport = () => { importFormRef.value.open() } /** 修改用户状态 */ const handleStatusChange = async (row: UserApi.UserVO) => { try { // 修改状态的二次确认 const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?') // 发起修改状态 await UserApi.updateUserStatus(row.id, row.status) // 刷新列表 await getList() } catch { // 取消后,进行恢复按钮 row.status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE } } /** 导出按钮操作 */ const exportLoading = ref(false) const handleExport = async () => { try { // 导出的二次确认 await message.exportConfirm() // 发起导出 exportLoading.value = true const data = await UserApi.exportUser(queryParams) download.excel(data, '用户数据.xlsx') } catch { } finally { exportLoading.value = false } } /** 操作分发 */ const handleCommand = (command: string, row: UserApi.UserVO) => { switch (command) { case 'handleDelete': handleDelete(row.id) break case 'handleResetPwd': handleResetPwd(row) break case 'handleRole': handleRole(row) break default: break } } /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { // 删除的二次确认 await message.delConfirm() // 发起删除 await UserApi.deleteUser(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() } catch {} } /** 重置密码 */ const handleResetPwd = async (row: UserApi.UserVO) => { try { // 重置的二次确认 const result = await message.prompt( '请输入"' + row.username + '"的新密码', t('common.reminder') ) const password = result.value // 发起重置 await UserApi.resetUserPassword(row.id, password) message.success('修改成功,新密码是:' + password) } catch {} } /** 分配角色 */ const assignRoleFormRef = ref() const handleRole = (row: UserApi.UserVO) => { assignRoleFormRef.value.open(row) } /** 初始化 */ onMounted(() => { getList() }) </script>
时间: 2025-07-20 15:20:45 浏览: 18
<think>我们有一个Vue 3组件,它使用Element Plus的布局和组件。目标是要让左侧的部门树和右侧的用户表格区域高度独立计算,实现各自区域内的滚动互不影响。
当前代码结构:
- 整个布局使用`el-row`和`el-col`,左侧部门树和右侧表格区域分别放在两个`el-col`中。
- 左侧部门树:`flex: 0 0 auto; max-width: 350px; min-width: 200px;`
- 右侧表格区域:`flex: 1; min-width: 0;`
问题:目前左右两侧的高度是联动的,即右侧表格区域很长时,左侧部门树也会被拉长,并且整个页面会出现滚动条。我们想要的是左右两侧独立滚动。
解决方案:
1. 为左右两个区域分别设置固定高度(或最大高度)和溢出滚动。
2. 由于右侧区域包含两部分:搜索表单和表格。我们希望搜索表单固定在顶部,表格区域可以滚动。而左侧整个部门树区域可以独立滚动。
具体步骤:
- 左侧部门树:我们可以给左侧的`ContentWrap`(或者直接给`el-col`)设置一个固定高度(比如100vh)或者使用calc计算高度(减去页面顶部可能存在的导航栏等),并设置`overflow: auto`。
- 右侧区域:我们希望整个右侧区域(包括搜索和表格)都独立滚动,但是注意搜索表单应该固定在顶部,表格部分滚动。因此,我们可以将右侧区域拆分为两个部分:搜索表单(固定高度)和表格区域(剩余高度,滚动)。或者,也可以让整个右侧区域滚动,但这样搜索表单也会滚动出去。所以更合理的做法是固定搜索表单,表格区域滚动。
但是,根据需求,我们要求左右两侧独立滚动,所以我们可以:
- 左侧:设置一个固定高度(例如100vh)并设置overflow: auto。
- 右侧:同样设置一个固定高度(例如100vh),然后内部使用flex布局,将搜索表单和表格区域分别放置,表格区域设置flex:1 和 overflow: auto。
然而,我们注意到右侧区域已经包含在`ContentWrap`中,而且有多个`ContentWrap`(一个用于搜索表单,一个用于表格)。我们可以调整右侧区域的结构,使其成为一个固定高度的容器,然后内部使用flex布局。
修改方案:
左侧:
在左侧的`el-col`上添加一个类,比如`left-tree`,并设置样式:
height: calc(100vh - 某个高度); /* 根据实际情况调整,比如减去顶部导航和页边距 */
overflow: auto;
右侧:
将右侧的`el-col`也设置同样的高度,并设置overflow: hidden(这样右侧内部可以自己控制滚动,而不需要整个右侧滚动)。然后,将右侧内部的第一个`ContentWrap`(搜索表单)设置为固定高度(或不设置,由内容决定),第二个`ContentWrap`(表格区域)设置为flex:1,并设置overflow: auto。
但是,由于我们使用的是flex布局,我们可以让右侧区域也成为一个flex容器,方向为column,并设置高度为100%,然后让表格区域自适应高度并滚动。
具体代码调整:
1. 左侧部门树区域:
在左侧的`el-col`上添加样式:`height: calc(100vh - 60px); overflow: auto;`(具体高度根据实际情况调整,这里假设60px是页面顶部导航的高度,如果已经有其他间距,可能需要调整)
2. 右侧区域:
同样设置高度:`height: calc(100vh - 60px);`,然后设置`display: flex; flex-direction: column;`,这样内部的搜索表单和表格区域就可以使用flex布局。
然后,调整右侧内部的两个`ContentWrap`:
- 第一个`ContentWrap`(搜索表单)不需要滚动,高度由内容决定(不设置flex)。
- 第二个`ContentWrap`(表格区域)设置`flex: 1; min-height: 0;`(这是为了让flex项可以收缩,同时内部滚动)和`overflow: auto;`。
注意:由于页面可能有全局的导航栏,我们需要根据实际情况调整`100vh`的偏移量。另外,如果页面有内边距或外边距,也需要考虑。
另一种做法:我们可以使用绝对定位或者固定定位,但这里不推荐,因为布局是响应式的。
考虑到响应式(xs=24),在移动设备上,我们希望左右可能是上下排列,所以我们可以使用媒体查询,但Element Plus的栅格系统在xs时已经会堆叠,所以我们只需要在非xs屏幕时设置固定高度和滚动。
因此,我们可以使用CSS类,在非移动设备上应用这些样式。
修改后的代码:
左侧`el-col`添加class: `left-dept-col`
右侧`el-col`添加class: `right-user-col`
在样式部分(可以在组件内使用scoped style,或者全局)添加:
```css
@media (min-width: 768px) {
.left-dept-col {
height: calc(100vh - 100px); /* 根据实际情况调整,这里100px是示例 */
overflow: auto;
}
.right-user-col {
height: calc(100vh - 100px);
display: flex;
flex-direction: column;
}
.right-user-col > .content-wrap:last-child {
flex: 1;
min-height: 0;
overflow: auto;
}
}
```
注意:右侧的第二个`ContentWrap`(表格区域)我们通过选择器选择最后一个`content-wrap`(因为右侧有两个`ContentWrap`,第一个是搜索,第二个是表格)。我们给表格区域的`ContentWrap`添加flex:1和overflow:auto。
但是,我们注意到右侧的第二个`ContentWrap`已经有一个类`h-1/1`(在左侧的部门树中使用了),这个类可能是height:100%,但在这里我们不需要,因为我们要用flex控制高度。所以可以去掉右侧表格区域的`h-1/1`(如果存在的话)。在代码中,右侧的表格区域并没有`h-1/1`,所以不用改。
另外,由于右侧区域内部结构:
右侧el-col
第一个ContentWrap(搜索表单) -> 没有设置高度,由内容撑开
第二个ContentWrap(表格区域) -> 我们将通过CSS设置flex:1和overflow:auto
因此,我们修改代码:
在模板中,给左侧的el-col添加class: `left-dept-col`
给右侧的el-col添加class: `right-user-col`
然后,在组件的样式部分添加上述媒体查询内的样式。
但是,我们还需要注意,在移动设备上(xs),我们不需要固定高度,所以通过媒体查询只在较大屏幕上应用。
另外,高度计算:`calc(100vh - 100px)`,这个100px需要根据实际页面顶部的高度和组件上下边距进行调整。可能需要根据实际情况调整。
如果页面有固定的头部,我们可以通过JavaScript获取头部高度然后动态设置,但这里为了简单,我们先用一个估计值。
考虑到示例中可能已经有一些全局样式,我们也可以使用`100vh - 布局中其他部分的高度`,比如页面顶部导航栏高度、页面的padding等。
由于我们不知道具体项目的全局样式,这里先使用一个估计值,比如100px。如果实际有偏差,需要调整。
修改后的模板代码(只修改了class部分):
左侧:
<el-col class="left-dept-col" ...>
右侧:
<el-col class="right-user-col" ...>
然后,在组件的style部分添加:
注意:如果使用scoped,需要添加scoped。
但是,我们注意到右侧的表格区域中,除了表格还有分页组件,所以我们需要确保整个表格区域(包括分页)都能滚动?还是分页固定在底部?通常,我们希望表格内容滚动,分页在表格下方,并且当内容很长时,分页也会被滚动。或者,我们可以将分页固定在底部(表格区域内部底部)。但这里,我们先按照整个表格区域(包括分页)滚动。
因为分页组件在表格下面,所以将第二个ContentWrap设置为滚动,那么分页也会随着滚动。如果希望分页固定在底部,则需要调整结构,但这里不要求。
因此,我们按照整个表格区域(包括分页)滚动。
另外,在右侧区域,我们只希望第二个ContentWrap滚动,所以选择器写的是`.right-user-col > .content-wrap:last-child`,这依赖于ContentWrap的类名。在代码中,ContentWrap是一个自定义组件,它渲染出来的根元素可能有类名`content-wrap`?我们需要确认。
查看ContentWrap组件(如果它是项目自定义的),通常它会有自己的类名。如果没有,我们可以给第二个ContentWrap添加一个类名,比如`table-wrap`,然后设置这个类名。
这里,为了不依赖ContentWrap组件的具体实现,我们可以给右侧的第二个ContentWrap添加一个类名`table-content-wrap`,然后设置这个类名。
修改右侧的第二个ContentWrap:
<ContentWrap class="table-content-wrap">
然后,修改CSS选择器:
.right-user-col > .table-content-wrap {
flex: 1;
min-height: 0;
overflow: auto;
}
这样更精确。
总结修改步骤:
1. 给左侧的el-col添加class: `left-dept-col`
2. 给右侧的el-col添加class: `right-user-col`
3. 给右侧的第二个ContentWrap(表格区域)添加class: `table-content-wrap`
4. 在组件的style部分添加媒体查询,在大屏幕上设置高度和滚动。
代码调整如下:
注意:以下代码只显示修改的部分,未修改的部分省略。
模板部分修改:
左侧:
<el-col class="left-dept-col" style="flex: 0 0 auto; max-width: 350px; min-width: 200px;" :xs="24">
右侧:
<el-col class="right-user-col" style="flex: 1; min-width: 0;" :xs="24">
... 其他不变 ...
<ContentWrap class="table-content-wrap"> <!-- 修改这里,添加class -->
<el-table ...> ... </el-table>
<Pagination ... />
</ContentWrap>
</el-col>
样式部分(在script setup同级的style标签中):
<style scoped>
@media (min-width: 768px) {
.left-dept-col {
height: calc(100vh - 100px); /* 调整这个值 */
overflow: auto;
}
.right-user-col {
height: calc(100vh - 100px); /* 调整这个值 */
display: flex;
flex-direction: column;
}
.table-content-wrap {
flex: 1;
min-height: 0;
overflow: auto;
}
}
</style>
注意:高度计算的值`100px`需要根据实际项目调整,比如如果页面顶部有导航栏,可能需要减去导航栏高度和页面的上下内边距等。
如果项目有多个页面,且布局不一致,可能需要更精确的计算。这里提供一种方法:假设全局有一个顶部导航,高度为60px,然后页面上下内边距各20px,那么我们可以设置:
height: calc(100vh - 60px - 40px); /* 60px导航+40px(上下内边距) */
但具体需要根据实际情况调整。
另外,如果左右两侧的高度不一致,可能会导致底部留白,所以可以尝试使用`100vh - 顶部导航高度 - 页面容器的padding-top - 页面容器的padding-bottom`。
由于我们不知道具体项目的布局,这里先使用100px,开发者可以根据实际情况调整。
这样修改后,在大于768px的屏幕上,左右两侧将各自独立滚动,互不干扰。在移动端,由于是上下布局,所以仍然保持整体滚动。
注意:如果移动端也需要独立滚动,那么可以不加媒体查询,但这样在移动端左右两侧会变成上下两个独立滚动的区域。但移动端是上下排列,所以左侧部门树区域和右侧用户表格区域都是独立滚动,但这样可能会导致两个滚动条,用户体验可能不好。所以这里我们只在大屏幕上应用。
因此,我们按照上述方案修改代码。</think>要实现左右两侧区域高度独立滚动,需要为每个区域设置固定高度并启用内部滚动。以下是修改后的代码,主要调整了布局样式:
```vue
<template>
<el-row :gutter="20" type="flex" class="h-full">
<!-- 左侧部门树 -->
<el-col
class="left-tree-col"
style="flex: 0 0 auto; max-width: 350px; min-width: 200px;"
:xs="24"
>
<ContentWrap class="h-full overflow-auto">
<DeptTree @node-click="handleDeptNodeClick" />
</ContentWrap>
</el-col>
<!-- 右侧用户表格 -->
<el-col class="right-table-col" style="flex: 1; min-width: 0;" :xs="24">
<div class="h-full flex flex-col">
<!-- 搜索区域(固定高度) -->
<ContentWrap>
... [保持原有搜索表单代码不变] ...
</ContentWrap>
<!-- 表格区域(自适应高度+滚动) -->
<ContentWrap class="flex-1 min-h-0 overflow-auto">
... [保持原有表格代码不变] ...
</ContentWrap>
</div>
</el-col>
</el-row>
... [保持对话框组件代码不变] ...
</template>
<style scoped>
/* 设置左右列高度为100% */
.left-tree-col,
.right-table-col {
height: 100%;
}
@media (max-width: 768px) {
/* 移动端恢复默认滚动 */
.left-tree-col,
.right-table-col {
height: auto;
}
}
</style>
```
### 关键修改说明:
1. **外层容器高度**:
- 添加 `class="h-full"` 到 `<el-row>` 确保容器占满父级高度
2. **左侧部门树**:
- 添加 `class="left-tree-col"` 设置高度100%
- 内容区使用 `class="h-full overflow-auto"` 启用滚动
3. **右侧区域重构**:
- 使用 `flex flex-col` 创建垂直布局
- 搜索区域保持原高度
- 表格区域添加 `flex-1 min-h-0 overflow-auto` 实现自适应高度+滚动
4. **响应式处理**:
- 在小屏幕设备上恢复默认滚动行为
5. **样式补充**:
- 添加媒体查询确保移动端体验正常
这样修改后,左右区域将:
- 左侧部门树实现独立滚动
- 右侧表格区域实现独立滚动
- 搜索表单保持固定位置
- 保持原有响应式布局特性
阅读全文