Files
CosScene/clients/pages/event/create.vue
T
2026-05-09 16:40:29 +08:00

298 lines
8.2 KiB
Vue

<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { createEvent, updateEvent, getEventDetail } from "@/api/event";
import { uploadImage } from "@/api/spot";
import { resolveImageUrl } from "@/utils/image";
const isEdit = ref(false);
const editId = ref(0);
const submitting = ref(false);
const form = ref({
title: "",
city: "",
description: "",
cover_url: "",
location_name: "",
start_date: "",
start_time: "10:00",
end_date: "",
end_time: "18:00",
max_participants: 0,
});
const coverPreview = ref("");
async function chooseCover() {
uni.chooseImage({
count: 1,
success: async (res) => {
const path = res.tempFilePaths[0];
try {
uni.showLoading({ title: "上传中..." });
const uploadRes = await uploadImage(path);
form.value.cover_url = uploadRes.url || uploadRes.path;
coverPreview.value = resolveImageUrl(form.value.cover_url);
uni.showToast({ title: "上传成功", icon: "success" });
} catch (e) {
uni.showToast({ title: "上传失败", icon: "none" });
} finally {
uni.hideLoading();
}
},
});
}
function validate() {
if (!form.value.title.trim()) {
uni.showToast({ title: "请输入活动标题", icon: "none" });
return false;
}
if (!form.value.city.trim()) {
uni.showToast({ title: "请输入城市", icon: "none" });
return false;
}
return true;
}
function buildDateTime(date, time) {
if (!date) return null;
return new Date(`${date}T${time || "00:00"}:00`).toISOString();
}
async function handleSubmit() {
if (!validate()) return;
submitting.value = true;
const data = {
title: form.value.title.trim(),
city: form.value.city.trim(),
description: form.value.description.trim() || null,
cover_url: form.value.cover_url || null,
location_name: form.value.location_name.trim() || null,
max_participants: Number(form.value.max_participants) || 0,
start_time: buildDateTime(form.value.start_date, form.value.start_time),
end_time: buildDateTime(form.value.end_date, form.value.end_time),
};
try {
if (isEdit.value) {
await updateEvent(editId.value, data);
uni.showToast({ title: "更新成功", icon: "success" });
} else {
await createEvent(data);
uni.showToast({ title: "发布成功,等待审核", icon: "success" });
}
setTimeout(() => uni.navigateBack(), 1200);
} catch (e) {
uni.showToast({ title: e.message || "提交失败", icon: "none" });
} finally {
submitting.value = false;
}
}
async function loadForEdit(id) {
try {
const d = await getEventDetail(id);
form.value.title = d.title || "";
form.value.city = d.city || "";
form.value.description = d.description || "";
form.value.cover_url = d.cover_url || "";
form.value.location_name = d.location_name || "";
form.value.max_participants = d.max_participants || 0;
if (d.cover_url) coverPreview.value = resolveImageUrl(d.cover_url);
if (d.start_time) {
const st = new Date(d.start_time);
form.value.start_date = st.toISOString().substring(0, 10);
form.value.start_time = `${String(st.getHours()).padStart(2, "0")}:${String(st.getMinutes()).padStart(2, "0")}`;
}
if (d.end_time) {
const et = new Date(d.end_time);
form.value.end_date = et.toISOString().substring(0, 10);
form.value.end_time = `${String(et.getHours()).padStart(2, "0")}:${String(et.getMinutes()).padStart(2, "0")}`;
}
} catch (e) {
uni.showToast({ title: "加载失败", icon: "none" });
}
}
onLoad((query) => {
if (query.id) {
isEdit.value = true;
editId.value = Number(query.id);
uni.setNavigationBarTitle({ title: "编辑活动" });
loadForEdit(editId.value);
}
});
</script>
<template>
<view class="create-page">
<view class="form-card">
<view class="cover-section" @tap="chooseCover">
<image v-if="coverPreview" class="cover-preview" :src="coverPreview" mode="aspectFill" />
<view v-else class="cover-placeholder">
<uni-icons type="plusempty" size="32" color="#9ca3af" />
<text class="cover-hint">添加封面图</text>
</view>
</view>
<view class="form-row">
<text class="form-label required">活动标题</text>
<input v-model="form.title" class="form-input" placeholder="如:外滩夜景约拍团" :maxlength="100" />
</view>
<view class="form-row">
<text class="form-label required">城市</text>
<input v-model="form.city" class="form-input" placeholder="如:上海" :maxlength="50" />
</view>
<view class="form-row">
<text class="form-label">活动地点</text>
<input v-model="form.location_name" class="form-input" placeholder="如:外滩观景平台" :maxlength="100" />
</view>
<view class="form-row">
<text class="form-label">开始日期</text>
<picker mode="date" :value="form.start_date" @change="form.start_date = $event.detail.value">
<view class="form-input picker-display">{{ form.start_date || "选择日期" }}</view>
</picker>
</view>
<view class="form-row">
<text class="form-label">开始时间</text>
<picker mode="time" :value="form.start_time" @change="form.start_time = $event.detail.value">
<view class="form-input picker-display">{{ form.start_time }}</view>
</picker>
</view>
<view class="form-row">
<text class="form-label">结束日期</text>
<picker mode="date" :value="form.end_date" @change="form.end_date = $event.detail.value">
<view class="form-input picker-display">{{ form.end_date || "选择日期" }}</view>
</picker>
</view>
<view class="form-row">
<text class="form-label">结束时间</text>
<picker mode="time" :value="form.end_time" @change="form.end_time = $event.detail.value">
<view class="form-input picker-display">{{ form.end_time }}</view>
</picker>
</view>
<view class="form-row">
<text class="form-label">人数限制</text>
<input v-model="form.max_participants" class="form-input" type="number" placeholder="0表示不限" />
</view>
<view class="form-row">
<text class="form-label">活动详情</text>
<textarea v-model="form.description" class="form-textarea" placeholder="描述活动内容、注意事项等" :maxlength="5000" />
</view>
</view>
<view class="submit-row">
<view class="submit-btn" :class="{ disabled: submitting }" @tap="handleSubmit">
{{ submitting ? "提交中..." : isEdit ? "保存修改" : "发布活动" }}
</view>
</view>
</view>
</template>
<style scoped>
.create-page {
min-height: 100vh;
background: #f5f6fa;
padding-bottom: 40rpx;
}
.form-card {
background: #fff;
margin: 16rpx 20rpx;
border-radius: 20rpx;
padding: 12rpx 28rpx;
}
.cover-section {
margin: 16rpx 0;
border-radius: 16rpx;
overflow: hidden;
}
.cover-preview {
width: 100%;
height: 300rpx;
}
.cover-placeholder {
width: 100%;
height: 300rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f3f4f6;
border: 2rpx dashed #d1d5db;
border-radius: 16rpx;
gap: 12rpx;
}
.cover-hint {
font-size: 26rpx;
color: #9ca3af;
}
.form-row {
padding: 20rpx 0;
border-bottom: 1rpx solid #f3f4f6;
}
.form-row:last-child {
border-bottom: none;
}
.form-label {
font-size: 26rpx;
color: #374151;
margin-bottom: 10rpx;
display: block;
}
.form-label.required::before {
content: "* ";
color: #ef4444;
}
.form-input {
width: 100%;
height: 72rpx;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 28rpx;
box-sizing: border-box;
line-height: 72rpx;
}
.picker-display {
display: flex;
align-items: center;
color: #374151;
}
.form-textarea {
width: 100%;
min-height: 200rpx;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 16rpx 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.submit-row {
padding: 20rpx 28rpx;
}
.submit-btn {
text-align: center;
padding: 24rpx 0;
background: #6366f1;
color: #fff;
font-size: 30rpx;
font-weight: 700;
border-radius: 16rpx;
}
.submit-btn.disabled {
opacity: 0.6;
}
</style>