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

This commit is contained in:
qwq
2026-06-04 15:53:23 +08:00
parent 4a8f530921
commit cd63391f89
84 changed files with 874 additions and 31 deletions
+5 -2
View File
@@ -21,7 +21,7 @@ api.interceptors.response.use(
response => response.data, 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 } })
} }
+57 -11
View File
@@ -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++
} }
} }
+14 -1
View File
@@ -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 }))
+38 -9
View File
@@ -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
View File
@@ -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.
+8
View File
@@ -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
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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};
+1 -1
View File
@@ -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
View File
@@ -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
// ======================== // ========================
+9
View File
@@ -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);
+141
View File
@@ -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
};