这是一个单独的拍照功能组件,作为子组件引入后可直接调用。
注意:1.上传图片成功后,需要在upload()函数将图片上传到自己的服务器,需要修改下参数和接口URL。2.重新加载的时候会从接口获取图片,需要在getImgList()函数修改为自己的接口地址。 这两处需要替换自己的服务器路径即可。
父组件中调用:
javascript
<!-- 使用拍照/选图组件 -->
<view class="picker-wrapper">
<PhotoPicker
:maxCount="9"
:sizeType="['original', 'compressed']"
/>
</view>
import PhotoPicker from '@/components/PhotoPicker.vue'
拍照功能的组件:共8个函数 choosePhoto()、choosePhotoCore()、cameraCallback()、upload()、dataURItoBlob()、getImgList()、previewImage()、deleteImage()
javascript
<template>
<view class="photo-picker">
<view class="image-preview-container">
<!-- 遍历显示已选择的图片 -->
<view class="image-item" v-for="(image, index) in imageList" :key="index">
<!-- 图片预览 -->
<image
class="preview-image"
:src="image"
mode="aspectFill"
@click="previewImage(index)"
></image>
<!-- 删除按钮 -->
<view class="delete-btn" @click.stop="deleteImage(index)">
<text class="delete-icon">×</text>
</view>
</view>
<!-- 添加图片按钮("+"号) -->
<view
class="add-image-btn"
v-if="imageList.length < maxCount"
@click="choosePhoto"
>
<text class="add-icon">+</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: "PhotoPicker",
props: {
//最大选择数量,默认9张
maxCount: {
type: Number,
default: 9,
},
sizeType: {
type: Array,
default: () => ["original", "compressed"], //图片压缩类型 original:原图 compressed:压缩图
},
},
data() {
return {
imageList: [], //图片列表
};
},
methods: {
choosePhoto() {
const that = this;
const remainCount = this.maxCount - this.imageList.length; //计算还能选择多少张图片
if (remainCount <= 0) {
uni.showToast({
title: `最多只能选择${this.maxCount}张图片`,
icon: "none",
duration: 2000,
});
return;
}
that.choosePhotoCore(remainCount);
},
choosePhotoCore(remainCount) {
const that = this;
uni.chooseImage({
sourceType: ["camera", "album"], //相机和相册
sizeType: this.sizeType, //原图和压缩图
count: remainCount, //可选择的最大数量(剩余可选数量)
success: function (res) {
console.log("选择图片成功:", res);
const tempFilePaths = res.tempFilePaths; //获取上传的图片路径
uni.showToast({
title:
tempFilePaths.length === 1
? "选择成功"
: `已选择${tempFilePaths.length}张图片`,
icon: "success",
duration: 1500,
});
this.cameraCallback(tempFilePaths); //base64转换,将图片数据处理
},
fail: function (err) {
console.log("选择图片失败:", err);
uni.showToast({
title: "选择失败,请检查权限",
icon: "none",
duration: 2000,
});
},
});
},
//base64转换,将图片数据处理
cameraCallback(b64Data, uri) {
let uriData = null;
uni.showLoading({
title: "上传中...",
mask: true,
});
//如果是uri路径,直接使用
if (uri) {
uriData = uri;
} else {
//如果是base64格式数据,处理为URI使用
if (b64Data === "") {
return;
}
let prefix = "data:image/jpeg;base64,";
if (b64Data.startsWith("data")) {
prefix = "";
}
let blob = this.dataURItoBlob(prefix + b64Data);
uriData = URL.createObjectURL(blob);
}
let imgs = [
{
name: "file0",
uri: uriData,
},
];
this.upload(imgs);
},
//上传至服务器
upload(imgs) {
var that = this;
var url = "https://api.heliping.com/api/v1/file/upload"; //这块写自己的服务器接口,我这接口是个demo
uni.uploadFile({
url: url,
files: imgs,
formData: {},
header: {
Authorization: "Bearer " + uni.getStorageSync("access_token"),
},
success: (uploadFileRes) => {
uni.hideLoading();
uni.showToast({
icon: "none",
title: "上传成功",
});
that.getImgList(); //上传成功后,获取图片列表
},
fail: (uploadFileRes) => {
uni.hideLoading();
uni.showToast({
icon: "none",
title: "上传失败",
});
},
});
},
//base64转为blob格式
dataURItoBlob(base64Data) {
var byteString;
if (base64Data.split(",")[0].indexOf("base64") >= 0)
byteString = atob(base64Data.split(",")[1]);
//base64 解码
else {
byteString = unescape(base64Data.split(",")[1]);
}
var mimeString = base64Data.split(",")[0].split(":")[1].split(";")[0]; //mime类型 -- image/pn
var ia = new Uint8Array(byteString.length); //创建视图
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var blob = new Blob([ia], {
type: mimeString,
});
return blob;
},
getImgList() {
var that = this;
var url = this.appConfig.dev_url + "/xxxxxxxxx/file/fileList"; //获取图片列表接口
var data = {
xxxxx: "xxxxxx", //这块写自己的接口参数
};
var access_token = uni.getStorageSync("access_token");
uni.request({
url: url,
method: "POST",
data: data,
header: {
Authorization: "Bearer " + access_token,
Accept: "application/json,text/plain,*/*",
},
success: (res) => {
that.imageList = "xxxxxxxxx"; //这块获取图片列表
},
});
},
//图片预览
previewImage(index) {
uni.previewImage({
current: index,
urls: this.imageList, //这块的urls填写自己的图片路径
});
},
//删除图片
deleteImage(index) {
const that = this;
uni.showModal({
title: "提示",
content: "确定要删除这张图片吗?",
success: function (res) {
if (res.confirm) {
that.imageList.splice(index, 1); //从列表中删除指定图片
that.$emit("change", that.imageList);
uni.showToast({
title: "已删除",
icon: "success",
duration: 1500,
});
}
},
});
},
},
mounted() {
this.getImgList();
},
};
</script>
<style lang="scss" scoped>
.photo-picker {
width: 100%;
box-sizing: border-box;
}
.image-preview-container {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
padding: 20rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
overflow: hidden;
background-color: #f5f5f5;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.preview-image {
width: 100%;
height: 100%;
display: block;
}
.delete-btn {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 48rpx;
height: 48rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.delete-icon {
color: #ffffff;
font-size: 36rpx;
font-weight: bold;
line-height: 1;
}
.add-image-btn {
width: 200rpx;
height: 200rpx;
border: 2rpx dashed #d0d0d0;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
box-sizing: border-box;
transition: all 0.3s ease;
}
.add-image-btn:active {
background-color: #f0f0f0;
border-color: #b0b0b0;
}
.add-icon {
font-size: 80rpx;
color: #d0d0d0;
font-weight: 300;
line-height: 1;
}
.image-preview-container:empty::before {
content: "暂无图片";
display: block;
width: 100%;
text-align: center;
color: #999;
font-size: 28rpx;
padding: 40rpx 0;
}
</style>