修复登录报错提示内容错误,增加节假日同步功能

This commit is contained in:
qwq
2026-06-04 15:53:23 +08:00
parent 4a8f530921
commit cd63391f89
84 changed files with 874 additions and 31 deletions
+5 -2
View File
@@ -21,7 +21,7 @@ api.interceptors.response.use(
response => response.data,
error => {
const msg = error.response?.data?.message || '请求失败'
if (error.response?.status === 401) {
if (error.response?.status === 401 && !error.config.url.includes('/auth/login')) {
localStorage.removeItem('token')
localStorage.removeItem('user')
router.push('/login')
@@ -49,6 +49,8 @@ export const adminApi = {
deleteUser: (id) => api.delete(`/admin/users/${id}`),
getWorkdays: (year) => api.get('/admin/workdays', { params: { year } }),
setWorkdays: (data) => api.post('/admin/workdays', data),
syncWorkdays: (year) => api.post('/admin/workdays/sync', { year }),
getHolidays: (year) => api.get('/admin/holidays', { params: { year } }),
getSalesStats: (params) => api.get('/admin/stats/sales', { params }),
getSalesDetail: (params) => api.get('/admin/stats/sales-detail', { params }),
getRdStats: (params) => api.get('/admin/stats/rd', { params }),
@@ -111,5 +113,6 @@ export const rdApi = {
createTimesheet: (data) => api.post('/rd/timesheets', data),
updateTimesheet: (id, data) => api.put(`/rd/timesheets/${id}`, data),
deleteTimesheet: (id) => api.delete(`/rd/timesheets/${id}`),
getStats: (params) => api.get('/rd/stats', { params })
getStats: (params) => api.get('/rd/stats', { params }),
getHolidays: (year) => api.get('/rd/holidays', { params: { year } })
}
+60 -14
View File
@@ -265,14 +265,14 @@
</div>
<!-- Inline Calendar -->
<el-calendar v-model="adminCalendarDate">
<el-calendar v-model="adminCalendarDate" :key="adminCalendarKey">
<template #date-cell="{ data }">
<div class="day-cell-content" @click="handleAdminSelectDate(data.day)">
<div class="day-header">
<span class="day-num">{{ data.day.split('-')[2] }}</span>
<span v-if="isWeekend(data.day)" class="weekend-tag"></span>
<span v-if="data.type === 'current-month' && isRestDay(data.day)" class="weekend-tag"></span>
</div>
<div class="day-body">
<div class="day-body" v-if="data.type === 'current-month'">
<div v-if="getAdminDayHours(data.day) >= 8" class="status-badge filled">
{{ getAdminDayHours(data.day) }}h
</div>
@@ -731,6 +731,7 @@ const adminSelectedDate = ref(new Date().toLocaleDateString('sv-SE', { timeZone:
const calendarRawRecords = ref([])
const gridData = ref([])
const adminRecordsMap = ref({})
const adminCalendarKey = ref(0)
const selectedDeveloperName = computed(() => {
const found = rdUsers.value.find(u => u.id === calendarFilter.user_id)
@@ -756,6 +757,19 @@ const getGridCellHours = (row, day) => {
return hours > 0 ? hours : ''
}
const holidaysMap = ref({})
const loadedHolidayYear = ref(null)
async function loadHolidays(year) {
try {
const res = await adminApi.getHolidays(year)
holidaysMap.value = res.data || {}
adminCalendarKey.value++
} catch (e) {
console.error('Failed to load holidays:', e)
}
}
const getGridCellClass = (row, day) => {
const dStr = `${calendarFilter.month}-${String(day).padStart(2, '0')}`
const hours = row.dates[dStr]?.hours || 0
@@ -764,7 +778,7 @@ const getGridCellClass = (row, day) => {
} else if (hours > 0) {
return 'cell-partial'
} else {
if (dStr < todayStr.value && !isWeekend(dStr)) {
if (dStr < todayStr.value && !isRestDay(dStr)) {
return 'cell-unfilled'
}
return 'cell-neutral'
@@ -777,7 +791,7 @@ const getGridCellTitle = (row, day) => {
if (hours > 0) {
return `${row.user_name}${dStr} 已填报 ${hours}h`
} else {
if (dStr < todayStr.value && !isWeekend(dStr)) {
if (dStr < todayStr.value && !isRestDay(dStr)) {
return `${row.user_name}${dStr} 漏填报`
}
return `${row.user_name}${dStr} 无需填报/未到期`
@@ -789,9 +803,17 @@ const getDayOfWeek = (dayStr) => {
return d.getDay()
}
const isWeekend = (dayStr) => {
const isWorkday = (dayStr) => {
const mmDd = dayStr.substring(5)
if (holidaysMap.value && holidaysMap.value[mmDd]) {
return !holidaysMap.value[mmDd].holiday
}
const day = getDayOfWeek(dayStr)
return day === 0 || day === 6
return day !== 0 && day !== 6
}
const isRestDay = (dayStr) => {
return !isWorkday(dayStr)
}
const getAdminDayHours = (dayStr) => {
@@ -800,7 +822,7 @@ const getAdminDayHours = (dayStr) => {
const isAdminPastUnfilled = (dayStr) => {
if (dayStr >= todayStr.value) return false
if (isWeekend(dayStr)) return false
if (isRestDay(dayStr)) return false
return getAdminDayHours(dayStr) === 0
}
@@ -811,11 +833,19 @@ const adminSelectedDateRecords = computed(() => {
async function loadCalendarData() {
calendarLoading.value = true
try {
const [year, month] = calendarFilter.month.split('-')
const [yearStr, monthStr] = calendarFilter.month.split('-')
const year = parseInt(yearStr)
const month = parseInt(monthStr)
const daysCount = new Date(year, month, 0).getDate()
const start_date = `${calendarFilter.month}-01`
const end_date = `${calendarFilter.month}-${String(daysCount).padStart(2, '0')}`
// Fetch holidays if year changes
if (loadedHolidayYear.value !== year) {
loadedHolidayYear.value = year
await loadHolidays(year)
}
const res = await adminApi.getRdStats({ start_date, end_date })
calendarRawRecords.value = res.data || []
buildGridData()
@@ -856,10 +886,12 @@ function buildGridData() {
userMap[uid].total_hours += r.hours
})
gridData.value = Object.values(userMap).map(u => {
u.total_hours = Math.round(u.total_hours * 10) / 10
return u
})
gridData.value = Object.values(userMap)
.map(u => {
u.total_hours = Math.round(u.total_hours * 10) / 10
return u
})
.filter(u => u.total_hours > 0)
}
function handleCalendarFilterChange() {
@@ -889,6 +921,7 @@ function loadIndividualCalendarData() {
}
})
adminRecordsMap.value = map
adminCalendarKey.value++
const [cy, cm] = calendarFilter.month.split('-').map(Number)
const currentCalDate = adminCalendarDate.value
@@ -926,6 +959,19 @@ watch(adminCalendarDate, (newDate) => {
}
})
watch(rdUsers, () => {
buildGridData()
})
watch(calendarViewMode, (newMode) => {
if (newMode === 'individual') {
if (!calendarFilter.user_id && rdUsers.value.length) {
calendarFilter.user_id = rdUsers.value[0].id
}
loadIndividualCalendarData()
}
})
const individualStats = computed(() => {
const [year, month] = calendarFilter.month.split('-').map(Number)
const daysCount = new Date(year, month, 0).getDate()
@@ -945,7 +991,7 @@ const individualStats = computed(() => {
} else if (hours > 0) {
partialDays++
} else {
if (dStr < todayStr.value && !isWeekend(dStr)) {
if (dStr < todayStr.value && !isRestDay(dStr)) {
unfilledDays++
}
}
+14 -1
View File
@@ -7,10 +7,11 @@
<div class="content-card">
<div class="card-title">
<div class="filter-bar" style="margin-bottom: 0">
<div class="filter-bar" style="margin-bottom: 0; display: flex; gap: 8px;">
<el-select v-model="selectedYear" @change="loadWorkdays" style="width: 120px">
<el-option v-for="y in years" :key="y" :label="`${y}年`" :value="y" />
</el-select>
<el-button type="warning" :loading="syncing" @click="handleSyncHolidays">🔄 联网自动同步节假日</el-button>
</div>
<el-button type="primary" :loading="saving" @click="handleSave">保存设置</el-button>
</div>
@@ -42,6 +43,18 @@ const currentYear = new Date().getFullYear()
const years = Array.from({ length: 5 }, (_, i) => currentYear - 2 + i)
const selectedYear = ref(currentYear)
const saving = ref(false)
const syncing = ref(false)
async function handleSyncHolidays() {
syncing.value = true
try {
const res = await adminApi.syncWorkdays(selectedYear.value)
ElMessage.success(res.message || '同步成功')
loadWorkdays()
} catch (e) {
// Handled by axios interceptor toast, but let's log or recover
} finally { syncing.value = false }
}
const workdays = ref(
Array.from({ length: 12 }, (_, i) => ({ month: i + 1, days: 22 }))
+38 -9
View File
@@ -19,14 +19,14 @@
</div>
</div>
<el-calendar v-model="calendarDate">
<el-calendar v-model="calendarDate" :key="calendarKey">
<template #date-cell="{ data }">
<div class="day-cell-content" @click="handleSelectDate(data.day)">
<div class="day-header">
<span class="day-num">{{ data.day.split('-')[2] }}</span>
<span v-if="isWeekend(data.day)" class="weekend-tag"></span>
<span v-if="data.type === 'current-month' && isRestDay(data.day)" class="weekend-tag"></span>
</div>
<div class="day-body">
<div class="day-body" v-if="data.type === 'current-month'">
<div v-if="getDayHours(data.day) >= 8" class="status-badge filled">
{{ getDayHours(data.day) }}h
</div>
@@ -113,6 +113,7 @@ const todayStr = ref(new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Sh
const recordsMap = ref({}) // date -> { hours, list }
const loading = ref(false)
const calendarKey = ref(0)
const currentMonthStr = computed(() => {
const year = calendarDate.value.getFullYear()
@@ -124,15 +125,36 @@ const selectedDateRecords = computed(() => {
return recordsMap.value[selectedDate.value]?.list || []
})
const holidaysMap = ref({})
const loadedHolidayYear = ref(null)
async function loadHolidays(year) {
try {
const res = await rdApi.getHolidays(year)
holidaysMap.value = res.data || {}
calendarKey.value++
} catch (e) {
console.error('Failed to load holidays:', e)
}
}
// Timezone safe day of week checker
const getDayOfWeek = (dayStr) => {
const d = new Date(dayStr + 'T00:00:00')
return d.getDay()
}
const isWeekend = (dayStr) => {
const isWorkday = (dayStr) => {
const mmDd = dayStr.substring(5)
if (holidaysMap.value && holidaysMap.value[mmDd]) {
return !holidaysMap.value[mmDd].holiday
}
const day = getDayOfWeek(dayStr)
return day === 0 || day === 6
return day !== 0 && day !== 6
}
const isRestDay = (dayStr) => {
return !isWorkday(dayStr)
}
const getDayHours = (dayStr) => {
@@ -141,7 +163,7 @@ const getDayHours = (dayStr) => {
const isPastUnfilled = (dayStr) => {
if (dayStr >= todayStr.value) return false
if (isWeekend(dayStr)) return false
if (isRestDay(dayStr)) return false
return getDayHours(dayStr) === 0
}
@@ -161,6 +183,7 @@ async function loadMonthRecords(yyyyMm) {
map[r.work_date].list.push(r)
})
recordsMap.value = map
calendarKey.value++
} catch (e) {
ElMessage.error('获取月度工时失败')
} finally {
@@ -168,12 +191,18 @@ async function loadMonthRecords(yyyyMm) {
}
}
// Watch calendar date changes to load month data
// Watch calendar date changes to load month data & holidays
watch(calendarDate, (newDate) => {
if (newDate) {
const year = newDate.getFullYear()
const month = String(newDate.getMonth() + 1).padStart(2, '0')
const yyyyMm = `${year}-${month}`
if (loadedHolidayYear.value !== year) {
loadedHolidayYear.value = year
loadHolidays(year)
}
loadMonthRecords(yyyyMm)
}
}, { immediate: true })
@@ -211,8 +240,8 @@ const monthStats = computed(() => {
} else if (hours > 0) {
partialDays++
} else {
// Unfilled. Check if it's a weekday in the past
if (dStr < todayStr.value && !isWeekend(dStr)) {
// Unfilled. Check if it's a workday in the past
if (dStr < todayStr.value && !isRestDay(dStr)) {
unfilledDays++
}
}