**摘要:**本文介绍在 ASP.NET Core Razor 视图项目中实现表格自定义列的实用方案:由 Razor 视图承载页面骨架与初始数据,通过 JavaScript 初始化 Vue 实例,再结合 iView Table 组件动态生成列配置。服务端负责权限校验、数据查询和列定义,前端负责弹窗交互、勾选状态与即时渲染。文章从前置知识、整体数据流、Razor 视图、JavaScript 初始化、服务端数据模型和列设置本地持久化等方面展开,并总结动态列排序、列宽控制、大数据量分页、权限过滤和特殊模板列等常见注意事项。
一、背景与需求
在企业后台系统或内部管理系统中,数据表格几乎是最常见的页面元素。无论是订单列表、用户管理还是报表查询,我们都需要把服务端返回的数据以表格形式展示给用户。但在实际业务里,同一个表格往往会被不同角色使用:有的角色只需要查看基础字段,有的角色需要同时查看金额、状态、操作等多个字段。如果每次都写死一套列配置,就很难同时满足大家的需求。
在 ASP.NET Core Razor 视图项目中,页面通常由服务端渲染,HTML 结构和初始数据会在服务端生成。这种模式的好处是直观、稳定,但当表格列很多,或者用户希望自己决定显示哪些列时,纯粹的固定列配置就会显得不够灵活:字段一多,表格会变得拥挤,阅读效率也会下降。
本文要解决的就是这个问题。我们采用一种前后端协同的轻量方案:由 Razor 视图承载页面骨架和初始数据,通过 JavaScript 初始化 Vue 实例,再结合 iView 的 Table 组件实现表格自定义列。用户可以打开「自定义列」弹窗,勾选自己关注的字段,表格会立刻按新的列配置重新渲染,而不需要重新请求页面。
这样做有几个明显的好处:
- 职责清晰:服务端继续负责权限校验、数据查询和列定义的生成,前端负责交互、列配置和即时渲染。
- 体验更好:用户勾选列之后,表格无需刷新页面就能直接更新,操作更加流畅。
- 易于扩展:后续可以继续加入列排序、列宽记忆、用户级配置保存等能力。
阅读本文需要哪些前置知识
要跟着本文实现,你需要对 ASP.NET Core Razor 视图、JavaScript 和 Vue 的基础语法有一定了解。Razor 视图负责在服务端输出 HTML 和 JSON 数据;JavaScript 和 Vue 负责在浏览器中完成交互;iView 是一个基于 Vue 的组件库,其中的 Table 组件用 columns 配置描述表格列。即使你之前没有深度用过 iView,只要理解"数据驱动视图"的思路,也可以顺利读懂后面的代码。
二、整体思路
整个功能可以理解为:服务端先把"有哪些列、每列叫什么、默认是否显示"定义好,随页面一起交给浏览器;浏览器端用 Vue 把这些列保存为响应式数据,并提供勾选界面;用户勾选完成后,Vue 根据勾选结果计算出 Table 组件真正需要的列配置,Table 组件再自动更新表格。
从实现上看,整个方案可以分为三部分:
- Razor 视图:输出页面容器、初始化数据、列定义和权限信息,并为 Vue 提供一个挂载点。
- JavaScript:负责启动 Vue,管理列配置弹窗、勾选状态和表格列渲染。
- Vue/iView :使用 iTable 组件展示数据,Table 的
columns配置由 Vue 的响应式数据动态生成。
核心思路在于把「自定义列」抽象成一组可勾选的列对象。每个列对象至少包含字段键、标题、宽度和是否勾选等信息。用户勾选或取消勾选列后,前端根据最新勾选结果重新生成 iView Table 需要的 columns 配置;如果后续需要保存用户的列宽、顺序或显隐状态,也都可以挂在这些列对象上,集中管理。
一次交互的数据流
可以把一次"修改列配置并刷新表格"的过程简化为下面几步:
- 服务端在渲染 Razor 视图时,把列定义和行数据序列化成 JSON,写入页面的
window.__tableConfig。 - 页面加载后,JavaScript 读取
window.__tableConfig,初始化 Vue 数据。 - 用户点击「自定义列」按钮,打开列选择弹窗。
- 用户勾选或取消勾选某一列,
allColumns中对应项的checked状态发生变化。 - Vue 的计算属性
tableColumns自动过滤出勾选的列,并生成 Table 组件需要的列配置。 - iView Table 使用新的
columns自动重新渲染表格。
这条链路中,前端不需要手动操作 DOM,只需要维护好数据状态,界面就会跟着数据变化,这也是 Vue 响应式机制带来的主要收益。
三、Razor 视图部分
Razor 视图的部分要做三件事:第一,提供一个页面容器,也就是一个带 id 的 div,让 Vue 可以挂载;第二,把服务端准备好的列定义和数据序列化成 JSON,方便浏览器端读取;第三,在容器内放置自定义列按钮、表格和列设置弹窗等页面元素。
写入 JSON 时需要注意安全。因为我们要把服务端数据原样放进页面,如果字段值里夹杂了恶意脚本,直接输出可能会引发脚本注入问题。实际项目中应当先对数据进行 HTML 编码处理,再使用 Html.Raw(Json.Serialize(...)) 输出,避免注入风险。
html
<div id="customTableApp" v-cloak>
<div class="toolbar">
<i-button type="primary" @@click="openColumnSetting">自定义列</i-button>
</div>
<i-table :columns="tableColumns" :data="tableData" border stripe></i-table>
<i-modal v-model="columnSettingVisible" title="自定义列" @@on-ok="applyColumnSetting">
<div v-for="column in allColumns" :key="column.key">
<i-checkbox v-model="column.checked">{{ column.title }}</i-checkbox>
</div>
</i-modal>
</div>
<script>
window.__tableConfig = {
columns: @Html.Raw(Json.Serialize(Model.Columns)),
data: @Html.Raw(Json.Serialize(Model.Rows))
};
</script>
下面简单解释这段模板中几个关键点:
- v-cloak:用于在 Vue 实例初始化完成前隐藏未编译的模板,避免用户看到花括号等原始表达式。
- @@click :Razor 视图中
@@会被转义为单个@,所以最终浏览器里读到的是 Vue 需要的@click。 - :columns 和 :data :分别把 Vue 实例中的
tableColumns和tableData以绑定的形式传给 iView Table。 - v-model :把弹窗的显示状态和 Vue 数据
columnSettingVisible双向绑定,打开和关闭弹窗都通过修改这个值来实现。 - v-for :遍历
allColumns生成一组复选列,每个复选框通过v-model="column.checked"直接绑定到对应列对象的勾选状态。
这里给出的只是核心示例。真实项目中,你需要根据项目情况在布局文件中引入 Vue、iView 以及具体的业务脚本;如果页面已经有全局的脚本管理方式,也可以将示例代码拆分到独立的 JavaScript 文件中维护。
四、JavaScript 初始化部分
页面加载完成后,前端从 window.__tableConfig 中读取服务端注入的列定义和行数据,然后创建 Vue 实例。初始化时,我们不会直接把服务端列定义丢给 Table 组件,而是先转换成前端需要维护的列对象:每个对象包含 key、title、width 和 checked。其中 checked 由 defaultVisible 决定,服务端标记为默认显示的列会被勾选。
javascript
new Vue({
el: '#customTableApp',
data: function () {
return {
columnSettingVisible: false,
allColumns: window.__tableConfig.columns.map(function (item) {
return {
key: item.key,
title: item.title,
width: item.width,
checked: item.defaultVisible !== false
};
}),
tableData: window.__tableConfig.data
};
},
computed: {
tableColumns: function () {
return this.allColumns
.filter(function (column) {
return column.checked;
})
.map(function (column) {
return {
key: column.key,
title: column.title,
width: column.width,
align: 'left'
};
});
}
},
methods: {
openColumnSetting: function () {
this.columnSettingVisible = true;
},
applyColumnSetting: function () {
this.columnSettingVisible = false;
}
}
});
这段代码分为三个部分:
- data :定义弹窗显示状态、可配置列列表和表格数据。所有列先保留一个
checked状态,默认勾选的列会进入表格显示配置。 - computed :
tableColumns是一个计算属性。它先过滤出checked为真的列,再把每列转换成 iView Table 需要的配置格式,并设置对齐方式。 - methods:提供打开列设置弹窗和确认设置两个方法。
这里最关键的是 tableColumns。因为它是计算属性,所以只要 allColumns 中任意一列的 checked 发生变化,Vue 就会自动重新计算 tableColumns,iView Table 也就会拿到新的列配置并更新表格。整个过程完全由数据驱动,不需要再写额外的 DOM 更新代码。如果后续要加入列排序,只需要在生成 tableColumns 前对列数组进行排序即可。
五、服务端数据模型
为了让 Razor 视图能够稳定地输出列配置,服务端最好用一个统一的模型来描述每一列,而不是在 Controller 里随意拼接匿名对象。这样既能保证字段命名一致,也方便根据用户权限、页面配置在服务端提前过滤列。
下面是一个最小可用的 TableColumnConfig 模型:
csharp
public class TableColumnConfig
{
public string Key { get; set; }
public string Title { get; set; }
public int? Width { get; set; }
public bool DefaultVisible { get; set; } = true;
}
- Key:列的字段键,对应数据行中的字段名,Table 组件会按这个键取值。
- Title:列在表头显示的标题。
- Width :列宽,可选。设置为
null时可以让 Table 组件自动分配宽度。 - DefaultVisible :默认是否显示。默认值为
true,表示所有列默认勾选;对于不常用或敏感字段,可以在服务端设置为false。
接口返回的表格数据通常采用字典列表的形式,这样 iView Table 可以根据列的 key 从字典中直接取值。当然,项目里也可以继续使用强类型对象列表,再由 Razor 视图或服务端完成到字典形式的转换。选择哪种方式主要看项目现有风格:如果只是给 Table 组件消费,字典形式更直接;如果对象还会被其他逻辑复用,保留强类型对象通常更好。
六、优化体验:保存用户列设置
如果列配置只保存在页面的 Vue 实例里,用户每次刷新页面都会回到服务端定义的默认状态。为了让用户下次打开页面时仍然看到自己的配置,可以把勾选结果保存到浏览器的 localStorage 中。
基本思路是:启动时先读取本地存储,把之前保存的列键列表恢复成勾选状态;用户确认修改后,再把当前勾选的列键写回本地存储。这样即使是跨会话访问,配置也能保留下来。
javascript
var columnStorageKey = 'customTableColumns';
function loadColumnSettings(columns) {
var saved = localStorage.getItem(columnStorageKey);
if (!saved) {
return;
}
try {
var savedKeys = JSON.parse(saved);
columns.forEach(function (column) {
column.checked = savedKeys.indexOf(column.key) !== -1;
});
} catch (e) {
// 本地存储数据异常时使用默认配置
}
}
function saveColumnSettings(columns) {
var checkedKeys = columns
.filter(function (column) {
return column.checked;
})
.map(function (column) {
return column.key;
});
localStorage.setItem(columnStorageKey, JSON.stringify(checkedKeys));
}
这里有两个核心函数:
- loadColumnSettings :读取本地存储字符串,解析出之前保存的列键列表,然后遍历所有列,把命中的列设为勾选。读取和解析都放在
try/catch中,一旦本地数据损坏,就继续使用默认配置,避免页面启动失败。 - saveColumnSettings :从列数组里过滤出已经勾选的列,提取它们的
key,以 JSON 字符串的形式写回本地存储。
在实际接入时,通常在 Vue 实例创建完成后调用一次 loadColumnSettings 恢复配置;在用户点击弹窗的"确定"按钮后调用 saveColumnSettings 保存配置。如果后续字段版本发生变化,比如某列被删除,本地保存的旧列键会自然失效,因为 indexOf 匹配不到对应列,代码仍然可以安全地回退到默认状态。
七、常见问题与注意事项
- 动态列与顺序 :如果需要支持拖拽排序,可以在列设置弹窗中加入上移、下移按钮,并调整
allColumns的顺序。 - 列宽控制 :固定列宽有助于避免表格抖动;如果允许用户调整列宽,可以记录宽度变化并合并到
tableColumns中。 - 大数据量:当行数很多时,建议服务端分页,iView Table 只负责渲染当前页数据。
- 权限过滤:某些字段如果用户没有权限查看,应在服务端生成列配置时直接排除,而不是只在前端隐藏。
- 特殊模板列 :对于操作列或需要自定义渲染的列,可以通过
render函数返回 Vue 模板或 h 函数生成的节点。
除上述问题外,还有几个细节值得留意:
- 空数据展示:当筛选结果为空或接口暂时没有数据时,可以给 Table 配置空状态提示,避免页面看起来像出错。
- 固定操作列:如果"操作"列需要在横向滚动时保持可见,可以结合 Table 的固定列配置来设置。
- 列配置与业务语义一致:列标题、排序字段和导出字段等最好都由服务端统一定义,避免前端另起一套配置造成不一致。
八、小结
本文围绕 ASP.NET Core Razor 视图项目中「表格自定义列」这一常见需求,完整梳理了一套前端与后端协同工作的实现方案。相比固定列配置,该方案让用户可以按角色、按场景自由选择需要展示的列,同时把权限校验和数据查询交给服务端,把交互逻辑和列配置交给前端,实现了职责清晰、响应及时的效果。即使项目并没有引入完整的前端工程化体系,也依然可以用这套思路渐进式地增强表格能力。
从整体实现来看,方案的核心可以归纳为三层配合:
- Razor 视图层:负责承载页面结构,并通过安全的 JSON 序列化方式,把列定义与行数据注入页面;
- JavaScript 层 :负责启动 Vue 实例,维护列勾选状态,并通过计算属性动态生成 iView Table 所需的
columns配置; - iView 组件层:利用响应式数据驱动表格渲染,使用户对列配置的修改可以即时反映到界面上。
在服务端,列配置可以通过 TableColumnConfig 等模型统一描述字段键、显示标题、列宽和默认显隐状态,既保证了列定义的规范性,也便于根据用户权限进行前置过滤。接口返回的数据通常采用字典或可转换的强类型列表,方便前端按 key 取值并交给 Table 组件渲染。
在体验优化方面,文章还给出了基于 localStorage 的列设置持久化方案。前端启动时读取保存的列配置,用户在确认列设置后写入本地存储,这样即使刷新页面或下次访问,也能恢复用户过去的选择。针对本地存储数据异常、版本变更等情况,代码中还可以通过 try/catch 进行兜底,避免因存储数据错误导致页面初始化失败。
对于实际落地场景,还需要重点关注几个方向:动态列顺序可以通过上移、下移或拖拽排序来实现;列宽控制与固定宽度结合,可以在灵活性和界面稳定性之间取得平衡;大数据量场景应优先考虑服务端分页,减小客户端渲染压力;权限控制应在服务端生成列配置时就完成过滤,而不能只依赖前端隐藏;操作列、自定义渲染列则需要借助 render 函数生成相应的模板或节点。此外,空数据提示、固定操作列以及列配置与业务语义的一致性,也会直接影响用户长期使用的体验。
总体而言,这套方案没有过度引入新的前端工程化依赖,而是充分发挥了现有 Razor、Vue 和 iView 的能力,适合在传统 Razor 视图项目或需要渐进式增强表格能力的系统中快速落地。通过前端响应式列管理、服务端统一配置、本地持久化以及合理的性能与权限设计,开发者可以显著提升复杂表格的可配置性和用户体验,并为后续进一步扩展列排序、列缓存、用户级个性化配置等能力留下清晰空间。