2025-11-05 16:05:34 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="apply-delay-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"></text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 内容区域 -->
|
|
|
|
|
|
<scroll-view class="content-scroll" scroll-y>
|
|
|
|
|
|
<view class="scroll-content">
|
|
|
|
|
|
<!-- 申请说明输入框 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<view class="input-wrapper">
|
|
|
|
|
|
<text class="input-icon">📄</text>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="formData.description"
|
|
|
|
|
|
class="description-input"
|
|
|
|
|
|
placeholder="输入申请说明"
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 日历选择器 -->
|
|
|
|
|
|
<view class="calendar-section">
|
|
|
|
|
|
<!-- 月份切换栏 -->
|
|
|
|
|
|
<view class="month-header">
|
|
|
|
|
|
<view class="month-nav-btn" @click="prevMonth">
|
|
|
|
|
|
<text>‹</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="month-title">{{ currentYear }}年{{ currentMonth }}月</view>
|
|
|
|
|
|
<view class="month-nav-btn" @click="nextMonth">
|
|
|
|
|
|
<text>›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 星期标题 -->
|
|
|
|
|
|
<view class="weekdays">
|
|
|
|
|
|
<view class="weekday-item" v-for="day in weekdays" :key="day">{{ day }}</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 日历网格 -->
|
|
|
|
|
|
<view class="calendar-grid">
|
|
|
|
|
|
<view
|
|
|
|
|
|
class="calendar-day"
|
|
|
|
|
|
v-for="(dayObj, index) in currentMonthDays"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
:class="{
|
|
|
|
|
|
'other-month': !dayObj.isCurrentMonth,
|
|
|
|
|
|
'today': isToday(dayObj),
|
|
|
|
|
|
'selected': isSelected(dayObj)
|
|
|
|
|
|
}"
|
|
|
|
|
|
@click="selectDate(dayObj)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<text class="day-number">{{ dayObj.day }}</text>
|
|
|
|
|
|
<view class="today-mark" v-if="isToday(dayObj) && !isSelected(dayObj)">
|
|
|
|
|
|
<text>今</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 具体时间选择 -->
|
|
|
|
|
|
<view class="form-item time-item" @click="openTimePicker">
|
|
|
|
|
|
<text class="time-label">具体时间</text>
|
|
|
|
|
|
<view class="time-value-wrapper">
|
|
|
|
|
|
<text class="time-value">{{ selectedTime }}</text>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 时间选择器弹窗 -->
|
|
|
|
|
|
<view v-if="showTimePicker" class="modal-mask" @click="closeTimePicker">
|
|
|
|
|
|
<view class="modal-content time-modal" @click.stop>
|
|
|
|
|
|
<view class="modal-title">选择时间</view>
|
|
|
|
|
|
<picker
|
|
|
|
|
|
mode="multiSelector"
|
|
|
|
|
|
:value="timePickerIndex"
|
|
|
|
|
|
:range="timePickerRange"
|
|
|
|
|
|
@change="handleTimeChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="picker-display">
|
|
|
|
|
|
{{ selectedTime }}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</picker>
|
|
|
|
|
|
<view class="modal-buttons">
|
|
|
|
|
|
<button class="modal-btn" @click="closeTimePicker">取消</button>
|
|
|
|
|
|
<button class="modal-btn primary" @click="closeTimePicker">确定</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 确认提交按钮 -->
|
|
|
|
|
|
<view class="submit-button-wrapper">
|
|
|
|
|
|
<uv-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="normal"
|
|
|
|
|
|
:disabled="!canSubmit"
|
|
|
|
|
|
@click="handleSubmit"
|
|
|
|
|
|
>
|
|
|
|
|
|
确认提交
|
|
|
|
|
|
</uv-button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-11-13 16:33:00 +08:00
|
|
|
|
import { ref, computed } from 'vue';
|
2025-11-05 16:05:34 +08:00
|
|
|
|
import { onLoad } from '@dcloudio/uni-app';
|
2025-11-13 16:33:00 +08:00
|
|
|
|
import { applyTaskDelay } from '@/api';
|
2025-11-05 16:05:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
|
const formData = ref({
|
|
|
|
|
|
description: '',
|
|
|
|
|
|
selectedDate: '',
|
|
|
|
|
|
selectedTime: '18:00'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 任务ID
|
|
|
|
|
|
const taskId = ref(null);
|
|
|
|
|
|
|
|
|
|
|
|
// 当前显示的年月
|
|
|
|
|
|
const currentYear = ref(new Date().getFullYear());
|
|
|
|
|
|
const currentMonth = ref(new Date().getMonth() + 1);
|
|
|
|
|
|
|
|
|
|
|
|
// 选中的日期
|
|
|
|
|
|
const selectedDate = ref(new Date().toISOString().slice(0, 10));
|
|
|
|
|
|
|
|
|
|
|
|
// 星期标题
|
|
|
|
|
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
|
|
|
|
|
|
|
|
|
|
|
// 时间选择器
|
|
|
|
|
|
const showTimePicker = ref(false);
|
|
|
|
|
|
const timePickerIndex = ref([18, 0]); // [小时, 分钟]
|
|
|
|
|
|
const timePickerRange = ref([
|
|
|
|
|
|
Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')),
|
|
|
|
|
|
Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0'))
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取月份的第一天是星期几(0-6,0是星期日)
|
|
|
|
|
|
const getFirstDayOfMonth = (year, month) => {
|
|
|
|
|
|
return new Date(year, month - 1, 1).getDay();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取月份的天数
|
|
|
|
|
|
const getDaysInMonth = (year, month) => {
|
|
|
|
|
|
return new Date(year, month, 0).getDate();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 生成月份的日期数组(包含前后月份的填充日期)
|
|
|
|
|
|
const generateMonthDays = (year, month) => {
|
|
|
|
|
|
const firstDay = getFirstDayOfMonth(year, month);
|
|
|
|
|
|
const daysInMonth = getDaysInMonth(year, month);
|
|
|
|
|
|
const days = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 计算上个月的最后几天
|
|
|
|
|
|
const prevMonth = month === 1 ? 12 : month - 1;
|
|
|
|
|
|
const prevYear = month === 1 ? year - 1 : year;
|
|
|
|
|
|
const prevMonthDays = getDaysInMonth(prevYear, prevMonth);
|
|
|
|
|
|
|
|
|
|
|
|
// 填充前面月份的日期(从最后几天开始)
|
|
|
|
|
|
if (firstDay > 0) {
|
|
|
|
|
|
for (let i = firstDay - 1; i >= 0; i--) {
|
|
|
|
|
|
days.push({
|
|
|
|
|
|
day: prevMonthDays - i,
|
|
|
|
|
|
year: prevYear,
|
|
|
|
|
|
month: prevMonth,
|
|
|
|
|
|
isCurrentMonth: false
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 填充当前月份的日期
|
|
|
|
|
|
for (let i = 1; i <= daysInMonth; i++) {
|
|
|
|
|
|
days.push({
|
|
|
|
|
|
day: i,
|
|
|
|
|
|
year: year,
|
|
|
|
|
|
month: month,
|
|
|
|
|
|
isCurrentMonth: true
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 填充后面月份的日期(确保有6行,即42个格子)
|
|
|
|
|
|
const totalDays = days.length;
|
|
|
|
|
|
const remainingDays = 42 - totalDays;
|
|
|
|
|
|
const nextMonth = month === 12 ? 1 : month + 1;
|
|
|
|
|
|
const nextYear = month === 12 ? year + 1 : year;
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i <= remainingDays; i++) {
|
|
|
|
|
|
days.push({
|
|
|
|
|
|
day: i,
|
|
|
|
|
|
year: nextYear,
|
|
|
|
|
|
month: nextMonth,
|
|
|
|
|
|
isCurrentMonth: false
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return days;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 当前月份的日期数组
|
|
|
|
|
|
const currentMonthDays = computed(() => {
|
|
|
|
|
|
return generateMonthDays(currentYear.value, currentMonth.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否是今天
|
|
|
|
|
|
const isToday = (dayObj) => {
|
|
|
|
|
|
if (!dayObj || !dayObj.day) return false;
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
|
return (
|
|
|
|
|
|
dayObj.year === today.getFullYear() &&
|
|
|
|
|
|
dayObj.month === today.getMonth() + 1 &&
|
|
|
|
|
|
dayObj.day === today.getDate()
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否被选中
|
|
|
|
|
|
const isSelected = (dayObj) => {
|
|
|
|
|
|
if (!dayObj || !dayObj.day) return false;
|
|
|
|
|
|
const selected = new Date(selectedDate.value);
|
|
|
|
|
|
return (
|
|
|
|
|
|
dayObj.year === selected.getFullYear() &&
|
|
|
|
|
|
dayObj.month === selected.getMonth() + 1 &&
|
|
|
|
|
|
dayObj.day === selected.getDate()
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化日期为 YYYY-MM-DD
|
|
|
|
|
|
const formatDate = (year, month, day) => {
|
|
|
|
|
|
const m = String(month).padStart(2, '0');
|
|
|
|
|
|
const d = String(day).padStart(2, '0');
|
|
|
|
|
|
return `${year}-${m}-${d}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选择日期
|
|
|
|
|
|
const selectDate = (dayObj) => {
|
|
|
|
|
|
if (!dayObj || !dayObj.day) return;
|
|
|
|
|
|
const dateStr = formatDate(dayObj.year, dayObj.month, dayObj.day);
|
|
|
|
|
|
selectedDate.value = dateStr;
|
|
|
|
|
|
formData.value.selectedDate = dateStr;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果选择的是其他月份的日期,切换到对应月份
|
|
|
|
|
|
if (!dayObj.isCurrentMonth) {
|
|
|
|
|
|
currentYear.value = dayObj.year;
|
|
|
|
|
|
currentMonth.value = dayObj.month;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 上一个月
|
|
|
|
|
|
const prevMonth = () => {
|
|
|
|
|
|
if (currentMonth.value === 1) {
|
|
|
|
|
|
currentMonth.value = 12;
|
|
|
|
|
|
currentYear.value -= 1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
currentMonth.value -= 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 下一个月
|
|
|
|
|
|
const nextMonth = () => {
|
|
|
|
|
|
if (currentMonth.value === 12) {
|
|
|
|
|
|
currentMonth.value = 1;
|
|
|
|
|
|
currentYear.value += 1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
currentMonth.value += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
|
|
const formatTime = (hour, minute) => {
|
|
|
|
|
|
return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选中的时间显示
|
|
|
|
|
|
const selectedTime = computed(() => {
|
|
|
|
|
|
return formData.value.selectedTime;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-13 16:33:00 +08:00
|
|
|
|
// 组合延期时间(YYYY-MM-DD HH:mm:ss)
|
|
|
|
|
|
const buildExpireDateTime = () => {
|
|
|
|
|
|
if (!formData.value.selectedDate) return '';
|
|
|
|
|
|
const rawTime = formData.value.selectedTime || '00:00';
|
|
|
|
|
|
const hasSeconds = /^\d{2}:\d{2}:\d{2}$/.test(rawTime);
|
|
|
|
|
|
const timeSegment = hasSeconds ? rawTime : `${rawTime}:00`;
|
|
|
|
|
|
return `${formData.value.selectedDate} ${timeSegment}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-05 16:05:34 +08:00
|
|
|
|
// 打开时间选择器
|
|
|
|
|
|
const openTimePicker = () => {
|
|
|
|
|
|
const timeParts = formData.value.selectedTime.split(':');
|
|
|
|
|
|
timePickerIndex.value = [parseInt(timeParts[0]), parseInt(timeParts[1])];
|
|
|
|
|
|
showTimePicker.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭时间选择器
|
|
|
|
|
|
const closeTimePicker = () => {
|
|
|
|
|
|
showTimePicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 时间选择变化
|
|
|
|
|
|
const handleTimeChange = (e) => {
|
|
|
|
|
|
const [hourIndex, minuteIndex] = e.detail.value;
|
|
|
|
|
|
const hour = parseInt(timePickerRange.value[0][hourIndex]);
|
|
|
|
|
|
const minute = parseInt(timePickerRange.value[1][minuteIndex]);
|
|
|
|
|
|
formData.value.selectedTime = formatTime(hour, minute);
|
|
|
|
|
|
timePickerIndex.value = [hourIndex, minuteIndex];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 是否可以提交
|
|
|
|
|
|
const canSubmit = computed(() => {
|
|
|
|
|
|
return formData.value.description.trim() !== '' && formData.value.selectedDate !== '';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载
|
|
|
|
|
|
onLoad((options) => {
|
|
|
|
|
|
taskId.value = options.taskId || options.id;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化选中日期为今天
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
|
selectedDate.value = formatDate(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
|
|
|
|
formData.value.selectedDate = selectedDate.value;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 取消
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提示',
|
|
|
|
|
|
content: '确定要取消申请吗?未保存的内容将丢失',
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
|
uni.navigateBack();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 提交申请
|
2025-11-13 16:33:00 +08:00
|
|
|
|
const handleSubmit = async () => {
|
2025-11-05 16:05:34 +08:00
|
|
|
|
if (!canSubmit.value) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '请填写申请说明',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 16:33:00 +08:00
|
|
|
|
if (!taskId.value) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '缺少任务ID',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const expireDateTime = buildExpireDateTime();
|
|
|
|
|
|
const payload = {
|
|
|
|
|
|
bstId: taskId.value,
|
|
|
|
|
|
bstType: 'UPDATE_TASK',
|
|
|
|
|
|
createRemark: formData.value.description.trim(),
|
|
|
|
|
|
data: JSON.stringify({
|
|
|
|
|
|
expireTime: expireDateTime
|
|
|
|
|
|
})
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-05 16:05:34 +08:00
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: '提交中...'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-13 16:33:00 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await applyTaskDelay(payload);
|
2025-11-05 16:05:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 将延期申请数据存储到本地,供任务详情页使用
|
|
|
|
|
|
uni.setStorageSync('delayApplication', {
|
|
|
|
|
|
taskId: taskId.value,
|
2025-11-13 16:33:00 +08:00
|
|
|
|
description: payload.createRemark,
|
|
|
|
|
|
delayDate: formData.value.selectedDate,
|
|
|
|
|
|
delayTime: formData.value.selectedTime,
|
|
|
|
|
|
expireTime: expireDateTime
|
2025-11-05 16:05:34 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-13 16:33:00 +08:00
|
|
|
|
uni.hideLoading();
|
2025-11-05 16:05:34 +08:00
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '申请提交成功',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.navigateBack();
|
|
|
|
|
|
}, 1500);
|
2025-11-13 16:33:00 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
console.error('申请延期失败:', error);
|
|
|
|
|
|
const message = error?.data?.message || error?.errMsg || '申请失败,请稍后再试';
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: message,
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-11-05 16:05:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.apply-delay-page {
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.custom-navbar {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
padding-top: var(--status-bar-height);
|
|
|
|
|
|
border-bottom: 1px solid #e5e5e5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.navbar-content {
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-btn {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
min-width: 40px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-title {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content-scroll {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.scroll-content {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-item {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.input-wrapper {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.input-icon {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.description-input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.calendar-section {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.month-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.month-nav-btn {
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.month-title {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.weekdays {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.weekday-item {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.calendar-grid {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.calendar-day {
|
|
|
|
|
|
width: calc(100% / 7);
|
|
|
|
|
|
aspect-ratio: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.calendar-day.other-month {
|
|
|
|
|
|
opacity: 0.3;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.calendar-day.today {
|
|
|
|
|
|
.day-number {
|
|
|
|
|
|
color: #2885ff;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.calendar-day.selected {
|
|
|
|
|
|
background-color: #2885ff;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
|
|
|
|
|
|
.day-number {
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.today-mark {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.day-number {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.today-mark {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 2px;
|
|
|
|
|
|
right: 2px;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
color: #2885ff;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-label {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-value-wrapper {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-value {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.arrow {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-mask {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-content {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 16px 16px 0 0;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-modal {
|
|
|
|
|
|
padding-bottom: 40px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-title {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.picker-display {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-buttons {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-btn {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
line-height: 44px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-btn.primary {
|
|
|
|
|
|
background-color: #2885ff;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.submit-button-wrapper {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-top: 1px solid #e5e5e5;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|