review(mes):cal-calender 日历功能

pull/871/MERGE
YunaiV 2026-02-19 12:23:18 +08:00
parent b8254baf5a
commit e7a8095a35
5 changed files with 675 additions and 0 deletions

View File

@ -0,0 +1,25 @@
import request from '@/config/axios'
// 排班日历 - 班组排班项
export interface CalCalendarTeamShiftItem {
teamId: number
teamName: string
shiftId: number
shiftName: string
sort: number
}
// 排班日历 - 日历天 VO
export interface CalCalendarDayVO {
day: string // yyyy-MM-dd
shiftType: number // 轮班方式
teamShifts: CalCalendarTeamShiftItem[]
}
// 排班日历 API
export const CalCalendarApi = {
// 查询排班日历列表
getCalendarList: async (params: any) => {
return await request.get({ url: `/mes/cal/calendar/list`, params })
}
}

View File

@ -0,0 +1,206 @@
<!-- 排班日历 - 按分类视图 -->
<template>
<div class="flex">
<!-- 左侧班组类型选择 -->
<div class="w-150px mr-10px">
<el-radio-group v-model="selectedType" class="flex flex-col" @change="onTypeSelected">
<el-radio-button
v-for="dict in getIntDictOptions(DICT_TYPE.MES_CAL_CALENDAR_TYPE)"
:key="dict.value"
:value="dict.value"
class="!rounded-0 !border-b-1px !border-b-solid !border-b-gray-200"
>
{{ dict.label }}
</el-radio-button>
</el-radio-group>
</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

@ -0,0 +1,206 @@
<!-- 排班日历 - 按个人视图 -->
<template>
<div>
<!-- 顶部用户选择 -->
<el-form :inline="true" label-width="80px" class="mb-10px">
<el-form-item label="用户编号">
<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

@ -0,0 +1,212 @@
<!-- 排班日历 - 按班组视图 -->
<template>
<div class="flex">
<!-- 左侧班组列表选择 -->
<div class="w-150px mr-10px">
<el-radio-group v-model="selectedTeamId" class="flex flex-col" @change="onTeamSelected">
<el-radio-button
v-for="team in teamList"
:key="team.id"
:value="team.id"
class="!rounded-0 !border-b-1px !border-b-solid !border-b-gray-200"
>
{{ team.name }}
</el-radio-button>
</el-radio-group>
</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 { 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'
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 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)
}
})
}
}
/** 查询排班日历 */
const fetchCalendar = async () => {
if (!selectedTeamId.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: 'TEAM',
teamId: selectedTeamId.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 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()
}
})
/** 初始化 */
onMounted(() => {
getTeamList()
getHolidayList()
})
</script>

View File

@ -0,0 +1,26 @@
<!-- MES 排班日历 -->
<template>
<ContentWrap>
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="按分类" name="type">
<CalendarTypeView />
</el-tab-pane>
<el-tab-pane label="按班组" name="team">
<TeamView />
</el-tab-pane>
<el-tab-pane label="按个人" name="user">
<PersonView />
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script setup lang="ts">
import CalendarTypeView from './CalendarTypeView.vue'
import TeamView from './TeamView.vue'
import PersonView from './PersonView.vue'
defineOptions({ name: 'MesCalCalendar' })
const activeTab = ref('type')
</script>