弹窗表单配置
约 1033 字大约 3 分钟
2025-02-05
提示
表格中的弹出的表单是内置组件avue-form组件,配置属性可以参考FORM 组件文档
默认值
配置`value`为字段默认值
<template>
<tvue-crud :option="option"
:data="data"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
value: 'small'
}
]
});
</script>打开表单前
打开表单前会执行`beforeOpen`方法,相关返回的方法值可以判断表单当前打开的类型是新增还是编辑,调用`done`方法打开弹窗
<template>
<tvue-crud :data="data"
v-model="form"
:before-open="beforeOpen"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessageBox } from 'element-plus';
const form = ref({});
const data = ref([{
name: '张三',
sex: '男'
}]);
const option = {
column: [
{
label: '姓名',
prop: 'name'
},
{
label: '性别',
prop: 'sex'
}
]
};
const beforeOpen = (done, type, loading) => {
ElMessageBox.alert(`我是${type}`, '提示', {
confirmButtonText: '确定'
}).then(() => {
if (['view', 'edit'].includes(type)) {
// 查看和编辑逻辑
} else {
// 新增逻辑
form.value.name = '初始化赋值';
}
done();
}).catch(() => {
// 处理取消或关闭的逻辑(如果有需要)
});
};
</script>加载数据
打开表单前会执行`beforeOpen`方法,调用`loading`加载等待数据,调用`done`完成加载
<template>
<tvue-crud :data="data"
v-model="form"
:before-open="beforeOpen"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessageBox } from 'element-plus';
const form = ref({});
const data = ref([{
name: '张三',
sex: '男'
}]);
const option = {
column: [
{
label: '姓名',
prop: 'name'
},
{
label: '性别',
prop: 'sex'
}
]
};
const beforeOpen = (done, type, loading) => {
if (['view', 'edit'].includes(type)) {
loading()
setTimeout(() => {
done()
}, 300)
} else {
// 新增逻辑
form.value.name = '初始化赋值';
done();
}
};
</script>关闭表单前
关闭表单前会执行`beforeClose`方法,执行返回的`done`方法后才会彻底关闭表单
<template>
<tvue-crud :data="data"
v-model="form"
:before-close="beforeClose"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessageBox } from 'element-plus'
const form = ref({});
const data = ref([{
name: '张三',
sex: '男'
}]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name'
},
{
label: '性别',
prop: 'sex'
}
]
});
const beforeClose = (done, type) => {
ElMessageBox.confirm('确认关闭?')
.then(() => {
done();
})
.catch(() => { });
};
</script>不关弹窗连续添加
关闭表单前会执行`beforeClose`方法,执行返回的`done`方法后才会彻底关闭表单
<template>
<tvue-crud ref="crud"
:data="data"
v-model="form"
@row-save="rowSave"
:before-open="beforeOpen"
:before-close="beforeClose"
:option="option">
<template #menu-form="{ row, index }">
<el-button type="primary"
icon="el-icon-check"
plain
v-if="type === 'add'"
@click="handleNext(row, index)">继续添加</el-button>
</template>
</tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElButton, ElMessage } from 'element-plus';
import 'element-plus/theme-chalk/el-message.css';
const form = ref({});
const flag = ref(false);
const type = ref('');
const data = ref([]);
const option = ref({
align: 'center',
menuAlign: 'center',
viewBtn: true,
column: [
{
label: '姓名',
prop: 'name'
}, {
label: '性别',
prop: 'sex'
}
]
});
const handleNext = (row, index) => {
flag.value = true;
// @ts-ignore
crud.value.rowSave();
};
const rowSave = (form, done, loading) => {
data.value.push(deepClone(form));
ElMessage.success(JSON.stringify(form));
if (flag.value) {
flag.value = false;
loading();
form.name = "";
form.sex = "";
return;
}
done();
};
const beforeClose = (done) => {
flag.value = false;
done();
};
const beforeOpen = (done, type) => {
type.value = type;
done();
};
const deepClone = (obj) => {
return JSON.parse(JSON.stringify(obj));
};
const crud = ref(null);
</script>表单按钮位置
配置`dialogMenuPosition`属性值即可,默认为`right`
<template>
<el-radio-group v-model="direction">
<el-radio label="left">居左</el-radio>
<el-radio label="center">居中</el-radio>
<el-radio label="right">居右</el-radio>
</el-radio-group>
<br /><br />
<tvue-crud :option="option"
:data="list"></tvue-crud>
</template>
<script setup>
import { ref, watch } from 'vue';
const direction = ref('right');
const list = ref([
{
id: 1,
name: '张三',
sex: 12
}, {
id: 2,
name: '李四',
sex: 12
}
]);
const option = ref({
dialogMenuPosition: 'right',
column: [
{
label: 'id',
prop: 'id'
}, {
label: '姓名',
prop: 'name'
}, {
label: '年龄',
prop: 'sex'
}
]
});
watch(direction, (value) => {
option.value.dialogMenuPosition = value;
});
</script>打开表单方式
配置`dialogType`为弹窗的方式,`dialogDirection`为弹窗的位置
<template>
<el-radio-group v-model="direction">
<el-radio label="ltr">从左往右开</el-radio>
<el-radio label="rtl">从右往左开</el-radio>
<el-radio label="ttb">从上往下开</el-radio>
<el-radio label="btt">从下往上开</el-radio>
</el-radio-group>
<br /><br />
<tvue-crud :option="option"
:data="list"></tvue-crud>
</template>
<script setup>
import { ref, watch } from 'vue';
const direction = ref('rtl');
const list = ref([
{
name: '张三',
sex: 12
}, {
name: '李四',
sex: 12
}
]);
const option = ref({
dialogDirection: 'rtl',
dialogType: 'drawer',
column: [
{
label: '姓名',
prop: 'name'
}, {
label: '年龄',
prop: 'sex'
}
]
});
watch(direction, (value) => {
option.value.dialogDirection = value;
});
</script>防重提交
为了防止数据重复提交,加入了防重提交机制,`rowSave`方法和`rowUpdate`方法返回`done`用于关闭表单方法和`loading`用于关闭禁止的表单不关闭表单
<template>
<tvue-crud :data="data"
:option="option"
@row-save="handleRowSave"
@row-update="handleRowUpdate"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
const data = ref([
{ name: '张三', sex: '男' },
{ name: '李四', sex: '女' }
]);
const option = {
column: [
{ label: '姓名', prop: 'name' },
{ label: '性别', prop: 'sex' }
]
};
const handleRowSave = (row, done, loading) => {
ElMessage.success('1秒后关闭禁止表单');
setTimeout(() => {
loading();
ElMessage.success('3秒后关闭表单');
}, 1000);
setTimeout(() => {
done();
}, 3000);
};
const handleRowUpdate = (row, index, done, loading) => {
ElMessage.success('1秒后关闭禁止表单');
setTimeout(() => {
loading();
ElMessage.success('3秒后关闭表单');
}, 1000);
setTimeout(() => {
done();
}, 3000);
};
</script>标题字段宽度
`labelWidth`为标题的宽度,默认为`90`,可以配置到`option`下作用于全部,也可以单独配置每一项
<template>
<tvue-crud :data="data"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{ name: '张三', sex: '男' },
{ name: '李四', sex: '女' }
]);
const option = ref({
labelWidth: 150,
column: [
{
labelWidth: 30,
label: '姓名',
prop: 'name'
},
{
label: '性别',
prop: 'sex'
}
]
});
</script>验证
提示
具体参考async-validator
配置验证字段的`rules`的数据对象
<template>
<tvue-crud :data="data"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{ name: '张三', sex: '男' },
{ name: '李四', sex: '女' }
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
rules: [{ required: true, message: '请输入姓名', trigger: 'blur' }]
},
{
label: '性别',
prop: 'sex',
rules: [{ required: true, message: '请输入性别', trigger: 'blur' }]
}
]
});
</script>自定义验证
提示
自定义校验 callback 必须被调用。 更多高级用法可参考 async-validator。
<template>
<tvue-crud :data="data"
v-model="obj"
:option="option"
@error="handleError"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
const obj = ref({});
const data = ref([]);
const validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
callback();
}
};
const validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== obj.value.password) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
const option = ref({
column: [
{
label: '密码',
prop: 'password',
rules: [{ required: true, validator: validatePass, trigger: 'blur' }]
},
{
label: '确认密码',
prop: 'oldpassword',
rules: [{ required: true, validator: validatePass2, trigger: 'blur' }]
}
]
});
const handleError = (err) => {
ElMessage.success('请查看控制台');
console.log(err);
};
</script>组件对象
打开表单的时候可以获取相关字段的 ref 对象
<template>
<tvue-crud ref="crud"
:data="data"
:before-open="beforeOpen"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
const data = ref([{
text: '测试数据'
}]);
const option = ref({
labelWidth: 120,
column: [{
label: '测试框',
prop: 'text',
}]
});
const crud = ref(null);
const beforeOpen = (done) => {
done();
setTimeout(() => {
ElMessage.success('查看控制台');
console.log('text的ref对象');
console.log(crud.value.getPropRef('text'));
}, 0);
};
</script>字段不同状态
`disabled`、`display`、`detail`等字段在新增和编辑不同状态下,字段的不同状态展示
<template>
<tvue-crud :data="data"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{
name: '张三',
sex: '男',
grade: 0
}, {
name: '李四',
sex: '女',
grade: 1
}
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
editDisabled: true
}, {
label: '性别',
prop: 'sex',
editDisplay: false
}, {
label: '权限',
prop: 'grade',
editDetail: true,
addDisabled: true
}, {
label: '测试',
prop: 'test',
addDisplay: false
}
]
});
</script>深结构绑定
`bing`绑定深层次的结构对象,`prop`也是需要填写
<template>
<tvue-crud :option="option"
:data="data"
v-model="form">
<template #bind-form="{}">
<el-button type="primary"
@click="changeDeepValue">改变deep.deep.deep.value值</el-button>
<el-button type="primary"
@click="changeTestValue">改变test值</el-button>
<br /><br />
{{ form }}
</template>
</tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElButton } from 'element-plus';
const form = ref({});
const data = ref([{
deep: {
deep: {
deep: {
value: '我是深结构'
}
}
}
}]);
const option = {
labelWidth: 120,
column: [
{
label: '深结构',
prop: 'test',
bind: 'deep.deep.deep.value'
},
{
label: '',
prop: 'bind',
span: 24,
hide: true
}
]
};
const changeDeepValue = () => {
form.value.deep.deep.deep.value = '改变deep.deep.deep.value值';
};
const changeTestValue = () => {
form.value.test = '改变test值';
};
</script>字段排序
配置`order`的序号可以实现表单和表格字段不同的顺序
<template>
<tvue-crud :data="data"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{
name: '张三',
sex: '男'
}, {
name: '李四',
sex: '女'
}
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name'
}, {
label: '性别',
prop: 'sex',
order: 1
}
]
});
</script>表单窗口拖拽
`dialogDrag`设置为`true`即可拖动表单
<template>
<tvue-crud :data="data"
:option="option"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{ name: '张三', sex: true },
{ name: '李四', sex: false }
]);
const option = {
dialogDrag: true,
column: [
{ label: '姓名', prop: 'name' },
{ label: '性别', prop: 'sex' }
]
};
</script>改变结构配置
<template>
<tvue-crud v-model:defaults="defaults"
v-model="form"
:option="option"
:data="data"></tvue-crud>
</template>
<script setup>
import { ref, watch } from 'vue';
const form = ref({});
const defaults = ref({});
const data = ref([{
text1: 0
}]);
const option = {
column: [{
label: '内容1',
prop: 'text1',
type: 'radio',
dicData: [{
label: '显示',
value: 0
}, {
label: '隐藏',
value: 1,
}]
}, {
label: '内容2',
prop: 'text2',
display: true
}, {
label: '内容3',
prop: 'text3'
}]
};
watch(() => form.value.text1, (val) => {
if (val === 0) {
defaults.value.text2 = { display: true };
defaults.value.text3 = { label: '内容3' };
} else {
defaults.value.text2 = { display: false };
defaults.value.text3 = { label: '有没有发现我变了' };
}
});
</script>与其它字段交互
<template>
<tvue-crud :data="data"
:option="option"
v-model="form"></tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([{
text1: 0
}]);
const form = ref({});
const option = {
column: [{
label: '内容1',
prop: 'text1',
type: 'radio',
dicData: [{
label: '显示',
value: 0
}, {
label: '隐藏',
value: 1,
}],
control: (val, form) => {
if (val === 0) {
return {
text2: {
display: true
},
text3: {
label: '内容3'
}
};
} else {
return {
text2: {
display: false
},
text3: {
label: '有没有发现我变了'
}
};
}
},
}, {
label: '内容2',
prop: 'text2',
display: true
}, {
label: '内容3',
prop: 'text3'
}]
};
</script>表单分组
提示
<template>
<tvue-crud :option="option"
v-model="form"
:data="data"></tvue-crud>
</template>
<script>
let baseUrl = 'https://cli.avuejs.com/api/area'
export default {
data () {
return {
form: {
name: '11'
},
data: [{
name: '张三',
sex: 15,
province: '110000',
city: '110100',
area: '110101',
checkbox: ['110000']
}],
option: {
column: [{
label: '姓名',
prop: 'name',
display: false
},
{
label: '年龄',
prop: 'sex',
display: false
}],
group: [
{
label: '用户信息',
prop: 'jbxx',
icon: 'el-icon-edit-outline',
column: [
{
label: '姓名',
prop: 'name'
},
{
label: '年龄',
prop: 'sex'
}
]
}, {
label: '退款申请',
prop: 'tksq',
icon: 'el-icon-view',
column: [
{
label: '省份',
prop: 'province',
type: 'select',
props: {
label: 'name',
value: 'code'
},
cascader: ['city'],
dicUrl: `${baseUrl}/getProvince`,
rules: [
{
required: true,
message: '请选择省份',
trigger: 'blur'
}
]
},
{
label: '城市',
prop: 'city',
type: 'select',
cascader: ['area'],
props: {
label: 'name',
value: 'code'
},
cascaderIndex: 0,
dicUrl: `${baseUrl}/getCity/{{key}}`,
rules: [
{
required: true,
message: '请选择城市',
trigger: 'blur'
}
]
},
{
label: '地区',
prop: 'area',
type: 'select',
props: {
label: 'name',
value: 'code'
},
cascaderIndex: 0,
dicUrl: `${baseUrl}/getArea/{{key}}`,
rules: [
{
required: true,
message: '请选择地区',
trigger: 'blur'
}
]
}
]
}
, {
label: '用户信息',
prop: 'yhxxs',
icon: 'el-icon-edit-outline',
column: [
{
label: '测试长度',
prop: 'len',
maxlength: 5,
}, {
labelWidth: 120,
label: '测试自定义',
prop: 'names'
}
]
}
]
}
}
}
}
</script>数据过滤
`filterDic`设置为`true`返回的对象不会包含`$`前缀的字典翻译, `filterNull`设置为`true`返回的对象不会包含空数据的字段
<template>
<tvue-crud :key="reload"
v-model="form"
:option="option"
:data="data">
<template #name-form="{ size }">
<el-button type="danger"
@click="filterDic">过滤数据字典</el-button>
<el-button type="success"
@click="filterNull">过滤空数据</el-button>
<el-button type="primary"
@click="filter">不过滤</el-button>
<p>{{ form }}</p>
<el-input v-model="form.name"
:size="size"></el-input>
</template>
</tvue-crud>
</template>
<script setup>
import { ref, reactive } from 'vue';
const data = ref([
{
cascader: [0, 1],
tree: 0
}
]);
const reload = ref(Math.random());
const form = reactive({});
const option = reactive({
column: [
{
label: '姓名',
span: 24,
prop: 'name',
},
{
label: '级别',
prop: 'cascader',
type: 'cascader',
dicData: [
{
label: '一级',
value: 0,
children: [
{
label: '一级1',
value: 1,
},
{
label: '一级2',
value: 2,
}
]
}
]
},
{
label: '树型',
prop: 'tree',
type: 'tree',
dicData: [
{
label: '一级',
value: 0,
children: [
{
label: '一级1',
value: 1,
},
{
label: '一级2',
value: 2,
}
]
}
]
}
]
});
function refresh () {
reload.value = Math.random();
}
function filter () {
option.filterDic = false;
option.filterNull = false;
refresh();
}
function filterDic () {
option.filterDic = true;
refresh();
}
function filterNull () {
option.filterNull = true;
refresh();
}
</script>render 渲染表单
<template>
<tvue-crud :data="data"
:option="option"
v-model="form"></tvue-crud>
</template>
<script setup>
import { h, ref } from 'vue';
const form = ref({});
const data = ref([
{
name: '张三',
sex: '男'
}, {
name: '李四',
sex: '女'
}
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
renderForm: ({ row }) => {
return h('p',
{
id: 'box',
class: { 'demo': true },
style: { color: 'pink', lineHeight: '30px' },
}, row.name || 'name');
}
},
{
label: '性别',
prop: 'sex'
}
]
});
</script>自定义表单
在卡槽中指定列的`prop`加上`-form`作为卡槽的名称开启自定义
<template>
<tvue-crud :data="data"
:option="option"
v-model="form">
<template #name-form="{ type, disabled }">
<el-tag v-if="type === 'add'">新增</el-tag>
<el-tag v-else-if="type === 'edit'">修改</el-tag>
<el-tag v-else-if="type === 'view'">查看</el-tag>
<el-tag>{{ user.name ? user.name : '暂时没有内容' }}</el-tag>
<el-input :disabled="disabled"
v-model="user.name"></el-input>
</template>
</tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const form = ref({});
const data = ref([
{ name: '张三', sex: '男' },
{ name: '李四', sex: '女' }
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
rules: [
{
required: true,
message: "请输入姓名",
trigger: "blur"
}
]
},
{
label: '性别',
prop: 'sex'
}
]
});
</script>自定义表单错误提示
在卡槽中指定列的`prop`加上`-error`作为卡槽的名称开启自定义
<template>
<tvue-crud :data="data"
:option="option">
<template #name-error="{ error }">
<p style="color:green">自定义提示{{ error }}</p>
</template>
</tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{ sex: '男' },
{ sex: '女' }
]);
const option = {
column: [
{
label: '姓名',
prop: 'name',
rules: [
{
required: true,
message: '请输入姓名',
trigger: 'blur'
}
]
},
{
label: '性别',
prop: 'sex'
}
]
};
</script>自定义表单标题
在卡槽中指定列的`prop`加上`-label`作为卡槽的名称开启自定义
<template>
<tvue-crud :data="data"
:option="option">
<template #name-label="{}">
<span>姓名 </span>
<el-tooltip class="item"
effect="dark"
content="文字提示"
placement="top-start">
<i class="el-icon-warning"></i>
</el-tooltip>
</template>
</tvue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{ name: '张三', sex: '男' },
{ name: '李四', sex: '女' }
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
rules: [
{
required: true,
message: '请输入姓名',
trigger: 'blur'
}
]
},
{
label: '性别',
prop: 'sex'
}
]
});
</script>