修复登录报错提示内容错误,增加节假日同步功能
This commit is contained in:
@@ -21,7 +21,7 @@ api.interceptors.response.use(
|
|||||||
response => response.data,
|
response => response.data,
|
||||||
error => {
|
error => {
|
||||||
const msg = error.response?.data?.message || '请求失败'
|
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('token')
|
||||||
localStorage.removeItem('user')
|
localStorage.removeItem('user')
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
@@ -49,6 +49,8 @@ export const adminApi = {
|
|||||||
deleteUser: (id) => api.delete(`/admin/users/${id}`),
|
deleteUser: (id) => api.delete(`/admin/users/${id}`),
|
||||||
getWorkdays: (year) => api.get('/admin/workdays', { params: { year } }),
|
getWorkdays: (year) => api.get('/admin/workdays', { params: { year } }),
|
||||||
setWorkdays: (data) => api.post('/admin/workdays', data),
|
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 }),
|
getSalesStats: (params) => api.get('/admin/stats/sales', { params }),
|
||||||
getSalesDetail: (params) => api.get('/admin/stats/sales-detail', { params }),
|
getSalesDetail: (params) => api.get('/admin/stats/sales-detail', { params }),
|
||||||
getRdStats: (params) => api.get('/admin/stats/rd', { params }),
|
getRdStats: (params) => api.get('/admin/stats/rd', { params }),
|
||||||
@@ -111,5 +113,6 @@ export const rdApi = {
|
|||||||
createTimesheet: (data) => api.post('/rd/timesheets', data),
|
createTimesheet: (data) => api.post('/rd/timesheets', data),
|
||||||
updateTimesheet: (id, data) => api.put(`/rd/timesheets/${id}`, data),
|
updateTimesheet: (id, data) => api.put(`/rd/timesheets/${id}`, data),
|
||||||
deleteTimesheet: (id) => api.delete(`/rd/timesheets/${id}`),
|
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>
|
</div>
|
||||||
|
|
||||||
<!-- Inline Calendar -->
|
<!-- Inline Calendar -->
|
||||||
<el-calendar v-model="adminCalendarDate">
|
<el-calendar v-model="adminCalendarDate" :key="adminCalendarKey">
|
||||||
<template #date-cell="{ data }">
|
<template #date-cell="{ data }">
|
||||||
<div class="day-cell-content" @click="handleAdminSelectDate(data.day)">
|
<div class="day-cell-content" @click="handleAdminSelectDate(data.day)">
|
||||||
<div class="day-header">
|
<div class="day-header">
|
||||||
<span class="day-num">{{ data.day.split('-')[2] }}</span>
|
<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>
|
||||||
<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">
|
<div v-if="getAdminDayHours(data.day) >= 8" class="status-badge filled">
|
||||||
{{ getAdminDayHours(data.day) }}h
|
{{ getAdminDayHours(data.day) }}h
|
||||||
</div>
|
</div>
|
||||||
@@ -731,6 +731,7 @@ const adminSelectedDate = ref(new Date().toLocaleDateString('sv-SE', { timeZone:
|
|||||||
const calendarRawRecords = ref([])
|
const calendarRawRecords = ref([])
|
||||||
const gridData = ref([])
|
const gridData = ref([])
|
||||||
const adminRecordsMap = ref({})
|
const adminRecordsMap = ref({})
|
||||||
|
const adminCalendarKey = ref(0)
|
||||||
|
|
||||||
const selectedDeveloperName = computed(() => {
|
const selectedDeveloperName = computed(() => {
|
||||||
const found = rdUsers.value.find(u => u.id === calendarFilter.user_id)
|
const found = rdUsers.value.find(u => u.id === calendarFilter.user_id)
|
||||||
@@ -756,6 +757,19 @@ const getGridCellHours = (row, day) => {
|
|||||||
return hours > 0 ? hours : ''
|
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 getGridCellClass = (row, day) => {
|
||||||
const dStr = `${calendarFilter.month}-${String(day).padStart(2, '0')}`
|
const dStr = `${calendarFilter.month}-${String(day).padStart(2, '0')}`
|
||||||
const hours = row.dates[dStr]?.hours || 0
|
const hours = row.dates[dStr]?.hours || 0
|
||||||
@@ -764,7 +778,7 @@ const getGridCellClass = (row, day) => {
|
|||||||
} else if (hours > 0) {
|
} else if (hours > 0) {
|
||||||
return 'cell-partial'
|
return 'cell-partial'
|
||||||
} else {
|
} else {
|
||||||
if (dStr < todayStr.value && !isWeekend(dStr)) {
|
if (dStr < todayStr.value && !isRestDay(dStr)) {
|
||||||
return 'cell-unfilled'
|
return 'cell-unfilled'
|
||||||
}
|
}
|
||||||
return 'cell-neutral'
|
return 'cell-neutral'
|
||||||
@@ -777,7 +791,7 @@ const getGridCellTitle = (row, day) => {
|
|||||||
if (hours > 0) {
|
if (hours > 0) {
|
||||||
return `${row.user_name} 在 ${dStr} 已填报 ${hours}h`
|
return `${row.user_name} 在 ${dStr} 已填报 ${hours}h`
|
||||||
} else {
|
} else {
|
||||||
if (dStr < todayStr.value && !isWeekend(dStr)) {
|
if (dStr < todayStr.value && !isRestDay(dStr)) {
|
||||||
return `${row.user_name} 在 ${dStr} 漏填报`
|
return `${row.user_name} 在 ${dStr} 漏填报`
|
||||||
}
|
}
|
||||||
return `${row.user_name} 在 ${dStr} 无需填报/未到期`
|
return `${row.user_name} 在 ${dStr} 无需填报/未到期`
|
||||||
@@ -789,9 +803,17 @@ const getDayOfWeek = (dayStr) => {
|
|||||||
return d.getDay()
|
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)
|
const day = getDayOfWeek(dayStr)
|
||||||
return day === 0 || day === 6
|
return day !== 0 && day !== 6
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRestDay = (dayStr) => {
|
||||||
|
return !isWorkday(dayStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAdminDayHours = (dayStr) => {
|
const getAdminDayHours = (dayStr) => {
|
||||||
@@ -800,7 +822,7 @@ const getAdminDayHours = (dayStr) => {
|
|||||||
|
|
||||||
const isAdminPastUnfilled = (dayStr) => {
|
const isAdminPastUnfilled = (dayStr) => {
|
||||||
if (dayStr >= todayStr.value) return false
|
if (dayStr >= todayStr.value) return false
|
||||||
if (isWeekend(dayStr)) return false
|
if (isRestDay(dayStr)) return false
|
||||||
return getAdminDayHours(dayStr) === 0
|
return getAdminDayHours(dayStr) === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,11 +833,19 @@ const adminSelectedDateRecords = computed(() => {
|
|||||||
async function loadCalendarData() {
|
async function loadCalendarData() {
|
||||||
calendarLoading.value = true
|
calendarLoading.value = true
|
||||||
try {
|
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 daysCount = new Date(year, month, 0).getDate()
|
||||||
const start_date = `${calendarFilter.month}-01`
|
const start_date = `${calendarFilter.month}-01`
|
||||||
const end_date = `${calendarFilter.month}-${String(daysCount).padStart(2, '0')}`
|
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 })
|
const res = await adminApi.getRdStats({ start_date, end_date })
|
||||||
calendarRawRecords.value = res.data || []
|
calendarRawRecords.value = res.data || []
|
||||||
buildGridData()
|
buildGridData()
|
||||||
@@ -856,10 +886,12 @@ function buildGridData() {
|
|||||||
userMap[uid].total_hours += r.hours
|
userMap[uid].total_hours += r.hours
|
||||||
})
|
})
|
||||||
|
|
||||||
gridData.value = Object.values(userMap).map(u => {
|
gridData.value = Object.values(userMap)
|
||||||
|
.map(u => {
|
||||||
u.total_hours = Math.round(u.total_hours * 10) / 10
|
u.total_hours = Math.round(u.total_hours * 10) / 10
|
||||||
return u
|
return u
|
||||||
})
|
})
|
||||||
|
.filter(u => u.total_hours > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCalendarFilterChange() {
|
function handleCalendarFilterChange() {
|
||||||
@@ -889,6 +921,7 @@ function loadIndividualCalendarData() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
adminRecordsMap.value = map
|
adminRecordsMap.value = map
|
||||||
|
adminCalendarKey.value++
|
||||||
|
|
||||||
const [cy, cm] = calendarFilter.month.split('-').map(Number)
|
const [cy, cm] = calendarFilter.month.split('-').map(Number)
|
||||||
const currentCalDate = adminCalendarDate.value
|
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 individualStats = computed(() => {
|
||||||
const [year, month] = calendarFilter.month.split('-').map(Number)
|
const [year, month] = calendarFilter.month.split('-').map(Number)
|
||||||
const daysCount = new Date(year, month, 0).getDate()
|
const daysCount = new Date(year, month, 0).getDate()
|
||||||
@@ -945,7 +991,7 @@ const individualStats = computed(() => {
|
|||||||
} else if (hours > 0) {
|
} else if (hours > 0) {
|
||||||
partialDays++
|
partialDays++
|
||||||
} else {
|
} else {
|
||||||
if (dStr < todayStr.value && !isWeekend(dStr)) {
|
if (dStr < todayStr.value && !isRestDay(dStr)) {
|
||||||
unfilledDays++
|
unfilledDays++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
|
|
||||||
<div class="content-card">
|
<div class="content-card">
|
||||||
<div class="card-title">
|
<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-select v-model="selectedYear" @change="loadWorkdays" style="width: 120px">
|
||||||
<el-option v-for="y in years" :key="y" :label="`${y}年`" :value="y" />
|
<el-option v-for="y in years" :key="y" :label="`${y}年`" :value="y" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-button type="warning" :loading="syncing" @click="handleSyncHolidays">🔄 联网自动同步节假日</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-button type="primary" :loading="saving" @click="handleSave">保存设置</el-button>
|
<el-button type="primary" :loading="saving" @click="handleSave">保存设置</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,6 +43,18 @@ const currentYear = new Date().getFullYear()
|
|||||||
const years = Array.from({ length: 5 }, (_, i) => currentYear - 2 + i)
|
const years = Array.from({ length: 5 }, (_, i) => currentYear - 2 + i)
|
||||||
const selectedYear = ref(currentYear)
|
const selectedYear = ref(currentYear)
|
||||||
const saving = ref(false)
|
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(
|
const workdays = ref(
|
||||||
Array.from({ length: 12 }, (_, i) => ({ month: i + 1, days: 22 }))
|
Array.from({ length: 12 }, (_, i) => ({ month: i + 1, days: 22 }))
|
||||||
|
|||||||
@@ -19,14 +19,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-calendar v-model="calendarDate">
|
<el-calendar v-model="calendarDate" :key="calendarKey">
|
||||||
<template #date-cell="{ data }">
|
<template #date-cell="{ data }">
|
||||||
<div class="day-cell-content" @click="handleSelectDate(data.day)">
|
<div class="day-cell-content" @click="handleSelectDate(data.day)">
|
||||||
<div class="day-header">
|
<div class="day-header">
|
||||||
<span class="day-num">{{ data.day.split('-')[2] }}</span>
|
<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>
|
||||||
<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">
|
<div v-if="getDayHours(data.day) >= 8" class="status-badge filled">
|
||||||
{{ getDayHours(data.day) }}h
|
{{ getDayHours(data.day) }}h
|
||||||
</div>
|
</div>
|
||||||
@@ -113,6 +113,7 @@ const todayStr = ref(new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Sh
|
|||||||
|
|
||||||
const recordsMap = ref({}) // date -> { hours, list }
|
const recordsMap = ref({}) // date -> { hours, list }
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const calendarKey = ref(0)
|
||||||
|
|
||||||
const currentMonthStr = computed(() => {
|
const currentMonthStr = computed(() => {
|
||||||
const year = calendarDate.value.getFullYear()
|
const year = calendarDate.value.getFullYear()
|
||||||
@@ -124,15 +125,36 @@ const selectedDateRecords = computed(() => {
|
|||||||
return recordsMap.value[selectedDate.value]?.list || []
|
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
|
// Timezone safe day of week checker
|
||||||
const getDayOfWeek = (dayStr) => {
|
const getDayOfWeek = (dayStr) => {
|
||||||
const d = new Date(dayStr + 'T00:00:00')
|
const d = new Date(dayStr + 'T00:00:00')
|
||||||
return d.getDay()
|
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)
|
const day = getDayOfWeek(dayStr)
|
||||||
return day === 0 || day === 6
|
return day !== 0 && day !== 6
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRestDay = (dayStr) => {
|
||||||
|
return !isWorkday(dayStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDayHours = (dayStr) => {
|
const getDayHours = (dayStr) => {
|
||||||
@@ -141,7 +163,7 @@ const getDayHours = (dayStr) => {
|
|||||||
|
|
||||||
const isPastUnfilled = (dayStr) => {
|
const isPastUnfilled = (dayStr) => {
|
||||||
if (dayStr >= todayStr.value) return false
|
if (dayStr >= todayStr.value) return false
|
||||||
if (isWeekend(dayStr)) return false
|
if (isRestDay(dayStr)) return false
|
||||||
return getDayHours(dayStr) === 0
|
return getDayHours(dayStr) === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +183,7 @@ async function loadMonthRecords(yyyyMm) {
|
|||||||
map[r.work_date].list.push(r)
|
map[r.work_date].list.push(r)
|
||||||
})
|
})
|
||||||
recordsMap.value = map
|
recordsMap.value = map
|
||||||
|
calendarKey.value++
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ElMessage.error('获取月度工时失败')
|
ElMessage.error('获取月度工时失败')
|
||||||
} finally {
|
} 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) => {
|
watch(calendarDate, (newDate) => {
|
||||||
if (newDate) {
|
if (newDate) {
|
||||||
const year = newDate.getFullYear()
|
const year = newDate.getFullYear()
|
||||||
const month = String(newDate.getMonth() + 1).padStart(2, '0')
|
const month = String(newDate.getMonth() + 1).padStart(2, '0')
|
||||||
const yyyyMm = `${year}-${month}`
|
const yyyyMm = `${year}-${month}`
|
||||||
|
|
||||||
|
if (loadedHolidayYear.value !== year) {
|
||||||
|
loadedHolidayYear.value = year
|
||||||
|
loadHolidays(year)
|
||||||
|
}
|
||||||
|
|
||||||
loadMonthRecords(yyyyMm)
|
loadMonthRecords(yyyyMm)
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
@@ -211,8 +240,8 @@ const monthStats = computed(() => {
|
|||||||
} else if (hours > 0) {
|
} else if (hours > 0) {
|
||||||
partialDays++
|
partialDays++
|
||||||
} else {
|
} else {
|
||||||
// Unfilled. Check if it's a weekday in the past
|
// Unfilled. Check if it's a workday in the past
|
||||||
if (dStr < todayStr.value && !isWeekend(dStr)) {
|
if (dStr < todayStr.value && !isRestDay(dStr)) {
|
||||||
unfilledDays++
|
unfilledDays++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -31,10 +31,10 @@ async function startServer() {
|
|||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
if (!username || !password) return res.status(400).json({ code: 400, message: '用户名和密码不能为空' });
|
if (!username || !password) return res.status(400).json({ code: 400, message: '用户名和密码不能为空' });
|
||||||
const user = db.get('SELECT * FROM users WHERE username = ?', [username]);
|
const user = db.get('SELECT * FROM users WHERE username = ?', [username]);
|
||||||
if (!user) return res.status(401).json({ code: 401, message: '用户名或密码错误' });
|
if (!user) return res.status(401).json({ code: 401, message: '该用户不存在' });
|
||||||
if (user.status !== 1) return res.status(403).json({ code: 403, message: '账号已被禁用' });
|
if (user.status !== 1) return res.status(403).json({ code: 403, message: '账号已被禁用' });
|
||||||
if (user.role === 'sales') return res.status(403).json({ code: 403, message: '销售人员无需登录系统' });
|
if (user.role === 'sales') return res.status(403).json({ code: 403, message: '销售人员无需登录系统' });
|
||||||
if (!bcrypt.compareSync(password, user.password)) return res.status(401).json({ code: 401, message: '用户名或密码错误' });
|
if (!bcrypt.compareSync(password, user.password)) return res.status(401).json({ code: 401, message: '密码错误请重试' });
|
||||||
const token = jwt.sign({ id: user.id, username: user.username, role: user.role, realname: user.realname }, JWT_SECRET, { expiresIn: '24h' });
|
const token = jwt.sign({ id: user.id, username: user.username, role: user.role, realname: user.realname }, JWT_SECRET, { expiresIn: '24h' });
|
||||||
res.json({ code: 200, message: '登录成功', data: { token, user: { id: user.id, username: user.username, realname: user.realname, role: user.role } } });
|
res.json({ code: 200, message: '登录成功', data: { token, user: { id: user.id, username: user.username, realname: user.realname, role: user.role } } });
|
||||||
});
|
});
|
||||||
|
|||||||
Binary file not shown.
@@ -184,6 +184,14 @@ async function initDB() {
|
|||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS holiday_cache (
|
||||||
|
year INTEGER PRIMARY KEY,
|
||||||
|
holiday_data TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
// Create indexes (ignore if exists)
|
// Create indexes (ignore if exists)
|
||||||
try { db.run("CREATE INDEX idx_timesheets_user ON timesheets(user_id)"); } catch (e) { }
|
try { db.run("CREATE INDEX idx_timesheets_user ON timesheets(user_id)"); } catch (e) { }
|
||||||
try { db.run("CREATE INDEX idx_timesheets_client ON timesheets(client_id)"); } catch (e) { }
|
try { db.run("CREATE INDEX idx_timesheets_client ON timesheets(client_id)"); } catch (e) { }
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
import{c as p,a as s,t as o,F as C,i as B,b as h,w as r,j as i,r as m,u as L,o as l,k as v,n as S,T as x,l as D}from"./index-B5IUyHJe.js";import{u as F}from"./user-Bf9RYC_c.js";const N={class:"layout-container"},T={class:"sidebar"},V={class:"sidebar-header"},j={class:"user-info"},z={class:"avatar"},A={class:"user-detail"},E={class:"name"},I={class:"role"},R={class:"sidebar-nav"},U={class:"icon"},$={class:"main-content"},J={__name:"Layout",setup(q){const b=L(),c=F(),n=i(()=>c.user),f=i(()=>{var a;return{admin:"管理员",sales:"销售",rd:"研发"}[(a=n.value)==null?void 0:a.role]||""}),k=i(()=>{var a;const t=(a=n.value)==null?void 0:a.role;return t==="admin"?[{path:"/dashboard",label:"统计看板",icon:"📊"},{path:"/clients",label:"客户/项目管理",icon:"🏢"},{path:"/users",label:"账号管理",icon:"👥"},{path:"/workdays",label:"工作日设置",icon:"📅"},{path:"/data-manage",label:"数据管理",icon:"🗄️"}]:t==="rd"?[{path:"/timesheet",label:"工时填报",icon:"⏰"},{path:"/timesheet-details",label:"填报详情",icon:"📅"},{path:"/rd-stats",label:"个人统计",icon:"📊"}]:[]});function g(){c.logout(),b.push("/login")}return(t,a)=>{var u,d,_;const y=m("router-link"),w=m("router-view");return l(),p("div",N,[s("aside",T,[s("div",V,[a[0]||(a[0]=s("h2",null,"⚡ 工时管理",-1)),s("div",j,[s("div",z,o((d=(u=n.value)==null?void 0:u.realname)==null?void 0:d.charAt(0)),1),s("div",A,[s("div",E,o((_=n.value)==null?void 0:_.realname),1),s("div",I,o(f.value),1)])])]),s("nav",R,[(l(!0),p(C,null,B(k.value,e=>(l(),v(y,{key:e.path,to:e.path,class:S(["nav-item",{active:t.$route.path===e.path}])},{default:r(()=>[s("span",U,o(e.icon),1),s("span",null,o(e.label),1)]),_:2},1032,["to","class"]))),128))]),s("div",{class:"sidebar-footer"},[s("button",{class:"logout-btn",onClick:g},[...a[1]||(a[1]=[s("span",{class:"icon"},"🚪",-1),s("span",null,"退出登录",-1)])])])]),s("main",$,[h(w,null,{default:r(({Component:e})=>[h(x,{name:"fade",mode:"out-in"},{default:r(()=>[(l(),v(D(e)))]),_:2},1024)]),_:1})])])}}};export{J as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as p,a as s,t as o,F as C,i as B,b as h,w as r,j as i,r as m,u as L,o as l,k as v,n as S,T as x,l as D}from"./index-jnsG7DVq.js";import{u as F}from"./user-vX7oVs4D.js";const N={class:"layout-container"},T={class:"sidebar"},V={class:"sidebar-header"},j={class:"user-info"},z={class:"avatar"},A={class:"user-detail"},E={class:"name"},I={class:"role"},R={class:"sidebar-nav"},U={class:"icon"},$={class:"main-content"},J={__name:"Layout",setup(q){const b=L(),c=F(),n=i(()=>c.user),f=i(()=>{var a;return{admin:"管理员",sales:"销售",rd:"研发"}[(a=n.value)==null?void 0:a.role]||""}),k=i(()=>{var a;const t=(a=n.value)==null?void 0:a.role;return t==="admin"?[{path:"/dashboard",label:"统计看板",icon:"📊"},{path:"/clients",label:"客户/项目管理",icon:"🏢"},{path:"/users",label:"账号管理",icon:"👥"},{path:"/workdays",label:"工作日设置",icon:"📅"},{path:"/data-manage",label:"数据管理",icon:"🗄️"}]:t==="rd"?[{path:"/timesheet",label:"工时填报",icon:"⏰"},{path:"/timesheet-details",label:"填报详情",icon:"📅"},{path:"/rd-stats",label:"个人统计",icon:"📊"}]:[]});function g(){c.logout(),b.push("/login")}return(t,a)=>{var u,d,_;const y=m("router-link"),w=m("router-view");return l(),p("div",N,[s("aside",T,[s("div",V,[a[0]||(a[0]=s("h2",null,"⚡ 工时管理",-1)),s("div",j,[s("div",z,o((d=(u=n.value)==null?void 0:u.realname)==null?void 0:d.charAt(0)),1),s("div",A,[s("div",E,o((_=n.value)==null?void 0:_.realname),1),s("div",I,o(f.value),1)])])]),s("nav",R,[(l(!0),p(C,null,B(k.value,e=>(l(),v(y,{key:e.path,to:e.path,class:S(["nav-item",{active:t.$route.path===e.path}])},{default:r(()=>[s("span",U,o(e.icon),1),s("span",null,o(e.label),1)]),_:2},1032,["to","class"]))),128))]),s("div",{class:"sidebar-footer"},[s("button",{class:"logout-btn",onClick:g},[...a[1]||(a[1]=[s("span",{class:"icon"},"🚪",-1),s("span",null,"退出登录",-1)])])])]),s("main",$,[h(w,null,{default:r(({Component:e})=>[h(x,{name:"fade",mode:"out-in"},{default:r(()=>[(l(),v(D(e)))]),_:2},1024)]),_:1})])])}}};export{J as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as p,a as s,t as o,F as C,i as B,b as h,w as r,j as i,r as m,u as L,o as l,k as v,n as S,T as x,l as D}from"./index-Dn6fDqC3.js";import{u as F}from"./user-DZgD4Eg8.js";const N={class:"layout-container"},T={class:"sidebar"},V={class:"sidebar-header"},j={class:"user-info"},z={class:"avatar"},A={class:"user-detail"},E={class:"name"},I={class:"role"},R={class:"sidebar-nav"},U={class:"icon"},$={class:"main-content"},J={__name:"Layout",setup(q){const b=L(),c=F(),n=i(()=>c.user),f=i(()=>{var a;return{admin:"管理员",sales:"销售",rd:"研发"}[(a=n.value)==null?void 0:a.role]||""}),k=i(()=>{var a;const t=(a=n.value)==null?void 0:a.role;return t==="admin"?[{path:"/dashboard",label:"统计看板",icon:"📊"},{path:"/clients",label:"客户/项目管理",icon:"🏢"},{path:"/users",label:"账号管理",icon:"👥"},{path:"/workdays",label:"工作日设置",icon:"📅"},{path:"/data-manage",label:"数据管理",icon:"🗄️"}]:t==="rd"?[{path:"/timesheet",label:"工时填报",icon:"⏰"},{path:"/timesheet-details",label:"填报详情",icon:"📅"},{path:"/rd-stats",label:"个人统计",icon:"📊"}]:[]});function g(){c.logout(),b.push("/login")}return(t,a)=>{var u,d,_;const y=m("router-link"),w=m("router-view");return l(),p("div",N,[s("aside",T,[s("div",V,[a[0]||(a[0]=s("h2",null,"⚡ 工时管理",-1)),s("div",j,[s("div",z,o((d=(u=n.value)==null?void 0:u.realname)==null?void 0:d.charAt(0)),1),s("div",A,[s("div",E,o((_=n.value)==null?void 0:_.realname),1),s("div",I,o(f.value),1)])])]),s("nav",R,[(l(!0),p(C,null,B(k.value,e=>(l(),v(y,{key:e.path,to:e.path,class:S(["nav-item",{active:t.$route.path===e.path}])},{default:r(()=>[s("span",U,o(e.icon),1),s("span",null,o(e.label),1)]),_:2},1032,["to","class"]))),128))]),s("div",{class:"sidebar-footer"},[s("button",{class:"logout-btn",onClick:g},[...a[1]||(a[1]=[s("span",{class:"icon"},"🚪",-1),s("span",null,"退出登录",-1)])])])]),s("main",$,[h(w,null,{default:r(({Component:e})=>[h(x,{name:"fade",mode:"out-in"},{default:r(()=>[(l(),v(D(e)))]),_:2},1024)]),_:1})])])}}};export{J as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as p,a as s,t as o,F as C,i as B,b as h,w as r,j as i,r as m,u as L,o as l,k as v,n as S,T as x,l as D}from"./index-CYgyy2lw.js";import{u as F}from"./user-uOvERC_1.js";const N={class:"layout-container"},T={class:"sidebar"},V={class:"sidebar-header"},j={class:"user-info"},z={class:"avatar"},A={class:"user-detail"},E={class:"name"},I={class:"role"},R={class:"sidebar-nav"},U={class:"icon"},$={class:"main-content"},J={__name:"Layout",setup(q){const b=L(),c=F(),n=i(()=>c.user),f=i(()=>{var a;return{admin:"管理员",sales:"销售",rd:"研发"}[(a=n.value)==null?void 0:a.role]||""}),k=i(()=>{var a;const t=(a=n.value)==null?void 0:a.role;return t==="admin"?[{path:"/dashboard",label:"统计看板",icon:"📊"},{path:"/clients",label:"客户/项目管理",icon:"🏢"},{path:"/users",label:"账号管理",icon:"👥"},{path:"/workdays",label:"工作日设置",icon:"📅"},{path:"/data-manage",label:"数据管理",icon:"🗄️"}]:t==="rd"?[{path:"/timesheet",label:"工时填报",icon:"⏰"},{path:"/timesheet-details",label:"填报详情",icon:"📅"},{path:"/rd-stats",label:"个人统计",icon:"📊"}]:[]});function g(){c.logout(),b.push("/login")}return(t,a)=>{var u,d,_;const y=m("router-link"),w=m("router-view");return l(),p("div",N,[s("aside",T,[s("div",V,[a[0]||(a[0]=s("h2",null,"⚡ 工时管理",-1)),s("div",j,[s("div",z,o((d=(u=n.value)==null?void 0:u.realname)==null?void 0:d.charAt(0)),1),s("div",A,[s("div",E,o((_=n.value)==null?void 0:_.realname),1),s("div",I,o(f.value),1)])])]),s("nav",R,[(l(!0),p(C,null,B(k.value,e=>(l(),v(y,{key:e.path,to:e.path,class:S(["nav-item",{active:t.$route.path===e.path}])},{default:r(()=>[s("span",U,o(e.icon),1),s("span",null,o(e.label),1)]),_:2},1032,["to","class"]))),128))]),s("div",{class:"sidebar-footer"},[s("button",{class:"logout-btn",onClick:g},[...a[1]||(a[1]=[s("span",{class:"icon"},"🚪",-1),s("span",null,"退出登录",-1)])])])]),s("main",$,[h(w,null,{default:r(({Component:e})=>[h(x,{name:"fade",mode:"out-in"},{default:r(()=>[(l(),v(D(e)))]),_:2},1024)]),_:1})])])}}};export{J as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as p,a as s,t as o,F as C,i as B,b as h,w as r,j as i,r as m,u as L,o as l,k as v,n as S,T as x,l as D}from"./index-C9J7Lk9r.js";import{u as F}from"./user-BJgrGGYs.js";const N={class:"layout-container"},T={class:"sidebar"},V={class:"sidebar-header"},j={class:"user-info"},z={class:"avatar"},A={class:"user-detail"},E={class:"name"},I={class:"role"},R={class:"sidebar-nav"},U={class:"icon"},$={class:"main-content"},J={__name:"Layout",setup(q){const b=L(),c=F(),n=i(()=>c.user),f=i(()=>{var a;return{admin:"管理员",sales:"销售",rd:"研发"}[(a=n.value)==null?void 0:a.role]||""}),k=i(()=>{var a;const t=(a=n.value)==null?void 0:a.role;return t==="admin"?[{path:"/dashboard",label:"统计看板",icon:"📊"},{path:"/clients",label:"客户/项目管理",icon:"🏢"},{path:"/users",label:"账号管理",icon:"👥"},{path:"/workdays",label:"工作日设置",icon:"📅"},{path:"/data-manage",label:"数据管理",icon:"🗄️"}]:t==="rd"?[{path:"/timesheet",label:"工时填报",icon:"⏰"},{path:"/timesheet-details",label:"填报详情",icon:"📅"},{path:"/rd-stats",label:"个人统计",icon:"📊"}]:[]});function g(){c.logout(),b.push("/login")}return(t,a)=>{var u,d,_;const y=m("router-link"),w=m("router-view");return l(),p("div",N,[s("aside",T,[s("div",V,[a[0]||(a[0]=s("h2",null,"⚡ 工时管理",-1)),s("div",j,[s("div",z,o((d=(u=n.value)==null?void 0:u.realname)==null?void 0:d.charAt(0)),1),s("div",A,[s("div",E,o((_=n.value)==null?void 0:_.realname),1),s("div",I,o(f.value),1)])])]),s("nav",R,[(l(!0),p(C,null,B(k.value,e=>(l(),v(y,{key:e.path,to:e.path,class:S(["nav-item",{active:t.$route.path===e.path}])},{default:r(()=>[s("span",U,o(e.icon),1),s("span",null,o(e.label),1)]),_:2},1032,["to","class"]))),128))]),s("div",{class:"sidebar-footer"},[s("button",{class:"logout-btn",onClick:g},[...a[1]||(a[1]=[s("span",{class:"icon"},"🚪",-1),s("span",null,"退出登录",-1)])])])]),s("main",$,[h(w,null,{default:r(({Component:e})=>[h(x,{name:"fade",mode:"out-in"},{default:r(()=>[(l(),v(D(e)))]),_:2},1024)]),_:1})])])}}};export{J as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as x,a as r,b as t,w as a,d as y,r as l,u as V,o as b,e as h,f as k,E as z,g as L,h as p}from"./index-jnsG7DVq.js";import{a as R}from"./index-DD1Ebu5S.js";import{u as U}from"./user-vX7oVs4D.js";const B={class:"login-container"},C={class:"login-card"},S={__name:"Login",setup(E){const f=V(),c=U(),d=p(null),n=p(!1),s=k({username:"",password:""}),g={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function m(){var e;if(await((e=d.value)==null?void 0:e.validate().catch(()=>!1))){n.value=!0;try{const o=await R.login(s);c.setLogin(o.data.token,o.data.user),z.success(`欢迎回来,${o.data.user.realname}`),f.push(L(o.data.user.role))}catch{}finally{n.value=!1}}}return(_,e)=>{const o=l("el-input"),u=l("el-form-item"),w=l("el-button"),v=l("el-form");return b(),x("div",B,[r("div",C,[e[3]||(e[3]=r("h1",null,"⚡ 工时管理系统",-1)),e[4]||(e[4]=r("p",{class:"subtitle"},"跨部门工时管理与项目协同平台",-1)),t(v,{model:s,rules:g,ref_key:"formRef",ref:d,onKeyup:y(m,["enter"])},{default:a(()=>[t(u,{prop:"username"},{default:a(()=>[t(o,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=i=>s.username=i),placeholder:"请输入用户名","prefix-icon":"User",size:"large"},null,8,["modelValue"])]),_:1}),t(u,{prop:"password"},{default:a(()=>[t(o,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=i=>s.password=i),placeholder:"请输入密码",type:"password","show-password":"","prefix-icon":"Lock",size:"large"},null,8,["modelValue"])]),_:1}),t(u,null,{default:a(()=>[t(w,{type:"primary",size:"large",loading:n.value,onClick:m,style:{width:"100%",height:"48px","font-size":"16px","border-radius":"10px"}},{default:a(()=>[...e[2]||(e[2]=[h(" 登 录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),e[5]||(e[5]=r("div",{style:{"text-align":"center",color:"var(--text-muted)","font-size":"12px","margin-top":"16px"}}," 默认管理员账号: admin / admin123 ",-1))])])}}};export{S as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as x,a as r,b as t,w as a,d as y,r as l,u as V,o as b,e as h,f as k,E as z,g as L,h as p}from"./index-CYgyy2lw.js";import{a as R}from"./index-B5S4JoyK.js";import{u as U}from"./user-uOvERC_1.js";const B={class:"login-container"},C={class:"login-card"},S={__name:"Login",setup(E){const f=V(),c=U(),d=p(null),n=p(!1),s=k({username:"",password:""}),g={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function m(){var e;if(await((e=d.value)==null?void 0:e.validate().catch(()=>!1))){n.value=!0;try{const o=await R.login(s);c.setLogin(o.data.token,o.data.user),z.success(`欢迎回来,${o.data.user.realname}`),f.push(L(o.data.user.role))}catch{}finally{n.value=!1}}}return(_,e)=>{const o=l("el-input"),u=l("el-form-item"),w=l("el-button"),v=l("el-form");return b(),x("div",B,[r("div",C,[e[3]||(e[3]=r("h1",null,"⚡ 工时管理系统",-1)),e[4]||(e[4]=r("p",{class:"subtitle"},"跨部门工时管理与项目协同平台",-1)),t(v,{model:s,rules:g,ref_key:"formRef",ref:d,onKeyup:y(m,["enter"])},{default:a(()=>[t(u,{prop:"username"},{default:a(()=>[t(o,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=i=>s.username=i),placeholder:"请输入用户名","prefix-icon":"User",size:"large"},null,8,["modelValue"])]),_:1}),t(u,{prop:"password"},{default:a(()=>[t(o,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=i=>s.password=i),placeholder:"请输入密码",type:"password","show-password":"","prefix-icon":"Lock",size:"large"},null,8,["modelValue"])]),_:1}),t(u,null,{default:a(()=>[t(w,{type:"primary",size:"large",loading:n.value,onClick:m,style:{width:"100%",height:"48px","font-size":"16px","border-radius":"10px"}},{default:a(()=>[...e[2]||(e[2]=[h(" 登 录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),e[5]||(e[5]=r("div",{style:{"text-align":"center",color:"var(--text-muted)","font-size":"12px","margin-top":"16px"}}," 默认管理员账号: admin / admin123 ",-1))])])}}};export{S as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as x,a as r,b as t,w as a,d as y,r as l,u as V,o as b,e as h,f as k,E as z,g as L,h as p}from"./index-C9J7Lk9r.js";import{a as R}from"./index-BfxBnpOb.js";import{u as U}from"./user-BJgrGGYs.js";const B={class:"login-container"},C={class:"login-card"},S={__name:"Login",setup(E){const f=V(),c=U(),d=p(null),n=p(!1),s=k({username:"",password:""}),g={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function m(){var e;if(await((e=d.value)==null?void 0:e.validate().catch(()=>!1))){n.value=!0;try{const o=await R.login(s);c.setLogin(o.data.token,o.data.user),z.success(`欢迎回来,${o.data.user.realname}`),f.push(L(o.data.user.role))}catch{}finally{n.value=!1}}}return(_,e)=>{const o=l("el-input"),u=l("el-form-item"),w=l("el-button"),v=l("el-form");return b(),x("div",B,[r("div",C,[e[3]||(e[3]=r("h1",null,"⚡ 工时管理系统",-1)),e[4]||(e[4]=r("p",{class:"subtitle"},"跨部门工时管理与项目协同平台",-1)),t(v,{model:s,rules:g,ref_key:"formRef",ref:d,onKeyup:y(m,["enter"])},{default:a(()=>[t(u,{prop:"username"},{default:a(()=>[t(o,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=i=>s.username=i),placeholder:"请输入用户名","prefix-icon":"User",size:"large"},null,8,["modelValue"])]),_:1}),t(u,{prop:"password"},{default:a(()=>[t(o,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=i=>s.password=i),placeholder:"请输入密码",type:"password","show-password":"","prefix-icon":"Lock",size:"large"},null,8,["modelValue"])]),_:1}),t(u,null,{default:a(()=>[t(w,{type:"primary",size:"large",loading:n.value,onClick:m,style:{width:"100%",height:"48px","font-size":"16px","border-radius":"10px"}},{default:a(()=>[...e[2]||(e[2]=[h(" 登 录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),e[5]||(e[5]=r("div",{style:{"text-align":"center",color:"var(--text-muted)","font-size":"12px","margin-top":"16px"}}," 默认管理员账号: admin / admin123 ",-1))])])}}};export{S as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as x,a as r,b as t,w as a,d as y,r as l,u as V,o as b,e as h,f as k,E as z,g as L,h as p}from"./index-B5IUyHJe.js";import{a as R}from"./index-OKS9XN2R.js";import{u as U}from"./user-Bf9RYC_c.js";const B={class:"login-container"},C={class:"login-card"},S={__name:"Login",setup(E){const f=V(),c=U(),d=p(null),n=p(!1),s=k({username:"",password:""}),g={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function m(){var e;if(await((e=d.value)==null?void 0:e.validate().catch(()=>!1))){n.value=!0;try{const o=await R.login(s);c.setLogin(o.data.token,o.data.user),z.success(`欢迎回来,${o.data.user.realname}`),f.push(L(o.data.user.role))}catch{}finally{n.value=!1}}}return(_,e)=>{const o=l("el-input"),u=l("el-form-item"),w=l("el-button"),v=l("el-form");return b(),x("div",B,[r("div",C,[e[3]||(e[3]=r("h1",null,"⚡ 工时管理系统",-1)),e[4]||(e[4]=r("p",{class:"subtitle"},"跨部门工时管理与项目协同平台",-1)),t(v,{model:s,rules:g,ref_key:"formRef",ref:d,onKeyup:y(m,["enter"])},{default:a(()=>[t(u,{prop:"username"},{default:a(()=>[t(o,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=i=>s.username=i),placeholder:"请输入用户名","prefix-icon":"User",size:"large"},null,8,["modelValue"])]),_:1}),t(u,{prop:"password"},{default:a(()=>[t(o,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=i=>s.password=i),placeholder:"请输入密码",type:"password","show-password":"","prefix-icon":"Lock",size:"large"},null,8,["modelValue"])]),_:1}),t(u,null,{default:a(()=>[t(w,{type:"primary",size:"large",loading:n.value,onClick:m,style:{width:"100%",height:"48px","font-size":"16px","border-radius":"10px"}},{default:a(()=>[...e[2]||(e[2]=[h(" 登 录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),e[5]||(e[5]=r("div",{style:{"text-align":"center",color:"var(--text-muted)","font-size":"12px","margin-top":"16px"}}," 默认管理员账号: admin / admin123 ",-1))])])}}};export{S as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{c as x,a as r,b as t,w as a,d as y,r as l,u as V,o as b,e as h,f as k,E as z,g as L,h as p}from"./index-Dn6fDqC3.js";import{a as R}from"./index-D0SgC7D8.js";import{u as U}from"./user-DZgD4Eg8.js";const B={class:"login-container"},C={class:"login-card"},S={__name:"Login",setup(E){const f=V(),c=U(),d=p(null),n=p(!1),s=k({username:"",password:""}),g={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function m(){var e;if(await((e=d.value)==null?void 0:e.validate().catch(()=>!1))){n.value=!0;try{const o=await R.login(s);c.setLogin(o.data.token,o.data.user),z.success(`欢迎回来,${o.data.user.realname}`),f.push(L(o.data.user.role))}catch{}finally{n.value=!1}}}return(_,e)=>{const o=l("el-input"),u=l("el-form-item"),w=l("el-button"),v=l("el-form");return b(),x("div",B,[r("div",C,[e[3]||(e[3]=r("h1",null,"⚡ 工时管理系统",-1)),e[4]||(e[4]=r("p",{class:"subtitle"},"跨部门工时管理与项目协同平台",-1)),t(v,{model:s,rules:g,ref_key:"formRef",ref:d,onKeyup:y(m,["enter"])},{default:a(()=>[t(u,{prop:"username"},{default:a(()=>[t(o,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=i=>s.username=i),placeholder:"请输入用户名","prefix-icon":"User",size:"large"},null,8,["modelValue"])]),_:1}),t(u,{prop:"password"},{default:a(()=>[t(o,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=i=>s.password=i),placeholder:"请输入密码",type:"password","show-password":"","prefix-icon":"Lock",size:"large"},null,8,["modelValue"])]),_:1}),t(u,null,{default:a(()=>[t(w,{type:"primary",size:"large",loading:n.value,onClick:m,style:{width:"100%",height:"48px","font-size":"16px","border-radius":"10px"}},{default:a(()=>[...e[2]||(e[2]=[h(" 登 录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),e[5]||(e[5]=r("div",{style:{"text-align":"center",color:"var(--text-muted)","font-size":"12px","margin-top":"16px"}}," 默认管理员账号: admin / admin123 ",-1))])])}}};export{S as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{r as b}from"./index-DD1Ebu5S.js";import{i as w,L as x}from"./index-Bn9PLWWT.js";import{h as p,q as y,c as k,a as e,e as m,t as f,b as l,w as M,v as R,r as u,o as S}from"./index-jnsG7DVq.js";const D={class:"stat-cards"},V={class:"stat-card purple"},C={class:"value"},Y={class:"stat-card green"},j={class:"value"},L={class:"content-card"},A={class:"card-title"},B={class:"filter-bar",style:{"margin-bottom":"0"}},N={class:"content-card"},F={__name:"RdStats",setup($){const n=p({}),d=p(null);let c=null;const r=p((()=>{const a=new Date,t=new Date;t.setMonth(t.getMonth()-1);const i=o=>String(o).padStart(2,"0"),s=o=>`${o.getFullYear()}-${i(o.getMonth()+1)}-${i(o.getDate())}`;return[s(t),s(a)]})());async function v(){const a={};r.value&&(a.start_date=r.value[0],a.end_date=r.value[1]);const t=await b.getStats(a);n.value=t.data,await R(),h()}function h(){if(!d.value)return;c||(c=w(d.value,"dark"));const a=n.value.projects||[];c.setOption({backgroundColor:"transparent",tooltip:{trigger:"axis",axisPointer:{type:"shadow"}},xAxis:{type:"category",data:a.map(t=>t.client_name),axisLabel:{color:"#94a3b8",interval:0,rotate:30}},yAxis:{type:"value",name:"工时(h)",axisLabel:{color:"#94a3b8"}},series:[{type:"bar",barMaxWidth:60,data:a.map(t=>t.total_hours),itemStyle:{color:new x(0,0,0,1,[{offset:0,color:"#6366f1"},{offset:1,color:"#4338ca"}]),borderRadius:[4,4,0,0]}}]})}return y(v),(a,t)=>{var _;const i=u("el-date-picker"),s=u("el-table-column"),o=u("el-table");return S(),k("div",null,[t[7]||(t[7]=e("div",{class:"page-header"},[e("h1",null,"个人统计"),e("p",null,"查看您的工时分布与月度趋势")],-1)),e("div",D,[e("div",V,[t[2]||(t[2]=e("div",{class:"label"},"参与项目数",-1)),e("div",C,[m(f(((_=n.value.projects)==null?void 0:_.length)||0),1),t[1]||(t[1]=e("span",{class:"unit"},"个",-1))])]),e("div",Y,[t[4]||(t[4]=e("div",{class:"label"},"累计总工时",-1)),e("div",j,[m(f(n.value.total_hours||0),1),t[3]||(t[3]=e("span",{class:"unit"},"小时",-1))])])]),e("div",L,[e("div",A,[t[5]||(t[5]=e("span",null,"各项目工时明细",-1)),e("div",B,[l(i,{modelValue:r.value,"onUpdate:modelValue":t[0]||(t[0]=g=>r.value=g),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",size:"default",onChange:v},null,8,["modelValue"])])]),l(o,{data:n.value.projects||[],stripe:""},{default:M(()=>[l(s,{prop:"client_name",label:"客户/项目","min-width":"180"}),l(s,{prop:"total_hours",label:"总工时(h)",width:"120"}),l(s,{prop:"work_days",label:"工作天数",width:"110"}),l(s,{prop:"first_date",label:"首次填报",width:"130"}),l(s,{prop:"last_date",label:"最近填报",width:"130"})]),_:1},8,["data"])]),e("div",N,[t[6]||(t[6]=e("div",{class:"card-title"},"各项目工时对比",-1)),e("div",{ref_key:"chartRef",ref:d,class:"chart-container"},null,512)])])}}};export{F as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{r as b}from"./index-OKS9XN2R.js";import{i as w,L as x}from"./index-Bn9PLWWT.js";import{h as p,q as y,c as k,a as e,e as m,t as f,b as l,w as M,v as R,r as u,o as S}from"./index-B5IUyHJe.js";const D={class:"stat-cards"},V={class:"stat-card purple"},C={class:"value"},Y={class:"stat-card green"},j={class:"value"},L={class:"content-card"},A={class:"card-title"},B={class:"filter-bar",style:{"margin-bottom":"0"}},N={class:"content-card"},F={__name:"RdStats",setup($){const n=p({}),d=p(null);let c=null;const r=p((()=>{const a=new Date,t=new Date;t.setMonth(t.getMonth()-1);const i=o=>String(o).padStart(2,"0"),s=o=>`${o.getFullYear()}-${i(o.getMonth()+1)}-${i(o.getDate())}`;return[s(t),s(a)]})());async function v(){const a={};r.value&&(a.start_date=r.value[0],a.end_date=r.value[1]);const t=await b.getStats(a);n.value=t.data,await R(),h()}function h(){if(!d.value)return;c||(c=w(d.value,"dark"));const a=n.value.projects||[];c.setOption({backgroundColor:"transparent",tooltip:{trigger:"axis",axisPointer:{type:"shadow"}},xAxis:{type:"category",data:a.map(t=>t.client_name),axisLabel:{color:"#94a3b8",interval:0,rotate:30}},yAxis:{type:"value",name:"工时(h)",axisLabel:{color:"#94a3b8"}},series:[{type:"bar",barMaxWidth:60,data:a.map(t=>t.total_hours),itemStyle:{color:new x(0,0,0,1,[{offset:0,color:"#6366f1"},{offset:1,color:"#4338ca"}]),borderRadius:[4,4,0,0]}}]})}return y(v),(a,t)=>{var _;const i=u("el-date-picker"),s=u("el-table-column"),o=u("el-table");return S(),k("div",null,[t[7]||(t[7]=e("div",{class:"page-header"},[e("h1",null,"个人统计"),e("p",null,"查看您的工时分布与月度趋势")],-1)),e("div",D,[e("div",V,[t[2]||(t[2]=e("div",{class:"label"},"参与项目数",-1)),e("div",C,[m(f(((_=n.value.projects)==null?void 0:_.length)||0),1),t[1]||(t[1]=e("span",{class:"unit"},"个",-1))])]),e("div",Y,[t[4]||(t[4]=e("div",{class:"label"},"累计总工时",-1)),e("div",j,[m(f(n.value.total_hours||0),1),t[3]||(t[3]=e("span",{class:"unit"},"小时",-1))])])]),e("div",L,[e("div",A,[t[5]||(t[5]=e("span",null,"各项目工时明细",-1)),e("div",B,[l(i,{modelValue:r.value,"onUpdate:modelValue":t[0]||(t[0]=g=>r.value=g),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",size:"default",onChange:v},null,8,["modelValue"])])]),l(o,{data:n.value.projects||[],stripe:""},{default:M(()=>[l(s,{prop:"client_name",label:"客户/项目","min-width":"180"}),l(s,{prop:"total_hours",label:"总工时(h)",width:"120"}),l(s,{prop:"work_days",label:"工作天数",width:"110"}),l(s,{prop:"first_date",label:"首次填报",width:"130"}),l(s,{prop:"last_date",label:"最近填报",width:"130"})]),_:1},8,["data"])]),e("div",N,[t[6]||(t[6]=e("div",{class:"card-title"},"各项目工时对比",-1)),e("div",{ref_key:"chartRef",ref:d,class:"chart-container"},null,512)])])}}};export{F as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{r as b}from"./index-B5S4JoyK.js";import{i as w,L as x}from"./index-Bn9PLWWT.js";import{h as p,q as y,c as k,a as e,e as m,t as f,b as l,w as M,v as R,r as u,o as S}from"./index-CYgyy2lw.js";const D={class:"stat-cards"},V={class:"stat-card purple"},C={class:"value"},Y={class:"stat-card green"},j={class:"value"},L={class:"content-card"},A={class:"card-title"},B={class:"filter-bar",style:{"margin-bottom":"0"}},N={class:"content-card"},F={__name:"RdStats",setup($){const n=p({}),d=p(null);let c=null;const r=p((()=>{const a=new Date,t=new Date;t.setMonth(t.getMonth()-1);const i=o=>String(o).padStart(2,"0"),s=o=>`${o.getFullYear()}-${i(o.getMonth()+1)}-${i(o.getDate())}`;return[s(t),s(a)]})());async function v(){const a={};r.value&&(a.start_date=r.value[0],a.end_date=r.value[1]);const t=await b.getStats(a);n.value=t.data,await R(),h()}function h(){if(!d.value)return;c||(c=w(d.value,"dark"));const a=n.value.projects||[];c.setOption({backgroundColor:"transparent",tooltip:{trigger:"axis",axisPointer:{type:"shadow"}},xAxis:{type:"category",data:a.map(t=>t.client_name),axisLabel:{color:"#94a3b8",interval:0,rotate:30}},yAxis:{type:"value",name:"工时(h)",axisLabel:{color:"#94a3b8"}},series:[{type:"bar",barMaxWidth:60,data:a.map(t=>t.total_hours),itemStyle:{color:new x(0,0,0,1,[{offset:0,color:"#6366f1"},{offset:1,color:"#4338ca"}]),borderRadius:[4,4,0,0]}}]})}return y(v),(a,t)=>{var _;const i=u("el-date-picker"),s=u("el-table-column"),o=u("el-table");return S(),k("div",null,[t[7]||(t[7]=e("div",{class:"page-header"},[e("h1",null,"个人统计"),e("p",null,"查看您的工时分布与月度趋势")],-1)),e("div",D,[e("div",V,[t[2]||(t[2]=e("div",{class:"label"},"参与项目数",-1)),e("div",C,[m(f(((_=n.value.projects)==null?void 0:_.length)||0),1),t[1]||(t[1]=e("span",{class:"unit"},"个",-1))])]),e("div",Y,[t[4]||(t[4]=e("div",{class:"label"},"累计总工时",-1)),e("div",j,[m(f(n.value.total_hours||0),1),t[3]||(t[3]=e("span",{class:"unit"},"小时",-1))])])]),e("div",L,[e("div",A,[t[5]||(t[5]=e("span",null,"各项目工时明细",-1)),e("div",B,[l(i,{modelValue:r.value,"onUpdate:modelValue":t[0]||(t[0]=g=>r.value=g),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",size:"default",onChange:v},null,8,["modelValue"])])]),l(o,{data:n.value.projects||[],stripe:""},{default:M(()=>[l(s,{prop:"client_name",label:"客户/项目","min-width":"180"}),l(s,{prop:"total_hours",label:"总工时(h)",width:"120"}),l(s,{prop:"work_days",label:"工作天数",width:"110"}),l(s,{prop:"first_date",label:"首次填报",width:"130"}),l(s,{prop:"last_date",label:"最近填报",width:"130"})]),_:1},8,["data"])]),e("div",N,[t[6]||(t[6]=e("div",{class:"card-title"},"各项目工时对比",-1)),e("div",{ref_key:"chartRef",ref:d,class:"chart-container"},null,512)])])}}};export{F as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{r as b}from"./index-BfxBnpOb.js";import{i as w,L as x}from"./index-Bn9PLWWT.js";import{h as p,q as y,c as k,a as e,e as m,t as f,b as l,w as M,v as R,r as u,o as S}from"./index-C9J7Lk9r.js";const D={class:"stat-cards"},V={class:"stat-card purple"},C={class:"value"},Y={class:"stat-card green"},j={class:"value"},L={class:"content-card"},A={class:"card-title"},B={class:"filter-bar",style:{"margin-bottom":"0"}},N={class:"content-card"},F={__name:"RdStats",setup($){const n=p({}),d=p(null);let c=null;const r=p((()=>{const a=new Date,t=new Date;t.setMonth(t.getMonth()-1);const i=o=>String(o).padStart(2,"0"),s=o=>`${o.getFullYear()}-${i(o.getMonth()+1)}-${i(o.getDate())}`;return[s(t),s(a)]})());async function v(){const a={};r.value&&(a.start_date=r.value[0],a.end_date=r.value[1]);const t=await b.getStats(a);n.value=t.data,await R(),h()}function h(){if(!d.value)return;c||(c=w(d.value,"dark"));const a=n.value.projects||[];c.setOption({backgroundColor:"transparent",tooltip:{trigger:"axis",axisPointer:{type:"shadow"}},xAxis:{type:"category",data:a.map(t=>t.client_name),axisLabel:{color:"#94a3b8",interval:0,rotate:30}},yAxis:{type:"value",name:"工时(h)",axisLabel:{color:"#94a3b8"}},series:[{type:"bar",barMaxWidth:60,data:a.map(t=>t.total_hours),itemStyle:{color:new x(0,0,0,1,[{offset:0,color:"#6366f1"},{offset:1,color:"#4338ca"}]),borderRadius:[4,4,0,0]}}]})}return y(v),(a,t)=>{var _;const i=u("el-date-picker"),s=u("el-table-column"),o=u("el-table");return S(),k("div",null,[t[7]||(t[7]=e("div",{class:"page-header"},[e("h1",null,"个人统计"),e("p",null,"查看您的工时分布与月度趋势")],-1)),e("div",D,[e("div",V,[t[2]||(t[2]=e("div",{class:"label"},"参与项目数",-1)),e("div",C,[m(f(((_=n.value.projects)==null?void 0:_.length)||0),1),t[1]||(t[1]=e("span",{class:"unit"},"个",-1))])]),e("div",Y,[t[4]||(t[4]=e("div",{class:"label"},"累计总工时",-1)),e("div",j,[m(f(n.value.total_hours||0),1),t[3]||(t[3]=e("span",{class:"unit"},"小时",-1))])])]),e("div",L,[e("div",A,[t[5]||(t[5]=e("span",null,"各项目工时明细",-1)),e("div",B,[l(i,{modelValue:r.value,"onUpdate:modelValue":t[0]||(t[0]=g=>r.value=g),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",size:"default",onChange:v},null,8,["modelValue"])])]),l(o,{data:n.value.projects||[],stripe:""},{default:M(()=>[l(s,{prop:"client_name",label:"客户/项目","min-width":"180"}),l(s,{prop:"total_hours",label:"总工时(h)",width:"120"}),l(s,{prop:"work_days",label:"工作天数",width:"110"}),l(s,{prop:"first_date",label:"首次填报",width:"130"}),l(s,{prop:"last_date",label:"最近填报",width:"130"})]),_:1},8,["data"])]),e("div",N,[t[6]||(t[6]=e("div",{class:"card-title"},"各项目工时对比",-1)),e("div",{ref_key:"chartRef",ref:d,class:"chart-container"},null,512)])])}}};export{F as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{r as b}from"./index-D0SgC7D8.js";import{i as w,L as x}from"./index-Bn9PLWWT.js";import{h as p,q as y,c as k,a as e,e as m,t as f,b as l,w as M,v as R,r as u,o as S}from"./index-Dn6fDqC3.js";const D={class:"stat-cards"},V={class:"stat-card purple"},C={class:"value"},Y={class:"stat-card green"},j={class:"value"},L={class:"content-card"},A={class:"card-title"},B={class:"filter-bar",style:{"margin-bottom":"0"}},N={class:"content-card"},F={__name:"RdStats",setup($){const n=p({}),d=p(null);let c=null;const r=p((()=>{const a=new Date,t=new Date;t.setMonth(t.getMonth()-1);const i=o=>String(o).padStart(2,"0"),s=o=>`${o.getFullYear()}-${i(o.getMonth()+1)}-${i(o.getDate())}`;return[s(t),s(a)]})());async function v(){const a={};r.value&&(a.start_date=r.value[0],a.end_date=r.value[1]);const t=await b.getStats(a);n.value=t.data,await R(),h()}function h(){if(!d.value)return;c||(c=w(d.value,"dark"));const a=n.value.projects||[];c.setOption({backgroundColor:"transparent",tooltip:{trigger:"axis",axisPointer:{type:"shadow"}},xAxis:{type:"category",data:a.map(t=>t.client_name),axisLabel:{color:"#94a3b8",interval:0,rotate:30}},yAxis:{type:"value",name:"工时(h)",axisLabel:{color:"#94a3b8"}},series:[{type:"bar",barMaxWidth:60,data:a.map(t=>t.total_hours),itemStyle:{color:new x(0,0,0,1,[{offset:0,color:"#6366f1"},{offset:1,color:"#4338ca"}]),borderRadius:[4,4,0,0]}}]})}return y(v),(a,t)=>{var _;const i=u("el-date-picker"),s=u("el-table-column"),o=u("el-table");return S(),k("div",null,[t[7]||(t[7]=e("div",{class:"page-header"},[e("h1",null,"个人统计"),e("p",null,"查看您的工时分布与月度趋势")],-1)),e("div",D,[e("div",V,[t[2]||(t[2]=e("div",{class:"label"},"参与项目数",-1)),e("div",C,[m(f(((_=n.value.projects)==null?void 0:_.length)||0),1),t[1]||(t[1]=e("span",{class:"unit"},"个",-1))])]),e("div",Y,[t[4]||(t[4]=e("div",{class:"label"},"累计总工时",-1)),e("div",j,[m(f(n.value.total_hours||0),1),t[3]||(t[3]=e("span",{class:"unit"},"小时",-1))])])]),e("div",L,[e("div",A,[t[5]||(t[5]=e("span",null,"各项目工时明细",-1)),e("div",B,[l(i,{modelValue:r.value,"onUpdate:modelValue":t[0]||(t[0]=g=>r.value=g),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",size:"default",onChange:v},null,8,["modelValue"])])]),l(o,{data:n.value.projects||[],stripe:""},{default:M(()=>[l(s,{prop:"client_name",label:"客户/项目","min-width":"180"}),l(s,{prop:"total_hours",label:"总工时(h)",width:"120"}),l(s,{prop:"work_days",label:"工作天数",width:"110"}),l(s,{prop:"first_date",label:"首次填报",width:"130"}),l(s,{prop:"last_date",label:"最近填报",width:"130"})]),_:1},8,["data"])]),e("div",N,[t[6]||(t[6]=e("div",{class:"card-title"},"各项目工时对比",-1)),e("div",{ref_key:"chartRef",ref:d,class:"chart-container"},null,512)])])}}};export{F as default};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
.details-layout[data-v-db00ded5]{display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap}.calendar-section[data-v-db00ded5]{flex:1.5;min-width:450px}.info-section[data-v-db00ded5]{flex:1;min-width:320px;display:flex;flex-direction:column;gap:20px}.calendar-legends[data-v-db00ded5]{display:flex;gap:12px;font-size:12px}.legend-item[data-v-db00ded5]{display:flex;align-items:center;gap:4px;color:var(--text-secondary)}.legend-item .dot[data-v-db00ded5]{width:10px;height:10px;border-radius:50%}.legend-item .dot.green[data-v-db00ded5]{background:var(--success)}.legend-item .dot.yellow[data-v-db00ded5]{background:var(--warning)}.legend-item .dot.red[data-v-db00ded5]{background:var(--danger)}.legend-item .dot.gray[data-v-db00ded5]{background:var(--text-muted)}[data-v-db00ded5] .el-calendar{--el-calendar-border-color: var(--border-color);background-color:var(--bg-card);border:1px solid var(--border-color);border-radius:var(--radius)}[data-v-db00ded5] .el-calendar__header{border-bottom:1px solid var(--border-color);padding:16px 20px}[data-v-db00ded5] .el-calendar__title{color:var(--text-primary);font-weight:600}[data-v-db00ded5] .el-calendar-table{background-color:transparent}[data-v-db00ded5] .el-calendar-table thead th{color:var(--text-secondary);border-bottom:1px solid var(--border-color);padding:12px 0}[data-v-db00ded5] .el-calendar-table td{border-bottom:1px solid var(--border-color);border-right:1px solid var(--border-color);background-color:transparent!important}[data-v-db00ded5] .el-calendar-table tr:first-child td{border-top:none}[data-v-db00ded5] .el-calendar-table tr td:first-child{border-left:none}[data-v-db00ded5] .el-calendar-table tr td:last-child{border-right:none}[data-v-db00ded5] .el-calendar-table .el-calendar-day{height:75px;padding:6px;transition:var(--transition)}[data-v-db00ded5] .el-calendar-table .el-calendar-day:hover{background-color:var(--bg-hover)!important}[data-v-db00ded5] .el-calendar-table td.is-selected{background-color:#6366f11f!important}[data-v-db00ded5] .el-calendar-table td.is-today{border:2px solid var(--primary)!important}.day-cell-content[data-v-db00ded5]{display:flex;flex-direction:column;height:100%;justify-content:space-between}.day-header[data-v-db00ded5]{display:flex;justify-content:space-between;align-items:center}.day-num[data-v-db00ded5]{font-size:14px;font-weight:600;color:var(--text-primary)}.weekend-tag[data-v-db00ded5]{font-size:10px;color:var(--text-muted);background:#ffffff0d;padding:1px 4px;border-radius:3px}.day-body[data-v-db00ded5]{margin-top:4px;display:flex;justify-content:center}.status-badge[data-v-db00ded5]{font-size:11px;padding:1px 5px;border-radius:4px;font-weight:500;text-align:center;width:90%;letter-spacing:.2px}.status-badge.filled[data-v-db00ded5]{background:#10b98126;color:#34d399;border:1px solid rgba(16,185,129,.3)}.status-badge.partial[data-v-db00ded5]{background:#f59e0b26;color:#fbbf24;border:1px solid rgba(245,158,11,.3)}.status-badge.unfilled[data-v-db00ded5]{background:#ef444426;color:#f87171;border:1px solid rgba(239,68,68,.3)}.mini-stats-grid[data-v-db00ded5]{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}.mini-stat-item[data-v-db00ded5]{background:#1e293b66;border:1px solid var(--border-color);padding:16px;border-radius:var(--radius-sm);display:flex;flex-direction:column;gap:6px;transition:var(--transition)}.mini-stat-item[data-v-db00ded5]:hover{transform:translateY(-1px)}.green-glow[data-v-db00ded5]:hover{border-color:#10b98166;box-shadow:0 4px 12px #10b9811a}.yellow-glow[data-v-db00ded5]:hover{border-color:#f59e0b66;box-shadow:0 4px 12px #f59e0b1a}.red-glow[data-v-db00ded5]:hover{border-color:#ef444466;box-shadow:0 4px 12px #ef44441a}.purple-glow[data-v-db00ded5]:hover{border-color:#6366f166;box-shadow:0 4px 12px #6366f11a}.mini-stat-item .label[data-v-db00ded5]{font-size:12px;color:var(--text-muted);font-weight:500}.mini-stat-item .value[data-v-db00ded5]{font-size:22px;font-weight:700}.mini-stat-item .unit[data-v-db00ded5]{font-size:12px;color:var(--text-secondary);font-weight:400;margin-left:2px}.green-text[data-v-db00ded5]{color:#10b981}.yellow-text[data-v-db00ded5]{color:#f59e0b}.red-text[data-v-db00ded5]{color:#ef4444}.purple-text[data-v-db00ded5]{color:#818cf8}.records-card[data-v-db00ded5]{flex:1}.empty-placeholder[data-v-db00ded5]{padding:30px 0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.details-layout[data-v-92c2cf43]{display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap}.calendar-section[data-v-92c2cf43]{flex:1.5;min-width:450px}.info-section[data-v-92c2cf43]{flex:1;min-width:320px;display:flex;flex-direction:column;gap:20px}.calendar-legends[data-v-92c2cf43]{display:flex;gap:12px;font-size:12px}.legend-item[data-v-92c2cf43]{display:flex;align-items:center;gap:4px;color:var(--text-secondary)}.legend-item .dot[data-v-92c2cf43]{width:10px;height:10px;border-radius:50%}.legend-item .dot.green[data-v-92c2cf43]{background:var(--success)}.legend-item .dot.yellow[data-v-92c2cf43]{background:var(--warning)}.legend-item .dot.red[data-v-92c2cf43]{background:var(--danger)}.legend-item .dot.gray[data-v-92c2cf43]{background:var(--text-muted)}[data-v-92c2cf43] .el-calendar{--el-calendar-border-color: var(--border-color);background-color:var(--bg-card);border:1px solid var(--border-color);border-radius:var(--radius)}[data-v-92c2cf43] .el-calendar__header{border-bottom:1px solid var(--border-color);padding:16px 20px}[data-v-92c2cf43] .el-calendar__title{color:var(--text-primary);font-weight:600}[data-v-92c2cf43] .el-calendar-table{background-color:transparent}[data-v-92c2cf43] .el-calendar-table thead th{color:var(--text-secondary);border-bottom:1px solid var(--border-color);padding:12px 0}[data-v-92c2cf43] .el-calendar-table td{border-bottom:1px solid var(--border-color);border-right:1px solid var(--border-color);background-color:transparent!important}[data-v-92c2cf43] .el-calendar-table tr:first-child td{border-top:none}[data-v-92c2cf43] .el-calendar-table tr td:first-child{border-left:none}[data-v-92c2cf43] .el-calendar-table tr td:last-child{border-right:none}[data-v-92c2cf43] .el-calendar-table .el-calendar-day{height:75px;padding:6px;transition:var(--transition)}[data-v-92c2cf43] .el-calendar-table .el-calendar-day:hover{background-color:var(--bg-hover)!important}[data-v-92c2cf43] .el-calendar-table td.is-selected{background-color:#6366f11f!important}[data-v-92c2cf43] .el-calendar-table td.is-today{border:2px solid var(--primary)!important}.day-cell-content[data-v-92c2cf43]{display:flex;flex-direction:column;height:100%;justify-content:space-between}.day-header[data-v-92c2cf43]{display:flex;justify-content:space-between;align-items:center}.day-num[data-v-92c2cf43]{font-size:14px;font-weight:600;color:var(--text-primary)}.weekend-tag[data-v-92c2cf43]{font-size:10px;color:var(--text-muted);background:#ffffff0d;padding:1px 4px;border-radius:3px}.day-body[data-v-92c2cf43]{margin-top:4px;display:flex;justify-content:center}.status-badge[data-v-92c2cf43]{font-size:11px;padding:1px 5px;border-radius:4px;font-weight:500;text-align:center;width:90%;letter-spacing:.2px}.status-badge.filled[data-v-92c2cf43]{background:#10b98126;color:#34d399;border:1px solid rgba(16,185,129,.3)}.status-badge.partial[data-v-92c2cf43]{background:#f59e0b26;color:#fbbf24;border:1px solid rgba(245,158,11,.3)}.status-badge.unfilled[data-v-92c2cf43]{background:#ef444426;color:#f87171;border:1px solid rgba(239,68,68,.3)}.mini-stats-grid[data-v-92c2cf43]{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}.mini-stat-item[data-v-92c2cf43]{background:#1e293b66;border:1px solid var(--border-color);padding:16px;border-radius:var(--radius-sm);display:flex;flex-direction:column;gap:6px;transition:var(--transition)}.mini-stat-item[data-v-92c2cf43]:hover{transform:translateY(-1px)}.green-glow[data-v-92c2cf43]:hover{border-color:#10b98166;box-shadow:0 4px 12px #10b9811a}.yellow-glow[data-v-92c2cf43]:hover{border-color:#f59e0b66;box-shadow:0 4px 12px #f59e0b1a}.red-glow[data-v-92c2cf43]:hover{border-color:#ef444466;box-shadow:0 4px 12px #ef44441a}.purple-glow[data-v-92c2cf43]:hover{border-color:#6366f166;box-shadow:0 4px 12px #6366f11a}.mini-stat-item .label[data-v-92c2cf43]{font-size:12px;color:var(--text-muted);font-weight:500}.mini-stat-item .value[data-v-92c2cf43]{font-size:22px;font-weight:700}.mini-stat-item .unit[data-v-92c2cf43]{font-size:12px;color:var(--text-secondary);font-weight:400;margin-left:2px}.green-text[data-v-92c2cf43]{color:#10b981}.yellow-text[data-v-92c2cf43]{color:#f59e0b}.red-text[data-v-92c2cf43]{color:#ef4444}.purple-text[data-v-92c2cf43]{color:#818cf8}.records-card[data-v-92c2cf43]{flex:1}.empty-placeholder[data-v-92c2cf43]{padding:30px 0}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
.details-layout[data-v-e631ac04]{display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap}.calendar-section[data-v-e631ac04]{flex:1.5;min-width:450px}.info-section[data-v-e631ac04]{flex:1;min-width:320px;display:flex;flex-direction:column;gap:20px}.calendar-legends[data-v-e631ac04]{display:flex;gap:12px;font-size:12px}.legend-item[data-v-e631ac04]{display:flex;align-items:center;gap:4px;color:var(--text-secondary)}.legend-item .dot[data-v-e631ac04]{width:10px;height:10px;border-radius:50%}.legend-item .dot.green[data-v-e631ac04]{background:var(--success)}.legend-item .dot.yellow[data-v-e631ac04]{background:var(--warning)}.legend-item .dot.red[data-v-e631ac04]{background:var(--danger)}.legend-item .dot.gray[data-v-e631ac04]{background:var(--text-muted)}[data-v-e631ac04] .el-calendar{--el-calendar-border-color: var(--border-color);background-color:var(--bg-card);border:1px solid var(--border-color);border-radius:var(--radius)}[data-v-e631ac04] .el-calendar__header{border-bottom:1px solid var(--border-color);padding:16px 20px}[data-v-e631ac04] .el-calendar__title{color:var(--text-primary);font-weight:600}[data-v-e631ac04] .el-calendar-table{background-color:transparent}[data-v-e631ac04] .el-calendar-table thead th{color:var(--text-secondary);border-bottom:1px solid var(--border-color);padding:12px 0}[data-v-e631ac04] .el-calendar-table td{border-bottom:1px solid var(--border-color);border-right:1px solid var(--border-color);background-color:transparent!important}[data-v-e631ac04] .el-calendar-table tr:first-child td{border-top:none}[data-v-e631ac04] .el-calendar-table tr td:first-child{border-left:none}[data-v-e631ac04] .el-calendar-table tr td:last-child{border-right:none}[data-v-e631ac04] .el-calendar-table .el-calendar-day{height:75px;padding:6px;transition:var(--transition)}[data-v-e631ac04] .el-calendar-table .el-calendar-day:hover{background-color:var(--bg-hover)!important}[data-v-e631ac04] .el-calendar-table td.is-selected{background-color:#6366f11f!important}[data-v-e631ac04] .el-calendar-table td.is-today{border:2px solid var(--primary)!important}.day-cell-content[data-v-e631ac04]{display:flex;flex-direction:column;height:100%;justify-content:space-between}.day-header[data-v-e631ac04]{display:flex;justify-content:space-between;align-items:center}.day-num[data-v-e631ac04]{font-size:14px;font-weight:600;color:var(--text-primary)}.weekend-tag[data-v-e631ac04]{font-size:10px;color:var(--text-muted);background:#ffffff0d;padding:1px 4px;border-radius:3px}.day-body[data-v-e631ac04]{margin-top:4px;display:flex;justify-content:center}.status-badge[data-v-e631ac04]{font-size:11px;padding:1px 5px;border-radius:4px;font-weight:500;text-align:center;width:90%;letter-spacing:.2px}.status-badge.filled[data-v-e631ac04]{background:#10b98126;color:#34d399;border:1px solid rgba(16,185,129,.3)}.status-badge.partial[data-v-e631ac04]{background:#f59e0b26;color:#fbbf24;border:1px solid rgba(245,158,11,.3)}.status-badge.unfilled[data-v-e631ac04]{background:#ef444426;color:#f87171;border:1px solid rgba(239,68,68,.3)}.mini-stats-grid[data-v-e631ac04]{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}.mini-stat-item[data-v-e631ac04]{background:#1e293b66;border:1px solid var(--border-color);padding:16px;border-radius:var(--radius-sm);display:flex;flex-direction:column;gap:6px;transition:var(--transition)}.mini-stat-item[data-v-e631ac04]:hover{transform:translateY(-1px)}.green-glow[data-v-e631ac04]:hover{border-color:#10b98166;box-shadow:0 4px 12px #10b9811a}.yellow-glow[data-v-e631ac04]:hover{border-color:#f59e0b66;box-shadow:0 4px 12px #f59e0b1a}.red-glow[data-v-e631ac04]:hover{border-color:#ef444466;box-shadow:0 4px 12px #ef44441a}.purple-glow[data-v-e631ac04]:hover{border-color:#6366f166;box-shadow:0 4px 12px #6366f11a}.mini-stat-item .label[data-v-e631ac04]{font-size:12px;color:var(--text-muted);font-weight:500}.mini-stat-item .value[data-v-e631ac04]{font-size:22px;font-weight:700}.mini-stat-item .unit[data-v-e631ac04]{font-size:12px;color:var(--text-secondary);font-weight:400;margin-left:2px}.green-text[data-v-e631ac04]{color:#10b981}.yellow-text[data-v-e631ac04]{color:#f59e0b}.red-text[data-v-e631ac04]{color:#ef4444}.purple-text[data-v-e631ac04]{color:#818cf8}.records-card[data-v-e631ac04]{flex:1}.empty-placeholder[data-v-e631ac04]{padding:30px 0}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
import{h as i,q as E,c as u,a as t,b as c,w as _,F as k,i as b,r as p,o as s,B as F,k as N,e as V,n as U,t as w,E as C}from"./index-C9J7Lk9r.js";import{b as v}from"./index-BfxBnpOb.js";const Y={class:"content-card"},D={class:"card-title"},M={class:"filter-bar",style:{"margin-bottom":"0",display:"flex",gap:"8px"}},q={style:{display:"grid","grid-template-columns":"repeat(auto-fill, minmax(200px, 1fr))",gap:"16px","margin-top":"8px"}},H={class:"label"},L={style:{display:"flex","align-items":"center",gap:"8px"}},T={style:{"margin-top":"6px","font-size":"12px",color:"var(--text-muted)"}},I={__name:"WorkdaySetting",setup($){const h=new Date().getFullYear(),B=Array.from({length:5},(a,e)=>h-2+e),n=i(h),m=i(!1),y=i(!1);async function W(){y.value=!0;try{const a=await v.syncWorkdays(n.value);C.success(a.message||"同步成功"),g()}catch{}finally{y.value=!1}}const f=i(Array.from({length:12},(a,e)=>({month:e+1,days:22})));async function g(){try{const a=await v.getWorkdays(n.value);a.data.length>0&&(f.value=Array.from({length:12},(e,o)=>{const r=a.data.find(d=>d.month===o+1);return{month:o+1,days:r?r.days:22}}))}catch{}}async function A(){m.value=!0;try{await v.setWorkdays({year:n.value,months:f.value.map(a=>({month:a.month,days:a.days}))}),C.success("保存成功")}finally{m.value=!1}}return E(g),(a,e)=>{const o=p("el-option"),r=p("el-select"),d=p("el-button"),S=p("el-input-number");return s(),u("div",null,[e[4]||(e[4]=t("div",{class:"page-header"},[t("h1",null,"工作日设置"),t("p",null,"按年份设定每月工作日天数,用于计算研发工时上限")],-1)),t("div",Y,[t("div",D,[t("div",M,[c(r,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=l=>n.value=l),onChange:g,style:{width:"120px"}},{default:_(()=>[(s(!0),u(k,null,b(F(B),l=>(s(),N(o,{key:l,label:`${l}年`,value:l},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),c(d,{type:"warning",loading:y.value,onClick:W},{default:_(()=>[...e[1]||(e[1]=[V("🔄 联网自动同步节假日",-1)])]),_:1},8,["loading"])]),c(d,{type:"primary",loading:m.value,onClick:A},{default:_(()=>[...e[2]||(e[2]=[V("保存设置",-1)])]),_:1},8,["loading"])]),t("div",q,[(s(!0),u(k,null,b(f.value,(l,x)=>(s(),u("div",{key:x,class:U(["stat-card",["purple","green","blue","orange"][x%4]])},[t("div",H,w(l.month)+"月",1),t("div",L,[c(S,{modelValue:l.days,"onUpdate:modelValue":z=>l.days=z,min:0,max:31,size:"default",style:{width:"120px"}},null,8,["modelValue","onUpdate:modelValue"]),e[3]||(e[3]=t("span",{class:"unit"},"天",-1))]),t("div",T," 工时上限: "+w(l.days*8)+" 小时 ",1)],2))),128))])])])}}};export{I as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{h as i,q as E,c as u,a as t,b as c,w as _,F as k,i as b,r as p,o as s,B as F,k as N,e as V,n as U,t as w,E as C}from"./index-jnsG7DVq.js";import{b as v}from"./index-DD1Ebu5S.js";const Y={class:"content-card"},D={class:"card-title"},M={class:"filter-bar",style:{"margin-bottom":"0",display:"flex",gap:"8px"}},q={style:{display:"grid","grid-template-columns":"repeat(auto-fill, minmax(200px, 1fr))",gap:"16px","margin-top":"8px"}},H={class:"label"},L={style:{display:"flex","align-items":"center",gap:"8px"}},T={style:{"margin-top":"6px","font-size":"12px",color:"var(--text-muted)"}},I={__name:"WorkdaySetting",setup($){const h=new Date().getFullYear(),B=Array.from({length:5},(a,e)=>h-2+e),n=i(h),m=i(!1),y=i(!1);async function W(){y.value=!0;try{const a=await v.syncWorkdays(n.value);C.success(a.message||"同步成功"),g()}catch{}finally{y.value=!1}}const f=i(Array.from({length:12},(a,e)=>({month:e+1,days:22})));async function g(){try{const a=await v.getWorkdays(n.value);a.data.length>0&&(f.value=Array.from({length:12},(e,o)=>{const r=a.data.find(d=>d.month===o+1);return{month:o+1,days:r?r.days:22}}))}catch{}}async function A(){m.value=!0;try{await v.setWorkdays({year:n.value,months:f.value.map(a=>({month:a.month,days:a.days}))}),C.success("保存成功")}finally{m.value=!1}}return E(g),(a,e)=>{const o=p("el-option"),r=p("el-select"),d=p("el-button"),S=p("el-input-number");return s(),u("div",null,[e[4]||(e[4]=t("div",{class:"page-header"},[t("h1",null,"工作日设置"),t("p",null,"按年份设定每月工作日天数,用于计算研发工时上限")],-1)),t("div",Y,[t("div",D,[t("div",M,[c(r,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=l=>n.value=l),onChange:g,style:{width:"120px"}},{default:_(()=>[(s(!0),u(k,null,b(F(B),l=>(s(),N(o,{key:l,label:`${l}年`,value:l},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),c(d,{type:"warning",loading:y.value,onClick:W},{default:_(()=>[...e[1]||(e[1]=[V("🔄 联网自动同步节假日",-1)])]),_:1},8,["loading"])]),c(d,{type:"primary",loading:m.value,onClick:A},{default:_(()=>[...e[2]||(e[2]=[V("保存设置",-1)])]),_:1},8,["loading"])]),t("div",q,[(s(!0),u(k,null,b(f.value,(l,x)=>(s(),u("div",{key:x,class:U(["stat-card",["purple","green","blue","orange"][x%4]])},[t("div",H,w(l.month)+"月",1),t("div",L,[c(S,{modelValue:l.days,"onUpdate:modelValue":z=>l.days=z,min:0,max:31,size:"default",style:{width:"120px"}},null,8,["modelValue","onUpdate:modelValue"]),e[3]||(e[3]=t("span",{class:"unit"},"天",-1))]),t("div",T," 工时上限: "+w(l.days*8)+" 小时 ",1)],2))),128))])])])}}};export{I as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{h as i,q as E,c as u,a as t,b as c,w as _,F as k,i as b,r as p,o as s,B as F,k as N,e as V,n as U,t as w,E as C}from"./index-CYgyy2lw.js";import{b as v}from"./index-B5S4JoyK.js";const Y={class:"content-card"},D={class:"card-title"},M={class:"filter-bar",style:{"margin-bottom":"0",display:"flex",gap:"8px"}},q={style:{display:"grid","grid-template-columns":"repeat(auto-fill, minmax(200px, 1fr))",gap:"16px","margin-top":"8px"}},H={class:"label"},L={style:{display:"flex","align-items":"center",gap:"8px"}},T={style:{"margin-top":"6px","font-size":"12px",color:"var(--text-muted)"}},I={__name:"WorkdaySetting",setup($){const h=new Date().getFullYear(),B=Array.from({length:5},(a,e)=>h-2+e),n=i(h),m=i(!1),y=i(!1);async function W(){y.value=!0;try{const a=await v.syncWorkdays(n.value);C.success(a.message||"同步成功"),g()}catch{}finally{y.value=!1}}const f=i(Array.from({length:12},(a,e)=>({month:e+1,days:22})));async function g(){try{const a=await v.getWorkdays(n.value);a.data.length>0&&(f.value=Array.from({length:12},(e,o)=>{const r=a.data.find(d=>d.month===o+1);return{month:o+1,days:r?r.days:22}}))}catch{}}async function A(){m.value=!0;try{await v.setWorkdays({year:n.value,months:f.value.map(a=>({month:a.month,days:a.days}))}),C.success("保存成功")}finally{m.value=!1}}return E(g),(a,e)=>{const o=p("el-option"),r=p("el-select"),d=p("el-button"),S=p("el-input-number");return s(),u("div",null,[e[4]||(e[4]=t("div",{class:"page-header"},[t("h1",null,"工作日设置"),t("p",null,"按年份设定每月工作日天数,用于计算研发工时上限")],-1)),t("div",Y,[t("div",D,[t("div",M,[c(r,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=l=>n.value=l),onChange:g,style:{width:"120px"}},{default:_(()=>[(s(!0),u(k,null,b(F(B),l=>(s(),N(o,{key:l,label:`${l}年`,value:l},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),c(d,{type:"warning",loading:y.value,onClick:W},{default:_(()=>[...e[1]||(e[1]=[V("🔄 联网自动同步节假日",-1)])]),_:1},8,["loading"])]),c(d,{type:"primary",loading:m.value,onClick:A},{default:_(()=>[...e[2]||(e[2]=[V("保存设置",-1)])]),_:1},8,["loading"])]),t("div",q,[(s(!0),u(k,null,b(f.value,(l,x)=>(s(),u("div",{key:x,class:U(["stat-card",["purple","green","blue","orange"][x%4]])},[t("div",H,w(l.month)+"月",1),t("div",L,[c(S,{modelValue:l.days,"onUpdate:modelValue":z=>l.days=z,min:0,max:31,size:"default",style:{width:"120px"}},null,8,["modelValue","onUpdate:modelValue"]),e[3]||(e[3]=t("span",{class:"unit"},"天",-1))]),t("div",T," 工时上限: "+w(l.days*8)+" 小时 ",1)],2))),128))])])])}}};export{I as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{h as i,q as E,c as u,a as t,b as c,w as _,F as k,i as b,r as p,o as s,B as F,k as N,e as V,n as U,t as w,E as C}from"./index-B5IUyHJe.js";import{b as v}from"./index-OKS9XN2R.js";const Y={class:"content-card"},D={class:"card-title"},M={class:"filter-bar",style:{"margin-bottom":"0",display:"flex",gap:"8px"}},q={style:{display:"grid","grid-template-columns":"repeat(auto-fill, minmax(200px, 1fr))",gap:"16px","margin-top":"8px"}},H={class:"label"},L={style:{display:"flex","align-items":"center",gap:"8px"}},T={style:{"margin-top":"6px","font-size":"12px",color:"var(--text-muted)"}},I={__name:"WorkdaySetting",setup($){const h=new Date().getFullYear(),B=Array.from({length:5},(a,e)=>h-2+e),n=i(h),m=i(!1),y=i(!1);async function W(){y.value=!0;try{const a=await v.syncWorkdays(n.value);C.success(a.message||"同步成功"),g()}catch{}finally{y.value=!1}}const f=i(Array.from({length:12},(a,e)=>({month:e+1,days:22})));async function g(){try{const a=await v.getWorkdays(n.value);a.data.length>0&&(f.value=Array.from({length:12},(e,o)=>{const r=a.data.find(d=>d.month===o+1);return{month:o+1,days:r?r.days:22}}))}catch{}}async function A(){m.value=!0;try{await v.setWorkdays({year:n.value,months:f.value.map(a=>({month:a.month,days:a.days}))}),C.success("保存成功")}finally{m.value=!1}}return E(g),(a,e)=>{const o=p("el-option"),r=p("el-select"),d=p("el-button"),S=p("el-input-number");return s(),u("div",null,[e[4]||(e[4]=t("div",{class:"page-header"},[t("h1",null,"工作日设置"),t("p",null,"按年份设定每月工作日天数,用于计算研发工时上限")],-1)),t("div",Y,[t("div",D,[t("div",M,[c(r,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=l=>n.value=l),onChange:g,style:{width:"120px"}},{default:_(()=>[(s(!0),u(k,null,b(F(B),l=>(s(),N(o,{key:l,label:`${l}年`,value:l},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),c(d,{type:"warning",loading:y.value,onClick:W},{default:_(()=>[...e[1]||(e[1]=[V("🔄 联网自动同步节假日",-1)])]),_:1},8,["loading"])]),c(d,{type:"primary",loading:m.value,onClick:A},{default:_(()=>[...e[2]||(e[2]=[V("保存设置",-1)])]),_:1},8,["loading"])]),t("div",q,[(s(!0),u(k,null,b(f.value,(l,x)=>(s(),u("div",{key:x,class:U(["stat-card",["purple","green","blue","orange"][x%4]])},[t("div",H,w(l.month)+"月",1),t("div",L,[c(S,{modelValue:l.days,"onUpdate:modelValue":z=>l.days=z,min:0,max:31,size:"default",style:{width:"120px"}},null,8,["modelValue","onUpdate:modelValue"]),e[3]||(e[3]=t("span",{class:"unit"},"天",-1))]),t("div",T," 工时上限: "+w(l.days*8)+" 小时 ",1)],2))),128))])])])}}};export{I as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{h as i,q as E,c as u,a as t,b as c,w as _,F as k,i as b,r as p,o as s,B as F,k as N,e as V,n as U,t as w,E as C}from"./index-Dn6fDqC3.js";import{b as v}from"./index-D0SgC7D8.js";const Y={class:"content-card"},D={class:"card-title"},M={class:"filter-bar",style:{"margin-bottom":"0",display:"flex",gap:"8px"}},q={style:{display:"grid","grid-template-columns":"repeat(auto-fill, minmax(200px, 1fr))",gap:"16px","margin-top":"8px"}},H={class:"label"},L={style:{display:"flex","align-items":"center",gap:"8px"}},T={style:{"margin-top":"6px","font-size":"12px",color:"var(--text-muted)"}},I={__name:"WorkdaySetting",setup($){const h=new Date().getFullYear(),B=Array.from({length:5},(a,e)=>h-2+e),n=i(h),m=i(!1),y=i(!1);async function W(){y.value=!0;try{const a=await v.syncWorkdays(n.value);C.success(a.message||"同步成功"),g()}catch{}finally{y.value=!1}}const f=i(Array.from({length:12},(a,e)=>({month:e+1,days:22})));async function g(){try{const a=await v.getWorkdays(n.value);a.data.length>0&&(f.value=Array.from({length:12},(e,o)=>{const r=a.data.find(d=>d.month===o+1);return{month:o+1,days:r?r.days:22}}))}catch{}}async function A(){m.value=!0;try{await v.setWorkdays({year:n.value,months:f.value.map(a=>({month:a.month,days:a.days}))}),C.success("保存成功")}finally{m.value=!1}}return E(g),(a,e)=>{const o=p("el-option"),r=p("el-select"),d=p("el-button"),S=p("el-input-number");return s(),u("div",null,[e[4]||(e[4]=t("div",{class:"page-header"},[t("h1",null,"工作日设置"),t("p",null,"按年份设定每月工作日天数,用于计算研发工时上限")],-1)),t("div",Y,[t("div",D,[t("div",M,[c(r,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=l=>n.value=l),onChange:g,style:{width:"120px"}},{default:_(()=>[(s(!0),u(k,null,b(F(B),l=>(s(),N(o,{key:l,label:`${l}年`,value:l},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),c(d,{type:"warning",loading:y.value,onClick:W},{default:_(()=>[...e[1]||(e[1]=[V("🔄 联网自动同步节假日",-1)])]),_:1},8,["loading"])]),c(d,{type:"primary",loading:m.value,onClick:A},{default:_(()=>[...e[2]||(e[2]=[V("保存设置",-1)])]),_:1},8,["loading"])]),t("div",q,[(s(!0),u(k,null,b(f.value,(l,x)=>(s(),u("div",{key:x,class:U(["stat-card",["purple","green","blue","orange"][x%4]])},[t("div",H,w(l.month)+"月",1),t("div",L,[c(S,{modelValue:l.days,"onUpdate:modelValue":z=>l.days=z,min:0,max:31,size:"default",style:{width:"120px"}},null,8,["modelValue","onUpdate:modelValue"]),e[3]||(e[3]=t("span",{class:"unit"},"天",-1))]),t("div",T," 工时上限: "+w(l.days*8)+" 小时 ",1)],2))),128))])])])}}};export{I as default};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
import{m as n,h as r}from"./index-C9J7Lk9r.js";const m=n("user",()=>{const o=r(JSON.parse(localStorage.getItem("user")||"null")),t=r(localStorage.getItem("token")||"");function s(e,l){t.value=e,o.value=l,localStorage.setItem("token",e),localStorage.setItem("user",JSON.stringify(l))}function a(){t.value="",o.value=null,localStorage.removeItem("token"),localStorage.removeItem("user")}return{user:o,token:t,setLogin:s,logout:a,isLoggedIn:()=>!!t.value,getRole:()=>{var e;return((e=o.value)==null?void 0:e.role)||""}}});export{m as u};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{m as n,h as r}from"./index-B5IUyHJe.js";const m=n("user",()=>{const o=r(JSON.parse(localStorage.getItem("user")||"null")),t=r(localStorage.getItem("token")||"");function s(e,l){t.value=e,o.value=l,localStorage.setItem("token",e),localStorage.setItem("user",JSON.stringify(l))}function a(){t.value="",o.value=null,localStorage.removeItem("token"),localStorage.removeItem("user")}return{user:o,token:t,setLogin:s,logout:a,isLoggedIn:()=>!!t.value,getRole:()=>{var e;return((e=o.value)==null?void 0:e.role)||""}}});export{m as u};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{m as n,h as r}from"./index-Dn6fDqC3.js";const m=n("user",()=>{const o=r(JSON.parse(localStorage.getItem("user")||"null")),t=r(localStorage.getItem("token")||"");function s(e,l){t.value=e,o.value=l,localStorage.setItem("token",e),localStorage.setItem("user",JSON.stringify(l))}function a(){t.value="",o.value=null,localStorage.removeItem("token"),localStorage.removeItem("user")}return{user:o,token:t,setLogin:s,logout:a,isLoggedIn:()=>!!t.value,getRole:()=>{var e;return((e=o.value)==null?void 0:e.role)||""}}});export{m as u};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{m as n,h as r}from"./index-CYgyy2lw.js";const m=n("user",()=>{const o=r(JSON.parse(localStorage.getItem("user")||"null")),t=r(localStorage.getItem("token")||"");function s(e,l){t.value=e,o.value=l,localStorage.setItem("token",e),localStorage.setItem("user",JSON.stringify(l))}function a(){t.value="",o.value=null,localStorage.removeItem("token"),localStorage.removeItem("user")}return{user:o,token:t,setLogin:s,logout:a,isLoggedIn:()=>!!t.value,getRole:()=>{var e;return((e=o.value)==null?void 0:e.role)||""}}});export{m as u};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{m as n,h as r}from"./index-jnsG7DVq.js";const m=n("user",()=>{const o=r(JSON.parse(localStorage.getItem("user")||"null")),t=r(localStorage.getItem("token")||"");function s(e,l){t.value=e,o.value=l,localStorage.setItem("token",e),localStorage.setItem("user",JSON.stringify(l))}function a(){t.value="",o.value=null,localStorage.removeItem("token"),localStorage.removeItem("user")}return{user:o,token:t,setLogin:s,logout:a,isLoggedIn:()=>!!t.value,getRole:()=>{var e;return((e=o.value)==null?void 0:e.role)||""}}});export{m as u};
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<title>工时管理系统</title>
|
<title>工时管理系统</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script type="module" crossorigin src="/assets/index-B7ewtAvV.js"></script>
|
<script type="module" crossorigin src="/assets/index-B5IUyHJe.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DkemfcLm.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DkemfcLm.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+43
-2
@@ -147,10 +147,26 @@ router.delete('/clients/:id', (req, res) => {
|
|||||||
// Workday Settings
|
// Workday Settings
|
||||||
// ========================
|
// ========================
|
||||||
|
|
||||||
router.get('/workdays', (req, res) => {
|
router.get('/workdays', async (req, res) => {
|
||||||
const db = getDB(req);
|
const db = getDB(req);
|
||||||
const year = parseInt(req.query.year) || new Date().getFullYear();
|
const year = parseInt(req.query.year) || new Date().getFullYear();
|
||||||
const workdays = db.all('SELECT * FROM workdays WHERE year = ? ORDER BY month', [year]);
|
let workdays = db.all('SELECT * FROM workdays WHERE year = ? ORDER BY month', [year]);
|
||||||
|
|
||||||
|
// Auto-calculate and seed if database is empty for this year
|
||||||
|
if (workdays.length === 0) {
|
||||||
|
try {
|
||||||
|
const { getHolidays, calculateMonthlyWorkdays } = require('../utils/holiday');
|
||||||
|
const holidayMap = await getHolidays(db, year);
|
||||||
|
const computed = calculateMonthlyWorkdays(year, holidayMap);
|
||||||
|
for (const item of computed) {
|
||||||
|
db.run('INSERT OR IGNORE INTO workdays (year, month, days) VALUES (?, ?, ?)', [year, item.month, item.days]);
|
||||||
|
}
|
||||||
|
workdays = db.all('SELECT * FROM workdays WHERE year = ? ORDER BY month', [year]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[Admin] Error auto-seeding workdays:', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.json({ code: 200, data: workdays });
|
res.json({ code: 200, data: workdays });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -169,6 +185,31 @@ router.post('/workdays', (req, res) => {
|
|||||||
res.json({ code: 200, message: '设置成功' });
|
res.json({ code: 200, message: '设置成功' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /api/admin/workdays/sync — manually trigger network sync
|
||||||
|
router.post('/workdays/sync', async (req, res) => {
|
||||||
|
const db = getDB(req);
|
||||||
|
const { year } = req.body;
|
||||||
|
if (!year) return res.status(400).json({ code: 400, message: '请指定要同步的年份' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { syncYearWorkdays } = require('../utils/holiday');
|
||||||
|
const workdaysList = await syncYearWorkdays(db, parseInt(year));
|
||||||
|
res.json({ code: 200, message: '自动同步成功', data: workdaysList });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[Admin] Sync error:', e.message);
|
||||||
|
res.status(500).json({ code: 500, message: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/admin/holidays — retrieve holiday data for a specific year
|
||||||
|
router.get('/holidays', async (req, res) => {
|
||||||
|
const db = getDB(req);
|
||||||
|
const year = parseInt(req.query.year) || new Date().getFullYear();
|
||||||
|
const { getHolidays } = require('../utils/holiday');
|
||||||
|
const holidayMap = await getHolidays(db, year);
|
||||||
|
res.json({ code: 200, data: holidayMap || {} });
|
||||||
|
});
|
||||||
|
|
||||||
// ========================
|
// ========================
|
||||||
// Global Stats
|
// Global Stats
|
||||||
// ========================
|
// ========================
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ router.get('/project-types', (req, res) => {
|
|||||||
res.json({ code: 200, data: db.all('SELECT id, name FROM project_types ORDER BY id') });
|
res.json({ code: 200, data: db.all('SELECT id, name FROM project_types ORDER BY id') });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/rd/holidays — retrieve holiday data for a specific year
|
||||||
|
router.get('/holidays', async (req, res) => {
|
||||||
|
const db = getDB(req);
|
||||||
|
const year = parseInt(req.query.year) || new Date().getFullYear();
|
||||||
|
const { getHolidays } = require('../utils/holiday');
|
||||||
|
const holidayMap = await getHolidays(db, year);
|
||||||
|
res.json({ code: 200, data: holidayMap || {} });
|
||||||
|
});
|
||||||
|
|
||||||
// GET /api/rd/timesheets?date=YYYY-MM-DD or ?month=YYYY-MM
|
// GET /api/rd/timesheets?date=YYYY-MM-DD or ?month=YYYY-MM
|
||||||
router.get('/timesheets', (req, res) => {
|
router.get('/timesheets', (req, res) => {
|
||||||
const db = getDB(req);
|
const db = getDB(req);
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch holiday data from timor.tech API for a specific year
|
||||||
|
* @param {number} year
|
||||||
|
* @returns {Promise<object>}
|
||||||
|
*/
|
||||||
|
function fetchHolidaysFromNetwork(year) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Note: timor.tech API works best with trailing slash
|
||||||
|
const url = `https://timor.tech/api/holiday/year/${year}/`;
|
||||||
|
https.get(url, { headers: { 'User-Agent': 'WorkTime-Manager/1.0' } }, (res) => {
|
||||||
|
let rawData = '';
|
||||||
|
res.on('data', (chunk) => { rawData += chunk; });
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(rawData);
|
||||||
|
if (parsed.code === 0 && parsed.holiday) {
|
||||||
|
resolve(parsed.holiday);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`API returned code ${parsed.code}`));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get holiday data for a specific year, from cache or network
|
||||||
|
* @param {object} db DB wrapper instance
|
||||||
|
* @param {number} year
|
||||||
|
* @returns {Promise<object|null>}
|
||||||
|
*/
|
||||||
|
async function getHolidays(db, year) {
|
||||||
|
try {
|
||||||
|
const cached = db.get('SELECT holiday_data FROM holiday_cache WHERE year = ?', [year]);
|
||||||
|
if (cached) {
|
||||||
|
return JSON.parse(cached.holiday_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not cached, fetch from network
|
||||||
|
console.log(`[Holiday] Fetching holiday data from network for year ${year}...`);
|
||||||
|
const holidays = await fetchHolidaysFromNetwork(year);
|
||||||
|
|
||||||
|
// Save to cache
|
||||||
|
db.run(
|
||||||
|
'INSERT OR REPLACE INTO holiday_cache (year, holiday_data, updated_at) VALUES (?, ?, datetime(\'now\', \'localtime\'))',
|
||||||
|
[year, JSON.stringify(holidays)]
|
||||||
|
);
|
||||||
|
return holidays;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[Holiday] Error getting holidays for ${year}:`, e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to check if a date is weekend (Sat/Sun) in local time
|
||||||
|
* @param {string} dateStr YYYY-MM-DD
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isWeekendDefault(dateStr) {
|
||||||
|
const d = new Date(dateStr + 'T00:00:00');
|
||||||
|
const day = d.getDay();
|
||||||
|
return day === 0 || day === 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the number of workdays for each month in a given year
|
||||||
|
* @param {number} year
|
||||||
|
* @param {object|null} holidayMap
|
||||||
|
* @returns {Array<{month: number, days: number}>}
|
||||||
|
*/
|
||||||
|
function calculateMonthlyWorkdays(year, holidayMap) {
|
||||||
|
const result = [];
|
||||||
|
for (let m = 1; m <= 12; m++) {
|
||||||
|
const daysInMonth = new Date(year, m, 0).getDate();
|
||||||
|
let workdays = 0;
|
||||||
|
|
||||||
|
for (let day = 1; day <= daysInMonth; day++) {
|
||||||
|
const dStr = `${year}-${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||||
|
const mmDd = `${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
let isWork = true;
|
||||||
|
if (holidayMap && holidayMap[mmDd]) {
|
||||||
|
// If date is in the map, holiday === true means rest day, holiday === false means work day (調休补班)
|
||||||
|
isWork = !holidayMap[mmDd].holiday;
|
||||||
|
} else {
|
||||||
|
// Fall back to default weekends
|
||||||
|
isWork = !isWeekendDefault(dStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWork) {
|
||||||
|
workdays++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push({ month: m, days: workdays });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync holiday data from network, calculate and update database workdays
|
||||||
|
* @param {object} db
|
||||||
|
* @param {number} year
|
||||||
|
* @returns {Promise<Array<{month: number, days: number}>>}
|
||||||
|
*/
|
||||||
|
async function syncYearWorkdays(db, year) {
|
||||||
|
// Clear cache first to force fresh fetch on manual sync request
|
||||||
|
db.run('DELETE FROM holiday_cache WHERE year = ?', [year]);
|
||||||
|
|
||||||
|
const holidayMap = await getHolidays(db, year);
|
||||||
|
if (!holidayMap) {
|
||||||
|
throw new Error('无法从网络同步节假日数据,请检查网络连接');
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyWorkdays = calculateMonthlyWorkdays(year, holidayMap);
|
||||||
|
|
||||||
|
// Save to workdays table
|
||||||
|
for (const item of monthlyWorkdays) {
|
||||||
|
const existing = db.get('SELECT id FROM workdays WHERE year = ? AND month = ?', [year, item.month]);
|
||||||
|
if (existing) {
|
||||||
|
db.run('UPDATE workdays SET days = ? WHERE year = ? AND month = ?', [item.days, year, item.month]);
|
||||||
|
} else {
|
||||||
|
db.run('INSERT INTO workdays (year, month, days) VALUES (?, ?, ?)', [year, item.month, item.days]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return monthlyWorkdays;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getHolidays,
|
||||||
|
syncYearWorkdays,
|
||||||
|
calculateMonthlyWorkdays
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user