546 lines
13 KiB
Vue
546 lines
13 KiB
Vue
<template>
|
||
<view class="ali-oss-uploader">
|
||
<view class="preview-container" :style="'width:'+width">
|
||
<!-- 预览区域 -->
|
||
<view v-if="accept == 'image/*'" class="preview-container" :style="'width:'+width">
|
||
<view
|
||
v-for="(file, index) in fileList"
|
||
:key="index"
|
||
class="preview-item"
|
||
>
|
||
<image
|
||
:src="file"
|
||
class="preview-image"
|
||
mode="aspectFill"
|
||
@click="handlePreview(index)"
|
||
/>
|
||
<view v-if="deletable" class="delete-icon" @click.stop="handleDelete(index)">
|
||
×
|
||
</view>
|
||
</view>
|
||
<!-- 上传按钮 -->
|
||
<view class="preview-item">
|
||
<!-- <view
|
||
class="upload-btn"
|
||
@click="handleUpload"
|
||
:style="btnStyle"
|
||
v-if="fileList.length < maxCount"
|
||
>
|
||
<slot name="button">
|
||
<text class="plus-icon">+</text>
|
||
</slot>
|
||
</view> -->
|
||
<image src="/static/images/evaluate/upload.png" class="upload-btn" :style="btnStyle" @click="handleUpload" v-if="fileList.length < maxCount"></image>
|
||
</view>
|
||
|
||
</view>
|
||
|
||
<!-- 视频预览区域 -->
|
||
<view class="preview-container" :style="'width:'+width" v-if="accept == 'video/*'">
|
||
<view
|
||
class="preview-item"
|
||
v-if="oneFile"
|
||
>
|
||
<!-- <video
|
||
:src="oneFile"
|
||
class="preview-image"
|
||
@click="handlePreview(index)"
|
||
/> -->
|
||
<image class="preview-image" :src="oneFile + '?x-oss-process=video/snapshot,t_0,f_jpg'" mode="aspectFill"></image>
|
||
<view v-if="deletable" class="delete-icon" @click.stop="handleDelete()">
|
||
×
|
||
</view>
|
||
</view>
|
||
<view class="preview-item">
|
||
<!-- 上传按钮 -->
|
||
<view
|
||
class="upload-btn"
|
||
@click="handleUpload"
|
||
:style="btnStyle"
|
||
v-if="!oneFile"
|
||
>
|
||
<slot name="button">
|
||
<text class="plus-icon">+</text>
|
||
</slot>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 提示文字 -->
|
||
<!-- <text v-if="tips" class="tips-text">{{ tips }}</text> -->
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import request from '@/utils/request'
|
||
import permissionUtils from '@/utils/per'
|
||
import locationService from '@/utils/locationService';
|
||
export default {
|
||
name: 'AliOssUploader',
|
||
|
||
props: {
|
||
// 已上传文件列表
|
||
value: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
// 单个上传文件
|
||
fileString: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 按钮文字
|
||
buttonText: {
|
||
type: String,
|
||
default: '上传图片'
|
||
},
|
||
// 提示文字
|
||
tips: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 最大上传数量
|
||
maxCount: {
|
||
type: Number,
|
||
default: 3
|
||
},
|
||
// 单个文件大小限制(MB)
|
||
maxSize: {
|
||
type: Number,
|
||
default: 5
|
||
},
|
||
// 是否显示预览
|
||
showPreview: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 是否可删除
|
||
deletable: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 按钮自定义样式
|
||
btnStyle: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
// 文件类型
|
||
accept: {
|
||
type: String,
|
||
default: 'image/*'
|
||
},
|
||
// 当前用户id
|
||
userId: {
|
||
type: Number,
|
||
default: null
|
||
},
|
||
width: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
type: {
|
||
type: Number,
|
||
default: null
|
||
},
|
||
kind: {
|
||
type: Number,
|
||
default: 1
|
||
}
|
||
},
|
||
|
||
data() {
|
||
return {
|
||
fileList: [],
|
||
oneFile: '',
|
||
loading: false
|
||
}
|
||
},
|
||
|
||
watch: {
|
||
value: {
|
||
immediate: true,
|
||
handler(newVal) {
|
||
this.fileList = [...newVal]
|
||
}
|
||
},
|
||
fileString: {
|
||
immediate: true,
|
||
handler(newVal) {
|
||
this.oneFile = newVal
|
||
}
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
|
||
// 显示权限说明弹窗
|
||
showPermissionDialog(title, content) {
|
||
return new Promise((resolve) => {
|
||
uni.showModal({
|
||
title,
|
||
content,
|
||
confirmText: '去开启',
|
||
success(res) {
|
||
resolve(res.confirm);
|
||
}
|
||
});
|
||
});
|
||
},
|
||
// 触发上传
|
||
async handleUpload() {
|
||
// 判断
|
||
try {
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
if(systemInfo.platform === 'ios') {
|
||
this.openCamera();
|
||
}else {
|
||
let xcpermission = '';
|
||
let xjpermission = '';
|
||
if(systemInfo.platform === 'ios') {
|
||
xcpermission = 'photo_library',
|
||
xjpermission = 'camera'
|
||
}else {
|
||
xcpermission = `android.permission.READ_EXTERNAL_STORAGE,android.permission.WRITE_EXTERNAL_STORAGE`
|
||
xjpermission = 'android.permission.CAMERA'
|
||
}
|
||
const xjfirstRequest = !plus.storage.getItem(`perm_${xjpermission}`)
|
||
const xcfirstRequest = !plus.storage.getItem(`perm_${xcpermission}`)
|
||
if(xcfirstRequest || xjfirstRequest) {
|
||
// this.isShowPer = true;
|
||
console.log('ckeckisShowPer',true)
|
||
this.$emit('ckeckisShowPer',true)
|
||
}
|
||
|
||
// 1. 检查权限
|
||
// const { granted } = await permissionUtils.checkPermission('photo_library', '需要相册权限用于上传照片');
|
||
this.$emit('ckecknowQer','xc')
|
||
const xc = await permissionUtils.checkPermission('photo_library', '需要相册权限用于上传照片');
|
||
this.$emit('ckecknowQer','xj')
|
||
const xj = await permissionUtils.checkPermission('camera', '需要相机权限用于拍摄照片');
|
||
this.$emit('ckeckisShowPer',false)
|
||
this.$emit('ckecknowQer','')
|
||
if (xc.granted && xj.granted) {
|
||
this.openCamera();
|
||
return;
|
||
}
|
||
// this.isShowPer = false;
|
||
// 2. 显示权限说明弹窗
|
||
const confirm = await this.showPermissionDialog(
|
||
'相机相册权限申请',
|
||
'我们需要访问您的相机以及相册以完成更换头像功能'
|
||
);
|
||
|
||
if (!confirm) return;
|
||
|
||
// 3. 请求权限
|
||
this.$emit('ckeckisShowPer',true)
|
||
this.$emit('ckecknowQer','xc')
|
||
const result1 = await permissionUtils.requestPermission('photo_library', '我们需要访问您的相册以上传图片/视频');
|
||
this.$emit('ckecknowQer','xj')
|
||
const result2 = await permissionUtils.requestPermission('camera', '我们需要访问您的相机以上传图片/视频');
|
||
if (result1 && result2) {
|
||
this.openCamera();
|
||
} else {
|
||
locationService.openAppSettings();
|
||
// uni.showToast({ title: '相机权限被拒绝', icon: 'none' });
|
||
}
|
||
this.$emit('ckeckisShowPer',false)
|
||
this.$emit('ckecknowQer','')
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('上传出错:', error)
|
||
this.$emit('error', error)
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
async openCamera() {
|
||
if (this.loading) return
|
||
if (this.fileList.length >= this.maxCount) {
|
||
return uni.showToast({
|
||
title: `最多上传${this.maxCount}个文件`,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
try {
|
||
this.loading = true
|
||
let res;
|
||
let tempFiles;
|
||
if(this.accept == 'image/*') {
|
||
const res = await uni.chooseImage({
|
||
count: this.maxCount - this.fileList.length,
|
||
sizeType: ['original', 'compressed'],
|
||
sourceType: ['album', 'camera']
|
||
})
|
||
const tempFiles = res.tempFiles
|
||
for (const file of tempFiles) {
|
||
await this.uploadFile(file)
|
||
}
|
||
}else if(this.accept == 'video/*') {
|
||
const res = await uni.chooseVideo({
|
||
count: 1,
|
||
sourceType: ['album', 'camera'],
|
||
compressed: false
|
||
})
|
||
let selectedFile;
|
||
// #ifdef H5
|
||
selectedFile = res.tempFile;
|
||
// #endif
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
selectedFile = res;
|
||
// #endif
|
||
await this.uploadFile(selectedFile)
|
||
}
|
||
} catch (error) {
|
||
console.error('上传出错:', error)
|
||
this.$emit('error', error)
|
||
}
|
||
},
|
||
// 上传文件到OSS
|
||
async uploadFile(file) {
|
||
// 检查文件大小
|
||
if (file.size > this.maxSize * 1024 * 1024) {
|
||
uni.showToast({
|
||
title: `文件大小不能超过${this.maxSize}MB`,
|
||
icon: 'none'
|
||
})
|
||
return Promise.reject(new Error('文件大小超出限制'))
|
||
}
|
||
let date = new Date().getTime()
|
||
let imageType;
|
||
if(this.accept == 'image/*') {
|
||
imageType = file.path.split('.')
|
||
}else if(this.accept == 'video/*') {
|
||
// #ifdef H5
|
||
imageType = file.name.split('.')
|
||
// #endif
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
imageType = file.tempFilePath.split('.')
|
||
// #endif
|
||
}
|
||
console.log('imageType',imageType)
|
||
|
||
let artisan = this.type==1 ? 'yh' : this.type==2 ? 'syr' : this.type==3 ? 'sj' : '';
|
||
let name = `${this.userId}_${artisan}_${date}.${imageType[imageType.length - 1]}`
|
||
console.log('name',name)
|
||
try {
|
||
// 1. 获取OSS上传凭证
|
||
const ossConfig = await this.getOssConfig()
|
||
// 2. 上传文件
|
||
const uploadRes = await this.uploadToOss(file, ossConfig, name)
|
||
// 3. 添加到文件列表
|
||
const fileUrl = `${ossConfig.host}/${ossConfig.dir}${name}`
|
||
if(this.accept == 'image/*') {
|
||
const newFile = [`${fileUrl}`]
|
||
this.fileList = this.fileList.concat(newFile)
|
||
this.$emit('input', this.fileList)
|
||
this.$emit('change', this.fileList)
|
||
this.$emit('success', newFile)
|
||
return newFile
|
||
}else if(this.accept == 'video/*') {
|
||
const newFile = fileUrl
|
||
this.oneFile = fileUrl
|
||
this.$emit('input', this.oneFile)
|
||
this.$emit('change', this.oneFile)
|
||
this.$emit('success', newFile)
|
||
return newFile
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('上传失败:', error)
|
||
this.$emit('error', error)
|
||
throw error
|
||
}
|
||
},
|
||
|
||
// 获取OSS配置
|
||
async getOssConfig() {
|
||
try {
|
||
const res = await request.post('/user/getalioss',{type: this.type,kind: this.kind})
|
||
return res
|
||
} catch (error) {
|
||
console.error('获取OSS配置失败:', error)
|
||
throw error
|
||
}
|
||
},
|
||
|
||
// 上传到OSS
|
||
uploadToOss(file, ossConfig, name) {
|
||
return new Promise((resolve, reject) => {
|
||
let formData;
|
||
// #ifdef H5
|
||
formData = new FormData()
|
||
formData.append('name',name);
|
||
formData.append('policy', ossConfig.policy);
|
||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||
formData.append('success_action_status', '200');
|
||
formData.append('signature', ossConfig.signature);
|
||
formData.append('key', ossConfig.dir + name);
|
||
// file必须为最后一个表单域,除file以外的其他表单域无顺序要求。
|
||
formData.append('file', file);
|
||
fetch(ossConfig.host, { method: 'POST', body: formData},).then((res) => {
|
||
resolve(res)
|
||
}).catch(()=>{
|
||
reject(new Error(`上传失败: ${res.statusCode}`))
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
formData = {};
|
||
formData.name = name;
|
||
formData.policy = ossConfig.policy;
|
||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
||
formData.success_action_status = '200';
|
||
formData.signature = ossConfig.signature;
|
||
formData.key = ossConfig.dir + name;
|
||
formData.file = file;
|
||
let path;
|
||
if(this.accept == 'image/*') {
|
||
path = file.path
|
||
}else if(this.accept == 'video/*') {
|
||
// #ifdef H5
|
||
path = file.name
|
||
// #endif
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
path = file.tempFilePath
|
||
// #endif
|
||
}
|
||
// 使用 uni.uploadFile 上传文件(支持多平台)
|
||
uni.uploadFile({
|
||
url: ossConfig.host,
|
||
filePath: path, // 手机端文件路径
|
||
name: 'file',
|
||
formData: {
|
||
key: ossConfig.dir + name, // 文件名
|
||
policy: ossConfig.policy, // 后台获取超时时间
|
||
OSSAccessKeyId: ossConfig.ossAccessKeyId, // 后台获取临时ID
|
||
success_action_status: 200, // 让服务端返回200,不然,默认会返回204
|
||
signature: ossConfig.signature // 后台获取签名
|
||
},
|
||
success(res) {
|
||
resolve(`${ossConfig.host}/${ossConfig.dir}${name}`)
|
||
}
|
||
});
|
||
// #endif
|
||
})
|
||
|
||
},
|
||
|
||
// 预览图片
|
||
handlePreview(index) {
|
||
uni.previewImage({
|
||
current: index,
|
||
urls: this.fileList.map(item => item.url)
|
||
})
|
||
},
|
||
|
||
// 删除文件
|
||
handleDelete(index) {
|
||
// uni.showModal({
|
||
// title: '提示',
|
||
// content: '确定删除该文件吗?',
|
||
// success: (res) => {
|
||
// if (res.confirm) {
|
||
if(this.accept == 'image/*') {
|
||
const deletedFile = this.fileList[index]
|
||
this.fileList.splice(index, 1)
|
||
this.$emit('input', this.fileList)
|
||
this.$emit('change', this.fileList)
|
||
this.$emit('delete', deletedFile, index)
|
||
}else if(this.accept == 'video/*') {
|
||
const deletedFile = this.oneFile
|
||
this.oneFile = ''
|
||
this.$emit('input', this.oneFile)
|
||
this.$emit('change', this.oneFile)
|
||
this.$emit('delete', deletedFile)
|
||
}
|
||
|
||
// }
|
||
// }
|
||
// })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* .ali-oss-uploader {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
justify-items: center;
|
||
align-items: center;
|
||
margin-top: 20rpx;
|
||
position: relative;
|
||
} */
|
||
|
||
.preview-container {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
justify-items: center;
|
||
align-items: center;
|
||
position: relative;
|
||
}
|
||
.upload-btn {
|
||
width: 148rpx;
|
||
height: 148rpx;
|
||
/* border: 2rpx dashed #ddd; */
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
/* margin-top: 20rpx; */
|
||
}
|
||
|
||
.plus-icon {
|
||
font-size: 60rpx;
|
||
color: #999;
|
||
line-height: 1;
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
.tips-text {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
.preview-item {
|
||
position: relative;
|
||
width: 148rpx;
|
||
height: 148rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.preview-image {
|
||
width: 148rpx;
|
||
height: 148rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.delete-icon {
|
||
position: absolute;
|
||
top: -10rpx;
|
||
right: -10rpx;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
background-color: #ff4d4f;
|
||
color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
font-size: 32rpx;
|
||
z-index: 1;
|
||
}
|
||
</style>
|