OfficeSystem/pages/add-event/index.vue

899 lines
23 KiB
Vue
Raw Normal View History

2025-11-03 09:47:11 +08:00
<template>
<view class="add-event-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="navbar-content">
<text class="nav-btn" @click="handleCancel">取消</text>
<text class="nav-title">新建日程</text>
<text class="nav-btn nav-btn-primary" @click="handleSave">完成</text>
</view>
</view>
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 标题输入 -->
<view class="form-item">
<input
v-model="formData.title"
class="title-input"
placeholder="添加标题"
placeholder-style="color: #999;"
/>
</view>
<!-- 时间日期选择 -->
<view class="form-item time-section">
<view class="time-item" @click="openStartPicker">
<view class="time-icon">🕐</view>
<view class="time-content">
<text class="time-value">{{ startTime }}</text>
<text class="time-date">{{ startDateText }}</text>
</view>
<text class="arrow"></text>
</view>
<text class="time-separator"></text>
<view class="time-item" @click="openEndPicker">
<view class="time-icon">🕐</view>
<view class="time-content">
<text class="time-value">{{ endTime }}</text>
<text class="time-date">{{ endDateText }}</text>
</view>
<text class="arrow"></text>
</view>
</view>
<!-- 全天开关 -->
2025-11-03 11:08:06 +08:00
<view class="form-item switch-item">
2025-11-03 09:47:11 +08:00
<text class="label">全天</text>
<switch :checked="formData.allDay" @change="toggleAllDay" color="#2885ff" />
</view>
<!-- 重复选项 -->
2025-11-03 11:29:56 +08:00
<view class="form-item clickable-item" @click="openRepeatPicker">
2025-11-03 09:47:11 +08:00
<text class="label">{{ repeatText }}</text>
<text class="arrow"></text>
</view>
<!-- 描述输入 -->
<view class="form-item clickable-item" @click="showDescInput = true">
<view class="desc-icon">📄</view>
<view class="desc-content">
<text v-if="formData.description" class="desc-text">{{ formData.description }}</text>
<text v-else class="desc-placeholder">添加描述</text>
</view>
<text class="arrow"></text>
</view>
<!-- 日程颜色 -->
<view class="form-item clickable-item" @click="showColorPicker = true">
<view class="color-dot" :style="{ backgroundColor: formData.color }"></view>
<text class="label">日程颜色</text>
<text class="arrow"></text>
</view>
<!-- 提醒设置 -->
<view class="form-item clickable-item" @click="showReminderPicker = true">
<view class="reminder-icon">🔔</view>
<text class="reminder-text">{{ reminderText }}</text>
<text class="arrow"></text>
</view>
</scroll-view>
<!-- 开始日期时间选择弹窗 -->
<view v-if="showStartDatePicker || showStartTimePicker" class="modal-mask" @click="closeStartPicker">
<view class="modal-content datetime-modal" @click.stop>
<view class="modal-title">选择开始时间</view>
<view class="datetime-content">
<!-- 日期选择 -->
<view class="date-section">
<text class="section-label">日期</text>
<uv-calendar
ref="startDateCalendar"
mode="single"
@confirm="handleStartDateConfirm"
/>
</view>
<!-- 时间选择 -->
<view class="time-section-picker">
<text class="section-label">时间</text>
<picker
mode="multiSelector"
:value="startPickerIndex"
:range="startPickerRange"
@change="handleStartTimeChange"
>
<view class="picker-display">
{{ formatTime(formData.startHour, formData.startMin) }}
</view>
</picker>
</view>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="closeStartPicker">取消</button>
<button class="modal-btn primary" @click="closeStartPicker">确定</button>
</view>
</view>
</view>
<!-- 结束日期时间选择弹窗 -->
<view v-if="showEndDatePicker || showEndTimePicker" class="modal-mask" @click="closeEndPicker">
<view class="modal-content datetime-modal" @click.stop>
<view class="modal-title">选择结束时间</view>
<view class="datetime-content">
<!-- 日期选择 -->
<view class="date-section">
<text class="section-label">日期</text>
<uv-calendar
ref="endDateCalendar"
mode="single"
@confirm="handleEndDateConfirm"
/>
</view>
<!-- 时间选择 -->
<view class="time-section-picker">
<text class="section-label">时间</text>
<picker
mode="multiSelector"
:value="endPickerIndex"
:range="endPickerRange"
@change="handleEndTimeChange"
>
<view class="picker-display">
{{ formatTime(formData.endHour, formData.endMin) }}
</view>
</picker>
</view>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="closeEndPicker">取消</button>
<button class="modal-btn primary" @click="closeEndPicker">确定</button>
</view>
</view>
</view>
<!-- 描述输入弹窗 -->
<view v-if="showDescInput" class="modal-mask" @click="showDescInput = false">
<view class="modal-content" @click.stop>
<view class="modal-title">添加描述</view>
<textarea
v-model="formData.description"
class="desc-textarea"
placeholder="请输入描述"
auto-height
/>
<view class="modal-buttons">
<button class="modal-btn" @click="showDescInput = false">取消</button>
<button class="modal-btn primary" @click="showDescInput = false">确定</button>
</view>
</view>
</view>
<!-- 颜色选择弹窗 -->
<view v-if="showColorPicker" class="modal-mask" @click="showColorPicker = false">
<view class="modal-content" @click.stop>
<view class="modal-title">选择颜色</view>
<view class="color-options">
<view
v-for="color in colorOptions"
:key="color"
class="color-option"
:class="{ active: formData.color === color }"
:style="{ backgroundColor: color }"
@click="selectColor(color)"
></view>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="showColorPicker = false">取消</button>
<button class="modal-btn primary" @click="showColorPicker = false">确定</button>
</view>
</view>
</view>
<!-- 重复选择弹窗 -->
2025-11-03 11:29:56 +08:00
<view v-if="showRepeatPicker" class="modal-mask" @click="handleCancelRepeat">
2025-11-03 09:47:11 +08:00
<view class="modal-content" @click.stop>
<view class="modal-title">重复设置</view>
<view class="repeat-options">
<view
v-for="item in repeatOptions"
:key="item.value"
class="repeat-option"
2025-11-03 11:29:56 +08:00
:class="{ active: tempRepeat === item.value }"
@click="tempRepeat = item.value"
2025-11-03 09:47:11 +08:00
>
<text>{{ item.label }}</text>
2025-11-03 11:29:56 +08:00
<text v-if="tempRepeat === item.value" class="check"></text>
2025-11-03 09:47:11 +08:00
</view>
</view>
<view class="modal-buttons">
2025-11-03 11:29:56 +08:00
<button class="modal-btn" @click="handleCancelRepeat">取消</button>
<button class="modal-btn primary" @click="handleConfirmRepeat">确定</button>
2025-11-03 09:47:11 +08:00
</view>
</view>
</view>
<!-- 提醒选择弹窗 -->
<view v-if="showReminderPicker" class="modal-mask" @click="showReminderPicker = false">
<view class="modal-content" @click.stop>
<view class="modal-title">提醒设置</view>
<view class="reminder-options">
<view
v-for="item in reminderOptions"
:key="item.value"
class="reminder-option"
:class="{ active: formData.reminder === item.value }"
@click="selectReminder(item.value)"
>
<text>{{ item.label }}</text>
<text v-if="formData.reminder === item.value" class="check"></text>
</view>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="showReminderPicker = false">取消</button>
<button class="modal-btn primary" @click="showReminderPicker = false">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
2025-11-03 10:52:56 +08:00
import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
2025-11-03 09:47:11 +08:00
// 表单数据
const formData = ref({
title: '',
startDate: '',
startHour: 16,
startMin: 0,
endDate: '',
endHour: 17,
endMin: 0,
allDay: false,
repeat: 'none',
description: '',
2025-11-04 17:10:40 +08:00
color: '#B3D9FF',
2025-11-03 09:47:11 +08:00
reminder: 15 // 提前多少分钟提醒
});
// 显示状态
const showStartTimePicker = ref(false);
const showStartDatePicker = ref(false);
const showEndTimePicker = ref(false);
const showEndDatePicker = ref(false);
const showDescInput = ref(false);
const showColorPicker = ref(false);
const showRepeatPicker = ref(false);
const showReminderPicker = ref(false);
2025-11-03 11:29:56 +08:00
// 临时数据(用于弹窗暂存)
const tempRepeat = ref('none');
2025-11-03 09:47:11 +08:00
// 时间选择器相关
const hourOptions = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0') + '时');
const minOptions = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '分');
const startPickerRange = ref([hourOptions, minOptions]);
const startPickerIndex = ref([16, 0]);
const endPickerRange = ref([hourOptions, minOptions]);
const endPickerIndex = ref([17, 0]);
const startDateCalendar = ref(null);
const endDateCalendar = ref(null);
// 颜色选项
const colorOptions = [
2025-11-04 17:10:40 +08:00
'#B3D9FF', // 更淡的蓝色
'#FFE8CC', // 更淡的橙色
'#FFD6D9', // 更淡的红色
'#D4F3E8', // 更淡的绿色
'#FFF4CC', // 更淡的黄色
'#E6E5FC', // 更淡的紫色
2025-11-03 09:47:11 +08:00
];
// 重复选项
const repeatOptions = [
{ label: '不重复', value: 'none' },
{ label: '每天', value: 'daily' },
{ label: '每周', value: 'weekly' },
{ label: '每月', value: 'monthly' },
{ label: '每年', value: 'yearly' }
];
// 提醒选项
const reminderOptions = [
{ label: '不提醒', value: 0 },
{ label: '开始时', value: 0 },
{ label: '开始前5分钟', value: 5 },
{ label: '开始前15分钟', value: 15 },
{ label: '开始前30分钟', value: 30 },
{ label: '开始前1小时', value: 60 },
{ label: '开始前1天', value: 1440 }
];
// 格式化时间
const formatTime = (hour, min) => {
const h = String(hour).padStart(2, '0');
const m = String(min).padStart(2, '0');
return `${h}:${m}`;
};
// 格式化日期文本
const formatDateText = (dateStr) => {
if (!dateStr) return '';
const date = new Date(dateStr);
const month = date.getMonth() + 1;
const day = date.getDate();
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const weekday = weekdays[date.getDay()];
return `${month}${day}${weekday}`;
};
// 计算属性
const startTime = computed(() => {
return formData.value.allDay ? '全天' : formatTime(formData.value.startHour, formData.value.startMin);
});
const endTime = computed(() => {
return formData.value.allDay ? '' : formatTime(formData.value.endHour, formData.value.endMin);
});
const startDateText = computed(() => {
return formatDateText(formData.value.startDate);
});
const endDateText = computed(() => {
return formatDateText(formData.value.endDate || formData.value.startDate);
});
// 打开开始时间选择器
const openStartPicker = () => {
if (formData.value.allDay) {
showStartDatePicker.value = true;
} else {
showStartDatePicker.value = true;
showStartTimePicker.value = true;
}
// 更新选择器索引
startPickerIndex.value = [formData.value.startHour, formData.value.startMin];
};
// 打开结束时间选择器
const openEndPicker = () => {
if (formData.value.allDay) {
showEndDatePicker.value = true;
} else {
showEndDatePicker.value = true;
showEndTimePicker.value = true;
}
// 更新选择器索引
endPickerIndex.value = [formData.value.endHour, formData.value.endMin];
};
// 关闭开始选择器
const closeStartPicker = () => {
showStartDatePicker.value = false;
showStartTimePicker.value = false;
};
// 关闭结束选择器
const closeEndPicker = () => {
showEndDatePicker.value = false;
showEndTimePicker.value = false;
};
const reminderText = computed(() => {
if (formData.value.reminder === 0) {
return '不提醒';
}
if (formData.value.reminder === 1440) {
return '开始前1天,应用弹窗提醒我';
}
if (formData.value.reminder === 60) {
return `开始前1小时,应用弹窗提醒我`;
}
return `开始前${formData.value.reminder}分钟,应用弹窗提醒我`;
});
const repeatText = computed(() => {
const option = repeatOptions.find(opt => opt.value === formData.value.repeat);
return option ? option.label : '不重复';
});
// 初始化日期
2025-11-03 10:52:56 +08:00
const initDates = (dateStr = '') => {
// 如果没有传入日期参数,使用今天
if (!dateStr) {
2025-11-03 09:47:11 +08:00
const today = new Date();
dateStr = today.toISOString().slice(0, 10);
}
formData.value.startDate = dateStr;
formData.value.endDate = dateStr;
// 设置默认时间为当前时间到一小时后(如果不是全天)
if (!formData.value.allDay) {
const today = new Date();
formData.value.startHour = today.getHours();
formData.value.startMin = Math.ceil(today.getMinutes() / 5) * 5; // 向上取整到5的倍数
const endTime = new Date(today);
endTime.setHours(today.getHours() + 1);
formData.value.endHour = endTime.getHours();
formData.value.endMin = endTime.getMinutes();
startPickerIndex.value = [formData.value.startHour, formData.value.startMin];
endPickerIndex.value = [formData.value.endHour, formData.value.endMin];
}
};
// 处理开始日期确认
const handleStartDateConfirm = (e) => {
const formattedDate = formatDateToYYYYMMDD(e);
if (formattedDate) {
formData.value.startDate = formattedDate;
// 如果结束日期未设置或早于开始日期,更新结束日期
if (!formData.value.endDate || formData.value.endDate < formData.value.startDate) {
formData.value.endDate = formData.value.startDate;
}
}
};
// 处理开始时间变化
const handleStartTimeChange = (e) => {
const [hourIndex, minIndex] = e.detail.value;
formData.value.startHour = hourIndex;
formData.value.startMin = minIndex;
startPickerIndex.value = [hourIndex, minIndex];
// 如果结束时间早于开始时间,自动调整结束时间
if (formData.value.endDate === formData.value.startDate) {
const startTotalMin = formData.value.startHour * 60 + formData.value.startMin;
const endTotalMin = formData.value.endHour * 60 + formData.value.endMin;
if (endTotalMin <= startTotalMin) {
const newEndTotalMin = startTotalMin + 60; // 默认延长1小时
formData.value.endHour = Math.floor(newEndTotalMin / 60) % 24;
formData.value.endMin = newEndTotalMin % 60;
endPickerIndex.value = [formData.value.endHour, formData.value.endMin];
}
}
};
// 处理结束日期确认
const handleEndDateConfirm = (e) => {
const formattedDate = formatDateToYYYYMMDD(e);
if (formattedDate) {
formData.value.endDate = formattedDate;
}
};
// 处理结束时间变化
const handleEndTimeChange = (e) => {
const [hourIndex, minIndex] = e.detail.value;
formData.value.endHour = hourIndex;
formData.value.endMin = minIndex;
endPickerIndex.value = [hourIndex, minIndex];
};
// 格式化日期为 YYYY-MM-DD复用首页的逻辑
function formatDateToYYYYMMDD(dateInput) {
if (!dateInput) return '';
let dateStr = '';
if (typeof dateInput === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateInput)) {
return dateInput;
}
if (typeof dateInput === 'string') {
dateStr = dateInput;
} else if (dateInput?.date) {
dateStr = dateInput.date;
} else if (dateInput?.value) {
dateStr = dateInput.value;
} else if (Array.isArray(dateInput) && dateInput.length > 0) {
dateStr = typeof dateInput[0] === 'string' ? dateInput[0] : (dateInput[0]?.date || dateInput[0]?.value || '');
}
if (!dateStr) return '';
const date = new Date(dateStr);
if (!isNaN(date.getTime())) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
if (/^\d{4}-\d{2}-\d{2}/.test(dateStr)) {
return dateStr.slice(0, 10);
}
return '';
}
// 切换全天
const toggleAllDay = () => {
formData.value.allDay = !formData.value.allDay;
if (formData.value.allDay) {
formData.value.startHour = 0;
formData.value.startMin = 0;
formData.value.endHour = 23;
formData.value.endMin = 59;
startPickerIndex.value = [0, 0];
endPickerIndex.value = [23, 59];
}
};
// 选择颜色
const selectColor = (color) => {
formData.value.color = color;
};
2025-11-03 11:29:56 +08:00
// 打开重复选择弹窗
const openRepeatPicker = () => {
// 打开弹窗时,将当前值复制到临时变量
tempRepeat.value = formData.value.repeat;
showRepeatPicker.value = true;
};
// 取消重复选择
const handleCancelRepeat = () => {
// 恢复原始值
tempRepeat.value = formData.value.repeat;
showRepeatPicker.value = false;
};
// 确认重复选择
const handleConfirmRepeat = () => {
// 将临时变量的值更新到表单数据
formData.value.repeat = tempRepeat.value;
showRepeatPicker.value = false;
2025-11-03 09:47:11 +08:00
};
// 选择提醒
const selectReminder = (value) => {
formData.value.reminder = value;
};
// 取消
const handleCancel = () => {
uni.navigateBack();
};
// 保存
const handleSave = () => {
if (!formData.value.title.trim()) {
uni.showToast({
title: '请输入标题',
icon: 'none'
});
return;
}
// 准备返回的数据
const eventData = {
title: formData.value.title,
date: formData.value.startDate,
startHour: formData.value.startHour,
startMin: formData.value.startMin,
endHour: formData.value.endHour,
endMin: formData.value.endMin,
color: formData.value.color,
allDay: formData.value.allDay,
repeat: formData.value.repeat,
description: formData.value.description,
reminder: formData.value.reminder
};
2025-11-03 10:52:56 +08:00
// 使用全局存储传递数据,在首页的 onShow 中读取
uni.setStorageSync('newEventData', eventData);
2025-11-03 09:47:11 +08:00
// 返回上一页
uni.navigateBack();
};
2025-11-03 10:52:56 +08:00
// 页面加载时接收参数
onLoad((options) => {
const dateStr = options?.date || '';
initDates(dateStr);
2025-11-03 09:47:11 +08:00
});
</script>
<style lang="scss" scoped>
.add-event-page {
min-height: 100vh;
background: #fff;
}
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background: #fff;
border-bottom: 1px solid #e5e5e5;
padding-top: var(--status-bar-height, 0);
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
}
.nav-btn {
font-size: 16px;
color: #333;
padding: 8px;
}
.nav-btn-primary {
color: #2885ff;
}
.nav-title {
font-size: 17px;
font-weight: 600;
color: #333;
}
.content-scroll {
margin-top: calc(var(--status-bar-height, 0) + 45px);
height: calc(100vh - var(--status-bar-height, 0) - 45px);
}
.form-item {
padding: 16px;
border-bottom: 1px solid #f5f5f5;
display: flex;
align-items: center;
}
.title-input {
width: 100%;
font-size: 18px;
padding: 8px 0;
}
.time-section {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 16px;
}
.time-item {
flex: 1;
display: flex;
align-items: center;
gap: 12px;
}
.time-icon {
font-size: 20px;
}
.time-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.time-value {
font-size: 24px;
font-weight: 600;
color: #333;
}
.time-date {
font-size: 12px;
color: #999;
}
.time-separator {
margin: 0 16px;
font-size: 20px;
color: #ccc;
}
.arrow {
font-size: 20px;
color: #ccc;
margin-left: auto;
}
.switch-item {
justify-content: space-between;
}
.label {
font-size: 16px;
color: #333;
}
.clickable-item {
cursor: pointer;
}
.desc-icon {
font-size: 20px;
margin-right: 12px;
}
.desc-content {
flex: 1;
}
.desc-text {
font-size: 16px;
color: #333;
}
.desc-placeholder {
font-size: 16px;
color: #999;
}
.color-dot {
width: 20px;
height: 20px;
border-radius: 50%;
margin-right: 12px;
}
.reminder-icon {
font-size: 20px;
margin-right: 12px;
}
.reminder-text {
flex: 1;
font-size: 16px;
color: #333;
}
/* 弹窗样式 */
.modal-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.modal-content {
background: #fff;
border-radius: 12px;
padding: 24px;
width: 80%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
}
.modal-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
}
.desc-textarea {
width: 100%;
min-height: 120px;
padding: 12px;
border: 1px solid #e5e5e5;
border-radius: 8px;
font-size: 16px;
}
.color-options {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: center;
margin-bottom: 20px;
}
.color-option {
width: 50px;
height: 50px;
border-radius: 50%;
border: 3px solid transparent;
cursor: pointer;
transition: all 0.3s;
}
.color-option.active {
border-color: #2885ff;
transform: scale(1.1);
}
.repeat-options,
.reminder-options {
margin-bottom: 20px;
}
.repeat-option,
.reminder-option {
padding: 16px;
border-bottom: 1px solid #f5f5f5;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
.repeat-option:last-child,
.reminder-option:last-child {
border-bottom: none;
}
.repeat-option.active,
.reminder-option.active {
color: #2885ff;
}
.check {
color: #2885ff;
font-size: 18px;
}
.modal-buttons {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 20px;
}
.modal-btn {
padding: 10px 24px;
border: none;
background: #f5f5f5;
border-radius: 8px;
font-size: 16px;
color: #333;
}
.modal-btn.primary {
background: #2885ff;
color: #fff;
}
.datetime-modal {
max-width: 90%;
}
.datetime-content {
max-height: 60vh;
overflow-y: auto;
}
.date-section {
margin-bottom: 20px;
}
.section-label {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.time-section-picker {
margin-top: 20px;
}
.picker-display {
padding: 12px;
border: 1px solid #e5e5e5;
border-radius: 8px;
text-align: center;
font-size: 16px;
color: #333;
}
</style>