一、背景
页面中频繁用到的表单导致代码的冗余,网上大部分的表单封装都是关于Element和Antd,对于AcroDesign的则很少,遂将过程记录一下。
二、开发环境
Arco-Design + Vite + Vue3
三、成品
html
<template>
<a-row :gutter="gutter">
<a-form
ref="tableForm"
class="table-form"
:model="formModel"
auto-label-width
:rules="rules"
:layout="layout"
:label-align="labelAlign"
>
<a-col
v-for="field in visibleFields"
:key="field.name"
:span="field.span ? field.span : 22"
:data-index="field.index"
>
<a-form-item
v-if="field.name"
:field="field.name"
:label="field.label"
:tooltip="field.tooltip"
:label-col-flex="`${field.labelWidth}px`"
>
<component
:is="field.type"
v-model="formModel[field.name]"
allow-clear
:multiple="field.multiple"
:style="field.style"
:allow-search="field.search"
:placeholder="field.placeholder"
:options="field.options"
:type="field.elType"
:field-names="field.fieldNames"
:disabled="field.disabled"
:checked-value="field.checkedValue"
:unchecked-value="field.uncheckedValue"
:show-time="field.showTime"
:allow-create="field.allowCreate"
:min="field.min"
:max="field.max"
:step="field.step"
:precision="field.precision"
:mode="field.mode"
@change="change"
/>
</a-form-item>
</a-col>
<slot name="custom"></slot>
</a-form>
</a-row>
</template>
<script lang="ts" setup>
import { toRefs, ref, watch, computed, nextTick } from 'vue';
import { FormInstance, ResponsiveValue } from '@arco-design/web-vue';
import type { FieldProps } from '@/types/global';
const props = withDefaults(
defineProps<{ formJson: Array<any>; // JSON配置表单的结构
rules?: any; // 表单验证规则
formState: FieldProps; // 表单的状态管理
layout?: 'horizontal' | 'vertical' | 'inline' | undefined; // 表单布局方式
labelAlign?: 'left' | 'right' | undefined; // 标签对齐方式
gutter?: | number | ResponsiveValue | [number | ResponsiveValue, number | ResponsiveValue]; // 表单项之间的间隔
display?: boolean | void; // 动态显示隐藏表单项 }>(),
{
layout: 'horizontal',
formJson: () => [],
display: false,
}
);
const emit = defineEmits(['change']);
// ref绑定组件
const tableForm = ref<FormInstance>();
const formModel = ref({} as any);
const { formJson, rules, formState, layout, labelAlign, gutter } =
toRefs(props);
defineExpose({
formModel,
tableForm,
});
/*
** 表单过滤,特殊业务场景需要,没有则可以删除
** 用于条件判断是否通过表单条件展示需要的内容
*/
const visibleFields = computed(() => {
if (!props.display) {
return formJson?.value === undefined ? false : formJson.value;
}
return props?.display();
}) as unknown as Array<FieldProps>;
/*
* 按 data-index 将组件进行排序
**/
nextTick(() => {
const elements = document.querySelectorAll('.table-form .arco-col');
const formBox = document.querySelector('.table-form');
// 将 elements 转换为数组
const elementsArray = Array.from(elements);
// 按照 data-index 进行排序
elementsArray.sort((a, b) => {
const indexA = a.getAttribute('data-index');
const indexB = b.getAttribute('data-index');
return indexA - indexB;
});
elementsArray.forEach((element) => {
// 如果没有data-index则不需要
if (!element.getAttribute('data-index')) return;
// 将元素插入到 DOM 中
formBox?.appendChild(element);
});
});
const change = (val: any) => {
emit('change', val);
};
// 监听表单数据变化赋值
watch(
() => props.formState,
(val) => {
if (!val) return;
formModel.value = val;
},
{ deep: true, immediate: true }
);
</script>
<script lang="ts">
export default {
name: 'TableForm',
};
</script>
<style lang="less" scoped></style>
- allow-clear 是否支持清空
- multiple 选择框是否支持多选
- style a-form-item的样式
- allow-search 选择框是否支持搜索
- placeholder 占位符
- options选择框的选择项
- 表单组件内的type样式
- field-names options选项值配置 ... 其它自定义可以依次添加
四、使用示例
html
<template>
<a-modal v-model:visible="visible" @ok="handleOk" @cancel="handleCancel">
<template #title>{{ title }}</template>
<TableForm
ref="formRef"
:form-json="newFormJson"
:form-state="state.formModel"
:rules="rules"
:display="filterFormJson"
></TableForm>
</a-modal>
</template>
<script setup lang="ts">
import { reactive, toRefs, ref, watch, computed } from 'vue';
import { omit } from 'lodash';
import { cpDetail } from '@/api/settings';
import { jsonToFormData } from '@/utils';
import { FormModelProps } from '../types';
import { adminFormJson } from '../formJson';
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
title: {
type: String,
default: '',
},
type: {
type: String,
default: '',
},
record: {
type: Object,
default: () => ({}),
},
serviceList: {
type: Array,
default: () => [],
},
logList: {
type: Array,
default: () => [],
},
});
const rules = {
real_name: [
{
required: true,
message: `所属人员不能为空!`,
},
],
account: [
{
required: true,
message: `账号不能为空!`,
},
],
pwd: [
{
required: true,
message: `请输入密码!`,
},
{
minLength: 6,
message: `密码不能少于六位!`,
},
],
comfirmpwd: [
{
required: true,
message: `请确认密码!`,
},
{
minLength: 6,
message: `密码不能少于六位!`,
},
],
pid: [
{
required: true,
message: `上级账号不能为空!`,
},
],
status: [
{
required: true,
message: `状态不能为空!`,
},
],
};
const { visible, title, type, record, serviceList, logList } = toRefs(props);
const state = reactive({
formModel: {
level: '1',
} as FormModelProps,
});
const formRef = ref();
// 生成一个新的json,补全选择框的options
const newFormJson = computed(() => {
return adminFormJson.map((item: any) => {
if (item.name === 'service_ids') {
return {
...item,
options: serviceList.value,
};
}
return { ...item };
});
});
const filterFormJson = () => {
return newFormJson.value.filter((item) => {
if (item.name === 'pid') {
return state.formModel.level === '2';
}
return true;
});
};
const emit = defineEmits(['onOk', 'onCancel']);
const handleCancel = async () => {
emit('onCancel', false);
};
const handleOk = async () => {
// 表单校验
const res = await formRef.value.tableForm?.validate();
if (!res) {
state.formModel.service_ids = state.formModel.service_ids.join();
if (state.formModel.level === '1')
state.formModel = omit(state.formModel, ['pid']);
emit('onOk', false, state.formModel);
}
};
const getCpDetail = async (id: string) => {
const { result } = await cpDetail(jsonToFormData({ id }));
state.formModel = result;
state.formModel.service_ids = result.service_list.map((item) => item.service_id);
state.formModel.pid = data.pid === '0' ? '' : data.pid;
};
watch(
() => type,
(val) => {
if (val.value === 'edit') {
getCpDetail(record.value.id);
state.formModel = omit(state.formModel, [
'last_time',
'service_list',
'p_name',
]);
}
},
{ deep: true, immediate: true }
);
</script>
<style scoped lang="less">
.arco-btn {
width: 80px;
}
.arco-col {
text-align: center;
}
下面贴一下formJson.ts
ts
export const adminFormJson = [
{
name: 'channelName',
label: '频道名称',
type: 'a-input',
placeholder: '请输入板块名称',
maxLength: 4,
},
{
name: 'channelOrder',
label: '频道顺序',
type: 'a-input-number',
placeholder: '请填写顺序',
fieldNames: { value: 'dictValue', label: 'dictLabel', key: 'dictValue' },
options: pagesOptions,
min: 0,
max: 10,
precision: 0,
},
{
name: 'isShow',
label: '是否露出',
type: 'a-select',
placeholder: '请选择',
fieldNames: { value: 'value', label: 'label', key: 'value' },
options: [
{
value: '1',
label: '是',
},
{
value: '2',
label: '否',
},
],
},
{
name: 'isDefault',
label: '是否设为默认页面',
type: 'a-select',
placeholder: '请选择',
fieldNames: { value: 'value', label: 'label', key: 'value' },
options: [
{
value: '1',
label: '是',
},
{
value: '2',
label: '否',
},
],
},
];