OfficeSystem/pages/task/apply-delay/index.vue
2025-11-07 11:40:13 +08:00

653 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>
import { ref, computed, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
// 表单数据
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-60是星期日
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;
});
// 打开时间选择器
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();
}
}
});
};
// 提交申请
const handleSubmit = () => {
if (!canSubmit.value) {
uni.showToast({
title: '请填写申请说明',
icon: 'none'
});
return;
}
uni.showLoading({
title: '提交中...'
});
// 准备提交数据
const submitData = {
taskId: taskId.value,
description: formData.value.description.trim(),
delayDate: formData.value.selectedDate,
delayTime: formData.value.selectedTime
};
// TODO: 调用提交接口
// 实际使用时应该调用API接口上传数据
// uni.request({
// url: '/api/task/delay/apply',
// method: 'POST',
// data: submitData,
// success: (res) => {
// // 处理成功响应
// },
// fail: (err) => {
// // 处理错误
// }
// });
// 模拟提交请求
setTimeout(() => {
uni.hideLoading();
// 将延期申请数据存储到本地,供任务详情页使用
uni.setStorageSync('delayApplication', {
taskId: taskId.value,
description: submitData.description,
delayDate: submitData.delayDate,
delayTime: submitData.delayTime
});
uni.showToast({
title: '申请提交成功',
icon: 'success'
});
// 延迟返回,让用户看到成功提示
setTimeout(() => {
uni.navigateBack();
}, 1500);
}, 1000);
};
</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>