新增个人轮班 MVP:左上角胶囊显示班/休
新增 ShiftPattern 数据模型,以锚点日期 + 循环序列描述周期性轮班,与法定调休完全独立。 默认配置 2026-05-15 起 [班,班,休,休] 4 天周期,DayCell 左上角渲染胶囊角标。
This commit is contained in:
parent
f63b57eef1
commit
ecf4cf601e
@ -77,6 +77,19 @@ class CalendarViewModel(
|
|||||||
var yearViewYear by mutableStateOf(today.year)
|
var yearViewYear by mutableStateOf(today.year)
|
||||||
internal set
|
internal set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人轮班。与法定节假日完全独立,不受调休影响。
|
||||||
|
* MVP 默认:2026-05-15 起,2 班 2 休循环。后续接入设置页与持久化。
|
||||||
|
*/
|
||||||
|
var shiftPattern: ShiftPattern? by mutableStateOf(
|
||||||
|
ShiftPattern(
|
||||||
|
anchorDate = LocalDate(2026, 5, 15),
|
||||||
|
cycle = listOf(ShiftKind.WORK, ShiftKind.WORK, ShiftKind.OFF, ShiftKind.OFF)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun shiftKindAt(date: LocalDate): ShiftKind? = shiftPattern?.kindAt(date)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选中指定日期。
|
* 选中指定日期。
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
package plus.rua.project
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.daysUntil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人轮班类型。仅区分上班与休息;后续可扩展早/中/晚班、休假等。
|
||||||
|
*/
|
||||||
|
enum class ShiftKind { WORK, OFF }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人轮班周期。
|
||||||
|
*
|
||||||
|
* 与法定节假日完全独立:周期内某天是 WORK 还是 OFF,只看
|
||||||
|
* `(date - anchorDate) mod cycle.size` 在 cycle 中的取值,不受任何节假日/调休影响。
|
||||||
|
*
|
||||||
|
* @param anchorDate 周期基准日,对应 cycle[0]
|
||||||
|
* @param cycle 一个周期内的班次序列,例如 [WORK, WORK, OFF, OFF] 表示 "2 班 2 休"
|
||||||
|
* @param name 方案名,用于后续多套方案场景
|
||||||
|
*/
|
||||||
|
data class ShiftPattern(
|
||||||
|
val anchorDate: LocalDate,
|
||||||
|
val cycle: List<ShiftKind>,
|
||||||
|
val name: String = "默认"
|
||||||
|
) {
|
||||||
|
fun kindAt(date: LocalDate): ShiftKind? {
|
||||||
|
if (cycle.isEmpty()) return null
|
||||||
|
val diff = anchorDate.daysUntil(date)
|
||||||
|
val size = cycle.size
|
||||||
|
val idx = ((diff % size) + size) % size
|
||||||
|
return cycle[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,6 +22,7 @@ import kotlinx.datetime.LocalDate
|
|||||||
import kotlinx.datetime.minus
|
import kotlinx.datetime.minus
|
||||||
import kotlinx.datetime.number
|
import kotlinx.datetime.number
|
||||||
import kotlinx.datetime.plus
|
import kotlinx.datetime.plus
|
||||||
|
import plus.rua.project.ShiftKind
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 月度日历网格页面,支持两阶段折叠动画。
|
* 月度日历网格页面,支持两阶段折叠动画。
|
||||||
@ -50,6 +51,7 @@ fun CalendarMonthPage(
|
|||||||
collapseProgress: Float,
|
collapseProgress: Float,
|
||||||
rowHeightPx: Int,
|
rowHeightPx: Int,
|
||||||
effectiveWeeks: Float,
|
effectiveWeeks: Float,
|
||||||
|
shiftKindAt: (LocalDate) -> ShiftKind?,
|
||||||
onRowHeightMeasured: ((Int) -> Unit)? = null,
|
onRowHeightMeasured: ((Int) -> Unit)? = null,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@ -152,6 +154,7 @@ fun CalendarMonthPage(
|
|||||||
isCurrentMonth = dayData.isCurrentMonth,
|
isCurrentMonth = dayData.isCurrentMonth,
|
||||||
isSelected = dayData.date == selectedDate,
|
isSelected = dayData.date == selectedDate,
|
||||||
isToday = dayData.date == today,
|
isToday = dayData.date == today,
|
||||||
|
shiftKind = shiftKindAt(dayData.date),
|
||||||
onClick = { onDateClick(dayData.date) },
|
onClick = { onDateClick(dayData.date) },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -259,6 +259,7 @@ fun CalendarMonthView(
|
|||||||
}
|
}
|
||||||
viewModel.selectDate(date)
|
viewModel.selectDate(date)
|
||||||
},
|
},
|
||||||
|
shiftKindAt = { date -> viewModel.shiftKindAt(date) },
|
||||||
modifier = pagerModifier
|
modifier = pagerModifier
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -275,6 +276,7 @@ fun CalendarMonthView(
|
|||||||
collapseProgress = viewModel.collapseProgress,
|
collapseProgress = viewModel.collapseProgress,
|
||||||
rowHeightPx = rowHeightPx,
|
rowHeightPx = rowHeightPx,
|
||||||
effectiveWeeks = effectiveWeeks,
|
effectiveWeeks = effectiveWeeks,
|
||||||
|
shiftKindAt = { date -> viewModel.shiftKindAt(date) },
|
||||||
onRowHeightMeasured = { h ->
|
onRowHeightMeasured = { h ->
|
||||||
if (h > 0) rowHeightPx = h
|
if (h > 0) rowHeightPx = h
|
||||||
},
|
},
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.drop
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.number
|
import kotlinx.datetime.number
|
||||||
|
import plus.rua.project.ShiftKind
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,6 +43,7 @@ fun CalendarPager(
|
|||||||
collapseProgress: Float,
|
collapseProgress: Float,
|
||||||
rowHeightPx: Int,
|
rowHeightPx: Int,
|
||||||
effectiveWeeks: Float,
|
effectiveWeeks: Float,
|
||||||
|
shiftKindAt: (LocalDate) -> ShiftKind?,
|
||||||
onRowHeightMeasured: ((Int) -> Unit)? = null,
|
onRowHeightMeasured: ((Int) -> Unit)? = null,
|
||||||
pagerState: PagerState,
|
pagerState: PagerState,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
@ -94,6 +96,7 @@ fun CalendarPager(
|
|||||||
collapseProgress = collapseProgress,
|
collapseProgress = collapseProgress,
|
||||||
rowHeightPx = rowHeightPx,
|
rowHeightPx = rowHeightPx,
|
||||||
effectiveWeeks = effectiveWeeks,
|
effectiveWeeks = effectiveWeeks,
|
||||||
|
shiftKindAt = shiftKindAt,
|
||||||
onRowHeightMeasured = onRowHeightMeasured,
|
onRowHeightMeasured = onRowHeightMeasured,
|
||||||
modifier = Modifier.alpha(alpha)
|
modifier = Modifier.alpha(alpha)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import com.tyme.solar.SolarDay
|
import com.tyme.solar.SolarDay
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
|
import plus.rua.project.ShiftKind
|
||||||
|
|
||||||
enum class DayCellState {
|
enum class DayCellState {
|
||||||
NORMAL, OTHER_MONTH, TODAY, SELECTED, SELECTED_TODAY
|
NORMAL, OTHER_MONTH, TODAY, SELECTED, SELECTED_TODAY
|
||||||
@ -48,6 +49,7 @@ enum class DayCellState {
|
|||||||
* @param isCurrentMonth 是否属于当前显示月份
|
* @param isCurrentMonth 是否属于当前显示月份
|
||||||
* @param isSelected 是否为选中日期
|
* @param isSelected 是否为选中日期
|
||||||
* @param isToday 是否为今天
|
* @param isToday 是否为今天
|
||||||
|
* @param shiftKind 个人轮班类型,左上角胶囊显示;null 表示不显示。与法定调休完全独立。
|
||||||
* @param onClick 点击回调
|
* @param onClick 点击回调
|
||||||
* @param modifier 外部布局修饰符
|
* @param modifier 外部布局修饰符
|
||||||
*/
|
*/
|
||||||
@ -57,6 +59,7 @@ fun DayCell(
|
|||||||
isCurrentMonth: Boolean,
|
isCurrentMonth: Boolean,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
isToday: Boolean,
|
isToday: Boolean,
|
||||||
|
shiftKind: ShiftKind?,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@ -243,6 +246,33 @@ fun DayCell(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (shiftKind != null) {
|
||||||
|
val shiftBgColor = if (shiftKind == ShiftKind.WORK) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.error
|
||||||
|
}
|
||||||
|
val shiftFgColor = if (shiftKind == ShiftKind.WORK) {
|
||||||
|
MaterialTheme.colorScheme.onPrimary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onError
|
||||||
|
}
|
||||||
|
val shiftLabel = if (shiftKind == ShiftKind.WORK) "班" else "休"
|
||||||
|
val shiftAlpha = if (isCurrentMonth) 1f else 0.38f
|
||||||
|
Text(
|
||||||
|
text = shiftLabel,
|
||||||
|
color = shiftFgColor.copy(alpha = shiftAlpha),
|
||||||
|
fontSize = 9.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
lineHeight = 9.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopStart)
|
||||||
|
.zIndex(1f)
|
||||||
|
.padding(top = 1.dp, start = 2.dp)
|
||||||
|
.background(shiftBgColor.copy(alpha = shiftAlpha), CircleShape)
|
||||||
|
.padding(horizontal = 2.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
if (holidayBadge != null) {
|
if (holidayBadge != null) {
|
||||||
Text(
|
Text(
|
||||||
text = holidayBadge,
|
text = holidayBadge,
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import kotlinx.datetime.DatePeriod
|
|||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.daysUntil
|
import kotlinx.datetime.daysUntil
|
||||||
import kotlinx.datetime.plus
|
import kotlinx.datetime.plus
|
||||||
|
import plus.rua.project.ShiftKind
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,6 +36,7 @@ fun WeekPager(
|
|||||||
today: LocalDate,
|
today: LocalDate,
|
||||||
onDateClick: (LocalDate) -> Unit,
|
onDateClick: (LocalDate) -> Unit,
|
||||||
onWeekChanged: (LocalDate) -> Unit,
|
onWeekChanged: (LocalDate) -> Unit,
|
||||||
|
shiftKindAt: (LocalDate) -> ShiftKind?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val initialWeekMonday = remember { selectedDate.toWeekMonday() }
|
val initialWeekMonday = remember { selectedDate.toWeekMonday() }
|
||||||
@ -82,6 +84,7 @@ fun WeekPager(
|
|||||||
&& date.year == selectedDate.year,
|
&& date.year == selectedDate.year,
|
||||||
isSelected = date == selectedDate,
|
isSelected = date == selectedDate,
|
||||||
isToday = date == today,
|
isToday = date == today,
|
||||||
|
shiftKind = shiftKindAt(date),
|
||||||
onClick = { onDateClick(date) },
|
onClick = { onDateClick(date) },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user