vue?ant?design?封裝彈窗表單的使用
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>
<!-- 開始結(jié)束時間選擇 -->
<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>
<!-- 開關(guān)按鈕 -->
<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;
// 彈出框?qū)挾?
@Prop({type: String, default: '500px'}) width!: string;
// 接收表單渲染內(nèi)容數(shù)據(jù)
@Prop({type: Object, default: () => {console.log()}}) form!: {};
// 接收彈窗窗口標(biāo)題
@Prop({type: String, default: '操作窗口'}) title!: string;
// 接收表單渲染內(nèi)容格式
@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;
}
}
// 監(jiān)聽表單渲染內(nèi)容數(shù)據(jù)接入 + 轉(zhuǎn)換多余傳入問題
@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)
})
}
// 監(jiān)聽是否彈窗屬性
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

所以,得到了這么一個項目,如下

安裝并導(dǎo)入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>啟動應(yīng)用,測試驗證
npm run serve啟動應(yīng)用,效果如下

好了,應(yīng)用就暫時介紹到這里。其實,我更想說說我的疑惑:
Hello.vue中,Username輸入框的前面有個圖片前綴,Password輸入框的前面也有一個圖片前綴,都是通過<template #prefix></template>實現(xiàn)的,一眼看去,應(yīng)該就是通過插槽實現(xiàn)的,但是具體的實現(xiàn)過程是怎樣的,尚不清楚。
簡單調(diào)試了一下,如下圖所示。

ant-design-vue的Form組件的FormItem.js的部分源碼如下,

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Vue.js組件可復(fù)用性的混合(mixin)方式和自定義指令
本篇文章主要介紹了詳解Vue.js組件可復(fù)用性的混合(mixin)方式和自定義指令,具有一定的參考價值,有興趣的可以了解一下2017-09-09
Vue 父子組件實現(xiàn)數(shù)據(jù)雙向綁定效果的兩種方式(案例代碼)
本文給大家分享Vue 父子組件實現(xiàn)數(shù)據(jù)雙向綁定效果的兩種方式,方式一是通過監(jiān)聽事件實現(xiàn)方式二是通過 v-model 實現(xiàn),每種方式結(jié)合實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2022-11-11

