You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue带新增行按钮动态表单实现方案及接口设计咨询

Vue长表单改造相关问题解答

问题背景

我正在尝试将一份长表单改造为Vue实现,以提升表单交互性,目前已经完成基础字段的双向绑定,基础功能运行良好。后续需要支持用户点击「新增行」按钮添加多条可重复记录(例如教育经历信息),原始Blade模板版本是直接循环$user->educationDetails渲染已有教育经历数据,现有两个疑问:

    1. 在Vue中,是否推荐将这类关联对象数据加载到data属性中,再通过v-for指令循环渲染?
    1. 若采用上述方案,接口需要同时返回用户基础信息及其他关联模型数据,这类数据应该拆分到不同接口,还是可以在同一个接口中统一返回?

现有实现示例代码

<template>
    <form v-on:submit.prevent action="" method="post">
        <div class="row no-gutter application-section-container">

            <div class="col-12 col-lg-4 application-section-category-sidebar">
                <h3 id="profile" class="application-section-header">Profile</h3>
                <small class="d-block application-section-info">Please fill in your profile details.</small>
                <small class="d-block application-section-requirements"><strong>(All Fields Required)</strong></small>
                <small class="d-block application-section-status"><strong>Incomplete</strong></small> 
            </div>

            <div class="col-12 col-lg-8">
                <div class="application-section" id="application-user-profile">

                <div class="form-row">

                    <div class="col">
                        <label for="first_name">First Name<span class="required">*</span></label>
                        <input type="text" class="form-control" name="first_name" id="first_name" placeholder="First Name" autocomplete="given-name" value="" readonly v-model="first_name">
                    </div>

                    <div class="col">
                        <label for="last_name">Last Name<span class="required">*</span></label>
                        <input type="text" class="form-control" name="last_name" id="last_name" placeholder="Last Name" autocomplete="family-name" value="" readonly v-model="last_name">
                    </div>

                </div>

                <div class="form-row">
                    <div class="col">
                        <label for="email">Email Address<span class="required">*</span></label>
                        <input type="email" class="form-control" name="email" id="email" placeholder="john.doe@example.com" autocomplete="email" value="" readonly v-model="email">
                    </div>

                    <div class="col">
                        <label for="contact_number">Contact Number<span class="required">*</span></label>
                        <input required type="tel" class="form-control" name="contact_number" id="contact_number" minlength="11" maxlength="14" autocomplete="tel" value="" v-model="contact_number">
                    </div>

                </div>

                <div class="form-row">

                    <div class="col-12 col-sm-6 col-md-4">
                        <label for="gender">Gender<span class="required">*</span></label>
                        <select required name="gender" id="gender" class="form-control" v-model="gender">
                            <option v-for="gender in genders" v-bind:key="gender" v-bind:value="gender">{{ gender }}</option>
                        </select>
                    </div>

                </div>

                <div class="form-row">
                    <div class="col-12 mb-3">
                        <label for="profile_address_line_1">Street Address<span class="required">*</span></label>
                        <input type="text" class="form-control mb-2" name="profile_address_line_1" id="profile_address_line_1" placeholder="Address, Line 1" value="" v-model="profile_address_line_1">
                        <input type="text" class="form-control mb-2" name="profile_address_line_2" id="profile_address_line_2" placeholder="Address, Line 2" value="" v-model="profile_address_line_2">
                        <input type="text" class="form-control mb-2" name="profile_address_line_3" id="profile_address_line_3" placeholder="Address, Line 3" value="" v-model="profile_address_line_3">
                    </div>

                    <div class="col-6 col-sm-4 col-md-4 mb-3">
                        <label for="profile_town">Town/City<span class="required">*</span></label>
                        <input type="text" class="form-control" name="profile_town" id="profile_town" value="" v-model="profile_town">
                    </div>
                    <div class="col-6 col-sm-4 col-md-4 mb-3">
                        <label for="profile_county">Region/County<span class="required">*</span></label>
                        <input type="text" class="form-control" name="profile_county" id="profile_county" value="" v-model="profile_county"> 
                    </div>
                    <div class="col-12 col-sm-4 col-md-4 mb-3">
                        <label for="profile_postcode">Postcode<span class="required">*</span></label>
                        <input type="text" class="form-control" name="profile_postcode" id="profile_postcode" value="" v-model="profile_postcode">
                    </div>
                    <div class="col-12">
                        <label for="profile_country">Country<span class="required">*</span></label>
                        <select class="form-control" name="profile_country" id="profile_country" v-model="profile_country">
                        </select>
                    </div>
                </div>

                <div class="form-row">
                    <div class="col-12">
                        <div class="custom-control custom-checkbox">
                            <input class="custom-control-input profile_has_disability" type="checkbox" name="profile_has_disability" value="" id="profile_has_disability" v-model="profile_has_disability">
                            <label class="custom-control-label" for="profile_has_disability">
                                Do you have any disabilities?
                            </label>
                        </div>
                    </div>

                    <div class="col-12 profile-disabilities-container" v-if="profile_has_disability">
                        <label for="profile_disabilities">Please state your disabilities:</label>
                        <textarea class="form-control" name="profile_disabilities" id="profile_disabilities" rows="4" v-model="profile_disabilities"></textarea>    
                        <small>*This will enable us to make any reasonable adjustments for candidates at interview stage</small>
                    </div>        
                </div>

            </div>
            </div>

        </div>
    </form>
</template>

<script>
    export default {
        data(){
            return{
                'genders': [
                    'Male',
                    'Female',
                    'Other',
                    'Prefer not to say'
                ],
                'first_name': null,
                'last_name': null,
                'email': null,
                'contact_number': null,
                'gender': null,
                'profile_address_line_1': null,
                'profile_address_line_2': null,
                'profile_address_line_3': null,
                'profile_town': null,
                'profile_county': null,
                'profile_postcode': null,
                'profile_country': null,
                'profile_has_disability': null,
                'profile_disabilities': null,
            }
        },
        mounted() {
            this.getProfile();
        },
        methods:{
            getProfile(){
                axios.get('/account/api/profile')
                    .then((response) => {
                        console.log(response.data);   
                        this.first_name = response.data.user.first_name;
                        this.last_name = response.data.user.last_name;
                        this.email = response.data.user.email;
                        this.contact_number = response.data.user.contact_number;
                        this.gender = response.data.user.gender;
                        this.profile_address_line_1 = response.data.user.profile_address_line_1;
                        this.profile_address_line_2 = response.data.user.profile_address_line_2;
                        this.profile_address_line_3 = response.data.user.profile_address_line_3;
                        this.profile_town = response.data.user.town;
                        this.profile_county = response.data.user.county;
                        this.profile_postcode = response.data.user.postcode;
                        this.profile_country = response.data.user.country;
                        this.profile_has_disability = response.data.user.has_disability;
                        this.profile_disabilities = response.data.user.disabilities;
                    }).catch((err) => {
                        console.log(err);
                    });
            }
        }
    }
</script>

问题解答

关于关联数据是否存在data中用v-for渲染

非常推荐这么做,这就是Vue处理这类动态列表场景的标准方案。
你要做可新增、删除、编辑的重复记录模块(比如教育经历、工作经历),直接在data里定义一个数组类型的字段(比如educationList),接口拉到已有数据就存进这个数组,点「新增行」就给数组push一个带空字段的新对象,点删除就splice对应索引的项,模板里直接v-for="(item, index) in educationList"循环渲染每一行的表单项,表单项直接用v-model="item.xxx"绑定对应字段就行,实现起来非常顺,响应式也完全不用手动操作DOM,比之前jQuery/Blade混写省至少一半代码。
唯一要注意的点是给v-for绑定唯一key,有后端返回的记录id就用id当key,新增的临时项可以用自增数或者时间戳当临时key,别直接用index当key,不然列表排序、删项的时候容易出表单值错位的问题。

关于接口是合并还是拆分

没有绝对标准答案,按表单实际场景选即可:

  • 如果长表单是单页一次性加载完所有模块(用户进页面就要看到个人信息+教育经历+工作经历所有内容),直接一个接口把所有关联数据一次性返回就行,省得发多次请求拖慢加载速度,后端做个接口关联查询把用户基础信息、各关联表数据一起吐出来,前端拿到直接赋值给对应data字段就好,逻辑更简单。
  • 如果表单是分步骤/分模块懒加载的(比如用户点到「教育经历」标签才加载对应内容),那就拆成独立接口,用户进入对应模块的时候再发请求拉数据,能减少首屏加载的数据包大小,首屏打开更快。
  • 如果关联数据量特别大(比如用户有几十上百条工作经历),哪怕是单页表单也建议拆接口做分页加载,不然一次性拉所有数据接口响应太慢反而影响体验。

你现在做的申请表单场景,一般用户的教育经历、工作经历也就三五条,直接合并到profile接口里一起返回完全没问题,不用过度拆分。


内容的提问来源于stack exchange,提问作者Jesse Luke Orange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 16:39:19