refactor(mes): 重构排班日历视图,提取公共日历格子组件

- 新增 CalendarDateCell.vue:抽取三个视图中重复的农历/节气/节假日/班次渲染逻辑,统一维护
- CalendarTypeView.vue → TypeView.vue,PersonView.vue → UserView.vue:按视图含义重命名,去掉多余前缀
- UserView.vue:用户选择从 el-input-number 改为 el-select 下拉,调用 getSimpleUserList 获取用户列表
- TeamView.vue / TypeView.vue / UserView.vue:统一使用 CalendarDateCell 组件;补充变量行尾注释、函数 JSDoc 及关键逻辑行内注释
- index.vue:更新组件引用
pull/871/MERGE
YunaiV 2026-02-19 20:56:39 +08:00
parent 64b3ce64c3
commit 93a76b19db
7 changed files with 378 additions and 553 deletions

View File

@ -0,0 +1,105 @@
<!-- 排班日历 - 日历格子公共组件 -->
<template>
<div class="h-full p-4px">
<!-- 顶部日期数字 + 上班/休息标签 -->
<div class="flex justify-between items-center">
<span class="text-16px font-500" :class="{ 'text-#f56c6c': isWeekend }">
{{ dayNumber }}
</span>
<el-tag v-if="isHoliday" size="small" effect="dark" type="success"></el-tag>
<el-tag v-else size="small" effect="dark"> </el-tag>
</div>
<!-- 农历 / 节气 / 节日显示 -->
<div
class="text-12px text-#909399 mt-2px"
:class="{ 'text-#67c23a': hasFestivalDay }"
>
{{ lunarDisplay }}
</div>
<!-- 班次信息节假日不显示排班 -->
<template v-if="!isHoliday">
<!-- TODO @AI最好有个地方说明下这个逻辑 -->
<div v-for="item in teamShifts" :key="item.sort" class="mt-2px">
<!-- sort=1 白班绿色 -->
<el-button v-if="item.sort === 1" type="success" size="small" class="!w-full !text-12px">
{{ item.teamName }}
</el-button>
<!-- sort=2 中班三班倒时用橙色两班倒时用灰色 -->
<el-button
v-else-if="item.sort === 2"
:type="shiftType === MesCalShiftTypeEnum.THREE ? 'warning' : 'info'"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
<!-- sort=3 夜班灰色 -->
<el-button v-else-if="item.sort === 3" type="info" size="small" class="!w-full !text-12px">
{{ item.teamName }}
</el-button>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { SolarDay } from 'tyme4ts'
import { MesCalShiftTypeEnum } from '@/views/mes/utils/constants'
import type { CalCalendarDayVO } from '@/api/mes/cal/calendar'
const props = defineProps<{
day: string // yyyy-MM-dd
holidaySet: Set<string> // key: yyyy-MM-dd
calendarDayMap: Map<string, CalCalendarDayVO> // key: yyyy-MM-dd
}>()
const dayNumber = computed(() => props.day.split('-')[2]) // ""
const isHoliday = computed(() => props.holidaySet.has(props.day)) //
const isWeekend = computed(() => {
const date = new Date(props.day)
const weekDay = date.getDay()
return weekDay === 0 || weekDay === 6 // 0=6=
})
const calDay = computed(() => props.calendarDayMap.get(props.day)) //
const teamShifts = computed(() => calDay.value?.teamShifts || []) //
const shiftType = computed(() => calDay.value?.shiftType) //
/** 解析当天的农历、节气、节日信息 */
const lunarInfo = computed(() => {
const parts = props.day.split('-')
const year = parseInt(parts[0])
const month = parseInt(parts[1])
const date = parseInt(parts[2])
try {
const solarDay = SolarDay.fromYmd(year, month, date)
const lunarDay = solarDay.getLunarDay()
const solarFestival = solarDay.getFestival() //
const lunarFestival = lunarDay.getFestival() //
const termDay = solarDay.getTermDay()
const termName = termDay.getDayIndex() === 0 ? termDay.getSolarTerm().getName() : null //
const lunarMonthName = lunarDay.getLunarMonth().getName()
const lunarDayName = lunarDay.getName()
return {
solarFestival: solarFestival ? solarFestival.getName() : null,
lunarFestival: lunarFestival ? lunarFestival.getName() : null,
termName,
lunarText: lunarMonthName + lunarDayName //
}
} catch {
return { solarFestival: null, lunarFestival: null, termName: null, lunarText: '' }
}
})
/** 优先级:公历节日 > 农历节日 > 节气 > 农历月日 */
const lunarDisplay = computed(() => {
const info = lunarInfo.value
return info.solarFestival || info.lunarFestival || info.termName || info.lunarText
})
/** 当天是否有节日或节气(用于高亮显示农历文字) */
const hasFestivalDay = computed(() => {
const info = lunarInfo.value
return !!(info.solarFestival || info.lunarFestival || info.termName)
})
</script>

View File

@ -1,209 +0,0 @@
<!-- 排班日历 - 按分类视图 -->
<!-- todo @ai是不是去掉开头的 calendar因为大家都是呀 -->
<template>
<div class="flex">
<!-- 左侧班组类型选择 -->
<div class="w-150px shrink-0 mr-12px border border-solid border-#dcdfe6 rounded-4px overflow-hidden">
<div
v-for="dict in getIntDictOptions(DICT_TYPE.MES_CAL_CALENDAR_TYPE)"
:key="dict.value"
class="px-16px py-10px cursor-pointer text-14px text-#606266 border-b border-b-solid border-b-#ebeef5 last:border-b-0 hover:bg-#f5f7fa transition-colors"
:class="selectedType === dict.value ? 'bg-#ecf5ff text-#409eff font-500' : ''"
@click="selectedType = dict.value; onTypeSelected()"
>
{{ dict.label }}
</div>
</div>
<!-- 右侧日历 -->
<div class="flex-1">
<el-calendar v-model="currentDate" v-loading="loading">
<template #date-cell="{ data }">
<div class="h-full p-4px">
<div class="flex justify-between items-center">
<span class="text-16px font-500" :class="{ 'text-#f56c6c': isWeekend(data.day) }">
{{ data.day.split('-')[2] }}
</span>
<el-tag v-if="holidaySet.has(data.day)" size="small" effect="dark" type="success">
</el-tag>
<el-tag v-else size="small" effect="dark"> </el-tag>
</div>
<div
class="text-12px text-#909399 mt-2px"
:class="{ 'text-#67c23a': hasFestival(data.day) }"
>
{{ getLunarDisplay(data.day) }}
</div>
<!-- 班次信息 -->
<template v-if="!holidaySet.has(data.day)">
<div
v-for="item in getTeamShiftsForDay(data.day)"
:key="item.sort"
class="mt-2px"
>
<el-button
v-if="item.sort === 1"
type="success"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
<el-button
v-else-if="item.sort === 2"
:type="getShiftTypeForDay(data.day) === MesCalShiftTypeEnum.THREE ? 'warning' : 'info'"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
<el-button
v-else-if="item.sort === 3"
type="info"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
</div>
</template>
</div>
</template>
</el-calendar>
</div>
</div>
</template>
<script setup lang="ts">
import { CalCalendarApi, CalCalendarDayVO } from '@/api/mes/cal/calendar'
import { CalHolidayApi, CalHolidayVO } from '@/api/mes/cal/holiday'
import { formatDate } from '@/utils/formatTime'
import { SolarDay } from 'tyme4ts'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { HolidayType, MesCalShiftTypeEnum } from '@/views/mes/utils/constants'
const loading = ref(false)
const currentDate = ref(new Date())
const selectedType = ref<number>()
const calendarDayMap = ref<Map<string, CalCalendarDayVO>>(new Map())
const holidaySet = ref(new Set<string>())
/** 获取假期列表 */
const getHolidayList = async () => {
holidaySet.value.clear()
const list = await CalHolidayApi.getHolidayList()
if (list) {
list.forEach((item: CalHolidayVO) => {
const day = item.day ? formatDate(item.day as any, 'YYYY-MM-DD') : ''
if (day && item.type === HolidayType.HOLIDAY) {
holidaySet.value.add(day)
}
})
}
}
/** 查询排班日历 */
const fetchCalendar = async () => {
if (!selectedType.value) {
return
}
loading.value = true
try {
//
const date = currentDate.value
const year = date.getFullYear()
const month = date.getMonth()
const startDay = new Date(year, month, 1)
const endDay = new Date(year, month + 1, 0, 23, 59, 59)
const list = await CalCalendarApi.getCalendarList({
queryType: 'TYPE',
calendarType: selectedType.value,
startDay: formatDate(startDay, 'YYYY-MM-DD HH:mm:ss'),
endDay: formatDate(endDay, 'YYYY-MM-DD HH:mm:ss')
})
calendarDayMap.value.clear()
if (list) {
list.forEach((item: CalCalendarDayVO) => {
calendarDayMap.value.set(item.day, item)
})
}
} finally {
loading.value = false
}
}
/** 选择分类 */
const onTypeSelected = () => {
fetchCalendar()
}
/** 获取指定日期的班次列表 */
const getTeamShiftsForDay = (day: string) => {
const calDay = calendarDayMap.value.get(day)
return calDay?.teamShifts || []
}
/** 获取指定日期的轮班方式 */
const getShiftTypeForDay = (day: string) => {
const calDay = calendarDayMap.value.get(day)
return calDay?.shiftType
}
/** 判断是否周末 */
const isWeekend = (day: string): boolean => {
const date = new Date(day)
const weekDay = date.getDay()
return weekDay === 0 || weekDay === 6
}
/** 获取农历显示信息 */
const getLunarInfo = (day: string) => {
const parts = day.split('-')
const year = parseInt(parts[0])
const month = parseInt(parts[1])
const date = parseInt(parts[2])
try {
const solarDay = SolarDay.fromYmd(year, month, date)
const lunarDay = solarDay.getLunarDay()
const solarFestival = solarDay.getFestival()
const lunarFestival = lunarDay.getFestival()
const termDay = solarDay.getTermDay()
const termName = termDay.getDayIndex() === 0 ? termDay.getSolarTerm().getName() : null
const lunarMonthName = lunarDay.getLunarMonth().getName()
const lunarDayName = lunarDay.getName()
return {
solarFestival: solarFestival ? solarFestival.getName() : null,
lunarFestival: lunarFestival ? lunarFestival.getName() : null,
termName,
lunarText: lunarMonthName + lunarDayName
}
} catch {
return { solarFestival: null, lunarFestival: null, termName: null, lunarText: '' }
}
}
/** 获取农历显示文本 */
const getLunarDisplay = (day: string): string => {
const info = getLunarInfo(day)
return info.solarFestival || info.lunarFestival || info.termName || info.lunarText
}
/** 判断是否有节日 */
const hasFestival = (day: string): boolean => {
const info = getLunarInfo(day)
return !!(info.solarFestival || info.lunarFestival || info.termName)
}
/** 监听月份变化 */
watch(currentDate, () => {
if (selectedType.value) {
fetchCalendar()
}
})
/** 初始化 */
onMounted(() => {
getHolidayList()
})
</script>

View File

@ -1,209 +0,0 @@
<!-- 排班日历 - 按个人视图 -->
<!-- TODO @AI: user > person 文件名 -->
<template>
<div>
<!-- 顶部用户选择 -->
<el-form :inline="true" label-width="80px" class="mb-10px">
<el-form-item label="用户编号">
<!-- TODO @AI下拉选择 -->
<el-input-number
v-model="userId"
placeholder="请输入用户编号"
:min="1"
controls-position="right"
class="!w-200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onUserQuery">
<Icon icon="ep:search" class="mr-5px" /> 查询
</el-button>
</el-form-item>
</el-form>
<!-- 日历 -->
<el-calendar v-model="currentDate" v-loading="loading">
<template #date-cell="{ data }">
<div class="h-full p-4px">
<div class="flex justify-between items-center">
<span class="text-16px font-500" :class="{ 'text-#f56c6c': isWeekend(data.day) }">
{{ data.day.split('-')[2] }}
</span>
<el-tag v-if="holidaySet.has(data.day)" size="small" effect="dark" type="success">
</el-tag>
<el-tag v-else size="small" effect="dark"> </el-tag>
</div>
<div
class="text-12px text-#909399 mt-2px"
:class="{ 'text-#67c23a': hasFestival(data.day) }"
>
{{ getLunarDisplay(data.day) }}
</div>
<!-- 班次信息 -->
<template v-if="!holidaySet.has(data.day)">
<div
v-for="item in getTeamShiftsForDay(data.day)"
:key="item.sort"
class="mt-2px"
>
<el-button
v-if="item.sort === 1"
type="success"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
<el-button
v-else-if="item.sort === 2"
:type="getShiftTypeForDay(data.day) === MesCalShiftTypeEnum.THREE ? 'warning' : 'info'"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
<el-button
v-else-if="item.sort === 3"
type="info"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
</div>
</template>
</div>
</template>
</el-calendar>
</div>
</template>
<script setup lang="ts">
import { CalCalendarApi, CalCalendarDayVO } from '@/api/mes/cal/calendar'
import { CalHolidayApi, CalHolidayVO } from '@/api/mes/cal/holiday'
import { formatDate } from '@/utils/formatTime'
import { SolarDay } from 'tyme4ts'
import { HolidayType, MesCalShiftTypeEnum } from '@/views/mes/utils/constants'
const loading = ref(false)
const currentDate = ref(new Date())
const userId = ref<number>()
const calendarDayMap = ref<Map<string, CalCalendarDayVO>>(new Map())
const holidaySet = ref(new Set<string>())
/** 获取假期列表 */
const getHolidayList = async () => {
holidaySet.value.clear()
const list = await CalHolidayApi.getHolidayList()
if (list) {
list.forEach((item: CalHolidayVO) => {
const day = item.day ? formatDate(item.day as any, 'YYYY-MM-DD') : ''
if (day && item.type === HolidayType.HOLIDAY) {
holidaySet.value.add(day)
}
})
}
}
/** 查询排班日历 */
const fetchCalendar = async () => {
if (!userId.value) return
loading.value = true
try {
const date = currentDate.value
const year = date.getFullYear()
const month = date.getMonth()
const startDay = new Date(year, month, 1)
const endDay = new Date(year, month + 1, 0, 23, 59, 59)
const list = await CalCalendarApi.getCalendarList({
queryType: 'USER',
userId: userId.value,
startDay: formatDate(startDay, 'YYYY-MM-DD HH:mm:ss'),
endDay: formatDate(endDay, 'YYYY-MM-DD HH:mm:ss')
})
calendarDayMap.value.clear()
if (list) {
list.forEach((item: CalCalendarDayVO) => {
calendarDayMap.value.set(item.day, item)
})
}
} finally {
loading.value = false
}
}
/** 查询按钮 */
const onUserQuery = () => {
fetchCalendar()
}
/** 获取指定日期的班次列表 */
const getTeamShiftsForDay = (day: string) => {
const calDay = calendarDayMap.value.get(day)
return calDay?.teamShifts || []
}
/** 获取指定日期的轮班方式 */
const getShiftTypeForDay = (day: string) => {
const calDay = calendarDayMap.value.get(day)
return calDay?.shiftType
}
/** 判断是否周末 */
const isWeekend = (day: string): boolean => {
const date = new Date(day)
const weekDay = date.getDay()
return weekDay === 0 || weekDay === 6
}
/** 获取农历显示信息 */
const getLunarInfo = (day: string) => {
const parts = day.split('-')
const year = parseInt(parts[0])
const month = parseInt(parts[1])
const date = parseInt(parts[2])
try {
const solarDay = SolarDay.fromYmd(year, month, date)
const lunarDay = solarDay.getLunarDay()
const solarFestival = solarDay.getFestival()
const lunarFestival = lunarDay.getFestival()
const termDay = solarDay.getTermDay()
const termName = termDay.getDayIndex() === 0 ? termDay.getSolarTerm().getName() : null
const lunarMonthName = lunarDay.getLunarMonth().getName()
const lunarDayName = lunarDay.getName()
return {
solarFestival: solarFestival ? solarFestival.getName() : null,
lunarFestival: lunarFestival ? lunarFestival.getName() : null,
termName,
lunarText: lunarMonthName + lunarDayName
}
} catch {
return { solarFestival: null, lunarFestival: null, termName: null, lunarText: '' }
}
}
/** 获取农历显示文本 */
const getLunarDisplay = (day: string): string => {
const info = getLunarInfo(day)
return info.solarFestival || info.lunarFestival || info.termName || info.lunarText
}
/** 判断是否有节日 */
const hasFestival = (day: string): boolean => {
const info = getLunarInfo(day)
return !!(info.solarFestival || info.lunarFestival || info.termName)
}
//
watch(currentDate, () => {
if (userId.value) {
fetchCalendar()
}
})
/** 初始化 */
onMounted(() => {
getHolidayList()
})
</script>

View File

@ -2,7 +2,11 @@
<template>
<div class="flex">
<!-- 左侧班组列表选择 -->
<div class="w-150px shrink-0 mr-12px border border-solid border-#dcdfe6 rounded-4px overflow-hidden">
<!-- TODO @AI默认选中首个 -->
<div
class="w-150px shrink-0 mr-12px border border-solid border-#dcdfe6 rounded-4px overflow-hidden"
>
<!-- TODO @AI可以把 @click 在封装下么更统一一些 -->
<div
v-for="team in teamList"
:key="team.id"
@ -18,56 +22,11 @@
<div class="flex-1">
<el-calendar v-model="currentDate" v-loading="loading">
<template #date-cell="{ data }">
<div class="h-full p-4px">
<div class="flex justify-between items-center">
<span class="text-16px font-500" :class="{ 'text-#f56c6c': isWeekend(data.day) }">
{{ data.day.split('-')[2] }}
</span>
<el-tag v-if="holidaySet.has(data.day)" size="small" effect="dark" type="success">
</el-tag>
<el-tag v-else size="small" effect="dark"> </el-tag>
</div>
<div
class="text-12px text-#909399 mt-2px"
:class="{ 'text-#67c23a': hasFestival(data.day) }"
>
{{ getLunarDisplay(data.day) }}
</div>
<!-- 班次信息 -->
<template v-if="!holidaySet.has(data.day)">
<div
v-for="item in getTeamShiftsForDay(data.day)"
:key="item.sort"
class="mt-2px"
>
<el-button
v-if="item.sort === 1"
type="success"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
<el-button
v-else-if="item.sort === 2"
:type="getShiftTypeForDay(data.day) === MesCalShiftTypeEnum.THREE ? 'warning' : 'info'"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
<el-button
v-else-if="item.sort === 3"
type="info"
size="small"
class="!w-full !text-12px"
>
{{ item.teamName }}
</el-button>
</div>
</template>
</div>
<CalendarDateCell
:day="data.day"
:holiday-set="holidaySet"
:calendar-day-map="calendarDayMap"
/>
</template>
</el-calendar>
</div>
@ -79,40 +38,42 @@ import { CalCalendarApi, CalCalendarDayVO } from '@/api/mes/cal/calendar'
import { CalTeamApi, CalTeamVO } from '@/api/mes/cal/team'
import { CalHolidayApi, CalHolidayVO } from '@/api/mes/cal/holiday'
import { formatDate } from '@/utils/formatTime'
import { SolarDay } from 'tyme4ts'
import { HolidayType, MesCalShiftTypeEnum } from '@/views/mes/utils/constants'
import { HolidayType } from '@/views/mes/utils/constants'
import CalendarDateCell from './CalendarDateCell.vue'
const loading = ref(false)
const currentDate = ref(new Date())
const selectedTeamId = ref<number>()
const teamList = ref<CalTeamVO[]>([])
const calendarDayMap = ref<Map<string, CalCalendarDayVO>>(new Map())
const holidaySet = ref(new Set<string>())
const currentDate = ref(new Date()) //
const selectedTeamId = ref<number>() //
const teamList = ref<CalTeamVO[]>([]) //
const calendarDayMap = ref<Map<string, CalCalendarDayVO>>(new Map()) // key: yyyy-MM-dd
const holidaySet = ref(new Set<string>()) // key: yyyy-MM-dd
/** 获取班组列表 */
const getTeamList = async () => {
teamList.value = await CalTeamApi.getTeamList()
}
/** 获取假期列表 */
/** 获取节假日列表,构建节假日日期集合 */
const getHolidayList = async () => {
holidaySet.value.clear()
const list = await CalHolidayApi.getHolidayList()
if (list) {
list.forEach((item: CalHolidayVO) => {
const day = item.day ? formatDate(item.day as any, 'YYYY-MM-DD') : ''
if (day && item.type === HolidayType.HOLIDAY) {
holidaySet.value.add(day)
}
})
if (!list) {
return
}
list.forEach((item: CalHolidayVO) => {
const day = item.day ? formatDate(item.day as any, 'YYYY-MM-DD') : ''
if (day && item.type === HolidayType.HOLIDAY) {
holidaySet.value.add(day)
}
})
}
/** 查询排班日历 */
/** 查询当前月份的排班日历,按选中班组过滤 */
const fetchCalendar = async () => {
if (!selectedTeamId.value) return
loading.value = true
try {
//
const date = currentDate.value
const year = date.getFullYear()
const month = date.getMonth()
@ -124,80 +85,25 @@ const fetchCalendar = async () => {
startDay: formatDate(startDay, 'YYYY-MM-DD HH:mm:ss'),
endDay: formatDate(endDay, 'YYYY-MM-DD HH:mm:ss')
})
// Map 便
calendarDayMap.value.clear()
if (list) {
list.forEach((item: CalCalendarDayVO) => {
calendarDayMap.value.set(item.day, item)
})
if (!list) {
return
}
list.forEach((item: CalCalendarDayVO) => {
calendarDayMap.value.set(item.day, item)
})
} finally {
loading.value = false
}
}
/** 选择班组 */
/** 选择班组后刷新日历 */
const onTeamSelected = () => {
fetchCalendar()
}
/** 获取指定日期的班次列表 */
const getTeamShiftsForDay = (day: string) => {
const calDay = calendarDayMap.value.get(day)
return calDay?.teamShifts || []
}
/** 获取指定日期的轮班方式 */
const getShiftTypeForDay = (day: string) => {
const calDay = calendarDayMap.value.get(day)
return calDay?.shiftType
}
/** 判断是否周末 */
const isWeekend = (day: string): boolean => {
const date = new Date(day)
const weekDay = date.getDay()
return weekDay === 0 || weekDay === 6
}
/** 获取农历显示信息 */
const getLunarInfo = (day: string) => {
const parts = day.split('-')
const year = parseInt(parts[0])
const month = parseInt(parts[1])
const date = parseInt(parts[2])
try {
const solarDay = SolarDay.fromYmd(year, month, date)
const lunarDay = solarDay.getLunarDay()
const solarFestival = solarDay.getFestival()
const lunarFestival = lunarDay.getFestival()
const termDay = solarDay.getTermDay()
const termName = termDay.getDayIndex() === 0 ? termDay.getSolarTerm().getName() : null
const lunarMonthName = lunarDay.getLunarMonth().getName()
const lunarDayName = lunarDay.getName()
return {
solarFestival: solarFestival ? solarFestival.getName() : null,
lunarFestival: lunarFestival ? lunarFestival.getName() : null,
termName,
lunarText: lunarMonthName + lunarDayName
}
} catch {
return { solarFestival: null, lunarFestival: null, termName: null, lunarText: '' }
}
}
/** 获取农历显示文本 */
const getLunarDisplay = (day: string): string => {
const info = getLunarInfo(day)
return info.solarFestival || info.lunarFestival || info.termName || info.lunarText
}
/** 判断是否有节日 */
const hasFestival = (day: string): boolean => {
const info = getLunarInfo(day)
return !!(info.solarFestival || info.lunarFestival || info.termName)
}
//
/** 监听月份切换,重新加载当月排班 */
watch(currentDate, () => {
if (selectedTeamId.value) {
fetchCalendar()

View File

@ -0,0 +1,114 @@
<!-- 排班日历 - 按分类视图 -->
<template>
<div class="flex">
<!-- 左侧班组类型选择 -->
<!-- TODO @AI默认选中首个 -->
<div
class="w-150px shrink-0 mr-12px border border-solid border-#dcdfe6 rounded-4px overflow-hidden"
>
<!-- TODO @AI可以把 @click 在封装下么更统一一些 -->
<div
v-for="dict in getIntDictOptions(DICT_TYPE.MES_CAL_CALENDAR_TYPE)"
:key="dict.value"
class="px-16px py-10px cursor-pointer text-14px text-#606266 border-b border-b-solid border-b-#ebeef5 last:border-b-0 hover:bg-#f5f7fa transition-colors"
:class="selectedType === dict.value ? 'bg-#ecf5ff text-#409eff font-500' : ''"
@click="
selectedType = dict.value
onTypeSelected()
"
>
{{ dict.label }}
</div>
</div>
<!-- 右侧日历 -->
<div class="flex-1">
<el-calendar v-model="currentDate" v-loading="loading">
<template #date-cell="{ data }">
<CalendarDateCell
:day="data.day"
:holiday-set="holidaySet"
:calendar-day-map="calendarDayMap"
/>
</template>
</el-calendar>
</div>
</div>
</template>
<script setup lang="ts">
import { CalCalendarApi, CalCalendarDayVO } from '@/api/mes/cal/calendar'
import { CalHolidayApi, CalHolidayVO } from '@/api/mes/cal/holiday'
import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { HolidayType } from '@/views/mes/utils/constants'
import CalendarDateCell from './CalendarDateCell.vue'
const loading = ref(false)
const currentDate = ref(new Date()) //
const selectedType = ref<number>() // MES_CAL_CALENDAR_TYPE
const calendarDayMap = ref<Map<string, CalCalendarDayVO>>(new Map()) // key: yyyy-MM-dd
const holidaySet = ref(new Set<string>()) // key: yyyy-MM-dd
/** 获取节假日列表,构建节假日日期集合 */
const getHolidayList = async () => {
holidaySet.value.clear()
const list = await CalHolidayApi.getHolidayList()
if (!list) {
return
}
list.forEach((item: CalHolidayVO) => {
const day = item.day ? formatDate(item.day as any, 'YYYY-MM-DD') : ''
if (day && item.type === HolidayType.HOLIDAY) {
holidaySet.value.add(day)
}
})
}
/** 查询当前月份的排班日历,按选中分类过滤 */
const fetchCalendar = async () => {
if (!selectedType.value) return
loading.value = true
try {
//
const date = currentDate.value
const year = date.getFullYear()
const month = date.getMonth()
const startDay = new Date(year, month, 1)
const endDay = new Date(year, month + 1, 0, 23, 59, 59)
const list = await CalCalendarApi.getCalendarList({
queryType: 'TYPE',
calendarType: selectedType.value,
startDay: formatDate(startDay, 'YYYY-MM-DD HH:mm:ss'),
endDay: formatDate(endDay, 'YYYY-MM-DD HH:mm:ss')
})
// Map 便
calendarDayMap.value.clear()
if (!list) {
return
}
list.forEach((item: CalCalendarDayVO) => {
calendarDayMap.value.set(item.day, item)
})
} finally {
loading.value = false
}
}
/** 选择分类后刷新日历 */
const onTypeSelected = () => {
fetchCalendar()
}
/** 监听月份切换,重新加载当月排班 */
watch(currentDate, () => {
if (selectedType.value) {
fetchCalendar()
}
})
/** 初始化 */
onMounted(() => {
getHolidayList()
})
</script>

View File

@ -0,0 +1,119 @@
<!-- 排班日历 - 按个人视图 -->
<template>
<div>
<!-- 顶部人员选择 -->
<el-form :inline="true" label-width="80px" class="mb-10px">
<el-form-item label="人员">
<el-select
v-model="userId"
filterable
placeholder="请选择人员"
class="!w-200px"
@change="onUserQuery"
>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onUserQuery">
<Icon icon="ep:search" class="mr-5px" /> 查询
</el-button>
</el-form-item>
</el-form>
<!-- 日历 -->
<el-calendar v-model="currentDate" v-loading="loading">
<template #date-cell="{ data }">
<CalendarDateCell
:day="data.day"
:holiday-set="holidaySet"
:calendar-day-map="calendarDayMap"
/>
</template>
</el-calendar>
</div>
</template>
<script setup lang="ts">
import { CalCalendarApi, CalCalendarDayVO } from '@/api/mes/cal/calendar'
import { CalHolidayApi, CalHolidayVO } from '@/api/mes/cal/holiday'
import { getSimpleUserList, UserVO } from '@/api/system/user'
import { formatDate } from '@/utils/formatTime'
import { HolidayType } from '@/views/mes/utils/constants'
import CalendarDateCell from './CalendarDateCell.vue'
const loading = ref(false)
const currentDate = ref(new Date()) //
const userId = ref<number>() //
const userList = ref<UserVO[]>([]) //
const calendarDayMap = ref<Map<string, CalCalendarDayVO>>(new Map()) // key: yyyy-MM-dd
const holidaySet = ref(new Set<string>()) // key: yyyy-MM-dd
/** 获取节假日列表,构建节假日日期集合 */
const getHolidayList = async () => {
holidaySet.value.clear()
const list = await CalHolidayApi.getHolidayList()
if (!list) {
return
}
list.forEach((item: CalHolidayVO) => {
const day = item.day ? formatDate(item.day as any, 'YYYY-MM-DD') : ''
if (day && item.type === HolidayType.HOLIDAY) {
holidaySet.value.add(day)
}
})
}
/** 查询当前月份的排班日历,按选中人员过滤 */
const fetchCalendar = async () => {
if (!userId.value) return
loading.value = true
try {
//
const date = currentDate.value
const year = date.getFullYear()
const month = date.getMonth()
const startDay = new Date(year, month, 1)
const endDay = new Date(year, month + 1, 0, 23, 59, 59)
const list = await CalCalendarApi.getCalendarList({
queryType: 'USER',
userId: userId.value,
startDay: formatDate(startDay, 'YYYY-MM-DD HH:mm:ss'),
endDay: formatDate(endDay, 'YYYY-MM-DD HH:mm:ss')
})
// Map 便
calendarDayMap.value.clear()
if (!list) {
return
}
list.forEach((item: CalCalendarDayVO) => {
calendarDayMap.value.set(item.day, item)
})
} finally {
loading.value = false
}
}
/** 查询按钮 / 下拉选人后刷新日历 */
const onUserQuery = () => {
fetchCalendar()
}
/** 监听月份切换,重新加载当月排班 */
watch(currentDate, () => {
if (userId.value) {
fetchCalendar()
}
})
/** 初始化 */
onMounted(async () => {
userList.value = await getSimpleUserList()
await getHolidayList()
})
</script>

View File

@ -2,24 +2,23 @@
<template>
<ContentWrap>
<el-tabs v-model="activeTab" type="border-card">
<!-- TODO @AI下面三个组件都有日历是不是可以复用下 -->
<el-tab-pane label="按分类" name="type">
<CalendarTypeView />
<TypeView />
</el-tab-pane>
<el-tab-pane label="按班组" name="team">
<TeamView />
</el-tab-pane>
<el-tab-pane label="按个人" name="user">
<PersonView />
<UserView />
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script setup lang="ts">
import CalendarTypeView from './CalendarTypeView.vue'
import TypeView from './TypeView.vue'
import TeamView from './TeamView.vue'
import PersonView from './PersonView.vue'
import UserView from './UserView.vue'
defineOptions({ name: 'MesCalCalendar' })