根据提供的表格动态渲染多个表单,每个配置项包含 label、prop、type 和 placeholder 等属性。

js 复制代码
<template>
  <div class="prop-form-container">
    <!-- 渲染多个表单,每个表单对应一个数据项 --> <el-card shadow="hover" class="form-card-item">

      <div v-for="(dataItem, index) in formDataList" :key="index" class="form-card">
        <!-- <template #header>
          <div class="card-header">
            <span>属性配置 {{ index + 1 }}</span>
            <el-button type="danger" link @click="removeForm(index)" :disabled="formDataList.length <= 1">
              删除
            </el-button>
          </div>
        </template> -->

        <el-form :model="dataItem" label-width="80px" label-position="right" class="form-content" :inline="true">
          <el-form-item :label="index + 1" label-width="10px" label-position="left"></el-form-item>
          <!-- 渲染指定的四个字段 -->
          <template v-for="(config, configIndex) in visibleFormConfigs" :key="configIndex">
            <el-form-item :label="config.label" :prop="config.prop">
              <!-- 输入框 -->
              <el-input v-if="config.type === 'input'" v-model="dataItem[config.prop]" :placeholder="config.placeholder"
                clearable class="form-input" />

              <!-- 文本域 -->
              <el-input v-else-if="config.type === 'textarea'" v-model="dataItem[config.prop]"
                :placeholder="config.placeholder" type="textarea" :rows="1" class="form-textarea" />
            </el-form-item>
          </template>
          <el-form-item label-width="10px" label-position="left">
            <el-button icon="DeleteFilled" type="danger" link @click="removeForm(index)"
              :disabled="formDataList.length <= 1">
            </el-button></el-form-item>
        </el-form>

      </div>

      <!-- 添加新属性按钮 -->

    </el-card>
    <div class="add-form-button">
      <el-button type="primary" @click="addForm" icon="Plus">
        添加新属性
      </el-button>
      <!-- 保存所有表单按钮 -->
      <el-button v-if="showSaveButton" type="success" @click="saveAllFormData" icon="Check" class="save-all-button">
        保存所有属性
      </el-button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus, Check } from '@element-plus/icons-vue'

interface FormItem {
  propId: number | null
  modelId: number | null
  propCode: string
  propName: string
  propValue: string
  remark: string | null
  customerId: number | null
  ouId: number | null
  createdDate: string | null
  createdBy: string | null
  lastUpdatedDate: string | null
  lastUpdatedBy: string | null
}

interface FormConfig {
  label: string
  prop: string
  type: string
  placeholder: string
}

interface Props {
  initialData?: FormItem[]
  showSaveButton?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  initialData: () => [],
  showSaveButton: false
})

const emit = defineEmits<{
  (e: 'save', data: FormItem | FormItem[]): void
}>()

// 只需要渲染的四个字段配置
const visibleFormConfigs = ref<FormConfig[]>([
  {
    label: "属性编码",
    prop: "propCode",
    type: "input",
    placeholder: "例如: hsCode"
  },
  {
    label: "属性名称",
    prop: "propName",
    type: "input",
    placeholder: "请输入属性名称"
  },
  {
    label: "属性数值",
    prop: "propValue",
    type: "input",
    placeholder: "请输入属性值"
  },
  {
    label: "备注",
    prop: "remark",
    type: "textarea",
    placeholder: "请输入备注信息"
  }
])

// 表单数据列表(包含所有字段)
const formDataList = ref<FormItem[]>([])

// 默认空表单数据
const getDefaultFormData = (): FormItem => ({
  propId: null,
  modelId: null,
  propCode: "",
  propName: "",
  propValue: "",
  remark: null,
  customerId: null,
  ouId: null,
  createdDate: null,
  createdBy: null,
  lastUpdatedDate: null,
  lastUpdatedBy: null,
})

// 初始化表单数据
const initForm = () => {
  // 如果传入了初始数据,则使用传入的数据
  if (props.initialData && props.initialData.length > 0) {
    formDataList.value = props.initialData.map(item => ({ ...item }))
  } else {
    // 如果没有传入数据,初始化一个空表单
    formDataList.value = [getDefaultFormData()]
  }
}

// 监听 initialData 的变化
watch(
  () => props.initialData,
  (newVal) => {
    if (newVal) {
      formDataList.value = newVal.map(item => ({ ...item }))
    } else {
      formDataList.value = [getDefaultFormData()]
    }
  },
  { deep: true }
)

// 保存单个表单数据
const saveFormData = (index: number) => {
  emit('save', formDataList.value[index])
  ElMessage.success(`属性配置 ${index + 1} 已保存`)
}

// 保存所有表单数据
const saveAllFormData = () => {
  emit('save', formDataList.value)
  ElMessage.success("所有属性配置已保存")
}

// 获取表单数据的方法(供父组件调用)
const getFormData = () => {
  return [...formDataList.value]
}

// 添加新表单
const addForm = () => {
  formDataList.value.push(getDefaultFormData())
  ElMessage.primary("已添加新属性配置")
}

// 删除表单
const removeForm = (index: number) => {
  if (formDataList.value.length <= 1) {
    ElMessage.warning("至少需要保留一个属性配置")
    return
  }

  formDataList.value.splice(index, 1)
  ElMessage.warning("已删除属性配置")
}

// 定义暴露给父组件的属性和方法
defineExpose({
  getFormData,
  saveAllFormData,
  addForm,
  removeForm
})

onMounted(() => {
  initForm()
})
</script>

<style scoped lang="scss">
.prop-form-container {
  padding: 20px;

  .form-card {

    &:last-child {
      margin-bottom: 0;
    }

    .form-card-item {
      border-radius: 8px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);

      .card-header {
        display: flex;
        justify-content: space-between;
        align-items: center;

        span {
          font-weight: bold;
          color: #303133;
        }
      }

      .form-content {
        :deep(.el-form--inline .el-form-item) {
          margin: 0 !important;
        }

        :deep(.el-form-item) {
          margin: 0 !important;


          .el-form-item__label {
            color: #606266;
            font-weight: 500;
          }

          .form-input,
          .form-textarea {
            width: 100%;
          }

          .form-textarea {
            min-width: 300px;
          }
        }
      }
    }
  }

  .add-form-button {
    display: flex;
    gap: 15px;
    margin-top: 20px;
    justify-content: flex-start;

    .save-all-button {
      margin-left: 10px;
    }
  }
}

// 响应式设计
@media (max-width: 768px) {
  .prop-form-container {
    padding: 10px;

    .form-card {
      .form-card-item {
        .form-content {
          :deep(.el-form-item) {
            .form-textarea {
              min-width: 200px;
            }
          }
        }
      }
    }

    .add-form-button {
      flex-direction: column;

      .save-all-button {
        margin-left: 0;
      }
    }
  }
}
</style>
相关推荐
用户4445543654261 小时前
自定义viewgroup
前端
ohyeah1 小时前
用 Coze 打造你的教育智能客服:从想法到前端集成的完整实践
前端·coze·trae
雨雨雨雨雨别下啦1 小时前
【从0开始学前端】 Git版本控制系统
前端·git
前端一课2 小时前
【前端每天一题】 第 15 题:CSS 水平垂直居中高频方案(Flex / Grid / transform 等)
前端·面试
前端一课2 小时前
【前端每天一题】🔥 第 19 题:什么是重排(Reflow)和重绘(Repaint)?有什么区别?如何减少?
前端·面试
前端一课2 小时前
【前端每天一题】🔥 第 14 题:Promise.then 链式调用执行顺序
前端·面试
前端一课2 小时前
【前端每天一题】🔥 第 18 题:防抖和节流是什么?区别是什么?如何实现?
前端·面试
前端一课2 小时前
【前端每天一题】第 16 题:数组去重高频方法(Set / filter / reduce / 对象键值法)
前端·面试
前端一课2 小时前
【前端每天一题】🔥 第 17 题:什么是浅拷贝与深拷贝?如何实现深拷贝?
前端·面试