逾期申请
This commit is contained in:
parent
3d0ea8cc69
commit
2913d96097
|
|
@ -61,6 +61,13 @@
|
|||
"navigationBarTitleText": "提交详情"
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/apply-delay/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请详情",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
|||
652
pages/apply-delay/index.vue
Normal file
652
pages/apply-delay/index.vue
Normal file
|
|
@ -0,0 +1,652 @@
|
|||
<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-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;
|
||||
});
|
||||
|
||||
// 打开时间选择器
|
||||
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>
|
||||
|
||||
|
|
@ -326,19 +326,8 @@ const submitTask = () => {
|
|||
|
||||
// 申请延期
|
||||
const applyDelay = () => {
|
||||
uni.showModal({
|
||||
title: '申请延期',
|
||||
content: '确定要申请延期吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
console.log("申请延期", task.value.id);
|
||||
uni.showToast({
|
||||
title: '延期申请已提交',
|
||||
icon: 'success'
|
||||
});
|
||||
// 可以在这里添加申请延期的API调用
|
||||
}
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/apply-delay/index?taskId=${task.value.id || ''}`
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user