diff --git a/apps/miniprogram/src/components/StepIndicator/index.scss b/apps/miniprogram/src/components/StepIndicator/index.scss new file mode 100644 index 0000000..acd9c70 --- /dev/null +++ b/apps/miniprogram/src/components/StepIndicator/index.scss @@ -0,0 +1,71 @@ +@import '../../styles/variables.scss'; + +.step-indicator { + display: flex; + align-items: flex-start; + justify-content: center; + padding: 24px 32px; + background: $card; +} + +.step-item { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + position: relative; +} + +.step-line { + position: absolute; + top: 24px; + left: -50%; + right: 50%; + height: 4px; + background: $bd-l; + transition: background 0.3s ease; + + &.step-line-done { + background: $acc; + } +} + +.step-dot { + width: 48px; + height: 48px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + background: $bd-l; + color: $tx3; + font-size: 24px; + transition: all 0.3s ease; + z-index: 1; + + &.step-current { + background: $pri; + color: white; + } + + &.step-done { + background: $acc; + color: white; + } +} + +.step-label { + font-size: 22px; + color: $tx3; + margin-top: 8px; + text-align: center; + + &.step-current { + color: $pri; + font-weight: bold; + } + + &.step-done { + color: $acc; + } +} diff --git a/apps/miniprogram/src/components/StepIndicator/index.tsx b/apps/miniprogram/src/components/StepIndicator/index.tsx new file mode 100644 index 0000000..a4bd659 --- /dev/null +++ b/apps/miniprogram/src/components/StepIndicator/index.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { View, Text } from '@tarojs/components'; +import './index.scss'; + +interface Step { + label: string; +} + +interface StepIndicatorProps { + steps: Step[]; + current: number; + onChange?: (index: number) => void; +} + +export default function StepIndicator({ steps, current, onChange }: StepIndicatorProps) { + return ( + + {steps.map((step, idx) => { + const isCurrent = idx === current; + const isDone = idx < current; + const isClickable = isDone && !!onChange; + + return ( + + {idx > 0 && ( + + )} + onChange(idx) : undefined} + > + {isDone ? : {idx + 1}} + + + {step.label} + + + ); + })} + + ); +} diff --git a/apps/miniprogram/src/components/WeekCalendar/index.scss b/apps/miniprogram/src/components/WeekCalendar/index.scss new file mode 100644 index 0000000..5eeeff0 --- /dev/null +++ b/apps/miniprogram/src/components/WeekCalendar/index.scss @@ -0,0 +1,84 @@ +@import '../../styles/variables.scss'; + +.week-calendar { + background: $card; + border-radius: $r; + padding: 16px; +} + +.week-nav { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.week-arrow { + font-size: 28px; + color: $pri; + padding: 0 16px; +} + +.week-label { + font-size: 24px; + color: $tx; + font-weight: bold; +} + +.week-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 8px; + text-align: center; +} + +.week-cell { + padding: 8px 4px; + border-radius: $r-sm; + position: relative; +} + +.cell-weekday { + font-size: 20px; + color: $tx3; + display: block; +} + +.cell-date { + font-size: 26px; + color: $tx; + display: block; + margin-top: 4px; +} + +.cell-today { + color: $pri; + font-weight: bold; +} + +.cell-dot { + position: absolute; + bottom: 2px; + left: 50%; + transform: translateX(-50%); + width: 8px; + height: 8px; + background: $acc; + border-radius: 50%; +} + +.cell-selected { + background: $pri; + border-radius: $r-sm; + + .cell-date { color: white; } + .cell-dot { background: white; } +} + +.cell-empty .cell-date { + color: $bd; +} + +.cell-past { + opacity: 0.4; +} diff --git a/apps/miniprogram/src/components/WeekCalendar/index.tsx b/apps/miniprogram/src/components/WeekCalendar/index.tsx new file mode 100644 index 0000000..f777419 --- /dev/null +++ b/apps/miniprogram/src/components/WeekCalendar/index.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import { View, Text } from '@tarojs/components'; +import './index.scss'; + +interface WeekCalendarProps { + scheduledDates: Set; + selectedDate: string; + onSelectDate: (date: string) => void; +} + +function getWeekDates(offset: number): string[] { + const now = new Date(); + const monday = new Date(now); + monday.setDate(now.getDate() - now.getDay() + 1 + offset * 7); + const dates: string[] = []; + for (let i = 0; i < 7; i++) { + const d = new Date(monday); + d.setDate(monday.getDate() + i); + dates.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`); + } + return dates; +} + +const WEEKDAYS = ['一', '二', '三', '四', '五', '六', '日']; + +export default function WeekCalendar({ scheduledDates, selectedDate, onSelectDate }: WeekCalendarProps) { + const [weekOffset, setWeekOffset] = useState(0); + const dates = getWeekDates(weekOffset); + const today = (() => { + const d = new Date(); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; + })(); + + return ( + + + setWeekOffset(weekOffset - 1)}>◀ + {dates[0].slice(5)} ~ {dates[6].slice(5)} + setWeekOffset(weekOffset + 1)}>▶ + + + {WEEKDAYS.map((day, idx) => { + const dateStr = dates[idx]; + const isScheduled = scheduledDates.has(dateStr); + const isSelected = dateStr === selectedDate; + const isToday = dateStr === today; + const isPast = dateStr < today; + return ( + onSelectDate(dateStr) : undefined} + > + {day} + {parseInt(dateStr.slice(8))} + {isScheduled && } + + ); + })} + + + ); +}