diff --git a/package.json b/package.json index 6e95f62..02698ae 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "license": "MIT", "type": "module", "scripts": { - "dev": "vite serve --mode development", "build:prod": "vite build --mode production", "build:dev": "vite build --mode development", @@ -37,6 +36,7 @@ "image-conversion": "2.1.1", "js-cookie": "3.0.5", "jsencrypt": "3.3.2", + "lunar-javascript": "^1.7.7", "mammoth": "^1.11.0", "nprogress": "0.2.0", "pinia": "3.0.2", diff --git a/src/components/WorkCalendar/index.vue b/src/components/WorkCalendar/index.vue new file mode 100644 index 0000000..cfd8cbf --- /dev/null +++ b/src/components/WorkCalendar/index.vue @@ -0,0 +1,1697 @@ + + + + + + + + 月 + + + 周 + + + 今天 + 添加日程 + 按住拖动可选择日期区间 + + + + + 图例 + + + + + {{ item.label }} + + + + + + + + + + + + 需关注 + + + + 待办 + {{ task.businessTitle }} + + + + + + + + + + + + + {{ formatDayTipHead(data.day).date }} + {{ formatDayTipHead(data.day).weekday }} + {{ getDayLunarInfo(data.day).fullLunar }} + + {{ getDayLunarInfo(data.day).festival }} + 今天 + + + + + {{ eventTypeLabel(item) }} + {{ item.content }} + + + 暂无安排 + 拖动选择区间 · 双击编辑 + + + + + + {{ dayjs(data.day).date() }} + + {{ getDayLunarInfo(data.day).label }} + + + + + + + {{ item.content }} + + + +{{ getVisibleEvents(data.day).moreCount }} + + + + + + + + + + + + {{ weekRangeLabel }} + + + + + + + + + {{ formatDayTipHead(day).date }} + {{ formatDayTipHead(day).weekday }} + {{ getDayLunarInfo(day).fullLunar }} + + {{ getDayLunarInfo(day).festival }} + 今天 + + + + + {{ eventTypeLabel(item) }} + {{ item.content }} + + + 暂无安排 + + + + + {{ weekDayShort(day) }} + {{ dayjs(day).date() }} + + {{ getDayLunarInfo(day).label }} + + + + + + {{ eventTypeLabel(item) }} + {{ item.content }} + + — + + + + + + + + + + 待办审批 + + + 待办 + {{ item.content }} + + + + + + + 新建日程 + + + + + + + {{ t.label }} + + + + + + + + + + + + + + + + {{ editingId ? '保存修改' : '添加日程' }} + + 取消编辑 + + + + + 已维护记录 + + + + {{ typeLabel(item.type) }} + + {{ formatRangeLabel(item.rangeStart, item.rangeEnd) }} + + {{ item.content }} + + + 编辑 + 删除 + + + + + + + 关闭 + + + + + + + + + + diff --git a/src/components/WorkCalendar/workCalendar.types.ts b/src/components/WorkCalendar/workCalendar.types.ts new file mode 100644 index 0000000..ff2d30a --- /dev/null +++ b/src/components/WorkCalendar/workCalendar.types.ts @@ -0,0 +1,9 @@ +export interface CalendarPendingTask { + id: string | number; + businessId: string; + businessTitle: string; + createTime?: string; + flowName?: string; + formCustom?: string; + formPath?: string; +} diff --git a/src/types/lunar-javascript.d.ts b/src/types/lunar-javascript.d.ts new file mode 100644 index 0000000..6f5fae5 --- /dev/null +++ b/src/types/lunar-javascript.d.ts @@ -0,0 +1,19 @@ +declare module 'lunar-javascript' { + export class Solar { + static fromYmd(year: number, month: number, day: number): Solar; + getLunar(): Lunar; + getFestivals(): string[]; + } + + export class Lunar { + getDay(): number; + getDayInChinese(): string; + getMonthInChinese(): string; + getFestivals(): string[]; + getJieQi(): string; + } + + export class HolidayUtil { + static getHoliday(day: string): { getName(): string } | null; + } +} diff --git a/src/utils/lunarCalendar.ts b/src/utils/lunarCalendar.ts new file mode 100644 index 0000000..3e98f23 --- /dev/null +++ b/src/utils/lunarCalendar.ts @@ -0,0 +1,61 @@ +import { HolidayUtil, Solar } from 'lunar-javascript'; + +export interface DayLunarInfo { + /** 单元格副标题:节日名或农历日 */ + label: string; + /** 完整农历,如 四月廿七 */ + fullLunar: string; + /** 节日/节气高亮 */ + isHighlight: boolean; + /** 节日或节气名称 */ + festival?: string; +} + +const cache = new Map(); + +const pickSolarFestival = (festivals: string[]) => festivals.find((name) => !/^国际/.test(name)); + +const resolveFestival = (dateStr: string, solar: Solar, lunar: ReturnType) => { + const holiday = HolidayUtil.getHoliday(dateStr); + if (holiday) return holiday.getName(); + + const lunarFestivals = lunar.getFestivals(); + if (lunarFestivals.length) return lunarFestivals[0]; + + const solarFestival = pickSolarFestival(solar.getFestivals()); + if (solarFestival) return solarFestival; + + const jieQi = lunar.getJieQi(); + if (jieQi) return jieQi; + + return ''; +}; + +export const getDayLunarInfo = (dateStr: string): DayLunarInfo => { + const cached = cache.get(dateStr); + if (cached) return cached; + + const [year, month, day] = dateStr.split('-').map(Number); + const solar = Solar.fromYmd(year, month, day); + const lunar = solar.getLunar(); + const festival = resolveFestival(dateStr, solar, lunar); + + let label = ''; + if (festival) { + label = festival; + } else if (lunar.getDay() === 1) { + label = `${lunar.getMonthInChinese()}月`; + } else { + label = lunar.getDayInChinese(); + } + + const info: DayLunarInfo = { + label, + fullLunar: `${lunar.getMonthInChinese()}月${lunar.getDayInChinese()}`, + isHighlight: !!festival, + festival: festival || undefined + }; + + cache.set(dateStr, info); + return info; +}; diff --git a/src/views/index.vue b/src/views/index.vue index ca17239..eb3c7e0 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -110,7 +110,7 @@ - + + + + @@ -207,6 +215,8 @@ import { TimesheetInfoVO } from '@/api/oa/erp/timesheetInfo/types'; import { useUserStore } from '@/store/modules/user'; import { useNoticeStore } from '@/store/modules/notice'; import { resolveMenuPath } from '@/utils/resolveMenuPath'; +import WorkCalendar from '@/components/WorkCalendar/index.vue'; +import type { CalendarPendingTask } from '@/components/WorkCalendar/workCalendar.types'; import dayjs from 'dayjs'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; @@ -277,6 +287,19 @@ const visibleShortcuts = computed(() => ); const noticeTab = ref('notice'); +const workCalendarRef = ref>(); + +const calendarPendingTasks = computed(() => + taskWaitList.value.map((t) => ({ + id: t.id, + businessId: t.businessId, + businessTitle: t.businessTitle || '—', + createTime: t.createTime ? String(t.createTime) : undefined, + flowName: t.flowName, + formCustom: t.formCustom, + formPath: t.formPath + })) +); const displayName = computed(() => userStore.nickname || '用户'); const displayDept = computed(() => { @@ -501,6 +524,9 @@ const loadDashboard = async () => { const handleRefresh = () => { loadDashboard(); + if (noticeTab.value === 'calendar') { + workCalendarRef.value?.reload(); + } }; const focusMessages = () => { @@ -552,6 +578,17 @@ const onShortcutClick = (s: { menuComponent: string }) => { openMenu(s.menuComponent); }; +const onCalendarTaskClick = (task: CalendarPendingTask) => { + const routerJumpVo = reactive({ + businessId: task.businessId, + taskId: task.id, + type: 'approval', + formCustom: task.formCustom, + formPath: task.formPath + }); + workflowCommon.routerJump(routerJumpVo, proxy); +}; + onMounted(() => { loadDashboard(); }); @@ -804,6 +841,25 @@ onMounted(() => { :deep(.el-tabs__header) { margin-bottom: 8px; } + + :deep(.el-tabs__item) { + font-size: 13px; + padding: 0 14px; + } + + :deep(.el-tab-pane) { + min-height: 0; + } +} + +.notice-tabs--calendar { + :deep(.el-tabs__content) { + overflow: visible; + } + + :deep(.el-card__body) { + padding-bottom: 12px; + } } .tab-badge {