|
|
|
<template>
|
|
|
|
<view class="calendar-page">
|
|
|
|
<!-- 顶部栏:年月选择器和功能按钮 -->
|
|
|
|
<!-- 顶部操作栏 -->
|
|
|
|
<view class="top-bar">
|
|
|
|
<view class="month-picker">
|
|
|
|
<uni-datetime-picker type="date" v-model="currentMonth" :clear-icon="false" />
|
|
|
|
<up-icon name="arrow-down" color="#333" size="15"></up-icon>
|
|
|
|
</view>
|
|
|
|
<!-- 年月选择胶囊 -->
|
|
|
|
<picker mode="date" fields="month" :value="currentMonth" @change="onMonthChange">
|
|
|
|
<view class="month-picker-pill">
|
|
|
|
<text class="month-text">{{ displayMonthText }}</text>
|
|
|
|
<up-icon name="arrow-down-fill" color="#333333" size="10"></up-icon>
|
|
|
|
</view>
|
|
|
|
</picker>
|
|
|
|
|
|
|
|
<!-- 说明按钮 -->
|
|
|
|
<view class="action-buttons">
|
|
|
|
<button class="action-btn" @click="ShowExplainPopup = true">说明</button>
|
|
|
|
<up-button text="说明" type="info" plain shape="circle" size="mini"
|
|
|
|
customStyle="padding: 0 24rpx; height: 56rpx; font-size: 24rpx; border-color: #dcdfe6; color: #606266;"
|
|
|
|
@click="showExplainPopup = true" />
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<!-- 日历网格 -->
|
|
|
|
<view class="calendar-grid">
|
|
|
|
|
|
|
|
<!-- 日历卡片容器 -->
|
|
|
|
<view class="calendar-card">
|
|
|
|
<!-- 星期表头 -->
|
|
|
|
<view class="week-header">
|
|
|
|
<text v-for="(day, index) in weekDays" :key="index" class="week-day">
|
|
|
|
{{ day }}
|
|
|
|
</text>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 日期网格 (动态 5行 或 6行) -->
|
|
|
|
<view class="date-grid">
|
|
|
|
<view v-for="(date, index) in dates" :key="index" class="date-cell" :class="{ today: date.today }"
|
|
|
|
@tap="selectDate(date)">
|
|
|
|
<text v-if="!date.today" class="date-number">{{ date.day }}</text>
|
|
|
|
<text v-else class="today-text">今</text>
|
|
|
|
<view v-for="(date, index) in dates" :key="index" class="date-cell" :class="{
|
|
|
|
|
|
|
|
'has-data': date.planList.length || date.noteList.length || (date.weight && date.weight > 0)
|
|
|
|
}" :style="{ backgroundColor: getCellBgColor(date) }" @tap="selectDate(date)">
|
|
|
|
<!-- 日期数字 / 今日徽章 -->
|
|
|
|
<view class="date-header">
|
|
|
|
<text v-if="date.today" class="today-badge">今</text>
|
|
|
|
<text v-else class="date-number">
|
|
|
|
{{ date.day }}
|
|
|
|
</text>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 内容显示区域 -->
|
|
|
|
<!-- 计划/容量数据列表 -->
|
|
|
|
<view class="plan-list"
|
|
|
|
v-if="date.planList.length || date.noteList.length || (date.weight && date.weight > 0)">
|
|
|
|
<template v-for="(item, idx) in getVisibleItems(date).items" :key="idx">
|
|
|
|
<view class="plan-name" :class="{
|
|
|
|
'weight-item': item.type === 'weight',
|
|
|
|
'note-item': item.type === 'note'
|
|
|
|
}" :style="item.bg ? { backgroundColor: item.bg } : ''">
|
|
|
|
{{ item.text }}
|
|
|
|
<template v-for="(item, idx) in getCellVisibleItems(date).items" :key="idx">
|
|
|
|
<view class="plan-tag" :class="`tag-type-${item.type}`"
|
|
|
|
:style="item.bg ? { backgroundColor: item.bg } : {}">
|
|
|
|
<text class="tag-text">{{ item.text }}</text>
|
|
|
|
</view>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<!-- 统一只显示一个 + 剩余总数 -->
|
|
|
|
<text v-if="getVisibleItems(date).remaining > 0" class="plan-extra">
|
|
|
|
+{{ getVisibleItems(date).remaining }}
|
|
|
|
</text>
|
|
|
|
<!-- 溢出剩余条数 -->
|
|
|
|
<view v-if="getCellVisibleItems(date).remaining > 0" class="plan-extra">
|
|
|
|
+{{ getCellVisibleItems(date).remaining }}
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<!-- <view class="gridCellContent" v-if="ShowGridCellPopup && selectedDate">
|
|
|
|
<GridCellContentPopup :date="selectedDate" @close="ShowGridCellPopup = false" />
|
|
|
|
</view> -->
|
|
|
|
|
|
|
|
<!-- 单元格详情弹窗 -->
|
|
|
|
<GridCellContentPopup ref="gridPopupRef" :date="selectedDate" @refreshCalendar="handleRefreshCalendar" />
|
|
|
|
|
|
|
|
<!-- 日历说明弹窗 -->
|
|
|
|
<view class="explain-popup-overlay" v-if="ShowExplainPopup" @click.self="ShowExplainPopup = false">
|
|
|
|
<view class="explain-popup">
|
|
|
|
<!-- 日历图标说明弹窗 -->
|
|
|
|
<up-popup :show="showExplainPopup" mode="bottom" round="24rpx" :safeAreaInsetBottom="true"
|
|
|
|
@close="showExplainPopup = false">
|
|
|
|
<view class="explain-popup-content">
|
|
|
|
<view class="explain-header">
|
|
|
|
<text class="explain-title">日历说明</text>
|
|
|
|
<view class="close-btn" @click="ShowExplainPopup = false">
|
|
|
|
<text>×</text>
|
|
|
|
</view>
|
|
|
|
<text class="explain-title">日历图标说明</text>
|
|
|
|
<up-icon name="close" color="#909399" size="18" @click="showExplainPopup = false" />
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="explain-list">
|
|
|
|
<view class="explain-item">
|
|
|
|
<view class="color-block blue"></view>
|
|
|
|
<text class="item-label">分钟</text>
|
|
|
|
<text class="item-desc">在场馆的运动时长</text>
|
|
|
|
<view class="item-info">
|
|
|
|
<text class="item-label">时长</text>
|
|
|
|
<text class="item-desc">在场馆的实际运动时长</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="explain-item">
|
|
|
|
<view class="color-block green"></view>
|
|
|
|
<text class="item-label">容量</text>
|
|
|
|
<text class="item-desc">力量训练的总重量</text>
|
|
|
|
<view class="item-info">
|
|
|
|
<text class="item-label">容量</text>
|
|
|
|
<text class="item-desc">力量训练累计产生的总负重重量</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="explain-item">
|
|
|
|
<view class="color-block yellow"></view>
|
|
|
|
<text class="item-label">课程</text>
|
|
|
|
<text class="item-desc">训记或已结课的线下课程</text>
|
|
|
|
<view class="item-info">
|
|
|
|
<text class="item-label">课程</text>
|
|
|
|
<text class="item-desc">训记完成或已结课的线下课程</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="explain-item">
|
|
|
|
<view class="color-block gray"></view>
|
|
|
|
<text class="item-label">课程</text>
|
|
|
|
<text class="item-desc">训记排课或待上课的线下课程</text>
|
|
|
|
<view class="item-info">
|
|
|
|
<text class="item-label">排课</text>
|
|
|
|
<text class="item-desc">训记排课计划或待上课的课程</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</up-popup>
|
|
|
|
</view>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
import { ref, watch, computed, nextTick } from 'vue';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
import DailyTemplateApi from '@/sheep/api/calendar/date';
|
|
|
|
import GridCellContentPopup from '@/pages/xunji/components/rili-components/grid-cell-content-popup.vue'
|
|
|
|
import GridCellContentPopup from '@/pages/xunji/components/rili-components/grid-cell-content-popup.vue';
|
|
|
|
|
|
|
|
// ====== 响应式数据 ======
|
|
|
|
// 常量定义
|
|
|
|
const MAX_CELL_LINES = 3;
|
|
|
|
const weekDays = ref(['一', '二', '三', '四', '五', '六', '日']);
|
|
|
|
|
|
|
|
// 响应式状态
|
|
|
|
const dates = ref([]);
|
|
|
|
const today = new Date();
|
|
|
|
const ShowExplainPopup = ref(false)
|
|
|
|
const showExplainPopup = ref(false);
|
|
|
|
const selectedDate = ref('');
|
|
|
|
// const ShowGridCellPopup = ref(false)
|
|
|
|
const gridPopupRef = ref(null);
|
|
|
|
|
|
|
|
// 自动获取当前年月
|
|
|
|
const now = new Date();
|
|
|
|
const currentMonth = ref(
|
|
|
|
`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
|
|
|
);
|
|
|
|
const currentMonth = ref(dayjs().format('YYYY-MM-DD'));
|
|
|
|
|
|
|
|
// ====== 工具函数 ======
|
|
|
|
const formatDateKey = (y, m, d) => {
|
|
|
|
return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
|
|
};
|
|
|
|
// 顶部年月显示
|
|
|
|
const displayMonthText = computed(() => {
|
|
|
|
return dayjs(currentMonth.value).format('YYYY年M月');
|
|
|
|
});
|
|
|
|
|
|
|
|
const arrayToDateStr = (arr) => {
|
|
|
|
if (!Array.isArray(arr) || arr.length !== 3) return null;
|
|
|
|
const [y, m, d] = arr;
|
|
|
|
return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
|
|
// 年月 Picker 触发
|
|
|
|
const onMonthChange = (e) => {
|
|
|
|
if (e.detail && e.detail.value) {
|
|
|
|
currentMonth.value = dayjs(e.detail.value).startOf('month').format('YYYY-MM-DD');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// ====== 生成日历数据 ======
|
|
|
|
const generateCalendar = (year, month) => {
|
|
|
|
const firstDay = new Date(year, month - 1, 1);
|
|
|
|
const lastDay = new Date(year, month, 0).getDate();
|
|
|
|
let startWeekDay = firstDay.getDay();
|
|
|
|
const adjustedStart = startWeekDay === 0 ? 6 : startWeekDay - 1;
|
|
|
|
// ====== 计算每个日期格子的背景颜色 ======
|
|
|
|
const getCellBgColor = (date) => {
|
|
|
|
|
|
|
|
const calendarDates = [];
|
|
|
|
|
|
|
|
// 上月填充
|
|
|
|
const prevMonthLastDay = new Date(year, month - 1, 0).getDate();
|
|
|
|
for (let i = adjustedStart; i > 0; i--) {
|
|
|
|
const day = prevMonthLastDay - i + 1;
|
|
|
|
const y = month === 1 ? year - 1 : year;
|
|
|
|
const m = month === 1 ? 12 : month - 1;
|
|
|
|
calendarDates.push({
|
|
|
|
day,
|
|
|
|
today: false,
|
|
|
|
dateKey: formatDateKey(y, m, day),
|
|
|
|
});
|
|
|
|
// 2. 如果存在训练计划/备注/重量,优先采用训练本身的背景色
|
|
|
|
if (date.planList && date.planList.length > 0 && date.planList[0].bg) {
|
|
|
|
return date.planList[0].bg;
|
|
|
|
}
|
|
|
|
if (date.noteList && date.noteList.length > 0 && date.noteList[0].backgroundColor) {
|
|
|
|
return date.noteList[0].backgroundColor;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 本月
|
|
|
|
for (let d = 1; d <= lastDay; d++) {
|
|
|
|
const isToday =
|
|
|
|
year === today.getFullYear() &&
|
|
|
|
month === today.getMonth() + 1 &&
|
|
|
|
d === today.getDate();
|
|
|
|
calendarDates.push({
|
|
|
|
day: d,
|
|
|
|
today: isToday,
|
|
|
|
dateKey: formatDateKey(year, month, d),
|
|
|
|
});
|
|
|
|
// 3. 有容量负重但无特定背景色:赋予柔和浅绿
|
|
|
|
if (date.weight && date.weight > 0) {
|
|
|
|
return '#f0fdf4';
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4. 有计划但未指定背景色:赋予柔和浅黄
|
|
|
|
if (date.planList && date.planList.length > 0) {
|
|
|
|
return '#fefce8';
|
|
|
|
}
|
|
|
|
|
|
|
|
// 下月填充
|
|
|
|
const remaining = 42 - calendarDates.length;
|
|
|
|
for (let d = 1; d <= remaining; d++) {
|
|
|
|
const y = month === 12 ? year + 1 : year;
|
|
|
|
const m = month === 12 ? 1 : month + 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 普通无训练的本月休息日:基础卡片白色/浅灰
|
|
|
|
return '#ededed';
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const generateCalendar = (monthStr) => {
|
|
|
|
const current = dayjs(monthStr);
|
|
|
|
const startOfMonth = current.startOf('month');
|
|
|
|
const daysInMonth = current.daysInMonth();
|
|
|
|
|
|
|
|
const dayOfWeek = startOfMonth.day(); // 0 表示周日
|
|
|
|
const startOffset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
|
|
|
|
|
|
// 动态计算总单元格:35(5行)或 42(6行)
|
|
|
|
const totalCellsNeeded = startOffset + daysInMonth;
|
|
|
|
const totalGridSize = totalCellsNeeded > 35 ? 42 : 35;
|
|
|
|
|
|
|
|
const gridStartDate = startOfMonth.subtract(startOffset, 'day');
|
|
|
|
const todayStr = dayjs().format('YYYY-MM-DD');
|
|
|
|
|
|
|
|
const calendarDates = [];
|
|
|
|
for (let i = 0; i < totalGridSize; i++) {
|
|
|
|
const cellDate = gridStartDate.add(i, 'day');
|
|
|
|
const dateKey = cellDate.format('YYYY-MM-DD');
|
|
|
|
|
|
|
|
calendarDates.push({
|
|
|
|
day: d,
|
|
|
|
today: false,
|
|
|
|
dateKey: formatDateKey(y, m, d),
|
|
|
|
day: cellDate.date(),
|
|
|
|
today: dateKey === todayStr,
|
|
|
|
isCurrentMonth: cellDate.month() === current.month(),
|
|
|
|
dateKey,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
...
|
...
|
@@ -168,26 +201,19 @@ const generateCalendar = (year, month) => { |
|
|
|
};
|
|
|
|
|
|
|
|
// ====== 加载计划数据 ======
|
|
|
|
const loadPlans = async (year, month) => {
|
|
|
|
const loadPlans = async (monthStr) => {
|
|
|
|
try {
|
|
|
|
// 关键:获取 上个月、当前月、下个月 → 保证前后日期都有数据
|
|
|
|
const prevMonth = month - 1 < 1 ? 12 : month - 1;
|
|
|
|
const prevYear = month - 1 < 1 ? year - 1 : year;
|
|
|
|
|
|
|
|
const nextMonth = month + 1 > 12 ? 1 : month + 1;
|
|
|
|
const nextYear = month + 1 > 12 ? year + 1 : year;
|
|
|
|
|
|
|
|
// 并行请求三个月数据
|
|
|
|
// const [prevRes, currRes, nextRes] = await Promise.all([
|
|
|
|
// DailyTemplateApi.getCalendarPlans(prevYear, prevMonth),
|
|
|
|
// DailyTemplateApi.getCalendarPlans(year, month),
|
|
|
|
// DailyTemplateApi.getCalendarPlans(nextYear, nextMonth),
|
|
|
|
// ]);
|
|
|
|
const prevRes = await DailyTemplateApi.getCalendarPlans(prevYear, prevMonth);
|
|
|
|
const currRes = await DailyTemplateApi.getCalendarPlans(year, month);
|
|
|
|
const nextRes = await DailyTemplateApi.getCalendarPlans(nextYear, nextMonth);
|
|
|
|
|
|
|
|
// 合并三个月数据
|
|
|
|
const curr = dayjs(monthStr);
|
|
|
|
const prev = curr.subtract(1, 'month');
|
|
|
|
const next = curr.add(1, 'month');
|
|
|
|
|
|
|
|
// 并行请求 3 个月数据
|
|
|
|
const [prevRes, currRes, nextRes] = await Promise.all([
|
|
|
|
DailyTemplateApi.getCalendarPlans(prev.year(), prev.month() + 1).catch(() => ({ data: [] })),
|
|
|
|
DailyTemplateApi.getCalendarPlans(curr.year(), curr.month() + 1).catch(() => ({ data: [] })),
|
|
|
|
DailyTemplateApi.getCalendarPlans(next.year(), next.month() + 1).catch(() => ({ data: [] })),
|
|
|
|
]);
|
|
|
|
|
|
|
|
const allPlans = [
|
|
|
|
...(prevRes.data || []),
|
|
|
|
...(currRes.data || []),
|
|
...
|
...
|
@@ -199,24 +225,25 @@ const loadPlans = async (year, month) => { |
|
|
|
const weightMap = {};
|
|
|
|
|
|
|
|
allPlans.forEach((plan) => {
|
|
|
|
let dateStr;
|
|
|
|
let dateStr = null;
|
|
|
|
if (Array.isArray(plan.trainDate)) {
|
|
|
|
dateStr = arrayToDateStr(plan.trainDate);
|
|
|
|
const [y, m, d] = plan.trainDate;
|
|
|
|
dateStr = dayjs(`${y}-${m}-${d}`).format('YYYY-MM-DD');
|
|
|
|
} else if (typeof plan.trainDate === 'string') {
|
|
|
|
dateStr = plan.trainDate;
|
|
|
|
dateStr = dayjs(plan.trainDate).format('YYYY-MM-DD');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dateStr) return;
|
|
|
|
if (!dateStr || dateStr === 'Invalid Date') return;
|
|
|
|
|
|
|
|
// 处理多条备注
|
|
|
|
// 备注处理
|
|
|
|
if (Array.isArray(plan.notes) && plan.notes.length > 0) {
|
|
|
|
noteMap[dateStr] = plan.notes.map(note => ({
|
|
|
|
noteMap[dateStr] = plan.notes.map((note) => ({
|
|
|
|
content: note.content || '',
|
|
|
|
backgroundColor: note.backgroundColor || '#ccc'
|
|
|
|
backgroundColor: note.backgroundColor || '#e2e8f0',
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
// 处理计划和重量
|
|
|
|
// 训练计划与容量处理
|
|
|
|
planMap[dateStr] = planMap[dateStr] || [];
|
|
|
|
if (Array.isArray(plan.templates)) {
|
|
|
|
let totalWeightForDay = 0;
|
|
...
|
...
|
@@ -224,7 +251,7 @@ const loadPlans = async (year, month) => { |
|
|
|
if (template && template.name) {
|
|
|
|
planMap[dateStr].push({
|
|
|
|
name: template.name,
|
|
|
|
bg: template.backgroundColor || '#ffdd44'
|
|
|
|
bg: template.backgroundColor || '#fef08a',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (template && typeof template.totalWeight === 'number') {
|
|
...
|
...
|
@@ -237,57 +264,37 @@ const loadPlans = async (year, month) => { |
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const newDates = generateCalendar(year, month).map((date) => ({
|
|
|
|
dates.value = generateCalendar(monthStr).map((date) => ({
|
|
|
|
...date,
|
|
|
|
planList: planMap[date.dateKey] || [],
|
|
|
|
noteList: noteMap[date.dateKey] || [],
|
|
|
|
weight: weightMap[date.dateKey] || null,
|
|
|
|
}));
|
|
|
|
|
|
|
|
dates.value = newDates;
|
|
|
|
} catch (err) {
|
|
|
|
console.error('加载日历失败:', err);
|
|
|
|
const [y, m] = [
|
|
|
|
parseInt(currentMonth.value.split('-')[0]),
|
|
|
|
parseInt(currentMonth.value.split('-')[1]),
|
|
|
|
];
|
|
|
|
dates.value = generateCalendar(y, m);
|
|
|
|
console.error('加载日历数据失败:', err);
|
|
|
|
dates.value = generateCalendar(monthStr);
|
|
|
|
}
|
|
|
|
console.log('日历主页面处理的完的是数据:dates', dates);
|
|
|
|
|
|
|
|
};
|
|
|
|
const selectedDateData = computed(() => {
|
|
|
|
return dates.value.find(item => item.dateKey === selectedDate.value) || null;
|
|
|
|
});
|
|
|
|
|
|
|
|
// ====================== 核心显示逻辑(已修复)======================
|
|
|
|
const maxLines = 3; // 一个格子最多显示3行
|
|
|
|
|
|
|
|
const getVisibleItems = (date) => {
|
|
|
|
// ====== 视图格子显示控制 ======
|
|
|
|
const getCellVisibleItems = (date) => {
|
|
|
|
const items = [];
|
|
|
|
let remaining = 0;
|
|
|
|
|
|
|
|
// 1. 重量优先显示
|
|
|
|
if (date.weight && date.weight > 0) {
|
|
|
|
items.push({ type: 'weight', text: `${date.weight}kg` });
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. 显示训练计划(已修改)
|
|
|
|
for (const plan of date.planList) {
|
|
|
|
if (items.length < maxLines) {
|
|
|
|
items.push({
|
|
|
|
type: 'plan',
|
|
|
|
text: plan.name,
|
|
|
|
bg: plan.bg
|
|
|
|
});
|
|
|
|
if (items.length < MAX_CELL_LINES) {
|
|
|
|
items.push({ type: 'plan', text: plan.name, bg: plan.bg });
|
|
|
|
} else {
|
|
|
|
remaining++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. 显示备注
|
|
|
|
for (const note of date.noteList) {
|
|
|
|
if (items.length < maxLines) {
|
|
|
|
if (items.length < MAX_CELL_LINES) {
|
|
|
|
items.push({ type: 'note', text: note.content, bg: note.backgroundColor });
|
|
|
|
} else {
|
|
|
|
remaining++;
|
|
...
|
...
|
@@ -297,153 +304,79 @@ const getVisibleItems = (date) => { |
|
|
|
return { items, remaining };
|
|
|
|
};
|
|
|
|
|
|
|
|
// 接收子组件通知,刷新整个日历数据
|
|
|
|
const handleRefreshCalendar = () => {
|
|
|
|
const [year, month] = currentMonth.value.split('-').map(Number);
|
|
|
|
loadPlans(year, month);
|
|
|
|
loadPlans(currentMonth.value);
|
|
|
|
};
|
|
|
|
// ====================================================================
|
|
|
|
|
|
|
|
// ====== 监听月份变化 ======
|
|
|
|
watch(
|
|
|
|
currentMonth,
|
|
|
|
(newVal) => {
|
|
|
|
const [year, month] = newVal.split('-').map(Number);
|
|
|
|
loadPlans(year, month);
|
|
|
|
if (newVal) {
|
|
|
|
loadPlans(newVal);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ immediate: true },
|
|
|
|
{ immediate: true }
|
|
|
|
);
|
|
|
|
|
|
|
|
const selectDate = async (date) => {
|
|
|
|
selectedDate.value = date.dateKey;
|
|
|
|
// ShowGridCellPopup.value = true;
|
|
|
|
await nextTick()
|
|
|
|
await nextTick();
|
|
|
|
gridPopupRef.value?.openPopup();
|
|
|
|
};
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.calendar-page {
|
|
|
|
height: 100%;
|
|
|
|
padding: 40rpx;
|
|
|
|
background-color: white;
|
|
|
|
padding: 30rpx 15rpx 20rpx;
|
|
|
|
background-color: #fff;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.top-bar {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
margin-top: 35rpx;
|
|
|
|
|
|
|
|
.month-picker {
|
|
|
|
height: 40rpx;
|
|
|
|
border-radius: 50rpx;
|
|
|
|
overflow: hidden;
|
|
|
|
padding: 0 20rpx;
|
|
|
|
gap: 10rpx;
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
|
|
:deep(.uni-date-x--border) {
|
|
|
|
border: none !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.uni-date__x-input) {
|
|
|
|
height: 40rpx;
|
|
|
|
line-height: 40rpx;
|
|
|
|
padding: 0 10rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 微信小程序端样式适配
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
|
.month-picker-pill {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
min-width: 200rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
:deep(.uni-date-x) {
|
|
|
|
height: 35rpx;
|
|
|
|
// min-height: 40rpx;
|
|
|
|
border-radius: 50rpx;
|
|
|
|
background-color: transparent;
|
|
|
|
padding: 0;
|
|
|
|
margin: 0;
|
|
|
|
border: none !important;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.uni-date-editor--x) {
|
|
|
|
height: 100%;
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
border: none !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.uni-date__x-input) {
|
|
|
|
// height: 40rpx;
|
|
|
|
// line-height: 40rpx;
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #333;
|
|
|
|
padding: 0 10rpx;
|
|
|
|
text-align: center;
|
|
|
|
flex: 1;
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
white-space: nowrap;
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.icon-calendar) {
|
|
|
|
display: none !important;
|
|
|
|
gap: 12rpx;
|
|
|
|
background-color: #ececec;
|
|
|
|
padding: 12rpx 28rpx;
|
|
|
|
border-radius: 40rpx;
|
|
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
|
|
|
border: 1rpx solid #fff;
|
|
|
|
|
|
|
|
.month-text {
|
|
|
|
font-size: 30rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #1a1a1a;
|
|
|
|
}
|
|
|
|
|
|
|
|
// #endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.action-buttons {
|
|
|
|
display: flex;
|
|
|
|
justify-content: flex-start;
|
|
|
|
gap: 10rpx;
|
|
|
|
|
|
|
|
// .action-btn {
|
|
|
|
// padding: 20rpx 20rpx;
|
|
|
|
// margin: 0;
|
|
|
|
// line-height: 1;
|
|
|
|
// border-radius: 20rpx;
|
|
|
|
// border: 1px solid #666;
|
|
|
|
// font-size: 20rpx;
|
|
|
|
// }
|
|
|
|
|
|
|
|
:deep(uni-button),
|
|
|
|
:deep(wx-button) {
|
|
|
|
padding: 10rpx 20rpx;
|
|
|
|
margin: 0;
|
|
|
|
line-height: 1;
|
|
|
|
border-radius: 20rpx;
|
|
|
|
border: 1px solid #666;
|
|
|
|
font-size: 20rpx;
|
|
|
|
.action-buttons {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.calendar-grid {
|
|
|
|
padding-bottom: 120rpx;
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
|
padding-bottom: 140rpx;
|
|
|
|
// #endif
|
|
|
|
.calendar-card {
|
|
|
|
background-color: #ffffff;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
|
|
|
|
|
|
|
|
.week-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
|
|
.week-day {
|
|
|
|
flex: 1;
|
|
|
|
text-align: center;
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #666;
|
|
|
|
font-size: 26rpx;
|
|
|
|
font-weight: 550;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
...
|
...
|
@@ -453,179 +386,182 @@ const selectDate = async (date) => { |
|
|
|
gap: 10rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 日期格子基础样式:保证每个格子都有独立背景与微细边框 */
|
|
|
|
.date-cell {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: flex-start;
|
|
|
|
border: 1px solid #eee;
|
|
|
|
font-size: 30rpx;
|
|
|
|
color: #333;
|
|
|
|
background-color: #f9fafc;
|
|
|
|
padding-top: 10rpx;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
height: 200rpx;
|
|
|
|
overflow: hidden;
|
|
|
|
box-sizing: border-box;
|
|
|
|
}
|
|
|
|
|
|
|
|
.plan-list {
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
gap: 6rpx;
|
|
|
|
margin-top: 10rpx;
|
|
|
|
padding: 0;
|
|
|
|
padding: 8rpx 4rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
}
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
.weight,
|
|
|
|
.plan-extra,
|
|
|
|
.note-item,
|
|
|
|
.plan-name {
|
|
|
|
font-size: 18rpx;
|
|
|
|
background: #ffdd44;
|
|
|
|
padding: 4rpx 0;
|
|
|
|
text-align: center;
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
width: 100%;
|
|
|
|
box-sizing: border-box;
|
|
|
|
}
|
|
|
|
|
|
|
|
.weight,
|
|
|
|
.weight-item {
|
|
|
|
background: #b8ff66;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
&:active {
|
|
|
|
filter: brightness(0.96);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 日历说明弹窗样式 */
|
|
|
|
.explain-popup-overlay {
|
|
|
|
position: fixed;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
right: 0;
|
|
|
|
bottom: 0;
|
|
|
|
background-color: rgba(0, 0, 0, 0.4);
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: flex-end;
|
|
|
|
z-index: 999;
|
|
|
|
|
|
|
|
.explain-popup {
|
|
|
|
width: 100%;
|
|
|
|
background-color: #ffffff;
|
|
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
|
|
padding: 40rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
&.other-month {
|
|
|
|
opacity: 0.5;
|
|
|
|
border-color: #f1f5f9;
|
|
|
|
}
|
|
|
|
|
|
|
|
.explain-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 40rpx;
|
|
|
|
&.is-today {
|
|
|
|
border: 2rpx solid #3b82f6;
|
|
|
|
}
|
|
|
|
|
|
|
|
.explain-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: 60;
|
|
|
|
color: #333333;
|
|
|
|
&.has-data {
|
|
|
|
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.02);
|
|
|
|
}
|
|
|
|
|
|
|
|
.close-btn {
|
|
|
|
width: 48rpx;
|
|
|
|
height: 48rpx;
|
|
|
|
border-radius: 50%;
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
.date-header {
|
|
|
|
height: 36rpx;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
margin-bottom: 6rpx;
|
|
|
|
|
|
|
|
.date-number {
|
|
|
|
font-size: 26rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.today-badge {
|
|
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
text {
|
|
|
|
font-size: 36rpx;
|
|
|
|
color: #666666;
|
|
|
|
line-height: 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.explain-list {
|
|
|
|
.explain-item {
|
|
|
|
.plan-list {
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 32rpx;
|
|
|
|
flex-direction: column;
|
|
|
|
gap: 6rpx;
|
|
|
|
|
|
|
|
.plan-tag {
|
|
|
|
width: 100%;
|
|
|
|
border-radius: 6rpx;
|
|
|
|
padding: 2rpx 4rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
background-color: rgba(255, 255, 255, 0.85);
|
|
|
|
/* 使 Tag 悬浮于彩色格子之上 */
|
|
|
|
|
|
|
|
.tag-text {
|
|
|
|
font-size: 18rpx;
|
|
|
|
line-height: 22rpx;
|
|
|
|
color: #111827;
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
text-align: center;
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.color-block {
|
|
|
|
width: 40rpx;
|
|
|
|
height: 40rpx;
|
|
|
|
border-radius: 8rpx;
|
|
|
|
margin-right: 20rpx;
|
|
|
|
&.tag-type-weight {
|
|
|
|
background-color: #bbf7d0 !important;
|
|
|
|
|
|
|
|
&.blue {
|
|
|
|
background-color: #a8d8ff;
|
|
|
|
.tag-text {
|
|
|
|
color: #14532d;
|
|
|
|
font-weight: 700;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&.green {
|
|
|
|
background-color: #b8ff66;
|
|
|
|
}
|
|
|
|
&.tag-type-note {
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
|
|
&.yellow {
|
|
|
|
background-color: #ffdd44;
|
|
|
|
.tag-text {
|
|
|
|
color: #374151;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&.gray {
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
border: 1rpx solid #eee;
|
|
|
|
}
|
|
|
|
.plan-extra {
|
|
|
|
font-size: 16rpx;
|
|
|
|
color: #4b5563;
|
|
|
|
text-align: center;
|
|
|
|
line-height: 20rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.explain-popup-content {
|
|
|
|
padding: 36rpx 32rpx 48rpx 32rpx;
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
|
|
.explain-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 32rpx;
|
|
|
|
|
|
|
|
.explain-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #111827;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.explain-list {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
gap: 28rpx;
|
|
|
|
|
|
|
|
.explain-item {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
.color-block {
|
|
|
|
width: 36rpx;
|
|
|
|
height: 36rpx;
|
|
|
|
border-radius: 8rpx;
|
|
|
|
margin-right: 24rpx;
|
|
|
|
|
|
|
|
&.blue {
|
|
|
|
background-color: #bae6fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.green {
|
|
|
|
background-color: #bbf7d0;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.yellow {
|
|
|
|
background-color: #fef08a;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.gray {
|
|
|
|
background-color: #e5e7eb;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-info {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
|
|
.item-label {
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #333333;
|
|
|
|
width: 80rpx;
|
|
|
|
margin-right: 20rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #1f2937;
|
|
|
|
width: 72rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-desc {
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #666666;
|
|
|
|
flex: 1;
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #6b7280;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// .gridCellContent {
|
|
|
|
// position: fixed;
|
|
|
|
// top: 0;
|
|
|
|
// left: 0;
|
|
|
|
// right: 0;
|
|
|
|
// bottom: 0;
|
|
|
|
// background: rgba(0, 0, 0, 0.5);
|
|
|
|
// z-index: 9999;
|
|
|
|
// display: flex;
|
|
|
|
// align-items: flex-end;
|
|
|
|
// padding: 0 !important;
|
|
|
|
// margin: 0 !important;
|
|
|
|
// width: 100vw !important;
|
|
|
|
// box-sizing: border-box;
|
|
|
|
// }
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<!-- #ifdef MP-WEIXIN -->
|
|
|
|
<style lang="scss">
|
|
|
|
.month-picker {
|
|
|
|
|
|
|
|
.uni-date-editor--x.uni-date-x--border,
|
|
|
|
.uni-date-x--border {
|
|
|
|
border: none !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
.uni-date-x,
|
|
|
|
.uni-date-editor--x {
|
|
|
|
border: none !important;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
<!-- #endif --> |
|
|
\ No newline at end of file |
|
|
|
</style> |
|
|
\ No newline at end of file |
...
|
...
|
|