后端接口

  • 后端接口支持Form表单提交,其中title、url、description、sort为必填,logo为选填

  • logo必须接收binary二进制image文件,不支持string或者base64

前端实现

  • 表单代码

    <el-form :model="currentItem" :rules="formRule" ref="submitRef" label-width="80px" enctype="multipart/form-data">
      <el-form-item label="标题" prop="title">
        <el-input v-model="currentItem.title"/>
      </el-form-item>
      <el-form-item label="描述" prop="description">
        <el-input v-model="currentItem.description" type="textarea"/>
      </el-form-item>
      <el-form-item label="链接" prop="url">
        <el-input v-model="currentItem.url"/>
      </el-form-item>
      <el-form-item label="分类" prop="category">
        <el-select v-model="currentItem.category" placeholder="请选择分类">
          <el-option v-for="category in state.category" :key="category.id" :label="category.title"
                     :value="category.id"/>
        </el-select>
      </el-form-item>
      <el-form-item label="排序" prop="sort">
        <el-input-number v-model="currentItem.sort"/>
      </el-form-item>
      <el-form-item label="Logo">
        <!-- 上传组件 -->
        <el-upload
            action=""
            class="avatar-uploader"
            :show-file-list="false"
            :before-upload="handleBeforeUpload"
        >
          <img v-if="currentItem.logo" :src="currentItem.logo" class="avatar"/>
          <el-icon v-else class="avatar-uploader-icon">
            <plus/>
          </el-icon>
        </el-upload>
      </el-form-item>
    </el-form>
const currentItem = reactive({
  id: null,
  category_cn: "",
  title: "",
  url: "",
  description: "",
  sort: 0,
  logo: null,
  category: 0
});

const handleAdd = () => {
  Object.assign(currentItem, {
    id: null,
    category_cn: '',
    title: '',
    url: '',
    description: '',
    sort: 1,
    logo: null,
    category: null,
    logoFile: null,
  })
  dialogVisible.value = true
}

function handleBeforeUpload(file) {
  // 构造图片本地链接
  var windowURL = window.URL || window.webkitURL;
  currentItem.logo = windowURL.createObjectURL(file);
  // 创建文件对象
  currentItem.logoFile = file
  return false;
}

function handleSubmit() {
  proxy.$refs.submitRef.validate((valid) => {
    if (valid) {
      // 构造提交表单
      const formData = new FormData();
      formData.append('title', currentItem.title);
      formData.append('url', currentItem.url);
      formData.append('description', currentItem.description);
      formData.append('sort', currentItem.sort);
      formData.append('category', currentItem.category);
      // 非必填项
      if (currentItem.logoFile) {
        formData.append('logo', currentItem.logoFile);
      }
      if (currentItem.id === null) {
        // 添加数据
        proxy.$axios.post("v1/site/", formData).then((res) => {
          state.sites.push(res.data);
          dialogVisible.value = false;
          ElMessage.success("添加成功")
        });
      } else {
        // 更新数据
        proxy.$axios.patch(`v1/site/${currentItem.id}/`, formData).then((res) => {
          const index = state.sites.findIndex((site) => site.id === currentItem.id);
          state.sites.splice(index, 1, res.data);
          dialogVisible.value = false;
          ElMessage.success("更新成功")
        });
      }
    } else {
      return false;
    }
  })
}
  • 首先构造一个空字典currentItem用来初始化提交表单

  • 然后在提交表单中配置el-upload组件

    • el-upload默认是上传文件后自动提交到action地址,这种方法是图片和表单分开提交,由于我们后端接口要求一起提交,所以我们需要关闭el-upload的默认上传动作

    • 将action地址留空,配置before-upload,上传文件后触发handleBeforeUpload

    • handleBeforeUpload接收file对象,先构造出file对象的本地路径,然后赋值给currentItem.logo,用来在网页上临时显示图片

    • 然后将file对象直接赋值给currentItem.logoFile,稍候用来提交到后端

    • return false关闭el-upload的自动上传动作

  • handleSubmit用来提交表单,提交表单前,构造一个formData对象,然后将currentItem字典分别传入formData对象

  • 由于logo是非必填项,需要判断一下currentItem.logoFile是否为空,如果不为空再传入formData

  • 最后将包含图片文件的formData直接提交到后端即可