uniapp的图片上传与提交(Demo记录)

发布于:2025-03-23 ⋅ 阅读:(19) ⋅ 点赞:(0)

前言

🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF

多张照片的Demo比较丰富
单张照片代码迭代过程中忘记书写,索性给一版Demo

1. 单张照片

基于Vue的xx页面,用户需要拍取xx的特定照片并提交审核

重点实现单张照片的提交功能

模块功能 描述
页面模板 定义页面结构,包括拍照字段、照片预览、提交按钮等元素
样式与布局 定义页面组件的样式和布局风格
功能逻辑 包括拍照、预览、删除、提交等功能的实现
状态提示 包括审核结果展示、消息提醒等状态展示

在这里插入图片描述

我的CSS抽离的代码如下:

<style scoped>
/* 让整个页面更整齐 */
.photo-section {
  padding: 10px;
}

/* 让每个上传项的宽度一致 */
.photo-item {
  display: flex;
  flex-direction: column; /* 纵向排列 */
  background: #f9f9f9;
  padding: 10px;
  border-radius: 10px;
  margin-bottom: 12px;
  width: 100%;
  justify-content: space-between;
}

/* 审核结果 */
.audit-result {
    padding: 10px;
    background: #fef3c7;
    color: #b45309;
    border-radius: 5px;
    margin: 10px 0;
	word-break: break-word; /* 让长单词换行 */
	white-space: pre-wrap;  /* 保持空格并换行 */
}


/* 文字 + 按钮 + 示意图 在同一行 */
.photo-label {
  display: flex;
  align-items: center;
  width: 100%;
  justify-content: space-between;
}

.photo-text {
  font-size: 16px;
  font-weight: 500;
}

/* “查看示意图” 按钮 */
.guide-btn {
  background-color: #007AFF;
  color: white;
  font-size: 14px;
  padding: 4px 12px;
  border-radius: 5px;
  border: none;
}

/* 示意图(固定展示) */
.guide-image {
  width: 80px;
  height: 80px;
  border-radius: 5px;
  border: 1px solid #ddd;
  margin-left: 10px;
}

/* 照片区域 */
.photo-container {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* 预览照片 */
.photo-preview {
  width: 80px;
  height: 80px;
  border-radius: 5px;
  border: 1px solid #ccc;
}

/* 时间文本 */
.photo-time {
  font-size: 12px;
  color: gray;
}

/* 删除按钮 */
.delete-btn {
  color: red;
  border: none;
  background: none;
  font-size: 14px;
  margin-left: 5px;
}

/* 拍照按钮 */
.take-photo-btn {
  background-color: #28A745;
  color: white;
  font-size: 14px;
  padding: 6px 12px;
  border-radius: 5px;
  border: none;
  margin-left: 10px;
}

/* 提交按钮 */
.submit-btn {
  width: 100%;
  margin-top: 15px;
}

</style>

整体Demo如下:(页面Demo)

页面整体模版如下:

<template>
  <view>
    <uni-card title="提柜操作" type="line">
      <view class="info">xx: {{ cntr }}</view>

      <!-- 单张照片字段 -->
      <view class="photo-item">
        <view class="photo-label">
          <text>侧面照片</text>
          <image 
            src="/static/images/img_cntr_l.png" 
            class="guide-image" 
            @click="previewGuideImage"
          />
        </view>

        <!-- 照片预览 -->
        <view v-if="photoUrl" class="photo-container">
          <image :src="photoUrl" class="photo-preview" @click="previewImage" />
          <button class="delete-btn" @click="delImage">删除</button>
        </view>

        <!-- 拍摄按钮 -->
        <button v-else class="take-photo-btn" @click="takePhoto">拍照</button>
      </view>

      <button type="primary" :disabled="!canSubmit" @click="submit">提交</button>
    </uni-card>
  </view>
</template>

css如下:

<style>
/* 信息显示样式 */
.info {
  margin-bottom: 15px;
  color: #333;
  font-size: 14px;
}

/* 照片拍摄和预览样式 */
.photo-label {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}

.guide-image {
  width: 30px;
  height: 30px;
  margin-left: 10px;
}

.photo-container {
  position: relative;
  margin-bottom: 15px;
}

.photo-preview {
  width: 100%;
  height: 150px;
  border-radius: 5px;
}

.delete-btn {
  position: absolute;
  right: 5px;
  top: 5px;
  background-color: rgba(0, 0, 0, 0.5);
  color: white;
  padding: 3px 8px;
  border-radius: 3px;
}

.take-photo-btn {
  width: 100%;
  padding: 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
}

/* 状态提示样式 */
.audit-result {
  padding: 10px;
  margin-bottom: 15px;
  background-color: #f8f9fa;
  border-radius: 5px;
}
</style>

整体逻辑:

<script>
export default {
  data() {
    return {
      cntr: "CSCU1234567",  // 编号
      photoUrl: null,      // 照片URL存储
      canSubmit: false,    // 提交按钮状态
      auditResult: '',     // 审核结果
      checkStatus: 0       // 审核状态(0:未审核, 1:已审核)
    };
  },

  computed: {
    // 确定提交按钮是否可用
    canSubmit() {
      return this.photoUrl !== null;
    }
  },

  methods: {
    // 拍摄照片
    takePhoto() {
      const that = this;
      uni.chooseImage({
        count: 1,  // 仅允许选择一张照片
        success: function (res) {
          that.photoUrl = res.tempFiles[0].url;
          that.canSubmit = true;
        }
      });
    },

    // 预览图片
    previewImage() {
      uni.previewImage({
        current: this.photoUrl,
        urls: [this.photoUrl]
      });
    },

    // 删除图片
    delImage() {
      this.photoUrl = null;
      this.canSubmit = false;
    },

    // 提交照片
    submit() {
      // 模拟照片提交逻辑
      this.checkStatus = 1;
      this.auditResult = '审核通过';
      
      // 此处可以添加真实提交逻辑
      setTimeout(() => {
        this.showMessage('提交成功,等待审核...');
      }, 500);
    },

    // 预览示意图
    previewGuideImage() {
      uni.previewImage({
        urls: ['/static/images/img_cntr_l.png']
      });
    },

    // 显示消息
    showMessage(msg) {
      uni.showToast({
        title: msg,
        icon: 'none'
      });
    }
  }
}
</script>

最后可叠加效果:

<view v-if="checkStatus === 1" class="audit-result">
  <text>审核结果: {{ auditResult }}</text>
</view>

2. 多张照片

初始界面如下:

在这里插入图片描述

代码如下:(缺点就是 图片太多的话会拥挤在一起)

  • takePhoto 方法:限制最多 6 张照片,并在上传过多时给出提示。
  • viewPhoto 方法:点击已拍摄的照片时,调用 uni.previewImage 展示大图。
  • submit 方法:确保至少拍摄 2 张照片后才能提交,否则会给出提示
<template>
  <view>
    <uni-card title="换柜操作" type="line">
      <view class="info">箱号: {{ cntr }}</view>
      <view class="photo-section">
        <text>请拍摄照片,最多可提交6张</text>
        <view class="photo-list">
          <view v-for="(photo, index) in photoList" :key="index" class="photo-item" @click="viewPhoto(index)">
            <image :src="photo" class="photo-preview" />
          </view>
        </view>
        <button @click="takePhoto" :disabled="photoList.length >= 6">拍照</button>
        <text class="photo-count">当前已拍摄 {{ photoList.length }} 张照片,最多可提交 6 张。</text>
      </view>
      <button type="primary" :disabled="photoList.length < 1" @click="submit">提交</button>
    </uni-card>
  </view>
</template>

<script>
export default {
  data() {
    return {
      cntr: '',
      photoList: []
    };
  },
  onLoad(options) {
    this.cntr = options.cntr || '';
  },
  methods: {
    takePhoto() {
      uni.chooseImage({
        count: 1,
        sourceType: ['camera'],
        success: (res) => {
          if (this.photoList.length < 6) {
            this.photoList.push(res.tempFilePaths[0]);
          } else {
            uni.showToast({ title: '最多只能上传 6 张照片', icon: 'none' });
          }
        }
      });
    },
    submit() {
      if (this.photoList.length < 2) {
        uni.showToast({ title: '请拍摄至少 2 张照片', icon: 'none' });
        return;
      }
      uni.showToast({ title: '提交成功', icon: 'success' });
      setTimeout(() => {
        uni.navigateBack();
      }, 1000);
    },
    viewPhoto(index) {
      uni.previewImage({
        current: index, // 当前查看的图片索引
        urls: this.photoList // 所有图片的数组
      });
    }
  }
};
</script>

<style scoped>
.photo-section {
  padding: 10px;
}
.photo-list {
  display: flex;
  gap: 10px;
  margin: 10px 0;
}
.photo-item {
  width: 100px;
  height: 100px;
  background: #f0f0f0;
  overflow: hidden;
  border-radius: 5px;
}
.photo-preview {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photo-count {
  margin-top: 10px;
  color: #999;
}
</style>

界面的UI继续优化:

在这里插入图片描述

<template>
  <view>
    <uni-card title="换柜操作" type="line">
      <view class="info">箱号: {{ cntr }}</view>
      <view class="photo-section">
        <text>请拍摄照片,最多可提交6张</text>
        <view class="photo-list">
          <view v-for="(photo, index) in photoList" :key="index" class="photo-item" @click="viewPhoto(index)">
            <image :src="photo" class="photo-preview" />
          </view>
        </view>
        <button @click="takePhoto" :disabled="photoList.length >= 6">拍照</button>
        <text class="photo-count">当前已拍摄 {{ photoList.length }} 张照片,最多可提交 6 张。</text>
      </view>
      <button type="primary" :disabled="photoList.length < 1" @click="submit">提交</button>
    </uni-card>
  </view>
</template>

<script>
export default {
  data() {
    return {
      cntr: '',
      photoList: []
    };
  },
  onLoad(options) {
    this.cntr = options.cntr || '';
  },
  methods: {
    takePhoto() {
      uni.chooseImage({
        count: 1,
        sourceType: ['camera'],
        success: (res) => {
          if (this.photoList.length < 6) {
            this.photoList.push(res.tempFilePaths[0]);
          } else {
            uni.showToast({ title: '最多只能上传 6 张照片', icon: 'none' });
          }
        }
      });
    },
    submit() {
      if (this.photoList.length < 2) {
        uni.showToast({ title: '请拍摄至少 2 张照片', icon: 'none' });
        return;
      }
      uni.showToast({ title: '提交成功', icon: 'success' });
      setTimeout(() => {
        uni.navigateBack();
      }, 1000);
    },
    viewPhoto(index) {
      uni.previewImage({
        current: index, // 当前查看的图片索引
        urls: this.photoList // 所有图片的数组
      });
    }
  }
};
</script>

<style scoped>
.photo-section {
  padding: 10px;
}
.photo-list {
  display: flex;
  flex-wrap: wrap; /* 使图片能自动换行 */
  gap: 10px;
  margin: 10px 0;
}
.photo-item {
  width: calc(33.33% - 10px); /* 每行最多显示 3 张 */
  height: 100px;
  background: #f0f0f0;
  overflow: hidden;
  border-radius: 5px;
}
.photo-preview {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photo-count {
  margin-top: 10px;
  color: #999;
}
</style>

后续在此基础上扩充增加删除

由于图片比较多,需要增加一个删除,可以在图片右上角增加一个绝对路径的删除标志

在这里插入图片描述

<template>
  <view>
    <uni-card title="换柜操作" type="line">
      <view class="info">箱号: {{ cntr }}</view>
      <view class="photo-section">
        <text>请拍摄照片,最多可提交6张</text>
        <view class="photo-list">
          <view v-for="(photo, index) in photoList" :key="index" class="photo-item" @click="viewPhoto(index)">
            <image :src="photo" class="photo-preview" />
            <!-- 删除按钮 -->
            <button class="delete-btn" @click.stop="deletePhoto(index)">x</button>
          </view>
        </view>
        <button @click="takePhoto" :disabled="photoList.length >= 6">拍照</button>
        <text class="photo-count">当前已拍摄 {{ photoList.length }} 张照片,最多可提交 6 张。</text>
      </view>
      <button type="primary" :disabled="photoList.length < 1" @click="submit">提交</button>
    </uni-card>
  </view>
</template>

<script>
export default {
  data() {
    return {
      cntr: '',
      photoList: []
    };
  },
  onLoad(options) {
    this.cntr = options.cntr || '';
  },
  methods: {
    takePhoto() {
      uni.chooseImage({
        count: 1,
        sourceType: ['camera'],
        success: (res) => {
          if (this.photoList.length < 6) {
            this.photoList.push(res.tempFilePaths[0]);
          } else {
            uni.showToast({ title: '最多只能上传 6 张照片', icon: 'none' });
          }
        }
      });
    },
    submit() {
      if (this.photoList.length < 2) {
        uni.showToast({ title: '请拍摄至少 2 张照片', icon: 'none' });
        return;
      }
      uni.showToast({ title: '提交成功', icon: 'success' });
      setTimeout(() => {
        uni.navigateBack();
      }, 1000);
    },
    viewPhoto(index) {
      uni.previewImage({
        current: index, // 当前查看的图片索引
        urls: this.photoList // 所有图片的数组
      });
    },
    // 删除照片
    deletePhoto(index) {
      this.photoList.splice(index, 1);
    }
  }
};
</script>

<style scoped>
.photo-section {
  padding: 10px;
}
.photo-list {
  display: flex;
  flex-wrap: wrap; /* 使图片能自动换行 */
  gap: 10px;
  margin: 10px 0;
}
.photo-item {
  width: calc(33.33% - 10px); /* 每行最多显示 3 张 */
  height: 100px;
  background: #f0f0f0;
  overflow: hidden;
  border-radius: 5px;
  position: relative; /* 为删除按钮提供定位参考 */
}
.photo-preview {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.delete-btn {
  position: absolute;
  top: 5px;
  right: 5px;
  background-color: rgba(0, 0, 0, 0.5);
  color: white;
  border: none;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  font-size: 16px;
  text-align: center;
  line-height: 20px;
}
.delete-btn:hover {
  background-color: rgba(0, 0, 0, 0.7);
}
.photo-count {
  margin-top: 10px;
  color: #999;
}
</style>

继续扩充一个查看功能

查看对应数据:

由于数值都放在同一个字段中,对应通过JSON进行解析:

通过传输的字段进行获取:

// 已有的数据
const res = await getCabinetSwap(item.cntr);
if (res.code === 0 && res.data) {
	const imgPath = res.data.imgPath || []; // 确保 imgPath 存在
	const encodedImgPath = encodeURIComponent(JSON.stringify(imgPath));
	uni.navigateTo({
	  url: `/pages/gate/gateCheck/swapList?cntr=${item.cntr}&imgPath=${encodedImgPath}`
	});
} else {
	uni.showToast({ title: '获取换柜信息失败', icon: 'error' });
}

跳转的页面:

<template>
  <view>
    <uni-card title="换柜照片查看" type="line">
	  <view class="info">箱号: {{ cntr }}</view>
      <view class="photo-section">
        <text>已提交的照片</text>
        <view class="photo-list">
          <view v-for="(photo, index) in photoList" :key="index" class="photo-item" @click="viewPhoto(index)">
            <image :src="photo" class="photo-preview" mode="aspectFill" />
          </view>
        </view>
        <text class="photo-count">共 {{ photoList.length }} 张照片</text>
      </view>
    </uni-card>
    <button type="primary" @click="goBack">返回</button>
  </view>
</template>
<script>
export default {
  data() {
    return {
      cntr: '',
      photoList: []
    };
  },
  onLoad(options) {
    this.cntr = options.cntr || '';

    if (options.imgPath) {
      try {
        this.photoList = JSON.parse(decodeURIComponent(options.imgPath));
      } catch (e) {
        console.error('解析图片数据失败:', e);
      }
    }
  },
  methods: {
    viewPhoto(index) {
      uni.previewImage({
        current: index,
        urls: this.photoList
      });
    },
    goBack() {
      uni.navigateBack();
    }
  }
};
</script>

<style scoped>
.photo-section {
  padding: 10px;
}
.photo-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 10px 0;
}
.photo-item {
  width: calc(33.33% - 10px);
  height: 100px;
  background: #f0f0f0;
  overflow: hidden;
  border-radius: 5px;
}
.photo-preview {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photo-count {
  margin-top: 10px;
  color: #999;
}
</style>