From 90a5c032c21027a68affdfacb8e3791a7f10d762 Mon Sep 17 00:00:00 2001 From: zhangjf Date: Wed, 25 Feb 2026 01:28:34 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E6=8A=80=E8=83=BD=E6=96=87=E6=A1=A3(skills)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - markdown-render.md: Markdown渲染实现方案 - dashboard-calendar.md: 管理后台首页日历功能实现 --- .qoder/skills/dashboard-calendar.md | 264 ++++++++++++++++++++++++++++ .qoder/skills/markdown-render.md | 257 +++++++++++++++++++++++++++ 2 files changed, 521 insertions(+) create mode 100644 .qoder/skills/dashboard-calendar.md create mode 100644 .qoder/skills/markdown-render.md diff --git a/.qoder/skills/dashboard-calendar.md b/.qoder/skills/dashboard-calendar.md new file mode 100644 index 0000000..9d4648c --- /dev/null +++ b/.qoder/skills/dashboard-calendar.md @@ -0,0 +1,264 @@ +# 管理后台首页日历Skill + +## 适用场景 + +需要在管理后台首页展示日历视图,用于: +- 工作日志录入情况概览 +- 考勤打卡情况展示 +- 日程安排查看 + +## 功能特性 + +- 按月份展示日历 +- 日期状态标识(已录入/未录入) +- 管理员可选择人员查看 +- 点击日期查看详情 + +## 实现方案 + +### 后端接口 + +```java +// LogController.java +@GetMapping("/calendar") +public Result> getCalendarData( + @RequestParam Integer year, + @RequestParam Integer month, + @RequestParam(required = false) String userId) { + return Result.success(logService.getLogDatesByMonth(year, month, userId)); +} + +// LogService.java +List getLogDatesByMonth(Integer year, Integer month, String userId); +``` + +### 前端实现 + +#### 1. 日历组件结构 + +```vue + +``` + +#### 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>(new Set()) +const userList = ref([]) +const selectedUserId = ref('') + +// 加载日历数据 +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 { + const params: Record = { year, month } + if (userId) params.userId = userId + return request.get('/log/calendar', { params }) +} +``` + +### 获取日志详情 + +```typescript +export function getLogByDate(date: string, userId?: string): Promise { + const params: Record = { date } + if (userId) params.userId = userId + return request.get('/log/byDate', { params }) +} +``` + +## 管理员权限扩展 + +### 后端扩展 + +```java +// UserService.java +List listEnabledUsers(); + +// UserServiceImpl.java +@Override +public List listEnabledUsers() { + return userMapper.selectList( + new LambdaQueryWrapper() + .eq(User::getStatus, 1) + .orderByAsc(User::getCreatedTime) + ); +} + +// LogService.java +List getLogDatesByMonth(Integer year, Integer month, String userId); + +// LogServiceImpl.java +@Override +public List 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. **性能优化**:日历数据按月加载,避免一次性请求过多 diff --git a/.qoder/skills/markdown-render.md b/.qoder/skills/markdown-render.md new file mode 100644 index 0000000..b8c2d8e --- /dev/null +++ b/.qoder/skills/markdown-render.md @@ -0,0 +1,257 @@ +# Markdown渲染Skill + +## 适用场景 + +当前端页面需要渲染Markdown格式的文本内容时使用,适用于: +- 日志详情展示 +- 文章内容展示 +- 备注/说明信息展示 + +## 实现方案 + +使用纯JavaScript实现简单的Markdown渲染,无需引入第三方库。 + +### Vue 3 组件实现 + +```vue + + + +``` + +### 样式规范 + +#### 管理后台(Element Plus) + +```css +.markdown-body { + font-size: 14px; + line-height: 1.8; + color: #303133; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3 { + margin: 16px 0 8px; + font-weight: 600; + line-height: 1.4; +} + +.markdown-body h1 { + font-size: 20px; + border-bottom: 1px solid #ebeef5; + padding-bottom: 8px; +} + +.markdown-body h2 { + font-size: 18px; +} + +.markdown-body h3 { + font-size: 16px; +} + +.markdown-body ul { + margin: 8px 0; + padding-left: 24px; +} + +.markdown-body li { + margin: 4px 0; +} + +.markdown-body code { + background-color: #f5f7fa; + padding: 2px 6px; + border-radius: 4px; + font-family: 'Courier New', Courier, monospace; + font-size: 13px; +} + +.markdown-body pre { + background-color: #f5f7fa; + padding: 12px; + border-radius: 6px; + overflow-x: auto; + margin: 12px 0; +} + +.markdown-body pre code { + background-color: transparent; + padding: 0; +} + +.empty-content { + color: #909399; + text-align: center; +} +``` + +#### 移动端(Vant) + +```css +.markdown-body { + font-size: 14px; + line-height: 1.8; + color: #333; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3 { + margin: 16px 0 8px; + font-weight: 600; + line-height: 1.4; +} + +.markdown-body h1 { + font-size: 18px; + border-bottom: 1px solid #ebedf0; + padding-bottom: 8px; +} + +.markdown-body h2 { + font-size: 16px; +} + +.markdown-body h3 { + font-size: 15px; +} + +.markdown-body ul { + margin: 8px 0; + padding-left: 20px; +} + +.markdown-body li { + margin: 4px 0; +} + +.markdown-body code { + background-color: #f5f7fa; + padding: 2px 6px; + border-radius: 4px; + font-family: 'Courier New', Courier, monospace; + font-size: 13px; +} + +.markdown-body pre { + background-color: #f5f7fa; + padding: 12px; + border-radius: 6px; + overflow-x: auto; + margin: 12px 0; +} + +.markdown-body pre code { + background-color: transparent; + padding: 0; +} + +.empty-content { + color: #969799; + text-align: center; +} +``` + +## 支持的Markdown语法 + +| 语法 | Markdown | 渲染结果 | +|------|----------|----------| +| 一级标题 | `# 标题` | `

标题

` | +| 二级标题 | `## 标题` | `

标题

` | +| 三级标题 | `### 标题` | `

标题

` | +| 粗体 | `**文本**` | `文本` | +| 斜体 | `*文本*` | `文本` | +| 行内代码 | `` `代码` `` | `代码` | +| 代码块 | ` ```代码块``` ` | `
代码块
` | +| 无序列表 | `- 列表项` | `
  • 列表项
` | +| 段落换行 | 空行分隔 | `

` 标签 | +| 普通换行 | 单个换行 | `
` | + +## 安全说明 + +**重要**:渲染前必须转义HTML特殊字符(`&`, `<`, `>`),防止XSS攻击。 + +```javascript +// 必须首先执行 +content = content + .replace(/&/g, '&') + .replace(//g, '>') +``` + +## 使用示例 + +### 日志详情弹窗 + +```vue + +``` + +## 注意事项 + +1. **性能考虑**:对于大量内容的渲染,考虑使用`v-memo`或虚拟滚动 +2. **代码高亮**:如需语法高亮,可额外集成highlight.js或prism.js +3. **扩展语法**:当前实现为基础语法,如需表格、引用等可按需扩展