使用 Ant-Design-Vue 制作一个带图片上传功能的表单对话框

功能需求

使用 AntdvModal 组件内嵌一个 a-form 表单,具有添加数据和图片的功能。

页面结构设计

<template>
  <!--Modal-->
  <a-modal>
    <div>
      <a-form>
        <a-form-item label="水果名称" name="fruitName">
          <a-input type="text" placeholder="水果名称">
        </a-form-item>
        <a-form-item label="价 格" name="price">
          <a-input type="number" placeholder="价格(元/公斤)">
        </a-form-item>
        <a-form-item label="库 存" name="stock">
          <a-input type="number" placeholder="库存(公斤)">
        </a-form-item>
        <a-form-item label="图 片">
          <a-upload>
            <div>
              <img v-if="!base64img" src="@/assets/plus.png">
              <!-- ↑ 如果没有添加图片则显示一个加号占位符图片 -->
              <img v-else :src="base64img">
              <!-- ↑ 如果添加了图片则显示图片 -->
            </div>
          </a-upload>
        </a-form-item>
        <a-form-item>
          <a-button>提交</a-button>
        </a-form-item>
      </a-form>
    </div>
  </a-modal>
</template>

只展示了基础的页面结构,省略了绑定的事件、逻辑和样式代码。

思路整理

Modal 中有一个表单,表单中有 4 个子项目,前三个为 input 输入框,最后一个是图片上传的组件,均为必填项。所以要为前三个输入框加上前端的表单验证,只需要在 a-form:rules 中加入 [{required: true, message: ...}]。上传图片则采用单独的逻辑判断,因为表单输入的前三项和图片上传采用的是不同的接口,上传表单数据(水果名称、价格、库存)和图片的路径使用的是一个接口,上传图片至服务器使用的是另一个接口。所以我们要在点击提交按钮时添加一个判断图片是否为空的操作,如果为空则拒绝向服务器发送请求。

关于图片上传的部分可以看 这篇文章

由于 AntdvModal 组件是有默认的 确认取消 按钮,这点对于提交表来来说不太方便,所以要禁用默认的按钮,再向表单中添加一个 form-item 提交按钮。

在上传文件时,为了避免上传相同名称的文件,我们要用当前时间戳来为文件重命名,手段是 const renameFile = new File(...)

添加数据成功后,需要重新从数据库中获取下数据,这个方法直接使用 onMounted 中的就可以了。由于数据量较少,故没有在服务端使用分页,每次请求都会返回数据库中的所有数据。

逻辑部分

// ↓ 控制 Modal 组件是否显示
const addModalVisible = ref<boolean>(false);

// 点击显示 Modal 的回调
function showAddModal() {
  console.log("Show Add Modal");
  addModalVisible.value = true;
}

// 表单数据
const formData = reactive({
  fruitName: "",
  price: "",
  stock: "",
  avatar: "",
})

/**
 * 上传图片需要用到的变量
 */
const fileList = ref();
// ↑ 放置图片的
const base64img = ref();
// ↑ 用来放置 base64 转码后的图片
const btnLoading = ref<boolean>(false);
// 控制 提交 按钮的 loading 状态


/**
 * 上传文件的回调
 */
function beforeUpload(file: any) {
  const isImg = (file.type === 'image/jpeg' || file.type === 'image/png');
  // 如果文件不是图片格式则禁止上传
  if (!isImg) {
    Notice.error("只能上传 jpeg/jpg/png 格式的文件!");
    // ↑ Notice.error 是我自己写的 Notice 类的一个静态函数
    // 用的是 antdv 的 Notifiacation
    return Upload.LIST_IGNORE;
    // ↑ 这个不解释了,上一篇博客中提到了
  }

  fileList.value = file;
  // ↓ 获取所需要上传图片的 Base64 编码
  getBase64(fileList.value, (cb_img: string) => {
    // 将获取到的所要上传图片的 Base64 编码渲染到图片上
    base64img.value = cb_img;
  })
  return false;
}

/**
 * 待上传图片被删除的回调
 */
function handleRemove() {
  fileList.value = null;
  // ↑ 置空待上传文件列表
  base64img.value = null;
  // ↑ 显示默认占位图
}

/**
 * 点击 Modal 中的添加按钮的回调
 */
function addItem() {
  // 添加图片文件到服务器指定位置
  const imgForm = new FormData();
  if (fileList.value != null) {
    // ↓ 以当前时间戳重命名文件
    const newFileName = Date.now() + "." + fileList.value.name.substring(fileList.value.name.lastIndexOf(".") + 1);
    // ↓ avatar 是数据库中储存图片地址的字段,后面会加上地址重新赋值一次
    formData.avatar = newFileName;
    const renameFile = new File([fileList.value], newFileName);
    // ↑ 创建新文件:以新文件名重命名文件
    imgForm.append("file", renameFile);
  } else {
    Notice.error("图片不能为空!");
    return Upload.LIST_IGNORE;
  }
  btnLoading.value = true;

  // 将图片上传至服务器
  const addData = new Promise((resolve, reject) => {
    // fruitApi 是自己封装的 Axios
    fruitApi({
      method: 'put',
      url: 'api/upload',
      data: imgForm,
    })
        .then((resp) => {
          resolve(resp);
        })
        .catch((error) => {
          reject(error);
        })
  })

  // 添加数据至数据库
  // 并添加文件的名称和路径至数据库
  const uploadPic = new Promise((resolve, reject) => {
    fruitApi({
      method: 'post',
      url: 'api/fruits',
      data: {
        fruitName: formData.fruitName,
        avatar: imagesAddr + "/" + formData.avatar,
        // imagesAddr 是服务器储存图片的地址
        price: formData.price,
        stock: formData.stock,
      }
    })
        .then((resp: any) => {
          resolve(resp);
        })
        .catch((error) => {
          reject(error);
        })
  })

  Promise.all([addData, uploadPic])
      .then((resp) => {
        console.log("请求成功: ");
        console.log(resp);
        changeUploadItemColor("success");
        // ↑ 上传成功改变颜色,也在上个博客提到过了
        Notice.success("添加成功!");

        setTimeout(() => {
          getFruits();
          // ↑ 上传成功后重新获取数据
        }, 500)

      })
      .catch((error) => {
        console.log("请求失败: ");
        console.log(error);
        changeUploadItemColor("error");
        Notice.error("添加失败~");
      })
      .finally(() => {
        btnLoading.value = false;
        // 取消按钮的 loading 状态
      })
}

完善页面结构

<!--Modal-->
<a-modal v-model:visible="addModalVisible"
         title="模态对话框"
         :confirm-loading="btnLoading"
         :footer="null"
>
  <div>
    <a-form
        :model="formData"
        @finish="addItem"
    >
      <a-form-item label="水果名称" name="fruitName" :rules="[{required: true, message: '请输入水果名称'}]">
        <a-input type="text" placeholder="水果名称" v-model:value="formData.fruitName"/>
      </a-form-item>
      <a-form-item label="价 格" name="price" :rules="[{required: true, message: '请输入价格'}]">
        <a-input type="number" placeholder="价格(元/公斤)" suffix="RMB" v-model:value="formData.price"/>
      </a-form-item>
      <a-form-item label="库 存" name="stock" :rules="[{required: true, message: '请输入库存'}]">
        <a-input type="number" placeholder="库存(公斤)" v-model:value="formData.stock"/>
      </a-form-item>
      <a-form-item label="图 片">
        <a-upload
            :before-upload="beforeUpload"
            :max-count="1"
            @remove="handleRemove"
        >
          <div>
            <img v-if="!base64img" src="@/assets/plus.png" />
            <img v-else :src="base64img" />
          </div>
        </a-upload>
      </a-form-item>

      <a-form-item>
        <a-button html-type="submit" :loading="btnLoading">提交</a-button>
      </a-form-item>
    </a-form>
  </div>
</a-modal>

完整代码:https://blog-static.cnblogs.com/files/blogs/754613/modalForm.vue.js?t=1665405534

posted @ 2022-10-10 20:40  HuStoking  阅读(490)  评论(0编辑  收藏  举报