fix: 修复奖品图片和赞助商logo删除bug

- 修复编辑奖品时删除图片后保存,图片仍存在的问题
- 修复编辑赞助商时删除logo后保存,logo仍存在的问题
- 前端添加删除检测逻辑,传递removeImage/removeLogo标记
- 后端处理删除标记,将对应字段设为null
This commit is contained in:
Yvan
2026-04-10 09:32:29 +08:00
parent 4920620d2c
commit 2530ceb6b4
3 changed files with 310 additions and 14 deletions
+131 -6
View File
@@ -138,7 +138,7 @@
</div>
<!-- 悬浮添加按钮 -->
<div class="float-add-btn" @click="showExpenseForm = true">
<div class="float-add-btn" @click="addNewExpense">
<van-icon name="plus" />
</div>
</div>
@@ -178,7 +178,7 @@
<van-empty v-if="!sponsors.length" description="暂无赞助商" />
<!-- 悬浮添加按钮 -->
<div class="float-add-btn" @click="showSponsorForm = true">
<div class="float-add-btn" @click="addNewSponsor">
<van-icon name="plus" />
</div>
</div>
@@ -214,7 +214,7 @@
<van-empty v-if="!prizes.length" description="暂无奖品" />
<!-- 悬浮添加按钮 -->
<div class="float-add-btn" @click="showPrizeForm = true">
<div class="float-add-btn" @click="addNewPrize">
<van-icon name="plus" />
</div>
</div>
@@ -732,6 +732,22 @@
required
/>
<van-field v-model="prizeForm.description" label="描述" type="textarea" placeholder="奖品描述(选填)" rows="2" autosize />
<van-field
v-model="prizeForm.source"
label="来源"
placeholder="选择来源"
is-link
readonly
@click="showSourcePicker = true"
required
/>
<van-field
v-if="prizeForm.source !== 'purchase'"
v-model="prizeForm.sponsor"
label="赞助者"
placeholder="请输入赞助者名称"
required
/>
<van-field
v-model.number="prizeForm.quantity"
type="number"
@@ -779,7 +795,31 @@
class="level-item"
@click="selectLevel(level)"
>
<span class="level-badge" :class="`level-${level}`">{{ level }}</span>
<span class="prize-level-badge" :class="`level-${level}`">{{ level }}</span>
</div>
</div>
</div>
</van-popup>
<!-- 来源选择器 -->
<van-popup v-model:show="showSourcePicker" position="bottom" round>
<div class="level-picker">
<div class="picker-header">
<h3>选择来源</h3>
<van-icon name="cross" @click="showSourcePicker = false" />
</div>
<div class="level-list">
<div class="level-item" @click="selectSource('member')">
<van-icon name="friends-o" color="#FF6700" />
<span>群友赞助</span>
</div>
<div class="level-item" @click="selectSource('sponsor')">
<van-icon name="shop-o" color="#FF6700" />
<span>赞助商提供</span>
</div>
<div class="level-item" @click="selectSource('purchase')">
<van-icon name="shopping-cart-o" color="#999" />
<span>采购</span>
</div>
</div>
</div>
@@ -880,6 +920,16 @@
<van-button size="large" type="success" @click="approveFromDetail('approved')">通过</van-button>
<van-button size="large" type="danger" @click="approveFromDetail('rejected')">拒绝</van-button>
</div>
<van-button
block
size="large"
type="danger"
plain
@click="deleteRegistration"
style="margin-top: 12px;"
>
删除报名
</van-button>
</div>
</div>
</van-popup>
@@ -935,6 +985,7 @@ const {
prizes,
showPrizeForm,
showLevelPicker,
showSourcePicker,
prizeImage,
prizeForm,
prizeLevels,
@@ -942,6 +993,7 @@ const {
editPrize,
onPrizeImageRead,
selectLevel,
selectSource,
submitPrize,
deletePrizeItem,
resetPrizeForm
@@ -1084,14 +1136,29 @@ const formatDateTime = (dateTime) => {
return `${year}-${month}-${day} ${hours}:${minutes}`;
};
// 安全解析datetime-local格式,确保以本地时区创建Date对象
const parseDateTimeLocal = (dateTimeStr) => {
console.log('[时区转换] 输入的datetime-local字符串:', dateTimeStr);
const [datePart, timePart] = dateTimeStr.split('T');
const [year, month, day] = datePart.split('-').map(Number);
const [hours, minutes] = timePart.split(':').map(Number);
const localDate = new Date(year, month - 1, day, hours, minutes);
console.log('[时区转换] 创建的本地Date对象:', localDate.toString());
console.log('[时区转换] 转换为UTC (ISO):', localDate.toISOString());
console.log('[时区转换] 时区偏移(分钟):', localDate.getTimezoneOffset());
return localDate;
};
// 开始时间确认
const confirmStartTime = () => {
if (!startTimeInput.value) {
showToast('请选择开始时间');
return;
}
const selectedDate = new Date(startTimeInput.value);
console.log('[开始时间] 用户选择的时间:', startTimeInput.value);
const selectedDate = parseDateTimeLocal(startTimeInput.value);
activityForm.value.startTime = selectedDate.toISOString();
console.log('[开始时间] 保存到表单的UTC时间:', activityForm.value.startTime);
showStartTimePicker.value = false;
};
@@ -1101,12 +1168,14 @@ const confirmDeadline = () => {
showToast('请选择报名截止时间');
return;
}
const selectedDate = new Date(deadlineInput.value);
console.log('[截止时间] 用户选择的时间:', deadlineInput.value);
const selectedDate = parseDateTimeLocal(deadlineInput.value);
if (activityForm.value.startTime && selectedDate > new Date(activityForm.value.startTime)) {
showToast('报名截止时间不能晚于活动开始时间');
return;
}
activityForm.value.registrationDeadline = selectedDate.toISOString();
console.log('[截止时间] 保存到表单的UTC时间:', activityForm.value.registrationDeadline);
showDeadlinePicker.value = false;
};
@@ -1162,17 +1231,31 @@ const loadData = async () => {
}
};
// 添加新奖品
const addNewPrize = () => {
resetPrizeForm();
showPrizeForm.value = true;
};
const editActivity = () => {
activityForm.value = { ...activity.value };
console.log('[编辑活动] 原始活动数据:', activity.value);
// 设置日期时间选择器的值
if (activity.value.startTime) {
console.log('[编辑活动] 数据库的开始时间(UTC):', activity.value.startTime);
const startDate = new Date(activity.value.startTime);
console.log('[编辑活动] 转换为本地Date对象:', startDate.toString());
startTimeInput.value = formatDateTimeLocal(startDate);
console.log('[编辑活动] 显示在选择器的时间:', startTimeInput.value);
}
if (activity.value.registrationDeadline) {
console.log('[编辑活动] 数据库的截止时间(UTC):', activity.value.registrationDeadline);
const deadlineDate = new Date(activity.value.registrationDeadline);
console.log('[编辑活动] 转换为本地Date对象:', deadlineDate.toString());
deadlineInput.value = formatDateTimeLocal(deadlineDate);
console.log('[编辑活动] 显示在选择器的时间:', deadlineInput.value);
}
showActivityForm.value = true;
@@ -1283,6 +1366,13 @@ const editExpense = (expense) => {
showExpenseForm.value = true;
};
// 添加新经费
const addNewExpense = () => {
expenseForm.value = { id: null, item: '', amount: '', category: '', note: '', media: [] };
expenseMediaFiles.value = [];
showExpenseForm.value = true;
};
const onExpenseMediaRead = (file) => {
// 文件读取后的处理,Vant会自动处理
};
@@ -1344,6 +1434,16 @@ const submitSponsor = async () => {
duration: 0
});
formData.append('logo', sponsorLogo.value[0].file);
} else if (sponsorLogo.value.length === 0 && sponsorForm.value.logo) {
// 用户删除了logo
formData.append('removeLogo', 'true');
console.log('用户删除了 Logo');
loadingToast = showToast({
type: 'loading',
message: '正在保存...',
forbidClick: true,
duration: 0
});
} else {
console.log('没有 Logo,仅保存数据');
loadingToast = showToast({
@@ -1411,6 +1511,13 @@ const editSponsor = (sponsor) => {
showSponsorForm.value = true;
};
// 添加新赞助商
const addNewSponsor = () => {
sponsorForm.value = { id: null, name: '', type: 'sponsor', link: '', sortOrder: 0 };
sponsorLogo.value = [];
showSponsorForm.value = true;
};
const onLogoRead = (file) => {
// 文件选择后的回调,显示文件已选择
if (file.file) {
@@ -1515,6 +1622,24 @@ const approveFromDetail = async (status) => {
}
};
// 删除报名
const deleteRegistration = async () => {
try {
await showConfirmDialog({
title: '确认删除',
message: '删除后用户可以重新报名,确定要删除这条报名信息吗?'
});
await api.deleteRegistration(currentReg.value.id);
showToast('删除成功');
showRegDetailDialog.value = false;
loadData();
} catch (error) {
if (error !== 'cancel') {
showToast('删除失败');
}
}
};
// 媒体管理相关方法
const uploadPhotos = async (files) => {
const fileArray = Array.isArray(files) ? files : [files];
+31 -2
View File
@@ -6,6 +6,7 @@ export function usePrizeManage(activityId) {
const prizes = ref([]);
const showPrizeForm = ref(false);
const showLevelPicker = ref(false);
const showSourcePicker = ref(false);
const prizeImage = ref([]);
const prizeForm = ref({
id: null,
@@ -15,10 +16,12 @@ export function usePrizeManage(activityId) {
description: '',
quantity: 1,
sortOrder: 0,
source: 'purchase',
sponsor: '',
image: null
});
const prizeLevels = ['一等奖', '二等奖', '三等奖', '参与奖', '伴手礼'];
const prizeLevels = ['特等奖', '一等奖', '二等奖', '三等奖', '四等奖', '参与奖', '伴手礼'];
const loadPrizes = async () => {
try {
@@ -38,6 +41,8 @@ export function usePrizeManage(activityId) {
description: prize.description || '',
quantity: prize.quantity,
sortOrder: prize.sortOrder || 0,
source: prize.source || 'purchase',
sponsor: prize.sponsor || '',
image: prize.image
};
// 回显图片 - 需要使用完整URL
@@ -60,12 +65,25 @@ export function usePrizeManage(activityId) {
showLevelPicker.value = false;
};
const selectSource = (source) => {
prizeForm.value.source = source;
if (source === 'purchase') {
prizeForm.value.sponsor = '';
}
showSourcePicker.value = false;
};
const submitPrize = async () => {
if (!prizeForm.value.name || !prizeForm.value.level) {
if (!prizeForm.value.name || !prizeForm.value.level || !prizeForm.value.source) {
showToast('请填写必填项');
return;
}
if (prizeForm.value.source !== 'purchase' && !prizeForm.value.sponsor) {
showToast('请填写赞助者名称');
return;
}
const loadingToast = showToast({
type: 'loading',
message: '保存中...',
@@ -81,9 +99,16 @@ export function usePrizeManage(activityId) {
formData.append('description', prizeForm.value.description);
formData.append('quantity', prizeForm.value.quantity);
formData.append('sortOrder', prizeForm.value.sortOrder);
formData.append('source', prizeForm.value.source);
if (prizeForm.value.sponsor) {
formData.append('sponsor', prizeForm.value.sponsor);
}
if (prizeForm.value.imageFile) {
formData.append('image', prizeForm.value.imageFile);
} else if (prizeImage.value.length === 0 && prizeForm.value.image) {
// 用户删除了图片
formData.append('removeImage', 'true');
}
if (prizeForm.value.id) {
@@ -130,6 +155,8 @@ export function usePrizeManage(activityId) {
description: '',
quantity: 1,
sortOrder: 0,
source: 'purchase',
sponsor: '',
image: null
};
prizeImage.value = [];
@@ -139,6 +166,7 @@ export function usePrizeManage(activityId) {
prizes,
showPrizeForm,
showLevelPicker,
showSourcePicker,
prizeImage,
prizeForm,
prizeLevels,
@@ -146,6 +174,7 @@ export function usePrizeManage(activityId) {
editPrize,
onPrizeImageRead,
selectLevel,
selectSource,
submitPrize,
deletePrizeItem,
resetPrizeForm