文件上传-项目学习

图片,语音,视频上传

前端-api

  /** 发送消息 */
  sendMsg: (data?: MessageReq) => postRequest<MessageType>(urls.sendMsg, data),
  
/**
 * 发送消息载体
 */
export type MessageReq = {
  /** 会话id */
  roomId: number
  /** 消息类型 */
  msgType: MsgEnum
  /** 消息体 */
  body: {
    /** 文本消息内容 */
    content?: string
    /** 回复的消息id */
    replyMsgId?: number
    /** 任意 */
    [key: string]: any
  }
}
/**
 * 消息类型
 */
export enum MsgEnum {
  /** 未知 */
  UNKNOWN,
  /** 文本 */
  TEXT,
  /** 撤回 */
  RECALL,
  /** 图片 */
  IMAGE,
  /** 文件 */
  FILE,
  /** 语音 */
  VOICE,
  /** 视频 */
  VIDEO,
  /** 表情包 */
  EMOJI,
}

 <div class="msg-input">
            <div class="action" @click="isAudio = !isAudio">
              <Icon v-show="!isAudio" icon="voice" class="audio" />
              <Icon v-show="isAudio" icon="jianpan" />
            </div>
            <div
              v-show="isAudio"
              class="recorded"
              @mousedown="onStartRecord()"
              @mouseup="stop()"
              @touchstart.passive="onStartRecord()"
              @touchend.passive="stop()"
            >
              <div class="recorded-tips">{{ isRecording ? `录制中 ${second}s` : '按住说话' }}</div>
            </div>
            <MsgInput
              class="m-input"
              v-show="!isAudio"
              v-model="inputMsg"
              ref="mentionRef"
              autofocus
              :tabindex="!isSign || isSending"
              :disabled="!isSign || isSending"
              :placeholder="isSign ? (isSending ? '消息发送中' : '来聊点什么吧~') : ''"
              :mentions="mentionList"
              @change="onInputChange"
              @send="sendMsgHandler"
            />
            <el-popover
              placement="top"
              effect="dark"
              title=""
              v-model:visible="showEmoji"
              popper-class="emoji-warpper"
              :show-arrow="false"
              :width="client === 'PC' ? 385 : '95%'"
              trigger="click"
            >
              <template #reference>
                <div class="action" @mouseover="isHovered = true" @mouseleave="isHovered = false">
                  <Icon v-if="isHovered" icon="shocked" :size="18" colorful />
                  <Icon v-else icon="happy1" :size="18" colorful />
                </div>
              </template>
              <div class="emoji-panel">
                <div v-show="panelIndex === 0" class="emoji-panel-content">
                  <ul class="emoji-list">
                    <li
                      class="emoji-item"
                      v-for="(emoji, $index) of emojis"
                      :key="$index"
                      v-login="() => insertEmoji(emoji)"
                    >
                      {{ emoji }}
                    </li>
                  </ul>
                </div>
                <div v-show="panelIndex === 1" class="emoji-panel-content">
                  <div
                    v-for="emoji in emojiList"
                    :key="emoji.id"
                    class="item"
                    @click="sendEmoji(emoji.expressionUrl)"
                    @contextmenu="handleRightClick($event, emoji.id)"
                  >
                    <img :src="emoji.expressionUrl" />
                    <Icon
                      v-if="emoji.id === tempEmojiId"
                      icon="guanbi1"
                      class="del"
                      @click.stop="emojiStore.deleteEmoji(emoji.id)"
                    />
                  </div>
                  <Icon
                    v-if="emojiList.length < 50 && !isEmojiUp"
                    class="cursor-pointer item-add"
                    icon="tianjia"
                    :size="30"
                    @click="openFileSelect('img', true)"
                  />
                  <div v-else class="item-add">
                    <Icon icon="loading" spin :size="30" />
                  </div>
                </div>
                <div class="footer">
                  <Icon
                    :class="['cursor-pointer', 'footer-act', { active: panelIndex === 0 }]"
                    icon="biaoqing"
                    :size="18"
                    @click="panelIndex = 0"
                  />
                  <Icon
                    :class="['cursor-pointer', 'footer-act', { active: panelIndex === 1 }]"
                    icon="aixin"
                    :size="18"
                    @click="panelIndex = 1"
                  />
                </div>
              </div>
            </el-popover>
            <Icon
              class="action"
              icon="at"
              :size="20"
              colorful
              @click="insertInputText({ content: '@', ...mentionRef?.range })"
            />
            <Icon
              :class="['action', { disabled: isUploading }]"
              icon="tupian"
              :size="18"
              colorful
              @click="openFileSelect('img')"
            />
            <Icon
              class="action"
              icon="wenjianjia2"
              :size="20"
              colorful
              @click="openFileSelect('file')"
            />
            <div class="divider" />
            <div
              :class="['action', { 'is-edit': inputMsg.length, 'disabled': !inputMsg.length }]"
              @click="sendMsgHandler"
            >
              <Icon class="send" icon="huojian" :size="20" />
            </div>vue
          </div>

这里图片和视频都是需要先上传到oss中然后再获取到url链接作为参数再传给发送消息

/** 获取临时上传链接 */
  getUploadUrl: (params: any) =>
    getRequest<{ downloadUrl: string; uploadUrl: string }>(urls.fileUpload, { localCache: 0, params }),

后端oss

oss.enabled=true
oss.type=minio
oss.endpoint=http://localhost:9000
oss.access-key=BEZ213
oss.secret-key=Ii4vCMIXuFfds1EZ8e7RXI2342342kV
oss.bucketName=default
package com.abin.mallchat.common.common.utils.oss;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "oss")
public class OssProperties {

    /**
     * 是否开启
     */
    Boolean enabled;

    /**
     * 存储对象服务器类型
     */
    OssType type;

    /**
     * OSS 访问端点,集群时需提供统一入口
     */
    String endpoint;

    /**
     * 用户名
     */
    String accessKey;

    /**
     * 密码
     */
    String secretKey;

    /**
     * 存储桶
     */
    String bucketName;
}

package com.abin.mallchat.common.common.config;

import com.abin.mallchat.common.common.utils.oss.MinIOTemplate;
import com.abin.mallchat.common.common.utils.oss.OssProperties;
import io.minio.MinioClient;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({MinioClient.class})
@EnableConfigurationProperties(OssProperties.class)
@ConditionalOnExpression("${oss.enabled}")
@ConditionalOnProperty(value = "oss.type", havingValue = "minio")
public class MinIOConfiguration {


    @Bean
    @SneakyThrows
    @ConditionalOnMissingBean(MinioClient.class)
    public MinioClient minioClient(OssProperties ossProperties) {
        return MinioClient.builder()
                .endpoint(ossProperties.getEndpoint())
                .credentials(ossProperties.getAccessKey(), ossProperties.getSecretKey())
                .build();
    }

    @Bean
    @ConditionalOnBean({MinioClient.class})
    @ConditionalOnMissingBean(MinIOTemplate.class)
    public MinIOTemplate minioTemplate(MinioClient minioClient, OssProperties ossProperties) {
        return new MinIOTemplate(minioClient, ossProperties);
    }
}
package com.abin.mallchat.common.common.utils.oss;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.StrUtil;
import com.abin.mallchat.common.common.utils.oss.domain.OssReq;
import com.abin.mallchat.common.common.utils.oss.domain.OssResp;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.InputStream;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Slf4j
@AllArgsConstructor
public class MinIOTemplate {

    /**
     * MinIO 客户端
     */
    MinioClient minioClient;

    /**
     * MinIO 配置类
     */
    OssProperties ossProperties;

    /**
     * 查询所有存储桶
     *
     * @return Bucket 集合
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 桶是否存在
     *
     * @param bucketName 桶名
     * @return 是否存在
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 桶名
     */
    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());

        }
    }

    /**
     * 删除一个空桶 如果存储桶存在对象不为空时,删除会报错。
     *
     * @param bucketName 桶名
     */
    @SneakyThrows
    public void removeBucket(String bucketName) {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 返回临时带签名、过期时间一天、Get请求方式的访问URL
     */
    @SneakyThrows
    public OssResp getPreSignedObjectUrl(OssReq req) {
        String absolutePath = req.isAutoPath() ? generateAutoPath(req) : req.getFilePath() + StrUtil.SLASH + req.getFileName();
        String url = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(ossProperties.getBucketName())
                        .object(absolutePath)
                        .expiry(60 * 60 * 24)
                        .build());
        return OssResp.builder()
                .uploadUrl(url)
                .downloadUrl(getDownloadUrl(ossProperties.getBucketName(), absolutePath))
                .build();
    }

    private String getDownloadUrl(String bucket, String pathFile) {
        return ossProperties.getEndpoint() + StrUtil.SLASH + bucket + pathFile;
    }

    /**
     * GetObject接口用于获取某个文件(Object)。此操作需要对此Object具有读权限。
     *
     * @param bucketName  桶名
     * @param ossFilePath Oss文件路径
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String ossFilePath) {
        return minioClient.getObject(
                GetObjectArgs.builder().bucket(bucketName).object(ossFilePath).build());
    }

    /**
     * 查询桶的对象信息
     *
     * @param bucketName 桶名
     * @param recursive  是否递归查询
     * @return
     */
    @SneakyThrows
    public Iterable<Result<Item>> listObjects(String bucketName, boolean recursive) {
        return minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).recursive(recursive).build());
    }

    /**
     * 生成随机文件名,防止重复
     *
     * @return
     */
    public String generateAutoPath(OssReq req) {
        String uid = Optional.ofNullable(req.getUid()).map(String::valueOf).orElse("000000");
        cn.hutool.core.lang.UUID uuid = cn.hutool.core.lang.UUID.fastUUID();
        String suffix = FileNameUtil.getSuffix(req.getFileName());
        String yearAndMonth = DateUtil.format(new Date(), DatePattern.NORM_MONTH_PATTERN);
        return req.getFilePath() + StrUtil.SLASH + yearAndMonth + StrUtil.SLASH + uid + StrUtil.SLASH + uuid + StrUtil.DOT + suffix;
    }

    /**
     * 获取带签名的临时上传元数据对象,前端可获取后,直接上传到Minio
     *
     * @param bucketName
     * @param fileName
     * @return
     */
    @SneakyThrows
    public Map<String, String> getPreSignedPostFormData(String bucketName, String fileName) {
        // 为存储桶创建一个上传策略,过期时间为7天
        PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusDays(7));
        // 设置一个参数key,值为上传对象的名称
        policy.addEqualsCondition("key", fileName);
        // 添加Content-Type以"image/"开头,表示只能上传照片
        policy.addStartsWithCondition("Content-Type", "image/");
        // 设置上传文件的大小 64kiB to 10MiB.
        policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
        return minioClient.getPresignedPostFormData(policy);
    }

}

package com.abin.mallchat.custom.user.domain.vo.request.oss;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;


/**
 * Description: 上传url请求入参
 * Author: <a href="https://github.com/zongzibinbin">abin</a>
 * Date: 2023-03-23
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UploadUrlReq {
    @ApiModelProperty(value = "文件名(带后缀)")
    @NotBlank
    private String fileName;
    @ApiModelProperty(value = "上传场景1.聊天室,2.表情包")
    @NotNull
    private Integer scene;
}

package com.abin.mallchat.custom.user.controller;

import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
import com.abin.mallchat.common.common.utils.RequestHolder;
import com.abin.mallchat.common.common.utils.oss.domain.OssResp;
import com.abin.mallchat.custom.user.domain.vo.request.oss.UploadUrlReq;
import com.abin.mallchat.custom.user.service.OssService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
 * Description: oss控制层
 * Author: <a href="https://github.com/zongzibinbin">abin</a>
 * Date: 2023-06-20
 */
@RestController
@RequestMapping("/capi/oss")
@Api(tags = "oss相关接口")
public class OssController {
    @Autowired
    private OssService ossService;

    @GetMapping("/upload/url")
    @ApiOperation("获取临时上传链接")
    public ApiResult<OssResp> getUploadUrl(@Valid UploadUrlReq req) {
        return ApiResult.success(ossService.getUploadUrl(RequestHolder.get().getUid(), req));
    }
}

package com.abin.mallchat.custom.user.domain.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Description: 场景枚举
 * Author: <a href="https://github.com/zongzibinbin">abin</a>
 * Date: 2023-06-20
 */
@AllArgsConstructor
@Getter
public enum OssSceneEnum {
    CHAT(1, "聊天", "/chat"),
    EMOJI(2, "表情包", "/emoji"),
    ;

    private final Integer type;
    private final String desc;
    private final String path;

    private static final Map<Integer, OssSceneEnum> cache;

    static {
        cache = Arrays.stream(OssSceneEnum.values()).collect(Collectors.toMap(OssSceneEnum::getType, Function.identity()));
    }

    public static OssSceneEnum of(Integer type) {
        return cache.get(type);
    }
}

package com.abin.mallchat.custom.user.service.impl;

import com.abin.mallchat.common.common.utils.AssertUtil;
import com.abin.mallchat.common.common.utils.oss.MinIOTemplate;
import com.abin.mallchat.common.common.utils.oss.domain.OssReq;
import com.abin.mallchat.common.common.utils.oss.domain.OssResp;
import com.abin.mallchat.custom.user.domain.enums.OssSceneEnum;
import com.abin.mallchat.custom.user.domain.vo.request.oss.UploadUrlReq;
import com.abin.mallchat.custom.user.service.OssService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Description:
 * Author: <a href="https://github.com/zongzibinbin">abin</a>
 * Date: 2023-06-20
 */
@Service
public class OssServiceImpl implements OssService {
    @Autowired
    private MinIOTemplate minIOTemplate;

    @Override
    public OssResp getUploadUrl(Long uid, UploadUrlReq req) {
        OssSceneEnum sceneEnum = OssSceneEnum.of(req.getScene());
        AssertUtil.isNotEmpty(sceneEnum, "场景有误");
        OssReq ossReq = OssReq.builder()
                .fileName(req.getFileName())
                .filePath(sceneEnum.getPath())
                .uid(uid)
                .build();
        return minIOTemplate.getPreSignedObjectUrl(ossReq);
    }
}

package com.abin.mallchat.common.common.utils.oss.domain;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;


/**
 * Description: 上传url请求出参
 * Author: <a href="https://github.com/zongzibinbin">abin</a>
 * Date: 2023-03-23
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OssResp {

    @ApiModelProperty(value = "上传的临时url")
    private String uploadUrl;

    @ApiModelProperty(value = "成功后能够下载的url")
    private String downloadUrl;
}

前端获取文件上传展示

import { ref } from 'vue'
import { createEventHook } from '@vueuse/core'
import apis from '@/services/apis'
import { ElMessage } from 'element-plus'

/** 文件信息类型 */
export type FileInfoType = {
  name: string
  type: string
  size: number
  suffix: string
  width?: number
  height?: number
  downloadUrl?: string
  second?: number
  thumbWidth?: number
  thumbHeight?: number
  thumbUrl?: string
}

const Max = 100 // 单位M
const MAX_FILE_SIZE = Max * 1024 * 1024 // 最大上传限制

/**
 * 文件上传Hook
 */
export const useUpload = () => {
  const isUploading = ref(false) // 是否正在上传
  const progress = ref(0) // 进度
  const fileInfo = ref<FileInfoType | null>(null) // 文件信息

  const { on: onChange, trigger } = createEventHook()
  const onStart = createEventHook()

  /**
   * 上传文件
   * @param url 上传链接
   * @param file 文件
   * @param inner 是否内部调用
   */
  const upload = async (url: string, file: File, inner?: boolean) => {
    isUploading.value = true

    const xhr = new XMLHttpRequest()
    xhr.open('PUT', url, true)
    xhr.setRequestHeader('Content-Type', file.type)
    xhr.upload.onprogress = function (e) {
      if (!inner) {
        progress.value = Math.round((e.loaded / e.total) * 100)
      }
    }
    xhr.onload = function () {
      isUploading.value = false
      if (inner) return
      if (xhr.status === 200) {
        trigger('success')
      } else {
        trigger('fail')
      }
    }
    xhr.send(file)
  }
  /**
   * 获取视频第一帧
   */
  const getVideoCover = (file: File) => {
    return new Promise((resolve, reject) => {
      const video = document.createElement('video')
      const tempUrl = URL.createObjectURL(file)
      video.src = tempUrl
      video.crossOrigin = 'anonymous' // 视频跨域
      video.currentTime = 2 // 第2帧
      video.oncanplay = () => {
        const canvas = document.createElement('canvas')
        canvas.width = video.videoWidth
        canvas.height = video.videoHeight
        canvas.getContext('2d')?.drawImage(video, 0, 0, canvas.width, canvas.height)

        // 将canvas转为图片file
        canvas.toBlob((blob) => {
          if (!blob) return
          // 时间戳生成唯一文件名
          const name = Date.now() + 'thumb.jpg'
          const thumbFile = new File([blob], name, { type: 'image/jpeg' })
          // 转成File对象 并上传
          apis
            .getUploadUrl({ fileName: name, scene: '1' })
            .send()
            .then((res) => {
              if (res.uploadUrl && res.downloadUrl) {
                upload(res.uploadUrl, thumbFile, true)
                // 等待上传完成
                const timer = setInterval(() => {
                  if (!isUploading.value) {
                    clearInterval(timer)
                    resolve({
                      thumbWidth: canvas.width,
                      thumbHeight: canvas.height,
                      thumbUrl: res.downloadUrl,
                      thumbSize: thumbFile.size,
                      tempUrl,
                    })
                  }
                })
              }
            })
        })
      }
      video.onerror = function () {
        URL.revokeObjectURL(tempUrl) // 释放临时URL资源
        reject({ width: 0, height: 0, url: null })
      }
    })
  }

  /**
   * 获取图片宽高
   */
  const getImgWH = (file: File) => {
    const img = new Image()
    const tempUrl = URL.createObjectURL(file)
    img.src = tempUrl
    return new Promise((resolve, reject) => {
      img.onload = function () {
        resolve({ width: img.width, height: img.height, tempUrl })
      }
      img.onerror = function () {
        URL.revokeObjectURL(tempUrl) // 释放临时URL资源
        reject({ width: 0, height: 0, url: null })
      }
    })
  }

  /**
   * 获取音频时长
   */
  const getAudioDuration = (file: File) => {
    return new Promise((resolve, reject) => {
      const audio = new Audio()
      const tempUrl = URL.createObjectURL(file)
      audio.src = tempUrl
      // 计算音频的时长
      const countAudioTime = async () => {
        while (isNaN(audio.duration) || audio.duration === Infinity) {
          // 防止浏览器卡死
          await new Promise((resolve) => setTimeout(resolve, 100))
          // 随机进度条位置
          audio.currentTime = 100000 * Math.random()
        }
        // 取整
        const second = Math.round(audio.duration || 0)
        resolve({ second, tempUrl })
      }
      countAudioTime()
      audio.onerror = function () {
        reject({ second: 0, tempUrl })
      }
    })
  }

  /**
   * 解析文件
   * @param file 文件
   * @returns 文件大小、文件类型、文件名、文件后缀...
   */
  const parseFile = async (file: File, addParams: Record<string, any> = {}) => {
    const { name, size, type } = file
    const suffix = name.split('.').pop()?.trim().toLowerCase() || ''
    const baseInfo = { name, size, type, suffix, ...addParams }

    if (type.includes('image')) {
      const { width, height, tempUrl } = (await getImgWH(file)) as any
      return { ...baseInfo, width, height, tempUrl }
    }

    if (type.includes('audio')) {
      const { second, tempUrl } = (await getAudioDuration(file)) as any
      return { second, tempUrl, ...baseInfo }
    }
    // 如果是视频
    if (type.includes('video')) {
      const { thumbWidth, thumbHeight, tempUrl, thumbTempUrl, thumbUrl, thumbSize } =
        (await getVideoCover(file)) as any
      return { ...baseInfo, thumbWidth, thumbHeight, tempUrl, thumbTempUrl, thumbUrl, thumbSize }
    }

    return baseInfo
  }

  /**
   * 上传文件
   * @param file 文件
   * @param addParams 额外参数
   */
  const uploadFile = async (file: File, addParams?: Record<string, any>) => {
    if (isUploading.value || !file) return
    const info = await parseFile(file, addParams)

    // 限制文件大小
    if (info.size > MAX_FILE_SIZE) {
      ElMessage.warning(`文件不得大于 ${Max} MB`)
      return
    }

    const { downloadUrl, uploadUrl } = await apis
      .getUploadUrl({ fileName: info.name, scene: '1' })
      .send()

    if (uploadUrl && downloadUrl) {
      fileInfo.value = { ...info, downloadUrl }
      onStart.trigger(fileInfo)
      upload(uploadUrl, file)
    } else {
      trigger('fail')
    }
  }

  return {
    fileInfo,
    isUploading,
    progress,
    onStart: onStart.on,
    onChange,
    uploadFile,
  }
}

hook-import { createEventHook } from ‘@vueuse/core’

/**
 * 文件上传Hook
 */
export const useUpload = () => {
  const isUploading = ref(false) // 是否正在上传
  const progress = ref(0) // 进度
  const fileInfo = ref<FileInfoType | null>(null) // 文件信息

  const { on: onChange, trigger } = createEventHook()
  const onStart = createEventHook()
  
  return {
    fileInfo,
    isUploading,
    progress,
    onStart: onStart.on,
    onChange,
    uploadFile,
  }
const { isUploading, fileInfo, uploadFile, onStart, onChange: useUploadChange } = useUpload()


useUploadChange((status) => {
  if (status === 'success') {
    if (!fileInfo.value) return
    const { body, type } = generateBody(fileInfo.value, nowMsgType.value)
    send(type, body)
  }
  reset()
})




// 生成消息体
export const generateBody = (fileInfo: any, msgType: MsgEnum, isMock?: boolean) => {
  const {
    size,
    width,
    height,
    downloadUrl,
    name,
    second,
    tempUrl,
    thumbWidth,
    thumbHeight,
    thumbUrl,
    thumbSize,
  } = fileInfo
  const url = isMock ? tempUrl : downloadUrl
  const baseBody = { size, url }
  let body = {}

  if (msgType === MsgEnum.IMAGE) {
    body = { ...baseBody, width, height }
  } else if (msgType === MsgEnum.VOICE) {
    body = { ...baseBody, second }
  } else if (msgType === MsgEnum.VIDEO) {
    body = { ...baseBody, thumbWidth, thumbHeight, thumbUrl, thumbSize }
  } else if (msgType === MsgEnum.FILE) {
    body = { ...baseBody, fileName: name, url: downloadUrl }
  }
  return { body, type: msgType }
}
const selectAndUploadFile = async (files?: FileList | null) => {
  if (!files?.length) return
  const file = files[0]
  if (nowMsgType.value === MsgEnum.IMAGE) {
    if (!file.type.includes('image')) {
      return ElMessage.error('请选择图片文件')
    }
  }
  if (isUpEmoji.value) {
    await uploadEmoji(file)
  } else {
    await uploadFile(file)
  }
}p

项目地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值