Commit 29eba1a3 by DaiJiezhang

feat: 列表页固定一屏高度,数据在表格内滚动

五个列表页不再随数据量把页面撑长,改为锁定在一屏内、表头固定、行区域内部滚动、分页始终可见:
手机号资产、企微资料、公司档案、设备资产、账号与权限。

实现要点:
- app-shell 锁定视口高度,滚动分给侧栏与内容区各自承担
- 列表页根节点用 min-height:100% 的 flex 纵向布局,表格面板吃掉剩余空间
- 表格外层新增 grid-wrap 容器并绝对定位 el-table,否则 el-table 会用自身内容高度
  反向撑开父级导致 flex 收缩失效;同时必须显式写 height:100%,
  盖掉 Element Plus 自带的 .el-table{height:fit-content}
- grid-wrap 设 min-height:220px 兜底,窗口过矮时改由内容区整页滚动,避免表格被压成一两行
- 断点 640px 以下整体回退为整页滚动,小屏可视行数太少反而不好用

顺带修复设备资产列表始终报「数据库字段未同步」:
AssetDeviceEntity 的 imageAttachment1/2 被 MyBatis-Plus 推导成 image_attachment1,
与表中的 image_attachment_1 对不上导致 SELECT 抛 BadSqlGrammarException,
补 @TableField 显式指定列名。

验证:1280x918 与 420px 矮窗口下逐页实测表头固定、内部滚动、分页可见与兜底生效;
设备页以 30 条真实数据验证滚动,并实测图片上传、列表读回与页面渲染全链路;
前端 27 项 e2e 全部通过。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 2857e4ea
package com.xyw.console.asset.entity; package com.xyw.console.asset.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.Data; import lombok.Data;
...@@ -15,7 +16,10 @@ import lombok.EqualsAndHashCode; ...@@ -15,7 +16,10 @@ import lombok.EqualsAndHashCode;
@TableName("as_asset_device") @TableName("as_asset_device")
public class AssetDeviceEntity extends AssetBaseEntity { public class AssetDeviceEntity extends AssetBaseEntity {
private String deviceName; private String deviceName;
/** 必须显式指定列名:MyBatis-Plus 的驼峰转下划线不会在数字前加下划线,会把 imageAttachment1 推成 image_attachment1,与表中的 image_attachment_1 对不上并抛 BadSqlGrammarException。 */
@TableField("image_attachment_1")
private String imageAttachment1; private String imageAttachment1;
@TableField("image_attachment_2")
private String imageAttachment2; private String imageAttachment2;
private Long userPersonId; private Long userPersonId;
private String userUsageStatus; private String userUsageStatus;
......
...@@ -140,7 +140,7 @@ export default { ...@@ -140,7 +140,7 @@ export default {
<section class="phone-asset-list-page company-profile-page"> <section class="phone-asset-list-page company-profile-page">
<header class="phone-asset-list-page__header"><div><h2>公司档案</h2><p class="wecom-account-page__eyebrow">COMPANY PROFILES</p></div><el-button v-if="canEdit" class="phone-asset-list-page__add" type="primary" @click="openCreate">新增公司档案</el-button></header> <header class="phone-asset-list-page__header"><div><h2>公司档案</h2><p class="wecom-account-page__eyebrow">COMPANY PROFILES</p></div><el-button v-if="canEdit" class="phone-asset-list-page__add" type="primary" @click="openCreate">新增公司档案</el-button></header>
<section class="phone-asset-list-page__panel phone-asset-list-page__search"><el-form class="phone-asset-list-page__filters" @submit.prevent="submitSearch"><el-input v-model="filters.keyword" placeholder="公司名称、简称、信用代码、地址、联系人或联系方式" clearable @input="scheduleSearch" @clear="scheduleSearch" @keydown.enter.prevent="submitSearch" /><el-button @click="resetSearch">重置</el-button></el-form></section> <section class="phone-asset-list-page__panel phone-asset-list-page__search"><el-form class="phone-asset-list-page__filters" @submit.prevent="submitSearch"><el-input v-model="filters.keyword" placeholder="公司名称、简称、信用代码、地址、联系人或联系方式" clearable @input="scheduleSearch" @clear="scheduleSearch" @keydown.enter.prevent="submitSearch" /><el-button @click="resetSearch">重置</el-button></el-form></section>
<section class="phone-asset-list-page__panel phone-asset-list-page__table"><header class="phone-asset-list-page__table-header"><h3>公司档案列表</h3><span>共 {{ total }} 条</span></header><el-table v-loading="loading" :data="records" empty-text="暂无匹配数据" class="phone-asset-list-page__grid company-profile-page__grid"><el-table-column label="公司名称" min-width="200"><template #default="{ row }">{{ formatValue(row.companyName) }}</template></el-table-column><el-table-column label="公司简称" min-width="150"><template #default="{ row }">{{ formatValue(row.shortName) }}</template></el-table-column><el-table-column label="统一社会信用代码" min-width="210"><template #default="{ row }">{{ formatValue(row.unifiedSocialCreditCode) }}</template></el-table-column><el-table-column label="地址" min-width="220" show-overflow-tooltip><template #default="{ row }">{{ formatValue(row.address) }}</template></el-table-column><el-table-column label="联系人" min-width="130"><template #default="{ row }">{{ formatValue(row.contactName) }}</template></el-table-column><el-table-column label="联系方式" min-width="170"><template #default="{ row }">{{ formatValue(row.contactValue) }}</template></el-table-column><el-table-column label="创建时间" min-width="180"><template #default="{ row }">{{ formatValue(row.createTime) }}</template></el-table-column><el-table-column label="更新时间" min-width="180"><template #default="{ row }">{{ formatValue(row.updateTime) }}</template></el-table-column></el-table><footer v-if="total" class="phone-asset-list-page__pagination"><span>共 {{ total }} 条</span><el-pagination layout="prev, pager, next" :current-page="filters.page" :page-size="filters.size" :total="total" @current-change="changePage" /></footer></section> <section class="phone-asset-list-page__panel phone-asset-list-page__table"><header class="phone-asset-list-page__table-header"><h3>公司档案列表</h3><span>共 {{ total }} 条</span></header><div class="phone-asset-list-page__grid-wrap"><el-table v-loading="loading" :data="records" empty-text="暂无匹配数据" class="phone-asset-list-page__grid company-profile-page__grid"><el-table-column label="公司名称" min-width="200"><template #default="{ row }">{{ formatValue(row.companyName) }}</template></el-table-column><el-table-column label="公司简称" min-width="150"><template #default="{ row }">{{ formatValue(row.shortName) }}</template></el-table-column><el-table-column label="统一社会信用代码" min-width="210"><template #default="{ row }">{{ formatValue(row.unifiedSocialCreditCode) }}</template></el-table-column><el-table-column label="地址" min-width="220" show-overflow-tooltip><template #default="{ row }">{{ formatValue(row.address) }}</template></el-table-column><el-table-column label="联系人" min-width="130"><template #default="{ row }">{{ formatValue(row.contactName) }}</template></el-table-column><el-table-column label="联系方式" min-width="170"><template #default="{ row }">{{ formatValue(row.contactValue) }}</template></el-table-column><el-table-column label="创建时间" min-width="180"><template #default="{ row }">{{ formatValue(row.createTime) }}</template></el-table-column><el-table-column label="更新时间" min-width="180"><template #default="{ row }">{{ formatValue(row.updateTime) }}</template></el-table-column></el-table></div><footer v-if="total" class="phone-asset-list-page__pagination"><span>共 {{ total }} 条</span><el-pagination layout="prev, pager, next" :current-page="filters.page" :page-size="filters.size" :total="total" @current-change="changePage" /></footer></section>
<el-dialog v-model="dialogVisible" class="phone-asset-modal" modal-class="phone-asset-modal-mask" title="新增公司档案" width="560px"> <el-dialog v-model="dialogVisible" class="phone-asset-modal" modal-class="phone-asset-modal-mask" title="新增公司档案" width="560px">
<el-form class="phone-asset-modal__form" label-width="112px" @submit.prevent="submitCreate"> <el-form class="phone-asset-modal__form" label-width="112px" @submit.prevent="submitCreate">
<el-form-item class="phone-asset-modal__form-row" label="公司名称" required><el-input v-model="form.companyName" maxlength="100" autocomplete="off" placeholder="请输入公司名称" /></el-form-item> <el-form-item class="phone-asset-modal__form-row" label="公司名称" required><el-input v-model="form.companyName" maxlength="100" autocomplete="off" placeholder="请输入公司名称" /></el-form-item>
......
/* 文件用途(白话):仅为设备资产管理页面提供样式,避免修改正在使用的全局 app.css。 */ /* 文件用途(白话):仅为设备资产管理页面提供样式,避免修改正在使用的全局 app.css。 */
.device-asset-page{max-width:1680px;margin:0 auto;padding:30px 4px}.device-asset-page__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.device-asset-page__header h2{margin:0;font-size:28px}.device-asset-page__panel{padding:20px;margin-bottom:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px}.device-asset-page__filters{display:flex;flex-wrap:wrap;gap:12px}.device-asset-page__filters .el-input,.device-asset-page__filters .el-select{width:210px}.device-asset-page__images{display:flex;gap:14px;flex-wrap:wrap}.device-asset-page__image-slot{width:150px}.device-asset-page__preview{display:flex;width:132px;height:96px;margin:8px 0;align-items:center;justify-content:center;overflow:hidden;border:1px dashed #cbd5e1;border-radius:8px;background:#f8fafc}.device-asset-page__preview img{width:100%;height:100%;object-fit:cover}.device-asset-page__pagination{display:flex;justify-content:space-between;align-items:center;padding-top:16px}.device-asset-page__image-cell img{width:44px;height:44px;object-fit:cover;border-radius:6px;cursor:pointer}@media(max-width:700px){.device-asset-page{padding:20px 0}.device-asset-page__header{align-items:stretch;flex-direction:column;gap:12px}.device-asset-page__filters .el-input,.device-asset-page__filters .el-select{width:100%}.device-asset-page__pagination{align-items:flex-start;flex-direction:column}} .device-asset-page{max-width:1680px;margin:0 auto;padding:30px 4px}.device-asset-page__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.device-asset-page__header h2{margin:0;font-size:28px}.device-asset-page__panel{padding:20px;margin-bottom:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px}.device-asset-page__filters{display:flex;flex-wrap:wrap;gap:12px}.device-asset-page__filters .el-input,.device-asset-page__filters .el-select{width:210px}.device-asset-page__images{display:flex;gap:14px;flex-wrap:wrap}.device-asset-page__image-slot{width:150px}.device-asset-page__preview{display:flex;width:132px;height:96px;margin:8px 0;align-items:center;justify-content:center;overflow:hidden;border:1px dashed #cbd5e1;border-radius:8px;background:#f8fafc}.device-asset-page__preview img{width:100%;height:100%;object-fit:cover}.device-asset-page__pagination{display:flex;justify-content:space-between;align-items:center;padding-top:16px}.device-asset-page__image-cell img{width:44px;height:44px;object-fit:cover;border-radius:6px;cursor:pointer}/* 固定高度骨架:宽屏下页面锁死一屏,表格内部滚动;断点与 app.css 中的同类规则保持一致。 */
@media(min-width:641px){/* 用 min-height 而不是 height:正常一屏不出外层滚动条,窗口过矮时页面被撑高改由内容区整页滚动。 */.device-asset-page{display:flex;flex-direction:column;min-height:100%;padding-bottom:24px}.device-asset-page__header,.device-asset-page__panel,.device-asset-page__pagination{flex:0 0 auto}.device-asset-page__table{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;margin-bottom:0}/* 表格必须绝对定位:el-table 会用自身内容高度反向撑开父级,留在文档流里 flex 就收缩不下去。 *//* min-height 220px 是兜底:表头约 40px,再留三行左右可视区,低于这个高度就不再压缩表格。 */.device-asset-page__grid-wrap{position:relative;flex:1 1 auto;min-height:220px}/* height 必须显式写:Element Plus 自带 .el-table{height:fit-content},只给 inset 会被它按内容高度顶掉。 */.device-asset-page__grid-wrap > .el-table{position:absolute;inset:0;height:100%}.device-asset-page__grid-wrap > .el-table > .el-table__inner-wrapper{height:100%}.device-asset-page__grid-wrap .el-table__body-wrapper{flex:1 1 auto;min-height:0}.device-asset-page__grid-wrap .el-table__body-wrapper > .el-scrollbar{height:100%}}
@media(max-width:700px){.device-asset-page{padding:20px 0}.device-asset-page__header{align-items:stretch;flex-direction:column;gap:12px}.device-asset-page__filters .el-input,.device-asset-page__filters .el-select{width:100%}.device-asset-page__pagination{align-items:flex-start;flex-direction:column}}
...@@ -291,7 +291,7 @@ export default { ...@@ -291,7 +291,7 @@ export default {
<el-config-provider :locale="elementLocale"><section class="phone-asset-page phone-asset-list-page"> <el-config-provider :locale="elementLocale"><section class="phone-asset-page phone-asset-list-page">
<header class="phone-asset-list-page__header"><h2>手机号资产</h2><el-button v-if="canEdit" class="phone-asset-list-page__add" type="primary" @click="openCreate">新增手机号资产</el-button></header> <header class="phone-asset-list-page__header"><h2>手机号资产</h2><el-button v-if="canEdit" class="phone-asset-list-page__add" type="primary" @click="openCreate">新增手机号资产</el-button></header>
<section class="phone-asset-list-page__panel phone-asset-list-page__search" aria-label="筛选手机号资产"><el-form class="phone-asset-list-page__filters" @submit.prevent="submitSearch"><el-input v-model="filters.phoneNumber" maxlength="11" inputmode="numeric" placeholder="手机号前3位、后4位或完整号码" @input="limitSearchPhone" @keydown.enter.prevent="submitSearch" /><el-input v-model="filters.iccid" maxlength="20" placeholder="请输入 ICCID" @input="scheduleSearch" @keydown.enter.prevent="submitSearch" /><el-input v-model="filters.realNameOwner" placeholder="请输入实名人" @input="scheduleSearch" @keydown.enter.prevent="submitSearch" /><el-select v-model="filters.disposalStatus" placeholder="使用状态:" clearable @change="changeStatus" @clear="restoreAllDisposalStatuses"><template #prefix>使用状态:</template><el-option label="全部" value="ALL" /><el-option label="正常" value="正常使用" /><el-option label="闲置" value="闲置" /><el-option label="停机" value="停机" /><el-option label="注销" value="已注销" /></el-select><el-button @click="resetSearch">重置</el-button></el-form></section> <section class="phone-asset-list-page__panel phone-asset-list-page__search" aria-label="筛选手机号资产"><el-form class="phone-asset-list-page__filters" @submit.prevent="submitSearch"><el-input v-model="filters.phoneNumber" maxlength="11" inputmode="numeric" placeholder="手机号前3位、后4位或完整号码" @input="limitSearchPhone" @keydown.enter.prevent="submitSearch" /><el-input v-model="filters.iccid" maxlength="20" placeholder="请输入 ICCID" @input="scheduleSearch" @keydown.enter.prevent="submitSearch" /><el-input v-model="filters.realNameOwner" placeholder="请输入实名人" @input="scheduleSearch" @keydown.enter.prevent="submitSearch" /><el-select v-model="filters.disposalStatus" placeholder="使用状态:" clearable @change="changeStatus" @clear="restoreAllDisposalStatuses"><template #prefix>使用状态:</template><el-option label="全部" value="ALL" /><el-option label="正常" value="正常使用" /><el-option label="闲置" value="闲置" /><el-option label="停机" value="停机" /><el-option label="注销" value="已注销" /></el-select><el-button @click="resetSearch">重置</el-button></el-form></section>
<section class="phone-asset-list-page__panel phone-asset-list-page__table"><header class="phone-asset-list-page__table-header"><h3>资产列表</h3><span>共 {{ total }} 条</span></header><el-table v-loading="loading" :data="records" empty-text="暂无匹配数据" class="phone-asset-list-page__grid"><el-table-column prop="phoneNumber" label="手机号" min-width="150" show-overflow-tooltip /><el-table-column label="号码类型" min-width="120"><template #default="{ row }"><a v-if="row.numberType === 'EXTERNAL' && row.sourceAssetType === 'WECOM'" class="phone-asset-list-page__external-link" :href="'#/reference/wecom?phoneAssetId=' + row.id">外部号码</a><span v-else>{{ row.numberType === 'EXTERNAL' ? '外部号码' : '自有号码' }}</span></template></el-table-column><el-table-column prop="cardType" label="运营商" min-width="110" show-overflow-tooltip /><el-table-column prop="iccid" label="ICCID" min-width="220" show-overflow-tooltip /><el-table-column prop="realNameOwner" label="实名人" min-width="150" show-overflow-tooltip /><el-table-column prop="managementType" label="管理模式" min-width="120" show-overflow-tooltip /><el-table-column label="使用状态" min-width="130"><template #default="{ row }"><span class="phone-asset-list-page__status"><i :class="['phone-asset-list-page__status-dot', row.disposalStatus]"></i>{{ formatDisposalStatus(row.disposalStatus) }}</span></template></el-table-column><el-table-column prop="deviceId" label="关联设备(ID)" min-width="150" show-overflow-tooltip /><el-table-column v-if="canEdit" label="操作" width="120"><template #default="{ row }"><span class="phone-asset-list-page__actions"><el-button link @click="openEdit(row)">编辑</el-button><el-button link type="danger" @click="confirmDelete(row)">删除</el-button></span></template></el-table-column></el-table><footer v-if="total" class="phone-asset-list-page__pagination"><span>共 {{ total }} 条</span><el-pagination layout="sizes, prev, pager, next, jumper" :current-page="filters.page" :page-size="filters.size" :page-sizes="[5,10,20,50]" :total="total" @current-change="changePage" @size-change="changePageSize" /></footer></section> <section class="phone-asset-list-page__panel phone-asset-list-page__table"><header class="phone-asset-list-page__table-header"><h3>资产列表</h3><span>共 {{ total }} 条</span></header><div class="phone-asset-list-page__grid-wrap"><el-table v-loading="loading" :data="records" empty-text="暂无匹配数据" class="phone-asset-list-page__grid"><el-table-column prop="phoneNumber" label="手机号" min-width="150" show-overflow-tooltip /><el-table-column label="号码类型" min-width="120"><template #default="{ row }"><a v-if="row.numberType === 'EXTERNAL' && row.sourceAssetType === 'WECOM'" class="phone-asset-list-page__external-link" :href="'#/reference/wecom?phoneAssetId=' + row.id">外部号码</a><span v-else>{{ row.numberType === 'EXTERNAL' ? '外部号码' : '自有号码' }}</span></template></el-table-column><el-table-column prop="cardType" label="运营商" min-width="110" show-overflow-tooltip /><el-table-column prop="iccid" label="ICCID" min-width="220" show-overflow-tooltip /><el-table-column prop="realNameOwner" label="实名人" min-width="150" show-overflow-tooltip /><el-table-column prop="managementType" label="管理模式" min-width="120" show-overflow-tooltip /><el-table-column label="使用状态" min-width="130"><template #default="{ row }"><span class="phone-asset-list-page__status"><i :class="['phone-asset-list-page__status-dot', row.disposalStatus]"></i>{{ formatDisposalStatus(row.disposalStatus) }}</span></template></el-table-column><el-table-column prop="deviceId" label="关联设备(ID)" min-width="150" show-overflow-tooltip /><el-table-column v-if="canEdit" label="操作" width="120"><template #default="{ row }"><span class="phone-asset-list-page__actions"><el-button link @click="openEdit(row)">编辑</el-button><el-button link type="danger" @click="confirmDelete(row)">删除</el-button></span></template></el-table-column></el-table></div><footer v-if="total" class="phone-asset-list-page__pagination"><span>共 {{ total }} 条</span><el-pagination layout="sizes, prev, pager, next, jumper" :current-page="filters.page" :page-size="filters.size" :page-sizes="[5,10,20,50]" :total="total" @current-change="changePage" @size-change="changePageSize" /></footer></section>
<el-dialog v-model="dialogVisible" class="phone-asset-modal" modal-class="phone-asset-modal-mask" :title="editingId === null ? '新增手机号资产' : '编辑手机号资产'" width="560px" @opened="resetDialogScroll"> <el-dialog v-model="dialogVisible" class="phone-asset-modal" modal-class="phone-asset-modal-mask" :title="editingId === null ? '新增手机号资产' : '编辑手机号资产'" width="560px" @opened="resetDialogScroll">
<el-form class="phone-asset-modal__form" label-width="96px"> <el-form class="phone-asset-modal__form" label-width="96px">
<el-form-item class="phone-asset-modal__form-row" label="手机号" required> <el-form-item class="phone-asset-modal__form-row" label="手机号" required>
......
...@@ -27,4 +27,4 @@ export default { setup() { const users = ref([]); const lockedAccounts = ref([]) ...@@ -27,4 +27,4 @@ export default { setup() { const users = ref([]); const lockedAccounts = ref([])
function openPassword(user) { target.value = user; password.value = ''; passwordOpen.value = true; } function openPassword(user) { target.value = user; password.value = ''; passwordOpen.value = true; }
/** 代码作用(白话):把新密码提交给后端并立即从页面内存清除;关联文件:system-user-api-client.js、V1__system_user_auth_permissions.sql。关联逻辑(调用链/数据流):新密码 -> BCrypt -> 数据库触发器 -> 旧会话失效。 */ /** 代码作用(白话):把新密码提交给后端并立即从页面内存清除;关联文件:system-user-api-client.js、V1__system_user_auth_permissions.sql。关联逻辑(调用链/数据流):新密码 -> BCrypt -> 数据库触发器 -> 旧会话失效。 */
async function savePassword() { try { await resetSystemUserPassword(target.value.id, password.value); password.value = ''; passwordOpen.value = false; ElMessage.success('密码已更新,旧会话已失效'); await load(); } catch (error) { ElMessage.error(error.message || '密码更新失败'); } } async function savePassword() { try { await resetSystemUserPassword(target.value.id, password.value); password.value = ''; passwordOpen.value = false; ElMessage.success('密码已更新,旧会话已失效'); await load(); } catch (error) { ElMessage.error(error.message || '密码更新失败'); } }
load(); return { users, lockedAccounts, loading, dialogOpen, passwordOpen, form, pages, roles, editingId, isDeveloper, isAdministratorRole, target, password, load, openCreate, openEdit, save, openPassword, savePassword, unlockAccount, formatLockTime }; }, template: `<section class="user-permission-page"><header class="page-header"><div><p class="eyebrow">设置 / 账号与权限</p><h2>账号与权限</h2><p>开发者账号 Jeddy 不会在此页面显示;逐页权限由此面板独立配置。</p></div><el-button type="primary" @click="openCreate">新增账号</el-button></header><section v-if="lockedAccounts.length" class="reference-card"><h3>登录锁定({{ lockedAccounts.length }})</h3><p>连续输错密码 20 次的账号会被锁定 24 小时。若为误触发或被他人恶意锁定,可在此立即解除。</p><el-table :data="lockedAccounts"><el-table-column prop="username" label="账号名" /><el-table-column prop="failures" label="失败次数" width="110" /><el-table-column label="锁定至"><template #default="scope">{{ formatLockTime(scope.row.lockedUntil) }}</template></el-table-column><el-table-column label="操作" width="140"><template #default="scope"><el-button link type="primary" @click="unlockAccount(scope.row)">解除锁定</el-button></template></el-table-column></el-table></section><section class="reference-card"><el-table :data="users" v-loading="loading"><el-table-column prop="username" label="用户名" /><el-table-column prop="roleCode" label="角色" /><el-table-column prop="status" label="状态" /><el-table-column label="操作" width="210"><template #default="scope"><el-button link @click="openEdit(scope.row)">编辑权限</el-button><el-button v-if="isDeveloper" link type="primary" @click="openPassword(scope.row)">设置密码</el-button></template></el-table-column></el-table></section><el-dialog v-model="dialogOpen" :title="editingId ? '编辑账号与权限' : '新增账号'" width="680px"><el-form label-width="112px"><el-form-item label="用户名" v-if="!editingId"><el-input v-model="form.username" /></el-form-item><el-form-item label="角色"><el-select v-model="form.roleCode"><el-option v-for="role in roles" :key="role.value" :label="role.label" :value="role.value" /></el-select></el-form-item><el-form-item label="账号状态" v-if="editingId"><el-select v-model="form.status"><el-option label="启用" value="ACTIVE" /><el-option label="禁用" value="DISABLED" /></el-select></el-form-item><el-form-item label="初始密码" v-if="!editingId && isDeveloper"><el-input v-model="form.password" type="password" show-password /><small>12-72 位</small></el-form-item><el-alert v-if="!editingId && !isDeveloper" title="超级管理员创建的账号会保持禁用且不设密码,需由开发者设置密码后再启用。" type="info" :closable="false" /><el-divider>页面权限</el-divider><div v-if="isAdministratorRole" class="permission-hint">超级管理员固定拥有全部页面的编辑权限。</div><el-form-item v-for="page in pages" :key="page.key" :label="page.label"><el-radio-group v-model="form.pagePermissions[page.key]" :disabled="isAdministratorRole"><el-radio value="NONE">无权限</el-radio><el-radio value="READ">只读</el-radio><el-radio value="EDIT">编辑</el-radio></el-radio-group></el-form-item></el-form><template #footer><el-button @click="dialogOpen=false">取消</el-button><el-button type="primary" @click="save">保存</el-button></template></el-dialog><el-dialog v-model="passwordOpen" title="设置账号密码" width="420px"><p>仅开发者可以设置密码:{{ target?.username }}</p><el-input v-model="password" type="password" show-password placeholder="12-72 位且不含用户名" /><template #footer><el-button @click="passwordOpen=false">取消</el-button><el-button type="primary" @click="savePassword">确认设置</el-button></template></el-dialog></section>` }; load(); return { users, lockedAccounts, loading, dialogOpen, passwordOpen, form, pages, roles, editingId, isDeveloper, isAdministratorRole, target, password, load, openCreate, openEdit, save, openPassword, savePassword, unlockAccount, formatLockTime }; }, template: `<section class="user-permission-page"><header class="page-header"><div><p class="eyebrow">设置 / 账号与权限</p><h2>账号与权限</h2><p>开发者账号 Jeddy 不会在此页面显示;逐页权限由此面板独立配置。</p></div><el-button type="primary" @click="openCreate">新增账号</el-button></header><section v-if="lockedAccounts.length" class="reference-card user-permission-page__locked"><h3>登录锁定({{ lockedAccounts.length }})</h3><p>连续输错密码 20 次的账号会被锁定 24 小时。若为误触发或被他人恶意锁定,可在此立即解除。</p><el-table :data="lockedAccounts"><el-table-column prop="username" label="账号名" /><el-table-column prop="failures" label="失败次数" width="110" /><el-table-column label="锁定至"><template #default="scope">{{ formatLockTime(scope.row.lockedUntil) }}</template></el-table-column><el-table-column label="操作" width="140"><template #default="scope"><el-button link type="primary" @click="unlockAccount(scope.row)">解除锁定</el-button></template></el-table-column></el-table></section><section class="reference-card user-permission-page__accounts"><div class="user-permission-page__grid-wrap"><el-table :data="users" v-loading="loading"><el-table-column prop="username" label="用户名" /><el-table-column prop="roleCode" label="角色" /><el-table-column prop="status" label="状态" /><el-table-column label="操作" width="210"><template #default="scope"><el-button link @click="openEdit(scope.row)">编辑权限</el-button><el-button v-if="isDeveloper" link type="primary" @click="openPassword(scope.row)">设置密码</el-button></template></el-table-column></el-table></div></section><el-dialog v-model="dialogOpen" :title="editingId ? '编辑账号与权限' : '新增账号'" width="680px"><el-form label-width="112px"><el-form-item label="用户名" v-if="!editingId"><el-input v-model="form.username" /></el-form-item><el-form-item label="角色"><el-select v-model="form.roleCode"><el-option v-for="role in roles" :key="role.value" :label="role.label" :value="role.value" /></el-select></el-form-item><el-form-item label="账号状态" v-if="editingId"><el-select v-model="form.status"><el-option label="启用" value="ACTIVE" /><el-option label="禁用" value="DISABLED" /></el-select></el-form-item><el-form-item label="初始密码" v-if="!editingId && isDeveloper"><el-input v-model="form.password" type="password" show-password /><small>12-72 位</small></el-form-item><el-alert v-if="!editingId && !isDeveloper" title="超级管理员创建的账号会保持禁用且不设密码,需由开发者设置密码后再启用。" type="info" :closable="false" /><el-divider>页面权限</el-divider><div v-if="isAdministratorRole" class="permission-hint">超级管理员固定拥有全部页面的编辑权限。</div><el-form-item v-for="page in pages" :key="page.key" :label="page.label"><el-radio-group v-model="form.pagePermissions[page.key]" :disabled="isAdministratorRole"><el-radio value="NONE">无权限</el-radio><el-radio value="READ">只读</el-radio><el-radio value="EDIT">编辑</el-radio></el-radio-group></el-form-item></el-form><template #footer><el-button @click="dialogOpen=false">取消</el-button><el-button type="primary" @click="save">保存</el-button></template></el-dialog><el-dialog v-model="passwordOpen" title="设置账号密码" width="420px"><p>仅开发者可以设置密码:{{ target?.username }}</p><el-input v-model="password" type="password" show-password placeholder="12-72 位且不含用户名" /><template #footer><el-button @click="passwordOpen=false">取消</el-button><el-button type="primary" @click="savePassword">确认设置</el-button></template></el-dialog></section>` };
...@@ -94,7 +94,7 @@ export default { ...@@ -94,7 +94,7 @@ export default {
<section class="phone-asset-list-page wecom-account-page"> <section class="phone-asset-list-page wecom-account-page">
<header class="phone-asset-list-page__header"><div><h2>企业微信资产</h2><p class="wecom-account-page__eyebrow">WECOM ACCOUNTS</p></div><el-button v-if="canEdit" class="phone-asset-list-page__add" type="primary" @click="openCreate">新增企业微信资产</el-button></header> <header class="phone-asset-list-page__header"><div><h2>企业微信资产</h2><p class="wecom-account-page__eyebrow">WECOM ACCOUNTS</p></div><el-button v-if="canEdit" class="phone-asset-list-page__add" type="primary" @click="openCreate">新增企业微信资产</el-button></header>
<section class="phone-asset-list-page__panel phone-asset-list-page__search"><el-form class="phone-asset-list-page__filters" @submit.prevent="submitSearch"><el-input v-model="filters.keyword" placeholder="企微名称或手机号" clearable @input="scheduleSearch" @clear="scheduleSearch" /><el-select v-model="filters.companyProfileId" filterable remote clearable :remote-method="loadCompanies" placeholder="注册主体:" @change="submitSearch" @clear="restoreAllCompanyProfiles"><template #prefix>注册主体:</template><el-option label="全部" value="ALL" /><el-option v-for="item in companyOptions" :key="item.id" :label="item.shortName || item.companyName" :value="item.id" /></el-select><el-select v-model="filters.realNameOwnerStatus" clearable placeholder="实名状态:" @change="submitSearch" @clear="restoreAllRealNameStatuses"><template #prefix>实名状态:</template><el-option label="全部" value="ALL" /><el-option label="在职" value="在职" /><el-option label="离职" value="离职" /></el-select><el-button @click="resetSearch">重置</el-button></el-form></section> <section class="phone-asset-list-page__panel phone-asset-list-page__search"><el-form class="phone-asset-list-page__filters" @submit.prevent="submitSearch"><el-input v-model="filters.keyword" placeholder="企微名称或手机号" clearable @input="scheduleSearch" @clear="scheduleSearch" /><el-select v-model="filters.companyProfileId" filterable remote clearable :remote-method="loadCompanies" placeholder="注册主体:" @change="submitSearch" @clear="restoreAllCompanyProfiles"><template #prefix>注册主体:</template><el-option label="全部" value="ALL" /><el-option v-for="item in companyOptions" :key="item.id" :label="item.shortName || item.companyName" :value="item.id" /></el-select><el-select v-model="filters.realNameOwnerStatus" clearable placeholder="实名状态:" @change="submitSearch" @clear="restoreAllRealNameStatuses"><template #prefix>实名状态:</template><el-option label="全部" value="ALL" /><el-option label="在职" value="在职" /><el-option label="离职" value="离职" /></el-select><el-button @click="resetSearch">重置</el-button></el-form></section>
<section class="phone-asset-list-page__panel phone-asset-list-page__table"><header class="phone-asset-list-page__table-header"><h3>资产列表</h3><span>共 {{ total }} 条</span></header><el-table v-loading="loading" :data="records" empty-text="暂无匹配数据" class="phone-asset-list-page__grid wecom-account-page__grid"><el-table-column prop="id" label="企业微信资产 ID" min-width="140" /><el-table-column prop="wecomName" label="企微名称" min-width="150" show-overflow-tooltip /><el-table-column prop="wecomAlias" label="企微别名" min-width="180" show-overflow-tooltip /><el-table-column prop="wecomAccount" label="企微账号" min-width="160" show-overflow-tooltip /><el-table-column label="注册主体" min-width="180"><template #default="{ row }">{{ formatRelation(row.companyProfileName, row.companyProfileId) }}</template></el-table-column><el-table-column label="注册手机号" min-width="180"><template #default="{ row }">{{ formatRelation(row.phoneNumber, row.phoneAssetId) }}</template></el-table-column><el-table-column label="关联方式" min-width="120"><template #default="{ row }">{{ row.phoneLinkMode === 'CREATED' ? '新建号码' : '已有号码' }}</template></el-table-column><el-table-column prop="realNameOwner" label="实名人" min-width="120" /><el-table-column prop="realNameOwnerStatus" label="实名状态" min-width="110" /><el-table-column prop="gender" label="性别" min-width="90" /><el-table-column label="企微号归属人" min-width="180"><template #default="{ row }">{{ formatRelation(row.operatorPersonName, row.operatorPersonId) }}</template></el-table-column><el-table-column prop="createTime" label="创建时间" min-width="180" /></el-table><footer v-if="total" class="phone-asset-list-page__pagination"><span>共 {{ total }} 条</span><el-pagination layout="prev, pager, next" :current-page="filters.page" :page-size="filters.size" :total="total" @current-change="changePage" /></footer></section> <section class="phone-asset-list-page__panel phone-asset-list-page__table"><header class="phone-asset-list-page__table-header"><h3>资产列表</h3><span>共 {{ total }} 条</span></header><div class="phone-asset-list-page__grid-wrap"><el-table v-loading="loading" :data="records" empty-text="暂无匹配数据" class="phone-asset-list-page__grid wecom-account-page__grid"><el-table-column prop="id" label="企业微信资产 ID" min-width="140" /><el-table-column prop="wecomName" label="企微名称" min-width="150" show-overflow-tooltip /><el-table-column prop="wecomAlias" label="企微别名" min-width="180" show-overflow-tooltip /><el-table-column prop="wecomAccount" label="企微账号" min-width="160" show-overflow-tooltip /><el-table-column label="注册主体" min-width="180"><template #default="{ row }">{{ formatRelation(row.companyProfileName, row.companyProfileId) }}</template></el-table-column><el-table-column label="注册手机号" min-width="180"><template #default="{ row }">{{ formatRelation(row.phoneNumber, row.phoneAssetId) }}</template></el-table-column><el-table-column label="关联方式" min-width="120"><template #default="{ row }">{{ row.phoneLinkMode === 'CREATED' ? '新建号码' : '已有号码' }}</template></el-table-column><el-table-column prop="realNameOwner" label="实名人" min-width="120" /><el-table-column prop="realNameOwnerStatus" label="实名状态" min-width="110" /><el-table-column prop="gender" label="性别" min-width="90" /><el-table-column label="企微号归属人" min-width="180"><template #default="{ row }">{{ formatRelation(row.operatorPersonName, row.operatorPersonId) }}</template></el-table-column><el-table-column prop="createTime" label="创建时间" min-width="180" /></el-table></div><footer v-if="total" class="phone-asset-list-page__pagination"><span>共 {{ total }} 条</span><el-pagination layout="prev, pager, next" :current-page="filters.page" :page-size="filters.size" :total="total" @current-change="changePage" /></footer></section>
<el-dialog v-model="dialogVisible" class="phone-asset-modal" modal-class="phone-asset-modal-mask" title="新增企业微信资产" width="560px" :close-on-click-modal="false" @opened="resetDialogScroll"><el-form class="phone-asset-modal__form" label-width="112px" @submit.prevent="submitCreate"><el-form-item class="phone-asset-modal__form-row" label="企微名称" required><el-input v-model="form.wecomName" /></el-form-item><el-form-item class="phone-asset-modal__form-row" label="企微别名"><el-input v-model="form.wecomAlias" /></el-form-item><el-form-item class="phone-asset-modal__form-row" label="企微账号"><el-input v-model="form.wecomAccount" /></el-form-item><el-form-item class="phone-asset-modal__form-row" label="注册手机号" required><el-autocomplete :model-value="form.phoneNumber" :fetch-suggestions="fetchPhoneSuggestions" maxlength="11" inputmode="numeric" placeholder="输入 11 位手机号" style="width:100%" @update:model-value="limitPhoneNumber"><template #suffix><span class="phone-asset-modal__character-count">{{ form.phoneNumber.length }}/11</span></template></el-autocomplete></el-form-item><el-form-item class="phone-asset-modal__form-row" label="注册主体"><el-select v-model="form.companyProfileId" filterable remote clearable :remote-method="loadCompanies" placeholder="输入公司名称或简称" style="width:100%"><el-option v-for="item in companyOptions" :key="item.id" :label="item.shortName || item.companyName" :value="item.id" /></el-select></el-form-item><el-form-item class="phone-asset-modal__form-row" label="企微号归属人"><el-select v-model="form.operatorPersonId" filterable remote clearable :remote-method="loadOwners" placeholder="输入人员姓名" style="width:100%"><el-option v-for="item in ownerOptions" :key="item.id" :label="item.personName" :value="item.id" /></el-select></el-form-item><el-form-item class="phone-asset-modal__form-row" label="实名人"><el-input v-model="form.realNameOwner" /></el-form-item><el-form-item class="phone-asset-modal__form-row" label="实名状态"><el-radio-group v-model="form.realNameOwnerStatus"><el-radio value="在职">在职</el-radio><el-radio value="离职">离职</el-radio></el-radio-group></el-form-item><el-form-item class="phone-asset-modal__form-row" label="性别"><el-radio-group v-model="form.gender"><el-radio value="男">男</el-radio><el-radio value="女">女</el-radio></el-radio-group></el-form-item></el-form><template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="submitCreate">保存</el-button></template></el-dialog> <el-dialog v-model="dialogVisible" class="phone-asset-modal" modal-class="phone-asset-modal-mask" title="新增企业微信资产" width="560px" :close-on-click-modal="false" @opened="resetDialogScroll"><el-form class="phone-asset-modal__form" label-width="112px" @submit.prevent="submitCreate"><el-form-item class="phone-asset-modal__form-row" label="企微名称" required><el-input v-model="form.wecomName" /></el-form-item><el-form-item class="phone-asset-modal__form-row" label="企微别名"><el-input v-model="form.wecomAlias" /></el-form-item><el-form-item class="phone-asset-modal__form-row" label="企微账号"><el-input v-model="form.wecomAccount" /></el-form-item><el-form-item class="phone-asset-modal__form-row" label="注册手机号" required><el-autocomplete :model-value="form.phoneNumber" :fetch-suggestions="fetchPhoneSuggestions" maxlength="11" inputmode="numeric" placeholder="输入 11 位手机号" style="width:100%" @update:model-value="limitPhoneNumber"><template #suffix><span class="phone-asset-modal__character-count">{{ form.phoneNumber.length }}/11</span></template></el-autocomplete></el-form-item><el-form-item class="phone-asset-modal__form-row" label="注册主体"><el-select v-model="form.companyProfileId" filterable remote clearable :remote-method="loadCompanies" placeholder="输入公司名称或简称" style="width:100%"><el-option v-for="item in companyOptions" :key="item.id" :label="item.shortName || item.companyName" :value="item.id" /></el-select></el-form-item><el-form-item class="phone-asset-modal__form-row" label="企微号归属人"><el-select v-model="form.operatorPersonId" filterable remote clearable :remote-method="loadOwners" placeholder="输入人员姓名" style="width:100%"><el-option v-for="item in ownerOptions" :key="item.id" :label="item.personName" :value="item.id" /></el-select></el-form-item><el-form-item class="phone-asset-modal__form-row" label="实名人"><el-input v-model="form.realNameOwner" /></el-form-item><el-form-item class="phone-asset-modal__form-row" label="实名状态"><el-radio-group v-model="form.realNameOwnerStatus"><el-radio value="在职">在职</el-radio><el-radio value="离职">离职</el-radio></el-radio-group></el-form-item><el-form-item class="phone-asset-modal__form-row" label="性别"><el-radio-group v-model="form.gender"><el-radio value="男">男</el-radio><el-radio value="女">女</el-radio></el-radio-group></el-form-item></el-form><template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="submitCreate">保存</el-button></template></el-dialog>
</section> </section>
` `
......
...@@ -20,14 +20,15 @@ body { margin: 0; } ...@@ -20,14 +20,15 @@ body { margin: 0; }
@media (max-width: 980px) { .login-page { justify-content: center; padding-right: 24px; padding-left: 24px; background-position: 64% center; }.login-panel { margin: 0 auto; } } @media (max-width: 980px) { .login-page { justify-content: center; padding-right: 24px; padding-left: 24px; background-position: 64% center; }.login-panel { margin: 0 auto; } }
@media (max-width: 640px) { .login-page { align-items: center; min-height: 100dvh; padding: 18px; background-position: 63% center; }.login-panel { padding: 26px 22px 24px; border-radius: 26px; }.login-brand { gap: 12px; justify-content: flex-start; }.login-brand__mark { width: 48px; height: 48px; border-radius: 16px; }.login-brand h1 { font-size: clamp(19px, 5.6vw, 22px); letter-spacing: .02em; }.login-brand__eyebrow { font-size: 9px; }.login-panel__intro { margin-bottom: 22px; text-align: left; }.login-form__input .el-input__wrapper, .login-submit.el-button { min-height: 56px; height: 56px; font-size: 16px; }.login-form__input .el-input__inner { font-size: 16px; } } @media (max-width: 640px) { .login-page { align-items: center; min-height: 100dvh; padding: 18px; background-position: 63% center; }.login-panel { padding: 26px 22px 24px; border-radius: 26px; }.login-brand { gap: 12px; justify-content: flex-start; }.login-brand__mark { width: 48px; height: 48px; border-radius: 16px; }.login-brand h1 { font-size: clamp(19px, 5.6vw, 22px); letter-spacing: .02em; }.login-brand__eyebrow { font-size: 9px; }.login-panel__intro { margin-bottom: 22px; text-align: left; }.login-form__input .el-input__wrapper, .login-submit.el-button { min-height: 56px; height: 56px; font-size: 16px; }.login-form__input .el-input__inner { font-size: 16px; } }
.logout-button { width: 100%; margin-top: 24px; padding: 10px 12px; border: 1px solid #496b8c; border-radius: 8px; color: #cbd5e1; background: transparent; cursor: pointer; }.logout-button:hover { color:#fff; background:#1f3b59; } .logout-button { width: 100%; margin-top: 24px; padding: 10px 12px; border: 1px solid #496b8c; border-radius: 8px; color: #cbd5e1; background: transparent; cursor: pointer; }.logout-button:hover { color:#fff; background:#1f3b59; }
.app-shell { min-height: 100vh; display: grid; grid-template-columns: 232px minmax(0, 1fr); } /* 后台外壳:把高度锁死在一屏,滚动交给侧栏和内容区各自承担,列表页才能按剩余空间计算表格高度。 */
.sidebar { padding: 28px 20px; background: #132338; color: #f8fafc; } .app-shell { height: 100dvh; min-height: 100dvh; overflow: hidden; display: grid; grid-template-columns: 232px minmax(0, 1fr); }
.sidebar { padding: 28px 20px; overflow-y: auto; background: #132338; color: #f8fafc; }
.sidebar h1 { margin: 0 0 32px; font-size: 20px; } .sidebar h1 { margin: 0 0 32px; font-size: 20px; }
.sidebar nav { display: grid; gap: 8px; } .sidebar nav { display: grid; gap: 8px; }
.sidebar a { border-radius: 8px; color: #cbd5e1; padding: 10px 12px; text-decoration: none; } .sidebar a { border-radius: 8px; color: #cbd5e1; padding: 10px 12px; text-decoration: none; }
.sidebar a.router-link-active, .sidebar a:hover { background: #1f3b59; color: white; } .sidebar a.router-link-active, .sidebar a:hover { background: #1f3b59; color: white; }
.sidebar .settings-link { margin-top: 16px; border-top: 1px solid #385673; border-radius: 0; padding-top: 18px; } .sidebar .settings-link { margin-top: 16px; border-top: 1px solid #385673; border-radius: 0; padding-top: 18px; }
.content { min-width: 0; padding: 40px; } .content { min-width: 0; height: 100%; overflow-y: auto; padding: 40px; }
.user-permission-page { max-width: 1180px; margin: 0 auto; }.user-permission-page small { display:block; margin-top:6px; color:#64748b; }.permission-hint { margin:-4px 0 16px; color:#64748b; font-size:13px; } .user-permission-page { max-width: 1180px; margin: 0 auto; }.user-permission-page small { display:block; margin-top:6px; color:#64748b; }.permission-hint { margin:-4px 0 16px; color:#64748b; font-size:13px; }
.eyebrow { margin: 0 0 8px; color: #5f8fcb; font-size: 12px; font-weight: 700; letter-spacing: .08em; } .eyebrow { margin: 0 0 8px; color: #5f8fcb; font-size: 12px; font-weight: 700; letter-spacing: .08em; }
.reference-page, .placeholder { max-width: 1180px; margin: 0 auto; } .reference-page, .placeholder { max-width: 1180px; margin: 0 auto; }
...@@ -531,3 +532,33 @@ body { margin: 0; } ...@@ -531,3 +532,33 @@ body { margin: 0; }
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
} }
/* 列表页固定高度骨架:宽屏下页面高度锁死在一屏,数据变多只在表格内部滚动;≤640px 回到整页滚动,避免小屏可视行数过少。 */
@media (min-width: 641px) {
/* 手机号资产、企微资料、公司档案三页共用同一套骨架:标题与筛选保持原高度,列表面板吃掉剩余空间。
用 min-height 而不是 height:正常情况刚好一屏、内容区不出滚动条;窗口矮到表格触及最小高度时页面被撑高,改由内容区整页滚动,避免表格被压成一两行。 */
.phone-asset-list-page { display: flex; flex-direction: column; min-height: 100%; padding-bottom: 28px; }
.phone-asset-list-page__header,
.phone-asset-list-page__search,
.phone-asset-list-page__table-header,
.phone-asset-list-page__pagination { flex: 0 0 auto; }
.phone-asset-list-page__table { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
/* 表格必须绝对定位:el-table 会用自身内容高度反向撑开父级,留在文档流里 flex 就收缩不下去。
min-height 220px 是兜底:表头约 46px,再留三行左右可视区,低于这个高度就不再压缩表格。 */
.phone-asset-list-page__grid-wrap { position: relative; flex: 1 1 auto; min-height: 220px; }
/* height 必须显式写:Element Plus 自带 .el-table{height:fit-content},只给 inset 会被它按内容高度顶掉。 */
.phone-asset-list-page__grid-wrap > .el-table { position: absolute; inset: 0; height: 100%; }
.phone-asset-list-page__grid-wrap > .el-table > .el-table__inner-wrapper { height: 100%; }
.phone-asset-list-page__grid-wrap .el-table__body-wrapper { flex: 1 1 auto; min-height: 0; }
.phone-asset-list-page__grid-wrap .el-table__body-wrapper > .el-scrollbar { height: 100%; }
/* 账号与权限页:登录锁定卡片保持自然高度,只有下方账号列表吃掉剩余空间并内部滚动。 */
.user-permission-page { display: flex; flex-direction: column; min-height: 100%; padding-bottom: 28px; }
.user-permission-page > .page-header,
.user-permission-page__locked { flex: 0 0 auto; }
.user-permission-page__locked { margin-bottom: 16px; }
.user-permission-page__accounts { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.user-permission-page__grid-wrap { position: relative; flex: 1 1 auto; min-height: 220px; }
.user-permission-page__grid-wrap > .el-table { position: absolute; inset: 0; height: 100%; }
.user-permission-page__grid-wrap > .el-table > .el-table__inner-wrapper { height: 100%; }
.user-permission-page__grid-wrap .el-table__body-wrapper { flex: 1 1 auto; min-height: 0; }
.user-permission-page__grid-wrap .el-table__body-wrapper > .el-scrollbar { height: 100%; }
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment