1.Form的封装
疯转思想:原组件不单个传递props,传递的参数与原组件保持一直
v-bind="item.fiedProps"的方式可以接受传递过来的数据
inheritAttrs: false, v-bind="$attrs" 父组件的值和组件保持一直
<template>
<div class="custom-form-wrap p20">
<a-form :model="formValue" v-bind="$attrs" ref="formRef">
<a-row :gutter="16">
<a-col :span="item.span" v-for="item in option" :key="item.id">
<a-form-item v-bind="item">
<component
v-if="componentsMap[item.type] !== 'slot' && componentsMap[item.type] !== 'UploadImage'"
:is="componentsMap[item.type]"
v-model="formValue[item.field]"
v-bind="item.fiedProps"
v-on="
Object.keys(item.event || {}).reduce((acc, key) => {
acc[key] = (value) => {
item.event[key](value);
};
return acc;
}, {})
"
/>
<template v-else-if="item.type === 'UploadImage'">
<MyUploadImage
v-model:fileList="formValue[item.field]"
:v-bind="item.fiedProps"
v-on="
Object.keys(item.event || {}).reduce((acc, key) => {
acc[key] = (value) => {
item.event[key](value);
};
return acc;
}, {})
"
/>
</template>
<template v-else-if="item.type === 'slot'">
<slot name="customSlot" :fieldName="item.field" :fiedProps="item.fiedProps" />
</template>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
</template>
<script setup lang="jsx">
import { computed, reactive, ref, watch } from 'vue';
import { componentsMap } from '@/config/index';
import MyUploadImage from './MyUploadImage.vue';
const props = defineProps({
fiedOptions: {
type: Array,
default: () => [],
},
formValue: {
type: Object,
default: () => ({}),
},
inheritAttrs: false,
});
watch(
() => props.fiedOptions,
(newVal) => {
console.log('newVal:', newVal);
},
{
deep: true,
},
);
const $emit = defineEmits(['update:formValue', 'validateFaild', 'validateSuccess']);
const formRef = ref(null);
const option = computed(() => {
return props.fiedOptions.map((item, index) => ({
...item,
id: index,
}));
});
const handleSubmit = () => {
console.log('formRef:', formRef.value);
formRef.value.validate((errors) => {
if (errors) {
$emit('validateFaild', errors);
return;
}
$emit('validateSuccess', props.formValue);
$emit('update:formValue', props.formValue);
});
};
const handleReset = () => {
$emit('update:formValue', {});
formRef.value.resetFields();
};
const handleClearValidate = () => {
formRef.value.clearValidate();
};
defineExpose({
handleSubmit,
handleReset,
handleClearValidate,
});
</script>
<style lang="scss" scoped></style>
2.form表单的使用
<template>
<div class="form-demo-wrap">
<MyForm
ref="myFormRefs"
v-model:formValue="formValue"
@validateSuccess="handleValidateSuccess"
@validateFaild="handleValidateFaild"
:fiedOptions="fiedOptions"
layout="horizontal"
label-align="left"
auto-label-width
scroll-to-first-error
>
<template #customSlot="{ fieldName }">
<a-input v-model="formValue[fieldName]" placeholder="Enter your name" />
</template>
</MyForm>
<div class="submit">
<a-button type="primary" html-type="submit" @click="myFormRefs.handleSubmit()">提交</a-button>
<a-button @click="myFormRefs.handleReset()">重置</a-button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import MyForm from '@/components/MyForm.vue';
const fiedOptions = [
{
field: 'name',
label: 'Name',
type: 'input',
fiedProps: {
placeholder: '请输入内容',
},
rules: [{ required: true, message: 'Please input your name', trigger: 'change' }],
span: 12,
},
{
field: 'select',
label: 'Select',
type: 'select',
fiedProps: {
placeholder: 'Please select',
options: [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
],
},
rules: [{ required: true, message: 'Please select', trigger: 'change' }],
span: 12,
},
{
field: 'date',
label: 'Date',
type: 'date',
fiedProps: {
placeholder: 'Select date',
},
rules: [{ required: true, message: 'Please select date', trigger: 'change' }],
span: 12,
},
{
field: 'checkbox',
label: 'Checkbox',
type: 'checkbox',
fiedProps: {
options: [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange' },
],
},
rules: [{ required: true, message: 'Please select at least one', trigger: 'change' }],
span: 12,
},
{
field: 'radio',
label: 'Radio',
type: 'radio',
fiedProps: {
options: [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange' },
],
},
rules: [{ required: true, message: 'Please select one', trigger: 'change' }],
span: 12,
},
{
field: 'textarea',
label: 'Textarea',
type: 'textarea',
fiedProps: {
placeholder: 'Enter something...',
},
rules: [{ required: true, message: 'Please input something', trigger: 'blur' }],
span: 12,
},
{
field: 'customSlot',
label: 'CustomSlot',
type: 'slot',
span: 12,
rules: [{ required: true, message: 'Please input something', trigger: 'blur' }],
},
];
const myFormRefs = ref(null);
const formValue = ref({
name: '',
select: '',
date: '',
checkbox: [],
radio: '',
textarea: '',
slot: '',
});
const handleValidateFaild = (error) => {
console.log(error);
};
const handleValidateSuccess = (value) => {
console.log(value);
};
</script>
<style lang="scss" scoped></style>
3.基本用法
<template>
<MyForm
:fiedOptions="fiedOptions"
v-model:formValue="formValue"
@validateSuccess="onValidateSuccess"
@validateFaild="onValidateFaild"
/>
</template>
<script setup>
import { ref } from 'vue';
import MyForm from './MyForm.vue';
const fiedOptions = ref([
{
field: 'name',
type: 'input',
label: '姓名',
span: 12,
fiedProps: {
placeholder: '请输入姓名',
},
event: {
change: (value) => {
console.log('姓名改变:', value);
},
},
},
{
field: 'age',
type: 'input',
label: '年龄',
span: 12,
fiedProps: {
placeholder: '请输入年龄',
},
},
]);
const formValue = ref({
name: '',
age: '',
});
const onValidateSuccess = (value) => {
console.log('验证成功:', value);
};
const onValidateFaild = (errors) => {
console.log('验证失败:', errors);
};
</script>
4.插槽用法
<template>
<MyForm
:fiedOptions="fiedOptions"
v-model:formValue="formValue"
>
<template #customSlot="{ fieldName, fiedProps }">
<a-input v-model="formValue[fieldName]" v-bind="fiedProps" />
</template>
</MyForm>
</template>
<script setup>
import { ref } from 'vue';
import MyForm from './MyForm.vue';
const fiedOptions = ref([
{
field: 'customField',
type: 'slot',
label: '自定义字段',
span: 24,
fiedProps: {
placeholder: '请输入自定义字段',
},
},
]);
const formValue = ref({
customField: '',
});
</script>
5.table和form的组合使用
<template>
<div class="custom-table-wrap g-content g-content-footer pt22 pb22 pl28 pr28">
<MyForm
ref="myFormRefs"
v-model:formValue="searchParam"
@validateSuccess="handleValidateSuccess"
@validateFaild="handleValidateFaild"
:fiedOptions="fiedOptions"
layout="horizontal"
label-align="left"
auto-label-width
scroll-to-first-error
>
<template #customSlot="{ fieldName }">
<a-input v-model="searchParam[fieldName]" placeholder="Enter your name" />
</template>
</MyForm>
<div style="text-align: right" class="mb10">
<a-button type="primary" @click="search">Search</a-button>
<a-button @click="reset">Reset</a-button>
</div>
<div class="org-table-wrap">
<a-table :loading="loading" :data="tableData" :columns="columns" :bordered="false" :pagination="false" class="arco-none-border" />
</div>
<div class="g-footer">
<MyPagination
v-if="tableData.length"
:total="pagination.count"
:current-page="pagination.page_num"
:page-size="pagination.page_size"
@handleSizeChange="handleSizeChange"
@handleChange="handleCurrentChange"
></MyPagination>
</div>
</div>
</template>
<script setup lang="jsx">
import { watch, toRefs, ref } from 'vue';
import MyPagination from '@/components/pagination.vue';
import { useTable } from '@/hooks/useTable';
import MyForm from '@/components/MyForm.vue';
import api from '@/api';
const $api = (req) => api.post('/api/employee/query-employee', req);
const { searchParam, pagination, tableData, loading, handleCurrentChange, handleSizeChange, search, reset } = useTable($api);
const columns = [
{
title: '部门',
dataIndex: 'department_name',
key: 'department_name',
align: 'center',
render: ({ text, record }) => {
return record?.department_name || '-';
},
},
{
title: '员工姓名',
dataIndex: 'employee_name',
key: 'employee_name',
align: 'center',
render: ({ text, record }) => {
return record?.employee_name || '-';
},
},
{
title: '登录手机号',
dataIndex: 'employee_phone',
key: 'employee_phone',
align: 'center',
render: ({ text, record }) => {
return record?.employee_phone || '-';
},
},
{
title: '性别',
dataIndex: 'employee_sex',
key: 'employee_sex',
align: 'center',
render: ({ text, record }) => {
return record?.employee_sex || '-';
},
},
{
title: '服务角色',
dataIndex: 'service_role_list',
key: 'service_role_list',
align: 'center',
render: ({ text, record }) => {
return record.service_role_list.map((item) => item.role_name).join('、');
},
},
{
title: '企业微信号',
dataIndex: 'cpwx_userid',
key: 'cpwx_userid',
align: 'center',
render: ({ text, record }) => {
return record?.cpwx_userid || '-';
},
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
align: 'center',
render: ({ text, record }) => {
return record?.remark || '-';
},
},
{
title: '状态',
dataIndex: 'employee_status',
key: 'employee_status',
align: 'center',
render: ({ text, record }) => {
return record.employee_status ? '启用' : '禁用';
},
},
{
title: '操作',
dataIndex: 'operation',
key: 'operation',
align: 'center',
render: ({ text, record }) => {
return (
<a-button onClick={() => handleEdit(record)} type="text">
编辑
</a-button>
);
},
},
];
const handleEdit = (record) => {
console.log(record);
searchParam.value.employee_id = record.id;
search();
};
const fiedOptions = [
{
field: 'name',
label: 'Name',
type: 'input',
fiedProps: {
placeholder: '请输入内容',
},
span: 12,
},
{
field: 'select',
label: 'Select',
type: 'select',
fiedProps: {
placeholder: 'Please select',
options: [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
],
},
span: 12,
},
{
field: 'date',
label: 'Date',
type: 'date',
fiedProps: {
placeholder: 'Select date',
},
span: 12,
},
{
field: 'checkbox',
label: 'Checkbox',
type: 'checkbox',
fiedProps: {
options: [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange' },
],
},
span: 12,
},
{
field: 'radio',
label: 'Radio',
type: 'radio',
fiedProps: {
options: [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange' },
],
},
span: 12,
},
{
field: 'textarea',
label: 'Textarea',
type: 'textarea',
fiedProps: {
placeholder: 'Enter something...',
},
span: 12,
},
{
field: 'customSlot',
label: 'CustomSlot',
type: 'slot',
span: 12,
},
];
const myFormRefs = ref(null);
const handleValidateFaild = (error) => {
console.log(error);
};
const handleValidateSuccess = (value) => {
console.log(value);
};
</script>
<style scoped lang="less"></style>
4.附赠useTable hooks 真的香
import { ref, onMounted, toRefs } from 'vue';
export const useTable = (api, initParam = {}, isPageable = true, isAuto = true) => {
const state = ref({
tableData: [],
pagination: {
page_num: 1,
page_size: 10,
count: 0,
},
searchParam: {},
totalParam: {},
loading: false,
});
onMounted(() => {
if (isAuto) reset();
});
const getTableData = async () => {
try {
state.value.loading = true;
const data = await api(state.value.totalParam);
const { count, rows } = data;
state.value.tableData = isPageable ? rows : [];
if (isPageable) updatePagination({ page_num: state.value.pagination.page_num, page_size: state.value.pagination.page_size, count });
} catch (err) {
console.error(err);
} finally {
state.value.loading = false;
}
};
const updatePagination = (resPageable) => {
Object.assign(state.value.pagination, resPageable);
};
const updatedTotalParam = () => {
state.value.totalParam = {};
let paginationParam = {};
let nowSearchParam = {};
Object.keys(state.value.searchParam).forEach((key) => {
if (state.value.searchParam[key] !== '' && state.value.searchParam[key] !== undefined && state.value.searchParam[key] !== null) {
nowSearchParam[key] = state.value.searchParam[key];
}
});
if (isPageable) {
paginationParam = {
page_num: state.value.pagination.page_num,
page_size: state.value.pagination.page_size,
};
}
Object.assign(state.value.totalParam, nowSearchParam, paginationParam, initParam);
};
const search = async () => {
updatedTotalParam();
await getTableData();
};
const reset = async () => {
state.value.pagination.page_num = 1;
state.value.pagination.page_size = 10;
state.value.searchParam = {};
updatedTotalParam();
await search();
};
const handleSizeChange = async (val) => {
state.value.pagination.page_num = 1;
state.value.pagination.page_size = val;
console.log('state.value.pagination', state.value.pagination);
await search();
};
const handleCurrentChange = async (val) => {
state.value.pagination.page_num = val;
await search();
};
const { searchParam, pagination, tableData, loading } = toRefs(state.value);
return {
searchParam,
pagination,
tableData,
loading,
handleCurrentChange,
handleSizeChange,
search,
reset,
};
};