Commit bc2154934a924f10abdab01f46174db881080bd9
1 parent
9441e28e
考勤记录
Showing
8 changed files
with
1069 additions
and
1565 deletions
api/index/index.js
| @@ -63,7 +63,7 @@ export const getCurrentOrg = (params) => { | @@ -63,7 +63,7 @@ export const getCurrentOrg = (params) => { | ||
| 63 | * @returns {Promise} | 63 | * @returns {Promise} |
| 64 | */ | 64 | */ |
| 65 | export const orgTaskIndicator = (params) => { | 65 | export const orgTaskIndicator = (params) => { |
| 66 | - return get('/app-api/homePage/summary/orgTaskIndicator', params); | 66 | + return post('/app-api/homePage/summary/orgTaskIndicator', params); |
| 67 | }; | 67 | }; |
| 68 | 68 | ||
| 69 | 69 |
api/user.js
| @@ -85,6 +85,25 @@ export const getSmsCode = (data) => { | @@ -85,6 +85,25 @@ export const getSmsCode = (data) => { | ||
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | 87 | ||
| 88 | +/** | ||
| 89 | + * 查询月度考勤日历 | ||
| 90 | + * @returns {Promise} | ||
| 91 | + */ | ||
| 92 | +export const attendanceDaily = (data) => { | ||
| 93 | + return get('/app-api/garden/attendance-daily/calendar',data) | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +/** | ||
| 97 | + * 查询月度考勤日历 | ||
| 98 | + * @returns {Promise} | ||
| 99 | + */ | ||
| 100 | +export const dayDetail = (data) => { | ||
| 101 | + return get('/app-api/garden/attendance-daily/day-detail',data) | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | + | ||
| 105 | + | ||
| 106 | + | ||
| 88 | 107 | ||
| 89 | 108 | ||
| 90 | 109 |
pages-sub/daily/attendanceRecord.vue
| 1 | <template> | 1 | <template> |
| 2 | -<view class="attendance-page"> | ||
| 3 | - <!-- 顶部统计卡片 --> | ||
| 4 | - <view class="stat-card"> | ||
| 5 | - <view class="stat-item"> | ||
| 6 | - <view class="stat-label">本月打卡</view> | ||
| 7 | - <view class="stat-value">{{ statData.checkInDays }}</view> | ||
| 8 | - </view> | ||
| 9 | - <view class="stat-item"> | ||
| 10 | - <view class="stat-label">正常天数</view> | ||
| 11 | - <view class="stat-value">{{ statData.normalDays }}</view> | ||
| 12 | - </view> | ||
| 13 | - <view class="stat-item"> | ||
| 14 | - <view class="stat-label">异常天数</view> | ||
| 15 | - <view class="stat-value">{{ statData.errorDays }}</view> | ||
| 16 | - </view> | ||
| 17 | - </view> | ||
| 18 | - | ||
| 19 | - <!-- 手写内联日历,无弹窗 --> | ||
| 20 | - <view class="calendar-wrap"> | ||
| 21 | - <view class="calendar-header"> | ||
| 22 | - <!-- 左右横向排列:上一月 | 年月标题 | 下一月 --> | ||
| 23 | - <view class="calendar-btn" @click="prevMonth">上一月</view> | ||
| 24 | - <view class="calendar-title">{{ currentYear }}年{{ currentMonth }}月</view> | ||
| 25 | - <!-- 当前是最新月份时,隐藏下个月按钮 --> | ||
| 26 | - <view v-if="canNext" class="calendar-btn" @click="nextMonth">下一月</view> | ||
| 27 | - <view v-else class="calendar-btn empty-btn"></view> | ||
| 28 | - </view> | ||
| 29 | - <!-- 星期头部 --> | ||
| 30 | - <view class="week-row"> | ||
| 31 | - <view class="week-item" v-for="w in weekList" :key="w">{{ w }}</view> | 2 | + <view class="attendance-page"> |
| 3 | + <!-- 顶部统计卡片 --> | ||
| 4 | + <view class="stat-card"> | ||
| 5 | + <view class="stat-item"> | ||
| 6 | + <view class="stat-label">本月打卡</view> | ||
| 7 | + <view class="stat-value">{{ statData.checkInDays }}</view> | ||
| 8 | + </view> | ||
| 9 | + <view class="stat-item"> | ||
| 10 | + <view class="stat-label">正常天数</view> | ||
| 11 | + <view class="stat-value">{{ statData.normalDays }}</view> | ||
| 12 | + </view> | ||
| 13 | + <view class="stat-item"> | ||
| 14 | + <view class="stat-label">异常天数</view> | ||
| 15 | + <view class="stat-value">{{ statData.errorDays }}</view> | ||
| 16 | + </view> | ||
| 32 | </view> | 17 | </view> |
| 33 | - <!-- 日期网格 --> | ||
| 34 | - <view class="day-row" v-for="(row, rIdx) in dayRows" :key="rIdx"> | ||
| 35 | - <view | ||
| 36 | - class="day-item" | ||
| 37 | - v-for="(day, dIdx) in row" | ||
| 38 | - :key="dIdx" | ||
| 39 | - :class="{ | 18 | + <!-- 手写内联日历,无弹窗 --> |
| 19 | + <view class="calendar-wrap"> | ||
| 20 | + <view class="calendar-header"> | ||
| 21 | + <!-- 左右横向排列:上一月 | 年月标题 | 下一月 --> | ||
| 22 | + <view class="calendar-btn" @click="prevMonth">上一月</view> | ||
| 23 | + <view class="calendar-title">{{ currentYear }}年{{ currentMonth }}月</view> | ||
| 24 | + <!-- 当前是最新月份时,隐藏下个月按钮 --> | ||
| 25 | + <view v-if="canNext" class="calendar-btn" @click="nextMonth">下一月</view> | ||
| 26 | + <view v-else class="calendar-btn empty-btn"></view> | ||
| 27 | + </view> | ||
| 28 | + <!-- 星期头部 --> | ||
| 29 | + <view class="week-row"> | ||
| 30 | + <view class="week-item" v-for="w in weekList" :key="w">{{ w }}</view> | ||
| 31 | + </view> | ||
| 32 | + <!-- 日期网格 --> | ||
| 33 | + <view class="day-row" v-for="(row, rIdx) in dayRows" :key="rIdx"> | ||
| 34 | + <view class="day-item" v-for="(day, dIdx) in row" :key="dIdx" :class="{ | ||
| 40 | 'day-item--other': day.otherMonth, | 35 | 'day-item--other': day.otherMonth, |
| 41 | 'day-item--active': day.dateStr === selectDate, | 36 | 'day-item--active': day.dateStr === selectDate, |
| 42 | - }" | ||
| 43 | - @click="onDayClick(day)" | ||
| 44 | - > | ||
| 45 | - <view class="day-text">{{ day.day }}</view> | ||
| 46 | - <view v-if="day.dot" class="day-dot" :style="{backgroundColor: day.dotColor}"></view> | 37 | + }" @click="onDayClick(day)"> |
| 38 | + <view class="day-text">{{ day.day }}</view> | ||
| 39 | + <view v-if="day.dot" class="day-dot" :style="{ backgroundColor: day.dotColor }"></view> | ||
| 40 | + </view> | ||
| 47 | </view> | 41 | </view> |
| 48 | </view> | 42 | </view> |
| 49 | - </view> | ||
| 50 | - | ||
| 51 | - <!-- 打卡详情区域 --> | ||
| 52 | - <view class="detail-card"> | ||
| 53 | - <view v-if="!currentAttendance"> | ||
| 54 | - <view class="empty-text">暂无考勤记录</view> | ||
| 55 | - </view> | ||
| 56 | - <view v-else> | ||
| 57 | - <view class="detail-row"> | ||
| 58 | - <view class="detail-title">上班打卡时间:</view> | ||
| 59 | - <view class="detail-text">{{ currentAttendance.clockInTime || '缺少打卡记录' }}</view> | 43 | + <!-- 打卡详情区域 --> |
| 44 | + <view class="detail-card"> | ||
| 45 | + <view v-if="!currentAttendance"> | ||
| 46 | + <view class="empty-text">暂无考勤记录</view> | ||
| 60 | </view> | 47 | </view> |
| 61 | - <view class="detail-row"> | ||
| 62 | - <view class="detail-title">上班打卡地点:</view> | ||
| 63 | - <view class="detail-text">{{ currentAttendance.clockInAddress || '-' }}</view> | ||
| 64 | - </view> | ||
| 65 | - <view class="divider"></view> | ||
| 66 | - <view class="detail-row"> | ||
| 67 | - <view class="detail-title">下班打卡时间:</view> | ||
| 68 | - <view class="detail-text">{{ currentAttendance.clockOutTime || '缺少打卡记录' }}</view> | ||
| 69 | - </view> | ||
| 70 | - <view class="detail-row"> | ||
| 71 | - <view class="detail-title">下班打卡地点:</view> | ||
| 72 | - <view class="detail-text">{{ currentAttendance.clockOutAddress || '-' }}</view> | 48 | + <view v-else> |
| 49 | + <view class="detail-row"> | ||
| 50 | + <view class="detail-title">上班打卡时间:</view> | ||
| 51 | + <view class="detail-text">{{ currentAttendance.onDutyTime || '缺少打卡记录' }}</view> | ||
| 52 | + </view> | ||
| 53 | + <view class="detail-row"> | ||
| 54 | + <view class="detail-title">上班打卡地点:</view> | ||
| 55 | + <view class="detail-text">{{ currentAttendance.onDutyAddress || '-' }}</view> | ||
| 56 | + </view> | ||
| 57 | + <view class="divider"></view> | ||
| 58 | + <view class="detail-row"> | ||
| 59 | + <view class="detail-title">下班打卡时间:</view> | ||
| 60 | + <view class="detail-text">{{ currentAttendance.offDutyTime || '缺少打卡记录' }}</view> | ||
| 61 | + </view> | ||
| 62 | + <view class="detail-row"> | ||
| 63 | + <view class="detail-title">下班打卡地点:</view> | ||
| 64 | + <view class="detail-text">{{ currentAttendance.offDutyAddress || '-' }}</view> | ||
| 65 | + </view> | ||
| 66 | + <!-- 可选:展示异常原因 --> | ||
| 67 | + <view v-if="currentAttendance.abnormalReason" class="detail-row"> | ||
| 68 | + <view class="detail-title">异常原因:</view> | ||
| 69 | + <view class="detail-text" style="color:#f56c6c">{{ currentAttendance.abnormalReason }}</view> | ||
| 70 | + </view> | ||
| 73 | </view> | 71 | </view> |
| 74 | </view> | 72 | </view> |
| 75 | - </view> | ||
| 76 | -</view> | 73 | + </view> |
| 77 | </template> | 74 | </template> |
| 78 | - | ||
| 79 | <script setup lang="ts"> | 75 | <script setup lang="ts"> |
| 80 | -import { ref, onMounted, computed } from 'vue'; | ||
| 81 | - | ||
| 82 | -const weekList = ['日','一','二','三','四','五','六']; | 76 | +import { ref, onMounted, computed, watch } from 'vue'; |
| 77 | +import { attendanceDaily, dayDetail } from '@/api/user' | ||
| 78 | +const weekList = ['日', '一', '二', '三', '四', '五', '六']; | ||
| 83 | const today = new Date(); | 79 | const today = new Date(); |
| 84 | const todayTs = today.getTime(); | 80 | const todayTs = today.getTime(); |
| 85 | // 当前显示月份 | 81 | // 当前显示月份 |
| @@ -87,65 +83,50 @@ const currentYear = ref(today.getFullYear()); | @@ -87,65 +83,50 @@ const currentYear = ref(today.getFullYear()); | ||
| 87 | const currentMonth = ref(today.getMonth() + 1); | 83 | const currentMonth = ref(today.getMonth() + 1); |
| 88 | const selectDate = ref(''); | 84 | const selectDate = ref(''); |
| 89 | const dayRows = ref<any[]>([]); | 85 | const dayRows = ref<any[]>([]); |
| 90 | - | ||
| 91 | // 是否允许点击下一月:判断当前展示月份是否小于本月 | 86 | // 是否允许点击下一月:判断当前展示月份是否小于本月 |
| 92 | const canNext = computed(() => { | 87 | const canNext = computed(() => { |
| 93 | - const showMonthDate = new Date(currentYear.value, currentMonth.value -1, 1); | 88 | + const showMonthDate = new Date(currentYear.value, currentMonth.value - 1, 1); |
| 94 | const nowMonthDate = new Date(today.getFullYear(), today.getMonth(), 1); | 89 | const nowMonthDate = new Date(today.getFullYear(), today.getMonth(), 1); |
| 95 | return showMonthDate < nowMonthDate; | 90 | return showMonthDate < nowMonthDate; |
| 96 | }); | 91 | }); |
| 97 | 92 | ||
| 98 | -// ========== 写死静态统计数据 ========== | 93 | +// 统计数据 |
| 99 | const statData = ref({ | 94 | const statData = ref({ |
| 100 | - checkInDays: 28, | ||
| 101 | - normalDays: 25, | ||
| 102 | - errorDays: 3 | 95 | + checkInDays: 0, |
| 96 | + normalDays: 0, | ||
| 97 | + errorDays: 0 | ||
| 103 | }); | 98 | }); |
| 99 | +// 当月考勤打点字典 {dateStr: {dot:string, abnormalReason:string}} | ||
| 100 | +const monthDayMap = ref<Record<string, { dot: string | null, abnormalReason: string | null }>>({}); | ||
| 104 | 101 | ||
| 105 | -// ========== 写死日历标记:红点error异常,灰点normal正常 ========== | ||
| 106 | -const markMap = ref<Record<string, {type:'error'|'normal'}>>({ | ||
| 107 | - '2026-10-02': { type: 'error' }, | ||
| 108 | - '2026-10-10': { type: 'error' }, | ||
| 109 | - '2026-10-16': { type: 'error' }, | ||
| 110 | - '2026-10-05': { type: 'normal' }, | ||
| 111 | - '2026-10-12': { type: 'normal' }, | ||
| 112 | -}); | 102 | +// 选中日期详情 |
| 103 | +const currentAttendance = ref<any>(null); | ||
| 113 | 104 | ||
| 114 | -// ========== 写死每日考勤详情数据 ========== | ||
| 115 | -const dayAttendanceMap = ref<Record<string, any>>({ | ||
| 116 | - '2026-10-02': { | ||
| 117 | - clockInTime: '2026-10-02 08:02:10', | ||
| 118 | - clockInAddress: '月坛南街安华大厦', | ||
| 119 | - clockOutTime: '', | ||
| 120 | - clockOutAddress: '' | ||
| 121 | - }, | ||
| 122 | - '2026-10-13': { | ||
| 123 | - clockInTime: '2026-10-13 07:58:25', | ||
| 124 | - clockInAddress: '月坛南街安华大厦', | ||
| 125 | - clockOutTime: '', | ||
| 126 | - clockOutAddress: '南礼士地铁站' | ||
| 127 | - }, | ||
| 128 | - '2026-10-05': { | ||
| 129 | - clockInTime: '2026-10-05 08:00:00', | ||
| 130 | - clockInAddress: '月坛南街安华大厦', | ||
| 131 | - clockOutTime: '2026-10-05 18:00:00', | ||
| 132 | - clockOutAddress: '月坛南街安华大厦' | ||
| 133 | - }, | ||
| 134 | - '2026-10-10': null, | ||
| 135 | - '2026-10-12': { | ||
| 136 | - clockInTime: '2026-10-12 07:55:10', | ||
| 137 | - clockInAddress: '月坛南街安华大厦', | ||
| 138 | - clockOutTime: '2026-10-12 17:50:30', | ||
| 139 | - clockOutAddress: '月坛南街安华大厦' | ||
| 140 | - }, | ||
| 141 | - '2026-10-16': { | ||
| 142 | - clockInTime: '2026-10-16 07:59:22', | ||
| 143 | - clockInAddress: '月坛南街安华大厦', | ||
| 144 | - clockOutTime: '', | ||
| 145 | - clockOutAddress: '' | 105 | +// 拉取月度考勤汇总(stat + 当月日期打点) |
| 106 | +const getMonthAttendance = async () => { | ||
| 107 | + try { | ||
| 108 | + const month = `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}`; | ||
| 109 | + const res = await attendanceDaily({ month }); | ||
| 110 | + // 赋值顶部统计 | ||
| 111 | + statData.value.checkInDays = res.stat.clockDays; | ||
| 112 | + statData.value.normalDays = res.stat.normalDays; | ||
| 113 | + statData.value.errorDays = res.stat.abnormalDays; | ||
| 114 | + // 构建日期映射字典 | ||
| 115 | + const map: Record<string, { dot: string | null, abnormalReason: string | null }> = {}; | ||
| 116 | + res.days.forEach((item: any) => { | ||
| 117 | + map[item.date] = { | ||
| 118 | + dot: item.dot, | ||
| 119 | + abnormalReason: item.abnormalReason | ||
| 120 | + }; | ||
| 121 | + }) | ||
| 122 | + monthDayMap.value = map; | ||
| 123 | + // 重新渲染日历 | ||
| 124 | + renderMonth(); | ||
| 125 | + } catch (err) { | ||
| 126 | + console.error('获取月度考勤失败', err); | ||
| 127 | + uni.showToast({ title: '加载月度考勤失败', icon: 'none' }); | ||
| 146 | } | 128 | } |
| 147 | -}); | ||
| 148 | -const currentAttendance = ref<any>(null); | 129 | +}; |
| 149 | 130 | ||
| 150 | // 生成月份日期网格 | 131 | // 生成月份日期网格 |
| 151 | const renderMonth = () => { | 132 | const renderMonth = () => { |
| @@ -159,46 +140,49 @@ const renderMonth = () => { | @@ -159,46 +140,49 @@ const renderMonth = () => { | ||
| 159 | const prevLastDay = new Date(year, month - 1, 0).getDate(); | 140 | const prevLastDay = new Date(year, month - 1, 0).getDate(); |
| 160 | // 当月第一天星期 0周日~6周六 | 141 | // 当月第一天星期 0周日~6周六 |
| 161 | const firstWeek = firstDay.getDay(); | 142 | const firstWeek = firstDay.getDay(); |
| 162 | - | ||
| 163 | const rows: any[] = []; | 143 | const rows: any[] = []; |
| 164 | let row: any[] = []; | 144 | let row: any[] = []; |
| 165 | // 填充上月尾部 | 145 | // 填充上月尾部 |
| 166 | - for(let i = 0; i < firstWeek; i++){ | 146 | + for (let i = 0; i < firstWeek; i++) { |
| 167 | const dayNum = prevLastDay - firstWeek + i + 1; | 147 | const dayNum = prevLastDay - firstWeek + i + 1; |
| 168 | row.push({ | 148 | row.push({ |
| 169 | day: dayNum, | 149 | day: dayNum, |
| 170 | otherMonth: true, | 150 | otherMonth: true, |
| 171 | - dateStr: `${year}-${String(month-1).padStart(2,'0')}-${String(dayNum).padStart(2,'0')}` | 151 | + dateStr: `${year}-${String(month - 1).padStart(2, '0')}-${String(dayNum).padStart(2, '0')}` |
| 172 | }) | 152 | }) |
| 173 | } | 153 | } |
| 174 | // 填充当月日期 | 154 | // 填充当月日期 |
| 175 | - for(let d = 1; d <= lastDay.getDate(); d++){ | ||
| 176 | - const dateStr = `${year}-${String(month).padStart(2,'0')}-${String(d).padStart(2,'0')}`; | ||
| 177 | - const mark = markMap.value[dateStr]; | ||
| 178 | - const item:any = { | 155 | + for (let d = 1; d <= lastDay.getDate(); d++) { |
| 156 | + const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(d).padStart(2, '0')}`; | ||
| 157 | + const mark = monthDayMap.value[dateStr]; | ||
| 158 | + const item: any = { | ||
| 179 | day: d, | 159 | day: d, |
| 180 | otherMonth: false, | 160 | otherMonth: false, |
| 181 | dateStr, | 161 | dateStr, |
| 182 | dot: false, | 162 | dot: false, |
| 183 | dotColor: '' | 163 | dotColor: '' |
| 184 | }; | 164 | }; |
| 185 | - if(mark){ | 165 | + if (mark?.dot) { |
| 186 | item.dot = true; | 166 | item.dot = true; |
| 187 | - item.dotColor = mark.type === 'error' ? '#f56c6c' : '#cccccc'; | 167 | + if (mark.dot === 'abnormal') { |
| 168 | + item.dotColor = '#f56c6c'; | ||
| 169 | + } else if (mark.dot === 'partial') { | ||
| 170 | + item.dotColor = '#cccccc'; | ||
| 171 | + } | ||
| 188 | } | 172 | } |
| 189 | row.push(item); | 173 | row.push(item); |
| 190 | - if(row.length ===7){ | 174 | + if (row.length === 7) { |
| 191 | rows.push(row); | 175 | rows.push(row); |
| 192 | row = []; | 176 | row = []; |
| 193 | } | 177 | } |
| 194 | } | 178 | } |
| 195 | // 填充下月开头 | 179 | // 填充下月开头 |
| 196 | let nextDay = 1; | 180 | let nextDay = 1; |
| 197 | - while(row.length <7){ | 181 | + while (row.length < 7) { |
| 198 | row.push({ | 182 | row.push({ |
| 199 | day: nextDay, | 183 | day: nextDay, |
| 200 | - otherMonth:true, | ||
| 201 | - dateStr: `${year}-${String(month+1).padStart(2,'0')}-${String(nextDay).padStart(2,'0')}` | 184 | + otherMonth: true, |
| 185 | + dateStr: `${year}-${String(month + 1).padStart(2, '0')}-${String(nextDay).padStart(2, '0')}` | ||
| 202 | }) | 186 | }) |
| 203 | nextDay++; | 187 | nextDay++; |
| 204 | } | 188 | } |
| @@ -208,180 +192,209 @@ const renderMonth = () => { | @@ -208,180 +192,209 @@ const renderMonth = () => { | ||
| 208 | 192 | ||
| 209 | // 上一月 | 193 | // 上一月 |
| 210 | const prevMonth = () => { | 194 | const prevMonth = () => { |
| 211 | - if(currentMonth.value === 1){ | 195 | + if (currentMonth.value === 1) { |
| 212 | currentYear.value--; | 196 | currentYear.value--; |
| 213 | currentMonth.value = 12; | 197 | currentMonth.value = 12; |
| 214 | - }else{ | 198 | + } else { |
| 215 | currentMonth.value--; | 199 | currentMonth.value--; |
| 216 | } | 200 | } |
| 217 | - renderMonth(); | 201 | + getMonthAttendance(); |
| 218 | }; | 202 | }; |
| 219 | // 下一月 | 203 | // 下一月 |
| 220 | const nextMonth = () => { | 204 | const nextMonth = () => { |
| 221 | let nextY = currentYear.value; | 205 | let nextY = currentYear.value; |
| 222 | let nextM = currentMonth.value + 1; | 206 | let nextM = currentMonth.value + 1; |
| 223 | - if(nextM > 12){ | 207 | + if (nextM > 12) { |
| 224 | nextY++; | 208 | nextY++; |
| 225 | - nextM =1; | 209 | + nextM = 1; |
| 226 | } | 210 | } |
| 227 | currentYear.value = nextY; | 211 | currentYear.value = nextY; |
| 228 | currentMonth.value = nextM; | 212 | currentMonth.value = nextM; |
| 229 | - renderMonth(); | 213 | + getMonthAttendance(); |
| 230 | }; | 214 | }; |
| 231 | 215 | ||
| 232 | -// 点击日期 | ||
| 233 | -const onDayClick = (day:any) => { | 216 | +// 点击日期:获取当日详情 |
| 217 | +const onDayClick = async (day: any) => { | ||
| 234 | // 禁止点击其他月份 | 218 | // 禁止点击其他月份 |
| 235 | - if(day.otherMonth) return; | 219 | + if (day.otherMonth) return; |
| 236 | const selectDay = new Date(day.dateStr); | 220 | const selectDay = new Date(day.dateStr); |
| 237 | - if(selectDay > today){ | 221 | + if (selectDay > today) { |
| 238 | currentAttendance.value = null; | 222 | currentAttendance.value = null; |
| 223 | + selectDate.value = day.dateStr; | ||
| 239 | return; | 224 | return; |
| 240 | } | 225 | } |
| 241 | selectDate.value = day.dateStr; | 226 | selectDate.value = day.dateStr; |
| 242 | - const data = dayAttendanceMap.value[day.dateStr]; | ||
| 243 | - currentAttendance.value = data ?? null; | 227 | + try { |
| 228 | + const res = await dayDetail({ date: day.dateStr }); | ||
| 229 | + // 取第一条记录,大多数场景单条记录 | ||
| 230 | + const record = res.records?.[0] ?? null; | ||
| 231 | + if (record) { | ||
| 232 | + currentAttendance.value = { | ||
| 233 | + ...record, | ||
| 234 | + abnormalReason: res.abnormalReason | ||
| 235 | + }; | ||
| 236 | + } else { | ||
| 237 | + currentAttendance.value = null; | ||
| 238 | + } | ||
| 239 | + } catch (err) { | ||
| 240 | + console.error('获取当日考勤详情失败', err); | ||
| 241 | + uni.showToast({ title: '加载当日考勤失败', icon: 'none' }); | ||
| 242 | + currentAttendance.value = null; | ||
| 243 | + } | ||
| 244 | }; | 244 | }; |
| 245 | 245 | ||
| 246 | -onMounted(() => { | ||
| 247 | - const todayStr = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`; | 246 | +// 页面初始化 |
| 247 | +onMounted(async () => { | ||
| 248 | + const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; | ||
| 248 | selectDate.value = todayStr; | 249 | selectDate.value = todayStr; |
| 249 | - fetchDayAttendance(todayStr); | ||
| 250 | - renderMonth(); | 250 | + await getMonthAttendance(); |
| 251 | + // 默认选中今天,加载当日详情 | ||
| 252 | + await onDayClick({ dateStr: todayStr, otherMonth: false }); | ||
| 251 | }); | 253 | }); |
| 252 | - | ||
| 253 | -const fetchDayAttendance = (date:string) => { | ||
| 254 | - const selectDay = new Date(date); | ||
| 255 | - if(selectDay > today){ | ||
| 256 | - currentAttendance.value = null; | ||
| 257 | - return; | ||
| 258 | - } | ||
| 259 | - const data = dayAttendanceMap.value[date]; | ||
| 260 | - currentAttendance.value = data ?? null; | ||
| 261 | -}; | ||
| 262 | </script> | 254 | </script> |
| 263 | - | ||
| 264 | <style scoped lang="scss"> | 255 | <style scoped lang="scss"> |
| 265 | -.attendance-page{ | 256 | +.attendance-page { |
| 266 | padding: 20rpx; | 257 | padding: 20rpx; |
| 267 | background-color: #f5f5f7; | 258 | background-color: #f5f5f7; |
| 268 | min-height: 100vh; | 259 | min-height: 100vh; |
| 269 | } | 260 | } |
| 270 | -.stat-card{ | 261 | + |
| 262 | +.stat-card { | ||
| 271 | display: flex; | 263 | display: flex; |
| 272 | background-color: #3c9cff; | 264 | background-color: #3c9cff; |
| 273 | border-radius: 16rpx; | 265 | border-radius: 16rpx; |
| 274 | - padding:30rpx 0; | ||
| 275 | - margin-bottom:20rpx; | ||
| 276 | - .stat-item{ | ||
| 277 | - flex:1; | ||
| 278 | - text-align:center; | ||
| 279 | - color:#fff; | ||
| 280 | - .stat-label{ | ||
| 281 | - font-size:28rpx; | ||
| 282 | - opacity:0.9; | 266 | + padding: 30rpx 0; |
| 267 | + margin-bottom: 20rpx; | ||
| 268 | + | ||
| 269 | + .stat-item { | ||
| 270 | + flex: 1; | ||
| 271 | + text-align: center; | ||
| 272 | + color: #fff; | ||
| 273 | + | ||
| 274 | + .stat-label { | ||
| 275 | + font-size: 28rpx; | ||
| 276 | + opacity: 0.9; | ||
| 283 | } | 277 | } |
| 284 | - .stat-value{ | ||
| 285 | - font-size:36rpx; | ||
| 286 | - margin-top:10rpx; | ||
| 287 | - font-weight:bold; | 278 | + |
| 279 | + .stat-value { | ||
| 280 | + font-size: 36rpx; | ||
| 281 | + margin-top: 10rpx; | ||
| 282 | + font-weight: bold; | ||
| 288 | } | 283 | } |
| 289 | } | 284 | } |
| 290 | } | 285 | } |
| 291 | -.calendar-wrap{ | ||
| 292 | - background:#fff; | ||
| 293 | - border-radius:12rpx; | ||
| 294 | - padding:20rpx; | ||
| 295 | - margin-bottom:20rpx; | 286 | + |
| 287 | +.calendar-wrap { | ||
| 288 | + background: #fff; | ||
| 289 | + border-radius: 12rpx; | ||
| 290 | + padding: 20rpx; | ||
| 291 | + margin-bottom: 20rpx; | ||
| 296 | } | 292 | } |
| 297 | -.calendar-header{ | ||
| 298 | - display:flex; | 293 | + |
| 294 | +.calendar-header { | ||
| 295 | + display: flex; | ||
| 299 | justify-content: space-between; | 296 | justify-content: space-between; |
| 300 | - align-items:center; | ||
| 301 | - margin-bottom:20rpx; | 297 | + align-items: center; |
| 298 | + margin-bottom: 20rpx; | ||
| 302 | } | 299 | } |
| 303 | -.calendar-title{ | ||
| 304 | - font-size:32rpx; | ||
| 305 | - font-weight:bold; | 300 | + |
| 301 | +.calendar-title { | ||
| 302 | + font-size: 32rpx; | ||
| 303 | + font-weight: bold; | ||
| 306 | } | 304 | } |
| 307 | -.calendar-btn{ | ||
| 308 | - padding:8rpx 16rpx; | ||
| 309 | - border:1rpx solid #eee; | ||
| 310 | - border-radius:8rpx; | ||
| 311 | - font-size:26rpx; | 305 | + |
| 306 | +.calendar-btn { | ||
| 307 | + padding: 8rpx 16rpx; | ||
| 308 | + border: 1rpx solid #eee; | ||
| 309 | + border-radius: 8rpx; | ||
| 310 | + font-size: 26rpx; | ||
| 312 | } | 311 | } |
| 313 | -.empty-btn{ | ||
| 314 | - border-color:transparent; | ||
| 315 | - color:transparent; | 312 | + |
| 313 | +.empty-btn { | ||
| 314 | + border-color: transparent; | ||
| 315 | + color: transparent; | ||
| 316 | } | 316 | } |
| 317 | -.week-row{ | ||
| 318 | - display:flex; | ||
| 319 | - .week-item{ | ||
| 320 | - flex:1; | ||
| 321 | - text-align:center; | ||
| 322 | - font-size:26rpx; | ||
| 323 | - color:#666; | ||
| 324 | - padding:10rpx 0; | 317 | + |
| 318 | +.week-row { | ||
| 319 | + display: flex; | ||
| 320 | + | ||
| 321 | + .week-item { | ||
| 322 | + flex: 1; | ||
| 323 | + text-align: center; | ||
| 324 | + font-size: 26rpx; | ||
| 325 | + color: #666; | ||
| 326 | + padding: 10rpx 0; | ||
| 325 | } | 327 | } |
| 326 | } | 328 | } |
| 327 | -.day-row{ | ||
| 328 | - display:flex; | ||
| 329 | - .day-item{ | ||
| 330 | - flex:1; | ||
| 331 | - min-height:70rpx; | ||
| 332 | - display:flex; | ||
| 333 | - flex-direction:column; | ||
| 334 | - align-items:center; | ||
| 335 | - justify-content:center; | ||
| 336 | - padding:8rpx 0; | ||
| 337 | - position:relative; | ||
| 338 | - .day-text{ | ||
| 339 | - font-size:28rpx; | 329 | + |
| 330 | +.day-row { | ||
| 331 | + display: flex; | ||
| 332 | + | ||
| 333 | + .day-item { | ||
| 334 | + flex: 1; | ||
| 335 | + min-height: 70rpx; | ||
| 336 | + display: flex; | ||
| 337 | + flex-direction: column; | ||
| 338 | + align-items: center; | ||
| 339 | + justify-content: center; | ||
| 340 | + padding: 8rpx 0; | ||
| 341 | + position: relative; | ||
| 342 | + | ||
| 343 | + .day-text { | ||
| 344 | + font-size: 28rpx; | ||
| 340 | } | 345 | } |
| 341 | - .day-dot{ | ||
| 342 | - width:10rpx; | ||
| 343 | - height:10rpx; | ||
| 344 | - border-radius:50%; | ||
| 345 | - margin-top:4rpx; | 346 | + |
| 347 | + .day-dot { | ||
| 348 | + position: absolute; | ||
| 349 | + bottom: 8rpx; | ||
| 350 | + width: 10rpx; | ||
| 351 | + height: 10rpx; | ||
| 352 | + border-radius: 50%; | ||
| 346 | } | 353 | } |
| 347 | } | 354 | } |
| 348 | - .day-item--other{ | ||
| 349 | - color:#ccc; | 355 | + |
| 356 | + .day-item--other { | ||
| 357 | + color: #ccc; | ||
| 350 | } | 358 | } |
| 351 | - .day-item--active{ | ||
| 352 | - background:#3c9cff; | ||
| 353 | - color:#fff; | ||
| 354 | - border-radius:50%; | 359 | + |
| 360 | + .day-item--active { | ||
| 361 | + color: #f00; | ||
| 355 | } | 362 | } |
| 356 | } | 363 | } |
| 357 | -.detail-card{ | ||
| 358 | - background:#fff; | ||
| 359 | - border-radius:12rpx; | ||
| 360 | - padding:30rpx; | ||
| 361 | - .empty-text{ | ||
| 362 | - text-align:center; | ||
| 363 | - color:#999; | ||
| 364 | - font-size:30rpx; | ||
| 365 | - padding:40rpx 0; | 364 | + |
| 365 | +.detail-card { | ||
| 366 | + background: #fff; | ||
| 367 | + border-radius: 12rpx; | ||
| 368 | + padding: 30rpx; | ||
| 369 | + | ||
| 370 | + .empty-text { | ||
| 371 | + text-align: center; | ||
| 372 | + color: #999; | ||
| 373 | + font-size: 30rpx; | ||
| 374 | + padding: 40rpx 0; | ||
| 366 | } | 375 | } |
| 367 | - .detail-row{ | ||
| 368 | - margin-bottom:20rpx; | ||
| 369 | - display:flex; | ||
| 370 | - .detail-title{ | ||
| 371 | - font-size:30rpx; | ||
| 372 | - color:#333; | ||
| 373 | - width:220rpx; | 376 | + |
| 377 | + .detail-row { | ||
| 378 | + margin-bottom: 20rpx; | ||
| 379 | + display: flex; | ||
| 380 | + | ||
| 381 | + .detail-title { | ||
| 382 | + font-size: 30rpx; | ||
| 383 | + color: #333; | ||
| 384 | + width: 220rpx; | ||
| 374 | } | 385 | } |
| 375 | - .detail-text{ | ||
| 376 | - flex:1; | ||
| 377 | - font-size:30rpx; | ||
| 378 | - color:#666; | 386 | + |
| 387 | + .detail-text { | ||
| 388 | + flex: 1; | ||
| 389 | + font-size: 30rpx; | ||
| 390 | + color: #666; | ||
| 379 | } | 391 | } |
| 380 | } | 392 | } |
| 381 | - .divider{ | ||
| 382 | - height:1px; | ||
| 383 | - background:#eee; | ||
| 384 | - margin:30rpx 0; | 393 | + |
| 394 | + .divider { | ||
| 395 | + height: 1px; | ||
| 396 | + background: #eee; | ||
| 397 | + margin: 30rpx 0; | ||
| 385 | } | 398 | } |
| 386 | } | 399 | } |
| 387 | </style> | 400 | </style> |
pages-sub/daily/patrol-manage/add-patrol-record.vue
| 1 | <template> | 1 | <template> |
| 2 | <view class="u-page"> | 2 | <view class="u-page"> |
| 3 | - <!-- 原有模板内容不变,仅调整表单校验相关配置 --> | ||
| 4 | - <view class="inspect-form-content commonPageLRpadding"> | ||
| 5 | - <up-form | ||
| 6 | - label-position="left" | ||
| 7 | - :model="inspectForm" | ||
| 8 | - ref="inspectFormRef" | ||
| 9 | - labelWidth="200rpx" | ||
| 10 | - > | ||
| 11 | - <!-- 1. 巡查描述 --> | ||
| 12 | - <up-form-item | ||
| 13 | - label="巡查描述" | ||
| 14 | - prop="content" | ||
| 15 | - class="form-item" | ||
| 16 | - required | ||
| 17 | - > | ||
| 18 | - <up-textarea | ||
| 19 | - v-model.trim="inspectForm.content" | ||
| 20 | - placeholder="请输入巡查描述" | ||
| 21 | - maxlength="200" | ||
| 22 | - count | ||
| 23 | - ></up-textarea> | ||
| 24 | - </up-form-item> | ||
| 25 | - | ||
| 26 | - <!-- 2. 上传图片 --> | ||
| 27 | - <up-form-item | ||
| 28 | - label="上传图片" | ||
| 29 | - prop="images" | ||
| 30 | - required | ||
| 31 | - border-bottom | ||
| 32 | - class="form-item" | ||
| 33 | - > | ||
| 34 | - <up-upload | ||
| 35 | - :file-list="imagesList" | ||
| 36 | - @after-read="(event) => uploadImgs(event)" | ||
| 37 | - @delete="(event) => deleteImg(event)" | ||
| 38 | - @on-exceed="handleExceed" | ||
| 39 | - multiple | ||
| 40 | - :max-count="3" | ||
| 41 | - upload-text="选择图片" | ||
| 42 | - del-color="#ff4d4f" | ||
| 43 | - class="upload-wrap" | ||
| 44 | - width="70" | ||
| 45 | - height="70" | ||
| 46 | - ></up-upload> | ||
| 47 | - </up-form-item> | ||
| 48 | - | ||
| 49 | - <!-- 3. 转为工单 --> | ||
| 50 | - <up-form-item | ||
| 51 | - label="转为工单" | ||
| 52 | - prop="isWorkOrder" | ||
| 53 | - required | ||
| 54 | - class="form-item" | ||
| 55 | - > | ||
| 56 | - <up-radio-group | ||
| 57 | - v-model="inspectForm.isWorkOrder" | ||
| 58 | - active-color="#1989fa" | ||
| 59 | - direction="row" | ||
| 60 | - class="radio-group" | ||
| 61 | - @change="handleWorkOrderChange" | ||
| 62 | - > | ||
| 63 | - <up-radio | ||
| 64 | - :custom-style="{marginRight: '40rpx'}" | ||
| 65 | - v-for="(item, index) in radioList" | ||
| 66 | - :key="index" | ||
| 67 | - :label="item.label" | ||
| 68 | - :name="item.value" | ||
| 69 | - > | 3 | +<!-- 原有模板内容不变,仅调整表单校验相关配置 --> |
| 4 | +<view class="inspect-form-content commonPageLRpadding"> | ||
| 5 | +<up-form label-position="left" :model="inspectForm" ref="inspectFormRef" labelWidth="200rpx"> | ||
| 6 | +<!-- 1. 巡查描述 --> | ||
| 7 | +<up-form-item label="巡查描述" prop="content" class="form-item" required> | ||
| 8 | +<up-textarea v-model.trim="inspectForm.content" placeholder="请输入巡查描述" maxlength="200" count></up-textarea> | ||
| 9 | +</up-form-item> | ||
| 10 | +<!-- 2. 上传图片 --> | ||
| 11 | +<up-form-item label="上传图片" prop="images" required border-bottom class="form-item"> | ||
| 12 | +<up-upload :file-list="imagesList" @after-read="(event) => uploadImgs(event)" | ||
| 13 | + @delete="(event) => deleteImg(event)" @on-exceed="handleExceed" multiple :max-count="3" upload-text="选择图片" | ||
| 14 | +del-color="#ff4d4f" class="upload-wrap" width="70" height="70"></up-upload> | ||
| 15 | +</up-form-item> | ||
| 16 | +<!-- 3. 转为工单 --> | ||
| 17 | +<up-form-item label="转为工单" prop="isWorkOrder" required class="form-item"> | ||
| 18 | +<up-radio-group v-model="inspectForm.isWorkOrder" active-color="#1989fa" direction="row" class="radio-group" | ||
| 19 | + @change="handleWorkOrderChange"> | ||
| 20 | +<up-radio :custom-style="{ marginRight: '40rpx' }" v-for="(item, index) in radioList" :key="index" | ||
| 21 | + :label="item.label" :name="item.value"> | ||
| 70 | {{ item.label }} | 22 | {{ item.label }} |
| 71 | - </up-radio> | ||
| 72 | - </up-radio-group> | ||
| 73 | - </up-form-item> | ||
| 74 | - | ||
| 75 | - <!-- 紧急程度(仍为必填) --> | ||
| 76 | -<!-- v-if="inspectForm.isWorkOrder === '2'"--> | ||
| 77 | - <up-form-item | ||
| 78 | - v-if="inspectForm.isWorkOrder === '2'" | ||
| 79 | - label="紧急程度" | ||
| 80 | - prop="pressingTypeName" | ||
| 81 | - border-bottom | ||
| 82 | - required | ||
| 83 | - class="form-item" | ||
| 84 | - @click="handleActionSheetOpen('pressingType')" | ||
| 85 | - > | ||
| 86 | - <up-input | ||
| 87 | - v-model="inspectForm.pressingTypeName" | ||
| 88 | - disabled | ||
| 89 | - disabled-color="#ffffff" | ||
| 90 | - placeholder="请选择紧急程度" | ||
| 91 | - border="none" | ||
| 92 | - | ||
| 93 | - ></up-input> | ||
| 94 | - <template #right> | ||
| 95 | - <up-icon name="arrow-right" size="16"></up-icon> | ||
| 96 | - </template> | ||
| 97 | - </up-form-item> | ||
| 98 | - | ||
| 99 | - <!-- 希望完成时间(移除 required 属性,改为非必填) --> | ||
| 100 | -<!-- v-if="inspectForm.isWorkOrder === '2'"--> | ||
| 101 | - <up-form-item | ||
| 102 | - v-if="inspectForm.isWorkOrder === '2'" | ||
| 103 | - label="希望完成时间" | ||
| 104 | - prop="expectedFinishDate" | ||
| 105 | - class="form-item" | ||
| 106 | - @click="show = true; uni.hideKeyboard()" | ||
| 107 | - > | ||
| 108 | - <up-input | ||
| 109 | - v-model="inspectForm.expectedFinishDate" | ||
| 110 | - border="none" | ||
| 111 | - readonly | ||
| 112 | - placeholder="点击选择时间" | ||
| 113 | - > | ||
| 114 | - | ||
| 115 | - | ||
| 116 | - </up-input> | ||
| 117 | - <template #right> | ||
| 118 | - <!-- 核心修复:用view包裹图标,添加双重@click.stop --> | ||
| 119 | - <view v-if="inspectForm.expectedFinishDate" @click.stop> | ||
| 120 | - <up-icon | ||
| 121 | - name="close" | ||
| 122 | - size="16" | ||
| 123 | - @click.stop="clearExpectedFinishDate" | ||
| 124 | - ></up-icon> | ||
| 125 | - </view> | ||
| 126 | - <up-icon name="arrow-right" size="16" v-else></up-icon> | ||
| 127 | - </template> | ||
| 128 | - </up-form-item> | ||
| 129 | - | ||
| 130 | - </up-form> | ||
| 131 | - </view> | ||
| 132 | - | ||
| 133 | - <!-- 底部提交按钮 --> | ||
| 134 | - <view class="fixed-bottom-btn-wrap"> | ||
| 135 | - <up-button | ||
| 136 | - type="primary" | ||
| 137 | - text="提交" | ||
| 138 | - @click="submitInspect" | ||
| 139 | - :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }" | ||
| 140 | - ></up-button> | ||
| 141 | - </view> | ||
| 142 | - | ||
| 143 | - <!-- 紧急程度下拉弹窗 --> | ||
| 144 | - <up-action-sheet | ||
| 145 | - :show="showActionSheet" | ||
| 146 | - :actions="pressingTypeList" | ||
| 147 | - title="请选择紧急程度" | ||
| 148 | - @close="handleActionSheetClose" | ||
| 149 | - @select="handlePressingTypeSelect" | ||
| 150 | - ></up-action-sheet> | ||
| 151 | - | ||
| 152 | - <!-- 完成时间选择器 --> | ||
| 153 | - <up-datetime-picker | ||
| 154 | - :show="show" | ||
| 155 | - v-model="datetimeValue" | ||
| 156 | - mode="datetime" | ||
| 157 | - :min-date="new Date()" | ||
| 158 | - @cancel="show = false" | ||
| 159 | - @confirm="expectedFinishDateConfirm" | ||
| 160 | - ></up-datetime-picker> | ||
| 161 | - </view> | 23 | +</up-radio> |
| 24 | +</up-radio-group> | ||
| 25 | +</up-form-item> | ||
| 26 | +<!-- ========== 【新增】业务线(单选框,对齐参考页面) ========== --> | ||
| 27 | +<up-form-item v-if="inspectForm.isWorkOrder === '2'" label="业务线" prop="busiLineCn" border-bottom required | ||
| 28 | +class="form-item"> | ||
| 29 | +<up-radio-group v-model="inspectForm.busiLineCn" placement="row" @change="handleBusiLineChange"> | ||
| 30 | +<up-radio v-for="item in busiLineOptions" :key="item.name" :label="item.name" :name="item.name"></up-radio> | ||
| 31 | +</up-radio-group> | ||
| 32 | +</up-form-item> | ||
| 33 | +<!-- ========== 【新增】问题类型(级联选择器,对齐参考页面) ========== --> | ||
| 34 | +<up-form-item v-if="inspectForm.isWorkOrder === '2'" label="问题类型" prop="orderName" border-bottom required | ||
| 35 | +class="form-item" @click="openCascader(); uni.hideKeyboard()"> | ||
| 36 | +<up-input v-model="inspectForm.orderName" disabled disabled-color="#ffffff" placeholder="请选择问题类型" | ||
| 37 | +border="none"></up-input> | ||
| 38 | +<template #right> | ||
| 39 | +<up-icon name="arrow-right" size="16"></up-icon> | ||
| 40 | +</template> | ||
| 41 | +</up-form-item> | ||
| 42 | +<!-- 紧急程度(仍为必填) --> | ||
| 43 | +<up-form-item v-if="inspectForm.isWorkOrder === '2'" label="紧急程度" prop="pressingTypeName" border-bottom required | ||
| 44 | +class="form-item" @click="handleActionSheetOpen('pressingType')"> | ||
| 45 | +<up-input v-model="inspectForm.pressingTypeName" disabled disabled-color="#ffffff" placeholder="请选择紧急程度" | ||
| 46 | +border="none"></up-input> | ||
| 47 | +<template #right> | ||
| 48 | +<up-icon name="arrow-right" size="16"></up-icon> | ||
| 49 | +</template> | ||
| 50 | +</up-form-item> | ||
| 51 | +<!-- 希望完成时间(移除 required 属性,改为非必填) --> | ||
| 52 | +<up-form-item v-if="inspectForm.isWorkOrder === '2'" label="希望完成时间" prop="expectedFinishDate" class="form-item" | ||
| 53 | + @click="show = true; uni.hideKeyboard()"> | ||
| 54 | +<up-input v-model="inspectForm.expectedFinishDate" border="none" readonly placeholder="点击选择时间"> | ||
| 55 | +</up-input> | ||
| 56 | +<template #right> | ||
| 57 | +<!-- 核心修复:用view包裹图标,添加双重@click.stop --> | ||
| 58 | +<view v-if="inspectForm.expectedFinishDate" @click.stop> | ||
| 59 | +<up-icon name="close" size="16" @click.stop="clearExpectedFinishDate"></up-icon> | ||
| 60 | +</view> | ||
| 61 | +<up-icon name="arrow-right" size="16" v-else></up-icon> | ||
| 62 | +</template> | ||
| 63 | +</up-form-item> | ||
| 64 | +</up-form> | ||
| 65 | +</view> | ||
| 66 | +<!-- 底部提交按钮 --> | ||
| 67 | +<view class="fixed-bottom-btn-wrap"> | ||
| 68 | +<up-button type="primary" text="提交" @click="submitInspect" | ||
| 69 | + :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }"></up-button> | ||
| 70 | +</view> | ||
| 71 | +<!-- 紧急程度下拉弹窗 --> | ||
| 72 | +<up-action-sheet :show="showActionSheet" :actions="pressingTypeList" title="请选择紧急程度" @close="handleActionSheetClose" | ||
| 73 | + @select="handlePressingTypeSelect"></up-action-sheet> | ||
| 74 | +<!-- ========== 新增级联选择器组件 ========== --> | ||
| 75 | +<up-cascader v-model:show="showCascader" v-model="cascaderValue" :data="cascaderOptions" title="选择问题类型" | ||
| 76 | + :auto-close="false" @confirm="cascaderConfirm"></up-cascader> | ||
| 77 | +<!-- 完成时间选择器 --> | ||
| 78 | +<up-datetime-picker :show="show" v-model="datetimeValue" mode="datetime" :min-date="new Date()" | ||
| 79 | + @cancel="show = false" @confirm="expectedFinishDateConfirm"></up-datetime-picker> | ||
| 80 | +</view> | ||
| 162 | </template> | 81 | </template> |
| 163 | - | ||
| 164 | <script setup lang="ts"> | 82 | <script setup lang="ts"> |
| 165 | -import { ref } from 'vue' | 83 | +import { ref, getCurrentInstance } from 'vue' |
| 166 | import type { UniFormRef } from '@/uni_modules/uview-plus/types' | 84 | import type { UniFormRef } from '@/uni_modules/uview-plus/types' |
| 85 | +import { useUserStore } from '@/pinia/user'; | ||
| 86 | +import { getDictLabel, getDictValue } from '@/common/utils/dict'; | ||
| 87 | +import { getProblemListByType } from '@/api/common' | ||
| 88 | +const { proxy } = getCurrentInstance()! | ||
| 167 | const inspectFormRef = ref<UniFormRef>(null) | 89 | const inspectFormRef = ref<UniFormRef>(null) |
| 168 | -</script> | 90 | +const userStore = useUserStore(); |
| 91 | +const BUSI_LINE_DICT_TYPE = 'business_line'; | ||
| 92 | +const CJK_RE = /[一-龥]/; | ||
| 93 | + | ||
| 94 | +// 级联选择器变量 | ||
| 95 | +const showCascader = ref(false); | ||
| 96 | +const cascaderValue = ref([]); | ||
| 97 | +const cascaderOptions = ref([]); | ||
| 98 | + | ||
| 99 | +// 打开级联弹窗 | ||
| 100 | +const openCascader = () => { | ||
| 101 | +const busiLineEn = getBusiLineEnByCn(proxy.$data.inspectForm.busiLineCn) | ||
| 102 | +if (!busiLineEn) { | ||
| 103 | +uni.showToast({ title: '请先选择业务线', icon: 'none' }) | ||
| 104 | +return | ||
| 105 | + } | ||
| 106 | +if (!cascaderOptions.value || cascaderOptions.value.length === 0) { | ||
| 107 | +uni.showToast({ title: '正在加载问题类型,请稍等', icon: 'none' }); | ||
| 108 | +loadProblemCascader(busiLineEn); | ||
| 109 | +return; | ||
| 110 | + } | ||
| 111 | +cascaderValue.value = []; | ||
| 112 | +showCascader.value = true; | ||
| 113 | +}; | ||
| 114 | + | ||
| 115 | +// 级联确认回调 | ||
| 116 | +const cascaderConfirm = (selectedArr) => { | ||
| 117 | +const getLabelByValueList = (list, valList) => { | ||
| 118 | +const labelArr = []; | ||
| 119 | +let currentList = list; | ||
| 120 | +for (const val of valList) { | ||
| 121 | +const target = currentList.find(item => item.value === val); | ||
| 122 | +if (!target) break; | ||
| 123 | +labelArr.push(target.label); | ||
| 124 | +currentList = target.children || []; | ||
| 125 | + } | ||
| 126 | +return labelArr.join('/'); | ||
| 127 | + }; | ||
| 128 | +const fullLabel = getLabelByValueList(cascaderOptions.value, selectedArr); | ||
| 129 | +proxy.$data.inspectForm.orderName = fullLabel; | ||
| 130 | +proxy.$data.inspectForm.orderCode = selectedArr.at(-1); | ||
| 131 | +cascaderValue.value = selectedArr; | ||
| 132 | +inspectFormRef.value?.validateField('orderName'); | ||
| 133 | +}; | ||
| 134 | + | ||
| 135 | +// 加载级联树形数据 | ||
| 136 | +const loadProblemCascader = async (parentCode) => { | ||
| 137 | +if (!parentCode) { | ||
| 138 | +cascaderOptions.value = [] | ||
| 139 | +return | ||
| 140 | + } | ||
| 141 | +try { | ||
| 142 | +uni.showLoading({ title: '加载类型中...' }) | ||
| 143 | +const res = await getProblemListByType({ parentCode }) | ||
| 144 | +uni.hideLoading() | ||
| 145 | +cascaderOptions.value = res || [] | ||
| 146 | + } catch (err) { | ||
| 147 | +uni.hideLoading() | ||
| 148 | +console.error('获取问题类型失败', err) | ||
| 149 | +uni.showToast({ title: '获取问题类型失败', icon: 'none' }) | ||
| 150 | +cascaderOptions.value = [] | ||
| 151 | + } | ||
| 152 | +} | ||
| 169 | 153 | ||
| 154 | +const getBusiLineCnByEn = (busiLineEn) => { | ||
| 155 | +if (!busiLineEn) return ''; | ||
| 156 | +return getDictLabel(BUSI_LINE_DICT_TYPE, busiLineEn) || busiLineEn; | ||
| 157 | +}; | ||
| 158 | +const getBusiLineEnByCn = (cnName) => { | ||
| 159 | +if (!cnName) return ''; | ||
| 160 | +const code = getDictValue(BUSI_LINE_DICT_TYPE, cnName); | ||
| 161 | +if (code) return code; | ||
| 162 | +return CJK_RE.test(cnName) ? '' : cnName; | ||
| 163 | +}; | ||
| 164 | + | ||
| 165 | +// 暴露给选项式访问 | ||
| 166 | +proxy.$setupCtx = { | ||
| 167 | + loadProblemCascader, | ||
| 168 | + getBusiLineEnByCn, | ||
| 169 | + cascaderOptions, | ||
| 170 | + cascaderValue | ||
| 171 | +} | ||
| 172 | +</script> | ||
| 170 | <script lang="ts"> | 173 | <script lang="ts"> |
| 171 | import { uploadImages } from '@/common/utils/upload'; | 174 | import { uploadImages } from '@/common/utils/upload'; |
| 172 | -import { inspectionCreate } from "@/api/patrol-manage/patrol-plan"; | 175 | +import { inspectionCreate } from "@/api/patrol-manage/patrol-plan"; |
| 173 | import { timeFormat } from '@/uni_modules/uview-plus' | 176 | import { timeFormat } from '@/uni_modules/uview-plus' |
| 174 | - | 177 | +import { useUserStore } from '@/pinia/user'; |
| 178 | +import { getDictLabel } from '@/common/utils/dict'; | ||
| 175 | export default { | 179 | export default { |
| 176 | - data() { | ||
| 177 | - return { | ||
| 178 | - imagesList: [], | ||
| 179 | - radioList: [ | 180 | +data() { |
| 181 | +return { | ||
| 182 | +imagesList: [], | ||
| 183 | +radioList: [ | ||
| 180 | { label: '否', value: '1' }, | 184 | { label: '否', value: '1' }, |
| 181 | { label: '是', value: '2' } | 185 | { label: '是', value: '2' } |
| 182 | ], | 186 | ], |
| 183 | - pressingTypeList: [], | ||
| 184 | - inspectForm: { | ||
| 185 | - content: '', | ||
| 186 | - isWorkOrder: '1', | ||
| 187 | - pressingTypeName: '', | ||
| 188 | - pressingType: '', | ||
| 189 | - expectedFinishDate: '' // 非必填,可空 | 187 | +pressingTypeList: [], |
| 188 | +busiLineOptions: [], // 业务线移到data,不再放在setup | ||
| 189 | +inspectForm: { | ||
| 190 | +content: '', | ||
| 191 | +isWorkOrder: '1', | ||
| 192 | +pressingTypeName: '', | ||
| 193 | +pressingType: '', | ||
| 194 | +busiLineCn: '', | ||
| 195 | +orderName: '', | ||
| 196 | +orderCode: '', | ||
| 197 | +expectedFinishDate: '' // 非必填,可空 | ||
| 190 | }, | 198 | }, |
| 191 | - paramsOptins: {}, | ||
| 192 | - showActionSheet: false, | ||
| 193 | - show: false, | ||
| 194 | - datetimeValue: Date.now(), | ||
| 195 | - inspectFormRules: { | ||
| 196 | - images: [ | 199 | +paramsOptins: {}, |
| 200 | +showActionSheet: false, | ||
| 201 | +show: false, | ||
| 202 | +datetimeValue: Date.now(), | ||
| 203 | +inspectFormRules: { | ||
| 204 | +images: [ | ||
| 197 | { | 205 | { |
| 198 | - required: true, | ||
| 199 | - message: '请上传图片', | ||
| 200 | - trigger: 'change', | ||
| 201 | - validator: (rule, value, callback) => { | ||
| 202 | - const hasSuccessImg = this.imagesList.some(item => item.status === 'success') | ||
| 203 | - const imgCount = this.imagesList.filter(item => item.status === 'success').length | ||
| 204 | - | ||
| 205 | - if (!hasSuccessImg || imgCount < 1) { | ||
| 206 | - callback(new Error('最少需要上传1张图片')) | 206 | +required: true, |
| 207 | +message: '请上传图片', | ||
| 208 | +trigger: 'change', | ||
| 209 | +validator: (rule, value, callback) => { | ||
| 210 | +const hasSuccessImg = this.imagesList.some(item => item.status === 'success') | ||
| 211 | +const imgCount = this.imagesList.filter(item => item.status === 'success').length | ||
| 212 | +if (!hasSuccessImg || imgCount < 1) { | ||
| 213 | +callback(new Error('最少需要上传1张图片')) | ||
| 207 | } else if (imgCount > 3) { | 214 | } else if (imgCount > 3) { |
| 208 | - callback(new Error('最多只能上传3张图片')) | 215 | +callback(new Error('最多只能上传3张图片')) |
| 209 | } else { | 216 | } else { |
| 210 | - callback() | 217 | +callback() |
| 211 | } | 218 | } |
| 212 | } | 219 | } |
| 213 | } | 220 | } |
| 214 | ], | 221 | ], |
| 215 | - content: [ | ||
| 216 | - { type: 'string', required: true, message: '请输入工单描述', trigger: ['change'] } | 222 | +content: [ |
| 223 | + { type: 'string', required: true, message: '请输入巡查描述', trigger: ['change'] } | ||
| 217 | ], | 224 | ], |
| 218 | - isWorkOrder: [ | 225 | +isWorkOrder: [ |
| 219 | { type: 'string', required: true, message: '请选择是否转为工单', trigger: ['change'] } | 226 | { type: 'string', required: true, message: '请选择是否转为工单', trigger: ['change'] } |
| 220 | ], | 227 | ], |
| 221 | - pressingTypeName: [ | 228 | +pressingTypeName: [ |
| 229 | + { | ||
| 230 | +required: true, | ||
| 231 | +message: '请选择紧急程度', | ||
| 232 | +trigger: 'change', | ||
| 233 | +validator: (rule, value, callback) => { | ||
| 234 | +if (this.inspectForm.isWorkOrder === '2' && !value) { | ||
| 235 | +callback(new Error('请选择紧急程度')) | ||
| 236 | + } else { | ||
| 237 | +callback() | ||
| 238 | + } | ||
| 239 | + } | ||
| 240 | + } | ||
| 241 | + ], | ||
| 242 | +busiLineCn: [ | ||
| 243 | + { | ||
| 244 | +required: true, | ||
| 245 | +message: '请选择业务线', | ||
| 246 | +trigger: 'change', | ||
| 247 | +validator: (rule, value, callback) => { | ||
| 248 | +if (this.inspectForm.isWorkOrder === '2' && !value) { | ||
| 249 | +callback(new Error('请选择业务线')) | ||
| 250 | + } else { | ||
| 251 | +callback() | ||
| 252 | + } | ||
| 253 | + } | ||
| 254 | + } | ||
| 255 | + ], | ||
| 256 | +orderName: [ | ||
| 222 | { | 257 | { |
| 223 | - required: true, | ||
| 224 | - message: '请选择紧急程度', | ||
| 225 | - trigger: 'change', | ||
| 226 | - validator: (rule, value, callback) => { | ||
| 227 | - if (this.inspectForm.isWorkOrder === '2' && !value) { | ||
| 228 | - callback(new Error('请选择紧急程度')) | 258 | +required: true, |
| 259 | +message: '请选择问题类型', | ||
| 260 | +trigger: 'change', | ||
| 261 | +validator: (rule, value, callback) => { | ||
| 262 | +if (this.inspectForm.isWorkOrder === '2' && !value) { | ||
| 263 | +callback(new Error('请选择问题类型')) | ||
| 229 | } else { | 264 | } else { |
| 230 | - callback() | 265 | +callback() |
| 231 | } | 266 | } |
| 232 | } | 267 | } |
| 233 | } | 268 | } |
| 234 | ], | 269 | ], |
| 235 | - // 关键修改:希望完成时间 移除必填校验,或改为非必填 | ||
| 236 | - expectedFinishDate: [ | 270 | +expectedFinishDate: [ |
| 237 | { | 271 | { |
| 238 | - // 移除 required: true,改为非必填 | ||
| 239 | - message: '请选择有效的希望完成时间', // 仅当有值时,校验格式(可选) | ||
| 240 | - trigger: 'change', | ||
| 241 | - validator: (rule, value, callback) => { | ||
| 242 | - // 只有当字段有值时,才校验时间格式是否有效 | ||
| 243 | - if (value && !/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$/.test(value)) { | ||
| 244 | - callback(new Error('请选择有效的希望完成时间')) | 272 | +message: '请选择有效的希望完成时间', |
| 273 | +trigger: 'change', | ||
| 274 | +validator: (rule, value, callback) => { | ||
| 275 | +if (value && !/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$/.test(value)) { | ||
| 276 | +callback(new Error('请选择有效的希望完成时间')) | ||
| 245 | } else { | 277 | } else { |
| 246 | - callback() // 无值时直接通过校验 | 278 | +callback() |
| 247 | } | 279 | } |
| 248 | } | 280 | } |
| 249 | } | 281 | } |
| @@ -251,185 +283,183 @@ export default { | @@ -251,185 +283,183 @@ export default { | ||
| 251 | } | 283 | } |
| 252 | } | 284 | } |
| 253 | }, | 285 | }, |
| 254 | - onLoad(option){ | ||
| 255 | - this.paramsOptins = option | ||
| 256 | - this.getPressingTypeDict() | 286 | +onLoad(option) { |
| 287 | +this.paramsOptins = option | ||
| 288 | +// 初始化业务线选项 | ||
| 289 | +const userStore = useUserStore(); | ||
| 290 | +if (userStore.userInfo?.user?.busiLine) { | ||
| 291 | +const rawBusiLines = userStore.userInfo.user.busiLine.split(','); | ||
| 292 | +this.busiLineOptions = rawBusiLines.map(item => ({ | ||
| 293 | +name: getDictLabel('business_line', item.trim()) || item.trim() | ||
| 294 | +})); | ||
| 295 | +} | ||
| 257 | }, | 296 | }, |
| 258 | - onReady() { | ||
| 259 | - this.$refs.inspectFormRef.setRules(this.inspectFormRules) | 297 | +onReady() { |
| 298 | +this.$refs.inspectFormRef.setRules(this.inspectFormRules) | ||
| 260 | }, | 299 | }, |
| 261 | - methods: { | ||
| 262 | - getPressingTypeDict() { | ||
| 263 | - const dictList = uni.$dict.getDictSimpleList('workorder_pressing_type') | ||
| 264 | - this.pressingTypeList = uni.$dict.transformLabelValueToNameValue(dictList) | ||
| 265 | - console.log(this.pressingTypeList) | ||
| 266 | - }, | ||
| 267 | - | ||
| 268 | - handleWorkOrderChange() { | ||
| 269 | - if (this.inspectForm.isWorkOrder === '1') { | ||
| 270 | - this.inspectForm.pressingTypeName = '' | ||
| 271 | - this.inspectForm.pressingType = '' | ||
| 272 | - this.inspectForm.expectedFinishDate = '' // 清空非必填字段 | ||
| 273 | - this.$refs.inspectFormRef.validateField(['pressingTypeName', 'expectedFinishDate']) | 300 | +onShow() { |
| 301 | +this.pressingTypeList = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('workorder_pressing_type')) | ||
| 302 | + }, | ||
| 303 | +methods: { | ||
| 304 | +handleWorkOrderChange() { | ||
| 305 | +if (this.inspectForm.isWorkOrder === '1') { | ||
| 306 | +this.inspectForm.pressingTypeName = '' | ||
| 307 | +this.inspectForm.pressingType = '' | ||
| 308 | +this.inspectForm.busiLineCn = '' | ||
| 309 | +this.inspectForm.orderName = '' | ||
| 310 | +this.inspectForm.orderCode = '' | ||
| 311 | +this.$setupCtx.cascaderOptions.value = [] | ||
| 312 | +this.$setupCtx.cascaderValue.value = [] | ||
| 313 | +this.inspectForm.expectedFinishDate = '' | ||
| 314 | +this.$refs.inspectFormRef.validateField(['pressingTypeName', 'busiLineCn', 'orderName', 'expectedFinishDate']) | ||
| 315 | + } else { | ||
| 316 | +// 修复点:直接读取data中的 busiLineOptions,不再访问setup | ||
| 317 | +const busiOpts = this.busiLineOptions | ||
| 318 | +if (busiOpts.length > 0) { | ||
| 319 | +this.inspectForm.busiLineCn = busiOpts[0].name | ||
| 320 | +this.handleBusiLineChange() | ||
| 321 | + } | ||
| 274 | } | 322 | } |
| 275 | }, | 323 | }, |
| 276 | - | ||
| 277 | - | ||
| 278 | - handleActionSheetOpen(type) { | ||
| 279 | - uni.hideKeyboard() | ||
| 280 | - console.log('showActionSheet') | ||
| 281 | - if (type === 'pressingType') { | ||
| 282 | - console.log('showActionSheet') | ||
| 283 | - this.showActionSheet = true | 324 | +handleBusiLineChange() { |
| 325 | +this.inspectForm.orderName = '' | ||
| 326 | +this.inspectForm.orderCode = '' | ||
| 327 | +this.$setupCtx.cascaderValue.value = [] | ||
| 328 | +this.$setupCtx.cascaderOptions.value = [] | ||
| 329 | +const busiLineEn = this.$setupCtx.getBusiLineEnByCn(this.inspectForm.busiLineCn) | ||
| 330 | +this.$setupCtx.loadProblemCascader(busiLineEn) | ||
| 331 | +this.$refs.inspectFormRef.validateField('busiLineCn') | ||
| 332 | + }, | ||
| 333 | +handleActionSheetOpen(type) { | ||
| 334 | +uni.hideKeyboard() | ||
| 335 | +if (type === 'pressingType') { | ||
| 336 | +this.showActionSheet = true | ||
| 284 | } | 337 | } |
| 285 | - uni.hideKeyboard() | ||
| 286 | }, | 338 | }, |
| 287 | - | ||
| 288 | - handleActionSheetClose() { | ||
| 289 | - this.showActionSheet = false | 339 | +handleActionSheetClose() { |
| 340 | +this.showActionSheet = false | ||
| 290 | }, | 341 | }, |
| 291 | - | ||
| 292 | - handlePressingTypeSelect(e) { | ||
| 293 | - console.log(e) | ||
| 294 | - this.inspectForm.pressingTypeName = e.name | ||
| 295 | - this.inspectForm.pressingType = e.value | ||
| 296 | - this.showActionSheet = false | ||
| 297 | - this.$refs.inspectFormRef.validateField('pressingTypeName') | 342 | +handlePressingTypeSelect(e) { |
| 343 | +this.inspectForm.pressingTypeName = e.name | ||
| 344 | +this.inspectForm.pressingType = e.value | ||
| 345 | +this.showActionSheet = false | ||
| 346 | +this.$refs.inspectFormRef.validateField('pressingTypeName') | ||
| 298 | }, | 347 | }, |
| 299 | - | ||
| 300 | - expectedFinishDateConfirm(e) { | ||
| 301 | - this.inspectForm.expectedFinishDate = timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss') | ||
| 302 | - this.show = false | ||
| 303 | - this.$refs.inspectFormRef.validateField('expectedFinishDate') | 348 | +expectedFinishDateConfirm(e) { |
| 349 | +this.inspectForm.expectedFinishDate = timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss') | ||
| 350 | +this.show = false | ||
| 351 | +this.$refs.inspectFormRef.validateField('expectedFinishDate') | ||
| 304 | }, | 352 | }, |
| 305 | - // 新增:清除希望完成时间的方法 | ||
| 306 | - clearExpectedFinishDate() { | ||
| 307 | - this.inspectForm.expectedFinishDate = '' | ||
| 308 | - // 清除后触发一次字段校验,确保校验状态更新 | ||
| 309 | - this.$refs.inspectFormRef.validateField('expectedFinishDate') | 353 | +clearExpectedFinishDate() { |
| 354 | +this.inspectForm.expectedFinishDate = '' | ||
| 355 | +this.$refs.inspectFormRef.validateField('expectedFinishDate') | ||
| 310 | }, | 356 | }, |
| 311 | - | ||
| 312 | - deleteImg(event) { | ||
| 313 | - this.imagesList.splice(event.index, 1) | ||
| 314 | - this.$refs.inspectFormRef.validateField('images') | ||
| 315 | - uni.showToast({ title: '图片删除成功', icon: 'success' }) | 357 | +deleteImg(event) { |
| 358 | +this.imagesList.splice(event.index, 1) | ||
| 359 | +this.$refs.inspectFormRef.validateField('images') | ||
| 360 | +uni.showToast({ title: '图片删除成功', icon: 'success' }) | ||
| 316 | }, | 361 | }, |
| 317 | - | ||
| 318 | - async uploadImgs(event) { | ||
| 319 | - const fileList = Array.isArray(event.file) ? event.file : [event.file] | ||
| 320 | - const targetImgList = this.imagesList | ||
| 321 | - | ||
| 322 | - const filePaths = fileList.map(item => item.url) | ||
| 323 | - const tempItems = fileList.map(item => ({ | ||
| 324 | - ...item, | ||
| 325 | - status: 'uploading', | ||
| 326 | - message: '上传中' | 362 | +async uploadImgs(event) { |
| 363 | +const fileList = Array.isArray(event.file) ? event.file : [event.file] | ||
| 364 | +const targetImgList = this.imagesList | ||
| 365 | +const filePaths = fileList.map(item => item.url) | ||
| 366 | +const tempItems = fileList.map(item => ({ | ||
| 367 | +...item, | ||
| 368 | +status: 'uploading', | ||
| 369 | +message: '上传中' | ||
| 327 | })) | 370 | })) |
| 328 | - const startIndex = targetImgList.length | ||
| 329 | - targetImgList.push(...tempItems) | ||
| 330 | - | ||
| 331 | - try { | ||
| 332 | - const uploadResultUrls = await uploadImages({ | ||
| 333 | - filePaths: filePaths, | ||
| 334 | - ignoreError: true | 371 | +const startIndex = targetImgList.length |
| 372 | +targetImgList.push(...tempItems) | ||
| 373 | +try { | ||
| 374 | +const uploadResultUrls = await uploadImages({ | ||
| 375 | +filePaths: filePaths, | ||
| 376 | +ignoreError: true | ||
| 335 | }) | 377 | }) |
| 336 | - | ||
| 337 | - uploadResultUrls.forEach((url, index) => { | ||
| 338 | - if (targetImgList[startIndex + index]) { | ||
| 339 | - targetImgList.splice(startIndex + index, 1, { | ||
| 340 | - ...fileList[index], | ||
| 341 | - status: 'success', | ||
| 342 | - message: '', | ||
| 343 | - url: url | 378 | +uploadResultUrls.forEach((url, index) => { |
| 379 | +if (targetImgList[startIndex + index]) { | ||
| 380 | +targetImgList.splice(startIndex + index, 1, { | ||
| 381 | +...fileList[index], | ||
| 382 | +status: 'success', | ||
| 383 | +message: '', | ||
| 384 | +url: url | ||
| 344 | }) | 385 | }) |
| 345 | } | 386 | } |
| 346 | }) | 387 | }) |
| 347 | - | ||
| 348 | - if (uploadResultUrls.length < fileList.length) { | ||
| 349 | - const failCount = fileList.length - uploadResultUrls.length | ||
| 350 | - uni.showToast({ title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none' }) | 388 | +if (uploadResultUrls.length < fileList.length) { |
| 389 | +const failCount = fileList.length - uploadResultUrls.length | ||
| 390 | +uni.showToast({ title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none' }) | ||
| 351 | } else { | 391 | } else { |
| 352 | - uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' }) | 392 | +uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' }) |
| 353 | } | 393 | } |
| 354 | - | ||
| 355 | - this.$refs.inspectFormRef.validateField('images') | 394 | +this.$refs.inspectFormRef.validateField('images') |
| 356 | } catch (err) { | 395 | } catch (err) { |
| 357 | - console.error('图片上传失败:', err) | ||
| 358 | - for (let i = 0; i < fileList.length; i++) { | ||
| 359 | - if (targetImgList[startIndex + i]) { | ||
| 360 | - targetImgList.splice(startIndex + i, 1, { | ||
| 361 | - ...fileList[i], | ||
| 362 | - status: 'failed', | ||
| 363 | - message: '上传失败' | 396 | +console.error('图片上传失败:', err) |
| 397 | +for (let i = 0; i < fileList.length; i++) { | ||
| 398 | +if (targetImgList[startIndex + i]) { | ||
| 399 | +targetImgList.splice(startIndex + i, 1, { | ||
| 400 | +...fileList[i], | ||
| 401 | +status: 'failed', | ||
| 402 | +message: '上传失败' | ||
| 364 | }) | 403 | }) |
| 365 | } | 404 | } |
| 366 | } | 405 | } |
| 367 | - uni.showToast({ title: '图片上传失败,请重试', icon: 'none' }) | ||
| 368 | - this.$refs.inspectFormRef.validateField('images') | 406 | +uni.showToast({ title: '图片上传失败,请重试', icon: 'none' }) |
| 407 | +this.$refs.inspectFormRef.validateField('images') | ||
| 369 | } | 408 | } |
| 370 | }, | 409 | }, |
| 371 | - | ||
| 372 | - handleExceed() { | ||
| 373 | - uni.showToast({ title: '最多只能上传3张图片', icon: 'none' }) | 410 | +handleExceed() { |
| 411 | +uni.showToast({ title: '最多只能上传3张图片', icon: 'none' }) | ||
| 374 | }, | 412 | }, |
| 375 | - | ||
| 376 | - getImgUrlList(imgList) { | ||
| 377 | - return imgList.filter(item => item.status === 'success').map(item => item.url) | 413 | +getImgUrlList(imgList) { |
| 414 | +return imgList.filter(item => item.status === 'success').map(item => item.url) | ||
| 378 | }, | 415 | }, |
| 379 | - | ||
| 380 | - async submitInspect() { | ||
| 381 | - console.log('当前选择是否转为工单:', this.inspectForm.isWorkOrder) | ||
| 382 | - try { | ||
| 383 | - await this.$refs.inspectFormRef.validate() | ||
| 384 | - console.log(this.imagesList) | ||
| 385 | - | ||
| 386 | - // 构造基础提交数据 | ||
| 387 | - const baseSubmitData = { | ||
| 388 | - "batchNo": this.paramsOptins.batchNo, | ||
| 389 | - "planNo": this.paramsOptins.planNo, | ||
| 390 | - "imgHost": "1", | ||
| 391 | - "imgList": this.getImgUrlList(this.imagesList), | ||
| 392 | - "inspectionState": this.inspectForm.isWorkOrder, | ||
| 393 | - "transState": this.inspectForm.isWorkOrder === '1' ? '1' : '2', | ||
| 394 | - "transWorkNo": "default'", | ||
| 395 | - "remark": this.inspectForm.content.trim() | 416 | +async submitInspect() { |
| 417 | +console.log('当前选择是否转为工单:', this.inspectForm.isWorkOrder) | ||
| 418 | +try { | ||
| 419 | +await this.$refs.inspectFormRef.validate() | ||
| 420 | +console.log(this.imagesList) | ||
| 421 | +const baseSubmitData = { | ||
| 422 | +"batchNo": this.paramsOptins.batchNo, | ||
| 423 | +"planNo": this.paramsOptins.planNo, | ||
| 424 | +"imgHost": "1", | ||
| 425 | +"imgList": this.getImgUrlList(this.imagesList), | ||
| 426 | +"inspectionState": this.inspectForm.isWorkOrder, | ||
| 427 | +"transState": this.inspectForm.isWorkOrder === '1' ? '1' : '2', | ||
| 428 | +"transWorkNo": "default'", | ||
| 429 | +"remark": this.inspectForm.content.trim() | ||
| 396 | } | 430 | } |
| 397 | - | ||
| 398 | - // 构造最终提交数据 | ||
| 399 | - let submitData = { ...baseSubmitData } | ||
| 400 | - if (this.inspectForm.isWorkOrder === '2') { | ||
| 401 | - // 紧急程度仍为必填,必传 | ||
| 402 | - submitData.pressingType = this.inspectForm.pressingType | ||
| 403 | - // 关键修改:仅当希望完成时间有值时,才添加该参数 | ||
| 404 | - if (this.inspectForm.expectedFinishDate) { | ||
| 405 | - submitData.expectedFinishDate = new Date(this.inspectForm.expectedFinishDate).getTime() | 431 | +let submitData = { ...baseSubmitData } |
| 432 | +if (this.inspectForm.isWorkOrder === '2') { | ||
| 433 | +submitData.pressingType = this.inspectForm.pressingType | ||
| 434 | +const busiLineEn = this.$setupCtx.getBusiLineEnByCn(this.inspectForm.busiLineCn) | ||
| 435 | +submitData.busiLine = busiLineEn | ||
| 436 | +submitData.orderName = this.inspectForm.orderName | ||
| 437 | +submitData.orderCode = this.inspectForm.orderCode | ||
| 438 | +if (this.inspectForm.expectedFinishDate) { | ||
| 439 | +submitData.expectedFinishDate = new Date(this.inspectForm.expectedFinishDate).getTime() | ||
| 406 | } | 440 | } |
| 407 | } | 441 | } |
| 408 | - | ||
| 409 | - uni.showLoading({ title: '提交中...' }) | ||
| 410 | - await inspectionCreate(submitData) | ||
| 411 | - | ||
| 412 | - uni.hideLoading() | ||
| 413 | - uni.showToast({ | ||
| 414 | - title: '提交成功', | ||
| 415 | - icon: 'success', | ||
| 416 | - duration: 1000 | 442 | +uni.showLoading({ title: '提交中...' }) |
| 443 | +await inspectionCreate(submitData) | ||
| 444 | +uni.hideLoading() | ||
| 445 | +uni.showToast({ | ||
| 446 | +title: '提交成功', | ||
| 447 | +icon: 'success', | ||
| 448 | +duration: 1000 | ||
| 417 | }) | 449 | }) |
| 418 | - | ||
| 419 | - setTimeout(() => { | ||
| 420 | - uni.reLaunch({ | ||
| 421 | - url: '/pages-sub/daily/patrol-manage/index' | 450 | +setTimeout(() => { |
| 451 | +uni.reLaunch({ | ||
| 452 | +url: '/pages-sub/daily/patrol-manage/index' | ||
| 422 | }) | 453 | }) |
| 423 | }, 1000) | 454 | }, 1000) |
| 424 | - | ||
| 425 | } catch (error) { | 455 | } catch (error) { |
| 426 | - uni.hideLoading() | ||
| 427 | - if (!Array.isArray(error)) { | ||
| 428 | - console.error('巡查表单提交失败:', error) | ||
| 429 | - uni.showToast({ | ||
| 430 | - title: '提交失败,请重试', | ||
| 431 | - icon: 'none', | ||
| 432 | - duration: 2000 | 456 | +uni.hideLoading() |
| 457 | +if (!Array.isArray(error)) { | ||
| 458 | +console.error('巡查表单提交失败:', error) | ||
| 459 | +uni.showToast({ | ||
| 460 | +title: '提交失败,请重试', | ||
| 461 | +icon: 'none', | ||
| 462 | +duration: 2000 | ||
| 433 | }) | 463 | }) |
| 434 | } | 464 | } |
| 435 | } | 465 | } |
| @@ -437,20 +467,15 @@ export default { | @@ -437,20 +467,15 @@ export default { | ||
| 437 | } | 467 | } |
| 438 | } | 468 | } |
| 439 | </script> | 469 | </script> |
| 440 | - | ||
| 441 | <style lang="scss" scoped> | 470 | <style lang="scss" scoped> |
| 442 | .u-page { | 471 | .u-page { |
| 443 | - min-height: 100vh; | 472 | +min-height: 100vh; |
| 444 | } | 473 | } |
| 445 | - | ||
| 446 | .inspect-form-content { | 474 | .inspect-form-content { |
| 447 | - background: #fff; | ||
| 448 | - padding: 20rpx; | 475 | +background: #fff; |
| 476 | +padding: 20rpx; | ||
| 449 | } | 477 | } |
| 450 | - | ||
| 451 | .form-item { | 478 | .form-item { |
| 452 | - margin-bottom: 20rpx; | 479 | +margin-bottom: 20rpx; |
| 453 | } | 480 | } |
| 454 | - | ||
| 455 | - | ||
| 456 | -</style> | ||
| 457 | \ No newline at end of file | 481 | \ No newline at end of file |
| 482 | +</style> |
pages-sub/problem/work-order-manage/add-order.vue
| @@ -212,7 +212,7 @@ const loadProblemCascader = async (parentCode) => { | @@ -212,7 +212,7 @@ const loadProblemCascader = async (parentCode) => { | ||
| 212 | // 表单Ref | 212 | // 表单Ref |
| 213 | const workOrderFormRef = ref(null) | 213 | const workOrderFormRef = ref(null) |
| 214 | const problemImgs = useUploadImgs({ | 214 | const problemImgs = useUploadImgs({ |
| 215 | - maxCount: 3, | 215 | + maxCount: 9, |
| 216 | uploadText: '选择问题照片', | 216 | uploadText: '选择问题照片', |
| 217 | sizeType: ['compressed'], | 217 | sizeType: ['compressed'], |
| 218 | formRef: workOrderFormRef, | 218 | formRef: workOrderFormRef, |
pages-sub/problem/work-order-manage/order-detail.vue
| @@ -116,6 +116,10 @@ | @@ -116,6 +116,10 @@ | ||
| 116 | </template> | 116 | </template> |
| 117 | </up-cell> | 117 | </up-cell> |
| 118 | 118 | ||
| 119 | + | ||
| 120 | + | ||
| 121 | + <up-cell title="重点巡检区域" :value="orderDetail.keyInspection==0?'否':'是'" align="middle"></up-cell> | ||
| 122 | + | ||
| 119 | <!-- 希望完成时间 --> | 123 | <!-- 希望完成时间 --> |
| 120 | <up-cell | 124 | <up-cell |
| 121 | v-if="orderDetail.expectedFinishDate!==0" | 125 | v-if="orderDetail.expectedFinishDate!==0" |
pages/index/index.vue
| @@ -24,26 +24,22 @@ | @@ -24,26 +24,22 @@ | ||
| 24 | 24 | ||
| 25 | <!-- 任务完成情况卡片 --> | 25 | <!-- 任务完成情况卡片 --> |
| 26 | <view class="task-chart-card"> | 26 | <view class="task-chart-card"> |
| 27 | - <view class="card-header"> | ||
| 28 | - <view class="unit-tip">数字指标</view> | ||
| 29 | - <!-- ActionSheet 点击唤起 --> | ||
| 30 | - <view class="sort-trigger" @click="openActionSheet"> | ||
| 31 | - {{ sortLabel }} | ||
| 32 | - <up-icon name="arrow-down" size="14" style="margin-left:4rpx;"></up-icon> | ||
| 33 | - </view> | ||
| 34 | - <!-- ActionSheet组件 --> | ||
| 35 | - <up-action-sheet | ||
| 36 | - :show="showActionSheet" | ||
| 37 | - :actions="actionSheetOptions" | ||
| 38 | - @select="handleSheetSelect" | ||
| 39 | - @close="showActionSheet = false" | ||
| 40 | - /> | ||
| 41 | - <view class="date-picker-wrap"> | ||
| 42 | - <neo-datetime-pro v-model="dateRange" type="daterange" :clearable="false" placeholder="请选择日期范围" | ||
| 43 | - @confirm="handleDateConfirm" :max-date="maxDate" @popup-show="handlePopupShow" | ||
| 44 | - @popup-hide="handlePopupHide" /> | ||
| 45 | - </view> | ||
| 46 | -</view> | 27 | + <view class="card-header"> |
| 28 | + <view class="unit-tip">数字指标</view> | ||
| 29 | + <!-- ActionSheet 点击唤起 --> | ||
| 30 | + <view class="sort-trigger" @click="openActionSheet"> | ||
| 31 | + {{ sortLabel }} | ||
| 32 | + <up-icon name="arrow-down" size="14" style="margin-left:4rpx;"></up-icon> | ||
| 33 | + </view> | ||
| 34 | + <!-- ActionSheet组件 --> | ||
| 35 | + <up-action-sheet :show="showActionSheet" :actions="actionSheetOptions" @select="handleSheetSelect" | ||
| 36 | + @close="showActionSheet = false" /> | ||
| 37 | + <view class="date-picker-wrap"> | ||
| 38 | + <neo-datetime-pro v-model="dateRange" type="daterange" :clearable="false" placeholder="请选择日期范围" | ||
| 39 | + @confirm="handleDateConfirm" :max-date="maxDate" @popup-show="handlePopupShow" | ||
| 40 | + @popup-hide="handlePopupHide" /> | ||
| 41 | + </view> | ||
| 42 | + </view> | ||
| 47 | 43 | ||
| 48 | 44 | ||
| 49 | <!-- 双折线K线图 --> | 45 | <!-- 双折线K线图 --> |
| @@ -52,23 +48,16 @@ | @@ -52,23 +48,16 @@ | ||
| 52 | :option="klineOption" :width="chartWidth" :height="chartHeight" /> | 48 | :option="klineOption" :width="chartWidth" :height="chartHeight" /> |
| 53 | </view> --> | 49 | </view> --> |
| 54 | 50 | ||
| 55 | - <up-grid | ||
| 56 | - :border="false" | ||
| 57 | - col="4" | ||
| 58 | - > | ||
| 59 | - <up-grid-item | ||
| 60 | - v-for="(baseListItem,baseListIndex) in baseList" | ||
| 61 | - :key="baseListIndex" | ||
| 62 | - > | ||
| 63 | - <!-- <up-icon | ||
| 64 | - :customStyle="{paddingTop:20+'rpx'}" | ||
| 65 | - :name="baseListItem.name" | ||
| 66 | - :size="22" | ||
| 67 | - ></up-icon> --> | ||
| 68 | - <text class="grid-text" style="font-size: 24px;">{{baseListItem.num}}</text> | ||
| 69 | - <text class="grid-text">{{baseListItem.title}}</text> | 51 | + <up-grid :border="false" col="4"> |
| 52 | + <up-grid-item v-for="(baseListItem, baseListIndex) in baseList" :key="baseListIndex"> | ||
| 53 | + <view class="grid-item-icon-box"> | ||
| 54 | + <up-icon :name="baseListItem.name" size="32" color="#1677ff"></up-icon> | ||
| 55 | + <text class="grid-num-icon">{{ baseListItem.num }}</text> | ||
| 56 | + <text class="grid-label-icon">{{ baseListItem.title }}</text> | ||
| 57 | + </view> | ||
| 70 | </up-grid-item> | 58 | </up-grid-item> |
| 71 | - </up-grid> | 59 | + </up-grid> |
| 60 | + | ||
| 72 | </view> | 61 | </view> |
| 73 | </view> | 62 | </view> |
| 74 | 63 | ||
| @@ -98,13 +87,8 @@ | @@ -98,13 +87,8 @@ | ||
| 98 | </view> | 87 | </view> |
| 99 | 88 | ||
| 100 | 89 | ||
| 101 | - <scroll-view | ||
| 102 | - class="task-list-container" | ||
| 103 | - scroll-y | ||
| 104 | - @scroll="onListScroll" | ||
| 105 | - @scrolltoupper="handleTouchTop" | ||
| 106 | - enhanced | ||
| 107 | - > | 90 | + <scroll-view class="task-list-container" scroll-y @scroll="onListScroll" @scrolltoupper="handleTouchTop" |
| 91 | + enhanced> | ||
| 108 | <view v-if="taskList.length === 0" class="empty-box"> | 92 | <view v-if="taskList.length === 0" class="empty-box"> |
| 109 | <up-empty /> | 93 | <up-empty /> |
| 110 | </view> | 94 | </view> |
| @@ -113,7 +97,7 @@ | @@ -113,7 +97,7 @@ | ||
| 113 | <view class="task-item__content"> | 97 | <view class="task-item__content"> |
| 114 | <view class="name-fixed-wrap"> | 98 | <view class="name-fixed-wrap"> |
| 115 | <view class="task-item__name "> | 99 | <view class="task-item__name "> |
| 116 | - 事项{{ index + 1 }} | 100 | + 事项{{ index + 1 }} |
| 117 | </view> | 101 | </view> |
| 118 | <view class="task-name-text"> | 102 | <view class="task-name-text"> |
| 119 | {{ item.taskName || '无' }} | 103 | {{ item.taskName || '无' }} |
| @@ -121,7 +105,8 @@ | @@ -121,7 +105,8 @@ | ||
| 121 | </view> | 105 | </view> |
| 122 | 106 | ||
| 123 | <view v-if="currentTabValue === 0" class="task-item__footer" style="padding-left: 60px;"> | 107 | <view v-if="currentTabValue === 0" class="task-item__footer" style="padding-left: 60px;"> |
| 124 | - <image class="loc-icon" src="https://img.jichengshanshui.com.cn:28207/appimg/定位icon@3x.png" mode="aspectFit"></image> | 108 | + <image class="loc-icon" src="https://img.jichengshanshui.com.cn:28207/appimg/定位icon@3x.png" |
| 109 | + mode="aspectFit"></image> | ||
| 125 | <template v-if="isWalkTextMode"> | 110 | <template v-if="isWalkTextMode"> |
| 126 | <text>距离当前{{ item.distance }}m ·约{{ item.estimatedWalkingMinutes }}分钟步行</text> | 111 | <text>距离当前{{ item.distance }}m ·约{{ item.estimatedWalkingMinutes }}分钟步行</text> |
| 127 | </template> | 112 | </template> |
| @@ -143,7 +128,7 @@ | @@ -143,7 +128,7 @@ | ||
| 143 | import { ref, computed, nextTick, watch } from 'vue'; | 128 | import { ref, computed, nextTick, watch } from 'vue'; |
| 144 | import { onShow, onUnload } from '@dcloudio/uni-app'; | 129 | import { onShow, onUnload } from '@dcloudio/uni-app'; |
| 145 | import { useUserStore } from '@/pinia/user'; | 130 | import { useUserStore } from '@/pinia/user'; |
| 146 | -// import { timeFormat } from '@/uni_modules/uview-plus/libs/function/date'; | 131 | +import { timeFormat } from '@/uni_modules/uview-plus'; |
| 147 | import uniCharts from '@/components/uni-charts/uni-charts.vue'; | 132 | import uniCharts from '@/components/uni-charts/uni-charts.vue'; |
| 148 | import { getUnreadCount } from "@/api/user"; | 133 | import { getUnreadCount } from "@/api/user"; |
| 149 | import { | 134 | import { |
| @@ -211,8 +196,8 @@ const currentTabValue = ref(0); | @@ -211,8 +196,8 @@ const currentTabValue = ref(0); | ||
| 211 | const dateRange = ref<string[]>([]); | 196 | const dateRange = ref<string[]>([]); |
| 212 | const yesterday = new Date(); | 197 | const yesterday = new Date(); |
| 213 | yesterday.setDate(yesterday.getDate() - 1); | 198 | yesterday.setDate(yesterday.getDate() - 1); |
| 214 | -const maxDate = ref(timeFormat(yesterday, 'YYYY-MM-DD')); | ||
| 215 | -const minDate = '2026-01-06'; | 199 | +const maxDate = ref(timeFormat(yesterday, 'yyyy-mm-dd')); |
| 200 | +// const minDate = '2026-01-06'; | ||
| 216 | const isDatePickerOpen = ref(false); | 201 | const isDatePickerOpen = ref(false); |
| 217 | const handlePopupShow = () => { | 202 | const handlePopupShow = () => { |
| 218 | isDatePickerOpen.value = true; | 203 | isDatePickerOpen.value = true; |
| @@ -365,23 +350,12 @@ const fetchOrgIndicator = async (orgId: number, beginDate: string, endDate: stri | @@ -365,23 +350,12 @@ const fetchOrgIndicator = async (orgId: number, beginDate: string, endDate: stri | ||
| 365 | }); | 350 | }); |
| 366 | console.log('orgTaskIndicator 返回:', res); | 351 | console.log('orgTaskIndicator 返回:', res); |
| 367 | baseList.value = [ | 352 | baseList.value = [ |
| 368 | - { | ||
| 369 | - num: String(res.totalCnt ?? 0), | ||
| 370 | - title: '应办任务数' | ||
| 371 | - }, | ||
| 372 | - { | ||
| 373 | - num: String(res.doneCnt ?? 0), | ||
| 374 | - title: '已办任务数' | ||
| 375 | - }, | ||
| 376 | - { | ||
| 377 | - num: String(res.pendingCnt ?? 0), | ||
| 378 | - title: '待办任务数' | ||
| 379 | - }, | ||
| 380 | - { | ||
| 381 | - num: String(res.overdueCnt ?? 0), | ||
| 382 | - title: '超时未完成' | ||
| 383 | - } | 353 | + { num: String(res.totalCnt ?? 0), title: '应办任务数', name: 'file-text' }, |
| 354 | + { num: String(res.doneCnt ?? 0), title: '已办任务数', name: 'checkmark-circle' }, | ||
| 355 | + { num: String(res.pendingCnt ?? 0), title: '待办任务数', name: 'clock' }, | ||
| 356 | + { num: String(res.overdueCnt ?? 0), title: '超时未完成', name: 'close-circle' } | ||
| 384 | ]; | 357 | ]; |
| 358 | + | ||
| 385 | } catch (err) { | 359 | } catch (err) { |
| 386 | console.error('orgTaskIndicator 请求失败:', err); | 360 | console.error('orgTaskIndicator 请求失败:', err); |
| 387 | } | 361 | } |
| @@ -393,11 +367,13 @@ const initRecent7Days = () => { | @@ -393,11 +367,13 @@ const initRecent7Days = () => { | ||
| 393 | const end = new Date(now); | 367 | const end = new Date(now); |
| 394 | end.setDate(end.getDate() - 1); | 368 | end.setDate(end.getDate() - 1); |
| 395 | const start = new Date(end); | 369 | const start = new Date(end); |
| 370 | + console.log(start) | ||
| 396 | start.setDate(start.getDate() - 6); | 371 | start.setDate(start.getDate() - 6); |
| 397 | dateRange.value = [ | 372 | dateRange.value = [ |
| 398 | - timeFormat(start, 'YYYY-MM-DD'), | ||
| 399 | - timeFormat(end, 'YYYY-MM-DD') | 373 | + timeFormat(start, 'yyyy-mm-dd'), |
| 374 | + timeFormat(end, 'yyyy-mm-dd') | ||
| 400 | ]; | 375 | ]; |
| 376 | + console.log(dateRange.value) | ||
| 401 | initChartData(dateRange.value[0], dateRange.value[1]); | 377 | initChartData(dateRange.value[0], dateRange.value[1]); |
| 402 | }; | 378 | }; |
| 403 | 379 | ||
| @@ -640,17 +616,21 @@ $border-color: #e5e5e5; | @@ -640,17 +616,21 @@ $border-color: #e5e5e5; | ||
| 640 | font-size: 16px; | 616 | font-size: 16px; |
| 641 | margin-bottom: 4px; | 617 | margin-bottom: 4px; |
| 642 | } | 618 | } |
| 619 | + | ||
| 643 | .login-desc { | 620 | .login-desc { |
| 644 | font-size: 16px; | 621 | font-size: 16px; |
| 645 | } | 622 | } |
| 623 | + | ||
| 646 | .msg-icon { | 624 | .msg-icon { |
| 647 | position: relative; | 625 | position: relative; |
| 648 | padding: 5px; | 626 | padding: 5px; |
| 649 | top: 10px; | 627 | top: 10px; |
| 650 | border-radius: 50%; | 628 | border-radius: 50%; |
| 629 | + | ||
| 651 | &--hover { | 630 | &--hover { |
| 652 | background-color: rgba(255, 255, 255, 0.2); | 631 | background-color: rgba(255, 255, 255, 0.2); |
| 653 | } | 632 | } |
| 633 | + | ||
| 654 | .msg-badge { | 634 | .msg-badge { |
| 655 | position: absolute; | 635 | position: absolute; |
| 656 | top: -2px; | 636 | top: -2px; |
| @@ -675,6 +655,7 @@ $border-color: #e5e5e5; | @@ -675,6 +655,7 @@ $border-color: #e5e5e5; | ||
| 675 | opacity: 1; | 655 | opacity: 1; |
| 676 | overflow: hidden; | 656 | overflow: hidden; |
| 677 | } | 657 | } |
| 658 | + | ||
| 678 | .chart-wrap[hidden] { | 659 | .chart-wrap[hidden] { |
| 679 | opacity: 0; | 660 | opacity: 0; |
| 680 | height: 0; | 661 | height: 0; |
| @@ -695,6 +676,7 @@ $border-color: #e5e5e5; | @@ -695,6 +676,7 @@ $border-color: #e5e5e5; | ||
| 695 | margin: 0 $spacing-lg $spacing-md; | 676 | margin: 0 $spacing-lg $spacing-md; |
| 696 | padding: $spacing-md; | 677 | padding: $spacing-md; |
| 697 | flex-shrink: 0; | 678 | flex-shrink: 0; |
| 679 | + | ||
| 698 | .card-header { | 680 | .card-header { |
| 699 | display: flex; | 681 | display: flex; |
| 700 | justify-content: space-between; | 682 | justify-content: space-between; |
| @@ -702,16 +684,19 @@ $border-color: #e5e5e5; | @@ -702,16 +684,19 @@ $border-color: #e5e5e5; | ||
| 702 | font-size: 12px; | 684 | font-size: 12px; |
| 703 | color: $text-color-placeholder; | 685 | color: $text-color-placeholder; |
| 704 | margin-bottom: $spacing-md; | 686 | margin-bottom: $spacing-md; |
| 687 | + | ||
| 705 | .date-picker-wrap { | 688 | .date-picker-wrap { |
| 706 | border-radius: 3px; | 689 | border-radius: 3px; |
| 707 | transition: background-color 0.2s; | 690 | transition: background-color 0.2s; |
| 708 | position: relative; | 691 | position: relative; |
| 709 | z-index: 999; | 692 | z-index: 999; |
| 693 | + | ||
| 710 | &:active { | 694 | &:active { |
| 711 | background-color: $bg-color; | 695 | background-color: $bg-color; |
| 712 | } | 696 | } |
| 713 | } | 697 | } |
| 714 | } | 698 | } |
| 699 | + | ||
| 715 | .chart-container { | 700 | .chart-container { |
| 716 | width: 100%; | 701 | width: 100%; |
| 717 | height: 150px; | 702 | height: 150px; |
| @@ -720,6 +705,7 @@ $border-color: #e5e5e5; | @@ -720,6 +705,7 @@ $border-color: #e5e5e5; | ||
| 720 | justify-content: center; | 705 | justify-content: center; |
| 721 | } | 706 | } |
| 722 | } | 707 | } |
| 708 | + | ||
| 723 | .con-wrap { | 709 | .con-wrap { |
| 724 | border-radius: 8px; | 710 | border-radius: 8px; |
| 725 | background-color: #fff; | 711 | background-color: #fff; |
| @@ -758,6 +744,7 @@ $border-color: #e5e5e5; | @@ -758,6 +744,7 @@ $border-color: #e5e5e5; | ||
| 758 | transition: all 0.2s ease; | 744 | transition: all 0.2s ease; |
| 759 | font-weight: 500; | 745 | font-weight: 500; |
| 760 | } | 746 | } |
| 747 | + | ||
| 761 | .tab-capsule.active { | 748 | .tab-capsule.active { |
| 762 | color: #d4af37; | 749 | color: #d4af37; |
| 763 | background: #fff8dc; | 750 | background: #fff8dc; |
| @@ -775,7 +762,7 @@ $border-color: #e5e5e5; | @@ -775,7 +762,7 @@ $border-color: #e5e5e5; | ||
| 775 | } | 762 | } |
| 776 | 763 | ||
| 777 | .tip-top { | 764 | .tip-top { |
| 778 | - padding: 8px 0 0 70px; | 765 | + padding: 8px 0 0 70px; |
| 779 | flex-shrink: 0; | 766 | flex-shrink: 0; |
| 780 | } | 767 | } |
| 781 | 768 | ||
| @@ -789,6 +776,7 @@ $border-color: #e5e5e5; | @@ -789,6 +776,7 @@ $border-color: #e5e5e5; | ||
| 789 | .empty-box { | 776 | .empty-box { |
| 790 | padding: 60rpx 0; | 777 | padding: 60rpx 0; |
| 791 | } | 778 | } |
| 779 | + | ||
| 792 | .sort-trigger { | 780 | .sort-trigger { |
| 793 | display: flex; | 781 | display: flex; |
| 794 | align-items: center; | 782 | align-items: center; |
| @@ -801,36 +789,44 @@ $border-color: #e5e5e5; | @@ -801,36 +789,44 @@ $border-color: #e5e5e5; | ||
| 801 | padding: $spacing-md; | 789 | padding: $spacing-md; |
| 802 | transition: background-color 0.2s; | 790 | transition: background-color 0.2s; |
| 803 | border-bottom: 1px solid $border-color; | 791 | border-bottom: 1px solid $border-color; |
| 792 | + | ||
| 804 | &:last-child { | 793 | &:last-child { |
| 805 | border-bottom: none; | 794 | border-bottom: none; |
| 806 | } | 795 | } |
| 796 | + | ||
| 807 | &--hover { | 797 | &--hover { |
| 808 | background-color: $bg-color; | 798 | background-color: $bg-color; |
| 809 | } | 799 | } |
| 800 | + | ||
| 810 | &__content { | 801 | &__content { |
| 811 | width: 100%; | 802 | width: 100%; |
| 812 | } | 803 | } |
| 804 | + | ||
| 813 | .name-fixed-wrap { | 805 | .name-fixed-wrap { |
| 814 | display: flex; | 806 | display: flex; |
| 815 | } | 807 | } |
| 808 | + | ||
| 816 | .task-item__name { | 809 | .task-item__name { |
| 817 | width: 60px; | 810 | width: 60px; |
| 818 | } | 811 | } |
| 819 | - .task-name-text { | ||
| 820 | - flex: 1; | ||
| 821 | - font-size: 14px; | ||
| 822 | - color: $text-color; | ||
| 823 | - display: -webkit-box; | ||
| 824 | - -webkit-line-clamp: 2; | ||
| 825 | - -webkit-box-orient: vertical; | ||
| 826 | - overflow: hidden; | ||
| 827 | - text-overflow: ellipsis; | ||
| 828 | -} | 812 | + |
| 813 | + .task-name-text { | ||
| 814 | + flex: 1; | ||
| 815 | + font-size: 14px; | ||
| 816 | + color: $text-color; | ||
| 817 | + display: -webkit-box; | ||
| 818 | + -webkit-line-clamp: 2; | ||
| 819 | + -webkit-box-orient: vertical; | ||
| 820 | + overflow: hidden; | ||
| 821 | + text-overflow: ellipsis; | ||
| 822 | + } | ||
| 823 | + | ||
| 829 | &__name { | 824 | &__name { |
| 830 | font-size: 15px; | 825 | font-size: 15px; |
| 831 | color: $text-color; | 826 | color: $text-color; |
| 832 | display: inline-block; | 827 | display: inline-block; |
| 833 | } | 828 | } |
| 829 | + | ||
| 834 | &__footer { | 830 | &__footer { |
| 835 | display: flex; | 831 | display: flex; |
| 836 | align-items: flex-start; | 832 | align-items: flex-start; |
| @@ -841,6 +837,7 @@ $border-color: #e5e5e5; | @@ -841,6 +837,7 @@ $border-color: #e5e5e5; | ||
| 841 | align-items: center; | 837 | align-items: center; |
| 842 | } | 838 | } |
| 843 | } | 839 | } |
| 840 | + | ||
| 844 | .loc-icon { | 841 | .loc-icon { |
| 845 | width: 20px; | 842 | width: 20px; |
| 846 | height: 20px; | 843 | height: 20px; |
| @@ -849,4 +846,23 @@ $border-color: #e5e5e5; | @@ -849,4 +846,23 @@ $border-color: #e5e5e5; | ||
| 849 | .urgency-tag { | 846 | .urgency-tag { |
| 850 | color: #7D7D7D; | 847 | color: #7D7D7D; |
| 851 | } | 848 | } |
| 849 | + | ||
| 850 | +.grid-item-icon-box { | ||
| 851 | + display:flex; | ||
| 852 | + flex-direction:column; | ||
| 853 | + align-items:center; | ||
| 854 | + padding:16rpx; | ||
| 855 | +} | ||
| 856 | +.grid-num-icon { | ||
| 857 | + font-size:40rpx; | ||
| 858 | + font-weight:600; | ||
| 859 | + color:#333; | ||
| 860 | + margin-top:8rpx; | ||
| 861 | +} | ||
| 862 | +.grid-label-icon { | ||
| 863 | + font-size:22rpx; | ||
| 864 | + color:#666; | ||
| 865 | + margin-top:4rpx; | ||
| 866 | +} | ||
| 867 | + | ||
| 852 | </style> | 868 | </style> |
| 853 | \ No newline at end of file | 869 | \ No newline at end of file |
uni_modules/uview-plus/components/u-cascader/u-cascader.vue
| 1 | <template> | 1 | <template> |
| 2 | - <u-popup | ||
| 3 | - :show="show" | ||
| 4 | - mode="bottom" | ||
| 5 | - :overlay="overlay" | ||
| 6 | - :duration="duration" | ||
| 7 | - :overlayStyle="overlayStyle" | ||
| 8 | - :overlayOpacity="overlayOpacity" | ||
| 9 | - :zIndex="zIndex" | ||
| 10 | - :safeAreaInsetBottom="safeAreaInsetBottom" | ||
| 11 | - :safeAreaInsetTop="safeAreaInsetTop" | ||
| 12 | - :bgColor="bgColor" | ||
| 13 | - :closeable="!pageInline" | 2 | + <up-popup |
| 3 | + :show="popupShow" | ||
| 4 | + mode="bottom" | ||
| 5 | + :popup="false" | ||
| 6 | + :mask="true" | ||
| 7 | + :closeable="closeable" | ||
| 8 | + :safe-area-inset-bottom="true" | ||
| 9 | + :close-icon-color="closeIconColor" | ||
| 10 | + :z-index="uZIndex" | ||
| 11 | + :maskCloseAble="maskCloseAble" | ||
| 14 | @close="close" | 12 | @close="close" |
| 15 | - @closed="$emit('closed')" | ||
| 16 | - :round="round" | ||
| 17 | - :pageInline="pageInline" | ||
| 18 | - :closeOnClickOverlay="closeOnClickOverlay" | ||
| 19 | > | 13 | > |
| 20 | - <view class="u-calendar"> | ||
| 21 | - <uHeader | ||
| 22 | - :title="title" | ||
| 23 | - :subtitle="subtitle" | ||
| 24 | - :showSubtitle="showSubtitle" | ||
| 25 | - :showTitle="showTitle" | ||
| 26 | - :weekText="weekText" | ||
| 27 | - :showSwitch="monthSwitch" | ||
| 28 | - :showToday="showToday" | ||
| 29 | - :todayText="todayText" | ||
| 30 | - :todayDisabled="todayDisabled" | ||
| 31 | - :prevDisabled="switchPrevDisabled" | ||
| 32 | - :nextDisabled="switchNextDisabled" | ||
| 33 | - :prevYearDisabled="switchPrevYearDisabled" | ||
| 34 | - :nextYearDisabled="switchNextYearDisabled" | ||
| 35 | - @prev="prevMonth" | ||
| 36 | - @next="nextMonth" | ||
| 37 | - @prevYear="prevYear" | ||
| 38 | - @nextYear="nextYear" | ||
| 39 | - @today="jumpToToday" | ||
| 40 | - ></uHeader> | ||
| 41 | - <view v-if="showTimePanel" class="u-calendar__time-panel"> | ||
| 42 | - <view v-if="mode === 'single'" class="u-calendar__time-row"> | ||
| 43 | - <text class="u-calendar__time-date">{{ singleDateLabel }}</text> | ||
| 44 | - <view class="u-calendar__time-trigger" @click="openTimePicker('single')"> | ||
| 45 | - <text class="u-calendar__time-text">{{ singleTime }}</text> | ||
| 46 | - </view> | ||
| 47 | - </view> | ||
| 48 | - <view v-else-if="mode === 'range' && rangeResultMode === 'boundary'"> | ||
| 49 | - <view class="u-calendar__time-row"> | ||
| 50 | - <text class="u-calendar__time-date">{{ rangeStartDateLabel }}</text> | ||
| 51 | - <view class="u-calendar__time-trigger" @click="openTimePicker('start')"> | ||
| 52 | - <text class="u-calendar__time-text">{{ rangeStartTime }}</text> | ||
| 53 | - </view> | ||
| 54 | - </view> | ||
| 55 | - <view class="u-calendar__time-row"> | ||
| 56 | - <text class="u-calendar__time-date">{{ rangeEndDateLabel }}</text> | ||
| 57 | - <view class="u-calendar__time-trigger" @click="openTimePicker('end')"> | ||
| 58 | - <text class="u-calendar__time-text">{{ rangeEndTime }}</text> | 14 | + <view class="up-p-t-30 up-p-l-20 up-m-b-10" v-if="headerDirection ==='column'"> |
| 15 | + <up-steps v-if="popupShow" dot direction="column" v-model:current="tabsIndex"> | ||
| 16 | + <view v-for="(item, index) in genTabsList" @click="toFatherIndex(index)"> | ||
| 17 | + <up-steps-item :title="item.name"/> | ||
| 18 | + </view> | ||
| 19 | + </up-steps> | ||
| 20 | + </view> | ||
| 21 | + <view class="up-p-t-20 up-m-b-10" v-else> | ||
| 22 | + <up-tabs | ||
| 23 | + v-if="popupShow" | ||
| 24 | + :list="genTabsList" | ||
| 25 | + :scrollable="true" | ||
| 26 | + v-model:current="tabsIndex" | ||
| 27 | + @change="tabsChange" | ||
| 28 | + ref="tabs" | ||
| 29 | + ></up-tabs> | ||
| 30 | + </view> | ||
| 31 | + <view class="area-box"> | ||
| 32 | + <view class="u-flex" :class="{ 'change':isChange }" | ||
| 33 | + :style="{transform: optionsCols == 2 && isChange ? 'translateX(-33.3333333%)' : ''}"> | ||
| 34 | + <template v-for="(levelData, levelIndex) in levelList" :key="levelIndex"> | ||
| 35 | + <view | ||
| 36 | + v-if="optionsCols == 2 || levelIndex == tabsIndex" | ||
| 37 | + class="area-item" | ||
| 38 | + :style="{ width: optionsCols == 2 ? '33.33333%' : '750rpx'}" | ||
| 39 | + > | ||
| 40 | + <view class="u-padding-10" :style="[levelPaneStyle, { height: '100%' }]"> | ||
| 41 | + <scroll-view :scroll-y="true" style="height: 100%"> | ||
| 42 | + <up-cell-group v-if="levelIndex === 0 || selectedValueIndexs[levelIndex - 1] !== undefined"> | ||
| 43 | + <up-cell | ||
| 44 | + v-for="(item,index) in levelData" | ||
| 45 | + :title="item[labelKey]" | ||
| 46 | + :arrow="false" | ||
| 47 | + :index="index" | ||
| 48 | + :key="index" | ||
| 49 | + @click="levelChange(levelIndex, index)" | ||
| 50 | + > | ||
| 51 | + <template v-slot:right-icon> | ||
| 52 | + <up-icon | ||
| 53 | + v-if="selectedValueIndexs[levelIndex] === index" | ||
| 54 | + size="17" | ||
| 55 | + name="checkbox-mark" | ||
| 56 | + ></up-icon> | ||
| 57 | + </template> | ||
| 58 | + </up-cell> | ||
| 59 | + </up-cell-group> | ||
| 60 | + </scroll-view> | ||
| 59 | </view> | 61 | </view> |
| 60 | </view> | 62 | </view> |
| 61 | - </view> | 63 | + </template> |
| 62 | </view> | 64 | </view> |
| 63 | - <scroll-view | ||
| 64 | - v-if="!monthSwitch" | ||
| 65 | - :style="{ | ||
| 66 | - height: addUnit(listHeight, 'px') | ||
| 67 | - }" | ||
| 68 | - scroll-y | ||
| 69 | - @scroll="onScroll" | ||
| 70 | - :scroll-top="scrollTop" | ||
| 71 | - :scrollIntoView="scrollIntoView" | ||
| 72 | - > | ||
| 73 | - <uMonth | ||
| 74 | - :color="color" | ||
| 75 | - :rowHeight="rowHeight" | ||
| 76 | - :showMark="showMark" | ||
| 77 | - :months="months" | ||
| 78 | - :mode="mode" | ||
| 79 | - :maxCount="maxCount" | ||
| 80 | - :startText="startText" | ||
| 81 | - :endText="endText" | ||
| 82 | - :defaultDate="defaultDate" | ||
| 83 | - :minDate="innerMinDate" | ||
| 84 | - :maxDate="innerMaxDate" | ||
| 85 | - :maxMonth="monthNum" | ||
| 86 | - :readonly="readonly" | ||
| 87 | - :maxRange="maxRange" | ||
| 88 | - :rangePrompt="rangePrompt" | ||
| 89 | - :showRangePrompt="showRangePrompt" | ||
| 90 | - :allowSameDay="allowSameDay" | ||
| 91 | - :forbidDays="forbidDays" | ||
| 92 | - :forbidDaysToast="forbidDaysToast" | ||
| 93 | - :monthFormat="monthFormat" | ||
| 94 | - :todayDate="todayDate" | ||
| 95 | - :todayColor="todayColor" | ||
| 96 | - ref="month" | ||
| 97 | - @monthSelected="monthSelected" | ||
| 98 | - @updateMonthTop="onUpdateMonthTop" | ||
| 99 | - ></uMonth> | ||
| 100 | - </scroll-view> | ||
| 101 | - <view | ||
| 102 | - v-else | ||
| 103 | - :style="{ | ||
| 104 | - height: addUnit(listHeight, 'px') | ||
| 105 | - }" | ||
| 106 | - > | ||
| 107 | - <uMonth | ||
| 108 | - :color="color" | ||
| 109 | - :rowHeight="rowHeight" | ||
| 110 | - :showMark="showMark" | ||
| 111 | - :months="currentMonths" | ||
| 112 | - :mode="mode" | ||
| 113 | - :maxCount="maxCount" | ||
| 114 | - :startText="startText" | ||
| 115 | - :endText="endText" | ||
| 116 | - :defaultDate="defaultDate" | ||
| 117 | - :minDate="innerMinDate" | ||
| 118 | - :maxDate="innerMaxDate" | ||
| 119 | - :maxMonth="monthNum" | ||
| 120 | - :readonly="readonly" | ||
| 121 | - :maxRange="maxRange" | ||
| 122 | - :rangePrompt="rangePrompt" | ||
| 123 | - :showRangePrompt="showRangePrompt" | ||
| 124 | - :allowSameDay="allowSameDay" | ||
| 125 | - :forbidDays="forbidDays" | ||
| 126 | - :forbidDaysToast="forbidDaysToast" | ||
| 127 | - :monthFormat="monthFormat" | ||
| 128 | - :todayDate="todayDate" | ||
| 129 | - :todayColor="todayColor" | ||
| 130 | - ref="month" | ||
| 131 | - @monthSelected="monthSelected" | ||
| 132 | - @updateMonthTop="onUpdateMonthTop" | ||
| 133 | - ></uMonth> | ||
| 134 | - </view> | ||
| 135 | - <slot name="footer" v-if="showConfirm"> | ||
| 136 | - <view class="u-calendar__confirm"> | ||
| 137 | - <u-button | ||
| 138 | - shape="circle" | ||
| 139 | - :text=" | ||
| 140 | - buttonDisabled ? confirmDisabledText : confirmText | ||
| 141 | - " | ||
| 142 | - :color="color" | ||
| 143 | - @click="confirm" | ||
| 144 | - :disabled="buttonDisabled" | ||
| 145 | - ></u-button> | ||
| 146 | - </view> | ||
| 147 | - </slot> | ||
| 148 | </view> | 65 | </view> |
| 149 | - </u-popup> | ||
| 150 | - <u-popup | ||
| 151 | - :show="timePickerShow" | ||
| 152 | - mode="center" | ||
| 153 | - :round="8" | ||
| 154 | - :closeOnClickOverlay="true" | ||
| 155 | - @close="closeTimePicker" | ||
| 156 | - > | ||
| 157 | - <view class="u-calendar__time-picker"> | ||
| 158 | - <view class="u-calendar__time-picker__header"> | ||
| 159 | - <text class="u-calendar__time-picker__cancel" @click="closeTimePicker">取消</text> | ||
| 160 | - <text class="u-calendar__time-picker__title">选择时间</text> | ||
| 161 | - <text class="u-calendar__time-picker__confirm" @click="confirmTimePicker">确定</text> | 66 | + <!-- 按钮区域 --> |
| 67 | + <view class="u-cascader-action up-flex up-flex-between"> | ||
| 68 | + <view class="u-padding-20 up-flex-fill"> | ||
| 69 | + <up-button @click="handleCancel" type="default">{{ t("up.common.cancel") }}</up-button> | ||
| 70 | + </view> | ||
| 71 | + <view class="u-padding-20 up-flex-fill"> | ||
| 72 | + <up-button @click="handleConfirm" type="primary">{{ t("up.common.confirm") }}</up-button> | ||
| 162 | </view> | 73 | </view> |
| 163 | - <picker-view class="u-calendar__time-picker__body" :value="timePickerValue" @change="onTimePickerChange"> | ||
| 164 | - <picker-view-column> | ||
| 165 | - <view v-for="(item, index) in hourOptions" :key="`h-${index}`" class="u-calendar__time-picker__item">{{ item }}</view> | ||
| 166 | - </picker-view-column> | ||
| 167 | - <picker-view-column v-if="timePrecision !== 'hour'"> | ||
| 168 | - <view v-for="(item, index) in minuteOptions" :key="`m-${index}`" class="u-calendar__time-picker__item">{{ item }}</view> | ||
| 169 | - </picker-view-column> | ||
| 170 | - <picker-view-column v-if="timePrecision === 'second'"> | ||
| 171 | - <view v-for="(item, index) in secondOptions" :key="`s-${index}`" class="u-calendar__time-picker__item">{{ item }}</view> | ||
| 172 | - </picker-view-column> | ||
| 173 | - </picker-view> | ||
| 174 | </view> | 74 | </view> |
| 175 | - </u-popup> | 75 | + </up-popup> |
| 176 | </template> | 76 | </template> |
| 177 | 77 | ||
| 178 | <script> | 78 | <script> |
| 179 | -import uHeader from './header.vue' | ||
| 180 | -import uMonth from './month.vue' | ||
| 181 | -import { props } from './props.js' | ||
| 182 | -import util from './util.js' | ||
| 183 | -import dayjs from '../u-datetime-picker/dayjs.esm.min.js'; | ||
| 184 | -import Calendar from '../../libs/util/calendar.js' | ||
| 185 | -import { mpMixin } from '../../libs/mixin/mpMixin.js' | ||
| 186 | -import { mixin } from '../../libs/mixin/mixin.js' | ||
| 187 | -import { addUnit, getPx, range, error, padZero } from '../../libs/function/index'; | ||
| 188 | -import test from '../../libs/function/test'; | ||
| 189 | import { t } from '../../libs/i18n' | 79 | import { t } from '../../libs/i18n' |
| 190 | -/** | ||
| 191 | - * Calendar 日历 | ||
| 192 | - * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中. | ||
| 193 | - * @tutorial https://uview-plus.jiangruyi.com/components/calendar.html | ||
| 194 | - * | ||
| 195 | - * @property {String} title 标题内容 (默认 日期选择 ) | ||
| 196 | - * @property {Boolean} showTitle 是否显示标题 (默认 true ) | ||
| 197 | - * @property {Boolean} showSubtitle 是否显示副标题 (默认 true ) | ||
| 198 | - * @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' ) | ||
| 199 | - * @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' ) | ||
| 200 | - * @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' ) | ||
| 201 | - * @property {Array} customList 自定义列表 | ||
| 202 | - * @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' ) | ||
| 203 | - * @property {String | Number} minDate 最小的可选日期 (默认 0 ) | ||
| 204 | - * @property {String | Number} maxDate 最大可选日期 (默认 0 ) | ||
| 205 | - * @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式 | ||
| 206 | - * @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER ) | ||
| 207 | - * @property {String | Number} rowHeight 日期行高 (默认 56 ) | ||
| 208 | - * @property {Function} formatter 日期格式化函数 | ||
| 209 | - * @property {Boolean} showLunar 是否显示农历 (默认 false ) | ||
| 210 | - * @property {Boolean} showMark 是否显示月份背景色 (默认 true ) | ||
| 211 | - * @property {String} confirmText 确定按钮的文字 (默认 '确定' ) | ||
| 212 | - * @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' ) | ||
| 213 | - * @property {Boolean} show 是否显示日历弹窗 (默认 false ) | ||
| 214 | - * @property {Boolean} overlay 是否显示遮罩 (默认 true ) | ||
| 215 | - * @property {String | Number} duration 动画时长,单位ms (默认 300 ) | ||
| 216 | - * @property {Object | String} overlayStyle 自定义遮罩的样式 | ||
| 217 | - * @property {Number | String} overlayOpacity 遮罩透明度,0-1之间 (默认 0.5 ) | ||
| 218 | - * @property {String | Number} zIndex 弹出层的z-index值 (默认 10075 ) | ||
| 219 | - * @property {Boolean} safeAreaInsetBottom 是否留出底部安全距离 (默认 true ) | ||
| 220 | - * @property {Boolean} safeAreaInsetTop 是否留出顶部安全距离(状态栏高度) (默认 false ) | ||
| 221 | - * @property {String} bgColor 弹窗背景色,设置为transparent可去除白色背景 | ||
| 222 | - * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false ) | ||
| 223 | - * @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false ) | ||
| 224 | - * @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效 | ||
| 225 | - * @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效 | ||
| 226 | - * @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true ) | ||
| 227 | - * @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false ) | ||
| 228 | - * @property {String} rangeResultMode 区间模式下确认返回值格式,all-返回区间内所有日期,boundary-仅返回起止日期 (默认 'all' ) | ||
| 229 | - * @property {Boolean} enableTime 是否开启时分秒选择 (默认 false ) | ||
| 230 | - * @property {String} timePrecision 时间精度,hour-仅时,minute-时分,second-时分秒 (默认 'minute' ) | ||
| 231 | - * @property {String} defaultTime 默认时间,支持HH/HH:mm/HH:mm:ss,不传则按00:00补齐 | ||
| 232 | - * @property {Number|String} round 圆角值,默认无圆角 (默认 0 ) | ||
| 233 | - * @property {Number|String} monthNum 最多展示的月份数量 (默认 3 ) | ||
| 234 | - * @property {Boolean} monthSwitch 是否启用非滚动的单月切换模式 (默认 false ) | ||
| 235 | - * @property {String} todayColor 今天日期的独立高亮颜色,默认跟随主题色 | ||
| 236 | - * @property {Array} weekText 星期文案 (默认 ['一', '二', '三', '四', '五', '六', '日'] ) | ||
| 237 | - * | ||
| 238 | - * @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数 | ||
| 239 | - * @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件 | ||
| 240 | - * @event {Function()} closed 日历已关闭(离场动画结束、弹窗真正消失后)触发 | ||
| 241 | - * @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm"> | ||
| 242 | - </u-calendar> | ||
| 243 | - * */ | ||
| 244 | export default { | 80 | export default { |
| 245 | - name: 'u-calendar', | ||
| 246 | - mixins: [mpMixin, mixin, props], | ||
| 247 | - components: { | ||
| 248 | - uHeader, | ||
| 249 | - uMonth | ||
| 250 | - }, | ||
| 251 | - data() { | ||
| 252 | - return { | ||
| 253 | - // 需要显示的月份的数组 | ||
| 254 | - months: [], | ||
| 255 | - // 在月份滚动区域中,当前视图中月份的index索引 | ||
| 256 | - monthIndex: 0, | ||
| 257 | - // 月份滚动区域的高度 | ||
| 258 | - listHeight: 0, | ||
| 259 | - // month组件中选择的日期数组 | ||
| 260 | - selected: [], | ||
| 261 | - scrollIntoView: '', | ||
| 262 | - scrollIntoViewScroll: '', | ||
| 263 | - scrollTop:0, | ||
| 264 | - timePickerShow: false, | ||
| 265 | - timePickerTarget: 'single', | ||
| 266 | - timePickerValue: [0, 0, 0], | ||
| 267 | - hourOptions: [], | ||
| 268 | - minuteOptions: [], | ||
| 269 | - secondOptions: [], | ||
| 270 | - singleTime: '00:00', | ||
| 271 | - rangeStartTime: '00:00', | ||
| 272 | - rangeEndTime: '00:00', | ||
| 273 | - // 过滤处理方法 | ||
| 274 | - innerFormatter: (value) => value | ||
| 275 | - } | ||
| 276 | - }, | ||
| 277 | - watch: { | ||
| 278 | - scrollIntoView: { | ||
| 279 | - immediate: true, | ||
| 280 | - handler(n) { | ||
| 281 | - // console.log('scrollIntoView', n) | ||
| 282 | - } | ||
| 283 | - }, | ||
| 284 | - selectedChange: { | ||
| 285 | - immediate: true, | ||
| 286 | - handler(n) { | ||
| 287 | - this.setMonth() | ||
| 288 | - } | ||
| 289 | - }, | ||
| 290 | - timePrecision() { | ||
| 291 | - this.initTimeOptions() | ||
| 292 | - this.initTimeValues() | ||
| 293 | - }, | ||
| 294 | - defaultTime() { | ||
| 295 | - this.initTimeValues() | ||
| 296 | - }, | ||
| 297 | - // 打开弹窗时,设置月份数据 | 81 | + name: 'up-cascader', |
| 82 | + props: { | ||
| 298 | show: { | 83 | show: { |
| 299 | - immediate: true, | ||
| 300 | - handler(n) { | ||
| 301 | - if (n) { | ||
| 302 | - this.setMonth() | ||
| 303 | - // 弹层每次打开都会重新挂载并滚动回默认月份,副标题指向的月份索引要一起复位, | ||
| 304 | - // 否则会沿用上一次关闭前滚动到的月份,导致副标题与日历内容对应不上。 | ||
| 305 | - // 页面内模式的内容一直挂载着,滚动位置不会重置,所以不做处理 | ||
| 306 | - if (!this.pageInline) { | ||
| 307 | - this.monthIndex = this.getDefaultMonthIndex() | ||
| 308 | - } | ||
| 309 | - } else { | ||
| 310 | - // 关闭时重置scrollIntoView,否则会出现二次打开日历,当前月份数据显示不正确。 | ||
| 311 | - // scrollIntoView需要有一个值变动过程,才会产生作用。 | ||
| 312 | - this.scrollIntoView = '' | ||
| 313 | - } | ||
| 314 | - } | ||
| 315 | - } | ||
| 316 | - }, | ||
| 317 | - computed: { | ||
| 318 | - // 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理 | ||
| 319 | - innerMaxDate() { | ||
| 320 | - return test.number(this.maxDate) | ||
| 321 | - ? Number(this.maxDate) | ||
| 322 | - : this.maxDate | 84 | + type: Boolean, |
| 85 | + default: false | ||
| 323 | }, | 86 | }, |
| 324 | - innerMinDate() { | ||
| 325 | - return test.number(this.minDate) | ||
| 326 | - ? Number(this.minDate) | ||
| 327 | - : this.minDate | ||
| 328 | - }, | ||
| 329 | - todayDate() { | ||
| 330 | - return dayjs().format('YYYY-MM-DD') | ||
| 331 | - }, | ||
| 332 | - todayText() { | ||
| 333 | - return t('up.calendar.today') | ||
| 334 | - }, | ||
| 335 | - todayDisabled() { | ||
| 336 | - const today = dayjs(this.todayDate) | ||
| 337 | - const minDate = this.innerMinDate | ||
| 338 | - ? dayjs(this.innerMinDate) | ||
| 339 | - : null | ||
| 340 | - const maxDate = this.innerMaxDate | ||
| 341 | - ? dayjs(this.innerMaxDate) | ||
| 342 | - : null | ||
| 343 | - if (minDate && today.isBefore(minDate, 'day')) { | ||
| 344 | - return true | ||
| 345 | - } | ||
| 346 | - if (maxDate && today.isAfter(maxDate, 'day')) { | ||
| 347 | - return true | ||
| 348 | - } | ||
| 349 | - return false | ||
| 350 | - }, | ||
| 351 | - // 多个条件的变化,会引起选中日期的变化,这里统一管理监听 | ||
| 352 | - selectedChange() { | ||
| 353 | - return [this.innerMinDate, this.innerMaxDate, this.defaultDate] | ||
| 354 | - }, | ||
| 355 | - subtitle() { | ||
| 356 | - // 初始化时,this.months为空数组,所以需要特别判断处理 | ||
| 357 | - if (this.months.length) { | ||
| 358 | - if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') { | ||
| 359 | - return this.months[this.monthIndex].year + '年' + (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '月' | ||
| 360 | - } else { | ||
| 361 | - return (this.months[this.monthIndex].month < 10 ? '0' + this.months[this.monthIndex].month : this.months[this.monthIndex].month) + '/' + this.months[this.monthIndex].year | ||
| 362 | - } | ||
| 363 | - } else { | ||
| 364 | - return '' | 87 | + data: { |
| 88 | + type: Array, | ||
| 89 | + default() { | ||
| 90 | + return []; | ||
| 365 | } | 91 | } |
| 366 | }, | 92 | }, |
| 367 | - currentMonths() { | ||
| 368 | - if (this.monthSwitch && this.months.length) { | ||
| 369 | - return [this.months[this.monthIndex]] | 93 | + modelValue: { |
| 94 | + type: Array, | ||
| 95 | + default() { | ||
| 96 | + return []; | ||
| 370 | } | 97 | } |
| 371 | - return this.months | ||
| 372 | }, | 98 | }, |
| 373 | - switchPrevDisabled() { | ||
| 374 | - return this.monthIndex <= 0 | 99 | + valueKey: { |
| 100 | + type: String, | ||
| 101 | + default: 'value' | ||
| 375 | }, | 102 | }, |
| 376 | - switchNextDisabled() { | ||
| 377 | - return this.monthIndex >= this.months.length - 1 | 103 | + labelKey: { |
| 104 | + type: String, | ||
| 105 | + default: 'label' | ||
| 378 | }, | 106 | }, |
| 379 | - switchPrevYearDisabled() { | ||
| 380 | - return this.monthIndex - 12 < 0 | 107 | + childrenKey: { |
| 108 | + type: String, | ||
| 109 | + default: 'children' | ||
| 381 | }, | 110 | }, |
| 382 | - switchNextYearDisabled() { | ||
| 383 | - return this.monthIndex + 12 > this.months.length - 1 | 111 | + maskCloseAble: { |
| 112 | + type: Boolean, | ||
| 113 | + default: true | ||
| 384 | }, | 114 | }, |
| 385 | - showTimePanel() { | ||
| 386 | - if (!this.enableTime) return false | ||
| 387 | - if (this.mode === 'single') return true | ||
| 388 | - if (this.mode === 'range' && this.rangeResultMode === 'boundary') { | ||
| 389 | - return true | ||
| 390 | - } | ||
| 391 | - return false | 115 | + zIndex: { |
| 116 | + type: [String, Number], | ||
| 117 | + default: 0 | ||
| 392 | }, | 118 | }, |
| 393 | - singleDateLabel() { | ||
| 394 | - return this.selected[0] || '--' | 119 | + autoClose: { |
| 120 | + type: Boolean, | ||
| 121 | + default: false | ||
| 395 | }, | 122 | }, |
| 396 | - rangeStartDateLabel() { | ||
| 397 | - return this.selected[0] || '--' | 123 | + headerDirection: { |
| 124 | + type: String, | ||
| 125 | + default: 'row' | ||
| 398 | }, | 126 | }, |
| 399 | - rangeEndDateLabel() { | ||
| 400 | - if (this.selected.length >= 2) { | ||
| 401 | - return this.selected[this.selected.length - 1] | ||
| 402 | - } | ||
| 403 | - return '--' | 127 | + optionsCols: { |
| 128 | + type: [Number], | ||
| 129 | + default: 2 | ||
| 404 | }, | 130 | }, |
| 405 | - buttonDisabled() { | ||
| 406 | - // 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态 | ||
| 407 | - if (this.mode === 'range') { | ||
| 408 | - if (this.selected.length <= 1) { | ||
| 409 | - return true | ||
| 410 | - } else { | ||
| 411 | - return false | ||
| 412 | - } | ||
| 413 | - } else { | ||
| 414 | - return false | ||
| 415 | - } | 131 | + closeable: { |
| 132 | + type: Boolean, | ||
| 133 | + default: true | ||
| 416 | } | 134 | } |
| 417 | }, | 135 | }, |
| 418 | - mounted() { | ||
| 419 | - this.start = Date.now() | ||
| 420 | - this.init() | 136 | + data() { |
| 137 | + return { | ||
| 138 | + levelList: [], | ||
| 139 | + selectedValueIndexs: [], | ||
| 140 | + tabsIndex: 0, | ||
| 141 | + popupShow: false, | ||
| 142 | + confirmValues: [] | ||
| 143 | + } | ||
| 421 | }, | 144 | }, |
| 422 | - emits: ["confirm", "close", "closed"], | ||
| 423 | - methods: { | ||
| 424 | - addUnit, | ||
| 425 | - padTime(num) { | ||
| 426 | - return String(num).padStart(2, '0') | ||
| 427 | - }, | ||
| 428 | - initTimeOptions() { | ||
| 429 | - this.hourOptions = Array.from({ length: 24 }, (_, i) => this.padTime(i)) | ||
| 430 | - this.minuteOptions = Array.from({ length: 60 }, (_, i) => this.padTime(i)) | ||
| 431 | - this.secondOptions = Array.from({ length: 60 }, (_, i) => this.padTime(i)) | ||
| 432 | - }, | ||
| 433 | - parseTimeValue(value = '') { | ||
| 434 | - const raw = String(value || '').trim() | ||
| 435 | - const parts = raw.length ? raw.split(':') : [] | ||
| 436 | - const getNumber = (index, max) => { | ||
| 437 | - const current = Number(parts[index] || 0) | ||
| 438 | - if (Number.isNaN(current)) return 0 | ||
| 439 | - return Math.max(0, Math.min(max, current)) | ||
| 440 | - } | ||
| 441 | - const hour = getNumber(0, 23) | ||
| 442 | - const minute = getNumber(1, 59) | ||
| 443 | - const second = getNumber(2, 59) | ||
| 444 | - return [hour, minute, second] | ||
| 445 | - }, | ||
| 446 | - getDefaultTimeValue() { | ||
| 447 | - const [hour, minute, second] = this.parseTimeValue(this.defaultTime) | ||
| 448 | - if (this.timePrecision === 'hour') { | ||
| 449 | - return this.padTime(hour) | ||
| 450 | - } | ||
| 451 | - if (this.timePrecision === 'second') { | ||
| 452 | - return `${this.padTime(hour)}:${this.padTime(minute)}:${this.padTime(second)}` | ||
| 453 | - } | ||
| 454 | - return `${this.padTime(hour)}:${this.padTime(minute)}` | ||
| 455 | - }, | ||
| 456 | - initTimeValues() { | ||
| 457 | - const value = this.getDefaultTimeValue() | ||
| 458 | - this.singleTime = value | ||
| 459 | - this.rangeStartTime = value | ||
| 460 | - this.rangeEndTime = value | ||
| 461 | - }, | ||
| 462 | - timeToPickerValue(value = '') { | ||
| 463 | - const [hour, minute, second] = this.parseTimeValue(value) | ||
| 464 | - return [hour, minute, second] | ||
| 465 | - }, | ||
| 466 | - pickerValueToTime(value = []) { | ||
| 467 | - const hour = Number(value[0] || 0) | ||
| 468 | - const minute = Number(value[1] || 0) | ||
| 469 | - const second = Number(value[2] || 0) | ||
| 470 | - if (this.timePrecision === 'hour') { | ||
| 471 | - return this.padTime(hour) | ||
| 472 | - } | ||
| 473 | - if (this.timePrecision === 'second') { | ||
| 474 | - return `${this.padTime(hour)}:${this.padTime(minute)}:${this.padTime(second)}` | ||
| 475 | - } | ||
| 476 | - return `${this.padTime(hour)}:${this.padTime(minute)}` | ||
| 477 | - }, | ||
| 478 | - openTimePicker(target) { | ||
| 479 | - this.timePickerTarget = target | ||
| 480 | - let currentValue = this.singleTime | ||
| 481 | - if (target === 'start') currentValue = this.rangeStartTime | ||
| 482 | - if (target === 'end') currentValue = this.rangeEndTime | ||
| 483 | - this.timePickerValue = this.timeToPickerValue(currentValue) | ||
| 484 | - this.timePickerShow = true | ||
| 485 | - }, | ||
| 486 | - onTimePickerChange(e) { | ||
| 487 | - this.timePickerValue = e.detail.value | ||
| 488 | - }, | ||
| 489 | - closeTimePicker() { | ||
| 490 | - this.timePickerShow = false | ||
| 491 | - }, | ||
| 492 | - confirmTimePicker() { | ||
| 493 | - const value = this.pickerValueToTime(this.timePickerValue) | ||
| 494 | - if (this.timePickerTarget === 'single') this.singleTime = value | ||
| 495 | - if (this.timePickerTarget === 'start') this.rangeStartTime = value | ||
| 496 | - if (this.timePickerTarget === 'end') this.rangeEndTime = value | ||
| 497 | - this.timePickerShow = false | ||
| 498 | - }, | ||
| 499 | - timeToSecond(timeText = '') { | ||
| 500 | - const [hour, minute, second] = this.parseTimeValue(timeText) | ||
| 501 | - return hour * 3600 + minute * 60 + second | ||
| 502 | - }, | ||
| 503 | - validateSameDayRangeTime() { | ||
| 504 | - if (!this.enableTime || this.mode !== 'range' || this.rangeResultMode !== 'boundary') { | ||
| 505 | - return true | ||
| 506 | - } | ||
| 507 | - if (this.selected.length < 2) return true | ||
| 508 | - const startDate = this.selected[0] | ||
| 509 | - const endDate = this.selected[this.selected.length - 1] | ||
| 510 | - if (startDate !== endDate) return true | ||
| 511 | - const startSeconds = this.timeToSecond(this.rangeStartTime) | ||
| 512 | - const endSeconds = this.timeToSecond(this.rangeEndTime) | ||
| 513 | - if (endSeconds < startSeconds) { | ||
| 514 | - uni.showToast({ | ||
| 515 | - title: '结束时间不能早于开始时间', | ||
| 516 | - icon: 'none' | ||
| 517 | - }) | ||
| 518 | - return false | ||
| 519 | - } | ||
| 520 | - return true | ||
| 521 | - }, | ||
| 522 | - appendTime(dateText, timeText) { | ||
| 523 | - return `${dateText} ${timeText}` | ||
| 524 | - }, | ||
| 525 | - getConfirmValue(selected = this.selected) { | ||
| 526 | - let result = selected | ||
| 527 | - if ( | ||
| 528 | - this.mode === 'range' && | ||
| 529 | - this.rangeResultMode === 'boundary' && | ||
| 530 | - selected.length >= 2 | ||
| 531 | - ) { | ||
| 532 | - const len = selected.length - 1 | ||
| 533 | - result = [selected[0], selected[len]] | ||
| 534 | - } | ||
| 535 | - if (!this.showTimePanel || !this.enableTime) { | ||
| 536 | - return result | ||
| 537 | - } | ||
| 538 | - if (this.mode === 'single' && result.length >= 1) { | ||
| 539 | - return [this.appendTime(result[0], this.singleTime)] | ||
| 540 | - } | ||
| 541 | - if (this.mode === 'range' && this.rangeResultMode === 'boundary' && result.length >= 2) { | ||
| 542 | - return [ | ||
| 543 | - this.appendTime(result[0], this.rangeStartTime), | ||
| 544 | - this.appendTime(result[1], this.rangeEndTime) | ||
| 545 | - ] | ||
| 546 | - } | ||
| 547 | - return result | ||
| 548 | - }, | ||
| 549 | - // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用 | ||
| 550 | - setFormatter(e) { | ||
| 551 | - this.innerFormatter = e | ||
| 552 | - }, | ||
| 553 | - // month组件内部选择日期后,通过事件通知给父组件 | ||
| 554 | - monthSelected(e,scene ='init') { | ||
| 555 | - this.selected = e | ||
| 556 | - if (!this.showConfirm) { | ||
| 557 | - // 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还 | ||
| 558 | - if ( | ||
| 559 | - this.mode === 'multiple' || | ||
| 560 | - this.mode === 'single' || | ||
| 561 | - (this.mode === 'range' && this.selected.length >= 2) | ||
| 562 | - ) { | ||
| 563 | - if( scene === 'init'){ | ||
| 564 | - return | ||
| 565 | - } | ||
| 566 | - if( scene === 'tap') { | ||
| 567 | - if (!this.validateSameDayRangeTime()) return | ||
| 568 | - this.$emit('confirm', this.getConfirmValue()) | ||
| 569 | - } | ||
| 570 | - } | ||
| 571 | - } | ||
| 572 | - }, | ||
| 573 | - init() { | ||
| 574 | - // 校验maxDate,不能小于minDate。 | ||
| 575 | - if ( | ||
| 576 | - this.innerMaxDate && | ||
| 577 | - this.innerMinDate && | ||
| 578 | - new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime() | ||
| 579 | - ) { | ||
| 580 | - return error('maxDate不能小于minDate时间') | ||
| 581 | - } | ||
| 582 | - // 滚动区域的高度 | ||
| 583 | - let bottomPadding = 0; | ||
| 584 | - if (this.pageInline) { | ||
| 585 | - bottomPadding = 0 | ||
| 586 | - } else { | ||
| 587 | - bottomPadding = 30 | ||
| 588 | - } | ||
| 589 | - this.listHeight = this.rowHeight * (this.monthSwitch ? 6 : 5) + bottomPadding | ||
| 590 | - this.initTimeOptions() | ||
| 591 | - this.initTimeValues() | ||
| 592 | - this.setMonth() | ||
| 593 | - }, | ||
| 594 | - close() { | ||
| 595 | - this.$emit('close') | ||
| 596 | - }, | ||
| 597 | - // 点击确定按钮 | ||
| 598 | - confirm() { | ||
| 599 | - if (!this.buttonDisabled) { | ||
| 600 | - if (!this.validateSameDayRangeTime()) return | ||
| 601 | - this.$emit('confirm', this.getConfirmValue()) | ||
| 602 | - } | ||
| 603 | - }, | ||
| 604 | - // 获得两个日期之间的月份数 | ||
| 605 | - getMonths(minDate, maxDate) { | ||
| 606 | - const minYear = dayjs(minDate).year() | ||
| 607 | - const minMonth = dayjs(minDate).month() + 1 | ||
| 608 | - const maxYear = dayjs(maxDate).year() | ||
| 609 | - const maxMonth = dayjs(maxDate).month() + 1 | ||
| 610 | - return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1 | 145 | + watch: { |
| 146 | + data: { | ||
| 147 | + handler() { | ||
| 148 | + this.initLevelList(); | ||
| 149 | + this.setDefaultValue(); | ||
| 150 | + }, | ||
| 151 | + immediate: true | ||
| 611 | }, | 152 | }, |
| 612 | - // 设置月份数据 | ||
| 613 | - setMonth() { | ||
| 614 | - // 最小日期的毫秒数 | ||
| 615 | - const minDate = this.innerMinDate || dayjs().valueOf() | ||
| 616 | - // 如果没有指定最大日期,则往后推3个月 | ||
| 617 | - const maxDate = | ||
| 618 | - this.innerMaxDate || | ||
| 619 | - dayjs(minDate) | ||
| 620 | - .add(this.monthNum - 1, 'month') | ||
| 621 | - .valueOf() | ||
| 622 | - // 最大最小月份之间的共有多少个月份, | ||
| 623 | - const months = range( | ||
| 624 | - 1, | ||
| 625 | - this.monthNum, | ||
| 626 | - this.getMonths(minDate, maxDate) | ||
| 627 | - ) | ||
| 628 | - // 记录上一次测量到的各月份top值,月份未发生变化时高度也不会变, | ||
| 629 | - // 复用后可避免重新生成数据后onScroll没有判断依据 | ||
| 630 | - const topCache = {} | ||
| 631 | - this.months.forEach(item => { | ||
| 632 | - if (typeof item.top === 'number') { | ||
| 633 | - topCache[`${item.year}-${item.month}`] = item.top | ||
| 634 | - } | ||
| 635 | - }) | ||
| 636 | - // 先清空数组 | ||
| 637 | - this.months = [] | ||
| 638 | - // 最大最小日期只需格式化一次,YYYY-MM-DD的字典序即时间序,可直接比较字符串 | ||
| 639 | - const minDateStr = dayjs(minDate).format('YYYY-MM-DD') | ||
| 640 | - const maxDateStr = dayjs(maxDate).format('YYYY-MM-DD') | ||
| 641 | - const formatter = this.formatter || this.innerFormatter | ||
| 642 | - for (let i = 0; i < months; i++) { | ||
| 643 | - // 每个月只构造一个dayjs对象,当月的日期数据都由它派生, | ||
| 644 | - // 避免按天重复解析(monthNum较大时这里是主要开销) | ||
| 645 | - const monthStart = dayjs(minDate).add(i, 'month') | ||
| 646 | - const monthValue = monthStart.month() + 1 | ||
| 647 | - this.months.push({ | ||
| 648 | - date: new Array(monthStart.daysInMonth()) | ||
| 649 | - .fill(1) | ||
| 650 | - .map((item, index) => { | ||
| 651 | - // 日期,取值1-31 | ||
| 652 | - let day = index + 1 | ||
| 653 | - const current = monthStart.date(day) | ||
| 654 | - // 星期,0-6,0为周日 | ||
| 655 | - const week = current.day() | ||
| 656 | - const date = current.format('YYYY-MM-DD') | ||
| 657 | - let bottomInfo = '' | ||
| 658 | - if (this.showLunar) { | ||
| 659 | - // 将日期转为农历格式 | ||
| 660 | - const lunar = Calendar.solar2lunar( | ||
| 661 | - current.year(), | ||
| 662 | - monthValue, | ||
| 663 | - day | ||
| 664 | - ) | ||
| 665 | - bottomInfo = lunar.IDayCn | ||
| 666 | - } | ||
| 667 | - let config = { | ||
| 668 | - day, | ||
| 669 | - week, | ||
| 670 | - // 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态 | ||
| 671 | - disabled: | ||
| 672 | - date < minDateStr || date > maxDateStr, | ||
| 673 | - // 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理 | ||
| 674 | - date: new Date(date), | ||
| 675 | - // 已格式化好的日期字符串,供month组件比较使用, | ||
| 676 | - // 省掉渲染期对每个日期格子的重复解析 | ||
| 677 | - dateStr: date, | ||
| 678 | - bottomInfo, | ||
| 679 | - dot: false, | ||
| 680 | - month: monthValue | ||
| 681 | - } | ||
| 682 | - return formatter(config) | ||
| 683 | - }), | ||
| 684 | - // 当前所属的月份 | ||
| 685 | - month: monthValue, | ||
| 686 | - // 当前年份 | ||
| 687 | - year: monthStart.year() | ||
| 688 | - }) | ||
| 689 | - } | ||
| 690 | - this.months.forEach(item => { | ||
| 691 | - const cachedTop = topCache[`${item.year}-${item.month}`] | ||
| 692 | - if (typeof cachedTop === 'number') { | ||
| 693 | - item.top = cachedTop | 153 | + show: { |
| 154 | + handler(val) { | ||
| 155 | + this.popupShow = val; | ||
| 156 | + if (val) { | ||
| 157 | + this.tabsIndex = 0; | ||
| 158 | + this.initLevelList(); | ||
| 159 | + this.setDefaultValue(); | ||
| 694 | } | 160 | } |
| 695 | - }) | ||
| 696 | - if (this.monthSwitch) { | ||
| 697 | - this.monthIndex = this.getDefaultMonthIndex() | ||
| 698 | - } | ||
| 699 | - }, | ||
| 700 | - getDefaultMonthIndex() { | ||
| 701 | - let selected = dayjs().format('YYYY-MM') | ||
| 702 | - if (this.defaultDate) { | ||
| 703 | - if (!test.array(this.defaultDate)) { | ||
| 704 | - selected = dayjs(this.defaultDate).format('YYYY-MM') | ||
| 705 | - } else if (this.defaultDate.length) { | ||
| 706 | - selected = dayjs(this.defaultDate[0]).format('YYYY-MM') | 161 | + }, |
| 162 | + immediate: true | ||
| 163 | + }, | ||
| 164 | + modelValue: { | ||
| 165 | + handler() { | ||
| 166 | + this.setDefaultValue(); | ||
| 167 | + }, | ||
| 168 | + immediate: true | ||
| 169 | + } | ||
| 170 | + }, | ||
| 171 | + computed: { | ||
| 172 | + isChange() { | ||
| 173 | + return this.tabsIndex > 1; | ||
| 174 | + }, | ||
| 175 | + genTabsList() { | ||
| 176 | + let tabsList = [{ name: "请选择" }]; | ||
| 177 | + for (let i = 0; i < this.selectedValueIndexs.length; i++) { | ||
| 178 | + if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) { | ||
| 179 | + const selectedItem = this.levelList[i][this.selectedValueIndexs[i]]; | ||
| 180 | + if (selectedItem) { | ||
| 181 | + tabsList[i] = { name: selectedItem[this.labelKey] }; | ||
| 182 | + if (i === this.selectedValueIndexs.length - 1 && | ||
| 183 | + selectedItem[this.childrenKey] && | ||
| 184 | + selectedItem[this.childrenKey].length > 0) { | ||
| 185 | + tabsList.push({ name: "请选择" }); | ||
| 186 | + } | ||
| 187 | + } | ||
| 707 | } | 188 | } |
| 708 | } | 189 | } |
| 709 | - const index = this.months.findIndex(({ year, month }) => { | ||
| 710 | - return `${year}-${padZero(month)}` === selected | ||
| 711 | - }) | ||
| 712 | - return index === -1 ? 0 : index | 190 | + return tabsList; |
| 713 | }, | 191 | }, |
| 714 | - prevMonth() { | ||
| 715 | - if (!this.switchPrevDisabled) { | ||
| 716 | - this.monthIndex -= 1 | ||
| 717 | - } | ||
| 718 | - }, | ||
| 719 | - nextMonth() { | ||
| 720 | - if (!this.switchNextDisabled) { | ||
| 721 | - this.monthIndex += 1 | ||
| 722 | - } | 192 | + uZIndex() { |
| 193 | + return this.zIndex ? this.zIndex : this.$u.zIndex.popup; | ||
| 723 | }, | 194 | }, |
| 724 | - prevYear() { | ||
| 725 | - if (!this.switchPrevYearDisabled) { | ||
| 726 | - this.monthIndex -= 12 | ||
| 727 | - } | 195 | + closeIconColor() { |
| 196 | + return 'var(--up-main-color, #303133)'; | ||
| 728 | }, | 197 | }, |
| 729 | - nextYear() { | ||
| 730 | - if (!this.switchNextYearDisabled) { | ||
| 731 | - this.monthIndex += 12 | 198 | + levelPaneStyle() { |
| 199 | + return { | ||
| 200 | + backgroundColor: 'var(--up-bg-color, #f7f7f7)' | ||
| 201 | + }; | ||
| 202 | + } | ||
| 203 | + }, | ||
| 204 | + emits: ['update:modelValue', 'update:show', 'change', 'confirm', 'cancel'], | ||
| 205 | + methods: { | ||
| 206 | + t, | ||
| 207 | + // 获取选中值数组 | ||
| 208 | + getSelectedValues() { | ||
| 209 | + const result = []; | ||
| 210 | + for (let i = 0; i < this.selectedValueIndexs.length; i++) { | ||
| 211 | + const selectedIndex = this.selectedValueIndexs[i]; | ||
| 212 | + if (selectedIndex === undefined) continue; | ||
| 213 | + const list = this.levelList[i]; | ||
| 214 | + if (!list || !list[selectedIndex]) continue; | ||
| 215 | + result.push(list[selectedIndex][this.valueKey]); | ||
| 216 | + } | ||
| 217 | + return result; | ||
| 218 | + }, | ||
| 219 | + // 判断当前选中项是否为最后一级(叶子节点) | ||
| 220 | + isCurrentLeafNode() { | ||
| 221 | + const len = this.selectedValueIndexs.length; | ||
| 222 | + if (len === 0) return false; | ||
| 223 | + const lastLevel = len - 1; | ||
| 224 | + const lastIndex = this.selectedValueIndexs[lastLevel]; | ||
| 225 | + const lastItem = this.levelList[lastLevel]?.[lastIndex]; | ||
| 226 | + if (!lastItem) return false; | ||
| 227 | + // 无子级 = 最后一级 | ||
| 228 | + return !lastItem[this.childrenKey] || lastItem[this.childrenKey].length === 0; | ||
| 229 | + }, | ||
| 230 | + initLevelList() { | ||
| 231 | + if (this.data && this.data.length > 0) { | ||
| 232 | + this.levelList = [this.data]; | ||
| 233 | + this.selectedValueIndexs = []; | ||
| 234 | + } else { | ||
| 235 | + this.levelList = []; | ||
| 236 | + this.selectedValueIndexs = []; | ||
| 732 | } | 237 | } |
| 733 | }, | 238 | }, |
| 734 | - jumpToToday() { | ||
| 735 | - if (this.todayDisabled) { | ||
| 736 | - return | 239 | + setDefaultValue() { |
| 240 | + if (!this.data || this.data.length === 0) { | ||
| 241 | + this.confirmValues = []; | ||
| 242 | + return; | ||
| 737 | } | 243 | } |
| 738 | - const targetMonth = dayjs(this.todayDate).format('YYYY-MM') | ||
| 739 | - const selectToday = () => { | ||
| 740 | - if (this.mode === 'range') { | ||
| 741 | - return | ||
| 742 | - } | ||
| 743 | - this.$refs.month && this.$refs.month.selectDate(this.todayDate) | 244 | + if (!this.modelValue || this.modelValue.length === 0) { |
| 245 | + this.confirmValues = []; | ||
| 246 | + return; | ||
| 744 | } | 247 | } |
| 745 | - if (this.monthSwitch) { | ||
| 746 | - const todayMonthIndex = this.months.findIndex(({ year, month }) => { | ||
| 747 | - return `${year}-${padZero(month)}` === targetMonth | ||
| 748 | - }) | ||
| 749 | - if (todayMonthIndex !== -1) { | ||
| 750 | - this.monthIndex = todayMonthIndex | ||
| 751 | - this.$nextTick(selectToday) | 248 | + this.selectedValueIndexs = []; |
| 249 | + this.levelList = []; | ||
| 250 | + let currentLevelData = this.data; | ||
| 251 | + | ||
| 252 | + for (let i = 0; i < this.modelValue.length; i++) { | ||
| 253 | + const value = this.modelValue[i]; | ||
| 254 | + const index = currentLevelData.findIndex(item => item[this.valueKey] === value); | ||
| 255 | + this.levelList[i] = currentLevelData; | ||
| 256 | + | ||
| 257 | + if (index !== -1) { | ||
| 258 | + this.selectedValueIndexs.push(index); | ||
| 259 | + if (currentLevelData[index][this.childrenKey]) { | ||
| 260 | + currentLevelData = currentLevelData[index][this.childrenKey]; | ||
| 261 | + } else { | ||
| 262 | + break; | ||
| 263 | + } | ||
| 264 | + } else { | ||
| 265 | + break; | ||
| 752 | } | 266 | } |
| 753 | - return | ||
| 754 | } | 267 | } |
| 755 | - this.scrollIntoDefaultMonth(targetMonth) | ||
| 756 | - this.$nextTick(selectToday) | 268 | + this.confirmValues = this.getSelectedValues(); |
| 757 | }, | 269 | }, |
| 758 | - // 滚动到默认设置的月份 | ||
| 759 | - scrollIntoDefaultMonth(selected) { | ||
| 760 | - // 查询默认日期在可选列表的下标 | ||
| 761 | - const _index = this.months.findIndex(({ | ||
| 762 | - year, | ||
| 763 | - month | ||
| 764 | - }) => { | ||
| 765 | - month = padZero(month) | ||
| 766 | - return `${year}-${month}` === selected | ||
| 767 | - }) | ||
| 768 | - if (_index !== -1) { | ||
| 769 | - // 滚动位置是程序主动设置的,同步更新副标题指向的月份索引。 | ||
| 770 | - // 只依赖scroll事件的话,滚动位置没有真正变化时收不到事件,副标题就会停留在上一次的月份 | ||
| 771 | - this.monthIndex = _index | ||
| 772 | - // #ifndef MP-WEIXIN | ||
| 773 | - this.$nextTick(() => { | ||
| 774 | - this.scrollIntoView = '' | ||
| 775 | - this.scrollIntoView = `month-${_index}` | ||
| 776 | - this.scrollIntoViewScroll = this.scrollIntoView | ||
| 777 | - }) | ||
| 778 | - // #endif | ||
| 779 | - // #ifdef MP-WEIXIN | ||
| 780 | - // scrollTop与scrollIntoView同理,需要有一个值变动过程才会生效, | ||
| 781 | - // 否则二次打开日历时沿用上次的数值,scroll-view不会滚动到默认月份 | ||
| 782 | - const _top = this.months[_index].top || 0; | ||
| 783 | - this.scrollTop = 0; | ||
| 784 | - if (_top !== 0) { | ||
| 785 | - this.$nextTick(() => { | ||
| 786 | - this.scrollTop = _top; | ||
| 787 | - }) | ||
| 788 | - } | ||
| 789 | - // #endif | ||
| 790 | - } | 270 | + |
| 271 | + close() { | ||
| 272 | + this.popupShow = false; | ||
| 273 | + this.levelList = []; | ||
| 274 | + this.selectedValueIndexs = []; | ||
| 275 | + this.tabsIndex = 0; | ||
| 276 | + this.confirmValues = []; | ||
| 277 | + this.$emit('update:show', false); | ||
| 278 | + this.$emit('cancel'); | ||
| 791 | }, | 279 | }, |
| 792 | - // scroll-view滚动监听 | ||
| 793 | - onScroll(event) { | ||
| 794 | - // 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值 | ||
| 795 | - const scrollTop = Math.max(0, event.detail.scrollTop) | ||
| 796 | - // 月份的top值尚未测量出来时不做判断,否则所有月份的判断阈值相同, | ||
| 797 | - // 会把副标题固定到最后一个月份上 | ||
| 798 | - if (!this.months.some(item => typeof item.top === 'number')) return | ||
| 799 | - // 将当前滚动条数值,与各月份的top值比较,可以得出当前滚动到了哪一个月份的索引 | ||
| 800 | - let monthIndex = 0 | ||
| 801 | - for (let i = 0; i < this.months.length; i++) { | ||
| 802 | - const top = this.months[i].top | ||
| 803 | - if (typeof top !== 'number') continue | ||
| 804 | - if (scrollTop >= top) { | ||
| 805 | - monthIndex = i | 280 | + |
| 281 | + tabsChange(item) {}, | ||
| 282 | + toFatherIndex(index) { | ||
| 283 | + this.tabsIndex = index; | ||
| 284 | + }, | ||
| 285 | + levelChange(levelIndex, index) { | ||
| 286 | + this.$set(this.selectedValueIndexs, levelIndex, index); | ||
| 287 | + this.selectedValueIndexs.splice(levelIndex + 1); | ||
| 288 | + this.tabsIndex = Math.min(this.tabsIndex, levelIndex); | ||
| 289 | + this.levelList.splice(levelIndex + 1); | ||
| 290 | + | ||
| 291 | + const currentItem = this.levelList[levelIndex][index]; | ||
| 292 | + if (currentItem && currentItem[this.childrenKey] && currentItem[this.childrenKey].length > 0) { | ||
| 293 | + if (this.levelList.length <= levelIndex + 1) { | ||
| 294 | + this.levelList.push(currentItem[this.childrenKey]); | ||
| 806 | } else { | 295 | } else { |
| 807 | - break | 296 | + this.$set(this.levelList, levelIndex + 1, currentItem[this.childrenKey]); |
| 297 | + } | ||
| 298 | + this.tabsIndex = levelIndex + 1; | ||
| 299 | + // 有子级,不自动确认 | ||
| 300 | + if (this.autoClose) return; | ||
| 301 | + } else { | ||
| 302 | + // 已选到最后一级,自动确认 | ||
| 303 | + if (this.autoClose) { | ||
| 304 | + this.handleConfirm(); | ||
| 808 | } | 305 | } |
| 809 | } | 306 | } |
| 810 | - this.monthIndex = monthIndex | ||
| 811 | - this.scrollIntoViewScroll = `month-${monthIndex}` | ||
| 812 | }, | 307 | }, |
| 813 | - // 更新月份的top值 | ||
| 814 | - onUpdateMonthTop(topArr = []) { | ||
| 815 | - if (this.monthSwitch) { | ||
| 816 | - return | 308 | + emitChange(closePopup = true) { |
| 309 | + const result = this.getSelectedValues(); | ||
| 310 | + this.confirmValues = [...result]; | ||
| 311 | + this.$emit('change', this.confirmValues); | ||
| 312 | + if (closePopup) { | ||
| 313 | + this.close(); | ||
| 817 | } | 314 | } |
| 818 | - this.updateMonthTop(topArr) | ||
| 819 | }, | 315 | }, |
| 820 | - updateMonthTop(topArr = []) { | ||
| 821 | - // 设置对应月份的top值,用于onScroll方法更新月份 | ||
| 822 | - topArr.map((item, index) => { | ||
| 823 | - this.months[index].top = item | ||
| 824 | - }) | ||
| 825 | - | ||
| 826 | - // 获取默认日期的下标 | ||
| 827 | - if (!this.defaultDate) { | ||
| 828 | - // 如果没有设置默认日期,则将当天日期设置为默认选中的日期 | ||
| 829 | - const selected = dayjs().format("YYYY-MM") | ||
| 830 | - this.scrollIntoDefaultMonth(selected) | ||
| 831 | - return | ||
| 832 | - } | ||
| 833 | - let selected = dayjs().format("YYYY-MM"); | ||
| 834 | - // 单选模式,可以是字符串或数组,Date对象等 | ||
| 835 | - if (!test.array(this.defaultDate)) { | ||
| 836 | - selected = dayjs(this.defaultDate).format("YYYY-MM") | ||
| 837 | - } else { | ||
| 838 | - selected = dayjs(this.defaultDate[0]).format("YYYY-MM"); | 316 | + handleCancel() { |
| 317 | + this.close(); | ||
| 318 | + }, | ||
| 319 | + handleConfirm() { | ||
| 320 | + // 校验:必须选择到最后一级 | ||
| 321 | + if (!this.isCurrentLeafNode()) { | ||
| 322 | + uni.showToast({ title: '请选择到最后一级', icon: 'none' }); | ||
| 323 | + return; | ||
| 839 | } | 324 | } |
| 840 | - this.scrollIntoDefaultMonth(selected) | 325 | + const values = this.getSelectedValues(); |
| 326 | + this.confirmValues = [...values]; | ||
| 327 | + // 向外输出 选中值数组 | ||
| 328 | + this.$emit('update:modelValue', values); | ||
| 329 | + this.$emit('confirm', values); | ||
| 330 | + this.close(); | ||
| 841 | } | 331 | } |
| 842 | } | 332 | } |
| 843 | } | 333 | } |
| 844 | </script> | 334 | </script> |
| 845 | 335 | ||
| 846 | -<style lang="scss" scoped> | ||
| 847 | -.u-calendar { | ||
| 848 | - color: var(--up-main-color, $u-main-color); | ||
| 849 | - background-color: var(--up-card-bg-color, #ffffff); | ||
| 850 | - | ||
| 851 | - &__time-panel { | ||
| 852 | - padding: 8px 16px 0; | ||
| 853 | - } | ||
| 854 | - | ||
| 855 | - &__time-row { | ||
| 856 | - height: 34px; | ||
| 857 | - display: flex; | ||
| 858 | - align-items: center; | ||
| 859 | - justify-content: space-between; | ||
| 860 | - margin-bottom: 6px; | ||
| 861 | - } | ||
| 862 | - | ||
| 863 | - &__time-date { | ||
| 864 | - font-size: 13px; | ||
| 865 | - color: #303133; | ||
| 866 | - } | ||
| 867 | - | ||
| 868 | - &__time-trigger { | ||
| 869 | - min-width: 92px; | ||
| 870 | - height: 30px; | ||
| 871 | - padding: 0 10px; | ||
| 872 | - border-radius: 15px; | ||
| 873 | - border: 1px solid #dcdfe6; | ||
| 874 | - display: flex; | ||
| 875 | - align-items: center; | ||
| 876 | - justify-content: center; | ||
| 877 | - } | ||
| 878 | - | ||
| 879 | - &__time-text { | ||
| 880 | - font-size: 14px; | ||
| 881 | - color: #303133; | ||
| 882 | - } | ||
| 883 | - | ||
| 884 | - &__time-picker { | ||
| 885 | - width: 320px; | ||
| 886 | - background-color: #ffffff; | ||
| 887 | - border-radius: 8px; | 336 | +<style lang="scss"> |
| 337 | + .area-box { | ||
| 338 | + width: 100%; | ||
| 888 | overflow: hidden; | 339 | overflow: hidden; |
| 889 | - } | ||
| 890 | - | ||
| 891 | - &__time-picker__header { | ||
| 892 | - height: 44px; | ||
| 893 | - padding: 0 14px; | ||
| 894 | - display: flex; | ||
| 895 | - align-items: center; | ||
| 896 | - justify-content: space-between; | ||
| 897 | - border-bottom: 1px solid #f0f0f0; | ||
| 898 | - } | ||
| 899 | - | ||
| 900 | - &__time-picker__cancel { | ||
| 901 | - font-size: 14px; | ||
| 902 | - color: #909399; | ||
| 903 | - } | 340 | + height: 800rpx; |
| 904 | 341 | ||
| 905 | - &__time-picker__title { | ||
| 906 | - font-size: 15px; | ||
| 907 | - color: #303133; | ||
| 908 | - font-weight: 600; | ||
| 909 | - } | 342 | + >view { |
| 343 | + width: 150%; | ||
| 344 | + transition: transform 0.3s ease-in-out 0s; | ||
| 345 | + transform: translateX(0); | ||
| 910 | 346 | ||
| 911 | - &__time-picker__confirm { | ||
| 912 | - font-size: 14px; | ||
| 913 | - color: #3c9cff; | ||
| 914 | - } | ||
| 915 | - | ||
| 916 | - &__time-picker__body { | ||
| 917 | - width: 320px; | ||
| 918 | - height: 200px; | ||
| 919 | - } | 347 | + &.change { |
| 348 | + transform: translateX(-33.3333333%); | ||
| 349 | + } | ||
| 350 | + } | ||
| 920 | 351 | ||
| 921 | - &__time-picker__item { | ||
| 922 | - height: 40px; | ||
| 923 | - line-height: 40px; | ||
| 924 | - text-align: center; | ||
| 925 | - font-size: 16px; | ||
| 926 | - color: #303133; | 352 | + .area-item { |
| 353 | + height: 800rpx; | ||
| 354 | + } | ||
| 927 | } | 355 | } |
| 928 | 356 | ||
| 929 | - &__confirm { | ||
| 930 | - padding: 7px 18px; | 357 | + .u-cascader-action { |
| 358 | + border-top: 1px solid var(--up-border-color, #eee); | ||
| 931 | } | 359 | } |
| 932 | -} | ||
| 933 | -</style> | 360 | +</style> |
| 934 | \ No newline at end of file | 361 | \ No newline at end of file |