Posted in Vue.js onJune 01, 2022
vue ant design 封装弹窗表单
<template>
<div id="formForm">
<a-modal
:visible="true"
:title='title'
@ok="handleOk('ok')"
@cancel="handleOk('return')"
:centered="true"
:confirmLoading="confirmLoading"
:width="width">
<a-form :form="formState" :label-col="{ span: 5 }" :wrapper-col="{ span: 17 }">
<div v-for="itme in formData" :key="itme.value" >
<!-- 输入款 -->
<a-form-item
:label="itme.label"
v-if="itme.type === 'input'"
:label-col="{ span: itme.labelCol ? itme.labelCol : 5 }"
:wrapper-col="{ span: itme.wrapper ? itme.wrapper : 17 }">
<a-input
v-decorator="[itme.value, { rules: [{
required: itme.required?itme.required:false,
message: itme.message?itme.message:' ' },
{validator: itme.validator}]}]"
:placeholder="!itme.placeholder ? itme.label : itme.label"
allowClear>
<!-- 插入输入框的下拉框选择器 -->
<a-select
v-if="itme.select && itme.select.length>0"
slot="addonBefore"
v-decorator="[ itme.header ]"
style="width: 90px">
<a-select-option v-for="select in itme.select" :key="select.value">
{{select.label}}
</a-select-option>
</a-select>
</a-input>
</a-form-item>
<!-- 开始结束时间选择 -->
<a-form-item :label="itme.label" v-if="itme.type === 'rangePicker'">
<a-range-picker
:placeholder="!itme.placeholder ? itme.label : itme.placeholder"
showTime
:style="`width: ${!itme.wrapper?'320':itme.wrapper}px;`"
v-decorator="[itme.value, { rules: [{ required: itme.required?itme.required:false, message: itme.message?itme.message:' ' }]}]" />
</a-form-item>
<!-- 单个时间选择 -->
<a-form-item
:label="itme.label" v-if="itme.type === 'datePicker'">
<a-date-picker
:style="`width: ${!itme.wrapper?'180':itme.wrapper}px;`"
v-decorator="[ itme.value, { rules: [{ required: itme.required?itme.required:false, message: itme.message?itme.message:' ' }]}]"
showTime
:placeholder="!itme.placeholder ? itme.label : itme.placeholder" />
</a-form-item>
<!-- 选择框 -->
<a-form-item
:label="itme.label"
v-if="itme.type === 'select'"
:label-col="{ span: itme.labelCol ? itme.labelCol : 5 }"
:wrapper-col="{ span: itme.wrapper ? itme.wrapper : 8 }">
<a-select
allowClear
v-decorator="[ itme.value, { rules: [{
required: itme.required?itme.required:false,
message: itme.message?itme.message:' ' }]}]"
:placeholder="!itme.placeholder ? itme.label : itme.placeholder">
<a-select-option v-for="optionItme in itme.option" :key="optionItme.value">
{{optionItme.label}}
</a-select-option>
</a-select>
</a-form-item>
<!-- 单选框 -->
<a-form-item :label="itme.label" v-if="itme.type === 'radio'">
<a-radio-group
v-decorator="[ itme.value, { rules: [{ required: itme.required?itme.required:false, message: itme.message?itme.message:' ' }]}]">
<a-radio v-for="radioItme in itme.radio" :key="radioItme.value" :value="radioItme.value">
{{radioItme.label}}
</a-radio>
</a-radio-group>
</a-form-item>
<!-- 开关按钮 -->
<a-form-item :label="itme.label" v-if="itme.type === 'switch'">
<a-switch v-decorator="[ itme.value, { valuePropName: 'checked' }]" />
</a-form-item>
<!-- 图片上传 -->
<a-form-item
:label="itme.label"
v-if="itme.type === 'upload'"
:label-col="{ span: itme.labelCol ? itme.labelCol : 5 }"
:wrapper-col="{ span: itme.wrapper ? itme.wrapper : 20 }">
<a-upload
v-decorator="[ itme.value, { valuePropName: 'fileList', getValueFromEvent: normFile, }]"
:action="itme.action?itme.action:'https://www.mocky.io/v2/5cc8019d300000980a055e76'"
listType="picture-card"
@preview="handlePreview">
<div v-if="itme.value.length < 8">
<a-icon type="plus" />
<div class="ant-upload-text">点击上传图片</div>
</div>
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="previewVisible = false">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal>
</a-form-item>
</div>
</a-form>
</a-modal>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Prop, Emit, Watch } from 'vue-property-decorator';
import Moment from 'moment'
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
@Component({
data() {
return {
formState: this.$form.createForm(this),
previewVisible: false,
previewImage: ''
};
},
})
export default class FormForm extends Vue {
[x: string]: any;
// 弹出框宽度
@Prop({type: String, default: '500px'}) width!: string;
// 接收表单渲染内容数据
@Prop({type: Object, default: () => {console.log()}}) form!: {};
// 接收弹窗窗口标题
@Prop({type: String, default: '操作窗口'}) title!: string;
// 接收表单渲染内容格式
@Prop({type: Array, default: () => []}) formData!: [];
// 返出取消和确定按钮
@Emit('handleOk')
handleOk(e) {
if (e === 'return') {
return 'true';
} else if (e === 'ok') {
let stateType: object | boolean = false;
this.formState.validateFields((err, value) => {
if (!err) {
this.confirmLoading = true;
stateType = value;
}
})
return stateType;
}
}
// 监听表单渲染内容数据接入 + 转换多余传入问题
@Watch('form', {immediate: true, deep: false})
onForm(e) {
let obj: object = {};
Object.keys(e).forEach(key => {
Array.from(this.formData).forEach((res: any | object) => {
if (key === res.value || key === res.header) {
if (res.type === 'rangePicker' && e[key].length > 0) {
e[key] = [ Moment(e[key][0]), Moment(e[key][1]) ]
}
if (res.type === 'datePicker' && e[key]) {
e[key] = Moment(e[key])
}
obj[key] = e[key]
}
})
})
this.$nextTick(() => {
this.formState.setFieldsValue(obj)
})
}
// 监听是否弹窗属性
public visibleOff: boolean = false;
// 确定按钮loading
public confirmLoading: boolean = false;
// --------- methods ------------
async handlePreview(file) {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
this.previewImage = file.url || file.preview;
this.previewVisible = true;
}
normFile(e) {
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
}
}
</script>
<style lang='scss' scpoed>
.ant-form-item-label{
white-space: pre-wrap;
line-height: 25px;
}
.ant-row{
display: flex;
align-items: center;
}
.ant-form{
max-height: 60vh;
overflow: auto;
&::-webkit-scrollbar {
display: none;;
}
}
.ant-form-item{
margin-bottom: 10px;
}
.ant-form-item-control{
left: 10px;
max-height: 225px;
overflow: auto;
&::-webkit-scrollbar{
display: none;
}
}
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
</style>
部分效果图:
使用ant-design-vue的Form表单
使用脚手架新建项目
vue create antd-demo
所以,得到了这么一个项目,如下
安装并导入ant-design-vue,使用Form组件
npm install --save ant-design-vue@next
修改main.ts
import { createApp } from 'vue';
import App from './App.vue';
import Antd from "ant-design-vue";
import "ant-design-vue/dist/antd.css";
createApp(App).use(Antd).mount('#app');
修改App.vue
<template>
<HelloWorld/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default defineComponent({
name: 'App',
components: {
HelloWorld
}
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
修改HelloWorld.vue
<template>
<a-form
layout="inline"
:model="formState"
@finish="handleFinish"
@finishFailed="handleFinishFailed"
>
<a-form-item>
<a-input v-model:value="formState.user" placeholder="Username">
<template #prefix><UserOutlined style="color: rgba(0, 0, 0, 0.25)" /></template>
</a-input>
</a-form-item>
<a-form-item>
<a-input v-model:value="formState.password" type="password" placeholder="Password">
<template #prefix><LockOutlined style="color: rgba(0, 0, 0, 0.25)" /></template>
</a-input>
</a-form-item>
<a-form-item>
<a-button
type="primary"
html-type="submit"
:disabled="formState.user === '' || formState.password === ''"
>
Log in
</a-button>
</a-form-item>
</a-form>
</template>
<script lang="ts">
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import { defineComponent, reactive, UnwrapRef } from 'vue';
interface FormState {
user: string;
password: string;
}
export default defineComponent({
setup() {
const formState: UnwrapRef<FormState> = reactive({
user: '',
password: '',
});
const handleFinish = (values: FormState) => {
console.log(values, formState);
};
const handleFinishFailed = (errors: ValidateErrorEntity<FormState>) => {
console.log(errors);
};
return {
formState,
handleFinish,
handleFinishFailed,
};
},
components: {
UserOutlined,
LockOutlined,
},
});
</script>
启动应用,测试验证
npm run serve启动应用,效果如下
好了,应用就暂时介绍到这里。其实,我更想说说我的疑惑:
Hello.vue中,Username输入框的前面有个图片前缀,Password输入框的前面也有一个图片前缀,都是通过<template #prefix></template>实现的,一眼看去,应该就是通过插槽实现的,但是具体的实现过程是怎样的,尚不清楚。
简单调试了一下,如下图所示。
ant-design-vue的Form组件的FormItem.js的部分源码如下,
以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。
vue ant design 封装弹窗表单的使用
- Author -
采药人~陈大仙- Original Sources -
声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
Tags in this post...
Reply on: @reply_date@
@reply_contents@