vue3项目封装form表单

本文基于Element-plus实现二次封装表单组件。

我们都知道表单组件应该是后台管理系统中用得最多的组件,我们不可能每个业务都写一次表单,然后每一次修改都去各自业务中大幅修改,这样就可能导致代码重复率太高了,工作效率频频降低,所以我们需要封装起来,这样我们就可以复用,大大减少项目体积,方便项目的后期维护,提高前端娃的工作效率。由于我们使用表单都是直接使用UI组件库的组件,所以我们需要做二次封装,那么问题来了,二次封装表单组件我们需要考虑什么?

  • 特性复用:必须继承原有组件的所有特性。
  • 命名规范:二次组件名必须见名知意,我一般都是用FormPanel表单这个名。
  • 接口简单:自定义暴露出来的接口越简单越好。
  • 容易拓展:留有自定义插槽,让用户可以自己选择。
  • 功能完善:具备更完善的功能如:表单验证、动态删减表单,集成第三方的插件(富文本)…
  • 场景通用:具备多个场景使用,比如弹框嵌套表单、页面嵌套表单。

话不多说,直接展示代码,以下代码可以直接使用:

<template>
  <el-form
    :model="model"
    :inline="inline"
    :label-position="labelPosition"
    :label-width="labelWidth"
    :label-suffix="labelSuffix"
    :hide-required-asterisk="hideRequiredAsterisk"
    :require-asterisk-position="requireAsteriskPosition"
    :show-message="showMessage"
    :inline-message="inlineMessage"
    :status-icon="statusIcon"
    :validate-on-rule-change="validateOnRuleChange"
    :size="size"
    :disabled="disabled"
    :scroll-to-error="scrollToError"
    :scroll-into-view-options="scrollIntoViewOptions"
		ref="formPanelRef"
  >
		<el-row v-if="inline">
			<el-col
				:span="(item.cols && item.cols != '') ? Number(item.cols) : 24/ Number(cols)"
				v-for="(item, index) in formConfig"
        :key="index"
			>
				<!-- input类型 -->
				<el-form-item
					v-if="item.type === 'input' && item.type === 'textarea'"
					:label="item.label" 
          :prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<el-input
						type="textarea"
						:clearable="clearable" 
						:style="item.style ? item.style : ''" 
						v-model="model[item.fieldName]" 
						:placeholder="item.placeholder ? item.placeholder : `请输入`"
						:disabled="item.disabled ? item.disabled : false"
						:maxlength="item.maxlength ? item.maxlength : ''"
						:minlength="item.minlength ? item.minlength : ''"
						:show-word-limit="item.showWordLimit ? item.showWordLimit : false"
						:autosize="item.autosize ? item.autosize : false"
						:rows="item.rows ? item.rows : 1"
						@change="item.change"
					>
						<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}</template>
						<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}</template>
					</el-input>
				</el-form-item>
				<el-form-item
					v-else-if="item.type === 'input'"
					:label="item.label" 
          :prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<el-input
						:type="item.inputType ? item.inputType : 'text'"
						:clearable="clearable" 
						:style="item.style ? item.style : ''" 
						v-model.trim="model[item.fieldName]" 
						:placeholder="item.placeholder ? item.placeholder : `请输入`"
						:disabled="item.disabled ? item.disabled : false"
						:maxlength="item.maxlength ? item.maxlength : ''"
						:minlength="item.minlength ? item.minlength : ''"
						:show-word-limit="item.showWordLimit ? item.showWordLimit : false"
						:autosize="item.autosize ? item.autosize : false"
						:rows="item.rows ? item.rows : 1"
						@change="item.change"
					>
						<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}</template>
						<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}</template>
					</el-input>
				</el-form-item>
				<!-- 自动补全 -->
				<el-form-item 
          v-if="item.type === 'autocomplete'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
					<el-autocomplete
						:clearable="clearable" 
						:style="item.style ? item.style : ''" 
						v-model="model[item.fieldName]" 
						:disabled="item.disabled ? item.disabled : false"
						:placeholder="item.placeholder ? item.placeholder : `请输入`"
						:fetch-suggestions="item.suggestions"
						@select="item.select"
						@change="item.change"
						@keydown="inputKeydown($event, item.DisableInput)"
					>
						<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}</template>
						<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}</template>
					</el-autocomplete>
				</el-form-item>
				<!-- 数字类型Input增加输入限制 -->
				<el-form-item 
          v-if="item.type === 'inputNumber'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-input
						type="Number"
						:clearable="clearable" 
						:style="item.style ? item.style : ''" 
						v-model="model[item.fieldName]" 
						:disabled="item.disabled ? item.disabled : false"
						:placeholder="item.placeholder ? item.placeholder : `请输入`"
						@change="item.change"
						@keydown="inputKeydown($event, item.DisableInput)"
					>
						<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}</template>
						<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}</template>
					</el-input>
        </el-form-item>
				<!-- 自带加减数字类型Input增加输入限制 -->
					<el-form-item 
						v-if="item.type === 'inputNumberBtn'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-input
							type="Number"
							:clearable="clearable" 
							:style="item.style ? item.style : ''" 
							v-model="model[item.fieldName]" 
							:disabled="item.disabled ? item.disabled : false"
							:placeholder="item.placeholder ? item.placeholder : `请输入`"
							@change="inputNumberBtnChange(item.fieldName,item.minNum,item.change);"
							ref="inputNumberBtnRef"
							@keydown="inputKeydown($event, item.DisableInput)"
						>
							<template  #prepend>
								<el-button :disabled="model[item.fieldName] <= 1 || model[item.fieldName] == '' || !model[item.fieldName] || model[item.fieldName] == undefined" style="width: 10px !important; min-width: 10px !important;" @click="inputNumberBtnReduce(item.fieldName,item.change)">-</el-button>
							</template>
							<template #append>
								<el-button style="width: 10px !important; min-width: 10px !important;" @click="inputNumberBtnAdd(item.fieldName, item.change)">+</el-button>
							</template>
						</el-input>
					</el-form-item>
				<!-- 范围输入框,默认为数字类型 -->
				<el-form-item 
          v-if="item.type === 'inputrange'"
          :label="item.label" 
          :required="item.rules ? true : false"
        >
          <el-col :span="11">
            <el-form-item 
              :prop="item.fieldName[0]"
              :rules="item.rules ? item.rules : []"
            >
              <el-input
                @keydown="inputKeydown($event, item.DisableInput)"
                type="Number"
                :clearable="clearable" 
                :style="item.style ? item.style : ''" 
                v-model="model[item.fieldName[0]]" 
                :disabled="item.disabled ? item.disabled : false"
                @change="inputrangeStarChange($event, item.fieldName[0],item.fieldName[1])"
                :placeholder="item.placeholder[0] ? item.placeholder[0] : `请输入`">
              </el-input>
            </el-form-item>
          </el-col>
          <el-col style="text-align: center;" :span="2">{{item.rangeSeparator}}</el-col>
          <el-col :span="11">
            <el-form-item 
              :prop="item.fieldName[1]"
              :rules="item.rules ? item.rules : []"
            >
              <el-input
                @keydown="inputKeydown($event, item.DisableInput)"
                type="Number"
                :clearable="clearable" 
                :style="item.style ? item.style : ''" 
                v-model="model[item.fieldName[1]]" 
                :disabled="item.disabled ? item.disabled : false"
                @change="inputrangeEndChange($event, item.fieldName[0],item.fieldName[1])"
                :placeholder="item.placeholder[1] ? item.placeholder[1] : `请输入`">
              </el-input>
            </el-form-item>
          </el-col>
        </el-form-item>
				<!-- 下拉框 -->
				<el-form-item 
          v-if="item.type === 'select'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-select
						:filterable="item.filterable === undefined ? true : item.filterable"
            :style="item.style ? item.style : ''" 
            :clearable="clearable" 
            v-model="model[item.fieldName]" 
            :disabled="item.disabled ? item.disabled : false"
						:placeholder="item.placeholder ? item.placeholder : `请选择`"
						:multiple="item.multiple ? item.multiple : false"
					  :collapse-tags="item.collapseTags || false"
      			collapse-tags-tooltip
						@change="item.change"
          >
            <el-option
              v-for="(optionItem, optionIndex) in item.optionList"
              :key="optionIndex"
              :label="optionItem.label"
							:disabled="optionItem.disabled"
              :value="optionItem.value === undefined ? optionItem.label : optionItem.value">
            </el-option>
          </el-select>
        </el-form-item>
				<!-- 日期时间-->
        <el-form-item 
          v-if="item.type === 'date'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-date-picker
						v-model="model[item.fieldName]"
            :value-format="item.valueFormat ? item.valueFormat :'yyyy-MM-dd'"
            :format="item.valueFormat ? item.valueFormat :'yyyy-MM-dd'"
            :style="item.style ? item.style : ''" 
            :clearable="clearable"
            :disabled="item.disabled ? item.disabled : false"
						:disabled-date="item.disabledDate ? item.disabledDate : null"
            :type="item.dataType ? item.dataType : 'date'"
            :placeholder="item.placeholder ? item.placeholder : `请选择`"
            :start-placeholder="item.startPlaceholder ? item.startPlaceholder : `请选择`"
						:end-placeholder="item.endPlaceholder ? item.endPlaceholder : `请选择`"
            @change="item.change"
          >
          </el-date-picker>
        </el-form-item>
				<!-- switch -->
        <el-form-item 
          v-if="item.type === 'switch'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-switch
						v-model="model[item.fieldName]" 
						:disabled="item.disabled ? item.disabled : false"
						:loading="item.loading ? item.loading : false"
            :active-value="item.activeValue ? item.activeValue : true"
            :inactive-value="item.inactiveValue ? item.inactiveValue : false"
						:style="`--el-switch-on-color: ${item.activeColor}; --el-switch-off-color: ${item.inactiveColor}; --el-switch-border-color: ${item.borderColor}`"
            :placeholder="item.placeholder ? item.placeholder : `请输入`"
            :before-change="item.beforeChange ? item.beforeChange : undefined"
            @change="item.change"
          >
          </el-switch>
        </el-form-item>
				<!-- 单选框 -->
        <el-form-item 
          v-if="item.type === 'radio'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-radio-group 
            v-model="model[item.fieldName]"
						:disabled="item.disabled ? item.disabled : false"
						:border="item.border ? item.border : false"
            @change="item.change"
          >
            <el-radio 
              v-for="(optionItem, optionIndex) in item.optionList" 
              :label="JSON.stringify(optionItem)" 
              :key="optionIndex"
            >
              {{optionItem.label}}
            </el-radio>
          </el-radio-group>
        </el-form-item>
				<!-- 多选框 -->
        <el-form-item 
          v-if="item.type === 'checkbox' && model[item.fieldName]"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-checkbox-group 
            v-model="model[item.fieldName]"
            :disabled="item.disabled ? item.disabled : false"
						:min="item.min ? item.min : undefined"
						:max="item.max ? item.max : undefined"
						:text-color="item.textColor ? item.textColor : '#FFFFFF'"
						:fill="item.fill ? item.fill : '#3171F1'"
						@change="item.change"
          >
            <el-checkbox 
              v-for="(optionItem, optionIndex) in item.optionList" 
              :label="optionItem" 
              :key="optionIndex">
              {{optionItem.label}}
            </el-checkbox>
          </el-checkbox-group>
        </el-form-item>
				<!-- 地图 -->
				<el-form-item 
					v-if="item.type === 'mapInput' && model[item.fieldName]"
					:label="item.label" 
					:prop="item.fieldName.address"
					:rules="item.rules ? item.rules : []"
				>
					<BaiduMap
						:placeholder="item.placeholder"
						:disabled="item.disabled ? item.disabled : false"
						:clearable="clearable"
						v-model:mapData="model[item.fieldName]"
					/>
				</el-form-item>
				<!-- 省市区选择 -->
				<el-form-item
					v-if="item.type === 'regionInput'"
					:label="item.label" 
          :prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<el-cascader
						v-model.trim="model[item.fieldName]" 
						:clearable="clearable" 
						:disabled="item.disabled ? item.disabled : false"
						:placeholder="item.placeholder ? item.placeholder : `请选择`"
						:options="regionList"
						separator="-"
						:props="{
							value: 'name',
							label: 'name',
							children: 'child'
						}"
						@change="item.change"
					/>
				</el-form-item>
				<!-- 上传组件 -->
				<el-form-item
					v-if="item.type === 'upload'"
					:label="item.label" 
          :prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<UploadPanel
						:uploadType="item.uploadType"
						:maxLength="item.maxLength"
						:maxSize="item.maxSize"
						:disabled="item.disabled"
						:FileList="item.initValue"
						:dir="item.dir"
						:localFiles="item.localFiles"
						:project_id="item.project_id"
					/>
				</el-form-item>
				<!-- 选择项目人员 -->
				<el-form-item
					v-if="item.type === 'selectProjectUser'"
					:label="item.label" 
					:prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<SelectProjectUser
						v-model:userList="model[item.fieldName]"
						:disabled="item.disabled"
						:placeholder="item.placeholder"
						:dialogTitle="item.dialogTitle"
						:separator="item.separator"
						:bottomCount="item.bottomCount"
						:change="item.change"
					/>
				</el-form-item>
				<!-- 仅展示 -->
				<el-form-item 
					:label-width="item.labelWidth"
					v-if="item.type === 'show'"
					:label="item.label" 
					style="margin-bottom:10px"
					class="l_formPanel_show"
				>
					<div :style="`line-height: 20px; ${item.style}`" v-if="model[item.fieldName]" v-html="model[item.fieldName].replace(/\n/g,'</br>')"></div>
				</el-form-item>
			</el-col>
			<el-col :span="24/ Number(cols)"><slot name="after"></slot></el-col>
		</el-row>
		<template v-else>
			<el-row 
				v-for="(item, index) in formConfig"
				:key="index"
			>
				<el-col
					:span="(item.cols && item.cols != '') ? Number(item.cols) : 24"
				>
					<!-- input类型 -->
					<el-form-item
						v-if="item.type === 'input' && item.inputType === 'textarea'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-input
							type="textarea"
							:clearable="clearable" 
							:style="item.style ? item.style : ''" 
							v-model="model[item.fieldName]" 
							:placeholder="item.placeholder ? item.placeholder : `请输入`"
							:disabled="item.disabled ? item.disabled : false"
							:maxlength="item.maxlength ? item.maxlength : ''"
							:minlength="item.minlength ? item.minlength : ''"
							:show-word-limit="item.showWordLimit ? item.showWordLimit : false"
							:autosize="item.autosize ? item.autosize : false"
							:rows="item.rows ? item.rows : (item.inputType === 'textarea' ? 2 : 1)"
							@change="item.change"
						>
							<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}</template>
							<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}</template>
						</el-input>
					</el-form-item>
					<el-form-item
						v-else-if="item.type === 'input'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-input
							:type="item.inputType ? item.inputType : 'text'"
							:clearable="clearable" 
							:style="item.style ? item.style : ''" 
							v-model.trim="model[item.fieldName]" 
							:placeholder="item.placeholder ? item.placeholder : `请输入`"
							:disabled="item.disabled ? item.disabled : false"
							:maxlength="item.maxlength ? item.maxlength : ''"
							:minlength="item.minlength ? item.minlength : ''"
							:show-word-limit="item.showWordLimit ? item.showWordLimit : false"
							:autosize="item.autosize ? item.autosize : false"
							:rows="item.rows ? item.rows : (item.inputType === 'textarea' ? 2 : 1)"
							@change="item.change"
						>
							<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}</template>
							<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}</template>
						</el-input>
					</el-form-item>
					<!-- 自动补全 -->
					<el-form-item 
						v-if="item.type === 'autocomplete'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-autocomplete
							:clearable="clearable" 
							:style="item.style ? item.style : ''" 
							v-model.trim="model[item.fieldName]" 
							:disabled="item.disabled ? item.disabled : false"
							:placeholder="item.placeholder ? item.placeholder : `请输入`"
							:fetch-suggestions="item.suggestions"
							:show-word-limit="item.showWordLimit ? item.showWordLimit : false"
							@select="item.select"
							@change="item.change"
							@keydown="inputKeydown($event, item.DisableInput)"
						>
							<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}</template>
							<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}</template>
						</el-autocomplete>
					</el-form-item>
					<!-- 数字类型Input增加输入限制 -->
					<el-form-item 
						v-if="item.type === 'inputNumber'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-input
							type="Number"
							:clearable="clearable" 
							:style="item.style ? item.style : ''" 
							v-model="model[item.fieldName]" 
							:disabled="item.disabled ? item.disabled : false"
							:placeholder="item.placeholder ? item.placeholder : `请输入`"
							@change="item.change"
							@keydown="inputKeydown($event, item.DisableInput)"
						>
							<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}</template>
							<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}</template>
						</el-input>
					</el-form-item>
					<!-- 自带加减数字类型Input增加输入限制 -->
					<el-form-item 
						v-if="item.type === 'inputNumberBtn'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-input
							type="Number"
							:clearable="clearable" 
							:style="item.style ? item.style : ''" 
							v-model="model[item.fieldName]" 
							:disabled="item.disabled ? item.disabled : false"
							:placeholder="item.placeholder ? item.placeholder : `请输入`"
							@change="inputNumberBtnChange(item.fieldName,item.minNum,item.change);"
							ref="inputNumberBtnRef"
							@keydown="inputKeydown($event, item.DisableInput)"
						>
							<template  #prepend>
								<el-button :disabled="model[item.fieldName] <= 1 || model[item.fieldName] == '' || !model[item.fieldName] || model[item.fieldName] == undefined" style="width: 10px !important; min-width: 10px !important;" @click="inputNumberBtnReduce(item.fieldName,item.change)">-</el-button>
							</template>
							<template #append>
								<el-button style="width: 10px !important; min-width: 10px !important;" @click="inputNumberBtnAdd(item.fieldName, item.change)">+</el-button>
							</template>
						</el-input>
					</el-form-item>
					<!-- 范围输入框,默认为数字类型 -->
					<el-form-item 
						v-if="item.type === 'inputrange'"
						:label="item.label" 
						:required="item.rules ? true : false"
					>
						<el-col :span="11">
							<el-form-item 
								:prop="item.fieldName[0]"
								:rules="item.rules ? item.rules : []"
							>
								<el-input
									@keydown="inputKeydown($event, item.DisableInput)"
									type="Number"
									:clearable="clearable" 
									:style="item.style ? item.style : ''" 
									v-model="model[item.fieldName[0]]" 
									:disabled="item.disabled ? item.disabled : false"
									@change="inputrangeStarChange($event, item.fieldName[0],item.fieldName[1])"
									:placeholder="item.placeholder[0] ? item.placeholder[0] : `请输入`">
								</el-input>
							</el-form-item>
						</el-col>
						<el-col style="text-align: center;" :span="2">{{item.rangeSeparator}}</el-col>
						<el-col :span="11">
							<el-form-item 
								:prop="item.fieldName[1]"
								:rules="item.rules ? item.rules : []"
							>
								<el-input
									@keydown="inputKeydown($event, item.DisableInput)"
									type="Number"
									:clearable="clearable" 
									:style="item.style ? item.style : ''" 
									v-model="model[item.fieldName[1]]" 
									:disabled="item.disabled ? item.disabled : false"
									@change="inputrangeEndChange($event, item.fieldName[0],item.fieldName[1])"
									:placeholder="item.placeholder[1] ? item.placeholder[1] : `请输入`">
								</el-input>
							</el-form-item>
						</el-col>
					</el-form-item>
					<!-- 下拉框 -->
					<el-form-item 
						v-if="item.type === 'select'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-select 
							:default-first-option="item.defaultFirstOption ? item.defaultFirstOption : false"
							:filterable="item.filterable === undefined ? true : item.filterable"
							:allow-create="item.allowcreate ? item.allowcreate : false"
							:style="item.style ? item.style : ''" 
							:clearable="clearable" 
							v-model="model[item.fieldName]" 
							:disabled="item.disabled ? item.disabled : false"
							:placeholder="item.placeholder ? item.placeholder : `请选择`"
							:multiple="item.multiple ? item.multiple : false"
							:collapse-tags="item.collapseTags || false"
							collapse-tags-tooltip
							@change="item.change"
							@remove-tag="item.remove"
						>
							<el-option
								v-for="(optionItem, optionIndex) in item.optionList"
								:key="optionIndex"
								:label="optionItem.label"
								:disabled="optionItem.disabled"
								:value="optionItem.value === undefined ? optionItem.label : optionItem.value">
							</el-option>
						</el-select>
					</el-form-item>
					<!-- 日期时间-->
					<el-form-item 
						v-if="item.type === 'date'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-date-picker
							v-model="model[item.fieldName]"
							:value-format="item.valueFormat ? item.valueFormat :'yyyy-MM-dd'"
							:format="item.valueFormat ? item.valueFormat :'yyyy-MM-dd'"
							:style="item.style ? item.style : ''" 
							:clearable="clearable"
							:disabled="item.disabled ? item.disabled : false"
							:disabled-date="item.disabledDate ? item.disabledDate : null"
							:type="item.dataType ? item.dataType : 'date'"
							:placeholder="item.placeholder ? item.placeholder : `请选择`"
							:start-placeholder="item.startPlaceholder ? item.startPlaceholder : `请选择`"
							:end-placeholder="item.endPlaceholder ? item.endPlaceholder : `请选择`"
							@change="item.change"
						>
						</el-date-picker>
					</el-form-item>
					<!-- switch -->
					<el-form-item 
						v-if="item.type === 'switch'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-switch
							v-model="model[item.fieldName]" 
							:disabled="item.disabled ? item.disabled : false"
							:loading="item.loading ? item.loading : false"
							:active-value="item.activeValue ? item.activeValue : true"
							:inactive-value="item.inactiveValue ? item.inactiveValue : false"
							:style="`--el-switch-on-color: ${item.activeColor}; --el-switch-off-color: ${item.inactiveColor}; --el-switch-border-color: ${item.borderColor}`"
							:placeholder="item.placeholder ? item.placeholder : `请输入`"
							:before-change="item.beforeChange ? item.beforeChange : undefined"
							@change="item.change"
						>
						</el-switch>
					</el-form-item>
					<!-- 单选框 -->
					<el-form-item 
						v-if="item.type === 'radio'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-radio-group 
							v-model="model[item.fieldName]"
							:disabled="item.disabled ? item.disabled : false"
							:border="item.border ? item.border : false"
							@change="item.change"
						>
							<el-radio 
								v-for="(optionItem, optionIndex) in item.optionList" 
								:label="JSON.stringify(optionItem)" 
								:key="optionIndex"
							>
								{{optionItem.label}}
							</el-radio>
						</el-radio-group>
					</el-form-item>
					<!-- 多选框 -->
					<el-form-item 
						v-if="item.type === 'checkbox' && model[item.fieldName]"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-checkbox-group 
							v-model="model[item.fieldName]"
							:disabled="item.disabled ? item.disabled : false"
							:min="item.min ? item.min : undefined"
							:max="item.max ? item.max : undefined"
							:text-color="item.textColor ? item.textColor : '#FFFFFF'"
							:fill="item.fill ? item.fill : '#3171F1'"
							@change="item.change"
						>
							<el-checkbox 
								v-for="(optionItem, optionIndex) in item.optionList" 
								:label="optionItem" 
								:key="optionIndex">
								{{optionItem.label}}
							</el-checkbox>
						</el-checkbox-group>
					</el-form-item>
					<!-- 地图 -->
					<el-form-item 
						v-if="item.type === 'mapInput' && model[item.fieldName]"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<BaiduMap
							:placeholder="placeholder"
							:disabled="item.disabled ? item.disabled : false"
							:clearable="clearable"
							v-model:mapData="model[item.fieldName]"
						/>
					</el-form-item>
					<!-- 省市区选择 -->
					<el-form-item
						v-if="item.type === 'regionInput'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-cascader
							v-model.trim="model[item.fieldName]" 
							:clearable="clearable" 
							:disabled="item.disabled ? item.disabled : false"
							:placeholder="item.placeholder ? item.placeholder : `请选择`"
							:options="regionList"
							separator="-"
							:props="{
								value: 'name',
								label: 'name',
								children: 'child'
							}"
							@change="item.change"
						/>
					</el-form-item>
					<!-- 仅展示 -->
					<el-form-item 
						:label-width="item.labelWidth"
						v-if="item.type === 'show'"
						:label="item.label" 
						style="margin-bottom:10px"
						class="l_formPanel_show"
					>
						<div :style="`line-height: 20px; ${item.style}`" v-if="model[item.fieldName]" v-html="model[item.fieldName].replace(/\n/g,'</br>')"></div>
					</el-form-item>
				</el-col>
			</el-row>
		</template>
  </el-form>
</template>
<script>
import { reactive,toRefs,onMounted, getCurrentInstance, watch} from 'vue';
import region from '../../assets/json/region';
export default {
  name: '',
  props: {
		formConfig: { // 表单的渲染数据,必须
      type: Array,
			default: ()=> {
				return [];
			},
      required: true,
    },
    // model: { // 表单数据对象
    //   type: Object,
		// 	default: ()=> {
		// 	return {};
		// 	}
    // },
		// rules: { // 表单验证规则
    //   type: Object,
		// 	default: ()=> {
		// 	return {};
		// 	}
    // },
		cols: { // 每一行显示数量,必须可以被24整除
      type: [String,Number],
      default: 2
    },
		inline: { // 行内表单模式
      type: Boolean,
			default: false
    },
		labelPosition: { // 表单域标签的位置, 当设置为 left 或 right 时,则也需要设置 label-width 属性
      type: String,
			default: 'right'
    },
		labelWidth: { // 标签的长度,例如 '50px'。 作为 Form 直接子元素的 form-item 会继承该值。 可以使用 auto。
      type: [String,Number],
			default: '100px'
    },
		labelSuffix: { // 表单域标签的后缀
      type: String,
			default: ''
    },
		hideRequiredAsterisk: { // 是否隐藏必填字段标签旁边的红色星号。
      type: Boolean,
			default: false
    },
		requireAsteriskPosition: { // 星号的位置。'left' | 'right'
      type: String,
			default: 'left'
    },
		showMessage: { // 是否显示校验错误信息
      type: Boolean,
			default: true
    },
		inlineMessage: { // 是否以行内形式展示校验信息
      type: Boolean,
			default: false
    },
		statusIcon: { // 是否在输入框中显示校验结果反馈图标
      type: Boolean,
			default: false
    },
		validateOnRuleChange: { // 是否在 rules 属性改变后立即触发一次验证
      type: Boolean,
			default: true
    },
		size: { // 用于控制该表单内组件的尺寸 '' | 'large' | 'default' | 'small'
      type: String,
			default: ''
    },
		disabled: { // 是否禁用该表单内的所有组件。 如果设置为 true, 它将覆盖内部组件的 disabled 属性
      type: Boolean,
			default: false
    },
		scrollToError: { // 当校验失败时,滚动到第一个错误表单项
			type: Boolean,
			default: true
		}, 
		scrollIntoViewOptions: { //当校验有失败结果时,滚动到第一个失败的表单项目 object/ boolean
			type: [Object, Boolean],
			default: ''
		},
		clearable: { // 是否可清空
      type: Boolean,
      default: true
    },
  },
  components: {
		UploadPanel,
		SelectProjectUser
	},
  setup(props) {
    const { proxy } = getCurrentInstance(); // 相当于之前的this
    const data = reactive({
			model: {},
			regionList: region,
    });
		const createFormkey = (type)=> {
			let formKeyList = {};
			props.formConfig.map(item => {
				if(typeof item.fieldName == 'string') {
					formKeyList[item.fieldName] = item.initValue === undefined ?  '' : item.initValue;
				} else { // 处理范围输入的key
          formKeyList[item.fieldName[0]] = item.initValue[0] ? item.initValue[0] : '';
          formKeyList[item.fieldName[1]] = item.initValue[1] ? item.initValue[1] : '';
        }
			});
			if(type && type == 'update'){ // 更新组件内容保留历史数据
				for(let key in data.model){
					props.formConfig.map(item => {
						if(item.fieldName == key){
							formKeyList[key] = data.model[key];
						}
					}) 
				}
			}
			data.model = formKeyList;
		};
		const inputKeydown = (e, DisableInput)=> { // 数字类型输入框禁止输入不合法字符
			let key = e.key;
      let DisableInputStr = '';
      if(DisableInput){
        DisableInputStr = DisableInput;
      } else {
        DisableInputStr = 'e,+,-,Shift'
      }
			// console.log(key, '---key')
      if(DisableInputStr.indexOf(key) > -1) {
        e.returnValue = false;
        return false
      };
      return true;
		};
		/**
   * @description: 清除表单验证
	 * proxy.$refs.FormPanelRef.clearValidate();
   * @return {*}
   * @author: 
   */	
		const clearValidate = () => {
			return proxy.$refs.formPanelRef.clearValidate();
		};
		/**
   * @description: 清除指定数据,不穿清除全部
	 * proxy.$refs.FormPanelRef.clearKey(['keyName']);
   * @return {*}
   * @author: 
   */	
		const clearKey = (key) => {
			if(key&&key.length>0){
				props.formConfig.map(item => {
					key.map(keyItem => {
						if(item.fieldName == keyItem){
							data.model[keyItem] = item.initValue;
						}
					})
				})
			} else {
				createFormkey();
			}
		};
		/**
   * @description: 清除数据和表单验证
	 * proxy.$refs.FormPanelRef.clear();
   * @return {*}
   * @author: 
   */	
		const clear = () => {
			clearValidate();
			clearKey();
		};
		/**
   * @description: 设置数据
	 * proxy.$refs.FormPanelRef.set('key', 'value');
   * @return {*}
   * @author: 
   */	
		const set = (key, value) => {
			data.model[key] = value;
		};
		/**
   * @description: 得到数据
	 * proxy.$refs.FormPanelRef.get('key');
   * @return {*}
   * @author: 
   */	
		const get = (key) => {
			return data.model[key];
		};
  /**
   * @description: 验证表单并输入结果通过
	 * proxy.$refs.FormPanelRef.FormPanelValidate().then((res)=> {
        console.log(res,)
      })
   * @return {*}
   * @author: 
   */		
		const validate = ()=> { // 验证结果并输出
		  return new Promise((resolve, reject) => {
        proxy.$refs.formPanelRef.validate((valid) => {
          if(valid) {
            let formData = JSON.parse(JSON.stringify(proxy.model))
            resolve(formData);
          } else {
            return false;
          }
        })
      })
		};
		const inputrangeStarChange = (e, skey, eKey)=> { // 范围查询输入框初始值
			if(proxy.model[eKey] && Number(proxy.model[eKey]) <= Number(e)) {
        if(proxy.model[eKey] > 1) {
          proxy.model[skey] = Number(proxy.model[eKey])  - 1;
        } else {
          proxy.model[skey] = 0;
        }
      }
		};
		const inputrangeEndChange = (e, skey, eKey)=> { // 范围查询输入框结束
			if(proxy.model[skey] && Number(proxy.model[skey]) >= Number(e)) {
        proxy.model[eKey] = Number(proxy.model[skey])  + 1;
      }
		};
		const inputNumberBtnChange = (key, minNum, change)=> {
			if(minNum && data.model[key] && data.model[key] != '' && data.model[key] != null && data.model[key] != undefined){
				if(Number(data.model[key]) < Number(minNum)){
					data.model[key] = Number(minNum)
				}
			};
			change(data.model[key]);
		}
		const inputNumberBtnAdd = (key, change)=> { // 数字加
			if(data.model[key] == '' || !data.model[key] || data.model[key] == null || data.model[key] == undefined){
				data.model[key] = 1;
			} else {
				data.model[key] = Number(data.model[key]) + 1
			};
			change(data.model[key]);
		};
		const inputNumberBtnReduce = (key,change)=> { // 数字减
			data.model[key] = Number(data.model[key]) - 1;
			change(data.model[key]);
		};
		onMounted(() => {
			createFormkey();
		});
		watch(props.formConfig,(newVal,oldVal)=>{
			createFormkey('update');
		});
    return {
      ...toRefs(data),
			inputKeydown,
			clearValidate,
			validate,
			inputrangeStarChange,
			inputrangeEndChange,
			inputNumberBtnAdd,
			inputNumberBtnReduce,
			inputNumberBtnChange,
			clearKey,
			clear,
			set,
			get,
    };
  }
}
</script>
<style lang='scss' scoped>
:deep(.el-form-item){
	width: 100%;
	.el-input{
		width: 100% !important;
	}
	.el-select{
		width: 100% !important;
	}
	.el-cascader{
		width: 100% !important;
	}
}
:deep(.el-input__prefix){
	margin-right: 5px;
}
:deep(.el-input__suffix){
	margin-left: 5px;
}


</style>
<style>
/* // 取消input的上下箭头 */
input::-webkit-outer-spin-button,  
input::-webkit-inner-spin-button{  
	-webkit-appearance: none !important;  
	margin: 0;  
}
/* 火狐 */
input[type="number"]{
	-moz-appearance: textfield;
	appearance: textfield;
}
</style>

接下来我来告诉大家怎么使用:

#### FormPanel 组件调用

`组件参数API`

- formConfig{Array | 表单面板组件数据数组,支持动态赋值(数据数组必须是新的引用)}
- inline{Boolean | 是否启用行内表单, 默认值 false}
- cols{Number | 每行显示多小列,默认是 2,注意:只能是被 24 整除的值}
- labelWidth{Number | label 标签的宽度,默认是 120}

`formConfig 公用字段配置项`

- type{String | 类型,支持 input-输入框 / inputNumber-数字类型输入框 / inputrange-区间输入 / select-下拉框 / date-日期选择 / switch-开关 / radio-单选 / checkbox-多选框 / mapInput-地图 / regionInput-省市区选择 / upload-上传}
- label{String | 标题}
- fieldName{String | 字段名称}
- initValue{All | 初始值}
- placeholder{String | 提示文字}
- rules{Array | 验证提示文字}
- style{Object | 内容样式}
- disabled{Boolean | 是否禁用}
- change{Function | 触发函数}

`示例代码`

```bash
# template
<template>
  <FormPanel 
    :formConfig="formConfig" 
    ref="FormRef"
    :inline="true"
    :cols="4"
  />
</template>

# js
export default {
  data() {
    return {
      formConfig: this.createFormConfig(),
    };
  },
  methods: {
    createFormConfig() {
      return [
        {
          type: 'input', // 普通输入框
          inputType: 'text', // 类型, text/textarea
          label: '字符串:', // 标签文本
          cols: '', // 自定义列
          fieldName: 'stringName', // 字段名称
          placeholder: '请输入商品名称', // 提示文字
          initValue: '', // 默认值
          rules: [{ required: false, message: '请选择所属分类', trigger: 'blur' }],
          style: {}, // 自定义样式
          disabled: false, // 是否禁用 boolean
          maxlength: '', //最大输入长度 string/number
          minlength: '', // 最小输入长度 number
          showWordLimit: false, // 	是否显示统计字数, 只在 type 为 'text' 或 'textarea' 的时候生效 boolean
          rows: 2, // 输入框行数,仅 inputType 为 'textarea' 时有效 number
          autosize: false, // textarea 高度是否自适应,仅 type 为 'textarea' 时生效。 可以接受一个对象,比如: { minRows: 2, maxRows: 6 }
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'input', // 普通输入框
          inputType: 'textarea', // 类型, text/textarea
          label: '文本类型', // 标签文本
          cols: '', // 自定义列
          fieldName: 'textareaName', // 字段名称
          placeholder: '请输入商品名称', // 提示文字
          initValue: '', // 默认值
          rules: [{ required: false, message: '请选择所属分类', trigger: 'blur' }],
          style: {}, // 自定义样式
          disabled: false, // 是否禁用 boolean
          maxlength: '', //最大输入长度 string/number
          minlength: '', // 最小输入长度 number
          showWordLimit: false, // 	是否显示统计字数, 只在 type 为 'text' 或 'textarea' 的时候生效 boolean
          rows: 2, // 输入框行数,仅 inputType 为 'textarea' 时有效 number
          autosize: { // 默认使用, 优先级高于rows
            minRows: 1, maxRows: 2
          }, // textarea 高度是否自适应,仅 type 为 'textarea' 时生效。 可以接受一个对象,比如: { minRows: 2, maxRows: 6 }
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'inputNumber',
          label: '数字类型 :',
          fieldName: 'numeberName',
          placeholder: '请输入上门人数',
          initValue: '',
          prefix: '¥', // 插入左侧的内容
          suffix: '人', // 插入右侧的内容
          DisableInput: 'e,+,-,Shift,.', // 禁止输入的值,默认值e,+,-,Shift
          rules: [{ required: false, message: '请输入上门人数', trigger: 'blur' }],
          disabled: false, // 是否禁用
          maxlength: '', //最大输入长度
          minlength: '', // 最小输入长度
          showWordLimit: false, // 	是否显示统计字数, 只在 type 为 'text' 或 'textarea' 的时候生效
          rows: 2, // 输入框行数,仅 inputType 为 'textarea' 时有效
          autosize: false, // textarea 高度是否自适应,仅 type 为 'textarea' 时生效。 可以接受一个对象,比如: { minRows: 2, maxRows: 6 }
          style: {
            
          },
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
        type: 'inputNumberBtn', // 加减号input
        label: '持续时长:',
        fieldName: 'planed_duration',
        placeholder: '请输入持续时长',
        initValue: '',
        DisableInput: 'e,+,-,Shift,.',
        style: 'width: 220px !important;',
        disabled: false, // 是否禁用
        rules: [{ required: true, message: '请选择持续时长', trigger: 'blur' }],
      },
        {
          type: 'inputrange',
          label: '区间输入',
          fieldName: ['min_price', 'max_price'],
          placeholder: ['请输入', '请输入'],
          initValue: ['', ''],
          rangeSeparator: '至',
          rules: [{ required: false, message: '请输入', trigger: 'blur' }],
          disabled: false, // 是否禁用
          maxlength: '', //最大输入长度
          minlength: '', // 最小输入长度
          showWordLimit: false, // 	是否显示统计字数, 只在 type 为 'text' 或 'textarea' 的时候生效
          rows: 2, // 输入框行数,仅 inputType 为 'textarea' 时有效
          autosize: false, // textarea 高度是否自适应,仅 type 为 'textarea' 时生效。 可以接受一个对象,比如: { minRows: 2, maxRows: 6 }
        },
        {
          type: 'select',
          label: '下拉框:',
          fieldName: 'selectName',
          placeholder: '请选中所属分类',
          initValue: '',
          optionList: [{ label: '全部', value: '1' }, { label: '待确认', value: '2' }],
          multiple: false, // 是否多选, 多选时initValue类型是数组
          collapseTags: true, // 多选时是否合并文字,默认合并
          allowcreate: true, //是否创建新的条目
          defaultFirstOption: true, //是否默认第一选项,此字段为true时,按下回车就可以选中当前选项列表中第一个选项
          style: {width: '200px'},
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'date',
          label: '日期选择:',
          fieldName: 'dateName',
          placeholder: '请选择日期',
          startPlaceholder: '', // 范围选择时开始日期的占位内容
          endPlaceholder: '', // 范围选择时结束日期的占位内容
          initValue: '',
          dataType: 'monthrange', // 	year(年-YYYY)/month(月-YYYY-MM)/date(日-YYYY-MM-DD)/dates(多日期选择)/datetime(日期时间-YYYY-MM-DD hh:mm:ss)/ week(周-ww 周)/datetimerange(日期时间段-YYYY-MM-DD hh:mm:ss)/daterange(日期段-YYYY-MM-DD)/ monthrange(月份范围-YYYY-MM)
          valueFormat: 'YYYY-MM', // 日期格式默认年月日
          disabled: false, // 是否禁用

          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'switch',
          label: '开关:',
          fieldName: 'switchName',
          initValue: '0',
          placeholder: '',
          disabled: false, // 是否禁用
          loading: false, // 是否显示loading
          activeValue: '1', // 打开时的值
          inactiveValue: '0', // 关闭时的值
          activeColor: '#3171F1', // 打开时的颜色
          inactiveColor: '#C0CCDA', // 关闭时的颜色
          borderColor: '', // 开关边框颜色
          validateEvent: true, // 切换时是否触发表单校验
          beforeChange: () => { // switch 状态改变前的钩子, 返回 false 或者返回 Promise 且被 reject 则停止切换
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                return resolve(true)
                return reject(new Error('Error'))
              }, 1000)
            })
          },
          change: (val) => { // 	switch 状态发生变化时的回调函数
            console.log(val, 'val--------')
          }
        },
        {
          type: 'radio',
          label: '单选框:',
          fieldName: 'radioName',
          initValue: '',
          disabled: false, // 是否禁用
          border: false, // 是否显示边框
          optionList: [{ label: '全部', value: '1' }, { label: '待确认', value: '2' }], // 选项数组,返回值为整个对象
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'checkbox',
          label: '多选框:',
          fieldName: 'checkboxName',
          placeholder: '请选择日期',
          initValue: [],
          disabled: false, // 是否禁用
          min: undefined, // 可被勾选的 checkbox 的最小数量
          max: 999, // 可被勾选的 checkbox 的最大数量
          textColor: '#FFFFFF', // 当按钮为活跃状态时的字体颜色
          fill: '#3171F1', // 当按钮为活跃状态时的边框和背景颜色
          optionList: [{ label: '全部', value: '1' }, { label: '待确认', value: '2' }],
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'mapInput',
          label: '地图',
          fieldName: 'mapInputName',
          placeholder: '请选择定位',
          initValue: {
            province: '', // 省
            city: '', // 市
            area: '', // 区
            address: '', // 详细地址
            lng: 0,  // 定位横坐标
            lat: 0, // 定位纵坐标
          },
          disabled: false, // 是否禁用
          rules: [{ required: true, message: '请选择定位', trigger: 'blur' }],
        },
        {
          type: 'regionInput', // 普通输入框
          label: '省市区:', // 标签文本
          cols: '', // 自定义列
          fieldName: 'regionInputName', // 字段名称
          placeholder: '请选择', // 提示文字
          initValue: '', // 默认值
          rules: [{ required: false, message: '请选择所', trigger: 'blur' }],
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'upload', // 普通输入框
          label: '上传', // 标签文本
          cols: '', // 自定义列
          fieldName: 'uploadName', // 字段名称
          initValue: [], // 默认值
          uploadType: 'uploadFile',// 组件的类型, 支持 uploadFile(上传图频文件),uploadImg(上传图片)showFile(仅展示)  默认值: uploadFile}
          maxLength: 10, // 上传数量,默认值10
          maxSize: 1000, // 默认1000M
          dir: '', // 存储文件的目录,后端提供
          disabled: false,
          rules: [{ type: 'array', required: true, message: '请上传', trigger: 'change' }],
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        { // 选择项目人员
          type: 'selectProjectUser',
          label: '审批人:', // 标签文本
          fieldName: 'user_data', // 字段名称
          placeholder: '请选择', // 提示文字
          dialogTitle: '选择审批人', // 弹窗名称
          separator: '>', // 分隔符
          bottomCount: true, // 启用底部计数
          initValue: [], // 默认值
        },
        {
          type: 'show',
          label: '仅展示:',
          fieldName: 'show',
          initValue: '展示的内容'
        },
      ]
    },
  }
};
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Vue3中封装form表单组件的思路可以参考以下步骤: 1. 创建一个Form组件,作为整个表单的容器。该组件可以包含一个form元素,并接受一个表单数据对象作为props。 2. 在Form组件内部,使用v-for指令遍历表单数据对象的属性,并根据属性的类型渲染对应的表单项。 3. 对于每个表单项,可以创建一个FormItem组件,并根据属性的类型渲染对应的表单元素,比如input、select、textarea等。 4. FormItem组件可以接受属性的名称、类型和值作为props,并根据类型渲染不同的表单元素。 5. 可以根据需要扩展Form组件和FormItem组件,添加更多的表单项类型,比如富文本编辑器、上传图片等。 6. 在Form组件中,可以监听表单元素的值变化,并将变化的值更新到表单数据对象中。 7. 可以在Form组件中添加提交按钮,并在点击按钮时触发表单提交事件,将表单数据对象作为参数传递给父组件。 需要注意的是,这只是一个基本的封装思路,如果需要使用到更多的业务场景,可以根据具体需求进行扩展和定制。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* *2* [vue3 ts element plus form表单二次封装详细步骤 (附参数、类型详细介绍及简单使用示例)](https://blog.csdn.net/weixin_45291937/article/details/126275955)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值