265 lines
6.7 KiB
Markdown
265 lines
6.7 KiB
Markdown
# 管理后台首页日历Skill
|
|
|
|
## 适用场景
|
|
|
|
需要在管理后台首页展示日历视图,用于:
|
|
- 工作日志录入情况概览
|
|
- 考勤打卡情况展示
|
|
- 日程安排查看
|
|
|
|
## 功能特性
|
|
|
|
- 按月份展示日历
|
|
- 日期状态标识(已录入/未录入)
|
|
- 管理员可选择人员查看
|
|
- 点击日期查看详情
|
|
|
|
## 实现方案
|
|
|
|
### 后端接口
|
|
|
|
```java
|
|
// LogController.java
|
|
@GetMapping("/calendar")
|
|
public Result<List<String>> getCalendarData(
|
|
@RequestParam Integer year,
|
|
@RequestParam Integer month,
|
|
@RequestParam(required = false) String userId) {
|
|
return Result.success(logService.getLogDatesByMonth(year, month, userId));
|
|
}
|
|
|
|
// LogService.java
|
|
List<String> getLogDatesByMonth(Integer year, Integer month, String userId);
|
|
```
|
|
|
|
### 前端实现
|
|
|
|
#### 1. 日历组件结构
|
|
|
|
```vue
|
|
<template>
|
|
<div class="dashboard-page">
|
|
<!-- 管理员人员选择器 -->
|
|
<el-card v-if="isAdmin" class="user-selector-card">
|
|
<el-select v-model="selectedUserId" placeholder="选择人员查看日志" clearable @change="handleUserChange">
|
|
<el-option label="自己" value="" />
|
|
<el-option v-for="user in userList" :key="user.id" :label="user.name" :value="user.id" />
|
|
</el-select>
|
|
</el-card>
|
|
|
|
<!-- 日历卡片 -->
|
|
<el-card class="calendar-card">
|
|
<!-- 月份切换 -->
|
|
<div class="calendar-header">
|
|
<el-button @click="prevMonth" :icon="ArrowLeft" circle />
|
|
<span class="month-title">{{ currentYear }}年{{ currentMonth }}月</span>
|
|
<el-button @click="nextMonth" :icon="ArrowRight" circle />
|
|
</div>
|
|
|
|
<!-- 日历 -->
|
|
<el-calendar v-model="calendarDate">
|
|
<template #date-cell="{ data }">
|
|
<div class="calendar-day" :class="getDayClass(data.day)" @click="handleDayClick(data.day)">
|
|
<span class="day-number">{{ data.day.split('-')[2] }}</span>
|
|
<span v-if="logDates.has(data.day)" class="log-indicator has-log">✓</span>
|
|
<span v-else-if="isCurrentMonth(data.day)" class="log-indicator no-log">○</span>
|
|
</div>
|
|
</template>
|
|
</el-calendar>
|
|
|
|
<!-- 图例 -->
|
|
<div class="calendar-legend">
|
|
<span class="legend-item"><span class="dot blue"></span>已记录</span>
|
|
<span class="legend-item"><span class="dot red"></span>未记录</span>
|
|
</div>
|
|
</el-card>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
#### 2. 核心逻辑
|
|
|
|
```typescript
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
import { useUserStore } from '@/store/user'
|
|
import { getCalendarData, getLogByDate } from '@/api/log'
|
|
import { listEnabledUsers } from '@/api/user'
|
|
|
|
const userStore = useUserStore()
|
|
const isAdmin = computed(() => userStore.isAdmin())
|
|
|
|
const today = new Date()
|
|
const currentYear = ref(today.getFullYear())
|
|
const currentMonth = ref(today.getMonth() + 1)
|
|
const calendarDate = ref(today)
|
|
|
|
const logDates = ref<Set<string>>(new Set())
|
|
const userList = ref<User[]>([])
|
|
const selectedUserId = ref<string>('')
|
|
|
|
// 加载日历数据
|
|
async function loadCalendarData() {
|
|
const dates = await getCalendarData(currentYear.value, currentMonth.value, selectedUserId.value || undefined)
|
|
logDates.value = new Set(dates)
|
|
}
|
|
|
|
// 月份切换
|
|
function prevMonth() {
|
|
if (currentMonth.value === 1) {
|
|
currentYear.value--
|
|
currentMonth.value = 12
|
|
} else {
|
|
currentMonth.value--
|
|
}
|
|
updateCalendarDate()
|
|
loadCalendarData()
|
|
}
|
|
|
|
function nextMonth() {
|
|
if (currentMonth.value === 12) {
|
|
currentYear.value++
|
|
currentMonth.value = 1
|
|
} else {
|
|
currentMonth.value++
|
|
}
|
|
updateCalendarDate()
|
|
loadCalendarData()
|
|
}
|
|
|
|
// 判断日期样式
|
|
function getDayClass(dateStr: string): string {
|
|
if (!isCurrentMonth(dateStr)) return 'other-month'
|
|
if (logDates.value.has(dateStr)) return 'has-log'
|
|
return 'no-log'
|
|
}
|
|
|
|
// 监听日历组件月份变化
|
|
watch(calendarDate, (newDate) => {
|
|
const year = newDate.getFullYear()
|
|
const month = newDate.getMonth() + 1
|
|
if (year !== currentYear.value || month !== currentMonth.value) {
|
|
currentYear.value = year
|
|
currentMonth.value = month
|
|
loadCalendarData()
|
|
}
|
|
})
|
|
```
|
|
|
|
#### 3. 样式规范
|
|
|
|
```css
|
|
/* 日期样式 - 已记录显示蓝色 */
|
|
.has-log .day-number,
|
|
.has-log .log-indicator {
|
|
color: #409eff;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* 日期样式 - 未记录显示红色 */
|
|
.no-log .day-number,
|
|
.no-log .log-indicator {
|
|
color: #f56c6c;
|
|
}
|
|
|
|
/* 非当月日期 */
|
|
.other-month .day-number {
|
|
color: #c0c4cc;
|
|
}
|
|
|
|
.other-month .log-indicator {
|
|
display: none;
|
|
}
|
|
|
|
/* 日历格子 */
|
|
.calendar-day {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.calendar-day:hover {
|
|
background-color: #f5f7fa;
|
|
}
|
|
```
|
|
|
|
## API接口定义
|
|
|
|
### 获取日历数据
|
|
|
|
```typescript
|
|
// api/log.ts
|
|
export function getCalendarData(year: number, month: number, userId?: string): Promise<string[]> {
|
|
const params: Record<string, string | number> = { year, month }
|
|
if (userId) params.userId = userId
|
|
return request.get('/log/calendar', { params })
|
|
}
|
|
```
|
|
|
|
### 获取日志详情
|
|
|
|
```typescript
|
|
export function getLogByDate(date: string, userId?: string): Promise<Log> {
|
|
const params: Record<string, string> = { date }
|
|
if (userId) params.userId = userId
|
|
return request.get('/log/byDate', { params })
|
|
}
|
|
```
|
|
|
|
## 管理员权限扩展
|
|
|
|
### 后端扩展
|
|
|
|
```java
|
|
// UserService.java
|
|
List<User> listEnabledUsers();
|
|
|
|
// UserServiceImpl.java
|
|
@Override
|
|
public List<User> listEnabledUsers() {
|
|
return userMapper.selectList(
|
|
new LambdaQueryWrapper<User>()
|
|
.eq(User::getStatus, 1)
|
|
.orderByAsc(User::getCreatedTime)
|
|
);
|
|
}
|
|
|
|
// LogService.java
|
|
List<String> getLogDatesByMonth(Integer year, Integer month, String userId);
|
|
|
|
// LogServiceImpl.java
|
|
@Override
|
|
public List<String> getLogDatesByMonth(Integer year, Integer month, String userId) {
|
|
String targetUserId = StringUtils.hasText(userId) ? userId : getCurrentUserId();
|
|
// 查询该用户当月的日志日期列表
|
|
return logMapper.selectDatesByMonth(year, month, targetUserId);
|
|
}
|
|
```
|
|
|
|
### 前端权限判断
|
|
|
|
```typescript
|
|
import { useUserStore } from '@/store/user'
|
|
|
|
const userStore = useUserStore()
|
|
const isAdmin = computed(() => userStore.isAdmin())
|
|
```
|
|
|
|
## 状态颜色规范
|
|
|
|
| 状态 | 颜色 | 说明 |
|
|
|------|------|------|
|
|
| 已记录 | #409eff (蓝色) | 当月日期有日志记录 |
|
|
| 未记录 | #f56c6c (红色) | 当月日期无日志记录 |
|
|
| 非当月 | #c0c4cc (灰色) | 不属于当前显示月份 |
|
|
|
|
## 注意事项
|
|
|
|
1. **日期格式**:统一使用 `YYYY-MM-DD` 格式
|
|
2. **时区处理**:注意前后端时区一致性
|
|
3. **权限控制**:非管理员只能查看自己的日志
|
|
4. **性能优化**:日历数据按月加载,避免一次性请求过多
|