| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644 |
- <template>
- <view class="custom-date-picker">
- <!-- 日期筛选触发器 -->
- <slot
- name="date-trigger"
- :text="displayDateText || placeholder"
- :displayText="displayDateText"
- :placeholder="placeholder"
- :hasValue="hasDateValue"
- :open="openDatePopup"
- :clear="clearDate"
- :value="currentValue"
- >
- <view class="picker-trigger date-trigger" @click="openDatePopup" :class="{ 'active': hasDateValue }">
- <text class="text">{{ displayDateText || placeholder }}</text>
- <view v-if="hasDateValue" class="clear-btn" @click.stop="clearDate">
- <text class="clear-icon">×</text>
- </view>
- <text v-else class="arrow">▼</text>
- </view>
- </slot>
- <!-- 时间筛选触发器 -->
- <!-- <slot
- name="time-trigger"
- :text="displayTimeText || timePlaceholder"
- :displayText="displayTimeText"
- :placeholder="timePlaceholder"
- :hasValue="hasTimeValue"
- :open="openTimePopup"
- :clear="clearTime"
- :value="currentValue"
- >
- <view class="picker-trigger time-trigger" @click="openTimePopup" :class="{ 'active': hasTimeValue }">
- <text class="text">{{ displayTimeText || timePlaceholder }}</text>
- <view v-if="hasTimeValue" class="clear-btn" @click.stop="clearTime">
- <text class="clear-icon">×</text>
- </view>
- <text v-else class="arrow">▼</text>
- </view>
- </slot> -->
- <!-- 弹出层 -->
- <view class="picker-popup" :class="{ 'show': showPopup }" @click.stop>
- <view class="mask" @click="closePopup"></view>
- <view class="popup-content">
- <view class="popup-header">
- <view class="btn cancel" @click="closePopup">取消</view>
- <view class="title">{{ popupTitle }}</view>
- <view class="btn confirm" @click="confirm">确定</view>
- </view>
-
- <!-- 模式切换 Tabs (仅在日期模式下显示) -->
- <view class="mode-tabs" v-if="popupType === 'date'">
- <view
- class="tab-item"
- :class="{ active: currentMode === 'year' }"
- @click="switchMode('year')"
- v-if="isModeEnabled('year')"
- >年份</view>
- <view
- class="tab-item"
- :class="{ active: currentMode === 'month' }"
- @click="switchMode('month')"
- v-if="isModeEnabled('month')"
- >月份</view>
- <view
- class="tab-item"
- :class="{ active: currentMode === 'range' }"
- @click="switchMode('range')"
- v-if="isModeEnabled('range')"
- >日期</view>
- <view
- class="tab-item"
- :class="{ active: currentMode === 'week' }"
- @click="switchMode('week')"
- v-if="isModeEnabled('week')"
- >周</view>
- </view>
- <view class="picker-body">
- <view class="range-tip" v-if="popupType === 'date' && currentMode === 'range'">
- 点击一次选择单日;再次选择可形成范围
- </view>
- <!-- 年份/月份选择器头部 (用于日历视图切换年月) -->
- <view class="calendar-header" v-if="popupType === 'date' && currentMode === 'range'">
- <view class="arrow-btn" @click="changeMonth(-1)">‹</view>
- <view class="current-date-text">
- {{ displayYear }}年 {{ displayMonth }}月
- </view>
- <view class="arrow-btn" @click="changeMonth(1)">›</view>
- </view>
- <!-- 星期头部 -->
- <view class="week-header" v-if="popupType === 'date' && currentMode === 'range'">
- <text v-for="day in weeks" :key="day" class="week-item">{{ day }}</text>
- </view>
- <!-- 日历网格 (日期/范围模式) -->
- <scroll-view scroll-y class="calendar-body" v-if="popupType === 'date' && currentMode === 'range'">
- <view class="days-grid">
- <view
- v-for="(day, index) in calendarDays"
- :key="index"
- class="day-item"
- :class="{
- 'empty': !day.day,
- 'today': isToday(day),
- 'selected': isSelected(day),
- 'in-range': isInRange(day),
- 'range-start': isRangeStart(day),
- 'range-end': isRangeEnd(day),
- 'disabled': day.disabled
- }"
- @click="onDayClick(day)"
- >
- <text>{{ day.day }}</text>
- </view>
- </view>
- </scroll-view>
- <!-- 月份选择列表 -->
- <scroll-view scroll-y class="list-body month-list" v-if="popupType === 'date' && currentMode === 'month'">
- <!-- <view class="range-tip">点击一次选择单月;再次选择可形成范围</view> -->
- <view class="year-selector">
- <view class="arrow-btn" @click="changeYear(-1)">‹</view>
- <view class="current-year">{{ tempYear }}年</view>
- <view class="arrow-btn" @click="changeYear(1)">›</view>
- </view>
- <view class="grid-container">
- <view
- v-for="m in 12"
- :key="m"
- class="grid-item"
- :class="{
- selected: isMonthSelected(m),
- 'range-start': isMonthRangeStart(m),
- 'range-end': isMonthRangeEnd(m),
- 'in-range': isMonthInRange(m)
- }"
- @click="onMonthSelect(m)"
- >
- {{ m }}月
- </view>
- </view>
- </scroll-view>
- <!-- 年份选择列表 -->
- <scroll-view scroll-y class="list-body year-list" v-if="popupType === 'date' && currentMode === 'year'">
- <!-- <view class="year-range-tip">点击一次选择单年;再次选择可形成区间范围</view> -->
- <view class="grid-container">
- <view
- v-for="y in yearList"
- :key="y"
- class="grid-item"
- :class="{
- selected: isYearSelected(y),
- 'range-start': isYearRangeStart(y),
- 'range-end': isYearRangeEnd(y),
- 'in-range': isYearInRange(y)
- }"
- @click="onYearSelect(y)"
- >
- {{ y }}年
- </view>
- </view>
- </scroll-view>
- <!-- 周区间选择 -->
- <view class="week-range-section" v-if="popupType === 'date' && currentMode === 'week'">
- <view class="range-tip">点击一次选择起始周;再次选择可形成区间范围</view>
- <view class="year-selector">
- <view class="arrow-btn" @click="changeWeekYear(-1)">‹</view>
- <view class="current-year">{{ tempWeekYear }}年</view>
- <view class="arrow-btn" @click="changeWeekYear(1)">›</view>
- </view>
- <scroll-view scroll-y class="list-body week-list">
- <view class="grid-container week-grid">
- <view
- v-for="w in weekList"
- :key="w"
- class="grid-item"
- :class="{
- selected: isWeekSelected(w),
- 'range-start': isWeekStart(w),
- 'range-end': isWeekEnd(w),
- 'in-range': isWeekInRange(w)
- }"
- @click="onWeekSelect(w)"
- >
- 第{{ w }}周
- </view>
- </view>
- </scroll-view>
- </view>
- <!-- 时间筛选区域 (仅在时间模式下显示) -->
- <view class="time-filter-section" v-if="popupType === 'time'">
- <!-- <view class="section-divider"></view> -->
- <view class="time-header">
- <!-- <view class="title">时间筛选</view> -->
- <view class="quick-btns">
- <text class="btn" :class="{ active: isQuickTimeActive('all') }" @click="selectQuickTime('all')">全天</text>
- <text class="btn" :class="{ active: isQuickTimeActive('am') }" @click="selectQuickTime('am')">上午(09:00-12:00)</text>
- <text class="btn" :class="{ active: isQuickTimeActive('pm') }" @click="selectQuickTime('pm')">下午(14:00-18:00)</text>
- </view>
- </view>
-
- <!-- 自定义时间范围 (固定Header) -->
- <view class="time-selector-container">
- <view class="time-table-header">
- <view class="th">开始时间</view>
- <view class="th-separator"></view>
- <view class="th">结束时间</view>
- </view>
- <view class="time-table-body">
- <scroll-view scroll-y class="time-column">
- <view
- v-for="t in timeList"
- :key="'start-' + t"
- class="time-item"
- :class="{ selected: tempTimeValue.startTime === t }"
- @click="onTimeSelect('start', t)"
- >
- {{ t }}
- </view>
- </scroll-view>
-
- <view class="body-separator">至</view>
- <scroll-view scroll-y class="time-column">
- <view
- v-for="t in timeList"
- :key="'end-' + t"
- class="time-item"
- :class="{ selected: tempTimeValue.endTime === t }"
- @click="onTimeSelect('end', t)"
- >
- {{ t }}
- </view>
- </scroll-view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script>
- export default {
- name: 'dy-date-time-picker',
- props: {
- // 选中的值
- // 日期模式(单选/范围合一):
- // { startDate: 'YYYY-MM-DD', endDate: 'YYYY-MM-DD' }(单选时返回 { date: 'YYYY-MM-DD' })
- // 月份模式(单选/范围合一):
- // { startMonth: 'YYYY-MM', endMonth: 'YYYY-MM' }(单选时返回 { month: 'YYYY-MM' })
- // 年份模式(单选/范围合一):
- // { startYear: 'YYYY', endYear: 'YYYY' }(单选时返回 { year: 'YYYY' })
- // 周区间模式:
- // { startWeek: 'YYYY-WW', endWeek: 'YYYY-WW', weekStartDate: 'YYYY-MM-DD', weekEndDate: 'YYYY-MM-DD' }
- // (单选时返回 { week: 'YYYY-WW', weekStartDate: 'YYYY-MM-DD', weekEndDate: 'YYYY-MM-DD' })
- // time: { startTime, endTime }
- value: {
- type: [String, Object],
- default: ''
- },
- modelValue: {
- type: [String, Object],
- default: undefined
- },
- minYear: {
- type: Number,
- default: 2004
- },
- maxYear: {
- type: Number,
- default: () => new Date().getFullYear()
- },
- placeholder: {
- type: String,
- default: '选择日期'
- },
- timePlaceholder: {
- type: String,
- default: '筛选时间'
- },
- typeEnabled: {
- type: Object,
- default: () => ({
- date: true,
- month: true,
- year: true,
- week: true
- })
- }
- },
- data() {
- return {
- showPopup: false,
- popupType: 'date', // 'date' 或 'time',决定弹窗显示内容
- currentMode: 'range', // range, month, year, week
- weeks: ['日', '一', '二', '三', '四', '五', '六'],
-
- // 内部显示的年月(用于日历翻页)
- displayYear: new Date().getFullYear(),
- displayMonth: new Date().getMonth() + 1,
-
- // 临时选中的值(未确认前)
- tempValue: null,
-
- // 时间筛选临时值
- tempTimeValue: {
- startTime: '',
- endTime: ''
- },
-
- // 辅助变量
- tempYear: new Date().getFullYear(), // 月份模式下的年份
- tempWeekYear: new Date().getFullYear()
- }
- },
- computed: {
- popupTitle() {
- return this.popupType === 'date' ? '选择日期' : '选择时间'
- },
- enabledTypeMap() {
- const defaults = { date: true, month: true, year: true, week: true }
- const config = this.typeEnabled || {}
- return { ...defaults, ...config }
- },
- currentValue() {
- return this.modelValue !== undefined ? this.modelValue : this.value
- },
- hasDateValue() {
- const val = this.currentValue
- if (!val) return false
- if (typeof val === 'object') {
- if (val.date) return true
- if (val.startDate || val.endDate) return true
- if (val.month) return true
- if (val.startMonth || val.endMonth) return true
- if (val.year) return true
- if (val.startYear || val.endYear) return true
- if (val.week || val.startWeek || val.endWeek) return true
- } else if (val) {
- return true // 字符串情况
- }
- return false
- },
- hasTimeValue() {
- const val = this.currentValue
- if (!val || typeof val !== 'object') return false
- const time = val.time || {}
- return !!(time.startTime || time.endTime)
- },
- displayDateText() {
- if (!this.hasDateValue) return ''
- const val = this.currentValue
- if (typeof val === 'object') {
- if (val.date) return val.date
- if (val.startDate || val.endDate) {
- const start = val.startDate
- const end = val.endDate
- if (start && end) return start === end ? start : `${start} 至 ${end}`
- if (start) return `${start} (未完成)`
- }
- if (val.startMonth || val.endMonth) {
- const start = val.startMonth
- const end = val.endMonth
- if (start && end) return start === end ? start : `${start} 至 ${end}`
- if (start) return `${start} (未完成)`
- }
- if (val.month) return val.month
- if (val.startYear || val.endYear) {
- const start = val.startYear
- const end = val.endYear
- if (start && end) return start === end ? `${start}年` : `${start}年 至 ${end}年`
- if (start) return `${start}年 (未完成)`
- }
- if (val.year) return val.year + '年'
- if (val.week || val.startWeek || val.endWeek) {
- const start = val.startWeek
- const end = val.endWeek
- const week = val.week
- if (week) {
- const parsed = this.parseWeekValue(week)
- return parsed ? `${parsed.year}年第${parsed.week}周` : week
- }
- if (start && end) {
- const startParsed = this.parseWeekValue(start)
- const endParsed = this.parseWeekValue(end)
- if (startParsed && endParsed) {
- return `${startParsed.year}年第${startParsed.week}周 至 第${endParsed.week}周`
- }
- return `${start} 至 ${end}`
- }
- if (start) {
- const startParsed = this.parseWeekValue(start)
- if (startParsed) return `${startParsed.year}年第${startParsed.week}周 (未完成)`
- return `${start} (未完成)`
- }
- }
- } else {
- const valStr = String(val)
- if (valStr.length === 4) return `${valStr}年`
- return valStr
- }
- return ''
- },
- displayTimeText() {
- if (!this.hasTimeValue) return ''
- const val = this.currentValue
- const time = val.time || {}
- if (time.startTime && time.endTime) return `${time.startTime}-${time.endTime}`
- if (time.startTime || time.endTime) return '请选择结束时间'
- return ''
- },
- calendarDays() {
- const year = this.displayYear
- const month = this.displayMonth
- const firstDayOfMonth = new Date(year, month - 1, 1).getDay()
- const daysInMonth = new Date(year, month, 0).getDate()
-
- const days = []
- // 填充上个月的空白
- for (let i = 0; i < firstDayOfMonth; i++) {
- days.push({ day: '', disabled: true })
- }
-
- // 填充当月天数
- for (let i = 1; i <= daysInMonth; i++) {
- const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(i).padStart(2, '0')}`
- days.push({
- day: i,
- fullDate: dateStr,
- disabled: false
- })
- }
- // 填充下个月的空白,补齐整行
- const remaining = days.length % 7
- if (remaining !== 0) {
- const fillCount = 7 - remaining
- for (let i = 0; i < fillCount; i++) {
- days.push({ day: '', disabled: true })
- }
- }
-
- return days
- },
- yearList() {
- const startYear = this.minYear || 2025
- const currentYear = new Date().getFullYear()
- const endYear = (this.maxYear || currentYear)
- const safeEndYear = endYear < startYear ? startYear : endYear
-
- const list = []
- for (let i = safeEndYear; i >= startYear; i--) {
- list.push(i)
- }
- return list
- },
- timeList() {
- const times = []
- for (let i = 0; i <= 23; i++) {
- const hour = String(i).padStart(2, '0')
- times.push(`${hour}:00`)
- times.push(`${hour}:30`) // 增加半小时刻度
- }
- times.push('23:59')
- return times
- },
- weekList() {
- const year = this.tempWeekYear
- const totalWeeks = this.getWeeksInYear(year)
- const weeks = []
- for (let i = 1; i <= totalWeeks; i++) {
- weeks.push(i)
- }
- return weeks
- }
- },
- watch: {
- // 监听 value / modelValue 变化,确保外部清空时内部也响应
- value: {
- handler(val) {
- if (!val) {
- this.tempValue = null
- }
- },
- deep: true
- },
- modelValue: {
- handler(val) {
- if (!val) {
- this.tempValue = null
- }
- },
- deep: true
- }
- },
- methods: {
- emitValue(result) {
- if (result && typeof result === 'object') {
- Object.keys(result).forEach((key) => {
- if (result[key] === undefined) delete result[key]
- })
- }
- this.$emit('input', result)
- this.$emit('update:modelValue', result)
- this.$emit('change', result)
- },
- openDatePopup() {
- this.popupType = 'date'
- this.showPopup = true
- this.initTempState()
- },
- openTimePopup() {
- this.popupType = 'time'
- this.showPopup = true
- this.initTempState()
- },
- openPopup(type = 'date') {
- if (type === 'time') {
- this.openTimePopup()
- } else {
- this.openDatePopup()
- }
- },
- open(type = 'date') {
- this.openPopup(type)
- },
- close() {
- this.closePopup()
- },
- closePopup() {
- this.showPopup = false
- },
- normalizeDisplayDate(year, month) {
- const minYear = this.minYear || 2025
- const maxYear = this.maxYear || new Date().getFullYear()
- let safeYear = year
- if (safeYear < minYear) safeYear = minYear
- if (safeYear > maxYear) safeYear = maxYear
- let safeMonth = month
- if (!safeMonth || safeMonth < 1 || safeMonth > 12) safeMonth = 1
- return { year: safeYear, month: safeMonth }
- },
- initTempState() {
- const currentValue = this.currentValue
- // 根据当前的 value 初始化 tempValue 和 displayYear/Month
- if (!currentValue) {
- this.tempValue = null
- this.currentMode = this.getDefaultMode() // 默认模式
- const now = new Date()
- const safeDate = this.normalizeDisplayDate(now.getFullYear(), now.getMonth() + 1)
- this.displayYear = safeDate.year
- this.displayMonth = safeDate.month
- this.tempWeekYear = safeDate.year
- return
- }
- // 初始化时间状态 (如果存在)
- if (typeof currentValue === 'object' && currentValue.time) {
- this.tempTimeValue = { ...currentValue.time }
- } else {
- this.tempTimeValue = { startTime: '00:00', endTime: '23:59' }
- }
- if (typeof currentValue === 'object') {
- if (currentValue.date) {
- this.currentMode = 'range'
- this.tempValue = { startDate: currentValue.date, endDate: '' }
- const [y, m] = currentValue.date.split('-')
- const safeDate = this.normalizeDisplayDate(parseInt(y), parseInt(m))
- this.displayYear = safeDate.year
- this.displayMonth = safeDate.month
- } else if (currentValue.startMonth || currentValue.endMonth || currentValue.month) {
- this.currentMode = 'month'
- const monthValue = currentValue.month || currentValue.startMonth || ''
- this.tempValue = {
- startMonth: monthValue,
- endMonth: currentValue.endMonth || ''
- }
- const [y] = (monthValue || '').split('-')
- if (y) {
- this.tempYear = this.normalizeDisplayDate(parseInt(y), 1).year
- }
- } else if (currentValue.startYear || currentValue.endYear) {
- this.currentMode = 'year'
- this.tempValue = {
- startYear: currentValue.startYear || '',
- endYear: currentValue.endYear || ''
- }
- this.tempYear = this.normalizeDisplayDate(parseInt(currentValue.startYear || currentValue.endYear || this.tempYear), 1).year
- } else if (currentValue.year) {
- this.currentMode = 'year'
- this.tempValue = currentValue.year
- this.tempYear = this.normalizeDisplayDate(parseInt(currentValue.year), 1).year
- } else if (currentValue.week || currentValue.startWeek || currentValue.endWeek) {
- this.currentMode = 'week'
- const weekValue = currentValue.week || currentValue.startWeek || ''
- const parsed = this.parseWeekValue(weekValue)
- this.tempWeekYear = parsed ? parsed.year : this.tempWeekYear
- const startParsed = this.parseWeekValue(currentValue.startWeek || currentValue.week)
- const endParsed = this.parseWeekValue(currentValue.endWeek || '')
- this.tempValue = {
- startWeek: startParsed ? startParsed.week : '',
- endWeek: endParsed ? endParsed.week : ''
- }
- } else {
- // 默认 Range
- this.currentMode = 'range'
- // 清理掉 time 属性,只保留 startDate/endDate
- const { time, ...rest } = currentValue
- this.tempValue = rest
- if (currentValue.startDate) {
- const [y, m] = currentValue.startDate.split('-')
- const safeDate = this.normalizeDisplayDate(parseInt(y), parseInt(m))
- this.displayYear = safeDate.year
- this.displayMonth = safeDate.month
- }
- }
- } else {
- // 字符串兼容 (虽然我们现在主要用对象)
- const val = currentValue.toString()
- if (val.length === 4) {
- this.currentMode = 'year'
- this.tempValue = val
- this.tempYear = this.normalizeDisplayDate(parseInt(val), 1).year
- } else if (val.includes('-') && val.length <= 7) {
- this.currentMode = 'month'
- this.tempValue = { startMonth: val, endMonth: '' }
- const [y] = val.split('-')
- this.tempYear = this.normalizeDisplayDate(parseInt(y), 1).year
- } else {
- this.currentMode = 'range'
- this.tempValue = { startDate: val, endDate: '' }
- if (val) {
- const [y, m] = val.split('-')
- const safeDate = this.normalizeDisplayDate(parseInt(y), parseInt(m))
- this.displayYear = safeDate.year
- this.displayMonth = safeDate.month
- }
- }
- }
- if (!this.isModeEnabled(this.currentMode)) {
- this.currentMode = this.getDefaultMode()
- this.tempValue = null
- }
- },
- switchMode(mode) {
- if (!this.isModeEnabled(mode)) return
- if (this.currentMode === mode) return
- this.currentMode = mode
- this.tempValue = null
-
- // 切换模式时,不重置时间选择,保持通用性
-
- // 重置显示时间
- const now = new Date()
- const safeDate = this.normalizeDisplayDate(now.getFullYear(), now.getMonth() + 1)
- this.displayYear = safeDate.year
- this.displayMonth = safeDate.month
- this.tempYear = safeDate.year
- if (mode === 'week') {
- this.tempWeekYear = safeDate.year
- }
- },
- getDefaultMode() {
- const order = ['year', 'month', 'range', 'week']
- return order.find((mode) => this.isModeEnabled(mode)) || 'range'
- },
- isModeEnabled(mode) {
- const map = this.enabledTypeMap
- if (mode === 'range') return map.date !== false
- return map[mode] !== false
- },
- changeMonth(delta) {
- let newMonth = this.displayMonth + delta
- let newYear = this.displayYear
-
- if (newMonth > 12) {
- newMonth = 1
- newYear++
- } else if (newMonth < 1) {
- newMonth = 12
- newYear--
- }
-
- // 限制年份范围
- const minYear = this.minYear || 2025
- const maxYear = this.maxYear || new Date().getFullYear()
-
- // 检查下限
- if (newYear < minYear) return
-
- // 检查上限
- if (newYear > maxYear) return
-
- this.displayMonth = newMonth
- this.displayYear = newYear
- },
- changeYear(delta) {
- const newYear = this.tempYear + delta
- const minYear = this.minYear || 2025
- const maxYear = this.maxYear || new Date().getFullYear()
-
- if (newYear >= minYear && newYear <= maxYear) {
- this.tempYear = newYear
- }
- },
- changeWeekYear(delta) {
- const newYear = this.tempWeekYear + delta
- const minYear = this.minYear || 2025
- const maxYear = this.maxYear || new Date().getFullYear()
- if (newYear < minYear || newYear > maxYear) return
- this.tempWeekYear = newYear
- if (this.tempValue && (this.tempValue.startWeek || this.tempValue.endWeek)) {
- this.tempValue = null
- }
- },
- isToday(day) {
- if (!day.fullDate) return false
- const now = new Date()
- const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
- return day.fullDate === todayStr
- },
- isSelected(day) {
- if (!day.fullDate) return false
- if (this.currentMode !== 'range' || !this.tempValue) return false
- return this.tempValue.startDate === day.fullDate && !this.tempValue.endDate
- },
- isRangeStart(day) {
- if (this.currentMode !== 'range' || !this.tempValue || !day.fullDate) return false
- return this.tempValue.startDate === day.fullDate
- },
- isRangeEnd(day) {
- if (this.currentMode !== 'range' || !this.tempValue || !day.fullDate) return false
- return this.tempValue.endDate === day.fullDate
- },
- isInRange(day) {
- if (this.currentMode !== 'range' || !this.tempValue || !day.fullDate) return false
- const { startDate, endDate } = this.tempValue
- if (!startDate || !endDate) return false
- return day.fullDate > startDate && day.fullDate < endDate
- },
- onDayClick(day) {
- if (!day.fullDate) return
-
- if (this.currentMode === 'range') {
- if (!this.tempValue || (this.tempValue.startDate && this.tempValue.endDate)) {
- // 开始新选择
- this.tempValue = { startDate: day.fullDate, endDate: '' }
- } else if (!this.tempValue.startDate) {
- this.tempValue = { startDate: day.fullDate, endDate: '' }
- } else {
- this.tempValue = { startDate: day.fullDate, endDate: '' }
- }
- }
- },
- formatDate(date, fmt) {
- const o = {
- "M+": date.getMonth() + 1,
- "d+": date.getDate(),
- "H+": date.getHours(),
- "m+": date.getMinutes(),
- "s+": date.getSeconds()
- }
- if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
- for (const k in o)
- if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
- return fmt
- },
- toMonthIndex(monthStr) {
- if (!monthStr) return 0
- const [y, m] = monthStr.split('-').map((v) => parseInt(v, 10))
- return y * 12 + m
- },
- isMonthSelected(m) {
- if (this.currentMode !== 'month' || !this.tempValue) return false
- const val = `${this.tempYear}-${String(m).padStart(2, '0')}`
- if (typeof this.tempValue === 'object') {
- const start = this.tempValue.startMonth
- const end = this.tempValue.endMonth
- if (start && end) {
- if (start === end) return val === start
- return false
- }
- if (start) return val === start
- return false
- }
- return this.tempValue === val
- },
- isMonthRangeStart(m) {
- if (this.currentMode !== 'month' || !this.tempValue || typeof this.tempValue !== 'object') return false
- const { startMonth, endMonth } = this.tempValue
- if (!startMonth || !endMonth) return false
- const val = `${this.tempYear}-${String(m).padStart(2, '0')}`
- return val === startMonth
- },
- isMonthRangeEnd(m) {
- if (this.currentMode !== 'month' || !this.tempValue || typeof this.tempValue !== 'object') return false
- const { startMonth, endMonth } = this.tempValue
- if (!startMonth || !endMonth) return false
- const val = `${this.tempYear}-${String(m).padStart(2, '0')}`
- return val === endMonth
- },
- isMonthInRange(m) {
- if (this.currentMode !== 'month' || !this.tempValue || typeof this.tempValue !== 'object') return false
- const { startMonth, endMonth } = this.tempValue
- if (!startMonth || !endMonth) return false
- const val = `${this.tempYear}-${String(m).padStart(2, '0')}`
- const idx = this.toMonthIndex(val)
- return idx > this.toMonthIndex(startMonth) && idx < this.toMonthIndex(endMonth)
- },
- onMonthSelect(m) {
- const val = `${this.tempYear}-${String(m).padStart(2, '0')}`
- if (!this.tempValue || (typeof this.tempValue === 'object' && this.tempValue.startMonth && this.tempValue.endMonth)) {
- this.tempValue = { startMonth: val, endMonth: '' }
- return
- }
- if (typeof this.tempValue === 'object') {
- const start = this.tempValue.startMonth
- if (!start) {
- this.tempValue = { startMonth: val, endMonth: '' }
- return
- }
- // const startIdx = this.toMonthIndex(start)
- // const valIdx = this.toMonthIndex(val)
- this.tempValue = { startMonth: val, endMonth: '' }
- return
- }
- this.tempValue = val
- },
- isYearSelected(y) {
- if (this.currentMode !== 'year' || !this.tempValue) return false
- if (typeof this.tempValue === 'object') {
- const start = parseInt(this.tempValue.startYear || 0)
- const end = parseInt(this.tempValue.endYear || 0)
- if (start && end) {
- if (start === end) return y === start
- return false
- }
- if (start) return y === start
- return false
- }
- return parseInt(this.tempValue) === y
- },
- isYearRangeStart(y) {
- if (this.currentMode !== 'year' || !this.tempValue || typeof this.tempValue !== 'object') return false
- const start = parseInt(this.tempValue.startYear || 0)
- const end = parseInt(this.tempValue.endYear || 0)
- if (!start || !end) return false
- return y === start
- },
- isYearRangeEnd(y) {
- if (this.currentMode !== 'year' || !this.tempValue || typeof this.tempValue !== 'object') return false
- const start = parseInt(this.tempValue.startYear || 0)
- const end = parseInt(this.tempValue.endYear || 0)
- if (!start || !end) return false
- return y === end
- },
- isYearInRange(y) {
- if (this.currentMode !== 'year' || !this.tempValue || typeof this.tempValue !== 'object') return false
- const start = parseInt(this.tempValue.startYear || 0)
- const end = parseInt(this.tempValue.endYear || 0)
- if (!start || !end) return false
- return y > start && y < end
- },
- onYearSelect(y) {
- if (!this.tempValue || (typeof this.tempValue === 'object' && this.tempValue.startYear && this.tempValue.endYear)) {
- this.tempValue = { startYear: y.toString(), endYear: '' }
- return
- }
- if (typeof this.tempValue === 'object') {
- const start = parseInt(this.tempValue.startYear || 0)
- if (!start) {
- this.tempValue = { startYear: y.toString(), endYear: '' }
- return
- }
- if (y < start) {
- this.tempValue = { startYear: y.toString(), endYear: start.toString() }
- } else if (y === start) {
- this.tempValue = { startYear: y.toString(), endYear: '' }
- } else {
- this.tempValue = { startYear: start.toString(), endYear: y.toString() }
- }
- return
- }
- this.tempValue = y.toString()
- },
- formatWeekValue(year, week) {
- return `${year}-W${String(week).padStart(2, '0')}`
- },
- parseWeekValue(value) {
- if (!value) return null
- const match = String(value).match(/^(\d{4})-W(\d{1,2})$/)
- if (!match) return null
- return { year: parseInt(match[1], 10), week: parseInt(match[2], 10) }
- },
- getISOWeekNumber(date) {
- const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
- const dayNum = d.getUTCDay() || 7
- d.setUTCDate(d.getUTCDate() + 4 - dayNum)
- const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
- return Math.ceil((((d - yearStart) / 86400000) + 1) / 7)
- },
- getWeeksInYear(year) {
- return this.getISOWeekNumber(new Date(year, 11, 28))
- },
- getWeekStartEnd(year, week) {
- const simple = new Date(Date.UTC(year, 0, 1 + (week - 1) * 7))
- const dayOfWeek = simple.getUTCDay() || 7
- const start = new Date(simple)
- start.setUTCDate(simple.getUTCDate() - (dayOfWeek - 1))
- const end = new Date(start)
- end.setUTCDate(start.getUTCDate() + 6)
- return {
- start: this.formatDate(new Date(start), 'yyyy-MM-dd'),
- end: this.formatDate(new Date(end), 'yyyy-MM-dd')
- }
- },
- isWeekSelected(week) {
- if (this.currentMode !== 'week' || !this.tempValue || !this.tempValue.startWeek) return false
- const start = parseInt(this.tempValue.startWeek, 10)
- const end = parseInt(this.tempValue.endWeek || 0, 10)
- if (start && end) {
- if (start === end) return week === start
- return false
- }
- return week === start
- },
- isWeekStart(week) {
- if (this.currentMode !== 'week' || !this.tempValue || !this.tempValue.startWeek) return false
- if (!this.tempValue.endWeek) return false
- return week === parseInt(this.tempValue.startWeek, 10)
- },
- isWeekEnd(week) {
- if (this.currentMode !== 'week' || !this.tempValue || !this.tempValue.endWeek) return false
- return week === parseInt(this.tempValue.endWeek, 10)
- },
- isWeekInRange(week) {
- if (this.currentMode !== 'week' || !this.tempValue) return false
- const start = parseInt(this.tempValue.startWeek || 0, 10)
- const end = parseInt(this.tempValue.endWeek || 0, 10)
- if (!start || !end) return false
- return week > start && week < end
- },
- onWeekSelect(week) {
- if (!this.tempValue || (this.tempValue.startWeek && this.tempValue.endWeek)) {
- this.tempValue = { startWeek: week, endWeek: '' }
- return
- }
- if (!this.tempValue.startWeek) {
- this.tempValue = { startWeek: week, endWeek: '' }
- return
- }
- const start = parseInt(this.tempValue.startWeek, 10)
- if (week < start) {
- this.tempValue = { startWeek: week, endWeek: start }
- } else if (week === start) {
- this.tempValue = { startWeek: week, endWeek: '' }
- } else {
- this.tempValue = { startWeek: start, endWeek: week }
- }
- },
- confirm() {
- // 如果是时间选择
- if (this.popupType === 'time') {
- if (!this.tempTimeValue.startTime) {
- uni.showToast({ title: '请选择开始时间', icon: 'none' })
- return
- }
- if (!this.tempTimeValue.endTime) {
- uni.showToast({ title: '请选择结束时间', icon: 'none' })
- return
- }
- if (this.tempTimeValue.startTime >= this.tempTimeValue.endTime) {
- uni.showToast({ title: '开始时间不能大于或等于结束时间', icon: 'none' })
- return
- }
- // 更新时间,保留日期
- const currentValue = this.currentValue
- let result = currentValue && typeof currentValue === 'object' ? { ...currentValue } : {}
- // 如果原来的 value 不是对象(是字符串),需要转换结构
- if (currentValue && typeof currentValue !== 'object') {
- // 尝试推断当前模式,或者重置日期?
- // 简单起见,如果之前是纯字符串,我们这里需要把它转成 date 对象
- const valStr = String(currentValue)
- if (valStr.length === 10) result = { date: valStr } // YYYY-MM-DD
- else if (valStr.length === 7) result = { month: valStr } // YYYY-MM
- else if (valStr.length === 4) result = { year: valStr } // YYYY
- }
-
- result.time = this.tempTimeValue
- this.emitValue(result)
- this.closePopup()
- return
- }
- // 如果是日期选择
- let result = null
- // 验证日期
- if (this.currentMode === 'range') {
- if (!this.tempValue || !this.tempValue.startDate) {
- uni.showToast({ title: '请选择日期', icon: 'none' })
- return
- }
- // 保留原有的时间选择 (如果有)
- const currentValue = this.currentValue
- const oldTime = currentValue && currentValue.time ? currentValue.time : null
- if (this.tempValue.endDate) {
- result = { ...this.tempValue }
- } else {
- result = { date: this.tempValue.startDate }
- }
- if (oldTime) result.time = oldTime
- }
- else if (this.currentMode === 'month') {
- if (!this.tempValue) {
- uni.showToast({ title: '请选择月份', icon: 'none' })
- return
- }
- const currentValue = this.currentValue
- const oldTime = currentValue && currentValue.time ? currentValue.time : null
- if (typeof this.tempValue === 'object') {
- const startMonth = this.tempValue.startMonth
- const endMonth = this.tempValue.endMonth
- if (startMonth && endMonth) {
- result = { startMonth, endMonth }
- } else if (startMonth) {
- result = { month: startMonth }
- } else {
- uni.showToast({ title: '请选择月份', icon: 'none' })
- return
- }
- } else {
- result = { month: this.tempValue }
- }
- if (oldTime) result.time = oldTime
- }
- else if (this.currentMode === 'year') {
- if (!this.tempValue) {
- uni.showToast({ title: '请选择年份', icon: 'none' })
- return
- }
- const currentValue = this.currentValue
- const oldTime = currentValue && currentValue.time ? currentValue.time : null
- if (typeof this.tempValue === 'object') {
- const startYear = this.tempValue.startYear
- const endYear = this.tempValue.endYear
- if (startYear && endYear) {
- result = { startYear, endYear }
- } else if (startYear) {
- result = { year: startYear }
- } else {
- uni.showToast({ title: '请选择年份', icon: 'none' })
- return
- }
- } else {
- result = { year: this.tempValue }
- }
- if (oldTime) result.time = oldTime
- }
- else if (this.currentMode === 'week') {
- if (!this.tempValue || !this.tempValue.startWeek) {
- uni.showToast({ title: '请选择起始周', icon: 'none' })
- return
- }
- const currentValue = this.currentValue
- const oldTime = currentValue && currentValue.time ? currentValue.time : null
- const startWeek = this.formatWeekValue(this.tempWeekYear, this.tempValue.startWeek)
- const startRange = this.getWeekStartEnd(this.tempWeekYear, this.tempValue.startWeek)
- if (this.tempValue.endWeek) {
- const endWeek = this.formatWeekValue(this.tempWeekYear, this.tempValue.endWeek)
- const endRange = this.getWeekStartEnd(this.tempWeekYear, this.tempValue.endWeek)
- result = { startWeek, endWeek, weekStartDate: startRange.start, weekEndDate: endRange.end }
- } else {
- result = { week: startWeek, weekStartDate: startRange.start, weekEndDate: startRange.end }
- }
- if (oldTime) result.time = oldTime
- }
-
- if (result) {
- this.emitValue(result)
- this.closePopup()
- }
- },
- clearDate() {
- // 清除日期,保留时间
- const currentValue = this.currentValue
- let result = currentValue && typeof currentValue === 'object' ? { ...currentValue } : {}
- delete result.date
- delete result.startDate
- delete result.endDate
- delete result.month
- delete result.startMonth
- delete result.endMonth
- delete result.year
- delete result.startYear
- delete result.endYear
- delete result.week
- delete result.startWeek
- delete result.endWeek
- delete result.weekStartDate
- delete result.weekEndDate
- if (Object.keys(result).length === 0) {
- this.emitValue(null)
- return
- }
- this.emitValue(result)
- },
- clearTime() {
- // 清除时间,保留日期
- const currentValue = this.currentValue
- if (!currentValue || typeof currentValue !== 'object') {
- this.emitValue(null)
- return
- }
- const result = { ...currentValue }
- delete result.time
- if (Object.keys(result).length === 0) {
- this.emitValue(null)
- return
- }
- this.emitValue(result)
- },
- // 时间筛选相关方法
- isQuickTimeActive(type) {
- if (type === 'all') return this.tempTimeValue.startTime === '00:00' && this.tempTimeValue.endTime === '23:59'
- if (type === 'am') return this.tempTimeValue.startTime === '09:00' && this.tempTimeValue.endTime === '12:00'
- if (type === 'pm') return this.tempTimeValue.startTime === '14:00' && this.tempTimeValue.endTime === '18:00'
- return false
- },
- selectQuickTime(type) {
- if (type === 'all') {
- this.tempTimeValue = { startTime: '00:00', endTime: '23:59' }
- } else if (type === 'am') {
- this.tempTimeValue = { startTime: '09:00', endTime: '12:00' }
- } else if (type === 'pm') {
- this.tempTimeValue = { startTime: '14:00', endTime: '18:00' }
- }
- },
- onTimeSelect(type, time) {
- if (type === 'start') {
- this.tempTimeValue.startTime = time
- } else {
- this.tempTimeValue.endTime = time
- }
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .custom-date-picker {
- width: 100%;
-
- .picker-trigger {
- height: 70rpx;
- background-color: #fff;
- border: 2rpx solid #e0e6ed;
- border-radius: 8rpx;
- padding: 0 20rpx;
- display: flex;
- align-items: center;
- transition: all 0.3s;
-
- &.time-trigger {
- margin-top: 20rpx; // 上下两个触发器之间的间距
- }
-
- .text {
- flex: 1;
- font-size: 26rpx;
- color: #333;
- }
-
- .arrow {
- font-size: 20rpx;
- color: #c0c4cc;
- }
-
- .clear-btn {
- width: 36rpx;
- height: 36rpx;
- background: rgba(0,0,0,0.1);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
-
- .clear-icon {
- font-size: 24rpx;
- color: #fff;
- line-height: 1;
- }
- }
- }
-
- .picker-popup {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100vh;
- z-index: 99999;
- visibility: hidden;
- opacity: 0;
- transition: all 0.3s;
-
- &.show {
- visibility: visible;
- opacity: 1;
-
- .popup-content {
- transform: translateY(0);
- }
- }
-
- .mask {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0,0,0,0.5);
- }
-
- .popup-content {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- background-color: #fff;
- border-radius: 24rpx 24rpx 0 0;
- transform: translateY(100%);
- transition: all 0.3s;
- padding-bottom: constant(safe-area-inset-bottom);
- padding-bottom: env(safe-area-inset-bottom);
-
- .popup-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 30rpx;
- border-bottom: 1rpx solid #eee;
-
- .btn {
- font-size: 28rpx;
- padding: 10rpx 20rpx;
-
- &.cancel { color: #909399; }
- &.confirm { color: #1890ff; font-weight: 500; }
- }
-
- .title {
- font-size: 32rpx;
- font-weight: 600;
- color: #333;
- }
- }
-
- .mode-tabs {
- display: flex;
- padding: 20rpx 30rpx 0;
- gap: 20rpx;
-
- .tab-item {
- padding: 12rpx 24rpx;
- background-color: #f5f7fa;
- border-radius: 30rpx;
- font-size: 26rpx;
- color: #606266;
- transition: all 0.3s;
-
- &.active {
- background-color: #1890ff;
- color: #fff;
- box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
- }
- }
- }
-
- .picker-body {
- padding: 30rpx;
- min-height: 600rpx;
-
- .calendar-header {
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 30rpx;
-
- .current-date-text {
- font-size: 32rpx;
- font-weight: 600;
- margin: 0 40rpx;
- color: #333;
- }
-
- .arrow-btn {
- width: 60rpx;
- height: 60rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 36rpx;
- color: #606266;
- background-color: #f5f7fa;
- border-radius: 50%;
-
- &:active {
- background-color: #e6e6e6;
- }
- }
- }
-
- .week-header {
- display: flex;
- margin-bottom: 20rpx;
-
- .week-item {
- flex: 1;
- text-align: center;
- font-size: 24rpx;
- color: #909399;
- }
- }
- .range-tip {
- margin-bottom: 16rpx;
- font-size: 24rpx;
- color: #909399;
- text-align: center;
- }
-
- .calendar-body {
- height: 500rpx;
-
- .days-grid {
- display: flex;
- flex-wrap: wrap;
-
- .day-item {
- width: 14.28%;
- height: 80rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- color: #333;
- position: relative;
- margin-bottom: 10rpx;
-
- &.empty { pointer-events: none; }
- &.disabled { opacity: 0.3; pointer-events: none; }
-
- &.today {
- color: #1890ff;
- font-weight: 600;
-
- &::after {
- content: '';
- position: absolute;
- bottom: 10rpx;
- left: 50%;
- transform: translateX(-50%);
- width: 8rpx;
- height: 8rpx;
- background-color: #1890ff;
- border-radius: 50%;
- }
- }
-
- // 选中样式
- &.selected {
- background-color: #1890ff;
- color: #fff;
- border-radius: 8rpx;
- box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
-
- &::after { display: none; }
- }
-
- // 范围样式
- &.range-start {
- background-color: #1890ff;
- color: #fff;
- border-radius: 8rpx 0 0 8rpx;
- &::after { display: none; }
- }
-
- &.range-end {
- background-color: #1890ff;
- color: #fff;
- border-radius: 0 8rpx 8rpx 0;
- &::after { display: none; }
- }
-
- &.in-range {
- background-color: #e6f7ff;
- color: #1890ff;
- }
-
- // 如果开始结束是同一天
- &.range-start.range-end {
- border-radius: 8rpx;
- }
- }
- }
- }
-
- .list-body {
- height: 600rpx;
-
- .year-selector {
- display: flex;
- justify-content: center;
- align-items: center;
- margin-bottom: 30rpx;
-
- .current-year {
- font-size: 32rpx;
- font-weight: 600;
- margin: 0 30rpx;
- }
-
- .arrow-btn {
- padding: 10rpx 20rpx;
- font-size: 36rpx;
- color: #606266;
- }
- }
- .year-range-tip {
- font-size: 24rpx;
- color: #909399;
- text-align: center;
- margin-bottom: 16rpx;
- }
-
- .grid-container {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
- padding: 10rpx;
-
- .grid-item {
- width: calc(33.33% - 14rpx);
- height: 80rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #f5f7fa;
- border-radius: 12rpx;
- font-size: 28rpx;
- color: #606266;
-
- &.selected {
- background-color: #1890ff;
- color: #fff;
- box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
- }
- &.in-range {
- background-color: #e6f7ff;
- color: #1890ff;
- }
- &.range-start,
- &.range-end {
- background-color: #1890ff;
- color: #fff;
- box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
- }
-
- &.active {
- opacity: 0.8;
- }
- }
- }
- }
- .week-range-section {
- .year-selector {
- display: flex;
- justify-content: center;
- align-items: center;
- margin-bottom: 30rpx;
- .current-year {
- font-size: 32rpx;
- font-weight: 600;
- margin: 0 30rpx;
- }
- .arrow-btn {
- padding: 10rpx 20rpx;
- font-size: 36rpx;
- color: #606266;
- }
- }
- .week-list {
- height: 500rpx;
- }
- .week-grid {
- .grid-item {
- width: calc(25% - 16rpx);
- &.in-range {
- background-color: #e6f7ff;
- color: #1890ff;
- }
- &.range-start,
- &.range-end {
- background-color: #1890ff;
- color: #fff;
- box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
- }
- }
- }
- }
-
- // 时间筛选样式 (在 date 模式下)
- .time-filter-section {
- margin-top: 30rpx;
-
- .section-divider {
- height: 1rpx;
- background-color: #eee;
- margin-bottom: 20rpx;
- }
-
- .time-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20rpx;
-
- .title {
- font-size: 28rpx;
- font-weight: 600;
- color: #333;
- }
-
- .quick-btns {
- display: flex;
- gap: 15rpx;
-
- .btn {
- font-size: 24rpx;
- color: #666;
- background-color: #f5f7fa;
- padding: 6rpx 20rpx;
- border-radius: 24rpx;
-
- &.active {
- background-color: #1890ff;
- color: #fff;
- }
- }
- }
- }
-
- .time-selector-container {
- border: 1rpx solid #e0e6ed;
- border-radius: 12rpx;
- background-color: #fff;
- overflow: hidden;
-
- .time-table-header {
- display: flex;
- align-items: center;
- background-color: #f9f9f9;
- border-bottom: 1rpx solid #eee;
- height: 70rpx;
-
- .th {
- flex: 1;
- text-align: center;
- font-size: 24rpx;
- color: #909399;
- font-weight: 500;
- }
-
- .th-separator {
- width: 1rpx;
- height: 30rpx;
- background-color: #e0e6ed;
- }
- }
-
- .time-table-body {
- display: flex;
- height: 300rpx; // 降低一点高度
- align-items: center;
-
- .time-column {
- flex: 1;
- height: 100%;
-
- .time-item {
- height: 70rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 26rpx;
- color: #333;
-
- &.selected {
- color: #1890ff;
- font-weight: 600;
- background-color: #f0f9ff;
- }
- }
- }
-
- .body-separator {
- width: 60rpx;
- text-align: center;
- font-size: 24rpx;
- color: #ccc;
- }
- }
- }
- }
- }
- }
- }
- }
- </style>
|