hookehuyr

feat(上传功能): 添加媒体上传组件和弹窗封装

实现完整的媒体上传功能,包括:
1. 新增 PopupWrapper 作为通用弹窗组件
2. 新增 UploadMediaPopup 作为上传弹窗容器
3. 新增 UploadMediaComponent 实现具体上传逻辑
4. 在 FamilyAlbum 中集成上传功能
5. 更新组件类型声明

支持图片和视频上传,包含预览、删除和提交功能
......@@ -29,6 +29,7 @@ declare module 'vue' {
NutToast: typeof import('@nutui/nutui-taro')['Toast']
Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
PointsCollector: typeof import('./src/components/PointsCollector.vue')['default']
PopupWrapper: typeof import('./src/components/PopupWrapper.vue')['default']
PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
PrimaryButton: typeof import('./src/components/PrimaryButton.vue')['default']
RankingCard: typeof import('./src/components/RankingCard.vue')['default']
......@@ -37,6 +38,8 @@ declare module 'vue' {
ShareButton: typeof import('./src/components/ShareButton/index.vue')['default']
TabBar: typeof import('./src/components/TabBar.vue')['default']
TotalPointsDisplay: typeof import('./src/components/TotalPointsDisplay.vue')['default']
UploadMediaComponent: typeof import('./src/components/UploadMediaComponent.vue')['default']
UploadMediaPopup: typeof import('./src/components/UploadMediaPopup.vue')['default']
WeRunAuth: typeof import('./src/components/WeRunAuth.vue')['default']
}
}
......
......@@ -109,6 +109,15 @@
@fullscreenchange="handleFullscreenChange"
/>
</view>
<!-- 上传媒体弹窗 -->
<UploadMediaPopup
v-model:visible="uploadPopupVisible"
from="family"
:id="''"
@success="handleUploadSuccess"
@close="closeUploadPopup"
/>
</view>
</template>
......@@ -118,12 +127,16 @@ import Taro, { useDidShow } from '@tarojs/taro';
import { Close, Photograph, IconFont } from '@nutui/icons-vue-taro';
import { getPhotoListAPI } from '@/api/photo';
import UploadMediaPopup from '@/components/UploadMediaPopup.vue';
// 视频播放相关状态
const videoVisible = ref(false);
const currentVideo = ref(null);
const videoId = ref(0);
// 上传弹窗相关状态
const uploadPopupVisible = ref(false);
/**
* 处理媒体项点击事件
* @param {Object} item - 媒体项
......@@ -239,10 +252,35 @@ const openAlbumList = () => {
};
/**
* 跳转到上传媒体页面
* 打开上传弹窗
*/
const navigateToUpload = () => {
Taro.navigateTo({ url: '/pages/UploadMedia/index' });
uploadPopupVisible.value = true;
};
/**
* 关闭上传弹窗
*/
const closeUploadPopup = () => {
uploadPopupVisible.value = false;
};
/**
* 上传成功回调
* @param {Object} data - 上传成功的数据
*/
const handleUploadSuccess = (data) => {
console.log('上传成功:', data);
// 刷新相册数据
refreshData();
// 关闭弹窗
closeUploadPopup();
// 显示成功提示
Taro.showToast({
title: '上传成功',
icon: 'success',
duration: 2000
});
};
// 组件挂载时获取数据
......
......@@ -126,7 +126,7 @@ const sourceTypeMap = {
'CHECK_IN_COUNT': '完成活动',
'FAMILY_SIZE': '家庭成员',
'COMPANION_PHOTO': '陪伴拍照',
'WHEELCHAIR_COMPANION': '陪伴轮椅'
'WHEELCHAIR_COMPANION': '特殊陪伴'
}
/**
......
<template>
<nut-popup
:visible="visibleModel"
:position="position"
:style="popupStyle"
:closeable="closeable"
:close-on-click-overlay="closeOnClickOverlay"
:round="round"
:z-index="zIndex"
@close="handleClose"
@open="handleOpen"
@opened="handleOpened"
@closed="handleClosed"
>
<!-- 自定义头部 -->
<view v-if="showHeader" class="popup-header">
<view class="flex items-center justify-between p-4 border-b border-gray-200">
<view class="text-lg font-medium">{{ title }}</view>
<view v-if="showCloseButton" @tap="handleClose" class="w-8 h-8 flex items-center justify-center"></view>
</view>
</view>
<!-- 内容区域 -->
<view class="popup-content" :style="contentStyle">
<slot></slot>
</view>
</nut-popup>
</template>
<script setup>
import { ref, computed, defineProps, defineEmits } from 'vue';
import { Close } from '@nutui/icons-vue-taro';
// 定义props
const props = defineProps({
// 控制弹窗显示/隐藏
visible: {
type: Boolean,
default: false
},
// 弹窗位置
position: {
type: String,
default: 'center',
validator: (value) => ['center', 'top', 'bottom', 'left', 'right'].includes(value)
},
// 弹窗标题
title: {
type: String,
default: ''
},
// 是否显示头部
showHeader: {
type: Boolean,
default: true
},
// 是否显示关闭按钮
showCloseButton: {
type: Boolean,
default: true
},
// 是否可关闭
closeable: {
type: Boolean,
default: true
},
// 点击遮罩是否关闭
closeOnClickOverlay: {
type: Boolean,
default: false
},
// 是否圆角
round: {
type: Boolean,
default: false
},
// 层级
zIndex: {
type: Number,
default: 2000
},
// 自定义宽度
width: {
type: String,
default: '100%'
},
// 自定义高度
height: {
type: String,
default: '100%'
},
// 是否全屏
fullscreen: {
type: Boolean,
default: false
}
});
// 定义emits
const emit = defineEmits(['update:visible', 'close', 'open', 'opened', 'closed']);
// 计算属性处理双向绑定
const visibleModel = computed({
get() {
return props.visible;
},
set(value) {
emit('update:visible', value);
}
});
// 计算弹窗样式
const popupStyle = computed(() => {
const style = {};
if (props.fullscreen) {
style.width = '100%';
style.height = '100%';
} else {
if (props.width) style.width = props.width;
if (props.height) style.height = props.height;
}
return style;
});
// 计算内容区域样式
const contentStyle = computed(() => {
const style = {};
if (props.showHeader) {
style.height = 'calc(100% - 60px)';
} else {
style.height = '100%';
}
return style;
});
/**
* 处理关闭事件
*/
const handleClose = () => {
emit('update:visible', false);
emit('close');
};
/**
* 处理打开事件
*/
const handleOpen = () => {
emit('open');
};
/**
* 处理已打开事件
*/
const handleOpened = () => {
emit('opened');
};
/**
* 处理已关闭事件
*/
const handleClosed = () => {
emit('closed');
};
</script>
<style lang="less" scoped>
.popup-header {
background-color: #fff;
border-bottom: 1px solid #e5e7eb;
}
.popup-content {
background-color: #f8f9fa;
overflow-y: auto;
}
</style>
This diff is collapsed. Click to expand it.
<template>
<PopupWrapper
v-model:visible="popupVisible"
position="right"
title="上传照片/视频"
:fullscreen="true"
:close-on-click-overlay="false"
@close="handleClose"
>
<UploadMediaComponent
:from="from"
:id="id"
@success="handleSuccess"
@close="handleClose"
/>
</PopupWrapper>
</template>
<script setup>
import { ref, defineProps, defineEmits, watch } from 'vue';
import PopupWrapper from '@/components/PopupWrapper.vue';
import UploadMediaComponent from '@/components/UploadMediaComponent.vue';
// 定义props
const props = defineProps({
// 控制弹窗显示/隐藏
visible: {
type: Boolean,
default: false
},
// 来源标识
from: {
type: String,
default: ''
},
// ID参数
id: {
type: String,
default: ''
}
});
// 定义emits
const emit = defineEmits(['update:visible', 'success', 'close']);
// 内部弹窗状态
const popupVisible = ref(false);
// 监听外部visible变化
watch(() => props.visible, (newVal) => {
popupVisible.value = newVal;
}, { immediate: true });
// 监听内部弹窗状态变化
watch(popupVisible, (newVal) => {
emit('update:visible', newVal);
});
/**
* 处理上传成功
* @param {Object} data - 上传成功的数据
*/
const handleSuccess = (data) => {
emit('success', data);
handleClose();
};
/**
* 处理关闭弹窗
*/
const handleClose = () => {
popupVisible.value = false;
emit('close');
};
</script>
<style lang="less" scoped>
// 组件样式由PopupWrapper和UploadMediaComponent提供
</style>
\ No newline at end of file