系列文章
Vue3+Vite+TypeScript+Element Plus开发
https://blog.csdn.net/sen_shan/category_12933362.html
26.Element-Plus 表单校验的"配置化封装 + 自定义插件入口"
https://blog.csdn.net/sen_shan/article/details/154388514
文章目录
目录
前言
本系列文章介绍了基于Vue3+Vite+TypeScript+ElementPlus的表单校验配置化封装方案。
文章详细讲解了如何通过.env文件配置分页参数(VITE_APP_TABLE_CURRENT_PAGE等),并实现config/index.ts中的类型转换工具(toNumber/toArray)。
核心组件ActionTableCont.vue展示了带分页功能的表格实现,包括动态列渲染、分页控制和选中项处理。
同时演示了如何通过CustomTable组件进行客制化表格开发,集成权限控制和按钮组功能。文章提供了一套完整的表单校验配置方案,便于在项目中复用和扩展。
本次修改细节蛮多,直接贴出源代码供参考。
配置文件
1..evn、.env.development文件
VITE_APP_TABLE_CURRENT_PAGE=1
VITE_APP_TABLE_PAGE_SIZE=10
VITE_APP_TABLE_PAGE_SIZES=[10,20,50,100]
表格默认页码,从 1 开始计数
VITE_APP_TABLE_CURRENT_PAGE=1
表格每页默认条数
VITE_APP_TABLE_PAGE_SIZE=10
表格"每页条数"下拉可选项,JSON 数组格式,**必须用双引号**
VITE_APP_TABLE_PAGE_SIZES=[10,20,50,100]
2.config文件修改
修改config\index.ts文件
TypeScript
// config/index.ts
// 对于数字类型的转换,可以创建类似的方法:
const toNumber = (value: string | undefined, defaultValue: number): number => {
const parsed = Number(value);
return isNaN(parsed) ? defaultValue : parsed;
};
const toArray = (value: string | undefined, defaultValue: number[]): number[] => {
if (!value) return defaultValue;
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed.map(Number) : defaultValue;
} catch {
return defaultValue;
}
};
const config = {
// API 地址
apiBaseUrl: import.meta.env.VITE_APP_API_URL || 'https://default.example.com/api',
apiKey: import.meta.env.VITE_APP_API_KEY || '',
// 应用示例:
tableCurrentPage : toNumber(import.meta.env.VITE_APP_TABLE_CURRENT_PAGE, 1),
tablePageSize : toNumber(import.meta.env.VITE_APP_TABLE_PAGE_SIZE, 10),
tablePageSizes: toArray(import.meta.env.VITE_APP_TABLE_PAGE_SIZES, [10, 20, 30, 40, 50]),
// 其他配置
featureFlags: {
newFeature: import.meta.env.VITE_APP_FEATURE_FLAG === 'true' || false,
},
};
export default config;
-
在 index.ts 顶部写转换器(复用现有或新建)。
-
在 config 对象里加键,必须 转换器(env key, 默认值) 。
-
在 .env.example 中给出示例值,CI 自动同步到各环境。
组件
修改components\ActionTableCont.vue文件
html
<template>
<div>
<!-- 表格 -->
<el-table
:data="currentPageData"
style="width: 100%"
border
@selection-change="handleSelectionChange"
>
<!-- 选择列 -->
<el-table-column
v-if="showSelection"
type="selection"
width="55"
align="center"
></el-table-column>
<!-- 序号列 -->
<el-table-column
type="index"
label="序号"
width="50"
align="center"
></el-table-column>
<!-- 动态列 -->
<el-table-column
v-for="column in visibleColumns"
:key="column.field"
:prop="column.field"
:label="column.label"
:width="column.width"
:align="column.align || 'left'"
></el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
:current-page="currentPage"
:page-size="pageSize"
:page-sizes="pageSizes"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.length"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="margin-top: 20px"
></el-pagination>
</div>
</template>
<script setup>
import { ref, computed, defineProps, defineEmits, watch } from 'vue';
import defaultConfig from '../../config/index.js'; // 相对路径
const props = defineProps({
tableColumns: {
type: Array,
required: true,
default: () => [],
},
tableData: {
type: Array,
required: true,
default: () => [],
},
pageSize: {
type: Number,
default: defaultConfig.tablePageSize,// 10
},
pageSizes: {
type: Array,
default: () => defaultConfig.tablePageSizes//[5, 10, 15, 20],
},
showSelection: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['update:currentPage', 'update:pageSize', 'selection-change']);
const currentPage = ref(defaultConfig.tableCurrentPage);
const currentPageData = computed(() => {
const start = (currentPage.value - 1) * props.pageSize;
const end = start + props.pageSize;
return props.tableData.slice(start, end);
});
const visibleColumns = computed(() => {
return props.tableColumns.filter((column) => !column.hide);
});
const handleSizeChange = (newSize) => {
console.log(`分页大小改变,新的分页大小为: ${newSize}`);
emit('update:pageSize', newSize);
currentPage.value = 1; // 重置当前页为第一页
emit('update:currentPage', currentPage.value);
};
const handleCurrentChange = (newPage) => {
console.log(`当前页码改变,新的页码为: ${newPage}`);
currentPage.value = newPage;
emit('update:currentPage', currentPage.value);
};
const handleSelectionChange = (selection) => {
emit('selection-change', selection);
};
// 监听 currentPage 和 pageSize 的变化并记录日志
watch(currentPage, (newVal, oldVal) => {
console.log(`currentPage changed from ${oldVal} to ${newVal}`);
});
watch(() => props.pageSize, (newVal, oldVal) => {
console.log(`pageSize changed from ${oldVal} to ${newVal}`);
});
</script>
修改重点:
1.统一把column.prop改为column.field
2.页码调整,直接从Config中带出
客制表格
html
<template>
<div>
<ActionButtonGroup
:show-add="hasPermission('demo2:create')"
:show-delete="true"
:disabled-add="false"
:disabled-edit="!selectedData.length"
:disabled-delete="!selectedData.length"
@add="handleAdd"
@edit="handleEdit"
@delete="handleDelete"
/>
<!-- 表格 -->
<CustomTable
:tableColumns="columns"
:tableData="data"
:pageSize="pageSize"
:pageSizes="[5, 10, 15, 20, 30]"
:showSelection="true"
@update:currentPage="currentPage = $event"
@update:pageSize="pageSize = $event"
@selection-change="handleSelectionChange"
/>
<!-- 显示选中数据 -->
<div v-if="selectedData.length > 0" style="margin-top: 20px">
<h3>选中的数据:</h3>
<pre>{{ selectedData }}</pre>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import CustomTable from '@/components/ActionTableCont.vue';
import { usePermission } from "@/utils/permissionUtils";
import ActionButtonGroup from "@/components/ActionBtnHdrCont.vue";
import {
Plus,
Check,
Delete,
Edit,
Message,
Search,
Star,
Upload,
} from '@element-plus/icons-vue'
const hasPermission = usePermission();
const columns = ref([
{
field: 'date',
label: '日期',
width: 180,
},
{
field: 'name',
label: '姓名',
width: 180,
},
{
field: 'address',
label: '地址',
hide: true,
},
]);
const data = ref([
{
id: 1,
date: '2025-04-14',
name: '张三',
address: '上海市普陀区金沙江路 1518 弄',
},
{
id: 2,
date: '2025-04-15',
name: '李四',
address: '上海市普陀区金沙江路 1517 弄',
},
{
id: 3,
date: '2025-04-16',
name: '王五',
address: '上海市普陀区金沙江路 1516 弄',
},
{
id: 4,
date: '2025-04-17',
name: '赵六',
address: '上海市普陀区金沙江路 1515 弄',
},
{
id: 5,
date: '2025-04-18',
name: '孙七',
address: '上海市普陀区金沙江路 1514 弄',
},
{
id: 6,
date: '2025-04-19',
name: '周八',
address: '上海市普陀区金沙江路 1513 弄',
},
{
id: 7,
date: '2025-04-20',
name: '吴九',
address: '上海市普陀区金沙江路 1512 弄',
},
{
id: 8,
date: '2025-04-21',
name: '郑十',
address: '上海市普陀区金沙江路 1511 弄',
},
{
id: 9,
date: '2025-04-22',
name: '钱十一',
address: '上海市普陀区金沙江路 1510 弄',
},
{
id: 10,
date: '2025-04-23',
name: '孔十二',
address: '上海市普陀区金沙江路 1509 弄',
},
{
id: 11,
date: '2025-04-24',
name: '秦十三',
address: '上海市普陀区金沙江路 1508 弄',
},
{
id: 12,
date: '2025-04-25',
name: '尤十四',
address: '上海市普陀区金沙江路 1507 弄',
},
]);
const currentPage = ref(1);
const pageSize = ref(5);// 默认每页显示5条数据
// 用于存储选中的数据
const selectedData = ref([]);
// 处理选中数据变化
const handleSelectionChange = (selection) => {
selectedData.value = selection;
};
// 获取选中数据
const handleDelete = () => {
console.log('Selected Data:', selectedData.value);
console.log('Selected Data length:', selectedData.value.length);
if (selectedData.value.length > 0) {
// 删除逻辑
console.log('Deleting selected data...');
selectedData.value = [];
} else {
console.log('No selected data to delete.');
}
};
</script>
未做调整,未来会从Config中带出。