修复登录报错提示内容错误,增加节假日同步功能
This commit is contained in:
@@ -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 } })
|
||||
}
|
||||
|
||||
@@ -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++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }))
|
||||
|
||||
@@ -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++
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user