192 lines
4.8 KiB
Vue
192 lines
4.8 KiB
Vue
|
|
<template>
|
||
|
|
<view class="avatar-uploader">
|
||
|
|
<view class="avatar-wrapper" @click="onOpenSheet">
|
||
|
|
<image class="avatar-image" :src="modelValue || placeholder" mode="aspectFill"></image>
|
||
|
|
<slot name="badge">
|
||
|
|
<image class="avatar-badge" :src="badgeIcon" mode="aspectFit" v-if="badgeIcon"></image>
|
||
|
|
</slot>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<!-- 底部选择弹窗 -->
|
||
|
|
<view class="action-sheet" v-if="internalVisible" @click="hideActionSheet">
|
||
|
|
<view class="action-sheet-content" @click.stop>
|
||
|
|
<view class="action-sheet-item" @click="handleChoose('camera')">
|
||
|
|
<text class="action-sheet-text">{{ $i18n.t('takePhoto') }}</text>
|
||
|
|
</view>
|
||
|
|
<view class="action-sheet-item" @click="handleChoose('album')">
|
||
|
|
<text class="action-sheet-text">{{ $i18n.t('chooseFromAlbum') }}</text>
|
||
|
|
</view>
|
||
|
|
<view class="action-sheet-cancel" @click="hideActionSheet">
|
||
|
|
<text class="action-sheet-text">{{ $i18n.t('actionSheetCancel') }}</text>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
export default {
|
||
|
|
name: 'AvatarUploader',
|
||
|
|
props: {
|
||
|
|
modelValue: {
|
||
|
|
type: String,
|
||
|
|
default: ''
|
||
|
|
},
|
||
|
|
qiniuToken: {
|
||
|
|
type: String,
|
||
|
|
default: ''
|
||
|
|
},
|
||
|
|
qiniuUploadUrl: {
|
||
|
|
type: String,
|
||
|
|
default: 'https://up-z2.qiniup.com'
|
||
|
|
},
|
||
|
|
qiniuKeyPrefix: {
|
||
|
|
type: String,
|
||
|
|
default: 'smartmeter/img/'
|
||
|
|
},
|
||
|
|
placeholder: {
|
||
|
|
type: String,
|
||
|
|
default: ''
|
||
|
|
},
|
||
|
|
badgeIcon: {
|
||
|
|
type: String,
|
||
|
|
default: ''
|
||
|
|
}
|
||
|
|
},
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
internalVisible: false
|
||
|
|
}
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
// 点击显示上传方式
|
||
|
|
onOpenSheet() {
|
||
|
|
this.$emit('open');
|
||
|
|
this.internalVisible = true;
|
||
|
|
},
|
||
|
|
// 点击隐藏上传方式
|
||
|
|
hideActionSheet() {
|
||
|
|
this.internalVisible = false;
|
||
|
|
this.$emit('close');
|
||
|
|
},
|
||
|
|
// 上传图片
|
||
|
|
handleChoose(source) {
|
||
|
|
this.hideActionSheet();
|
||
|
|
const sourceType = source === 'camera' ? ['camera'] : ['album'];
|
||
|
|
uni.chooseImage({
|
||
|
|
count: 1,
|
||
|
|
sizeType: ['compressed'],
|
||
|
|
sourceType,
|
||
|
|
success: (imgRes) => {
|
||
|
|
const tempPath = imgRes.tempFilePaths[0]
|
||
|
|
this.uploadToQiniu(tempPath)
|
||
|
|
},
|
||
|
|
fail: (err) => {
|
||
|
|
console.error('chooseImage failed:', err)
|
||
|
|
uni.showToast({
|
||
|
|
title: this.$i18n.t('imageOperationCancelled'),
|
||
|
|
icon: 'none',
|
||
|
|
duration: 3000
|
||
|
|
})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
},
|
||
|
|
uploadToQiniu(filePath) {
|
||
|
|
if (!this.qiniuToken) {
|
||
|
|
uni.showToast({ title: this.$i18n.t('uploadFailed'), icon: 'none' })
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const key = this.qiniuKeyPrefix + this.generateGuid(20)
|
||
|
|
uni.uploadFile({
|
||
|
|
url: this.qiniuUploadUrl,
|
||
|
|
name: 'file',
|
||
|
|
filePath,
|
||
|
|
formData: { token: this.qiniuToken, key },
|
||
|
|
success: (res) => {
|
||
|
|
try {
|
||
|
|
const data = JSON.parse(res.data);
|
||
|
|
const finalUrl = 'https://api.ccttiot.com/' + data.key;
|
||
|
|
this.$emit('update:modelValue', finalUrl);
|
||
|
|
this.$emit('success', finalUrl, data);
|
||
|
|
uni.showToast({ title: this.$i18n.t('imageSelectedSuccess'), icon: 'success' })
|
||
|
|
} catch (e) {
|
||
|
|
console.error('parse upload response failed', e)
|
||
|
|
uni.showToast({ title: this.$i18n.t('uploadFailed'), icon: 'none' })
|
||
|
|
}
|
||
|
|
},
|
||
|
|
fail: (err) => {
|
||
|
|
console.error('upload failed:', err)
|
||
|
|
this.$emit('error', err)
|
||
|
|
uni.showToast({ title: this.$i18n.t('uploadFailed'), icon: 'none' })
|
||
|
|
}
|
||
|
|
})
|
||
|
|
},
|
||
|
|
// 随机数
|
||
|
|
generateGuid(length) {
|
||
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||
|
|
let result = '';
|
||
|
|
for (let i = 0; i < length; i++) {
|
||
|
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||
|
|
}
|
||
|
|
return 'static/' + result;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped lang="less">
|
||
|
|
.avatar-uploader {
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
.avatar-wrapper {
|
||
|
|
position: relative;
|
||
|
|
left: 50%;
|
||
|
|
transform: translateX(-50%);
|
||
|
|
width: 272rpx;
|
||
|
|
}
|
||
|
|
.avatar-image {
|
||
|
|
width: 272rpx;
|
||
|
|
height: 272rpx;
|
||
|
|
border-radius: 50%;
|
||
|
|
}
|
||
|
|
.avatar-badge {
|
||
|
|
width: 116rpx;
|
||
|
|
height: 72rpx;
|
||
|
|
position: absolute;
|
||
|
|
right: 0;
|
||
|
|
bottom: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* action sheet styles */
|
||
|
|
.action-sheet {
|
||
|
|
position: fixed;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
right: 0;
|
||
|
|
bottom: 0;
|
||
|
|
background-color: rgba(0, 0, 0, 0.5);
|
||
|
|
z-index: 9999;
|
||
|
|
display: flex;
|
||
|
|
align-items: flex-end;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
.action-sheet-content {
|
||
|
|
width: 100%;
|
||
|
|
background-color: #fff;
|
||
|
|
border-radius: 20rpx 20rpx 0 0;
|
||
|
|
padding-bottom: 40rpx;
|
||
|
|
animation: slideUp 0.3s ease-out;
|
||
|
|
}
|
||
|
|
@keyframes slideUp {
|
||
|
|
from { transform: translateY(100%); }
|
||
|
|
to { transform: translateY(0); }
|
||
|
|
}
|
||
|
|
.action-sheet-item { padding: 30rpx 40rpx; border-bottom: 1rpx solid #f0f0f0; text-align: center; }
|
||
|
|
.action-sheet-item:active { background-color: #f5f5f5; }
|
||
|
|
.action-sheet-cancel { padding: 30rpx 40rpx; text-align: center; margin-top: 20rpx; background-color: #f8f8f8; }
|
||
|
|
.action-sheet-cancel:active { background-color: #e8e8e8; }
|
||
|
|
.action-sheet-text { font-size: 32rpx; color: #333; }
|
||
|
|
.action-sheet-cancel .action-sheet-text { color: #666; }
|
||
|
|
</style>
|
||
|
|
|