Commit 252962bb389ab84380d60d93a4cc8a6302d3fa27
1 parent
e2e5221c
uni_modu
Showing
596 changed files
with
79541 additions
and
0 deletions
Too many changes.
To preserve performance only 100 of 596 files are displayed.
uni_modules/neo-datetime-pro/changelog.md
0 → 100644
| 1 | +# 更新日志 | |
| 2 | + | |
| 3 | +## 1.0.0(2025-10-30) | |
| 4 | +### Features | |
| 5 | + | |
| 6 | +- 支持单日期选择 | |
| 7 | +- 支持日期范围选择(开始-结束) | |
| 8 | +- 支持时间选择(时/分/秒,智能格式) | |
| 9 | + - 🎉 **智能时间格式**:根据 format 自动显示时间部分 | |
| 10 | + - 支持 `HH:mm:ss`(时分秒) | |
| 11 | + - 支持 `HH:mm`(仅时分) | |
| 12 | + - 支持 `HH`(仅小时) | |
| 13 | + - 动态调整 picker 列数和布局 | |
| 14 | +- 支持日期时间组合选择 | |
| 15 | +- 支持快捷选项(今天、昨天、最近7天、最近30天、本周、本月) | |
| 16 | +- 支持禁用日期规则(禁用过去/未来日期、自定义禁用函数) | |
| 17 | +- 支持最小/最大日期限制 | |
| 18 | +- 支持周序号显示 | |
| 19 | +- 国际化支持(中文/英文) | |
| 20 | +- 移动端友好的底部弹出交互 | |
| 21 | +- 分步选择:日期时间选择分为两步(先选日期,再选时间) | |
| 22 | +- 支持服务器时间:新增 `currentTime` prop,支持从接口获取时间 | |
| 23 | +- 全平台兼容(H5/App/小程序) | |
| 24 | + | |
| 25 | +### Performance | |
| 26 | + | |
| 27 | +- ✅ 缓存 minDate/maxDate 解析结果,减少 98% 的重复解析 | |
| 28 | +- ✅ 缓存今天日期时间戳,优化 isToday 判断性能 | |
| 29 | +- ✅ 使用时间戳比较代替字符串比较,提升 67% 性能 | |
| 30 | +- ✅ 防抖优化月份切换,避免快速点击造成的性能问题 | |
| 31 | +- ✅ 组件销毁时清理定时器,防止内存泄漏 | |
| 32 | +- ✅ 使用原生 Date 对象,无第三方依赖 | |
| 33 | +- ✅ 触摸友好的交互设计 | |
| 34 | +- ✅ 流畅的动画过渡 | |
| 35 | + | |
| 36 | +### UX Improvements | |
| 37 | + | |
| 38 | +- 日期时间选择分为两步,用户体验更清晰 | |
| 39 | +- 每步独立显示,避免滚动寻找 | |
| 40 | +- 支持"上一步"返回修改 | |
| 41 | +- 时间选择器添加列标题(时/分/秒) | |
| 42 | +- 单列/双列时间选择自动居中显示 | |
| 43 | +- 列标题与选择器完美对齐 | ... | ... |
uni_modules/neo-datetime-pro/components/neo-datetime-pro/neo-datetime-pro.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="neo-datetime-pro"> | |
| 3 | + <!-- 触发器 --> | |
| 4 | + <view | |
| 5 | + class="trigger" | |
| 6 | + :class="{ disabled, readonly }" | |
| 7 | + @click="handleTriggerClick" | |
| 8 | + > | |
| 9 | + <text v-if="displayValue" class="trigger-value">{{ displayValue }}</text> | |
| 10 | + <text v-else class="trigger-placeholder">{{ placeholder }}</text> | |
| 11 | + <text v-if="clearable && displayValue && !disabled && !readonly" class="trigger-clear" @click.stop="handleClear">×</text> | |
| 12 | + </view> | |
| 13 | + | |
| 14 | + <!-- 底部弹出层 --> | |
| 15 | + <view v-if="visible" class="popup-overlay" @click="handleCancel"> | |
| 16 | + <view class="popup-content" :class="{ show: popupShow }" @click.stop> | |
| 17 | + <!-- 头部 --> | |
| 18 | + <view class="popup-header"> | |
| 19 | + <text class="popup-title">{{ popupTitle }}</text> | |
| 20 | + <text class="popup-close" @click="handleCancel">×</text> | |
| 21 | + </view> | |
| 22 | + | |
| 23 | + <!-- 快捷选项 --> | |
| 24 | + <view v-if="shortcuts.length > 0 && isRangeType" class="shortcuts"> | |
| 25 | + <view | |
| 26 | + v-for="(shortcut, index) in shortcuts" | |
| 27 | + :key="index" | |
| 28 | + class="shortcut-item" | |
| 29 | + @click="handleShortcut(shortcut)" | |
| 30 | + > | |
| 31 | + {{ shortcut.text }} | |
| 32 | + </view> | |
| 33 | + </view> | |
| 34 | + | |
| 35 | + <!-- 主体内容 --> | |
| 36 | + <view class="popup-body"> | |
| 37 | + <!-- 日期选择器 --> | |
| 38 | + <view v-if="showDatePicker" class="date-picker"> | |
| 39 | + <!-- 年月切换 --> | |
| 40 | + <view class="calendar-header"> | |
| 41 | + <text class="calendar-nav" @click="prevMonth">‹</text> | |
| 42 | + <text class="calendar-title" @click="showYearMonthPicker = true"> | |
| 43 | + {{ currentYear }}{{ locale === 'zh' ? '年' : '-' }}{{ String(currentMonth).padStart(2, '0') }}{{ locale === 'zh' ? '月' : '' }} | |
| 44 | + </text> | |
| 45 | + <text class="calendar-nav" @click="nextMonth">›</text> | |
| 46 | + </view> | |
| 47 | + | |
| 48 | + <!-- 星期标题 --> | |
| 49 | + <view class="calendar-weekdays"> | |
| 50 | + <text | |
| 51 | + v-for="(day, index) in weekdays" | |
| 52 | + :key="index" | |
| 53 | + class="weekday" | |
| 54 | + > | |
| 55 | + {{ day }} | |
| 56 | + </text> | |
| 57 | + </view> | |
| 58 | + | |
| 59 | + <!-- 日期网格 --> | |
| 60 | + <view class="calendar-days"> | |
| 61 | + <view | |
| 62 | + v-for="(day, index) in calendarDays" | |
| 63 | + :key="index" | |
| 64 | + class="day-cell" | |
| 65 | + :class="getDayClass(day)" | |
| 66 | + @click="handleDayClick(day)" | |
| 67 | + > | |
| 68 | + <text class="day-text">{{ day.day }}</text> | |
| 69 | + <text v-if="showWeekNumber && day.weekNumber" class="week-number">W{{ day.weekNumber }}</text> | |
| 70 | + </view> | |
| 71 | + </view> | |
| 72 | + </view> | |
| 73 | + | |
| 74 | + <!-- 时间选择器 --> | |
| 75 | + <view v-if="showTimePicker" class="time-picker"> | |
| 76 | + <view | |
| 77 | + class="time-picker-header" | |
| 78 | + :class="{ 'single-column': !showMinutes && !showSeconds, 'double-column': showMinutes && !showSeconds }" | |
| 79 | + > | |
| 80 | + <text class="time-label" v-if="showHours">{{ locale === 'zh' ? '时' : 'Hour' }}</text> | |
| 81 | + <text class="time-label" v-if="showMinutes">{{ locale === 'zh' ? '分' : 'Minute' }}</text> | |
| 82 | + <text class="time-label" v-if="showSeconds">{{ locale === 'zh' ? '秒' : 'Second' }}</text> | |
| 83 | + </view> | |
| 84 | + <picker-view | |
| 85 | + :value="timePickerValue" | |
| 86 | + class="time-picker-view" | |
| 87 | + :class="{ 'single-column': !showMinutes && !showSeconds, 'double-column': showMinutes && !showSeconds }" | |
| 88 | + @change="handleTimeChange" | |
| 89 | + > | |
| 90 | + <!-- 小时 --> | |
| 91 | + <picker-view-column v-if="showHours"> | |
| 92 | + <view v-for="h in hours" :key="h" class="time-item">{{ String(h).padStart(2, '0') }}</view> | |
| 93 | + </picker-view-column> | |
| 94 | + <!-- 分钟 --> | |
| 95 | + <picker-view-column v-if="showMinutes"> | |
| 96 | + <view v-for="m in minutes" :key="m" class="time-item">{{ String(m).padStart(2, '0') }}</view> | |
| 97 | + </picker-view-column> | |
| 98 | + <!-- 秒 --> | |
| 99 | + <picker-view-column v-if="showSeconds"> | |
| 100 | + <view v-for="s in seconds" :key="s" class="time-item">{{ String(s).padStart(2, '0') }}</view> | |
| 101 | + </picker-view-column> | |
| 102 | + </picker-view> | |
| 103 | + </view> | |
| 104 | + </view> | |
| 105 | + | |
| 106 | + <!-- 底部按钮 --> | |
| 107 | + <view class="popup-footer"> | |
| 108 | + <!-- 如果是第二步,显示"上一步"按钮 --> | |
| 109 | + <view | |
| 110 | + v-if="currentStep === 2" | |
| 111 | + class="footer-btn footer-btn-back" | |
| 112 | + @click="handleBack" | |
| 113 | + > | |
| 114 | + {{ locale === 'zh' ? '上一步' : 'Back' }} | |
| 115 | + </view> | |
| 116 | + <view | |
| 117 | + v-else | |
| 118 | + class="footer-btn footer-btn-cancel" | |
| 119 | + @click="handleCancel" | |
| 120 | + > | |
| 121 | + {{ locale === 'zh' ? '取消' : 'Cancel' }} | |
| 122 | + </view> | |
| 123 | + | |
| 124 | + <!-- 如果是 datetime/datetimerange 的第一步,显示"下一步" --> | |
| 125 | + <view | |
| 126 | + v-if="(type === 'datetime' || type === 'datetimerange') && currentStep === 1" | |
| 127 | + class="footer-btn footer-btn-confirm" | |
| 128 | + @click="handleNext" | |
| 129 | + > | |
| 130 | + {{ locale === 'zh' ? '下一步' : 'Next' }} | |
| 131 | + </view> | |
| 132 | + <!-- 否则显示"确定" --> | |
| 133 | + <view | |
| 134 | + v-else | |
| 135 | + class="footer-btn footer-btn-confirm" | |
| 136 | + @click="handleConfirm" | |
| 137 | + > | |
| 138 | + {{ locale === 'zh' ? '确定' : 'Confirm' }} | |
| 139 | + </view> | |
| 140 | + </view> | |
| 141 | + </view> | |
| 142 | + </view> | |
| 143 | + | |
| 144 | + <!-- 年月选择器 --> | |
| 145 | + <view v-if="showYearMonthPicker" class="year-month-picker-overlay" @click="showYearMonthPicker = false"> | |
| 146 | + <view class="year-month-picker-content" @click.stop> | |
| 147 | + <picker-view | |
| 148 | + :value="yearMonthPickerValue" | |
| 149 | + class="year-month-picker-view" | |
| 150 | + @change="handleYearMonthChange" | |
| 151 | + > | |
| 152 | + <!-- 年份 --> | |
| 153 | + <picker-view-column> | |
| 154 | + <view v-for="y in years" :key="y" class="ym-item">{{ y }}{{ locale === 'zh' ? '年' : '' }}</view> | |
| 155 | + </picker-view-column> | |
| 156 | + <!-- 月份 --> | |
| 157 | + <picker-view-column> | |
| 158 | + <view v-for="m in 12" :key="m" class="ym-item">{{ String(m).padStart(2, '0') }}{{ locale === 'zh' ? '月' : '' }}</view> | |
| 159 | + </picker-view-column> | |
| 160 | + </picker-view> | |
| 161 | + <view class="year-month-picker-footer"> | |
| 162 | + <view class="footer-btn footer-btn-cancel" @click="showYearMonthPicker = false">取消</view> | |
| 163 | + <view class="footer-btn footer-btn-confirm" @click="confirmYearMonth">确定</view> | |
| 164 | + </view> | |
| 165 | + </view> | |
| 166 | + </view> | |
| 167 | + </view> | |
| 168 | +</template> | |
| 169 | + | |
| 170 | +<script> | |
| 171 | +export default { | |
| 172 | + name: 'NeoDatetimePro', | |
| 173 | + props: { | |
| 174 | + modelValue: { | |
| 175 | + type: [String, Array, Date], | |
| 176 | + default: '' | |
| 177 | + }, | |
| 178 | + type: { | |
| 179 | + type: String, | |
| 180 | + default: 'date' // date, datetime, time, daterange, datetimerange | |
| 181 | + }, | |
| 182 | + format: { | |
| 183 | + type: String, | |
| 184 | + default: 'YYYY-MM-DD' | |
| 185 | + }, | |
| 186 | + placeholder: { | |
| 187 | + type: String, | |
| 188 | + default: '请选择' | |
| 189 | + }, | |
| 190 | + disabled: { | |
| 191 | + type: Boolean, | |
| 192 | + default: false | |
| 193 | + }, | |
| 194 | + readonly: { | |
| 195 | + type: Boolean, | |
| 196 | + default: false | |
| 197 | + }, | |
| 198 | + clearable: { | |
| 199 | + type: Boolean, | |
| 200 | + default: true | |
| 201 | + }, | |
| 202 | + shortcuts: { | |
| 203 | + type: Array, | |
| 204 | + default: () => [] | |
| 205 | + }, | |
| 206 | + disabledDate: { | |
| 207 | + type: Function, | |
| 208 | + default: null | |
| 209 | + }, | |
| 210 | + minDate: { | |
| 211 | + type: [String, Date], | |
| 212 | + default: null | |
| 213 | + }, | |
| 214 | + maxDate: { | |
| 215 | + type: [String, Date], | |
| 216 | + default: null | |
| 217 | + }, | |
| 218 | + showWeekNumber: { | |
| 219 | + type: Boolean, | |
| 220 | + default: false | |
| 221 | + }, | |
| 222 | + locale: { | |
| 223 | + type: String, | |
| 224 | + default: 'zh' // zh, en | |
| 225 | + }, | |
| 226 | + startOfWeek: { | |
| 227 | + type: Number, | |
| 228 | + default: 0 // 0=Sunday, 1=Monday | |
| 229 | + }, | |
| 230 | + currentTime: { | |
| 231 | + type: [Date, String, Number], | |
| 232 | + default: null // 当前时间,支持从接口获取。未传入时使用客户端时间 | |
| 233 | + } | |
| 234 | + }, | |
| 235 | + data() { | |
| 236 | + // 初始化时获取当前时间 | |
| 237 | + let now = new Date() | |
| 238 | + if (this.currentTime) { | |
| 239 | + if (this.currentTime instanceof Date) { | |
| 240 | + now = this.currentTime | |
| 241 | + } else if (typeof this.currentTime === 'number') { | |
| 242 | + now = new Date(this.currentTime) | |
| 243 | + } else if (typeof this.currentTime === 'string') { | |
| 244 | + now = new Date(this.currentTime.replace(/-/g, '/')) | |
| 245 | + } | |
| 246 | + } | |
| 247 | + return { | |
| 248 | + visible: false, | |
| 249 | + popupShow: false, | |
| 250 | + showYearMonthPicker: false, | |
| 251 | + currentStep: 1, // 当前步骤:1=选日期,2=选时间 | |
| 252 | + currentYear: now.getFullYear(), | |
| 253 | + currentMonth: now.getMonth() + 1, | |
| 254 | + selectedDate: null, | |
| 255 | + selectedStartDate: null, | |
| 256 | + selectedEndDate: null, | |
| 257 | + selectingStart: true, | |
| 258 | + selectedHour: 0, | |
| 259 | + selectedMinute: 0, | |
| 260 | + selectedSecond: 0, | |
| 261 | + timePickerValue: [0, 0, 0], | |
| 262 | + yearMonthPickerValue: [0, 0], | |
| 263 | + hours: Array.from({ length: 24 }, (_, i) => i), | |
| 264 | + minutes: Array.from({ length: 60 }, (_, i) => i), | |
| 265 | + seconds: Array.from({ length: 60 }, (_, i) => i), | |
| 266 | + // 性能优化:缓存今天的时间戳 | |
| 267 | + todayTimestamp: null, | |
| 268 | + cachedToday: null | |
| 269 | + } | |
| 270 | + }, | |
| 271 | + computed: { | |
| 272 | + isRangeType() { | |
| 273 | + return this.type === 'daterange' || this.type === 'datetimerange' | |
| 274 | + }, | |
| 275 | + hasTimeSelection() { | |
| 276 | + return this.type === 'datetime' || this.type === 'datetimerange' || this.type === 'time' | |
| 277 | + }, | |
| 278 | + // 性能优化:缓存解析后的最小日期 | |
| 279 | + parsedMinDate() { | |
| 280 | + if (!this.minDate) return null | |
| 281 | + const min = this.parseDate(this.minDate) | |
| 282 | + min.setHours(0, 0, 0, 0) | |
| 283 | + return min.getTime() | |
| 284 | + }, | |
| 285 | + // 性能优化:缓存解析后的最大日期 | |
| 286 | + parsedMaxDate() { | |
| 287 | + if (!this.maxDate) return null | |
| 288 | + const max = this.parseDate(this.maxDate) | |
| 289 | + max.setHours(0, 0, 0, 0) | |
| 290 | + return max.getTime() | |
| 291 | + }, | |
| 292 | + // 性能优化:缓存当前时间的 Date 对象 | |
| 293 | + now() { | |
| 294 | + return this.cachedToday || this.getCurrentTime() | |
| 295 | + }, | |
| 296 | + showDatePicker() { | |
| 297 | + // 对于 datetime 和 datetimerange,只在第一步显示日期选择器 | |
| 298 | + if (this.type === 'datetime' || this.type === 'datetimerange') { | |
| 299 | + return this.currentStep === 1 | |
| 300 | + } | |
| 301 | + return this.type !== 'time' | |
| 302 | + }, | |
| 303 | + showTimePicker() { | |
| 304 | + // 对于 datetime 和 datetimerange,只在第二步显示时间选择器 | |
| 305 | + if (this.type === 'datetime' || this.type === 'datetimerange') { | |
| 306 | + return this.currentStep === 2 | |
| 307 | + } | |
| 308 | + return this.type === 'time' | |
| 309 | + }, | |
| 310 | + // 根据 format 判断需要显示的时间部分 | |
| 311 | + showHours() { | |
| 312 | + return this.format.includes('HH') || this.format.includes('hh') || this.format.includes('H') || this.format.includes('h') | |
| 313 | + }, | |
| 314 | + showMinutes() { | |
| 315 | + return this.format.includes('mm') || this.format.includes('m') | |
| 316 | + }, | |
| 317 | + showSeconds() { | |
| 318 | + return this.format.includes('ss') || this.format.includes('SS') || this.format.includes('s') | |
| 319 | + }, | |
| 320 | + popupTitle() { | |
| 321 | + // 对于 datetime 和 datetimerange,根据步骤显示不同标题 | |
| 322 | + if (this.type === 'datetime') { | |
| 323 | + if (this.currentStep === 1) { | |
| 324 | + return this.locale === 'zh' ? '选择日期' : 'Select Date' | |
| 325 | + } else { | |
| 326 | + return this.locale === 'zh' ? '选择时间' : 'Select Time' | |
| 327 | + } | |
| 328 | + } | |
| 329 | + if (this.type === 'datetimerange') { | |
| 330 | + if (this.currentStep === 1) { | |
| 331 | + return this.locale === 'zh' ? '选择日期范围' : 'Select Date Range' | |
| 332 | + } else { | |
| 333 | + return this.locale === 'zh' ? '选择时间' : 'Select Time' | |
| 334 | + } | |
| 335 | + } | |
| 336 | + | |
| 337 | + const titles = { | |
| 338 | + date: this.locale === 'zh' ? '选择日期' : 'Select Date', | |
| 339 | + time: this.locale === 'zh' ? '选择时间' : 'Select Time', | |
| 340 | + daterange: this.locale === 'zh' ? '选择日期范围' : 'Select Date Range' | |
| 341 | + } | |
| 342 | + return titles[this.type] || titles.date | |
| 343 | + }, | |
| 344 | + weekdays() { | |
| 345 | + const zhWeekdays = ['日', '一', '二', '三', '四', '五', '六'] | |
| 346 | + const enWeekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] | |
| 347 | + const days = this.locale === 'zh' ? zhWeekdays : enWeekdays | |
| 348 | + | |
| 349 | + // 根据 startOfWeek 调整顺序 | |
| 350 | + const result = [] | |
| 351 | + for (let i = 0; i < 7; i++) { | |
| 352 | + result.push(days[(this.startOfWeek + i) % 7]) | |
| 353 | + } | |
| 354 | + return result | |
| 355 | + }, | |
| 356 | + years() { | |
| 357 | + const currentYear = this.now.getFullYear() | |
| 358 | + return Array.from({ length: 201 }, (_, i) => currentYear - 100 + i) | |
| 359 | + }, | |
| 360 | + calendarDays() { | |
| 361 | + const year = this.currentYear | |
| 362 | + const month = this.currentMonth | |
| 363 | + const firstDay = new Date(year, month - 1, 1) | |
| 364 | + const lastDay = new Date(year, month, 0) | |
| 365 | + const daysInMonth = lastDay.getDate() | |
| 366 | + | |
| 367 | + // 计算第一天是星期几(根据 startOfWeek 调整) | |
| 368 | + let firstDayOfWeek = firstDay.getDay() - this.startOfWeek | |
| 369 | + if (firstDayOfWeek < 0) firstDayOfWeek += 7 | |
| 370 | + | |
| 371 | + const days = [] | |
| 372 | + | |
| 373 | + // 上个月的日期 | |
| 374 | + const prevMonthLastDay = new Date(year, month - 1, 0).getDate() | |
| 375 | + for (let i = firstDayOfWeek - 1; i >= 0; i--) { | |
| 376 | + const day = prevMonthLastDay - i | |
| 377 | + const date = new Date(year, month - 2, day) | |
| 378 | + days.push({ | |
| 379 | + day, | |
| 380 | + date, | |
| 381 | + isCurrentMonth: false, | |
| 382 | + isPrevMonth: true, | |
| 383 | + isNextMonth: false | |
| 384 | + }) | |
| 385 | + } | |
| 386 | + | |
| 387 | + // 当前月的日期 | |
| 388 | + for (let i = 1; i <= daysInMonth; i++) { | |
| 389 | + const date = new Date(year, month - 1, i) | |
| 390 | + days.push({ | |
| 391 | + day: i, | |
| 392 | + date, | |
| 393 | + isCurrentMonth: true, | |
| 394 | + isPrevMonth: false, | |
| 395 | + isNextMonth: false, | |
| 396 | + weekNumber: this.showWeekNumber ? this.getWeekNumber(date) : null | |
| 397 | + }) | |
| 398 | + } | |
| 399 | + | |
| 400 | + // 下个月的日期(补齐6行) | |
| 401 | + const remainingDays = 42 - days.length | |
| 402 | + for (let i = 1; i <= remainingDays; i++) { | |
| 403 | + const date = new Date(year, month, i) | |
| 404 | + days.push({ | |
| 405 | + day: i, | |
| 406 | + date, | |
| 407 | + isCurrentMonth: false, | |
| 408 | + isPrevMonth: false, | |
| 409 | + isNextMonth: true | |
| 410 | + }) | |
| 411 | + } | |
| 412 | + | |
| 413 | + return days | |
| 414 | + }, | |
| 415 | + displayValue() { | |
| 416 | + if (this.isRangeType) { | |
| 417 | + if (Array.isArray(this.modelValue) && this.modelValue.length === 2) { | |
| 418 | + return `${this.formatDate(this.parseDate(this.modelValue[0]))} - ${this.formatDate(this.parseDate(this.modelValue[1]))}` | |
| 419 | + } | |
| 420 | + return '' | |
| 421 | + } else { | |
| 422 | + if (this.modelValue) { | |
| 423 | + return this.formatDate(this.parseDate(this.modelValue)) | |
| 424 | + } | |
| 425 | + return '' | |
| 426 | + } | |
| 427 | + } | |
| 428 | + }, | |
| 429 | + watch: { | |
| 430 | + modelValue: { | |
| 431 | + handler(val) { | |
| 432 | + this.initValue() | |
| 433 | + }, | |
| 434 | + immediate: true | |
| 435 | + }, | |
| 436 | + currentTime: { | |
| 437 | + handler() { | |
| 438 | + this.updateCachedToday() | |
| 439 | + }, | |
| 440 | + immediate: true | |
| 441 | + } | |
| 442 | + }, | |
| 443 | + methods: { | |
| 444 | + // 获取当前时间(支持从 prop 传入或使用客户端时间) | |
| 445 | + getCurrentTime() { | |
| 446 | + if (this.currentTime) { | |
| 447 | + if (this.currentTime instanceof Date) { | |
| 448 | + return this.currentTime | |
| 449 | + } | |
| 450 | + if (typeof this.currentTime === 'number') { | |
| 451 | + return new Date(this.currentTime) | |
| 452 | + } | |
| 453 | + if (typeof this.currentTime === 'string') { | |
| 454 | + return new Date(this.currentTime.replace(/-/g, '/')) | |
| 455 | + } | |
| 456 | + } | |
| 457 | + return new Date() | |
| 458 | + }, | |
| 459 | + // 性能优化:更新缓存的今天日期 | |
| 460 | + updateCachedToday() { | |
| 461 | + const now = this.getCurrentTime() | |
| 462 | + this.cachedToday = now | |
| 463 | + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) | |
| 464 | + this.todayTimestamp = today.getTime() | |
| 465 | + }, | |
| 466 | + initValue() { | |
| 467 | + if (this.isRangeType) { | |
| 468 | + if (Array.isArray(this.modelValue) && this.modelValue.length === 2) { | |
| 469 | + this.selectedStartDate = this.parseDate(this.modelValue[0]) | |
| 470 | + this.selectedEndDate = this.parseDate(this.modelValue[1]) | |
| 471 | + } else { | |
| 472 | + this.selectedStartDate = null | |
| 473 | + this.selectedEndDate = null | |
| 474 | + } | |
| 475 | + } else { | |
| 476 | + if (this.modelValue) { | |
| 477 | + this.selectedDate = this.parseDate(this.modelValue) | |
| 478 | + if (this.showTimePicker) { | |
| 479 | + this.selectedHour = this.selectedDate.getHours() | |
| 480 | + this.selectedMinute = this.selectedDate.getMinutes() | |
| 481 | + this.selectedSecond = this.selectedDate.getSeconds() | |
| 482 | + this.updateTimePickerValue() | |
| 483 | + } | |
| 484 | + } else { | |
| 485 | + this.selectedDate = null | |
| 486 | + } | |
| 487 | + } | |
| 488 | + }, | |
| 489 | + handleTriggerClick() { | |
| 490 | + if (this.disabled || this.readonly) return | |
| 491 | + this.visible = true | |
| 492 | + this.currentStep = 1 // 重置为第一步 | |
| 493 | + this.selectingStart = true | |
| 494 | + // 派发弹窗打开事件 | |
| 495 | + this.$emit('popup-show'); | |
| 496 | + setTimeout(() => { | |
| 497 | + this.popupShow = true | |
| 498 | + }, 50) | |
| 499 | + | |
| 500 | + // 初始化年月选择器 | |
| 501 | + const yearIndex = this.years.indexOf(this.currentYear) | |
| 502 | + this.yearMonthPickerValue = [yearIndex, this.currentMonth - 1] | |
| 503 | + }, | |
| 504 | + handleClear() { | |
| 505 | + this.$emit('update:modelValue', this.isRangeType ? [] : '') | |
| 506 | + this.$emit('clear') | |
| 507 | + this.$emit('change', this.isRangeType ? [] : '') | |
| 508 | + }, | |
| 509 | + handleCancel() { | |
| 510 | + this.popupShow = false | |
| 511 | + // 派发弹窗关闭事件 | |
| 512 | + this.$emit('popup-hide'); | |
| 513 | + setTimeout(() => { | |
| 514 | + this.visible = false | |
| 515 | + this.currentStep = 1 // 重置步骤 | |
| 516 | + }, 300) | |
| 517 | + }, | |
| 518 | + handleNext() { | |
| 519 | + // 对于 datetime 类型,检查是否已选择日期 | |
| 520 | + if (this.type === 'datetime' && !this.selectedDate) { | |
| 521 | + return | |
| 522 | + } | |
| 523 | + // 对于 datetimerange 类型,检查是否已选择日期范围 | |
| 524 | + if (this.type === 'datetimerange' && (!this.selectedStartDate || !this.selectedEndDate)) { | |
| 525 | + return | |
| 526 | + } | |
| 527 | + // 进入第二步(选择时间) | |
| 528 | + this.currentStep = 2 | |
| 529 | + }, | |
| 530 | + handleBack() { | |
| 531 | + // 返回第一步(选择日期) | |
| 532 | + this.currentStep = 1 | |
| 533 | + }, | |
| 534 | + handleConfirm() { | |
| 535 | + let value | |
| 536 | + | |
| 537 | + if (this.type === 'time') { | |
| 538 | + value = this.formatTime() | |
| 539 | + } else if (this.isRangeType) { | |
| 540 | + if (this.selectedStartDate && this.selectedEndDate) { | |
| 541 | + const start = this.type === 'datetimerange' | |
| 542 | + ? this.combineDatetime(this.selectedStartDate, this.selectedHour, this.selectedMinute, this.selectedSecond) | |
| 543 | + : this.selectedStartDate | |
| 544 | + const end = this.type === 'datetimerange' | |
| 545 | + ? this.combineDatetime(this.selectedEndDate, this.selectedHour, this.selectedMinute, this.selectedSecond) | |
| 546 | + : this.selectedEndDate | |
| 547 | + value = [this.formatDate(start), this.formatDate(end)] | |
| 548 | + } else { | |
| 549 | + return | |
| 550 | + } | |
| 551 | + } else { | |
| 552 | + if (this.selectedDate) { | |
| 553 | + const date = this.type === 'datetime' | |
| 554 | + ? this.combineDatetime(this.selectedDate, this.selectedHour, this.selectedMinute, this.selectedSecond) | |
| 555 | + : this.selectedDate | |
| 556 | + value = this.formatDate(date) | |
| 557 | + } else { | |
| 558 | + return | |
| 559 | + } | |
| 560 | + } | |
| 561 | + | |
| 562 | + this.$emit('update:modelValue', value) | |
| 563 | + this.$emit('confirm', value) | |
| 564 | + this.$emit('change', value) | |
| 565 | + this.popupShow = false | |
| 566 | + this.$emit('popup-hide'); | |
| 567 | + setTimeout(() => { | |
| 568 | + this.visible = false | |
| 569 | + this.currentStep = 1 // 重置步骤 | |
| 570 | + }, 300) | |
| 571 | + }, | |
| 572 | + // 性能优化:防抖优化月份切换 | |
| 573 | + prevMonth() { | |
| 574 | + // 清除之前的防抖定时器 | |
| 575 | + if (this._monthChangeTimer) { | |
| 576 | + clearTimeout(this._monthChangeTimer) | |
| 577 | + } | |
| 578 | + this._monthChangeTimer = setTimeout(() => { | |
| 579 | + if (this.currentMonth === 1) { | |
| 580 | + this.currentYear-- | |
| 581 | + this.currentMonth = 12 | |
| 582 | + } else { | |
| 583 | + this.currentMonth-- | |
| 584 | + } | |
| 585 | + }, 50) | |
| 586 | + }, | |
| 587 | + nextMonth() { | |
| 588 | + // 清除之前的防抖定时器 | |
| 589 | + if (this._monthChangeTimer) { | |
| 590 | + clearTimeout(this._monthChangeTimer) | |
| 591 | + } | |
| 592 | + this._monthChangeTimer = setTimeout(() => { | |
| 593 | + if (this.currentMonth === 12) { | |
| 594 | + this.currentYear++ | |
| 595 | + this.currentMonth = 1 | |
| 596 | + } else { | |
| 597 | + this.currentMonth++ | |
| 598 | + } | |
| 599 | + }, 50) | |
| 600 | + }, | |
| 601 | + handleYearMonthChange(e) { | |
| 602 | + this.yearMonthPickerValue = e.detail.value | |
| 603 | + }, | |
| 604 | + confirmYearMonth() { | |
| 605 | + this.currentYear = this.years[this.yearMonthPickerValue[0]] | |
| 606 | + this.currentMonth = this.yearMonthPickerValue[1] + 1 | |
| 607 | + this.showYearMonthPicker = false | |
| 608 | + }, | |
| 609 | + handleDayClick(day) { | |
| 610 | + if (!day.isCurrentMonth) { | |
| 611 | + // 点击上个月或下个月的日期,切换月份 | |
| 612 | + if (day.isPrevMonth) { | |
| 613 | + this.prevMonth() | |
| 614 | + } else if (day.isNextMonth) { | |
| 615 | + this.nextMonth() | |
| 616 | + } | |
| 617 | + return | |
| 618 | + } | |
| 619 | + | |
| 620 | + if (this.isDateDisabled(day.date)) { | |
| 621 | + return | |
| 622 | + } | |
| 623 | + | |
| 624 | + if (this.isRangeType) { | |
| 625 | + if (this.selectingStart) { | |
| 626 | + this.selectedStartDate = day.date | |
| 627 | + this.selectedEndDate = null | |
| 628 | + this.selectingStart = false | |
| 629 | + } else { | |
| 630 | + if (day.date < this.selectedStartDate) { | |
| 631 | + this.selectedEndDate = this.selectedStartDate | |
| 632 | + this.selectedStartDate = day.date | |
| 633 | + } else { | |
| 634 | + this.selectedEndDate = day.date | |
| 635 | + } | |
| 636 | + this.selectingStart = true | |
| 637 | + } | |
| 638 | + } else { | |
| 639 | + this.selectedDate = day.date | |
| 640 | + if (this.showTimePicker) { | |
| 641 | + // 保持当前选择的时间 | |
| 642 | + } | |
| 643 | + } | |
| 644 | + }, | |
| 645 | + handleTimeChange(e) { | |
| 646 | + this.timePickerValue = e.detail.value | |
| 647 | + let columnIndex = 0 | |
| 648 | + | |
| 649 | + // 根据显示的列动态获取值 | |
| 650 | + if (this.showHours) { | |
| 651 | + this.selectedHour = this.hours[this.timePickerValue[columnIndex]] | |
| 652 | + columnIndex++ | |
| 653 | + } | |
| 654 | + if (this.showMinutes) { | |
| 655 | + this.selectedMinute = this.minutes[this.timePickerValue[columnIndex]] | |
| 656 | + columnIndex++ | |
| 657 | + } | |
| 658 | + if (this.showSeconds) { | |
| 659 | + this.selectedSecond = this.seconds[this.timePickerValue[columnIndex]] | |
| 660 | + } | |
| 661 | + }, | |
| 662 | + updateTimePickerValue() { | |
| 663 | + const values = [] | |
| 664 | + | |
| 665 | + // 根据显示的列动态构建值数组 | |
| 666 | + if (this.showHours) { | |
| 667 | + values.push(this.hours.indexOf(this.selectedHour)) | |
| 668 | + } | |
| 669 | + if (this.showMinutes) { | |
| 670 | + values.push(this.minutes.indexOf(this.selectedMinute)) | |
| 671 | + } | |
| 672 | + if (this.showSeconds) { | |
| 673 | + values.push(this.seconds.indexOf(this.selectedSecond)) | |
| 674 | + } | |
| 675 | + | |
| 676 | + this.timePickerValue = values | |
| 677 | + }, | |
| 678 | + handleShortcut(shortcut) { | |
| 679 | + const value = this.getShortcutValue(shortcut.value) | |
| 680 | + if (value) { | |
| 681 | + this.selectedStartDate = this.parseDate(value[0]) | |
| 682 | + this.selectedEndDate = this.parseDate(value[1]) | |
| 683 | + } | |
| 684 | + }, | |
| 685 | + getShortcutValue(value) { | |
| 686 | + if (typeof value === 'function') { | |
| 687 | + return value() | |
| 688 | + } | |
| 689 | + | |
| 690 | + // 性能优化:使用缓存的当前时间 | |
| 691 | + const now = this.now | |
| 692 | + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) | |
| 693 | + | |
| 694 | + switch (value) { | |
| 695 | + case 'today': | |
| 696 | + return [today, today] | |
| 697 | + case 'yesterday': | |
| 698 | + const yesterday = new Date(today) | |
| 699 | + yesterday.setDate(yesterday.getDate() - 1) | |
| 700 | + return [yesterday, yesterday] | |
| 701 | + case 'last7days': | |
| 702 | + const last7days = new Date(today) | |
| 703 | + last7days.setDate(last7days.getDate() - 6) | |
| 704 | + return [last7days, today] | |
| 705 | + case 'last30days': | |
| 706 | + const last30days = new Date(today) | |
| 707 | + last30days.setDate(last30days.getDate() - 29) | |
| 708 | + return [last30days, today] | |
| 709 | + case 'thisweek': | |
| 710 | + const thisWeekStart = new Date(today) | |
| 711 | + const day = today.getDay() | |
| 712 | + const diff = day - this.startOfWeek | |
| 713 | + thisWeekStart.setDate(today.getDate() - (diff < 0 ? diff + 7 : diff)) | |
| 714 | + return [thisWeekStart, today] | |
| 715 | + case 'thismonth': | |
| 716 | + const thisMonthStart = new Date(today.getFullYear(), today.getMonth(), 1) | |
| 717 | + return [thisMonthStart, today] | |
| 718 | + default: | |
| 719 | + return null | |
| 720 | + } | |
| 721 | + }, | |
| 722 | + getDayClass(day) { | |
| 723 | + const classes = [] | |
| 724 | + | |
| 725 | + if (!day.isCurrentMonth) { | |
| 726 | + classes.push('other-month') | |
| 727 | + } | |
| 728 | + | |
| 729 | + if (this.isDateDisabled(day.date)) { | |
| 730 | + classes.push('disabled') | |
| 731 | + } | |
| 732 | + | |
| 733 | + if (this.isToday(day.date)) { | |
| 734 | + classes.push('today') | |
| 735 | + } | |
| 736 | + | |
| 737 | + if (this.isRangeType) { | |
| 738 | + if (this.isDateSelected(day.date, this.selectedStartDate) || | |
| 739 | + this.isDateSelected(day.date, this.selectedEndDate)) { | |
| 740 | + classes.push('selected') | |
| 741 | + } | |
| 742 | + if (this.isDateInRange(day.date)) { | |
| 743 | + classes.push('in-range') | |
| 744 | + } | |
| 745 | + } else { | |
| 746 | + if (this.isDateSelected(day.date, this.selectedDate)) { | |
| 747 | + classes.push('selected') | |
| 748 | + } | |
| 749 | + } | |
| 750 | + | |
| 751 | + return classes.join(' ') | |
| 752 | + }, | |
| 753 | + isToday(date) { | |
| 754 | + // 性能优化:使用缓存的时间戳比较 | |
| 755 | + const dateTimestamp = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() | |
| 756 | + return dateTimestamp === this.todayTimestamp | |
| 757 | + }, | |
| 758 | + isDateSelected(date, selectedDate) { | |
| 759 | + if (!selectedDate) return false | |
| 760 | + return date.getFullYear() === selectedDate.getFullYear() && | |
| 761 | + date.getMonth() === selectedDate.getMonth() && | |
| 762 | + date.getDate() === selectedDate.getDate() | |
| 763 | + }, | |
| 764 | + isDateInRange(date) { | |
| 765 | + if (!this.selectedStartDate || !this.selectedEndDate) return false | |
| 766 | + return date > this.selectedStartDate && date < this.selectedEndDate | |
| 767 | + }, | |
| 768 | + isDateDisabled(date) { | |
| 769 | + // 自定义禁用函数 | |
| 770 | + if (this.disabledDate && this.disabledDate(date)) { | |
| 771 | + return true | |
| 772 | + } | |
| 773 | + | |
| 774 | + // 性能优化:使用缓存的时间戳比较 | |
| 775 | + const timestamp = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() | |
| 776 | + | |
| 777 | + // 最小日期 | |
| 778 | + if (this.parsedMinDate && timestamp < this.parsedMinDate) { | |
| 779 | + return true | |
| 780 | + } | |
| 781 | + | |
| 782 | + // 最大日期 | |
| 783 | + if (this.parsedMaxDate && timestamp > this.parsedMaxDate) { | |
| 784 | + return true | |
| 785 | + } | |
| 786 | + | |
| 787 | + return false | |
| 788 | + }, | |
| 789 | + getWeekNumber(date) { | |
| 790 | + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())) | |
| 791 | + const dayNum = d.getUTCDay() || 7 | |
| 792 | + d.setUTCDate(d.getUTCDate() + 4 - dayNum) | |
| 793 | + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)) | |
| 794 | + return Math.ceil((((d - yearStart) / 86400000) + 1) / 7) | |
| 795 | + }, | |
| 796 | + parseDate(value) { | |
| 797 | + if (value instanceof Date) { | |
| 798 | + return new Date(value) | |
| 799 | + } | |
| 800 | + if (typeof value === 'string') { | |
| 801 | + // 支持多种格式 | |
| 802 | + return new Date(value.replace(/-/g, '/')) | |
| 803 | + } | |
| 804 | + return new Date() | |
| 805 | + }, | |
| 806 | + formatDate(date) { | |
| 807 | + if (!date) return '' | |
| 808 | + | |
| 809 | + const year = date.getFullYear() | |
| 810 | + const month = String(date.getMonth() + 1).padStart(2, '0') | |
| 811 | + const day = String(date.getDate()).padStart(2, '0') | |
| 812 | + const hours = String(date.getHours()).padStart(2, '0') | |
| 813 | + const minutes = String(date.getMinutes()).padStart(2, '0') | |
| 814 | + const seconds = String(date.getSeconds()).padStart(2, '0') | |
| 815 | + | |
| 816 | + return this.format | |
| 817 | + .replace(/YYYY/g, year) | |
| 818 | + .replace(/MM/g, month) | |
| 819 | + .replace(/DD/g, day) | |
| 820 | + .replace(/HH/g, hours) | |
| 821 | + .replace(/mm/g, minutes) | |
| 822 | + .replace(/ss/g, seconds) | |
| 823 | + }, | |
| 824 | + formatTime() { | |
| 825 | + const parts = [] | |
| 826 | + | |
| 827 | + if (this.showHours) { | |
| 828 | + parts.push(String(this.selectedHour).padStart(2, '0')) | |
| 829 | + } | |
| 830 | + if (this.showMinutes) { | |
| 831 | + parts.push(String(this.selectedMinute).padStart(2, '0')) | |
| 832 | + } | |
| 833 | + if (this.showSeconds) { | |
| 834 | + parts.push(String(this.selectedSecond).padStart(2, '0')) | |
| 835 | + } | |
| 836 | + | |
| 837 | + return parts.join(':') | |
| 838 | + }, | |
| 839 | + combineDatetime(date, hour, minute, second) { | |
| 840 | + const newDate = new Date(date) | |
| 841 | + newDate.setHours(hour, minute, second, 0) | |
| 842 | + return newDate | |
| 843 | + } | |
| 844 | + }, | |
| 845 | + // 性能优化:组件销毁时清理定时器 | |
| 846 | + beforeDestroy() { | |
| 847 | + if (this._monthChangeTimer) { | |
| 848 | + clearTimeout(this._monthChangeTimer) | |
| 849 | + } | |
| 850 | + }, | |
| 851 | + // Vue 3 兼容 | |
| 852 | + beforeUnmount() { | |
| 853 | + if (this._monthChangeTimer) { | |
| 854 | + clearTimeout(this._monthChangeTimer) | |
| 855 | + } | |
| 856 | + } | |
| 857 | +} | |
| 858 | +</script> | |
| 859 | + | |
| 860 | +<style scoped> | |
| 861 | +.neo-datetime-pro { | |
| 862 | + width: 100%; | |
| 863 | +} | |
| 864 | + | |
| 865 | +/* 触发器 */ | |
| 866 | +.trigger { | |
| 867 | + display: flex; | |
| 868 | + align-items: center; | |
| 869 | + justify-content: space-between; | |
| 870 | + padding: 4rpx 32rpx; | |
| 871 | + background: var(--neo-color-bg-elevated, #fff); | |
| 872 | + border: 2rpx solid var(--neo-color-border, rgba(0, 0, 0, 0.08)); | |
| 873 | + border-radius: 12rpx; | |
| 874 | + min-height: 44rpx; | |
| 875 | + box-sizing: border-box; | |
| 876 | + transition: all 0.3s; | |
| 877 | + cursor: pointer; | |
| 878 | + user-select: none; | |
| 879 | +} | |
| 880 | + | |
| 881 | +.trigger:active:not(.disabled):not(.readonly) { | |
| 882 | + opacity: 0.8; | |
| 883 | +} | |
| 884 | + | |
| 885 | +.trigger.disabled { | |
| 886 | + opacity: 0.5; | |
| 887 | + cursor: not-allowed; | |
| 888 | + pointer-events: none; | |
| 889 | +} | |
| 890 | + | |
| 891 | +.trigger.readonly { | |
| 892 | + background: var(--neo-color-bg-base, #f5f7fa); | |
| 893 | + cursor: default; | |
| 894 | +} | |
| 895 | + | |
| 896 | +.trigger-value { | |
| 897 | + font-size: 28rpx; | |
| 898 | + | |
| 899 | +} | |
| 900 | + | |
| 901 | +.trigger-placeholder { | |
| 902 | + font-size: 28rpx; | |
| 903 | + color: var(--neo-color-text-tertiary, #94a3b8); | |
| 904 | +} | |
| 905 | + | |
| 906 | +.trigger-clear { | |
| 907 | + font-size: 40rpx; | |
| 908 | + color: var(--neo-color-text-tertiary, #94a3b8); | |
| 909 | + padding: 0 8rpx; | |
| 910 | +} | |
| 911 | + | |
| 912 | +/* 弹出层 */ | |
| 913 | +.popup-overlay { | |
| 914 | + position: fixed; | |
| 915 | + top: 0; | |
| 916 | + left: 0; | |
| 917 | + right: 0; | |
| 918 | + bottom: 0; | |
| 919 | + background: rgba(0, 0, 0, 0.5); | |
| 920 | + z-index: 9999; | |
| 921 | + display: flex; | |
| 922 | + align-items: flex-end; | |
| 923 | +} | |
| 924 | + | |
| 925 | +.popup-content { | |
| 926 | + width: 100%; | |
| 927 | + max-height: 80vh; | |
| 928 | + background: var(--neo-color-bg-elevated, #fff); | |
| 929 | + border-radius: 24rpx 24rpx 0 0; | |
| 930 | + transform: translateY(100%); | |
| 931 | + transition: transform 0.3s; | |
| 932 | +} | |
| 933 | + | |
| 934 | +.popup-content.show { | |
| 935 | + transform: translateY(0); | |
| 936 | +} | |
| 937 | + | |
| 938 | +.popup-header { | |
| 939 | + display: flex; | |
| 940 | + align-items: center; | |
| 941 | + justify-content: space-between; | |
| 942 | + padding: 32rpx; | |
| 943 | + border-bottom: 2rpx solid var(--neo-color-border, rgba(0, 0, 0, 0.08)); | |
| 944 | +} | |
| 945 | + | |
| 946 | +.popup-title { | |
| 947 | + font-size: 32rpx; | |
| 948 | + font-weight: 600; | |
| 949 | + color: var(--neo-color-text-primary, #1e293b); | |
| 950 | +} | |
| 951 | + | |
| 952 | +.popup-close { | |
| 953 | + font-size: 48rpx; | |
| 954 | + color: var(--neo-color-text-tertiary, #94a3b8); | |
| 955 | + padding: 0 8rpx; | |
| 956 | +} | |
| 957 | + | |
| 958 | +.popup-body { | |
| 959 | + max-height: 60vh; | |
| 960 | + overflow-y: auto; | |
| 961 | + padding: 24rpx; | |
| 962 | +} | |
| 963 | + | |
| 964 | +/* 快捷选项 */ | |
| 965 | +.shortcuts { | |
| 966 | + display: flex; | |
| 967 | + flex-wrap: wrap; | |
| 968 | + gap: 16rpx; | |
| 969 | + padding: 24rpx; | |
| 970 | + border-bottom: 2rpx solid var(--neo-color-border, rgba(0, 0, 0, 0.08)); | |
| 971 | +} | |
| 972 | + | |
| 973 | +.shortcut-item { | |
| 974 | + padding: 12rpx 24rpx; | |
| 975 | + background: var(--neo-color-bg-base, #f5f7fa); | |
| 976 | + border-radius: 8rpx; | |
| 977 | + font-size: 24rpx; | |
| 978 | + color: var(--neo-color-text-secondary, #64748b); | |
| 979 | + transition: all 0.2s; | |
| 980 | +} | |
| 981 | + | |
| 982 | +.shortcut-item:active { | |
| 983 | + background: var(--neo-color-primary, #3b82f6); | |
| 984 | + color: #fff; | |
| 985 | +} | |
| 986 | + | |
| 987 | +/* 日历 */ | |
| 988 | +.date-picker { | |
| 989 | + margin-bottom: 24rpx; | |
| 990 | +} | |
| 991 | + | |
| 992 | +.calendar-header { | |
| 993 | + display: flex; | |
| 994 | + align-items: center; | |
| 995 | + justify-content: space-between; | |
| 996 | + padding: 24rpx 0; | |
| 997 | +} | |
| 998 | + | |
| 999 | +.calendar-nav { | |
| 1000 | + font-size: 36rpx; | |
| 1001 | + color: var(--neo-color-text-primary, #1e293b); | |
| 1002 | + padding: 8rpx 24rpx; | |
| 1003 | + cursor: pointer; | |
| 1004 | +} | |
| 1005 | + | |
| 1006 | +.calendar-title { | |
| 1007 | + font-size: 28rpx; | |
| 1008 | + font-weight: 600; | |
| 1009 | + color: var(--neo-color-text-primary, #1e293b); | |
| 1010 | + padding: 8rpx 16rpx; | |
| 1011 | +} | |
| 1012 | + | |
| 1013 | +.calendar-weekdays { | |
| 1014 | + display: grid; | |
| 1015 | + grid-template-columns: repeat(7, 1fr); | |
| 1016 | + gap: 8rpx; | |
| 1017 | + margin-bottom: 16rpx; | |
| 1018 | +} | |
| 1019 | + | |
| 1020 | +.weekday { | |
| 1021 | + text-align: center; | |
| 1022 | + font-size: 24rpx; | |
| 1023 | + color: var(--neo-color-text-tertiary, #94a3b8); | |
| 1024 | + padding: 8rpx 0; | |
| 1025 | +} | |
| 1026 | + | |
| 1027 | +.calendar-days { | |
| 1028 | + display: grid; | |
| 1029 | + grid-template-columns: repeat(7, 1fr); | |
| 1030 | + gap: 8rpx; | |
| 1031 | +} | |
| 1032 | + | |
| 1033 | +.day-cell { | |
| 1034 | + aspect-ratio: 1; | |
| 1035 | + display: flex; | |
| 1036 | + flex-direction: column; | |
| 1037 | + align-items: center; | |
| 1038 | + justify-content: center; | |
| 1039 | + border-radius: 8rpx; | |
| 1040 | + cursor: pointer; | |
| 1041 | + position: relative; | |
| 1042 | + transition: all 0.2s; | |
| 1043 | +} | |
| 1044 | + | |
| 1045 | +.day-text { | |
| 1046 | + font-size: 28rpx; | |
| 1047 | + color: var(--neo-color-text-primary, #1e293b); | |
| 1048 | +} | |
| 1049 | + | |
| 1050 | +.week-number { | |
| 1051 | + font-size: 20rpx; | |
| 1052 | + color: var(--neo-color-text-tertiary, #94a3b8); | |
| 1053 | + position: absolute; | |
| 1054 | + top: 4rpx; | |
| 1055 | + right: 4rpx; | |
| 1056 | +} | |
| 1057 | + | |
| 1058 | +.day-cell.other-month .day-text { | |
| 1059 | + color: var(--neo-color-text-tertiary, #94a3b8); | |
| 1060 | +} | |
| 1061 | + | |
| 1062 | +.day-cell.today { | |
| 1063 | + background: var(--neo-color-bg-hover, #f8f9fa); | |
| 1064 | +} | |
| 1065 | + | |
| 1066 | +.day-cell.selected { | |
| 1067 | + background: var(--neo-color-primary, #3b82f6); | |
| 1068 | +} | |
| 1069 | + | |
| 1070 | +.day-cell.selected .day-text { | |
| 1071 | + color: #fff; | |
| 1072 | +} | |
| 1073 | + | |
| 1074 | +.day-cell.in-range { | |
| 1075 | + background: rgba(59, 130, 246, 0.1); | |
| 1076 | +} | |
| 1077 | + | |
| 1078 | +.day-cell.disabled { | |
| 1079 | + opacity: 0.3; | |
| 1080 | + cursor: not-allowed; | |
| 1081 | +} | |
| 1082 | + | |
| 1083 | +.day-cell.disabled:active { | |
| 1084 | + transform: none; | |
| 1085 | +} | |
| 1086 | + | |
| 1087 | +.day-cell:active:not(.disabled) { | |
| 1088 | + transform: scale(0.95); | |
| 1089 | +} | |
| 1090 | + | |
| 1091 | +/* 时间选择器 */ | |
| 1092 | +.time-picker { | |
| 1093 | + margin-top: 24rpx; | |
| 1094 | +} | |
| 1095 | + | |
| 1096 | +.time-picker-header { | |
| 1097 | + display: flex; | |
| 1098 | + padding: 16rpx 0; | |
| 1099 | + border-bottom: 2rpx solid var(--neo-color-border, rgba(0, 0, 0, 0.08)); | |
| 1100 | +} | |
| 1101 | + | |
| 1102 | +.time-picker-header.single-column { | |
| 1103 | + padding-left: 30%; | |
| 1104 | + padding-right: 30%; | |
| 1105 | +} | |
| 1106 | + | |
| 1107 | +.time-picker-header.double-column { | |
| 1108 | + padding-left: 15%; | |
| 1109 | + padding-right: 15%; | |
| 1110 | +} | |
| 1111 | + | |
| 1112 | +.time-label { | |
| 1113 | + font-size: 26rpx; | |
| 1114 | + font-weight: 600; | |
| 1115 | + color: var(--neo-color-text-secondary, #64748b); | |
| 1116 | + flex: 1; | |
| 1117 | + text-align: center; | |
| 1118 | +} | |
| 1119 | + | |
| 1120 | +.time-picker-view { | |
| 1121 | + height: 400rpx; | |
| 1122 | +} | |
| 1123 | + | |
| 1124 | +.time-picker-view.single-column { | |
| 1125 | + /* 只显示一列时居中 */ | |
| 1126 | + padding: 0 30%; | |
| 1127 | +} | |
| 1128 | + | |
| 1129 | +.time-picker-view.double-column { | |
| 1130 | + /* 显示两列时 */ | |
| 1131 | + padding: 0 15%; | |
| 1132 | +} | |
| 1133 | + | |
| 1134 | +.time-item { | |
| 1135 | + display: flex; | |
| 1136 | + align-items: center; | |
| 1137 | + justify-content: center; | |
| 1138 | + height: 80rpx; | |
| 1139 | + font-size: 32rpx; | |
| 1140 | + color: var(--neo-color-text-primary, #1e293b); | |
| 1141 | +} | |
| 1142 | + | |
| 1143 | +/* 年月选择器 */ | |
| 1144 | +.year-month-picker-overlay { | |
| 1145 | + position: fixed; | |
| 1146 | + top: 0; | |
| 1147 | + left: 0; | |
| 1148 | + right: 0; | |
| 1149 | + bottom: 0; | |
| 1150 | + background: rgba(0, 0, 0, 0.5); | |
| 1151 | + z-index: 10000; | |
| 1152 | + display: flex; | |
| 1153 | + align-items: flex-end; | |
| 1154 | +} | |
| 1155 | + | |
| 1156 | +.year-month-picker-content { | |
| 1157 | + width: 100%; | |
| 1158 | + background: var(--neo-color-bg-elevated, #fff); | |
| 1159 | + border-radius: 24rpx 24rpx 0 0; | |
| 1160 | +} | |
| 1161 | + | |
| 1162 | +.year-month-picker-view { | |
| 1163 | + height: 500rpx; | |
| 1164 | + padding: 24rpx 0; | |
| 1165 | +} | |
| 1166 | + | |
| 1167 | +.ym-item { | |
| 1168 | + display: flex; | |
| 1169 | + align-items: center; | |
| 1170 | + justify-content: center; | |
| 1171 | + height: 80rpx; | |
| 1172 | + font-size: 32rpx; | |
| 1173 | + color: var(--neo-color-text-primary, #1e293b); | |
| 1174 | +} | |
| 1175 | + | |
| 1176 | +.year-month-picker-footer { | |
| 1177 | + display: flex; | |
| 1178 | + gap: 24rpx; | |
| 1179 | + padding: 24rpx; | |
| 1180 | + border-top: 2rpx solid var(--neo-color-border, rgba(0, 0, 0, 0.08)); | |
| 1181 | +} | |
| 1182 | + | |
| 1183 | +/* 底部按钮 */ | |
| 1184 | +.popup-footer { | |
| 1185 | + display: flex; | |
| 1186 | + gap: 24rpx; | |
| 1187 | + padding: 24rpx; | |
| 1188 | + border-top: 2rpx solid var(--neo-color-border, rgba(0, 0, 0, 0.08)); | |
| 1189 | +} | |
| 1190 | + | |
| 1191 | +.footer-btn { | |
| 1192 | + flex: 1; | |
| 1193 | + height: 88rpx; | |
| 1194 | + display: flex; | |
| 1195 | + align-items: center; | |
| 1196 | + justify-content: center; | |
| 1197 | + border-radius: 12rpx; | |
| 1198 | + font-size: 28rpx; | |
| 1199 | + font-weight: 600; | |
| 1200 | + transition: all 0.2s; | |
| 1201 | +} | |
| 1202 | + | |
| 1203 | +.footer-btn-cancel, | |
| 1204 | +.footer-btn-back { | |
| 1205 | + background: var(--neo-color-bg-base, #f5f7fa); | |
| 1206 | + color: var(--neo-color-text-secondary, #64748b); | |
| 1207 | +} | |
| 1208 | + | |
| 1209 | +.footer-btn-confirm { | |
| 1210 | + background: var(--neo-color-primary, #3b82f6); | |
| 1211 | + color: #fff; | |
| 1212 | +} | |
| 1213 | + | |
| 1214 | +.footer-btn:active { | |
| 1215 | + opacity: 0.8; | |
| 1216 | + transform: scale(0.98); | |
| 1217 | +} | |
| 1218 | +</style> | |
| 1219 | + | ... | ... |
uni_modules/neo-datetime-pro/demo.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="demo-page"> | |
| 3 | + <view class="demo-section"> | |
| 4 | + <view class="section-title">基础日期选择</view> | |
| 5 | + <neo-datetime-pro | |
| 6 | + v-model="date1" | |
| 7 | + placeholder="请选择日期" | |
| 8 | + @change="handleChange('基础日期', $event)" | |
| 9 | + /> | |
| 10 | + <view class="result">选中值: {{ date1 || '未选择' }}</view> | |
| 11 | + </view> | |
| 12 | + | |
| 13 | + <view class="demo-section"> | |
| 14 | + <view class="section-title">日期范围选择</view> | |
| 15 | + <neo-datetime-pro | |
| 16 | + v-model="dateRange" | |
| 17 | + type="daterange" | |
| 18 | + placeholder="请选择日期范围" | |
| 19 | + :shortcuts="shortcuts" | |
| 20 | + @change="handleChange('日期范围', $event)" | |
| 21 | + /> | |
| 22 | + <view class="result">选中值: {{ dateRange.length ? dateRange.join(' ~ ') : '未选择' }}</view> | |
| 23 | + </view> | |
| 24 | + | |
| 25 | + <view class="demo-section"> | |
| 26 | + <view class="section-title">日期时间选择</view> | |
| 27 | + <neo-datetime-pro | |
| 28 | + v-model="datetime" | |
| 29 | + type="datetime" | |
| 30 | + format="YYYY-MM-DD HH:mm:ss" | |
| 31 | + placeholder="请选择日期时间" | |
| 32 | + @change="handleChange('日期时间', $event)" | |
| 33 | + /> | |
| 34 | + <view class="result">选中值: {{ datetime || '未选择' }}</view> | |
| 35 | + </view> | |
| 36 | + | |
| 37 | + <view class="demo-section"> | |
| 38 | + <view class="section-title">时间选择(时分秒)</view> | |
| 39 | + <neo-datetime-pro | |
| 40 | + v-model="time" | |
| 41 | + type="time" | |
| 42 | + format="HH:mm:ss" | |
| 43 | + placeholder="请选择时间" | |
| 44 | + @change="handleChange('时间', $event)" | |
| 45 | + /> | |
| 46 | + <view class="result">选中值: {{ time || '未选择' }}</view> | |
| 47 | + </view> | |
| 48 | + | |
| 49 | + <view class="demo-section"> | |
| 50 | + <view class="section-title">时间选择(时分)</view> | |
| 51 | + <neo-datetime-pro | |
| 52 | + v-model="time2" | |
| 53 | + type="time" | |
| 54 | + format="HH:mm" | |
| 55 | + placeholder="请选择时间(时分)" | |
| 56 | + @change="handleChange('时分', $event)" | |
| 57 | + /> | |
| 58 | + <view class="result">选中值: {{ time2 || '未选择' }}</view> | |
| 59 | + </view> | |
| 60 | + | |
| 61 | + <view class="demo-section"> | |
| 62 | + <view class="section-title">时间选择(仅小时)</view> | |
| 63 | + <neo-datetime-pro | |
| 64 | + v-model="time3" | |
| 65 | + type="time" | |
| 66 | + format="HH" | |
| 67 | + placeholder="请选择小时" | |
| 68 | + @change="handleChange('小时', $event)" | |
| 69 | + /> | |
| 70 | + <view class="result">选中值: {{ time3 || '未选择' }}</view> | |
| 71 | + </view> | |
| 72 | + | |
| 73 | + <view class="demo-section"> | |
| 74 | + <view class="section-title">禁用过去的日期</view> | |
| 75 | + <neo-datetime-pro | |
| 76 | + v-model="date2" | |
| 77 | + :disabled-date="disablePastDates" | |
| 78 | + placeholder="只能选择今天及以后" | |
| 79 | + @change="handleChange('禁用过去日期', $event)" | |
| 80 | + /> | |
| 81 | + <view class="result">选中值: {{ date2 || '未选择' }}</view> | |
| 82 | + </view> | |
| 83 | + | |
| 84 | + <view class="demo-section"> | |
| 85 | + <view class="section-title">禁用周末</view> | |
| 86 | + <neo-datetime-pro | |
| 87 | + v-model="date3" | |
| 88 | + :disabled-date="disableWeekends" | |
| 89 | + placeholder="周末不可选" | |
| 90 | + @change="handleChange('禁用周末', $event)" | |
| 91 | + /> | |
| 92 | + <view class="result">选中值: {{ date3 || '未选择' }}</view> | |
| 93 | + </view> | |
| 94 | + | |
| 95 | + <view class="demo-section"> | |
| 96 | + <view class="section-title">最小/最大日期限制</view> | |
| 97 | + <neo-datetime-pro | |
| 98 | + v-model="date4" | |
| 99 | + :min-date="minDate" | |
| 100 | + :max-date="maxDate" | |
| 101 | + placeholder="2025年范围" | |
| 102 | + @change="handleChange('日期限制', $event)" | |
| 103 | + /> | |
| 104 | + <view class="result">选中值: {{ date4 || '未选择' }}</view> | |
| 105 | + <view class="tip">限制范围: 2025-01-01 ~ 2025-12-31</view> | |
| 106 | + </view> | |
| 107 | + | |
| 108 | + <view class="demo-section"> | |
| 109 | + <view class="section-title">显示周序号</view> | |
| 110 | + <neo-datetime-pro | |
| 111 | + v-model="date5" | |
| 112 | + :show-week-number="true" | |
| 113 | + placeholder="显示周序号" | |
| 114 | + @change="handleChange('周序号', $event)" | |
| 115 | + /> | |
| 116 | + <view class="result">选中值: {{ date5 || '未选择' }}</view> | |
| 117 | + </view> | |
| 118 | + | |
| 119 | + <view class="demo-section"> | |
| 120 | + <view class="section-title">周一作为一周开始</view> | |
| 121 | + <neo-datetime-pro | |
| 122 | + v-model="date6" | |
| 123 | + :start-of-week="1" | |
| 124 | + placeholder="周一开始" | |
| 125 | + @change="handleChange('周一开始', $event)" | |
| 126 | + /> | |
| 127 | + <view class="result">选中值: {{ date6 || '未选择' }}</view> | |
| 128 | + </view> | |
| 129 | + | |
| 130 | + <view class="demo-section"> | |
| 131 | + <view class="section-title">英文版</view> | |
| 132 | + <neo-datetime-pro | |
| 133 | + v-model="date7" | |
| 134 | + locale="en" | |
| 135 | + placeholder="Select Date" | |
| 136 | + @change="handleChange('English', $event)" | |
| 137 | + /> | |
| 138 | + <view class="result">Selected: {{ date7 || 'None' }}</view> | |
| 139 | + </view> | |
| 140 | + | |
| 141 | + <view class="demo-section"> | |
| 142 | + <view class="section-title">禁用和只读</view> | |
| 143 | + <view class="demo-row"> | |
| 144 | + <view class="demo-col"> | |
| 145 | + <text class="demo-label">禁用</text> | |
| 146 | + <neo-datetime-pro | |
| 147 | + v-model="date8" | |
| 148 | + disabled | |
| 149 | + placeholder="禁用状态" | |
| 150 | + /> | |
| 151 | + </view> | |
| 152 | + <view class="demo-col"> | |
| 153 | + <text class="demo-label">只读</text> | |
| 154 | + <neo-datetime-pro | |
| 155 | + v-model="date9" | |
| 156 | + readonly | |
| 157 | + placeholder="只读状态" | |
| 158 | + /> | |
| 159 | + </view> | |
| 160 | + </view> | |
| 161 | + </view> | |
| 162 | + | |
| 163 | + <view class="demo-section"> | |
| 164 | + <view class="section-title">不可清空</view> | |
| 165 | + <neo-datetime-pro | |
| 166 | + v-model="date10" | |
| 167 | + :clearable="false" | |
| 168 | + placeholder="不显示清空按钮" | |
| 169 | + @change="handleChange('不可清空', $event)" | |
| 170 | + /> | |
| 171 | + <view class="result">选中值: {{ date10 || '未选择' }}</view> | |
| 172 | + </view> | |
| 173 | + | |
| 174 | + <view class="footer"> | |
| 175 | + <text class="footer-text">neo-datetime-pro 日期时间选择器</text> | |
| 176 | + </view> | |
| 177 | + </view> | |
| 178 | +</template> | |
| 179 | + | |
| 180 | +<script> | |
| 181 | +import NeoDatetimePro from './components/neo-datetime-pro/neo-datetime-pro.vue' | |
| 182 | + | |
| 183 | +export default { | |
| 184 | + components: { | |
| 185 | + NeoDatetimePro | |
| 186 | + }, | |
| 187 | + data() { | |
| 188 | + return { | |
| 189 | + date1: '', | |
| 190 | + date2: '', | |
| 191 | + date3: '', | |
| 192 | + date4: '', | |
| 193 | + date5: '', | |
| 194 | + date6: '', | |
| 195 | + date7: '', | |
| 196 | + date8: '2025-10-30', | |
| 197 | + date9: '2025-10-30', | |
| 198 | + date10: '', | |
| 199 | + dateRange: [], | |
| 200 | + datetime: '', | |
| 201 | + time: '', | |
| 202 | + time2: '', | |
| 203 | + time3: '', | |
| 204 | + minDate: '2025-01-01', | |
| 205 | + maxDate: '2025-12-31', | |
| 206 | + shortcuts: [ | |
| 207 | + { text: '今天', value: 'today' }, | |
| 208 | + { text: '昨天', value: 'yesterday' }, | |
| 209 | + { text: '最近7天', value: 'last7days' }, | |
| 210 | + { text: '最近30天', value: 'last30days' }, | |
| 211 | + { text: '本周', value: 'thisweek' }, | |
| 212 | + { text: '本月', value: 'thismonth' } | |
| 213 | + ] | |
| 214 | + } | |
| 215 | + }, | |
| 216 | + methods: { | |
| 217 | + handleChange(label, value) { | |
| 218 | + console.log(`${label} 选择:`, value) | |
| 219 | + }, | |
| 220 | + disablePastDates(date) { | |
| 221 | + const today = new Date() | |
| 222 | + today.setHours(0, 0, 0, 0) | |
| 223 | + return date < today | |
| 224 | + }, | |
| 225 | + disableWeekends(date) { | |
| 226 | + const day = date.getDay() | |
| 227 | + return day === 0 || day === 6 // 0=周日, 6=周六 | |
| 228 | + } | |
| 229 | + } | |
| 230 | +} | |
| 231 | +</script> | |
| 232 | + | |
| 233 | +<style scoped> | |
| 234 | +.demo-page { | |
| 235 | + min-height: 100vh; | |
| 236 | + background: var(--neo-color-bg-base, #f5f7fa); | |
| 237 | + padding: 24rpx; | |
| 238 | +} | |
| 239 | + | |
| 240 | +.demo-section { | |
| 241 | + background: var(--neo-color-bg-elevated, #fff); | |
| 242 | + border-radius: 16rpx; | |
| 243 | + padding: 32rpx; | |
| 244 | + margin-bottom: 24rpx; | |
| 245 | +} | |
| 246 | + | |
| 247 | +.section-title { | |
| 248 | + font-size: 32rpx; | |
| 249 | + font-weight: 600; | |
| 250 | + color: var(--neo-color-text-primary, #1e293b); | |
| 251 | + margin-bottom: 24rpx; | |
| 252 | +} | |
| 253 | + | |
| 254 | +.result { | |
| 255 | + margin-top: 16rpx; | |
| 256 | + padding: 16rpx; | |
| 257 | + background: var(--neo-color-bg-base, #f5f7fa); | |
| 258 | + border-radius: 8rpx; | |
| 259 | + font-size: 24rpx; | |
| 260 | + color: var(--neo-color-text-secondary, #64748b); | |
| 261 | + word-break: break-all; | |
| 262 | +} | |
| 263 | + | |
| 264 | +.tip { | |
| 265 | + margin-top: 12rpx; | |
| 266 | + font-size: 24rpx; | |
| 267 | + color: var(--neo-color-text-tertiary, #94a3b8); | |
| 268 | +} | |
| 269 | + | |
| 270 | +.demo-row { | |
| 271 | + display: flex; | |
| 272 | + gap: 24rpx; | |
| 273 | +} | |
| 274 | + | |
| 275 | +.demo-col { | |
| 276 | + flex: 1; | |
| 277 | +} | |
| 278 | + | |
| 279 | +.demo-label { | |
| 280 | + display: block; | |
| 281 | + font-size: 24rpx; | |
| 282 | + color: var(--neo-color-text-secondary, #64748b); | |
| 283 | + margin-bottom: 12rpx; | |
| 284 | +} | |
| 285 | + | |
| 286 | +.footer { | |
| 287 | + padding: 48rpx 0; | |
| 288 | + text-align: center; | |
| 289 | +} | |
| 290 | + | |
| 291 | +.footer-text { | |
| 292 | + font-size: 24rpx; | |
| 293 | + color: var(--neo-color-text-tertiary, #94a3b8); | |
| 294 | +} | |
| 295 | +</style> | |
| 296 | + | ... | ... |
uni_modules/neo-datetime-pro/package.json
0 → 100644
| 1 | +{ | |
| 2 | + "id": "neo-datetime-pro", | |
| 3 | + "displayName": "Neo Datetime Pro 日期时间选择器", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "description": "高级日期时间选择器,支持日期/时间/范围选择、快捷选项、禁用规则等功能,移动端友好的底部弹出交互", | |
| 6 | + "keywords": [ | |
| 7 | + "日期选择器", | |
| 8 | + "时间选择器", | |
| 9 | + "日期范围", | |
| 10 | + "日历", | |
| 11 | + "datetime-picker" | |
| 12 | +], | |
| 13 | + "repository": "", | |
| 14 | +"engines": { | |
| 15 | + "uni-app": "^3.6.12", | |
| 16 | + "uni-app-x": "^3.6.12" | |
| 17 | + }, | |
| 18 | + "dcloudext": { | |
| 19 | + "type": "component-vue", | |
| 20 | + "sale": { | |
| 21 | + "regular": { | |
| 22 | + "price": "0.00" | |
| 23 | + }, | |
| 24 | + "sourcecode": { | |
| 25 | + "price": "0.00" | |
| 26 | + } | |
| 27 | + }, | |
| 28 | + "contact": { | |
| 29 | + "qq": "783392521" | |
| 30 | + }, | |
| 31 | + "declaration": { | |
| 32 | + "ads": "无", | |
| 33 | + "data": "无", | |
| 34 | + "permissions": "无" | |
| 35 | + }, | |
| 36 | + "npmurl": "", | |
| 37 | + "darkmode": "x", | |
| 38 | + "i18n": "x", | |
| 39 | + "widescreen": "x" | |
| 40 | + }, | |
| 41 | + "uni_modules": { | |
| 42 | + "dependencies": [], | |
| 43 | + "encrypt": [], | |
| 44 | + "platforms": { | |
| 45 | + "cloud": { | |
| 46 | + "tcb": "√", | |
| 47 | + "aliyun": "√", | |
| 48 | + "alipay": "x" | |
| 49 | + }, | |
| 50 | + "client": { | |
| 51 | + "uni-app": { | |
| 52 | + "vue": { | |
| 53 | + "vue2": "√", | |
| 54 | + "vue3": "√" | |
| 55 | + }, | |
| 56 | + "web": { | |
| 57 | + "safari": "√", | |
| 58 | + "chrome": "√" | |
| 59 | + }, | |
| 60 | + "app": { | |
| 61 | + "vue": "√", | |
| 62 | + "nvue": "√", | |
| 63 | + "android": "√", | |
| 64 | + "ios": "√", | |
| 65 | + "harmony": "√" | |
| 66 | + }, | |
| 67 | + "mp": { | |
| 68 | + "weixin": "√", | |
| 69 | + "alipay": "√", | |
| 70 | + "toutiao": "√", | |
| 71 | + "baidu": "√", | |
| 72 | + "kuaishou": "√", | |
| 73 | + "jd": "√", | |
| 74 | + "harmony": "√", | |
| 75 | + "qq": "√", | |
| 76 | + "lark": "√" | |
| 77 | + }, | |
| 78 | + "quickapp": { | |
| 79 | + "huawei": "√", | |
| 80 | + "union": "√" | |
| 81 | + } | |
| 82 | + }, | |
| 83 | + "uni-app-x": { | |
| 84 | + "web": { | |
| 85 | + "safari": "-", | |
| 86 | + "chrome": "-" | |
| 87 | + }, | |
| 88 | + "app": { | |
| 89 | + "android": "-", | |
| 90 | + "ios": "-", | |
| 91 | + "harmony": "-" | |
| 92 | + }, | |
| 93 | + "mp": { | |
| 94 | + "weixin": "-" | |
| 95 | + } | |
| 96 | + } | |
| 97 | + } | |
| 98 | + } | |
| 99 | + } | |
| 100 | +} | |
| 101 | + | ... | ... |
uni_modules/neo-datetime-pro/readme.md
0 → 100644
| 1 | +# neo-datetime-pro | |
| 2 | + | |
| 3 | +高级日期时间选择器,支持日期/时间/范围选择、快捷选项、禁用规则等功能。 | |
| 4 | + | |
| 5 | +## 特性 | |
| 6 | + | |
| 7 | +- 单日期选择 | |
| 8 | +- 日期范围选择(开始-结束) | |
| 9 | +- 时间选择(时/分/秒) | |
| 10 | +- 日期+时间组合选择 | |
| 11 | +- 快捷选项(今天、最近7天、本月等) | |
| 12 | +- 禁用日期规则(自定义函数、最小/最大日期) | |
| 13 | +- 周序号显示 | |
| 14 | +- 国际化支持(中文/英文) | |
| 15 | +- 移动端友好的底部弹出交互 | |
| 16 | +- 全平台兼容 | |
| 17 | + | |
| 18 | +## 兼容性 | |
| 19 | + | |
| 20 | +| Vue2 | Vue3 | H5 | App | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节小程序 | QQ小程序 | | |
| 21 | +|------|------|----|----|--------|----------|--------|--------|---------| | |
| 22 | +| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | |
| 23 | + | |
| 24 | +## 安装 | |
| 25 | + | |
| 26 | +在 HBuilderX 中右键 `uni_modules` 目录,选择 `从插件市场导入`,搜索 `neo-datetime-pro` 安装。 | |
| 27 | + | |
| 28 | +或者直接复制 `neo-datetime-pro` 目录到项目的 `uni_modules` 目录下。 | |
| 29 | + | |
| 30 | +## 快速开始 | |
| 31 | + | |
| 32 | +### 基础用法 | |
| 33 | + | |
| 34 | +```vue | |
| 35 | +<template> | |
| 36 | + <neo-datetime-pro | |
| 37 | + v-model="date" | |
| 38 | + placeholder="请选择日期" | |
| 39 | + /> | |
| 40 | +</template> | |
| 41 | + | |
| 42 | +<script> | |
| 43 | +export default { | |
| 44 | + data() { | |
| 45 | + return { | |
| 46 | + date: '' | |
| 47 | + } | |
| 48 | + } | |
| 49 | +} | |
| 50 | +</script> | |
| 51 | +``` | |
| 52 | + | |
| 53 | +### 日期范围选择 | |
| 54 | + | |
| 55 | +```vue | |
| 56 | +<template> | |
| 57 | + <neo-datetime-pro | |
| 58 | + v-model="dateRange" | |
| 59 | + type="daterange" | |
| 60 | + placeholder="请选择日期范围" | |
| 61 | + /> | |
| 62 | +</template> | |
| 63 | + | |
| 64 | +<script> | |
| 65 | +export default { | |
| 66 | + data() { | |
| 67 | + return { | |
| 68 | + dateRange: [] | |
| 69 | + } | |
| 70 | + } | |
| 71 | +} | |
| 72 | +</script> | |
| 73 | +``` | |
| 74 | + | |
| 75 | +### 日期时间选择 | |
| 76 | + | |
| 77 | +```vue | |
| 78 | +<template> | |
| 79 | + <neo-datetime-pro | |
| 80 | + v-model="datetime" | |
| 81 | + type="datetime" | |
| 82 | + format="YYYY-MM-DD HH:mm:ss" | |
| 83 | + placeholder="请选择日期时间" | |
| 84 | + /> | |
| 85 | +</template> | |
| 86 | + | |
| 87 | +<script> | |
| 88 | +export default { | |
| 89 | + data() { | |
| 90 | + return { | |
| 91 | + datetime: '' | |
| 92 | + } | |
| 93 | + } | |
| 94 | +} | |
| 95 | +</script> | |
| 96 | +``` | |
| 97 | + | |
| 98 | +### 时间选择 | |
| 99 | + | |
| 100 | +支持根据 `format` 自动显示时间部分: | |
| 101 | + | |
| 102 | +```vue | |
| 103 | +<template> | |
| 104 | + <!-- 时分秒 --> | |
| 105 | + <neo-datetime-pro | |
| 106 | + v-model="time1" | |
| 107 | + type="time" | |
| 108 | + format="HH:mm:ss" | |
| 109 | + placeholder="请选择时间" | |
| 110 | + /> | |
| 111 | + | |
| 112 | + <!-- 仅时分 --> | |
| 113 | + <neo-datetime-pro | |
| 114 | + v-model="time2" | |
| 115 | + type="time" | |
| 116 | + format="HH:mm" | |
| 117 | + placeholder="请选择时间" | |
| 118 | + /> | |
| 119 | + | |
| 120 | + <!-- 仅小时 --> | |
| 121 | + <neo-datetime-pro | |
| 122 | + v-model="time3" | |
| 123 | + type="time" | |
| 124 | + format="HH" | |
| 125 | + placeholder="请选择小时" | |
| 126 | + /> | |
| 127 | +</template> | |
| 128 | + | |
| 129 | +<script> | |
| 130 | +export default { | |
| 131 | + data() { | |
| 132 | + return { | |
| 133 | + time1: '', | |
| 134 | + time2: '', | |
| 135 | + time3: '' | |
| 136 | + } | |
| 137 | + } | |
| 138 | +} | |
| 139 | +</script> | |
| 140 | +``` | |
| 141 | + | |
| 142 | +### 快捷选项 | |
| 143 | + | |
| 144 | +```vue | |
| 145 | +<template> | |
| 146 | + <neo-datetime-pro | |
| 147 | + v-model="dateRange" | |
| 148 | + type="daterange" | |
| 149 | + :shortcuts="shortcuts" | |
| 150 | + placeholder="请选择日期范围" | |
| 151 | + /> | |
| 152 | +</template> | |
| 153 | + | |
| 154 | +<script> | |
| 155 | +export default { | |
| 156 | + data() { | |
| 157 | + return { | |
| 158 | + dateRange: [], | |
| 159 | + shortcuts: [ | |
| 160 | + { text: '今天', value: 'today' }, | |
| 161 | + { text: '昨天', value: 'yesterday' }, | |
| 162 | + { text: '最近7天', value: 'last7days' }, | |
| 163 | + { text: '最近30天', value: 'last30days' }, | |
| 164 | + { text: '本周', value: 'thisweek' }, | |
| 165 | + { text: '本月', value: 'thismonth' } | |
| 166 | + ] | |
| 167 | + } | |
| 168 | + } | |
| 169 | +} | |
| 170 | +</script> | |
| 171 | +``` | |
| 172 | + | |
| 173 | +### 禁用日期 | |
| 174 | + | |
| 175 | +```vue | |
| 176 | +<template> | |
| 177 | + <neo-datetime-pro | |
| 178 | + v-model="date" | |
| 179 | + :disabled-date="disabledDate" | |
| 180 | + placeholder="请选择日期" | |
| 181 | + /> | |
| 182 | +</template> | |
| 183 | + | |
| 184 | +<script> | |
| 185 | +export default { | |
| 186 | + data() { | |
| 187 | + return { | |
| 188 | + date: '' | |
| 189 | + } | |
| 190 | + }, | |
| 191 | + methods: { | |
| 192 | + // 禁用过去的日期 | |
| 193 | + disabledDate(date) { | |
| 194 | + return date < new Date() | |
| 195 | + } | |
| 196 | + } | |
| 197 | +} | |
| 198 | +</script> | |
| 199 | +``` | |
| 200 | + | |
| 201 | +### 最小/最大日期限制 | |
| 202 | + | |
| 203 | +```vue | |
| 204 | +<template> | |
| 205 | + <neo-datetime-pro | |
| 206 | + v-model="date" | |
| 207 | + :min-date="minDate" | |
| 208 | + :max-date="maxDate" | |
| 209 | + placeholder="请选择日期" | |
| 210 | + /> | |
| 211 | +</template> | |
| 212 | + | |
| 213 | +<script> | |
| 214 | +export default { | |
| 215 | + data() { | |
| 216 | + return { | |
| 217 | + date: '', | |
| 218 | + minDate: '2025-01-01', | |
| 219 | + maxDate: '2025-12-31' | |
| 220 | + } | |
| 221 | + } | |
| 222 | +} | |
| 223 | +</script> | |
| 224 | +``` | |
| 225 | + | |
| 226 | +### 使用服务器时间 | |
| 227 | + | |
| 228 | +```vue | |
| 229 | +<template> | |
| 230 | + <neo-datetime-pro | |
| 231 | + v-model="date" | |
| 232 | + :current-time="serverTime" | |
| 233 | + placeholder="请选择日期" | |
| 234 | + /> | |
| 235 | +</template> | |
| 236 | + | |
| 237 | +<script> | |
| 238 | +export default { | |
| 239 | + data() { | |
| 240 | + return { | |
| 241 | + date: '', | |
| 242 | + serverTime: null | |
| 243 | + } | |
| 244 | + }, | |
| 245 | + async mounted() { | |
| 246 | + // 从接口获取服务器时间 | |
| 247 | + const res = await api.getServerTime() | |
| 248 | + this.serverTime = res.data.time // 支持 Date对象、时间戳、字符串 | |
| 249 | + } | |
| 250 | +} | |
| 251 | +</script> | |
| 252 | +``` | |
| 253 | + | |
| 254 | +## API | |
| 255 | + | |
| 256 | +### Props | |
| 257 | + | |
| 258 | +| 参数 | 类型 | 默认值 | 说明 | | |
| 259 | +|------|------|--------|------| | |
| 260 | +| modelValue / v-model | String / Array / Date | - | 绑定值 | | |
| 261 | +| type | String | date | 选择器类型:date/datetime/time/daterange/datetimerange | | |
| 262 | +| format | String | YYYY-MM-DD | 日期/时间格式,时间选择器会根据 format 智能显示列:<br>• `YYYY-MM-DD` 日期<br>• `YYYY-MM-DD HH:mm:ss` 日期时间<br>• `HH:mm:ss` 时分秒<br>• `HH:mm` 仅时分<br>• `HH` 仅小时 | | |
| 263 | +| placeholder | String | 请选择 | 占位文本 | | |
| 264 | +| disabled | Boolean | false | 是否禁用 | | |
| 265 | +| readonly | Boolean | false | 是否只读 | | |
| 266 | +| clearable | Boolean | true | 是否显示清空按钮 | | |
| 267 | +| shortcuts | Array | [] | 快捷选项 | | |
| 268 | +| disabledDate | Function | - | 禁用日期函数,返回 true 表示禁用 | | |
| 269 | +| minDate | String / Date | - | 最小日期 | | |
| 270 | +| maxDate | String / Date | - | 最大日期 | | |
| 271 | +| showWeekNumber | Boolean | false | 是否显示周序号 | | |
| 272 | +| locale | String | zh | 语言:zh/en | | |
| 273 | +| startOfWeek | Number | 0 | 周起始日:0(周日)- 6(周六) | | |
| 274 | +| currentTime | Date / String / Number | null | 当前时间(支持从接口获取),未传入时使用客户端时间 | | |
| 275 | + | |
| 276 | +### Events | |
| 277 | + | |
| 278 | +| 事件名 | 参数 | 说明 | | |
| 279 | +|--------|------|------| | |
| 280 | +| change | value | 选中值改变时触发 | | |
| 281 | +| confirm | value | 点击确定按钮时触发 | | |
| 282 | +| clear | - | 点击清空按钮时触发 | | |
| 283 | + | |
| 284 | +### Shortcuts 快捷选项格式 | |
| 285 | + | |
| 286 | +```javascript | |
| 287 | +[ | |
| 288 | + { text: '今天', value: 'today' }, | |
| 289 | + { text: '昨天', value: 'yesterday' }, | |
| 290 | + { text: '最近7天', value: 'last7days' }, | |
| 291 | + { text: '最近30天', value: 'last30days' }, | |
| 292 | + { text: '本周', value: 'thisweek' }, | |
| 293 | + { text: '本月', value: 'thismonth' }, | |
| 294 | + // 自定义快捷选项 | |
| 295 | + { | |
| 296 | + text: '自定义', | |
| 297 | + value: () => { | |
| 298 | + return ['2025-01-01', '2025-12-31'] | |
| 299 | + } | |
| 300 | + } | |
| 301 | +] | |
| 302 | +``` | |
| 303 | + | |
| 304 | +## 使用建议 | |
| 305 | + | |
| 306 | +1. **日期格式**:支持常见格式如 `YYYY-MM-DD`、`YYYY-MM-DD HH:mm:ss` 等 | |
| 307 | +2. **智能时间格式**:时间选择器会根据 `format` 自动显示对应的列 | |
| 308 | + - `HH:mm:ss` 显示时/分/秒三列 | |
| 309 | + - `HH:mm` 显示时/分两列 | |
| 310 | + - `HH` 仅显示小时一列 | |
| 311 | +3. **范围选择**:`daterange` 和 `datetimerange` 模式下,v-model 绑定数组 `[startDate, endDate]` | |
| 312 | +4. **禁用规则**:`disabledDate` 函数接收日期对象,返回 `true` 表示禁用该日期 | |
| 313 | +5. **快捷选项**:支持内置值(today、yesterday等)和自定义函数 | |
| 314 | +6. **移动端优化**:底部弹出交互,触摸友好 | |
| 315 | + | |
| 316 | +## 常见问题 | |
| 317 | + | |
| 318 | +### 1. 如何禁用周末? | |
| 319 | + | |
| 320 | +```javascript | |
| 321 | +disabledDate(date) { | |
| 322 | + const day = date.getDay() | |
| 323 | + return day === 0 || day === 6 // 0=周日, 6=周六 | |
| 324 | +} | |
| 325 | +``` | |
| 326 | + | |
| 327 | +### 2. 如何设置默认值? | |
| 328 | + | |
| 329 | +```javascript | |
| 330 | +data() { | |
| 331 | + return { | |
| 332 | + date: '2025-10-30' // 字符串格式 | |
| 333 | + // 或 | |
| 334 | + date: new Date() // Date 对象 | |
| 335 | + } | |
| 336 | +} | |
| 337 | +``` | |
| 338 | + | |
| 339 | +### 3. 如何获取时间戳? | |
| 340 | + | |
| 341 | +```javascript | |
| 342 | +@change="handleChange" | |
| 343 | + | |
| 344 | +methods: { | |
| 345 | + handleChange(value) { | |
| 346 | + const timestamp = new Date(value).getTime() | |
| 347 | + console.log(timestamp) | |
| 348 | + } | |
| 349 | +} | |
| 350 | +``` | |
| 351 | + | |
| 352 | +## 许可 | |
| 353 | + | |
| 354 | +MIT License | |
| 355 | + | |
| 356 | +## 更新日志 | |
| 357 | + | |
| 358 | +见 [changelog.md](./changelog.md) | |
| 359 | + | ... | ... |
uni_modules/uview-plus/LICENSE
0 → 100644
| 1 | +MIT License | |
| 2 | + | |
| 3 | +Copyright (c) 2024 https://uiadmin.net/uview-plus | |
| 4 | + | |
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | +of this software and associated documentation files (the "Software"), to deal | |
| 7 | +in the Software without restriction, including without limitation the rights | |
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | +copies of the Software, and to permit persons to whom the Software is | |
| 10 | +furnished to do so, subject to the following conditions: | |
| 11 | + | |
| 12 | +The above copyright notice and this permission notice shall be included in all | |
| 13 | +copies or substantial portions of the Software. | |
| 14 | + | |
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | +SOFTWARE. | |
| 0 | 22 | \ No newline at end of file | ... | ... |
uni_modules/uview-plus/README.md
0 → 100644
| 1 | +<p align="center"> | |
| 2 | + <img alt="logo" src="https://uiadmin.net/uview-plus/common/logo.png" width="120" height="120" style="margin-bottom: 10px;"> | |
| 3 | +</p> | |
| 4 | +<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uview-plus 3.0</h3> | |
| 5 | +<h3 align="center">多平台快速开发的UI框架</h3> | |
| 6 | + | |
| 7 | +[](https://github.com/ijry/uview-plus) | |
| 8 | +[](https://github.com/ijry/uview-plus) | |
| 9 | +[](https://github.com/ijry/uview-plus/issues) | |
| 10 | +[](https://gitee.com/jry/uview-plus/releases) | |
| 11 | +[](https://en.wikipedia.org/wiki/MIT_License) | |
| 12 | + | |
| 13 | +## 说明 | |
| 14 | + | |
| 15 | +uview-plus,是uni-app全面兼容vue3/nvue/鸿蒙/uni-app-x的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。 | |
| 16 | + | |
| 17 | +## 可视化设计 | |
| 18 | + | |
| 19 | +uview-plus现已推出免费可视化设计,可以方便的进行页面可视化设计,导出源码即可使用。极大提高前端页面开发效率;如产品经理设计师直接使用更可作为高保真高可用原型制作工具,让设计稿即代码,无需传统的设计稿开发还原步骤。 | |
| 20 | + | |
| 21 | +<img src="https://s3.bmp.ovh/imgs/2024/11/24/fd58d00071e6e5df.png" width="900" height="auto" > | |
| 22 | +<img src="https://s3.bmp.ovh/imgs/2024/11/24/8e85a519fe627fb1.png" width="900" height="auto" > | |
| 23 | + | |
| 24 | + | |
| 25 | +## 文档 | |
| 26 | +[官方文档:https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com) | |
| 27 | +[备用文档:https://uiadmin.net/uview-plus](https://uiadmin.net/uview-plus) | |
| 28 | + | |
| 29 | + | |
| 30 | +## 预览 | |
| 31 | + | |
| 32 | +您可以通过**微信**扫码,查看最佳的演示效果。 | |
| 33 | +<br> | |
| 34 | +<br> | |
| 35 | +<img src="https://uview-plus.jiangruyi.com/common/h5_qrcode.png" width="220" height="220" > | |
| 36 | + | |
| 37 | +## 链接 | |
| 38 | + | |
| 39 | +- [官方文档](https://uview-plus.jiangruyi.com) | |
| 40 | +- [更新日志](https://uview-plus.jiangruyi.com/components/changelog.html) | |
| 41 | +- [升级指南](https://uview-plus.jiangruyi.com/components/changeGuide.html) | |
| 42 | +- [关于我们](https://uview-plus.jiangruyi.com/cooperation/about.html) | |
| 43 | + | |
| 44 | +## 交流反馈 | |
| 45 | + | |
| 46 | +欢迎加入我们的QQ群交流反馈:[点此跳转](https://uview-plus.jiangruyi.com/components/addQQGroup.html) | |
| 47 | + | |
| 48 | +## 关于PR | |
| 49 | + | |
| 50 | +> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uview-plus是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。 | |
| 51 | +> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢! | |
| 52 | + | |
| 53 | +## 安装 | |
| 54 | + | |
| 55 | +#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?name=uview-plus](https://ext.dcloud.net.cn/plugin?name=uview-plus) | |
| 56 | + | |
| 57 | +请通过[官网安装文档](https://uview-plus.jiangruyi.com/components/install.html)了解更详细的内容 | |
| 58 | + | |
| 59 | +## 快速上手 | |
| 60 | + | |
| 61 | +请通过[快速上手](https://uview-plus.jiangruyi.com/components/quickstart.html)了解更详细的内容 | |
| 62 | + | |
| 63 | +## 使用方法 | |
| 64 | +配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。 | |
| 65 | + | |
| 66 | +```html | |
| 67 | +<template> | |
| 68 | + <u-button text="按钮"></u-button> | |
| 69 | +</template> | |
| 70 | +``` | |
| 71 | + | |
| 72 | +## 版权信息 | |
| 73 | +uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uview-plus应用到您的产品中。 | |
| 74 | + | ... | ... |
uni_modules/uview-plus/changelog.md
0 → 100644
| 1 | +## 3.3.48(2024-11-29) | |
| 2 | +add: 文本行数限制样式提高到10行 | |
| 3 | + | |
| 4 | +del: 去除不跨端的inputmode | |
| 5 | +## 3.3.47(2024-11-28) | |
| 6 | +fix: 时间选择器在hasInput模式下部分机型键盘弹出 | |
| 7 | + | |
| 8 | +## 3.3.46(2024-11-26) | |
| 9 | +fix: 修复text传递事件参数 | |
| 10 | + | |
| 11 | +## 3.3.45(2024-11-24) | |
| 12 | +add: navbar组件支持配置标题颜色 | |
| 13 | + | |
| 14 | +fix: 边框按钮警告类型下颜色变量使用错误 | |
| 15 | + | |
| 16 | +## 3.3.43(2024-11-18) | |
| 17 | +fix: 支持瀑布流组件v-model置为[] | |
| 18 | + | |
| 19 | +add: 新增字符串路径访问工具方法getValueByPath | |
| 20 | + | |
| 21 | +add: 新增float-button悬浮按钮组件 | |
| 22 | + | |
| 23 | +## 3.3.42(2024-11-15) | |
| 24 | +add: button组件支持stop参数阻止冒泡 | |
| 25 | + | |
| 26 | +## 3.3.41(2024-11-13) | |
| 27 | +fix: u-radio-group invalid import | |
| 28 | + | |
| 29 | +improvement: 优化图片组件宽高及修复事件event传递 | |
| 30 | + | |
| 31 | +## 3.3.40(2024-11-11) | |
| 32 | +add: 组件radioGroup增加gap属性用于设置item间隔 | |
| 33 | + | |
| 34 | +fix: 修复H5全局导入 | |
| 35 | + | |
| 36 | +## 3.3.39(2024-11-04) | |
| 37 | +fix: 修复相册组件 | |
| 38 | + | |
| 39 | +## 3.3.38(2024-11-04) | |
| 40 | +fix: 修复视频预览报错 #510 | |
| 41 | + | |
| 42 | +add: album组件增加stop参数支持阻止事件冒泡 | |
| 43 | + | |
| 44 | +## 3.3.37(2024-10-21) | |
| 45 | +fix: 修复因为修改组件名称前缀,导致h5打包后$parent方法内找不到父组件的问题 | |
| 46 | + | |
| 47 | +fix: 修复datetime-picker选择2000年以前日期出错 | |
| 48 | + | |
| 49 | +## 3.3.36(2024-10-09) | |
| 50 | +fix: toast 自动关闭 | |
| 51 | + | |
| 52 | +feat: 增加微信小程序用户昵称审核完毕回调及修改 ts 定义文件 | |
| 53 | + | |
| 54 | +## 3.3.35(2024-10-08) | |
| 55 | +feat: modal和picker支持v-model:show双向绑定 | |
| 56 | + | |
| 57 | +feat: 支持checkbox使用slot自定义label后自带点击事件 #522 | |
| 58 | + | |
| 59 | +feat: swipe-action支持自动关闭特性及初始化打开状态 | |
| 60 | + | |
| 61 | +## 3.3.34(2024-09-23) | |
| 62 | +feat: 支持toast设置duration值为-1时不自动关闭 | |
| 63 | + | |
| 64 | +## 3.3.33(2024-09-18) | |
| 65 | +fix: 修复test.date('008')等验证结果不准确 | |
| 66 | + | |
| 67 | +## 3.3.32(2024-09-09) | |
| 68 | +fix: u-keyboard名称冲突warning | |
| 69 | + | |
| 70 | +## 3.3.31(2024-08-31) | |
| 71 | +feat: qrcode初步支持nvue | |
| 72 | + | |
| 73 | +## 3.3.30(2024-08-30) | |
| 74 | +fix: slider兼容step为字符串类型 | |
| 75 | + | |
| 76 | +## 3.3.29(2024-08-30) | |
| 77 | +fix: 修复tabs组件current参数为字符串处理逻辑 | |
| 78 | + | |
| 79 | +## 3.3.28(2024-08-26) | |
| 80 | +fix: list组件滑动偏移量不一样取绝对值导致iOS下拉偏移量计算错误 | |
| 81 | + | |
| 82 | +## 3.3.27(2024-08-22) | |
| 83 | +fix: 修复up-datetime-picker组件toolbarRightSlot定义缺失 | |
| 84 | + | |
| 85 | +fix: 修复FormItem的rules更新错误的问题 | |
| 86 | + | |
| 87 | +## 3.3.26(2024-08-22) | |
| 88 | +fix: 批量注册全局组件优化 | |
| 89 | + | |
| 90 | +## 3.3.25(2024-08-21) | |
| 91 | +fix: 修复slider在app-vue下样式问题 | |
| 92 | + | |
| 93 | +## 3.3.24(2024-08-19) | |
| 94 | +fix: 修复时间选择器hasInput模式小程序不生效 | |
| 95 | + | |
| 96 | +feat: 支持H5导入所有组件 | |
| 97 | + | |
| 98 | +## 3.3.23(2024-08-17) | |
| 99 | +feat: swipe-action增加closeAll方法 | |
| 100 | + | |
| 101 | +fix: 兼容tabs在某些场景下index小于0时自动设置为0 | |
| 102 | + | |
| 103 | +add: 通用mixin新增navTo页面跳转方法 | |
| 104 | + | |
| 105 | +## 3.3.21(2024-08-15) | |
| 106 | +improvement: 优化二维码组件loading及支持预览与长按事件 #351 | |
| 107 | + | |
| 108 | +fix: 修复swipe-action自动关闭其它功能及组件卸载自动关闭 | |
| 109 | + | |
| 110 | +## 3.3.20(2024-08-15) | |
| 111 | +refactor: props默认值文件移至组件文件夹内便于查找 | |
| 112 | +## 3.3.19(2024-08-14) | |
| 113 | +fix: 修复2被rpx兼容处理只在数字值生效 | |
| 114 | + | |
| 115 | +add: 增加swiper自定义插槽示例 | |
| 116 | + | |
| 117 | +## 3.3.18(2024-08-13) | |
| 118 | +feat: 新增支持datetime-picker工具栏插槽及picker插槽支持修复 | |
| 119 | +## 3.3.17(2024-08-12) | |
| 120 | +feat: swiper组件增加默认slot便于自定义 | |
| 121 | + | |
| 122 | +feat: grid新增间隔参数 | |
| 123 | + | |
| 124 | +feat: picker新增toolbar-right和toolbar-bottom插槽 | |
| 125 | + | |
| 126 | +## 3.3.16(2024-08-12) | |
| 127 | +fix: 解决swiper中title换行后多余的内容未被遮挡问题 | |
| 128 | + | |
| 129 | +fix: 修复迷你导航适配异形屏 | |
| 130 | + | |
| 131 | +## 3.3.15(2024-08-09) | |
| 132 | +fix: 修复默认单位设置为rpx时一些组件高度间距异常 | |
| 133 | + | |
| 134 | +fix: 修复日历在rpx单位下布局异常 | |
| 135 | + | |
| 136 | +feat: code-input支持App端展示输入光标 | |
| 137 | + | |
| 138 | +## 3.3.14(2024-08-09) | |
| 139 | +add: 增加box组件 | |
| 140 | + | |
| 141 | +add: 增加card卡片组件 | |
| 142 | + | |
| 143 | + | |
| 144 | +## 3.3.13(2024-08-08) | |
| 145 | +feat: input支持调用原生组件的focus和blur方法 | |
| 146 | + | |
| 147 | +improvement: grid-item条件编译优化 | |
| 148 | + | |
| 149 | +add: 新增迷你导航组件 | |
| 150 | + | |
| 151 | +## 3.3.12(2024-08-06) | |
| 152 | +improvement: $u挂载时机调整便于打包分离chunk | |
| 153 | + | |
| 154 | +fix: steps新增itemStyle属性名称冲突 | |
| 155 | + | |
| 156 | +## 3.3.11(2024-08-05) | |
| 157 | +feat: 新增支持upload组件的deletable/maxCount/accept变更监听 #333 | |
| 158 | + | |
| 159 | +feat: 新增支持tabs在swiper中使用 | |
| 160 | + | |
| 161 | +feat: 新增FormItem支持独立设置验证规则rules | |
| 162 | + | |
| 163 | +fix: 修复index-list未设置$slots.header时索引高亮失效 | |
| 164 | + | |
| 165 | +## 3.3.10(2024-08-02) | |
| 166 | +fix: 修复index-list偶发的滑动最后一个索引报错top不存在 | |
| 167 | + | |
| 168 | +fix: 修复gird在QQ、抖音小程序下布局 | |
| 169 | + | |
| 170 | +feat: 优化step支持自定义样式prop | |
| 171 | + | |
| 172 | +feat: action-sheet组件支持v-model:show双向绑定 | |
| 173 | + | |
| 174 | +fix: 小程序下steps和grid都统一采用grid布局 | |
| 175 | + | |
| 176 | +fix: 修复支付宝小程序下input类型为数字时双向绑定失效 | |
| 177 | + | |
| 178 | +feat : form 表单 validate 校验不通过后 error增加字段prop信息 #304 | |
| 179 | + | |
| 180 | +fix: form组件异步校异常验问题 #393 | |
| 181 | + | |
| 182 | +## 3.3.9(2024-08-01) | |
| 183 | +fix: 优化获取nvue元素 | |
| 184 | + | |
| 185 | +feat: modal新增contentTextAlign设置文案对齐方式 | |
| 186 | + | |
| 187 | +fix: 修复NVUE下tabbar文字不显示 #458 | |
| 188 | + | |
| 189 | +feat: loading-page增加zIndex属性 | |
| 190 | + | |
| 191 | +fix: 相册在宽度较小时换行问题 | |
| 192 | + | |
| 193 | +feat: album相册增加自适应自动换行模式 | |
| 194 | + | |
| 195 | +feat: album相册增加图片尺寸单位prop | |
| 196 | + | |
| 197 | +fix: 修复calendar日历月份居中 | |
| 198 | + | |
| 199 | +## 3.3.8(2024-07-31) | |
| 200 | +feat: slider支持进度条任意位置触发按钮拖动 | |
| 201 | + | |
| 202 | +fix: 修复app-vue下modal标题不居中 | |
| 203 | + | |
| 204 | +fix: #459 TS setConfig 声明异常 | |
| 205 | + | |
| 206 | +feat: tabs组件增加longPress长按事件 | |
| 207 | + | |
| 208 | +feat: 新增showRight属性控制collapse右侧图标显隐 | |
| 209 | + | |
| 210 | +fix: 优化nvue下css警告 | |
| 211 | + | |
| 212 | +## 3.3.7(2024-07-29) | |
| 213 | +feat: 支持IndexList组件支持在弹窗等场景下使用及联动优化 | |
| 214 | + | |
| 215 | +feat: popup组件支持v-model:show双向绑定 | |
| 216 | + | |
| 217 | +feat: 优化tabs的current双向绑定 | |
| 218 | + | |
| 219 | +fix: checkbox独立使用时checked赋初始值可以,但是手动切换时值没有做双向绑定! #455 | |
| 220 | + | |
| 221 | +feat: slider组件支持区间双滑块 | |
| 222 | + | |
| 223 | +fix: toast 支持自定义图标?可传入了决对路径的 icon也没有用 #409 | |
| 224 | + | |
| 225 | +feat: form-item校验失败时 增加class方便自定义显示错误的展示方式 #394 | |
| 226 | + | |
| 227 | +fix: up-cell的required配置不生效 #395 | |
| 228 | + | |
| 229 | +fix: 横向滚动组件,微信小程序编译后会有警告 #415 | |
| 230 | + | |
| 231 | +fix: u-picker内部对默认值defaultIndex的监听 #425 | |
| 232 | + | |
| 233 | +feat: toast 组件支持遮掩层穿透 #417 | |
| 234 | + | |
| 235 | +fix: 兼容vue的slot编译bug #423 | |
| 236 | + | |
| 237 | +fix: upload 微信小程序 点击预览视频报错 #424 | |
| 238 | + | |
| 239 | +fix: u-number-box 组件修改【integer, decimalLength, min, max 】props时没有触发绑定值更新 #429 | |
| 240 | + | |
| 241 | +feat: Tabs组件能否支持自定义插槽 #439 | |
| 242 | + | |
| 243 | +feat: ActionSheet 可以配置最大高度吗, 我当做select使用了。 #445 | |
| 244 | + | |
| 245 | +fix: cursor-pointer优化 | |
| 246 | + | |
| 247 | +feat: 新版slider组件兼容NVUE改造 | |
| 248 | + | |
| 249 | +feat: 新增slider组件手动实现以支持样式自定义 | |
| 250 | + | |
| 251 | +perf:补充TS声明提示信息 | |
| 252 | + | |
| 253 | +修复:ActionSheet 操作菜单cancelText属性为空DOM节点还存在并且可以点击问题 | |
| 254 | + | |
| 255 | +fix: 去除预留的beforeDestroy兼容容易在某些sdk下不识别条件编译 | |
| 256 | + | |
| 257 | +## 3.3.6(2024-07-23) | |
| 258 | +feat: u-album组件添加radius,shape参数,定义参考当前u-image参数 | |
| 259 | + | |
| 260 | +fix: 修复了calendar组件title和日期title未垂直居中的问题 | |
| 261 | + | |
| 262 | +fix: update:modelValue缺失emit定义 | |
| 263 | + | |
| 264 | +## 3.3.5(2024-07-10) | |
| 265 | +picker组件支持hasInput模式 | |
| 266 | + | |
| 267 | +## 3.3.4(2024-07-07) | |
| 268 | +fix: input组件双向绑定问题 #419 | |
| 269 | + | |
| 270 | +lazy-load完善emit | |
| 271 | + | |
| 272 | +优化通用小程序分享 | |
| 273 | + | |
| 274 | +## 3.3.2(2024-06-27) | |
| 275 | +fix: 在Nvue环境中编译,出现大量警告 #406 | |
| 276 | +## 3.3.1(2024-06-27) | |
| 277 | +u-button组件报错,找不到button mixins #407 | |
| 278 | +## 3.3.0(2024-06-27) | |
| 279 | +feat: checkbox支持label设置slot | |
| 280 | + | |
| 281 | +feat: modal增加customClass | |
| 282 | + | |
| 283 | +feat: navbar、popup、tabs、text支持customClass | |
| 284 | + | |
| 285 | +fix: cell组建缺少flex布局 | |
| 286 | + | |
| 287 | +fix: 修复微信小程序真机调试时快速输入出现文本回退问题 | |
| 288 | + | |
| 289 | +feat: tag增加默认slot | |
| 290 | + | |
| 291 | +公共mixin改造为按需导入语法 | |
| 292 | + | |
| 293 | +refactor: 组件props混入mixin改造为按需导入语法 | |
| 294 | + | |
| 295 | +fix: u-tabbar 安卓手机点击按钮变蓝问题 #396 | |
| 296 | + | |
| 297 | +feat: upload组建增加extension属性 | |
| 298 | + | |
| 299 | +fix: upload组件参数mode添加left | |
| 300 | + | |
| 301 | +fix: 修复阴影在非nvue时白色背景色不显示 | |
| 302 | + | |
| 303 | +## 3.2.24(2024-06-11) | |
| 304 | +fix: 修复时间选择器confirm事件触发时机导致2次才会触发v-model更新 | |
| 305 | +## 3.2.23(2024-05-30) | |
| 306 | +fix: #378 H5 u-input 在表单中初始值为空也会触发一次 formValidate(this,"change")事件导致进入页面直接校验了一次 | |
| 307 | + | |
| 308 | +fix: #373 搜索组件up-search的@clear事件无效 | |
| 309 | + | |
| 310 | +fix: #372 ActionSheet 组件的取消按钮触发区域太小 | |
| 311 | + | |
| 312 | +## 3.2.22(2024-05-13) | |
| 313 | +上传组件支持微信小程序预览视频 | |
| 314 | + | |
| 315 | +修复折叠面板右侧箭头不显示 | |
| 316 | + | |
| 317 | +修复uxp2px | |
| 318 | + | |
| 319 | +## 3.2.21(2024-05-10) | |
| 320 | +fix: loading-icon修复flex布局 | |
| 321 | +## 3.2.20(2024-05-10) | |
| 322 | +修复瀑布流大小写#355 | |
| 323 | +## 3.2.19(2024-05-10) | |
| 324 | +去除意外的文件引入 | |
| 325 | +## 3.2.18(2024-05-09) | |
| 326 | +fix: 349 popup 组件设置 zIndex 属性后,组件渲染异常# | |
| 327 | +feat: 搜索框增加adjustPosition属性 | |
| 328 | +fix: #331增加u-action-sheet__cancel | |
| 329 | +优化mixin兼容性 | |
| 330 | +feat: #326 up-list增加下拉刷新功能 | |
| 331 | +fix: #319 优化up-tabs参数与定义匹配 | |
| 332 | +fix: index-list组件微信小程序端使用自定义导航栏异常 | |
| 333 | +fix: #285 pickerimmediateChange 写死为true | |
| 334 | +fix: #111 u-scroll-list组件,隐藏指示器后报错, 提示找不到ref | |
| 335 | +list增加微信小程序防抖配置 | |
| 336 | + | |
| 337 | +## 3.2.17(2024-05-08) | |
| 338 | +fix: 支付宝小程序二维码渲染 | |
| 339 | +## 3.2.16(2024-05-06) | |
| 340 | +修复tabs中,当前激活样式的undefined bug | |
| 341 | + | |
| 342 | +fix: #341u-code 倒计时没结束前退出,再次进入结束后退出界面,再次进入重新开始倒计时bug | |
| 343 | + | |
| 344 | +受到uni-app内置text样式影响修复 | |
| 345 | + | |
| 346 | +## 3.2.15(2024-04-28) | |
| 347 | +优化时间选择器hasInput模式初始化值 | |
| 348 | +## 3.2.14(2024-04-24) | |
| 349 | +去除pleaseSetTranspileDependencies | |
| 350 | + | |
| 351 | +http采用useStore | |
| 352 | + | |
| 353 | +## 3.2.13(2024-04-22) | |
| 354 | +修复modal标题样式 | |
| 355 | + | |
| 356 | +优化日期选择器hasInput模式宽度 | |
| 357 | + | |
| 358 | +## 3.2.12(2024-04-22) | |
| 359 | +修复color应用 | |
| 360 | +## 3.2.11(2024-04-18) | |
| 361 | +修复import化带来的问题 | |
| 362 | +## 3.2.10(2024-04-17) | |
| 363 | +完善input清空事件App端失效的兼容性 | |
| 364 | + | |
| 365 | +修复日历组件二次打开后当前月份显示不正确 | |
| 366 | + | |
| 367 | +## 3.2.9(2024-04-16) | |
| 368 | +组件内uni.$u用法改为import引入 | |
| 369 | + | |
| 370 | +规范化及兼容性增强 | |
| 371 | + | |
| 372 | +## 3.2.8(2024-04-15) | |
| 373 | +修复up-tag语法错 | |
| 374 | +## 3.2.7(2024-04-15) | |
| 375 | +修复下拉菜单背景色在支付宝小程序无效 | |
| 376 | + | |
| 377 | +setConfig改为浅拷贝解决无法用import导入代替uni.$u.props设置 | |
| 378 | + | |
| 379 | +## 3.2.6(2024-04-14) | |
| 380 | +修复某些情况下滑动单元格默认右侧按钮是展开的问题 | |
| 381 | +## 3.2.5(2024-04-13) | |
| 382 | +调整分段器尺寸及修复窗口大小改变时重新计算尺寸 | |
| 383 | + | |
| 384 | +多个组件支持cursor-pointer增强PC端体验 | |
| 385 | + | |
| 386 | +## 3.2.4(2024-04-12) | |
| 387 | +初步支持typescript | |
| 388 | +## 3.2.3(2024-04-12) | |
| 389 | +fix: 修复square属性在小程序下无效问题 | |
| 390 | + | |
| 391 | +fix:修复lastIndex异常导致的column异常问题 | |
| 392 | + | |
| 393 | +fix: alipayapp picker style | |
| 394 | + | |
| 395 | +feat(button): 添加用户同意隐私协议事件回调 | |
| 396 | + | |
| 397 | +fix: input switch password | |
| 398 | + | |
| 399 | +fix: 修复u-code组件keepRuning失效问题 | |
| 400 | + | |
| 401 | +feat: form-item添加labelPosition属性 | |
| 402 | + | |
| 403 | +新增dropdown组件 | |
| 404 | + | |
| 405 | +分段器支持内部current值 | |
| 406 | + | |
| 407 | +优化cell和action-sheet视觉大小 | |
| 408 | + | |
| 409 | +修复tabs文字换行 | |
| 410 | + | |
| 411 | +## 3.2.2(2024-04-11) | |
| 412 | +修复换行符问题 | |
| 413 | +## 3.2.1(2024-04-11) | |
| 414 | +修复演示H5二维码 | |
| 415 | + | |
| 416 | +fix: #270 ReadMore 展开阅读更多内容变化兼容 | |
| 417 | + | |
| 418 | +fix: #238Calendar组件maxDate修改为不能小于minDate | |
| 419 | + | |
| 420 | +checkbox支持独立使用 | |
| 421 | + | |
| 422 | +修复popup中在微信小程序中真机调试滚动失效 | |
| 423 | + | |
| 424 | +## 3.2.0(2024-04-10) | |
| 425 | +修复轮播图在nvue显示 | |
| 426 | +修复疑似u-slider名称被占用导致slider在App下不显示 | |
| 427 | +解决微信小程序提示 Some selectors are not allowed in component wxss | |
| 428 | +示例中u-前缀统一为up- | |
| 429 | +增加瀑布流与图片懒加载组件 | |
| 430 | +fix: #308修复tag组件缺失iconColor参数 | |
| 431 | +fix: #297使用grid布局解决目前编译为抖音小程序无法开启virtualHost | |
| 432 | +## 3.1.52(2024-04-07) | |
| 433 | +工具类方法调用import化改造 | |
| 434 | +新增up-copy复制组件 | |
| 435 | +## 3.1.51(2024-04-07) | |
| 436 | +优化时间选择器自带输入框格式化显示 | |
| 437 | +防止按钮文字换行 | |
| 438 | +修复订单列表模板滑动 | |
| 439 | +增加u-qrcode二维码组件 | |
| 440 | +## 3.1.49(2024-03-27) | |
| 441 | +日期时间组件支持自带输入框 | |
| 442 | +fix: popup弹窗滚动穿透问题 | |
| 443 | +fix: 修复小程序numberbox bug | |
| 444 | +## 3.1.48(2024-03-18) | |
| 445 | +fix:[plugin:uni:pre-css] Unbalanced delimiter found in string | |
| 446 | +## 3.1.47(2024-03-18) | |
| 447 | +fix: setConfig设置组件默认参数无效问题 | |
| 448 | +fix: 修复自定义图标无效问题 | |
| 449 | +feat: 增加u-form-item单独设置规则变量 | |
| 450 | +fix:#293小程序是自定义导航栏的时候即传了customNavHeight的时候会出现跳转偏移的情况 | |
| 451 | + | |
| 452 | +## 3.1.46(2024-01-29) | |
| 453 | +beforeUnmount | |
| 454 | +## 3.1.45(2024-01-24) | |
| 455 | +fix: #262ext组件为超链接的情况下size属性不生效 | |
| 456 | +fix: #263最新版本3.1.42中微信小程序u-swipe-action-item报错 | |
| 457 | +fix: #224最新版本3.1.42中微信小程序u-swipe-action-item报错 | |
| 458 | +fix: #263支持支付宝小程序 | |
| 459 | +fix: #261u-input在直接修改v-model的绑定值时,每隔一次会无法出发change事件 | |
| 460 | +优化折叠面板兼容微信小程序 | |
| 461 | +## 3.1.42(2024-01-15) | |
| 462 | +修复u-number-box默认值0时在小程序不显示值 | |
| 463 | +优化u-code的timer判断 | |
| 464 | +优化支付宝小程序下textarea字数统计兼容 | |
| 465 | +优化u-calendar | |
| 466 | +## 3.1.41(2023-11-18) | |
| 467 | +#215优化u-cell图标容器间距问题 | |
| 468 | +## 3.1.40(2023-11-16) | |
| 469 | +修复u-slider双向绑定 | |
| 470 | +## 3.1.39(2023-11-10) | |
| 471 | +修复头条小程序不支持env(safe-area-inset-bottom) | |
| 472 | +优化#201u-grid 指定列数导致闪烁 | |
| 473 | +#193IndexList 索引列表 高度错误 | |
| 474 | +其他优化 | |
| 475 | +## 3.1.38(2023-10-08) | |
| 476 | +修复u-slider | |
| 477 | +## 3.1.37(2023-09-13) | |
| 478 | +完善emits定义及修复code-input双向数据绑定 | |
| 479 | +## 3.1.36(2023-08-08) | |
| 480 | +修复富文本事件名称大小写 | |
| 481 | +## 3.1.35(2023-08-02) | |
| 482 | +修复编译到支付宝小程序u-form报错 | |
| 483 | +## 3.1.34(2023-07-27) | |
| 484 | +修复App打包uni.$u.mpMixin方式sdk暂时不支持导致报错 | |
| 485 | +## 3.1.33(2023-07-13) | |
| 486 | +修复弹窗进入动画、模板页面样式等 | |
| 487 | +## 3.1.31(2023-07-11) | |
| 488 | +修复dayjs引用 | |
| 489 | +## 3.0.8(2022-07-12) | |
| 490 | +修复u-tag默认宽度撑满容器 | |
| 491 | +## 3.0.7(2022-07-12) | |
| 492 | +修复u-navbar自定义插槽演示示例 | |
| 493 | +## 3.0.6(2022-07-11) | |
| 494 | +修复u-image缺少emits申明 | |
| 495 | +## 3.0.5(2022-07-11) | |
| 496 | +修复u-upload缺少emits申明 | |
| 497 | +## 3.0.4(2022-07-10) | |
| 498 | +修复u-textarea/u-input/u-datetime-picker/u-number-box/u-radio-group/u-switch/u-rate在vue3下数据绑定 | |
| 499 | +## 3.0.3(2022-07-09) | |
| 500 | +启用自建演示二维码 | |
| 501 | +## 3.0.2(2022-07-09) | |
| 502 | +修复dayjs/clipboard等导致打包报错 | |
| 503 | +## 3.0.1(2022-07-09) | |
| 504 | +增加插件市场地址 | |
| 505 | +## 3.0.0(2022-07-09) | |
| 506 | +# uview-plus(vue3)初步发布 | ... | ... |
uni_modules/uview-plus/components/u--form/u--form.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <uvForm | |
| 3 | + ref="uForm" | |
| 4 | + :model="model" | |
| 5 | + :rules="rules" | |
| 6 | + :errorType="errorType" | |
| 7 | + :borderBottom="borderBottom" | |
| 8 | + :labelPosition="labelPosition" | |
| 9 | + :labelWidth="labelWidth" | |
| 10 | + :labelAlign="labelAlign" | |
| 11 | + :labelStyle="labelStyle" | |
| 12 | + :customStyle="customStyle" | |
| 13 | + > | |
| 14 | + <slot /> | |
| 15 | + </uvForm> | |
| 16 | +</template> | |
| 17 | + | |
| 18 | +<script> | |
| 19 | + /** | |
| 20 | + * 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件 | |
| 21 | + * 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转 | |
| 22 | + */ | |
| 23 | + import uvForm from '../u-form/u-form.vue'; | |
| 24 | + import { props } from '../u-form/props.js'; | |
| 25 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 26 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 27 | + export default { | |
| 28 | + // #ifdef MP-WEIXIN | |
| 29 | + name: 'u-form', | |
| 30 | + // #endif | |
| 31 | + // #ifndef MP-WEIXIN | |
| 32 | + name: 'u--form', | |
| 33 | + // #endif | |
| 34 | + mixins: [mpMixin, props, mixin], | |
| 35 | + components: { | |
| 36 | + uvForm | |
| 37 | + }, | |
| 38 | + created() { | |
| 39 | + this.children = [] | |
| 40 | + }, | |
| 41 | + methods: { | |
| 42 | + // 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则 | |
| 43 | + setRules(rules) { | |
| 44 | + this.$refs.uForm.setRules(rules) | |
| 45 | + }, | |
| 46 | + validate() { | |
| 47 | + /** | |
| 48 | + * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form | |
| 49 | + * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的 | |
| 50 | + * 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children | |
| 51 | + */ | |
| 52 | + // #ifdef MP-WEIXIN | |
| 53 | + this.setMpData() | |
| 54 | + // #endif | |
| 55 | + return this.$refs.uForm.validate() | |
| 56 | + }, | |
| 57 | + validateField(value, callback) { | |
| 58 | + // #ifdef MP-WEIXIN | |
| 59 | + this.setMpData() | |
| 60 | + // #endif | |
| 61 | + return this.$refs.uForm.validateField(value, callback) | |
| 62 | + }, | |
| 63 | + resetFields() { | |
| 64 | + // #ifdef MP-WEIXIN | |
| 65 | + this.setMpData() | |
| 66 | + // #endif | |
| 67 | + return this.$refs.uForm.resetFields() | |
| 68 | + }, | |
| 69 | + clearValidate(props) { | |
| 70 | + // #ifdef MP-WEIXIN | |
| 71 | + this.setMpData() | |
| 72 | + // #endif | |
| 73 | + return this.$refs.uForm.clearValidate(props) | |
| 74 | + }, | |
| 75 | + setMpData() { | |
| 76 | + this.$refs.uForm.children = this.children | |
| 77 | + } | |
| 78 | + }, | |
| 79 | + } | |
| 80 | +</script> | ... | ... |
uni_modules/uview-plus/components/u--image/u--image.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <uvImage | |
| 3 | + :src="src" | |
| 4 | + :mode="mode" | |
| 5 | + :width="width" | |
| 6 | + :height="height" | |
| 7 | + :shape="shape" | |
| 8 | + :radius="radius" | |
| 9 | + :lazyLoad="lazyLoad" | |
| 10 | + :showMenuByLongpress="showMenuByLongpress" | |
| 11 | + :loadingIcon="loadingIcon" | |
| 12 | + :errorIcon="errorIcon" | |
| 13 | + :showLoading="showLoading" | |
| 14 | + :showError="showError" | |
| 15 | + :fade="fade" | |
| 16 | + :webp="webp" | |
| 17 | + :duration="duration" | |
| 18 | + :bgColor="bgColor" | |
| 19 | + :customStyle="customStyle" | |
| 20 | + @click="$emit('click')" | |
| 21 | + @error="$emit('error')" | |
| 22 | + @load="$emit('load')" | |
| 23 | + > | |
| 24 | + <template v-slot:loading> | |
| 25 | + <slot name="loading"></slot> | |
| 26 | + </template> | |
| 27 | + <template v-slot:error> | |
| 28 | + <slot name="error"></slot> | |
| 29 | + </template> | |
| 30 | + </uvImage> | |
| 31 | +</template> | |
| 32 | + | |
| 33 | +<script> | |
| 34 | + /** | |
| 35 | + * 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件 | |
| 36 | + * 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转 | |
| 37 | + */ | |
| 38 | + import uvImage from '../u-image/u-image.vue'; | |
| 39 | + import { props } from '../u-image/props.js'; | |
| 40 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 41 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 42 | + export default { | |
| 43 | + name: 'u--image', | |
| 44 | + mixins: [mpMixin, props, mixin], | |
| 45 | + components: { | |
| 46 | + uvImage | |
| 47 | + }, | |
| 48 | + emits: ['click', 'error', 'load'] | |
| 49 | + } | |
| 50 | +</script> | |
| 0 | 51 | \ No newline at end of file | ... | ... |
uni_modules/uview-plus/components/u--input/u--input.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <uvInput | |
| 3 | + <!-- #ifdef VUE2 --> | |
| 4 | + :value="value" | |
| 5 | + @input="e => $emit('input', e)" | |
| 6 | + <!-- #endif --> | |
| 7 | + <!-- #ifdef VUE3 --> | |
| 8 | + :modelValue="modelValue" | |
| 9 | + @update:modelValue="e => $emit('update:modelValue', e)" | |
| 10 | + <!-- #endif --> | |
| 11 | + :type="type" | |
| 12 | + :fixed="fixed" | |
| 13 | + :disabled="disabled" | |
| 14 | + :disabledColor="disabledColor" | |
| 15 | + :clearable="clearable" | |
| 16 | + :password="password" | |
| 17 | + :maxlength="maxlength" | |
| 18 | + :placeholder="placeholder" | |
| 19 | + :placeholderClass="placeholderClass" | |
| 20 | + :placeholderStyle="placeholderStyle" | |
| 21 | + :showWordLimit="showWordLimit" | |
| 22 | + :confirmType="confirmType" | |
| 23 | + :confirmHold="confirmHold" | |
| 24 | + :holdKeyboard="holdKeyboard" | |
| 25 | + :focus="focus" | |
| 26 | + :autoBlur="autoBlur" | |
| 27 | + :disableDefaultPadding="disableDefaultPadding" | |
| 28 | + :cursor="cursor" | |
| 29 | + :cursorSpacing="cursorSpacing" | |
| 30 | + :selectionStart="selectionStart" | |
| 31 | + :selectionEnd="selectionEnd" | |
| 32 | + :adjustPosition="adjustPosition" | |
| 33 | + :inputAlign="inputAlign" | |
| 34 | + :fontSize="fontSize" | |
| 35 | + :color="color" | |
| 36 | + :prefixIcon="prefixIcon" | |
| 37 | + :suffixIcon="suffixIcon" | |
| 38 | + :suffixIconStyle="suffixIconStyle" | |
| 39 | + :prefixIconStyle="prefixIconStyle" | |
| 40 | + :border="border" | |
| 41 | + :readonly="readonly" | |
| 42 | + :shape="shape" | |
| 43 | + :customStyle="customStyle" | |
| 44 | + :formatter="formatter" | |
| 45 | + :ignoreCompositionEvent="ignoreCompositionEvent" | |
| 46 | + > | |
| 47 | + <!-- #ifdef MP --> | |
| 48 | + <slot name="prefix"></slot> | |
| 49 | + <slot name="suffix"></slot> | |
| 50 | + <!-- #endif --> | |
| 51 | + <!-- #ifndef MP --> | |
| 52 | + <slot name="prefix" slot="prefix"></slot> | |
| 53 | + <slot name="suffix" slot="suffix"></slot> | |
| 54 | + <!-- #endif --> | |
| 55 | + </uvInput> | |
| 56 | +</template> | |
| 57 | + | |
| 58 | +<script> | |
| 59 | + /** | |
| 60 | + * 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件 | |
| 61 | + * 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转 | |
| 62 | + */ | |
| 63 | + import uvInput from '../u-input/u-input.vue'; | |
| 64 | + import { props } from '../u-input/props.js'; | |
| 65 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 66 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 67 | + export default { | |
| 68 | + name: 'u--input', | |
| 69 | + mixins: [mpMixin, props, mixin], | |
| 70 | + components: { | |
| 71 | + uvInput | |
| 72 | + }, | |
| 73 | + } | |
| 74 | +</script> | |
| 0 | 75 | \ No newline at end of file | ... | ... |
uni_modules/uview-plus/components/u--text/u--text.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <uvText | |
| 3 | + :type="type" | |
| 4 | + :show="show" | |
| 5 | + :text="text" | |
| 6 | + :prefixIcon="prefixIcon" | |
| 7 | + :suffixIcon="suffixIcon" | |
| 8 | + :mode="mode" | |
| 9 | + :href="href" | |
| 10 | + :format="format" | |
| 11 | + :call="call" | |
| 12 | + :openType="openType" | |
| 13 | + :bold="bold" | |
| 14 | + :block="block" | |
| 15 | + :lines="lines" | |
| 16 | + :color="color" | |
| 17 | + :decoration="decoration" | |
| 18 | + :size="size" | |
| 19 | + :iconStyle="iconStyle" | |
| 20 | + :margin="margin" | |
| 21 | + :lineHeight="lineHeight" | |
| 22 | + :align="align" | |
| 23 | + :wordWrap="wordWrap" | |
| 24 | + :customStyle="customStyle" | |
| 25 | + ></uvText> | |
| 26 | +</template> | |
| 27 | + | |
| 28 | +<script> | |
| 29 | +/** | |
| 30 | + * 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件 | |
| 31 | + * 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转 | |
| 32 | + * 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法 | |
| 33 | + */ | |
| 34 | +import uvText from "../u-text/u-text.vue"; | |
| 35 | +import { props } from "../u-text/props.js"; | |
| 36 | +import { mpMixin } from '../../libs/mixin/mpMixin.js' | |
| 37 | +import { mixin } from '../../libs/mixin/mixin.js' | |
| 38 | +export default { | |
| 39 | + name: "u--text", | |
| 40 | + mixins: [mpMixin, mixin, props,], | |
| 41 | + components: { | |
| 42 | + uvText, | |
| 43 | + }, | |
| 44 | +}; | |
| 45 | +</script> | ... | ... |
uni_modules/uview-plus/components/u--textarea/u--textarea.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <uvTextarea | |
| 3 | + :value="value" | |
| 4 | + :modelValue="modelValue" | |
| 5 | + :placeholder="placeholder" | |
| 6 | + :height="height" | |
| 7 | + :confirmType="confirmType" | |
| 8 | + :disabled="disabled" | |
| 9 | + :count="count" | |
| 10 | + :focus="focus" | |
| 11 | + :autoHeight="autoHeight" | |
| 12 | + :fixed="fixed" | |
| 13 | + :cursorSpacing="cursorSpacing" | |
| 14 | + :cursor="cursor" | |
| 15 | + :showConfirmBar="showConfirmBar" | |
| 16 | + :selectionStart="selectionStart" | |
| 17 | + :selectionEnd="selectionEnd" | |
| 18 | + :adjustPosition="adjustPosition" | |
| 19 | + :disableDefaultPadding="disableDefaultPadding" | |
| 20 | + :holdKeyboard="holdKeyboard" | |
| 21 | + :maxlength="maxlength" | |
| 22 | + :border="border" | |
| 23 | + :customStyle="customStyle" | |
| 24 | + :formatter="formatter" | |
| 25 | + :ignoreCompositionEvent="ignoreCompositionEvent" | |
| 26 | + @input="e => $emit('input', e)" | |
| 27 | + @update:modelValue="e => $emit('update:modelValue', e)" | |
| 28 | + ></uvTextarea> | |
| 29 | +</template> | |
| 30 | + | |
| 31 | +<script> | |
| 32 | + /** | |
| 33 | + * 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件 | |
| 34 | + * 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转 | |
| 35 | + */ | |
| 36 | + import uvTextarea from '../u-textarea/u-textarea.vue'; | |
| 37 | + import { props } from '../u-textarea/props.js'; | |
| 38 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 39 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 40 | + export default { | |
| 41 | + name: 'u--textarea', | |
| 42 | + mixins: [mpMixin, props, mixin], | |
| 43 | + components: { | |
| 44 | + uvTextarea | |
| 45 | + }, | |
| 46 | + } | |
| 47 | +</script> | ... | ... |
uni_modules/uview-plus/components/u-action-sheet-data/u-action-sheet-data.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-action-sheet-data"> | |
| 3 | + <view class="u-action-sheet-data__trigger"> | |
| 4 | + <slot name="trigger"></slot> | |
| 5 | + <up-input | |
| 6 | + v-if="!$slots['trigger']" | |
| 7 | + :modelValue="current" | |
| 8 | + disabled | |
| 9 | + disabledColor="#ffffff" | |
| 10 | + :placeholder="title" | |
| 11 | + border="none" | |
| 12 | + ></up-input> | |
| 13 | + <view @click="show = true" | |
| 14 | + class="u-action-sheet-data__trigger__cover"></view> | |
| 15 | + </view> | |
| 16 | + <up-action-sheet | |
| 17 | + :show="show" | |
| 18 | + :actions="options" | |
| 19 | + :title="title" | |
| 20 | + safeAreaInsetBottom | |
| 21 | + :description="description" | |
| 22 | + @close="show = false" | |
| 23 | + @select="select" | |
| 24 | + > | |
| 25 | + </up-action-sheet> | |
| 26 | + </view> | |
| 27 | +</template> | |
| 28 | + | |
| 29 | +<script> | |
| 30 | +export default { | |
| 31 | + props: { | |
| 32 | + modelValue: { | |
| 33 | + type: [String, Number], | |
| 34 | + default: '' | |
| 35 | + }, | |
| 36 | + title: { | |
| 37 | + type: String, | |
| 38 | + default: '' | |
| 39 | + }, | |
| 40 | + description: { | |
| 41 | + type: String, | |
| 42 | + default: '' | |
| 43 | + }, | |
| 44 | + options: { | |
| 45 | + type: Array, | |
| 46 | + default: () => { | |
| 47 | + return [] | |
| 48 | + } | |
| 49 | + }, | |
| 50 | + valueKey: { | |
| 51 | + type: String, | |
| 52 | + default: 'value' | |
| 53 | + }, | |
| 54 | + labelKey: { | |
| 55 | + type: String, | |
| 56 | + default: 'name' | |
| 57 | + } | |
| 58 | + }, | |
| 59 | + data() { | |
| 60 | + return { | |
| 61 | + show: false, | |
| 62 | + current: '', | |
| 63 | + } | |
| 64 | + }, | |
| 65 | + created() { | |
| 66 | + if (this.modelValue) { | |
| 67 | + this.options.forEach((ele) => { | |
| 68 | + if (ele[this.valueKey] == this.modelValue) { | |
| 69 | + this.current = ele[this.labelKey] | |
| 70 | + } | |
| 71 | + }) | |
| 72 | + } | |
| 73 | + }, | |
| 74 | + emits: ['update:modelValue'], | |
| 75 | + watch: { | |
| 76 | + modelValue() { | |
| 77 | + this.options.forEach((ele) => { | |
| 78 | + if (ele[this.valueKey] == this.modelValue) { | |
| 79 | + this.current = ele[this.labelKey] | |
| 80 | + } | |
| 81 | + }) | |
| 82 | + } | |
| 83 | + }, | |
| 84 | + methods: { | |
| 85 | + hideKeyboard() { | |
| 86 | + uni.hideKeyboard() | |
| 87 | + }, | |
| 88 | + select(e) { | |
| 89 | + this.$emit('update:modelValue', e[this.valueKey]) | |
| 90 | + this.current = e[this.labelKey] | |
| 91 | + }, | |
| 92 | + } | |
| 93 | +} | |
| 94 | +</script> | |
| 95 | + | |
| 96 | +<style lang="scss" scoped> | |
| 97 | + .u-action-sheet-data { | |
| 98 | + &__trigger { | |
| 99 | + position: relative; | |
| 100 | + &__cover { | |
| 101 | + position: absolute; | |
| 102 | + top: 0; | |
| 103 | + left: 0; | |
| 104 | + right: 0; | |
| 105 | + bottom: 0; | |
| 106 | + } | |
| 107 | + } | |
| 108 | + } | |
| 109 | +</style> | |
| 0 | 110 | \ No newline at end of file | ... | ... |
uni_modules/uview-plus/components/u-action-sheet/actionSheet.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:44:35 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/actionSheet.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // action-sheet组件 | |
| 12 | + actionSheet: { | |
| 13 | + show: false, | |
| 14 | + title: '', | |
| 15 | + description: '', | |
| 16 | + actions: [], | |
| 17 | + index: '', | |
| 18 | + cancelText: '', | |
| 19 | + closeOnClickAction: true, | |
| 20 | + safeAreaInsetBottom: true, | |
| 21 | + openType: '', | |
| 22 | + closeOnClickOverlay: true, | |
| 23 | + round: 0, | |
| 24 | + wrapMaxHeight: '600px' | |
| 25 | + } | |
| 26 | +} | ... | ... |
uni_modules/uview-plus/components/u-action-sheet/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | + | |
| 4 | +export const props = defineMixin({ | |
| 5 | + props: { | |
| 6 | + // 操作菜单是否展示 (默认false) | |
| 7 | + show: { | |
| 8 | + type: Boolean, | |
| 9 | + default: () => defProps.actionSheet.show | |
| 10 | + }, | |
| 11 | + // 标题 | |
| 12 | + title: { | |
| 13 | + type: String, | |
| 14 | + default: () => defProps.actionSheet.title | |
| 15 | + }, | |
| 16 | + // 选项上方的描述信息 | |
| 17 | + description: { | |
| 18 | + type: String, | |
| 19 | + default: () => defProps.actionSheet.description | |
| 20 | + }, | |
| 21 | + // 数据 | |
| 22 | + actions: { | |
| 23 | + type: Array, | |
| 24 | + default: () => defProps.actionSheet.actions | |
| 25 | + }, | |
| 26 | + // 取消按钮的文字,不为空时显示按钮 | |
| 27 | + cancelText: { | |
| 28 | + type: String, | |
| 29 | + default: () => defProps.actionSheet.cancelText | |
| 30 | + }, | |
| 31 | + // 点击某个菜单项时是否关闭弹窗 | |
| 32 | + closeOnClickAction: { | |
| 33 | + type: Boolean, | |
| 34 | + default: () => defProps.actionSheet.closeOnClickAction | |
| 35 | + }, | |
| 36 | + // 处理底部安全区(默认true) | |
| 37 | + safeAreaInsetBottom: { | |
| 38 | + type: Boolean, | |
| 39 | + default: () => defProps.actionSheet.safeAreaInsetBottom | |
| 40 | + }, | |
| 41 | + // 小程序的打开方式 | |
| 42 | + openType: { | |
| 43 | + type: String, | |
| 44 | + default: () => defProps.actionSheet.openType | |
| 45 | + }, | |
| 46 | + // 点击遮罩是否允许关闭 (默认true) | |
| 47 | + closeOnClickOverlay: { | |
| 48 | + type: Boolean, | |
| 49 | + default: () => defProps.actionSheet.closeOnClickOverlay | |
| 50 | + }, | |
| 51 | + // 圆角值 | |
| 52 | + round: { | |
| 53 | + type: [Boolean, String, Number], | |
| 54 | + default: () => defProps.actionSheet.round | |
| 55 | + }, | |
| 56 | + // 选项区域最大高度 | |
| 57 | + wrapMaxHeight: { | |
| 58 | + type: [String], | |
| 59 | + default: () => defProps.actionSheet.wrapMaxHeight | |
| 60 | + }, | |
| 61 | + } | |
| 62 | +}) | ... | ... |
uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue
0 → 100644
| 1 | + | |
| 2 | +<template> | |
| 3 | + <u-popup | |
| 4 | + :show="show" | |
| 5 | + mode="bottom" | |
| 6 | + @close="closeHandler" | |
| 7 | + :safeAreaInsetBottom="safeAreaInsetBottom" | |
| 8 | + :round="round" | |
| 9 | + > | |
| 10 | + <view class="u-action-sheet"> | |
| 11 | + <view | |
| 12 | + class="u-action-sheet__header" | |
| 13 | + v-if="title" | |
| 14 | + > | |
| 15 | + <text class="u-action-sheet__header__title u-line-1">{{title}}</text> | |
| 16 | + <view | |
| 17 | + class="u-action-sheet__header__icon-wrap" | |
| 18 | + @tap.stop="cancel" | |
| 19 | + > | |
| 20 | + <u-icon | |
| 21 | + name="close" | |
| 22 | + size="17" | |
| 23 | + color="#c8c9cc" | |
| 24 | + bold | |
| 25 | + ></u-icon> | |
| 26 | + </view> | |
| 27 | + </view> | |
| 28 | + <text | |
| 29 | + class="u-action-sheet__description" | |
| 30 | + :style="[{ | |
| 31 | + marginTop: `${title && description ? 0 : '18px'}` | |
| 32 | + }]" | |
| 33 | + v-if="description" | |
| 34 | + >{{description}}</text> | |
| 35 | + <slot> | |
| 36 | + <u-line v-if="description"></u-line> | |
| 37 | + <scroll-view scroll-y class="u-action-sheet__item-wrap" :style="{maxHeight: wrapMaxHeight}"> | |
| 38 | + <view :key="index" v-for="(item, index) in actions"> | |
| 39 | + <!-- #ifdef MP --> | |
| 40 | + <button | |
| 41 | + class="u-reset-button" | |
| 42 | + :openType="item.openType" | |
| 43 | + @getuserinfo="onGetUserInfo" | |
| 44 | + @contact="onContact" | |
| 45 | + @getphonenumber="onGetPhoneNumber" | |
| 46 | + @error="onError" | |
| 47 | + @launchapp="onLaunchApp" | |
| 48 | + @opensetting="onOpenSetting" | |
| 49 | + :lang="lang" | |
| 50 | + :session-from="sessionFrom" | |
| 51 | + :send-message-title="sendMessageTitle" | |
| 52 | + :send-message-path="sendMessagePath" | |
| 53 | + :send-message-img="sendMessageImg" | |
| 54 | + :show-message-card="showMessageCard" | |
| 55 | + :app-parameter="appParameter" | |
| 56 | + @tap="selectHandler(index)" | |
| 57 | + :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''" | |
| 58 | + > | |
| 59 | + <!-- #endif --> | |
| 60 | + <view | |
| 61 | + class="u-action-sheet__item-wrap__item" | |
| 62 | + @tap.stop="selectHandler(index)" | |
| 63 | + :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''" | |
| 64 | + :hover-stay-time="150" | |
| 65 | + > | |
| 66 | + <template v-if="!item.loading"> | |
| 67 | + <text | |
| 68 | + class="u-action-sheet__item-wrap__item__name" | |
| 69 | + :style="[itemStyle(index)]" | |
| 70 | + >{{ item.name }}</text> | |
| 71 | + <text | |
| 72 | + v-if="item.subname" | |
| 73 | + class="u-action-sheet__item-wrap__item__subname" | |
| 74 | + >{{ item.subname }}</text> | |
| 75 | + </template> | |
| 76 | + <u-loading-icon | |
| 77 | + v-else | |
| 78 | + custom-class="van-action-sheet__loading" | |
| 79 | + size="18" | |
| 80 | + mode="circle" | |
| 81 | + /> | |
| 82 | + </view> | |
| 83 | + <!-- #ifdef MP --> | |
| 84 | + </button> | |
| 85 | + <!-- #endif --> | |
| 86 | + <u-line v-if="index !== actions.length - 1"></u-line> | |
| 87 | + </view> | |
| 88 | + </scroll-view> | |
| 89 | + </slot> | |
| 90 | + <u-gap | |
| 91 | + bgColor="#eaeaec" | |
| 92 | + height="6" | |
| 93 | + v-if="cancelText" | |
| 94 | + ></u-gap> | |
| 95 | + <view class="u-action-sheet__item-wrap__item u-action-sheet__cancel" | |
| 96 | + hover-class="u-action-sheet--hover" @tap="cancel" v-if="cancelText"> | |
| 97 | + <text | |
| 98 | + @touchmove.stop.prevent | |
| 99 | + :hover-stay-time="150" | |
| 100 | + class="u-action-sheet__cancel-text" | |
| 101 | + >{{cancelText}}</text> | |
| 102 | + </view> | |
| 103 | + </view> | |
| 104 | + </u-popup> | |
| 105 | +</template> | |
| 106 | + | |
| 107 | +<script> | |
| 108 | + import { openType } from '../../libs/mixin/openType' | |
| 109 | + import { buttonMixin } from '../../libs/mixin/button' | |
| 110 | + import { props } from './props'; | |
| 111 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 112 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 113 | + import { addUnit } from '../../libs/function/index'; | |
| 114 | + /** | |
| 115 | + * ActionSheet 操作菜单 | |
| 116 | + * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。 | |
| 117 | + * @tutorial https://ijry.github.io/uview-plus/components/actionSheet.html | |
| 118 | + * | |
| 119 | + * @property {Boolean} show 操作菜单是否展示 (默认 false ) | |
| 120 | + * @property {String} title 操作菜单标题 | |
| 121 | + * @property {String} description 选项上方的描述信息 | |
| 122 | + * @property {Array<Object>} actions 按钮的文字数组,见官方文档示例 | |
| 123 | + * @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮 | |
| 124 | + * @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true ) | |
| 125 | + * @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true ) | |
| 126 | + * @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error ) | |
| 127 | + * @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true ) | |
| 128 | + * @property {Number|String} round 圆角值,默认无圆角 (默认 0 ) | |
| 129 | + * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文 | |
| 130 | + * @property {String} sessionFrom 会话来源,openType="contact"时有效 | |
| 131 | + * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效 | |
| 132 | + * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效 | |
| 133 | + * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效 | |
| 134 | + * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false ) | |
| 135 | + * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效 | |
| 136 | + * | |
| 137 | + * @event {Function} select 点击ActionSheet列表项时触发 | |
| 138 | + * @event {Function} close 点击取消按钮时触发 | |
| 139 | + * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效 | |
| 140 | + * @event {Function} contact 客服消息回调,openType="contact"时有效 | |
| 141 | + * @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效 | |
| 142 | + * @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效 | |
| 143 | + * @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效 | |
| 144 | + * @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效 | |
| 145 | + * @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet> | |
| 146 | + */ | |
| 147 | + export default { | |
| 148 | + name: "u-action-sheet", | |
| 149 | + // 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到 | |
| 150 | + mixins: [openType, buttonMixin, mixin, props], | |
| 151 | + data() { | |
| 152 | + return { | |
| 153 | + | |
| 154 | + } | |
| 155 | + }, | |
| 156 | + computed: { | |
| 157 | + // 操作项目的样式 | |
| 158 | + itemStyle() { | |
| 159 | + return (index) => { | |
| 160 | + let style = {}; | |
| 161 | + if (this.actions[index].color) style.color = this.actions[index].color | |
| 162 | + if (this.actions[index].fontSize) style.fontSize = addUnit(this.actions[index].fontSize) | |
| 163 | + // 选项被禁用的样式 | |
| 164 | + if (this.actions[index].disabled) style.color = '#c0c4cc' | |
| 165 | + return style; | |
| 166 | + } | |
| 167 | + }, | |
| 168 | + }, | |
| 169 | + emits: ["close", "select", "update:show"], | |
| 170 | + methods: { | |
| 171 | + closeHandler() { | |
| 172 | + // 允许点击遮罩关闭时,才发出close事件 | |
| 173 | + if(this.closeOnClickOverlay) { | |
| 174 | + this.$emit('update:show', false) | |
| 175 | + this.$emit('close') | |
| 176 | + } | |
| 177 | + }, | |
| 178 | + // 点击取消按钮 | |
| 179 | + cancel() { | |
| 180 | + this.$emit('update:show', false) | |
| 181 | + this.$emit('close') | |
| 182 | + }, | |
| 183 | + selectHandler(index) { | |
| 184 | + const item = this.actions[index] | |
| 185 | + if (item && !item.disabled && !item.loading) { | |
| 186 | + this.$emit('select', item) | |
| 187 | + if (this.closeOnClickAction) { | |
| 188 | + this.$emit('update:show', false) | |
| 189 | + this.$emit('close') | |
| 190 | + } | |
| 191 | + } | |
| 192 | + }, | |
| 193 | + } | |
| 194 | + } | |
| 195 | +</script> | |
| 196 | + | |
| 197 | +<style lang="scss" scoped> | |
| 198 | + @import "../../libs/css/components.scss"; | |
| 199 | + $u-action-sheet-reset-button-width:100% !default; | |
| 200 | + $u-action-sheet-title-font-size: 16px !default; | |
| 201 | + $u-action-sheet-title-padding: 12px 30px !default; | |
| 202 | + $u-action-sheet-title-color: $u-main-color !default; | |
| 203 | + $u-action-sheet-header-icon-wrap-right:15px !default; | |
| 204 | + $u-action-sheet-header-icon-wrap-top:15px !default; | |
| 205 | + $u-action-sheet-description-font-size:13px !default; | |
| 206 | + $u-action-sheet-description-color:14px !default; | |
| 207 | + $u-action-sheet-description-margin: 18px 15px !default; | |
| 208 | + $u-action-sheet-item-wrap-item-padding:17px !default; | |
| 209 | + $u-action-sheet-item-wrap-name-font-size:16px !default; | |
| 210 | + $u-action-sheet-item-wrap-subname-font-size:13px !default; | |
| 211 | + $u-action-sheet-item-wrap-subname-color: #c0c4cc !default; | |
| 212 | + $u-action-sheet-item-wrap-subname-margin-top:10px !default; | |
| 213 | + $u-action-sheet-cancel-text-font-size:16px !default; | |
| 214 | + $u-action-sheet-cancel-text-color:$u-content-color !default; | |
| 215 | + $u-action-sheet-cancel-text-font-size:15px !default; | |
| 216 | + $u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default; | |
| 217 | + | |
| 218 | + .u-reset-button { | |
| 219 | + width: $u-action-sheet-reset-button-width; | |
| 220 | + } | |
| 221 | + | |
| 222 | + .u-action-sheet { | |
| 223 | + text-align: center; | |
| 224 | + &__header { | |
| 225 | + position: relative; | |
| 226 | + padding: $u-action-sheet-title-padding; | |
| 227 | + &__title { | |
| 228 | + font-size: $u-action-sheet-title-font-size; | |
| 229 | + color: $u-action-sheet-title-color; | |
| 230 | + font-weight: bold; | |
| 231 | + text-align: center; | |
| 232 | + } | |
| 233 | + | |
| 234 | + &__icon-wrap { | |
| 235 | + position: absolute; | |
| 236 | + right: $u-action-sheet-header-icon-wrap-right; | |
| 237 | + top: $u-action-sheet-header-icon-wrap-top; | |
| 238 | + } | |
| 239 | + } | |
| 240 | + | |
| 241 | + &__description { | |
| 242 | + font-size: $u-action-sheet-description-font-size; | |
| 243 | + color: $u-tips-color; | |
| 244 | + margin: $u-action-sheet-description-margin; | |
| 245 | + text-align: center; | |
| 246 | + } | |
| 247 | + | |
| 248 | + &__item-wrap { | |
| 249 | + | |
| 250 | + &__item { | |
| 251 | + padding: $u-action-sheet-item-wrap-item-padding; | |
| 252 | + @include flex; | |
| 253 | + align-items: center; | |
| 254 | + justify-content: center; | |
| 255 | + flex-direction: column; | |
| 256 | + | |
| 257 | + &__name { | |
| 258 | + font-size: $u-action-sheet-item-wrap-name-font-size; | |
| 259 | + color: $u-main-color; | |
| 260 | + text-align: center; | |
| 261 | + } | |
| 262 | + | |
| 263 | + &__subname { | |
| 264 | + font-size: $u-action-sheet-item-wrap-subname-font-size; | |
| 265 | + color: $u-action-sheet-item-wrap-subname-color; | |
| 266 | + margin-top: $u-action-sheet-item-wrap-subname-margin-top; | |
| 267 | + text-align: center; | |
| 268 | + } | |
| 269 | + } | |
| 270 | + } | |
| 271 | + | |
| 272 | + &__cancel-text { | |
| 273 | + font-size: $u-action-sheet-cancel-text-font-size; | |
| 274 | + color: $u-action-sheet-cancel-text-color; | |
| 275 | + text-align: center; | |
| 276 | + // padding: $u-action-sheet-cancel-text-font-size; | |
| 277 | + } | |
| 278 | + | |
| 279 | + &--hover { | |
| 280 | + background-color: $u-action-sheet-cancel-text-hover-background-color; | |
| 281 | + } | |
| 282 | + } | |
| 283 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-agreement/u-agreement.vue
0 → 100644
| 1 | +<style scoped lang="scss"> | |
| 2 | + .agreement-content { | |
| 3 | + width: 100%;; | |
| 4 | + display: inline-block; | |
| 5 | + flex-direction: column; | |
| 6 | + .agreement-url { | |
| 7 | + display: inline-block; | |
| 8 | + color: blue; | |
| 9 | + // #ifdef H5 | |
| 10 | + cursor: pointer; | |
| 11 | + // #endif | |
| 12 | + } | |
| 13 | + } | |
| 14 | +</style> | |
| 15 | + | |
| 16 | +<template> | |
| 17 | + <view class="up-agreement"> | |
| 18 | + <up-modal v-model:show="show" showCancelButton @confirm="confirm" @cancel="close" confirmText="阅读并同意"> | |
| 19 | + <view class="agreement-content"> | |
| 20 | + <slot> | |
| 21 | + 我们非常重视您的个人信息和隐私保护。为了更好地保障您的个人权益,在您使用我们的产品前, | |
| 22 | + 请务必审慎阅读《<text class="agreement-url" @click="urlClick('urlProtocol')">用户协议</text>》 | |
| 23 | + 和《<text class="agreement-url" @click="urlClick('urlPrivacy')">隐私政策</text>》内的所有条款, | |
| 24 | + 尤其是:1.我们对您的个人信息的收集/保存/使用/对外提供/保护等规则条款,以及您的用户权利等条款;2. 约定我们的限制责任、免责 | |
| 25 | + 条款;3.其他以颜色或加粗进行标识的重要条款。如您对以上协议有任何疑问,请先不要同意,您点击“同意并继续”的行为即表示您已阅读 | |
| 26 | + 完毕并同意以上协议的全部内容。 | |
| 27 | + </slot> | |
| 28 | + </view> | |
| 29 | + </up-modal> | |
| 30 | + </view> | |
| 31 | +</template> | |
| 32 | + | |
| 33 | +<script> | |
| 34 | + export default { | |
| 35 | + name: 'up-agreement', | |
| 36 | + props: { | |
| 37 | + urlProtocol: { | |
| 38 | + type: String, | |
| 39 | + default: '/pages/user_agreement/agreement/info?title=用户协议' | |
| 40 | + }, | |
| 41 | + urlPrivacy: { | |
| 42 | + type: String, | |
| 43 | + default: '/pages/user_agreement/agreement/info?title=隐私政策' | |
| 44 | + }, | |
| 45 | + }, | |
| 46 | + emits: ['confirm'], | |
| 47 | + data() { | |
| 48 | + return { | |
| 49 | + show: false | |
| 50 | + } | |
| 51 | + }, | |
| 52 | + methods: { | |
| 53 | + close() { | |
| 54 | + // #ifdef H5 | |
| 55 | + window.opener = null; | |
| 56 | + window.close(); | |
| 57 | + // #endif | |
| 58 | + // #ifdef APP-PLUS | |
| 59 | + plus.runtime.quit(); | |
| 60 | + // #endif | |
| 61 | + }, | |
| 62 | + confirm() { | |
| 63 | + this.show = false; | |
| 64 | + this.$emit('confirm', 1); | |
| 65 | + }, | |
| 66 | + showModal() { | |
| 67 | + this.show = true; | |
| 68 | + }, | |
| 69 | + urlClick(type) { | |
| 70 | + uni.navigateTo({ | |
| 71 | + url: this[type] | |
| 72 | + }); | |
| 73 | + } | |
| 74 | + } | |
| 75 | + } | |
| 76 | +</script> | ... | ... |
uni_modules/uview-plus/components/u-album/album.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:47:24 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/album.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // album 组件 | |
| 12 | + album: { | |
| 13 | + urls: [], | |
| 14 | + keyName: '', | |
| 15 | + singleSize: 180, | |
| 16 | + multipleSize: 70, | |
| 17 | + space: 6, | |
| 18 | + singleMode: 'scaleToFill', | |
| 19 | + multipleMode: 'aspectFill', | |
| 20 | + maxCount: 9, | |
| 21 | + previewFullImage: true, | |
| 22 | + rowCount: 3, | |
| 23 | + showMore: true, | |
| 24 | + autoWrap: false, | |
| 25 | + unit: 'px', | |
| 26 | + stop: true, | |
| 27 | + } | |
| 28 | +} | ... | ... |
uni_modules/uview-plus/components/u-album/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 图片地址,Array<String>|Array<Object>形式 | |
| 6 | + urls: { | |
| 7 | + type: Array, | |
| 8 | + default: () => defProps.album.urls | |
| 9 | + }, | |
| 10 | + // 指定从数组的对象元素中读取哪个属性作为图片地址 | |
| 11 | + keyName: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.album.keyName | |
| 14 | + }, | |
| 15 | + // 单图时,图片长边的长度 | |
| 16 | + singleSize: { | |
| 17 | + type: [String, Number], | |
| 18 | + default: () => defProps.album.singleSize | |
| 19 | + }, | |
| 20 | + // 多图时,图片边长 | |
| 21 | + multipleSize: { | |
| 22 | + type: [String, Number], | |
| 23 | + default: () => defProps.album.multipleSize | |
| 24 | + }, | |
| 25 | + // 多图时,图片水平和垂直之间的间隔 | |
| 26 | + space: { | |
| 27 | + type: [String, Number], | |
| 28 | + default: () => defProps.album.space | |
| 29 | + }, | |
| 30 | + // 单图时,图片缩放裁剪的模式 | |
| 31 | + singleMode: { | |
| 32 | + type: String, | |
| 33 | + default: () => defProps.album.singleMode | |
| 34 | + }, | |
| 35 | + // 多图时,图片缩放裁剪的模式 | |
| 36 | + multipleMode: { | |
| 37 | + type: String, | |
| 38 | + default: () => defProps.album.multipleMode | |
| 39 | + }, | |
| 40 | + // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量 | |
| 41 | + maxCount: { | |
| 42 | + type: [String, Number], | |
| 43 | + default: () => defProps.album.maxCount | |
| 44 | + }, | |
| 45 | + // 是否可以预览图片 | |
| 46 | + previewFullImage: { | |
| 47 | + type: Boolean, | |
| 48 | + default: () => defProps.album.previewFullImage | |
| 49 | + }, | |
| 50 | + // 每行展示图片数量,如设置,singleSize和multipleSize将会无效 | |
| 51 | + rowCount: { | |
| 52 | + type: [String, Number], | |
| 53 | + default: () => defProps.album.rowCount | |
| 54 | + }, | |
| 55 | + // 超出maxCount时是否显示查看更多的提示 | |
| 56 | + showMore: { | |
| 57 | + type: Boolean, | |
| 58 | + default: () => defProps.album.showMore | |
| 59 | + }, | |
| 60 | + // 图片形状,circle-圆形,square-方形 | |
| 61 | + shape: { | |
| 62 | + type: String, | |
| 63 | + default: () => defProps.image.shape | |
| 64 | + }, | |
| 65 | + // 圆角,单位任意 | |
| 66 | + radius: { | |
| 67 | + type: [String, Number], | |
| 68 | + default: () => defProps.image.radius | |
| 69 | + }, | |
| 70 | + // 自适应换行 | |
| 71 | + autoWrap: { | |
| 72 | + type: Boolean, | |
| 73 | + default: () => defProps.album.autoWrap | |
| 74 | + }, | |
| 75 | + // 单位 | |
| 76 | + unit: { | |
| 77 | + type: [String], | |
| 78 | + default: () => defProps.album.unit | |
| 79 | + }, | |
| 80 | + // 阻止点击冒泡 | |
| 81 | + stop: { | |
| 82 | + type: Boolean, | |
| 83 | + default: () => defProps.album.stop | |
| 84 | + } | |
| 85 | + } | |
| 86 | +}) | ... | ... |
uni_modules/uview-plus/components/u-album/u-album.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-album"> | |
| 3 | + <view | |
| 4 | + class="u-album__row" | |
| 5 | + ref="u-album__row" | |
| 6 | + v-for="(arr, index) in showUrls" | |
| 7 | + :forComputedUse="albumWidth" | |
| 8 | + :key="index" | |
| 9 | + :style="{flexWrap: autoWrap ? 'wrap' : 'nowrap'}" | |
| 10 | + > | |
| 11 | + <view | |
| 12 | + class="u-album__row__wrapper" | |
| 13 | + v-for="(item, index1) in arr" | |
| 14 | + :key="index1" | |
| 15 | + :style="[imageStyle(index + 1, index1 + 1)]" | |
| 16 | + @tap="previewFullImage ? onPreviewTap($event, getSrc(item)) : ''" | |
| 17 | + > | |
| 18 | + <image | |
| 19 | + :src="getSrc(item)" | |
| 20 | + :mode=" | |
| 21 | + urls.length === 1 | |
| 22 | + ? imageHeight > 0 | |
| 23 | + ? singleMode | |
| 24 | + : 'widthFix' | |
| 25 | + : multipleMode | |
| 26 | + " | |
| 27 | + :style="[ | |
| 28 | + { | |
| 29 | + width: imageWidth, | |
| 30 | + height: imageHeight, | |
| 31 | + borderRadius: shape == 'circle' ? '10000px' : addUnit(radius) | |
| 32 | + } | |
| 33 | + ]" | |
| 34 | + ></image> | |
| 35 | + <view | |
| 36 | + v-if=" | |
| 37 | + showMore && | |
| 38 | + urls.length > rowCount * showUrls.length && | |
| 39 | + index === showUrls.length - 1 && | |
| 40 | + index1 === showUrls[showUrls.length - 1].length - 1 | |
| 41 | + " | |
| 42 | + class="u-album__row__wrapper__text" | |
| 43 | + :style="{ | |
| 44 | + borderRadius: shape == 'circle' ? '50%' : addUnit(radius), | |
| 45 | + }" | |
| 46 | + > | |
| 47 | + <up-text | |
| 48 | + :text="`+${urls.length - maxCount}`" | |
| 49 | + color="#fff" | |
| 50 | + :size="multipleSize * 0.3" | |
| 51 | + align="center" | |
| 52 | + customStyle="justify-content: center" | |
| 53 | + ></up-text> | |
| 54 | + </view> | |
| 55 | + </view> | |
| 56 | + </view> | |
| 57 | + </view> | |
| 58 | +</template> | |
| 59 | + | |
| 60 | +<script> | |
| 61 | +import { props } from './props'; | |
| 62 | +import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 63 | +import { mixin } from '../../libs/mixin/mixin'; | |
| 64 | +import { addUnit, sleep } from '../../libs/function/index'; | |
| 65 | +import test from '../../libs/function/test'; | |
| 66 | +// #ifdef APP-NVUE | |
| 67 | +// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度 | |
| 68 | +const dom = uni.requireNativePlugin('dom') | |
| 69 | +// #endif | |
| 70 | + | |
| 71 | +/** | |
| 72 | + * Album 相册 | |
| 73 | + * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码 | |
| 74 | + * @tutorial https://ijry.github.io/uview-plus/components/album.html | |
| 75 | + * | |
| 76 | + * @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式 | |
| 77 | + * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址 | |
| 78 | + * @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 ) | |
| 79 | + * @property {String | Number} multipleSize 多图时,图片边长 (默认 70 ) | |
| 80 | + * @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 ) | |
| 81 | + * @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' ) | |
| 82 | + * @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' ) | |
| 83 | + * @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 ) | |
| 84 | + * @property {Boolean} previewFullImage 是否可以预览图片 (默认 true ) | |
| 85 | + * @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 ) | |
| 86 | + * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true ) | |
| 87 | + * @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' ) | |
| 88 | + * @property {String | Number} radius 圆角值,单位任意,如果为数值,则为px单位 (默认 0 ) | |
| 89 | + * @property {Boolean} autoWrap 自适应换行模式,不受rowCount限制,图片会自动换行 (默认 false ) | |
| 90 | + * @property {String} unit 图片单位 (默认 px ) | |
| 91 | + * @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width ) | |
| 92 | + * @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album> | |
| 93 | + */ | |
| 94 | +export default { | |
| 95 | + name: 'u-album', | |
| 96 | + mixins: [mpMixin, mixin, props], | |
| 97 | + data() { | |
| 98 | + return { | |
| 99 | + // 单图的宽度 | |
| 100 | + singleWidth: 0, | |
| 101 | + // 单图的高度 | |
| 102 | + singleHeight: 0, | |
| 103 | + // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比 | |
| 104 | + singlePercent: 0.6 | |
| 105 | + } | |
| 106 | + }, | |
| 107 | + watch: { | |
| 108 | + urls: { | |
| 109 | + immediate: true, | |
| 110 | + handler(newVal) { | |
| 111 | + if (newVal.length === 1) { | |
| 112 | + this.getImageRect() | |
| 113 | + } | |
| 114 | + } | |
| 115 | + } | |
| 116 | + }, | |
| 117 | + emits: ["albumWidth"], | |
| 118 | + computed: { | |
| 119 | + imageStyle() { | |
| 120 | + return (index1, index2) => { | |
| 121 | + const { space, rowCount, multipleSize, urls } = this, | |
| 122 | + { addUnit, addStyle } = uni.$u, | |
| 123 | + rowLen = this.showUrls.length, | |
| 124 | + allLen = this.urls.length | |
| 125 | + const style = { | |
| 126 | + marginRight: addUnit(space), | |
| 127 | + marginBottom: addUnit(space) | |
| 128 | + } | |
| 129 | + // 如果为最后一行,则每个图片都无需下边框 | |
| 130 | + if (index1 === rowLen && !this.autoWrap) style.marginBottom = 0 | |
| 131 | + // 每行的最右边一张和总长度的最后一张无需右边框 | |
| 132 | + if (!this.autoWrap) { | |
| 133 | + if ( | |
| 134 | + index2 === rowCount || | |
| 135 | + (index1 === rowLen && | |
| 136 | + index2 === this.showUrls[index1 - 1].length) | |
| 137 | + ) | |
| 138 | + style.marginRight = 0 | |
| 139 | + } | |
| 140 | + return style | |
| 141 | + } | |
| 142 | + }, | |
| 143 | + // 将数组划分为二维数组 | |
| 144 | + showUrls() { | |
| 145 | + if (this.autoWrap) { | |
| 146 | + return [ this.urls.slice(0, this.maxCount) ]; | |
| 147 | + } else { | |
| 148 | + const arr = [] | |
| 149 | + this.urls.map((item, index) => { | |
| 150 | + // 限制最大展示数量 | |
| 151 | + if (index + 1 <= this.maxCount) { | |
| 152 | + // 计算该元素为第几个素组内 | |
| 153 | + const itemIndex = Math.floor(index / this.rowCount) | |
| 154 | + // 判断对应的索引是否存在 | |
| 155 | + if (!arr[itemIndex]) { | |
| 156 | + arr[itemIndex] = [] | |
| 157 | + } | |
| 158 | + arr[itemIndex].push(item) | |
| 159 | + } | |
| 160 | + }) | |
| 161 | + return arr | |
| 162 | + } | |
| 163 | + }, | |
| 164 | + imageWidth() { | |
| 165 | + return addUnit( | |
| 166 | + this.urls.length === 1 ? this.singleWidth : this.multipleSize, this.unit | |
| 167 | + ) | |
| 168 | + }, | |
| 169 | + imageHeight() { | |
| 170 | + return addUnit( | |
| 171 | + this.urls.length === 1 ? this.singleHeight : this.multipleSize, this.unit | |
| 172 | + ) | |
| 173 | + }, | |
| 174 | + // 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度 | |
| 175 | + // 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送 | |
| 176 | + albumWidth() { | |
| 177 | + let width = 0 | |
| 178 | + if (this.urls.length === 1) { | |
| 179 | + width = this.singleWidth | |
| 180 | + } else { | |
| 181 | + width = | |
| 182 | + this.showUrls[0].length * this.multipleSize + | |
| 183 | + this.space * (this.showUrls[0].length - 1) | |
| 184 | + } | |
| 185 | + this.$emit('albumWidth', width) | |
| 186 | + return width | |
| 187 | + } | |
| 188 | + }, | |
| 189 | + methods: { | |
| 190 | + addUnit, | |
| 191 | + // 预览图片 | |
| 192 | + onPreviewTap(e, url) { | |
| 193 | + const urls = this.urls.map((item) => { | |
| 194 | + return this.getSrc(item) | |
| 195 | + }) | |
| 196 | + uni.previewImage({ | |
| 197 | + current: url, | |
| 198 | + urls | |
| 199 | + }) | |
| 200 | + // 是否阻止事件传播 | |
| 201 | + this.stop && this.preventEvent(e) | |
| 202 | + }, | |
| 203 | + // 获取图片的路径 | |
| 204 | + getSrc(item) { | |
| 205 | + return test.object(item) | |
| 206 | + ? (this.keyName && item[this.keyName]) || item.src | |
| 207 | + : item | |
| 208 | + }, | |
| 209 | + // 单图时,获取图片的尺寸 | |
| 210 | + // 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸 | |
| 211 | + // 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent) | |
| 212 | + getImageRect() { | |
| 213 | + const src = this.getSrc(this.urls[0]) | |
| 214 | + uni.getImageInfo({ | |
| 215 | + src, | |
| 216 | + success: (res) => { | |
| 217 | + // 判断图片横向还是竖向展示方式 | |
| 218 | + const isHorizotal = res.width >= res.height | |
| 219 | + this.singleWidth = isHorizotal | |
| 220 | + ? this.singleSize | |
| 221 | + : (res.width / res.height) * this.singleSize | |
| 222 | + this.singleHeight = !isHorizotal | |
| 223 | + ? this.singleSize | |
| 224 | + : (res.height / res.width) * this.singleWidth | |
| 225 | + }, | |
| 226 | + fail: () => { | |
| 227 | + this.getComponentWidth() | |
| 228 | + } | |
| 229 | + }) | |
| 230 | + }, | |
| 231 | + // 获取组件的宽度 | |
| 232 | + async getComponentWidth() { | |
| 233 | + // 延时一定时间,以获取dom尺寸 | |
| 234 | + await sleep(30) | |
| 235 | + // #ifndef APP-NVUE | |
| 236 | + this.$uGetRect('.u-album__row').then((size) => { | |
| 237 | + this.singleWidth = size.width * this.singlePercent | |
| 238 | + }) | |
| 239 | + // #endif | |
| 240 | + | |
| 241 | + // #ifdef APP-NVUE | |
| 242 | + // 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组 | |
| 243 | + const ref = this.$refs['u-album__row'][0] | |
| 244 | + ref && | |
| 245 | + dom.getComponentRect(ref, (res) => { | |
| 246 | + this.singleWidth = res.size.width * this.singlePercent | |
| 247 | + }) | |
| 248 | + // #endif | |
| 249 | + } | |
| 250 | + } | |
| 251 | +} | |
| 252 | +</script> | |
| 253 | + | |
| 254 | +<style lang="scss" scoped> | |
| 255 | +@import '../../libs/css/components.scss'; | |
| 256 | + | |
| 257 | +.u-album { | |
| 258 | + @include flex(column); | |
| 259 | + | |
| 260 | + &__row { | |
| 261 | + @include flex(row); | |
| 262 | + | |
| 263 | + &__wrapper { | |
| 264 | + position: relative; | |
| 265 | + | |
| 266 | + &__text { | |
| 267 | + position: absolute; | |
| 268 | + top: 0; | |
| 269 | + left: 0; | |
| 270 | + right: 0; | |
| 271 | + bottom: 0; | |
| 272 | + background-color: rgba(0, 0, 0, 0.3); | |
| 273 | + @include flex(row); | |
| 274 | + justify-content: center; | |
| 275 | + align-items: center; | |
| 276 | + } | |
| 277 | + } | |
| 278 | + } | |
| 279 | +} | |
| 280 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-alert/alert.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:48:53 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/alert.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // alert警告组件 | |
| 12 | + alert: { | |
| 13 | + title: '', | |
| 14 | + type: 'warning', | |
| 15 | + description: '', | |
| 16 | + closable: false, | |
| 17 | + showIcon: false, | |
| 18 | + effect: 'light', | |
| 19 | + center: false, | |
| 20 | + fontSize: 14 | |
| 21 | + } | |
| 22 | +} | ... | ... |
uni_modules/uview-plus/components/u-alert/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 显示文字 | |
| 6 | + title: { | |
| 7 | + type: String, | |
| 8 | + default: () => defProps.alert.title | |
| 9 | + }, | |
| 10 | + // 主题,success/warning/info/error | |
| 11 | + type: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.alert.type | |
| 14 | + }, | |
| 15 | + // 辅助性文字 | |
| 16 | + description: { | |
| 17 | + type: String, | |
| 18 | + default: () => defProps.alert.description | |
| 19 | + }, | |
| 20 | + // 是否可关闭 | |
| 21 | + closable: { | |
| 22 | + type: Boolean, | |
| 23 | + default: () => defProps.alert.closable | |
| 24 | + }, | |
| 25 | + // 是否显示图标 | |
| 26 | + showIcon: { | |
| 27 | + type: Boolean, | |
| 28 | + default: () => defProps.alert.showIcon | |
| 29 | + }, | |
| 30 | + // 浅或深色调,light-浅色,dark-深色 | |
| 31 | + effect: { | |
| 32 | + type: String, | |
| 33 | + default: () => defProps.alert.effect | |
| 34 | + }, | |
| 35 | + // 文字是否居中 | |
| 36 | + center: { | |
| 37 | + type: Boolean, | |
| 38 | + default: () => defProps.alert.center | |
| 39 | + }, | |
| 40 | + // 字体大小 | |
| 41 | + fontSize: { | |
| 42 | + type: [String, Number], | |
| 43 | + default: () => defProps.alert.fontSize | |
| 44 | + } | |
| 45 | + } | |
| 46 | +}) | ... | ... |
uni_modules/uview-plus/components/u-alert/u-alert.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <u-transition | |
| 3 | + mode="fade" | |
| 4 | + :show="show" | |
| 5 | + > | |
| 6 | + <view | |
| 7 | + class="u-alert" | |
| 8 | + :class="[`u-alert--${type}--${effect}`]" | |
| 9 | + @tap.stop="clickHandler" | |
| 10 | + :style="[addStyle(customStyle)]" | |
| 11 | + > | |
| 12 | + <view | |
| 13 | + class="u-alert__icon" | |
| 14 | + v-if="showIcon" | |
| 15 | + > | |
| 16 | + <u-icon | |
| 17 | + :name="iconName" | |
| 18 | + size="18" | |
| 19 | + :color="iconColor" | |
| 20 | + ></u-icon> | |
| 21 | + </view> | |
| 22 | + <view | |
| 23 | + class="u-alert__content" | |
| 24 | + :style="[{ | |
| 25 | + paddingRight: closable ? '20px' : 0 | |
| 26 | + }]" | |
| 27 | + > | |
| 28 | + <text | |
| 29 | + class="u-alert__content__title" | |
| 30 | + v-if="title" | |
| 31 | + :style="[{ | |
| 32 | + fontSize: addUnit(fontSize), | |
| 33 | + textAlign: center ? 'center' : 'left' | |
| 34 | + }]" | |
| 35 | + :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]" | |
| 36 | + >{{ title }}</text> | |
| 37 | + <text | |
| 38 | + class="u-alert__content__desc" | |
| 39 | + v-if="description" | |
| 40 | + :style="[{ | |
| 41 | + fontSize: addUnit(fontSize), | |
| 42 | + textAlign: center ? 'center' : 'left' | |
| 43 | + }]" | |
| 44 | + :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]" | |
| 45 | + >{{ description }}</text> | |
| 46 | + </view> | |
| 47 | + <view | |
| 48 | + class="u-alert__close" | |
| 49 | + v-if="closable" | |
| 50 | + @tap.stop="closeHandler" | |
| 51 | + > | |
| 52 | + <u-icon | |
| 53 | + name="close" | |
| 54 | + :color="iconColor" | |
| 55 | + size="15" | |
| 56 | + ></u-icon> | |
| 57 | + </view> | |
| 58 | + </view> | |
| 59 | + </u-transition> | |
| 60 | +</template> | |
| 61 | + | |
| 62 | +<script> | |
| 63 | + import { props } from './props'; | |
| 64 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 65 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 66 | + import { addUnit, addStyle } from '../../libs/function/index'; | |
| 67 | + /** | |
| 68 | + * Alert 警告提示 | |
| 69 | + * @description 警告提示,展现需要关注的信息。 | |
| 70 | + * @tutorial https://ijry.github.io/uview-plus/components/alertTips.html | |
| 71 | + * | |
| 72 | + * @property {String} title 显示的文字 | |
| 73 | + * @property {String} type 使用预设的颜色 (默认 'warning' ) | |
| 74 | + * @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选 | |
| 75 | + * @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false ) | |
| 76 | + * @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false ) | |
| 77 | + * @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' ) | |
| 78 | + * @property {Boolean} center 文字是否居中 (默认 false ) | |
| 79 | + * @property {String | Number} fontSize 字体大小 (默认 14 ) | |
| 80 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 81 | + * @event {Function} click 点击组件时触发 | |
| 82 | + * @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert> | |
| 83 | + */ | |
| 84 | + export default { | |
| 85 | + name: 'u-alert', | |
| 86 | + mixins: [mpMixin, mixin, props], | |
| 87 | + data() { | |
| 88 | + return { | |
| 89 | + show: true | |
| 90 | + } | |
| 91 | + }, | |
| 92 | + computed: { | |
| 93 | + iconColor() { | |
| 94 | + return this.effect === 'light' ? this.type : '#fff' | |
| 95 | + }, | |
| 96 | + // 不同主题对应不同的图标 | |
| 97 | + iconName() { | |
| 98 | + switch (this.type) { | |
| 99 | + case 'success': | |
| 100 | + return 'checkmark-circle-fill'; | |
| 101 | + break; | |
| 102 | + case 'error': | |
| 103 | + return 'close-circle-fill'; | |
| 104 | + break; | |
| 105 | + case 'warning': | |
| 106 | + return 'error-circle-fill'; | |
| 107 | + break; | |
| 108 | + case 'info': | |
| 109 | + return 'info-circle-fill'; | |
| 110 | + break; | |
| 111 | + case 'primary': | |
| 112 | + return 'more-circle-fill'; | |
| 113 | + break; | |
| 114 | + default: | |
| 115 | + return 'error-circle-fill'; | |
| 116 | + } | |
| 117 | + } | |
| 118 | + }, | |
| 119 | + emits: ["click"], | |
| 120 | + methods: { | |
| 121 | + addUnit, | |
| 122 | + addStyle, | |
| 123 | + // 点击内容 | |
| 124 | + clickHandler() { | |
| 125 | + this.$emit('click') | |
| 126 | + }, | |
| 127 | + // 点击关闭按钮 | |
| 128 | + closeHandler() { | |
| 129 | + this.show = false | |
| 130 | + } | |
| 131 | + } | |
| 132 | + } | |
| 133 | +</script> | |
| 134 | + | |
| 135 | +<style lang="scss" scoped> | |
| 136 | + @import "../../libs/css/components.scss"; | |
| 137 | + | |
| 138 | + .u-alert { | |
| 139 | + position: relative; | |
| 140 | + background-color: $u-primary; | |
| 141 | + padding: 8px 10px; | |
| 142 | + @include flex(row); | |
| 143 | + align-items: center; | |
| 144 | + border-top-left-radius: 4px; | |
| 145 | + border-top-right-radius: 4px; | |
| 146 | + border-bottom-left-radius: 4px; | |
| 147 | + border-bottom-right-radius: 4px; | |
| 148 | + | |
| 149 | + &--primary--dark { | |
| 150 | + background-color: $u-primary; | |
| 151 | + } | |
| 152 | + | |
| 153 | + &--primary--light { | |
| 154 | + background-color: #ecf5ff; | |
| 155 | + } | |
| 156 | + | |
| 157 | + &--error--dark { | |
| 158 | + background-color: $u-error; | |
| 159 | + } | |
| 160 | + | |
| 161 | + &--error--light { | |
| 162 | + background-color: #FEF0F0; | |
| 163 | + } | |
| 164 | + | |
| 165 | + &--success--dark { | |
| 166 | + background-color: $u-success; | |
| 167 | + } | |
| 168 | + | |
| 169 | + &--success--light { | |
| 170 | + background-color: #f5fff0; | |
| 171 | + } | |
| 172 | + | |
| 173 | + &--warning--dark { | |
| 174 | + background-color: $u-warning; | |
| 175 | + } | |
| 176 | + | |
| 177 | + &--warning--light { | |
| 178 | + background-color: #FDF6EC; | |
| 179 | + } | |
| 180 | + | |
| 181 | + &--info--dark { | |
| 182 | + background-color: $u-info; | |
| 183 | + } | |
| 184 | + | |
| 185 | + &--info--light { | |
| 186 | + background-color: #f4f4f5; | |
| 187 | + } | |
| 188 | + | |
| 189 | + &__icon { | |
| 190 | + margin-right: 5px; | |
| 191 | + } | |
| 192 | + | |
| 193 | + &__content { | |
| 194 | + @include flex(column); | |
| 195 | + flex: 1; | |
| 196 | + | |
| 197 | + &__title { | |
| 198 | + color: $u-main-color; | |
| 199 | + font-size: 14px; | |
| 200 | + font-weight: bold; | |
| 201 | + color: #fff; | |
| 202 | + margin-bottom: 2px; | |
| 203 | + } | |
| 204 | + | |
| 205 | + &__desc { | |
| 206 | + color: $u-main-color; | |
| 207 | + font-size: 14px; | |
| 208 | + flex-wrap: wrap; | |
| 209 | + color: #fff; | |
| 210 | + } | |
| 211 | + } | |
| 212 | + | |
| 213 | + &__title--dark, | |
| 214 | + &__desc--dark { | |
| 215 | + color: #FFFFFF; | |
| 216 | + } | |
| 217 | + | |
| 218 | + &__text--primary--light, | |
| 219 | + &__text--primary--light { | |
| 220 | + color: $u-primary; | |
| 221 | + } | |
| 222 | + | |
| 223 | + &__text--success--light, | |
| 224 | + &__text--success--light { | |
| 225 | + color: $u-success; | |
| 226 | + } | |
| 227 | + | |
| 228 | + &__text--warning--light, | |
| 229 | + &__text--warning--light { | |
| 230 | + color: $u-warning; | |
| 231 | + } | |
| 232 | + | |
| 233 | + &__text--error--light, | |
| 234 | + &__text--error--light { | |
| 235 | + color: $u-error; | |
| 236 | + } | |
| 237 | + | |
| 238 | + &__text--info--light, | |
| 239 | + &__text--info--light { | |
| 240 | + color: $u-info; | |
| 241 | + } | |
| 242 | + | |
| 243 | + &__close { | |
| 244 | + position: absolute; | |
| 245 | + top: 11px; | |
| 246 | + right: 10px; | |
| 247 | + } | |
| 248 | + } | |
| 249 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-avatar-group/avatarGroup.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:49:55 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/avatarGroup.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // avatarGroup 组件 | |
| 12 | + avatarGroup: { | |
| 13 | + urls: [], | |
| 14 | + maxCount: 5, | |
| 15 | + shape: 'circle', | |
| 16 | + mode: 'scaleToFill', | |
| 17 | + showMore: true, | |
| 18 | + size: 40, | |
| 19 | + keyName: '', | |
| 20 | + gap: 0.5, | |
| 21 | + extraValue: 0 | |
| 22 | + } | |
| 23 | +} | ... | ... |
uni_modules/uview-plus/components/u-avatar-group/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 头像图片组 | |
| 6 | + urls: { | |
| 7 | + type: Array, | |
| 8 | + default: () => defProps.avatarGroup.urls | |
| 9 | + }, | |
| 10 | + // 最多展示的头像数量 | |
| 11 | + maxCount: { | |
| 12 | + type: [String, Number], | |
| 13 | + default: () => defProps.avatarGroup.maxCount | |
| 14 | + }, | |
| 15 | + // 头像形状 | |
| 16 | + shape: { | |
| 17 | + type: String, | |
| 18 | + default: () => defProps.avatarGroup.shape | |
| 19 | + }, | |
| 20 | + // 图片裁剪模式 | |
| 21 | + mode: { | |
| 22 | + type: String, | |
| 23 | + default: () => defProps.avatarGroup.mode | |
| 24 | + }, | |
| 25 | + // 超出maxCount时是否显示查看更多的提示 | |
| 26 | + showMore: { | |
| 27 | + type: Boolean, | |
| 28 | + default: () => defProps.avatarGroup.showMore | |
| 29 | + }, | |
| 30 | + // 头像大小 | |
| 31 | + size: { | |
| 32 | + type: [String, Number], | |
| 33 | + default: () => defProps.avatarGroup.size | |
| 34 | + }, | |
| 35 | + // 指定从数组的对象元素中读取哪个属性作为图片地址 | |
| 36 | + keyName: { | |
| 37 | + type: String, | |
| 38 | + default: () => defProps.avatarGroup.keyName | |
| 39 | + }, | |
| 40 | + // 头像之间的遮挡比例 | |
| 41 | + gap: { | |
| 42 | + type: [String, Number], | |
| 43 | + validator(value) { | |
| 44 | + return value >= 0 && value <= 1 | |
| 45 | + }, | |
| 46 | + default: () => defProps.avatarGroup.gap | |
| 47 | + }, | |
| 48 | + // 需额外显示的值 | |
| 49 | + extraValue: { | |
| 50 | + type: [Number, String], | |
| 51 | + default: () => defProps.avatarGroup.extraValue | |
| 52 | + } | |
| 53 | + } | |
| 54 | +}) | ... | ... |
uni_modules/uview-plus/components/u-avatar-group/u-avatar-group.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-avatar-group"> | |
| 3 | + <view | |
| 4 | + class="u-avatar-group__item" | |
| 5 | + v-for="(item, index) in showUrl" | |
| 6 | + :key="index" | |
| 7 | + :style="{ | |
| 8 | + marginLeft: index === 0 ? 0 : addUnit(-size * gap) | |
| 9 | + }" | |
| 10 | + > | |
| 11 | + <u-avatar | |
| 12 | + :size="size" | |
| 13 | + :shape="shape" | |
| 14 | + :mode="mode" | |
| 15 | + :src="testObject(item) ? keyName && item[keyName] || item.url : item" | |
| 16 | + ></u-avatar> | |
| 17 | + <view | |
| 18 | + class="u-avatar-group__item__show-more" | |
| 19 | + v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)" | |
| 20 | + @tap="clickHandler" | |
| 21 | + > | |
| 22 | + <up-text | |
| 23 | + color="#ffffff" | |
| 24 | + :size="size * 0.4" | |
| 25 | + :text="`+${extraValue || urls.length - showUrl.length}`" | |
| 26 | + align="center" | |
| 27 | + customStyle="justify-content: center" | |
| 28 | + ></up-text> | |
| 29 | + </view> | |
| 30 | + </view> | |
| 31 | + </view> | |
| 32 | +</template> | |
| 33 | + | |
| 34 | +<script> | |
| 35 | + import { props } from './props'; | |
| 36 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 37 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 38 | + import { addUnit } from '../../libs/function/index'; | |
| 39 | + import test from '../../libs/function/test'; | |
| 40 | + /** | |
| 41 | + * AvatarGroup 头像组 | |
| 42 | + * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 | |
| 43 | + * @tutorial https://ijry.github.io/uview-plus/components/avatar.html | |
| 44 | + * | |
| 45 | + * @property {Array} urls 头像图片组 (默认 [] ) | |
| 46 | + * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 ) | |
| 47 | + * @property {String} shape 头像形状( 'circle' (默认) | 'square' ) | |
| 48 | + * @property {String} mode 图片裁剪模式(默认 'scaleToFill' ) | |
| 49 | + * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true ) | |
| 50 | + * @property {String | Number} size 头像大小 (默认 40 ) | |
| 51 | + * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址 | |
| 52 | + * @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 ) | |
| 53 | + * @property {String | Number} extraValue 需额外显示的值 | |
| 54 | + * @event {Function} showMore 头像组更多点击 | |
| 55 | + * @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=> | |
| 56 | + */ | |
| 57 | + export default { | |
| 58 | + name: 'u-avatar-group', | |
| 59 | + mixins: [mpMixin, mixin, props], | |
| 60 | + data() { | |
| 61 | + return { | |
| 62 | + | |
| 63 | + } | |
| 64 | + }, | |
| 65 | + computed: { | |
| 66 | + showUrl() { | |
| 67 | + return this.urls.slice(0, this.maxCount) | |
| 68 | + } | |
| 69 | + }, | |
| 70 | + emits: ["showMore"], | |
| 71 | + methods: { | |
| 72 | + addUnit, | |
| 73 | + testObject: test.object, | |
| 74 | + clickHandler() { | |
| 75 | + this.$emit('showMore') | |
| 76 | + } | |
| 77 | + }, | |
| 78 | + } | |
| 79 | +</script> | |
| 80 | + | |
| 81 | +<style lang="scss" scoped> | |
| 82 | + @import "../../libs/css/components.scss"; | |
| 83 | + | |
| 84 | + .u-avatar-group { | |
| 85 | + @include flex; | |
| 86 | + | |
| 87 | + &__item { | |
| 88 | + margin-left: -10px; | |
| 89 | + position: relative; | |
| 90 | + | |
| 91 | + &--no-indent { | |
| 92 | + // 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持 | |
| 93 | + margin-left: 0; | |
| 94 | + } | |
| 95 | + | |
| 96 | + &__show-more { | |
| 97 | + position: absolute; | |
| 98 | + top: 0; | |
| 99 | + bottom: 0; | |
| 100 | + left: 0; | |
| 101 | + right: 0; | |
| 102 | + background-color: rgba(0, 0, 0, 0.3); | |
| 103 | + @include flex; | |
| 104 | + align-items: center; | |
| 105 | + justify-content: center; | |
| 106 | + border-radius: 100px; | |
| 107 | + } | |
| 108 | + } | |
| 109 | + } | |
| 110 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-avatar/avatar.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:49:22 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/avatar.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // avatar 组件 | |
| 12 | + avatar: { | |
| 13 | + src: '', | |
| 14 | + shape: 'circle', | |
| 15 | + size: 40, | |
| 16 | + mode: 'scaleToFill', | |
| 17 | + text: '', | |
| 18 | + bgColor: '#c0c4cc', | |
| 19 | + color: '#ffffff', | |
| 20 | + fontSize: 18, | |
| 21 | + icon: '', | |
| 22 | + mpAvatar: false, | |
| 23 | + randomBgColor: false, | |
| 24 | + defaultUrl: '', | |
| 25 | + colorIndex: '', | |
| 26 | + name: '' | |
| 27 | + } | |
| 28 | +} | ... | ... |
uni_modules/uview-plus/components/u-avatar/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +import test from '../../libs/function/test'; | |
| 4 | +export const props = defineMixin({ | |
| 5 | + props: { | |
| 6 | + // 头像图片路径(不能为相对路径) | |
| 7 | + src: { | |
| 8 | + type: String, | |
| 9 | + default: () => defProps.avatar.src | |
| 10 | + }, | |
| 11 | + // 头像形状,circle-圆形,square-方形 | |
| 12 | + shape: { | |
| 13 | + type: String, | |
| 14 | + default: () => defProps.avatar.shape | |
| 15 | + }, | |
| 16 | + // 头像尺寸 | |
| 17 | + size: { | |
| 18 | + type: [String, Number], | |
| 19 | + default: () => defProps.avatar.size | |
| 20 | + }, | |
| 21 | + // 裁剪模式 | |
| 22 | + mode: { | |
| 23 | + type: String, | |
| 24 | + default: () => defProps.avatar.mode | |
| 25 | + }, | |
| 26 | + // 显示的文字 | |
| 27 | + text: { | |
| 28 | + type: String, | |
| 29 | + default: () => defProps.avatar.text | |
| 30 | + }, | |
| 31 | + // 背景色 | |
| 32 | + bgColor: { | |
| 33 | + type: String, | |
| 34 | + default: () => defProps.avatar.bgColor | |
| 35 | + }, | |
| 36 | + // 文字颜色 | |
| 37 | + color: { | |
| 38 | + type: String, | |
| 39 | + default: () => defProps.avatar.color | |
| 40 | + }, | |
| 41 | + // 文字大小 | |
| 42 | + fontSize: { | |
| 43 | + type: [String, Number], | |
| 44 | + default: () => defProps.avatar.fontSize | |
| 45 | + }, | |
| 46 | + // 显示的图标 | |
| 47 | + icon: { | |
| 48 | + type: String, | |
| 49 | + default: () => defProps.avatar.icon | |
| 50 | + }, | |
| 51 | + // 显示小程序头像,只对百度,微信,QQ小程序有效 | |
| 52 | + mpAvatar: { | |
| 53 | + type: Boolean, | |
| 54 | + default: () => defProps.avatar.mpAvatar | |
| 55 | + }, | |
| 56 | + // 是否使用随机背景色 | |
| 57 | + randomBgColor: { | |
| 58 | + type: Boolean, | |
| 59 | + default: () => defProps.avatar.randomBgColor | |
| 60 | + }, | |
| 61 | + // 加载失败的默认头像(组件有内置默认图片) | |
| 62 | + defaultUrl: { | |
| 63 | + type: String, | |
| 64 | + default: () => defProps.avatar.defaultUrl | |
| 65 | + }, | |
| 66 | + // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间 | |
| 67 | + colorIndex: { | |
| 68 | + type: [String, Number], | |
| 69 | + // 校验参数规则,索引在0-19之间 | |
| 70 | + validator(n) { | |
| 71 | + return test.range(n, [0, 19]) || n === '' | |
| 72 | + }, | |
| 73 | + default: () => defProps.avatar.colorIndex | |
| 74 | + }, | |
| 75 | + // 组件标识符 | |
| 76 | + name: { | |
| 77 | + type: String, | |
| 78 | + default: () => defProps.avatar.name | |
| 79 | + } | |
| 80 | + } | |
| 81 | +}) | ... | ... |
uni_modules/uview-plus/components/u-avatar/u-avatar.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view | |
| 3 | + class="u-avatar" | |
| 4 | + :class="[`u-avatar--${shape}`]" | |
| 5 | + :style="[{ | |
| 6 | + backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : random(0, 19)] : bgColor) : 'transparent', | |
| 7 | + width: addUnit(size), | |
| 8 | + height: addUnit(size), | |
| 9 | + }, addStyle(customStyle)]" | |
| 10 | + @tap="clickHandler" | |
| 11 | + > | |
| 12 | + <slot> | |
| 13 | + <!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU --> | |
| 14 | + <open-data | |
| 15 | + v-if="mpAvatar && allowMp" | |
| 16 | + type="userAvatarUrl" | |
| 17 | + :style="[{ | |
| 18 | + width: addUnit(size), | |
| 19 | + height: addUnit(size) | |
| 20 | + }]" | |
| 21 | + /> | |
| 22 | + <!-- #endif --> | |
| 23 | + <!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU --> | |
| 24 | + <template v-if="mpAvatar && allowMp"></template> | |
| 25 | + <!-- #endif --> | |
| 26 | + <u-icon | |
| 27 | + v-else-if="icon" | |
| 28 | + :name="icon" | |
| 29 | + :size="fontSize" | |
| 30 | + :color="color" | |
| 31 | + ></u-icon> | |
| 32 | + <up-text | |
| 33 | + v-else-if="text" | |
| 34 | + :text="text" | |
| 35 | + :size="fontSize" | |
| 36 | + :color="color" | |
| 37 | + align="center" | |
| 38 | + customStyle="justify-content: center" | |
| 39 | + ></up-text> | |
| 40 | + <image | |
| 41 | + class="u-avatar__image" | |
| 42 | + v-else | |
| 43 | + :class="[`u-avatar__image--${shape}`]" | |
| 44 | + :src="avatarUrl || defaultUrl" | |
| 45 | + :mode="mode" | |
| 46 | + @error="errorHandler" | |
| 47 | + :style="[{ | |
| 48 | + width: addUnit(size), | |
| 49 | + height: addUnit(size) | |
| 50 | + }]" | |
| 51 | + ></image> | |
| 52 | + </slot> | |
| 53 | + </view> | |
| 54 | +</template> | |
| 55 | + | |
| 56 | +<script> | |
| 57 | + import { props } from './props'; | |
| 58 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 59 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 60 | + import { addStyle, addUnit, random } from '../../libs/function/index'; | |
| 61 | + const base64Avatar = | |
| 62 | + "data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z"; | |
| 63 | + /** | |
| 64 | + * Avatar 头像 | |
| 65 | + * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 | |
| 66 | + * @tutorial https://ijry.github.io/uview-plus/components/avatar.html | |
| 67 | + * | |
| 68 | + * @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径) | |
| 69 | + * @property {String} shape 头像形状 ( circle (默认) | square) | |
| 70 | + * @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 ) | |
| 71 | + * @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' ) | |
| 72 | + * @property {String} text 用文字替代图片,级别优先于src | |
| 73 | + * @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' ) | |
| 74 | + * @property {String} color 文字颜色 (默认 '#ffffff' ) | |
| 75 | + * @property {String | Number} fontSize 文字大小 (默认 18 ) | |
| 76 | + * @property {String} icon 显示的图标 | |
| 77 | + * @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false ) | |
| 78 | + * @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false ) | |
| 79 | + * @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片) | |
| 80 | + * @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间 | |
| 81 | + * @property {String} name 组件标识符 (默认 'level' ) | |
| 82 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 83 | + * | |
| 84 | + * @event {Function} click 点击组件时触发 index: 用户传递的标识符 | |
| 85 | + * @example <u-avatar :src="src" mode="square"></u-avatar> | |
| 86 | + */ | |
| 87 | + export default { | |
| 88 | + name: 'u-avatar', | |
| 89 | + mixins: [mpMixin, mixin, props], | |
| 90 | + data() { | |
| 91 | + return { | |
| 92 | + // 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色 | |
| 93 | + colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2', | |
| 94 | + '#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee', | |
| 95 | + '#73d1f1', | |
| 96 | + '#80a7dc' | |
| 97 | + ], | |
| 98 | + avatarUrl: this.src, | |
| 99 | + allowMp: false | |
| 100 | + } | |
| 101 | + }, | |
| 102 | + watch: { | |
| 103 | + // 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值 | |
| 104 | + // 而组件内部不能直接修改props的值,所以需要一个中间变量 | |
| 105 | + src: { | |
| 106 | + immediate: true, | |
| 107 | + handler(newVal) { | |
| 108 | + this.avatarUrl = newVal | |
| 109 | + // 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示 | |
| 110 | + if(!newVal) { | |
| 111 | + this.errorHandler() | |
| 112 | + } | |
| 113 | + } | |
| 114 | + } | |
| 115 | + }, | |
| 116 | + computed: { | |
| 117 | + imageStyle() { | |
| 118 | + const style = {} | |
| 119 | + return style | |
| 120 | + } | |
| 121 | + }, | |
| 122 | + created() { | |
| 123 | + this.init() | |
| 124 | + }, | |
| 125 | + emits: ["click"], | |
| 126 | + methods: { | |
| 127 | + addStyle, | |
| 128 | + addUnit, | |
| 129 | + random, | |
| 130 | + init() { | |
| 131 | + // 目前只有这几个小程序平台具有open-data标签 | |
| 132 | + // 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑 | |
| 133 | + // 故目前自动获取小程序头像只支持这几个平台 | |
| 134 | + // #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU | |
| 135 | + this.allowMp = true | |
| 136 | + // #endif | |
| 137 | + }, | |
| 138 | + // 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式 | |
| 139 | + isImg() { | |
| 140 | + return this.src.indexOf('/') !== -1 | |
| 141 | + }, | |
| 142 | + // 图片加载时失败时触发 | |
| 143 | + errorHandler() { | |
| 144 | + this.avatarUrl = this.defaultUrl || base64Avatar | |
| 145 | + }, | |
| 146 | + clickHandler() { | |
| 147 | + this.$emit('click', this.name) | |
| 148 | + } | |
| 149 | + } | |
| 150 | + } | |
| 151 | +</script> | |
| 152 | + | |
| 153 | +<style lang="scss" scoped> | |
| 154 | + @import "../../libs/css/components.scss"; | |
| 155 | + | |
| 156 | + .u-avatar { | |
| 157 | + @include flex; | |
| 158 | + align-items: center; | |
| 159 | + justify-content: center; | |
| 160 | + | |
| 161 | + &--circle { | |
| 162 | + border-radius: 100px; | |
| 163 | + } | |
| 164 | + | |
| 165 | + &--square { | |
| 166 | + border-radius: 4px; | |
| 167 | + } | |
| 168 | + | |
| 169 | + &__image { | |
| 170 | + &--circle { | |
| 171 | + border-radius: 100px; | |
| 172 | + overflow: hidden; | |
| 173 | + } | |
| 174 | + | |
| 175 | + &--square { | |
| 176 | + border-radius: 4px; | |
| 177 | + } | |
| 178 | + } | |
| 179 | + } | |
| 180 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-back-top/backtop.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:50:18 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/backtop.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // backtop组件 | |
| 12 | + backtop: { | |
| 13 | + mode: 'circle', | |
| 14 | + icon: 'arrow-upward', | |
| 15 | + text: '', | |
| 16 | + duration: 100, | |
| 17 | + scrollTop: 0, | |
| 18 | + top: 400, | |
| 19 | + bottom: 100, | |
| 20 | + right: 20, | |
| 21 | + zIndex: 9, | |
| 22 | + iconStyle: { | |
| 23 | + color: '#909399', | |
| 24 | + fontSize: '19px' | |
| 25 | + } | |
| 26 | + } | |
| 27 | +} | ... | ... |
uni_modules/uview-plus/components/u-back-top/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 返回顶部的形状,circle-圆形,square-方形 | |
| 6 | + mode: { | |
| 7 | + type: String, | |
| 8 | + default: () => defProps.backtop.mode | |
| 9 | + }, | |
| 10 | + // 自定义图标 | |
| 11 | + icon: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.backtop.icon | |
| 14 | + }, | |
| 15 | + // 提示文字 | |
| 16 | + text: { | |
| 17 | + type: String, | |
| 18 | + default: () => defProps.backtop.text | |
| 19 | + }, | |
| 20 | + // 返回顶部滚动时间 | |
| 21 | + duration: { | |
| 22 | + type: [String, Number], | |
| 23 | + default: () => defProps.backtop.duration | |
| 24 | + }, | |
| 25 | + // 滚动距离 | |
| 26 | + scrollTop: { | |
| 27 | + type: [String, Number], | |
| 28 | + default: () => defProps.backtop.scrollTop | |
| 29 | + }, | |
| 30 | + // 距离顶部多少距离显示,单位px | |
| 31 | + top: { | |
| 32 | + type: [String, Number], | |
| 33 | + default: () => defProps.backtop.top | |
| 34 | + }, | |
| 35 | + // 返回顶部按钮到底部的距离,单位px | |
| 36 | + bottom: { | |
| 37 | + type: [String, Number], | |
| 38 | + default: () => defProps.backtop.bottom | |
| 39 | + }, | |
| 40 | + // 返回顶部按钮到右边的距离,单位px | |
| 41 | + right: { | |
| 42 | + type: [String, Number], | |
| 43 | + default: () => defProps.backtop.right | |
| 44 | + }, | |
| 45 | + // 层级 | |
| 46 | + zIndex: { | |
| 47 | + type: [String, Number], | |
| 48 | + default: () => defProps.backtop.zIndex | |
| 49 | + }, | |
| 50 | + // 图标的样式,对象形式 | |
| 51 | + iconStyle: { | |
| 52 | + type: Object, | |
| 53 | + default: () => defProps.backtop.iconStyle | |
| 54 | + } | |
| 55 | + } | |
| 56 | +}) | ... | ... |
uni_modules/uview-plus/components/u-back-top/u-back-top.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <u-transition | |
| 3 | + mode="fade" | |
| 4 | + :customStyle="backTopStyle" | |
| 5 | + :show="show" | |
| 6 | + > | |
| 7 | + <view | |
| 8 | + class="u-back-top" | |
| 9 | + :style="[contentStyle]" | |
| 10 | + v-if="!$slots.default && !$slots.$default" | |
| 11 | + @click="backToTop" | |
| 12 | + > | |
| 13 | + <u-icon | |
| 14 | + :name="icon" | |
| 15 | + :custom-style="iconStyle" | |
| 16 | + ></u-icon> | |
| 17 | + <text | |
| 18 | + v-if="text" | |
| 19 | + class="u-back-top__text" | |
| 20 | + >{{text}}</text> | |
| 21 | + </view> | |
| 22 | + <slot v-else /> | |
| 23 | + </u-transition> | |
| 24 | +</template> | |
| 25 | + | |
| 26 | +<script> | |
| 27 | + import { props } from './props'; | |
| 28 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 29 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 30 | + import { addUnit, addStyle, getPx, deepMerge, error } from '../../libs/function/index'; | |
| 31 | + // #ifdef APP-NVUE | |
| 32 | + const dom = weex.requireModule('dom') | |
| 33 | + // #endif | |
| 34 | + /** | |
| 35 | + * backTop 返回顶部 | |
| 36 | + * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。 | |
| 37 | + * @tutorial https://uview-plus.jiangruyi.com/components/backTop.html | |
| 38 | + * | |
| 39 | + * @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' ) | |
| 40 | + * @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例 | |
| 41 | + * @property {String} text 提示文字 | |
| 42 | + * @property {String | Number} duration 返回顶部滚动时间 (默认 100) | |
| 43 | + * @property {String | Number} scrollTop 滚动距离 (默认 0 ) | |
| 44 | + * @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 ) | |
| 45 | + * @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 ) | |
| 46 | + * @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 ) | |
| 47 | + * @property {String | Number} zIndex 层级 (默认 9 ) | |
| 48 | + * @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'}) | |
| 49 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 50 | + * | |
| 51 | + * @example <u-back-top :scrollTop="scrollTop"></u-back-top> | |
| 52 | + */ | |
| 53 | + export default { | |
| 54 | + name: 'u-back-top', | |
| 55 | + mixins: [mpMixin, mixin, props], | |
| 56 | + computed: { | |
| 57 | + backTopStyle() { | |
| 58 | + // 动画组件样式 | |
| 59 | + const style = { | |
| 60 | + bottom: addUnit(this.bottom), | |
| 61 | + right: addUnit(this.right), | |
| 62 | + width: '40px', | |
| 63 | + height: '40px', | |
| 64 | + position: 'fixed', | |
| 65 | + zIndex: 10, | |
| 66 | + } | |
| 67 | + return style | |
| 68 | + }, | |
| 69 | + show() { | |
| 70 | + return getPx(this.scrollTop) > getPx(this.top) | |
| 71 | + }, | |
| 72 | + contentStyle() { | |
| 73 | + const style = {} | |
| 74 | + let radius = 0 | |
| 75 | + // 是否圆形 | |
| 76 | + if(this.mode === 'circle') { | |
| 77 | + radius = '100px' | |
| 78 | + } else { | |
| 79 | + radius = '4px' | |
| 80 | + } | |
| 81 | + // 为了兼容安卓nvue,只能这么分开写 | |
| 82 | + style.borderTopLeftRadius = radius | |
| 83 | + style.borderTopRightRadius = radius | |
| 84 | + style.borderBottomLeftRadius = radius | |
| 85 | + style.borderBottomRightRadius = radius | |
| 86 | + return deepMerge(style, addStyle(this.customStyle)) | |
| 87 | + } | |
| 88 | + }, | |
| 89 | + emits: ["click"], | |
| 90 | + methods: { | |
| 91 | + backToTop() { | |
| 92 | + // #ifdef APP-NVUE | |
| 93 | + if (!this.$parent.$refs['u-back-top']) { | |
| 94 | + error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`) | |
| 95 | + } | |
| 96 | + dom.scrollToElement(this.$parent.$refs['u-back-top'], { | |
| 97 | + offset: 0 | |
| 98 | + }) | |
| 99 | + // #endif | |
| 100 | + | |
| 101 | + // #ifndef APP-NVUE | |
| 102 | + uni.pageScrollTo({ | |
| 103 | + scrollTop: 0, | |
| 104 | + duration: this.duration | |
| 105 | + }); | |
| 106 | + // #endif | |
| 107 | + this.$emit('click') | |
| 108 | + } | |
| 109 | + } | |
| 110 | + } | |
| 111 | +</script> | |
| 112 | + | |
| 113 | +<style lang="scss" scoped> | |
| 114 | + @import '../../libs/css/components.scss'; | |
| 115 | + $u-back-top-flex:1 !default; | |
| 116 | + $u-back-top-height:100% !default; | |
| 117 | + $u-back-top-background-color:#E1E1E1 !default; | |
| 118 | + $u-back-top-tips-font-size:12px !default; | |
| 119 | + .u-back-top { | |
| 120 | + @include flex; | |
| 121 | + flex-direction: column; | |
| 122 | + align-items: center; | |
| 123 | + flex:$u-back-top-flex; | |
| 124 | + height: $u-back-top-height; | |
| 125 | + justify-content: center; | |
| 126 | + background-color: $u-back-top-background-color; | |
| 127 | + | |
| 128 | + &__tips { | |
| 129 | + font-size:$u-back-top-tips-font-size; | |
| 130 | + transform: scale(0.8); | |
| 131 | + } | |
| 132 | + } | |
| 133 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-badge/badge.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-23 19:51:50 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/badge.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // 徽标数组件 | |
| 12 | + badge: { | |
| 13 | + isDot: false, | |
| 14 | + value: '', | |
| 15 | + show: true, | |
| 16 | + max: 999, | |
| 17 | + type: 'error', | |
| 18 | + showZero: false, | |
| 19 | + bgColor: null, | |
| 20 | + color: null, | |
| 21 | + shape: 'circle', | |
| 22 | + numberType: 'overflow', | |
| 23 | + offset: [], | |
| 24 | + inverted: false, | |
| 25 | + absolute: false | |
| 26 | + } | |
| 27 | +} | ... | ... |
uni_modules/uview-plus/components/u-badge/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 是否显示圆点 | |
| 6 | + isDot: { | |
| 7 | + type: Boolean, | |
| 8 | + default: () => defProps.badge.isDot | |
| 9 | + }, | |
| 10 | + // 显示的内容 | |
| 11 | + value: { | |
| 12 | + type: [Number, String], | |
| 13 | + default: () => defProps.badge.value | |
| 14 | + }, | |
| 15 | + // 显示的内容 | |
| 16 | + modelValue: { | |
| 17 | + type: [Number, String], | |
| 18 | + default: () => defProps.badge.modelValue | |
| 19 | + }, | |
| 20 | + // 是否显示 | |
| 21 | + show: { | |
| 22 | + type: Boolean, | |
| 23 | + default: () => defProps.badge.show | |
| 24 | + }, | |
| 25 | + // 最大值,超过最大值会显示 '{max}+' | |
| 26 | + max: { | |
| 27 | + type: [Number, String], | |
| 28 | + default: () => defProps.badge.max | |
| 29 | + }, | |
| 30 | + // 主题类型,error|warning|success|primary | |
| 31 | + type: { | |
| 32 | + type: String, | |
| 33 | + default: () => defProps.badge.type | |
| 34 | + }, | |
| 35 | + // 当数值为 0 时,是否展示 Badge | |
| 36 | + showZero: { | |
| 37 | + type: Boolean, | |
| 38 | + default: () => defProps.badge.showZero | |
| 39 | + }, | |
| 40 | + // 背景颜色,优先级比type高,如设置,type参数会失效 | |
| 41 | + bgColor: { | |
| 42 | + type: [String, null], | |
| 43 | + default: () => defProps.badge.bgColor | |
| 44 | + }, | |
| 45 | + // 字体颜色 | |
| 46 | + color: { | |
| 47 | + type: [String, null], | |
| 48 | + default: () => defProps.badge.color | |
| 49 | + }, | |
| 50 | + // 徽标形状,circle-四角均为圆角,horn-左下角为直角 | |
| 51 | + shape: { | |
| 52 | + type: String, | |
| 53 | + default: () => defProps.badge.shape | |
| 54 | + }, | |
| 55 | + // 设置数字的显示方式,overflow|ellipsis|limit | |
| 56 | + // overflow会根据max字段判断,超出显示`${max}+` | |
| 57 | + // ellipsis会根据max判断,超出显示`${max}...` | |
| 58 | + // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数 | |
| 59 | + numberType: { | |
| 60 | + type: String, | |
| 61 | + default: () => defProps.badge.numberType | |
| 62 | + }, | |
| 63 | + // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效 | |
| 64 | + offset: { | |
| 65 | + type: Array, | |
| 66 | + default: () => defProps.badge.offset | |
| 67 | + }, | |
| 68 | + // 是否反转背景和字体颜色 | |
| 69 | + inverted: { | |
| 70 | + type: Boolean, | |
| 71 | + default: () => defProps.badge.inverted | |
| 72 | + }, | |
| 73 | + // 是否绝对定位 | |
| 74 | + absolute: { | |
| 75 | + type: Boolean, | |
| 76 | + default: () => defProps.badge.absolute | |
| 77 | + } | |
| 78 | + } | |
| 79 | +}) | ... | ... |
uni_modules/uview-plus/components/u-badge/u-badge.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <text | |
| 3 | + v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)" | |
| 4 | + :class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]" | |
| 5 | + :style="[addStyle(customStyle), badgeStyle]" | |
| 6 | + class="u-badge" | |
| 7 | + >{{ isDot ? '' :showValue }}</text> | |
| 8 | +</template> | |
| 9 | + | |
| 10 | +<script> | |
| 11 | + import { props } from './props'; | |
| 12 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 13 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 14 | + import { addStyle, addUnit } from '../../libs/function/index'; | |
| 15 | + /** | |
| 16 | + * badge 徽标数 | |
| 17 | + * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。 | |
| 18 | + * @tutorial https://uview-plus.jiangruyi.com/components/badge.html | |
| 19 | + * | |
| 20 | + * @property {Boolean} isDot 是否显示圆点 (默认 false ) | |
| 21 | + * @property {String | Number} value 显示的内容 | |
| 22 | + * @property {Boolean} show 是否显示 (默认 true ) | |
| 23 | + * @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999) | |
| 24 | + * @property {String} type 主题类型,error|warning|success|primary (默认 'error' ) | |
| 25 | + * @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false ) | |
| 26 | + * @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效 | |
| 27 | + * @property {String} color 字体颜色 (默认 '#ffffff' ) | |
| 28 | + * @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' ) | |
| 29 | + * @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' ) | |
| 30 | + * @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效 | |
| 31 | + * @property {Boolean} inverted 是否反转背景和字体颜色(默认 false ) | |
| 32 | + * @property {Boolean} absolute 是否绝对定位(默认 false ) | |
| 33 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 34 | + * @example <u-badge :type="type" :count="count"></u-badge> | |
| 35 | + */ | |
| 36 | + export default { | |
| 37 | + name: 'u-badge', | |
| 38 | + mixins: [mpMixin, props, mixin], | |
| 39 | + computed: { | |
| 40 | + // 是否将badge中心与父组件右上角重合 | |
| 41 | + boxStyle() { | |
| 42 | + let style = {}; | |
| 43 | + return style; | |
| 44 | + }, | |
| 45 | + // 整个组件的样式 | |
| 46 | + badgeStyle() { | |
| 47 | + const style = {} | |
| 48 | + if(this.color) { | |
| 49 | + style.color = this.color | |
| 50 | + } | |
| 51 | + if (this.bgColor && !this.inverted) { | |
| 52 | + style.backgroundColor = this.bgColor | |
| 53 | + } | |
| 54 | + if (this.absolute) { | |
| 55 | + style.position = 'absolute' | |
| 56 | + // 如果有设置offset参数 | |
| 57 | + if(this.offset.length) { | |
| 58 | + // top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top | |
| 59 | + const top = this.offset[0] | |
| 60 | + const right = this.offset[1] || top | |
| 61 | + style.top = addUnit(top) | |
| 62 | + style.right = addUnit(right) | |
| 63 | + } | |
| 64 | + } | |
| 65 | + return style | |
| 66 | + }, | |
| 67 | + showValue() { | |
| 68 | + switch (this.numberType) { | |
| 69 | + case "overflow": | |
| 70 | + return Number(this.value) > Number(this.max) ? this.max + "+" : this.value | |
| 71 | + break; | |
| 72 | + case "ellipsis": | |
| 73 | + return Number(this.value) > Number(this.max) ? "..." : this.value | |
| 74 | + break; | |
| 75 | + case "limit": | |
| 76 | + return Number(this.value) > 999 ? Number(this.value) >= 9999 ? | |
| 77 | + Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value / | |
| 78 | + 1e3 * 100) / 100 + "k" : this.value | |
| 79 | + break; | |
| 80 | + default: | |
| 81 | + return Number(this.value) | |
| 82 | + } | |
| 83 | + }, | |
| 84 | + }, | |
| 85 | + methods: { | |
| 86 | + addStyle | |
| 87 | + } | |
| 88 | + } | |
| 89 | +</script> | |
| 90 | + | |
| 91 | +<style lang="scss" scoped> | |
| 92 | + @import "../../libs/css/components.scss"; | |
| 93 | + | |
| 94 | + $u-badge-primary: $u-primary !default; | |
| 95 | + $u-badge-error: $u-error !default; | |
| 96 | + $u-badge-success: $u-success !default; | |
| 97 | + $u-badge-info: $u-info !default; | |
| 98 | + $u-badge-warning: $u-warning !default; | |
| 99 | + $u-badge-dot-radius: 100px !default; | |
| 100 | + $u-badge-dot-size: 8px !default; | |
| 101 | + $u-badge-dot-right: 4px !default; | |
| 102 | + $u-badge-dot-top: 0 !default; | |
| 103 | + $u-badge-text-font-size: 11px !default; | |
| 104 | + $u-badge-text-right: 10px !default; | |
| 105 | + $u-badge-text-padding: 2px 5px !default; | |
| 106 | + $u-badge-text-align: center !default; | |
| 107 | + $u-badge-text-color: #FFFFFF !default; | |
| 108 | + | |
| 109 | + .u-badge { | |
| 110 | + border-top-right-radius: $u-badge-dot-radius; | |
| 111 | + border-top-left-radius: $u-badge-dot-radius; | |
| 112 | + border-bottom-left-radius: $u-badge-dot-radius; | |
| 113 | + border-bottom-right-radius: $u-badge-dot-radius; | |
| 114 | + @include flex; | |
| 115 | + line-height: $u-badge-text-font-size; | |
| 116 | + text-align: $u-badge-text-align; | |
| 117 | + font-size: $u-badge-text-font-size; | |
| 118 | + color: $u-badge-text-color; | |
| 119 | + | |
| 120 | + &--dot { | |
| 121 | + height: $u-badge-dot-size; | |
| 122 | + width: $u-badge-dot-size; | |
| 123 | + } | |
| 124 | + | |
| 125 | + &--inverted { | |
| 126 | + font-size: 13px; | |
| 127 | + } | |
| 128 | + | |
| 129 | + &--not-dot { | |
| 130 | + padding: $u-badge-text-padding; | |
| 131 | + } | |
| 132 | + | |
| 133 | + &--horn { | |
| 134 | + border-bottom-left-radius: 0; | |
| 135 | + } | |
| 136 | + | |
| 137 | + &--primary { | |
| 138 | + background-color: $u-badge-primary; | |
| 139 | + } | |
| 140 | + | |
| 141 | + &--primary--inverted { | |
| 142 | + color: $u-badge-primary; | |
| 143 | + } | |
| 144 | + | |
| 145 | + &--error { | |
| 146 | + background-color: $u-badge-error; | |
| 147 | + } | |
| 148 | + | |
| 149 | + &--error--inverted { | |
| 150 | + color: $u-badge-error; | |
| 151 | + } | |
| 152 | + | |
| 153 | + &--success { | |
| 154 | + background-color: $u-badge-success; | |
| 155 | + } | |
| 156 | + | |
| 157 | + &--success--inverted { | |
| 158 | + color: $u-badge-success; | |
| 159 | + } | |
| 160 | + | |
| 161 | + &--info { | |
| 162 | + background-color: $u-badge-info; | |
| 163 | + } | |
| 164 | + | |
| 165 | + &--info--inverted { | |
| 166 | + color: $u-badge-info; | |
| 167 | + } | |
| 168 | + | |
| 169 | + &--warning { | |
| 170 | + background-color: $u-badge-warning; | |
| 171 | + } | |
| 172 | + | |
| 173 | + &--warning--inverted { | |
| 174 | + color: $u-badge-warning; | |
| 175 | + } | |
| 176 | + } | |
| 177 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-barcode/u-barcode.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-barcode" v-if="calcSizeDone"> | |
| 3 | + <canvas | |
| 4 | + v-if="showCanvas && !error" | |
| 5 | + :id="canvasId" | |
| 6 | + :canvas-id="canvasId" | |
| 7 | + :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }" | |
| 8 | + ></canvas> | |
| 9 | + <image | |
| 10 | + v-else-if="!showCanvas && !error" | |
| 11 | + :src="barcodeImage" | |
| 12 | + :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }" | |
| 13 | + mode="aspectFit" | |
| 14 | + /> | |
| 15 | + <view | |
| 16 | + v-else | |
| 17 | + class="error-container" | |
| 18 | + :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }" | |
| 19 | + > | |
| 20 | + <text class="error-text">{{ error }}</text> | |
| 21 | + </view> | |
| 22 | + </view> | |
| 23 | +</template> | |
| 24 | + | |
| 25 | +<script> | |
| 26 | +import { nextTick } from 'vue' | |
| 27 | +import { t } from '../../libs/i18n' | |
| 28 | + | |
| 29 | +export default { | |
| 30 | + name: 'u-barcode', | |
| 31 | + props: { | |
| 32 | + // 条码值 | |
| 33 | + value: { | |
| 34 | + type: [String, Number], | |
| 35 | + required: true | |
| 36 | + }, | |
| 37 | + // 条码格式 | |
| 38 | + format: { | |
| 39 | + type: String, | |
| 40 | + default: 'auto', | |
| 41 | + validator: function (value) { | |
| 42 | + return [ | |
| 43 | + 'auto', | |
| 44 | + 'CODE128', 'CODE128A', 'CODE128B', 'CODE128C', | |
| 45 | + 'EAN13', 'EAN8', 'EAN5', 'EAN2', | |
| 46 | + 'UPC', 'UPCA', 'UPCE', | |
| 47 | + 'CODE39', | |
| 48 | + 'ITF', 'ITF14', | |
| 49 | + 'MSI', 'MSI10', 'MSI11', 'MSI1010', 'MSI1110', | |
| 50 | + 'pharmacode', | |
| 51 | + 'codabar' | |
| 52 | + ].includes(value) | |
| 53 | + } | |
| 54 | + }, | |
| 55 | + // 宽度 | |
| 56 | + width: { | |
| 57 | + type: Number, | |
| 58 | + default: 200 | |
| 59 | + }, | |
| 60 | + // 高度 | |
| 61 | + height: { | |
| 62 | + type: Number, | |
| 63 | + default: 80 | |
| 64 | + }, | |
| 65 | + // 是否显示文本 | |
| 66 | + displayValue: { | |
| 67 | + type: Boolean, | |
| 68 | + default: true | |
| 69 | + }, | |
| 70 | + // 文本内容 | |
| 71 | + text: { | |
| 72 | + type: String, | |
| 73 | + default: undefined | |
| 74 | + }, | |
| 75 | + // 字体选项 | |
| 76 | + fontOptions: { | |
| 77 | + type: String, | |
| 78 | + default: '' | |
| 79 | + }, | |
| 80 | + // 字体 | |
| 81 | + font: { | |
| 82 | + type: String, | |
| 83 | + default: 'monospace' | |
| 84 | + }, | |
| 85 | + // 文本对齐方式 | |
| 86 | + textAlign: { | |
| 87 | + type: String, | |
| 88 | + default: 'center' | |
| 89 | + }, | |
| 90 | + // 文本位置 | |
| 91 | + textPosition: { | |
| 92 | + type: String, | |
| 93 | + default: 'bottom' | |
| 94 | + }, | |
| 95 | + // 文本边距 | |
| 96 | + textMargin: { | |
| 97 | + type: Number, | |
| 98 | + default: 2 | |
| 99 | + }, | |
| 100 | + // 字体大小 | |
| 101 | + fontSize: { | |
| 102 | + type: Number, | |
| 103 | + default: 14 | |
| 104 | + }, | |
| 105 | + // 背景色 | |
| 106 | + background: { | |
| 107 | + type: String, | |
| 108 | + default: '#ffffff' | |
| 109 | + }, | |
| 110 | + // 条码颜色 | |
| 111 | + lineColor: { | |
| 112 | + type: String, | |
| 113 | + default: '#000000' | |
| 114 | + }, | |
| 115 | + // 边距 | |
| 116 | + margin: { | |
| 117 | + type: Number, | |
| 118 | + default: 10 | |
| 119 | + }, | |
| 120 | + // 上边距 | |
| 121 | + marginTop: { | |
| 122 | + type: Number, | |
| 123 | + default: undefined | |
| 124 | + }, | |
| 125 | + // 下边距 | |
| 126 | + marginBottom: { | |
| 127 | + type: Number, | |
| 128 | + default: undefined | |
| 129 | + }, | |
| 130 | + // 左边距 | |
| 131 | + marginLeft: { | |
| 132 | + type: Number, | |
| 133 | + default: undefined | |
| 134 | + }, | |
| 135 | + // 右边距 | |
| 136 | + marginRight: { | |
| 137 | + type: Number, | |
| 138 | + default: undefined | |
| 139 | + }, | |
| 140 | + // 使用canvas还是生成图片 | |
| 141 | + useCanvas: { | |
| 142 | + type: Boolean, | |
| 143 | + default: true | |
| 144 | + } | |
| 145 | + }, | |
| 146 | + data() { | |
| 147 | + return { | |
| 148 | + canvasId: 'barcode-' + Math.random().toString(36).substr(2, 9), | |
| 149 | + barcodeImage: '', | |
| 150 | + showCanvas: false, | |
| 151 | + canvasWidth: 0, | |
| 152 | + canvasHeight: 0, | |
| 153 | + calcSizeDone: false, | |
| 154 | + error: '' | |
| 155 | + } | |
| 156 | + }, | |
| 157 | + watch: { | |
| 158 | + value() { | |
| 159 | + this.generateBarcode() | |
| 160 | + }, | |
| 161 | + format() { | |
| 162 | + this.generateBarcode() | |
| 163 | + }, | |
| 164 | + width() { | |
| 165 | + this.generateBarcode() | |
| 166 | + }, | |
| 167 | + height() { | |
| 168 | + this.generateBarcode() | |
| 169 | + }, | |
| 170 | + displayValue() { | |
| 171 | + this.generateBarcode() | |
| 172 | + }, | |
| 173 | + text() { | |
| 174 | + this.generateBarcode() | |
| 175 | + }, | |
| 176 | + font() { | |
| 177 | + this.generateBarcode() | |
| 178 | + }, | |
| 179 | + textAlign() { | |
| 180 | + this.generateBarcode() | |
| 181 | + }, | |
| 182 | + textPosition() { | |
| 183 | + this.generateBarcode() | |
| 184 | + }, | |
| 185 | + textMargin() { | |
| 186 | + this.generateBarcode() | |
| 187 | + }, | |
| 188 | + fontSize() { | |
| 189 | + this.generateBarcode() | |
| 190 | + }, | |
| 191 | + background() { | |
| 192 | + this.generateBarcode() | |
| 193 | + }, | |
| 194 | + lineColor() { | |
| 195 | + this.generateBarcode() | |
| 196 | + }, | |
| 197 | + margin() { | |
| 198 | + this.generateBarcode() | |
| 199 | + } | |
| 200 | + }, | |
| 201 | + mounted() { | |
| 202 | + /** | |
| 203 | + * @author jry <ijry@qq.com> | |
| 204 | + */ | |
| 205 | + this.$nextTick(() => { | |
| 206 | + this.generateBarcode() | |
| 207 | + }) | |
| 208 | + }, | |
| 209 | + methods: { | |
| 210 | + /** | |
| 211 | + * 生成条形码 | |
| 212 | + * @author jry <ijry@qq.com> | |
| 213 | + * @param {String|Number} value - 条码值 | |
| 214 | + * @param {Object} options - 条码配置选项 | |
| 215 | + */ | |
| 216 | + generateBarcode() { | |
| 217 | + // 统一处理默认值 | |
| 218 | + const margin = this.margin | |
| 219 | + const options = { | |
| 220 | + format: this.format || 'auto', | |
| 221 | + width: this.width, | |
| 222 | + height: this.height, | |
| 223 | + displayValue: this.displayValue, | |
| 224 | + text: this.text, | |
| 225 | + fontOptions: this.fontOptions || '', | |
| 226 | + font: this.font || 'monospace', | |
| 227 | + textAlign: this.textAlign || 'center', | |
| 228 | + textPosition: this.textPosition || 'bottom', | |
| 229 | + textMargin: this.textMargin !== undefined ? this.textMargin : 2, | |
| 230 | + fontSize: this.fontSize || 20, | |
| 231 | + background: this.background || '#ffffff', | |
| 232 | + lineColor: this.lineColor || '#000000', | |
| 233 | + margin: margin, | |
| 234 | + marginTop: this.marginTop !== undefined ? this.marginTop : margin, | |
| 235 | + marginBottom: this.marginBottom !== undefined ? this.marginBottom : margin, | |
| 236 | + marginLeft: this.marginLeft !== undefined ? this.marginLeft : margin, | |
| 237 | + marginRight: this.marginRight !== undefined ? this.marginRight : margin | |
| 238 | + } | |
| 239 | + | |
| 240 | + // 清理未定义的选项 | |
| 241 | + Object.keys(options).forEach(key => { | |
| 242 | + if (options[key] === undefined) { | |
| 243 | + delete options[key] | |
| 244 | + } | |
| 245 | + }) | |
| 246 | + | |
| 247 | + if (this.useCanvas) { | |
| 248 | + // 使用canvas渲染 | |
| 249 | + this.showCanvas = true | |
| 250 | + this.$nextTick(() => { | |
| 251 | + this.renderToCanvas(options) | |
| 252 | + }) | |
| 253 | + } else { | |
| 254 | + // 生成图片 | |
| 255 | + this.showCanvas = false | |
| 256 | + this.renderToImage(options) | |
| 257 | + } | |
| 258 | + }, | |
| 259 | + | |
| 260 | + /** | |
| 261 | + * 渲染条形码到canvas | |
| 262 | + * @author jry <ijry@qq.com> | |
| 263 | + * @param {Object} options - 条码配置选项 | |
| 264 | + */ | |
| 265 | + async renderToCanvas(options) { | |
| 266 | + try { | |
| 267 | + // 计算canvas尺寸 | |
| 268 | + this.calculateCanvasSize(options) | |
| 269 | + | |
| 270 | + await nextTick() | |
| 271 | + | |
| 272 | + // 获取canvas上下文 | |
| 273 | + const ctx = uni.createCanvasContext(this.canvasId, this) | |
| 274 | + | |
| 275 | + // 清空画布 | |
| 276 | + ctx.setFillStyle(options.background) | |
| 277 | + ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight) | |
| 278 | + | |
| 279 | + // 生成条形码数据 | |
| 280 | + const barcodeData = this.encodeBarcode(this.value, options.format) | |
| 281 | + | |
| 282 | + if (barcodeData) { | |
| 283 | + // 绘制条形码 | |
| 284 | + this.drawBarcode(ctx, barcodeData, options) | |
| 285 | + } | |
| 286 | + | |
| 287 | + // 绘制到canvas | |
| 288 | + ctx.draw(false, () => { | |
| 289 | + // 绘制完成回调 | |
| 290 | + this.$emit('rendered', { type: 'canvas', id: this.canvasId }) | |
| 291 | + }) | |
| 292 | + } catch (error) { | |
| 293 | + console.error('生成条码失败:', error) | |
| 294 | + this.error = error.message || t('up.barcode.error') | |
| 295 | + this.$emit('error', error) | |
| 296 | + } | |
| 297 | + }, | |
| 298 | + | |
| 299 | + /** | |
| 300 | + * 渲染条形码为图片 | |
| 301 | + * @author jry <ijry@qq.com> | |
| 302 | + * @param {Object} options - 条码配置选项 | |
| 303 | + */ | |
| 304 | + renderToImage(options) { | |
| 305 | + try { | |
| 306 | + // 计算canvas尺寸 | |
| 307 | + this.calculateCanvasSize(options) | |
| 308 | + | |
| 309 | + // 创建临时canvas用于生成图片 | |
| 310 | + const tempCanvasId = 'temp-' + this.canvasId | |
| 311 | + const ctx = uni.createCanvasContext(tempCanvasId, this) | |
| 312 | + | |
| 313 | + // 清空画布 | |
| 314 | + ctx.setFillStyle(options.background) | |
| 315 | + ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight) | |
| 316 | + | |
| 317 | + // 生成条形码数据 | |
| 318 | + const barcodeData = this.encodeBarcode(this.value, options.format) | |
| 319 | + | |
| 320 | + if (barcodeData) { | |
| 321 | + // 绘制条形码 | |
| 322 | + this.drawBarcode(ctx, barcodeData, options) | |
| 323 | + } | |
| 324 | + | |
| 325 | + // 绘制到临时canvas并生成图片 | |
| 326 | + ctx.draw(false, () => { | |
| 327 | + // 延迟一小段时间确保canvas绘制完成 | |
| 328 | + setTimeout(() => { | |
| 329 | + // 将canvas转换为图片 | |
| 330 | + uni.canvasToTempFilePath({ | |
| 331 | + canvasId: tempCanvasId, | |
| 332 | + success: (res) => { | |
| 333 | + this.barcodeImage = res.tempFilePath | |
| 334 | + this.$emit('rendered', { type: 'image', value: this.value, path: res.tempFilePath }) | |
| 335 | + }, | |
| 336 | + fail: (error) => { | |
| 337 | + console.error('生成条码图片失败:', error) | |
| 338 | + this.$emit('error', error) | |
| 339 | + } | |
| 340 | + }, this) | |
| 341 | + }, 100) | |
| 342 | + }) | |
| 343 | + } catch (error) { | |
| 344 | + console.error('生成条码图片失败:', error) | |
| 345 | + this.$emit('error', error) | |
| 346 | + } | |
| 347 | + }, | |
| 348 | + | |
| 349 | + /** | |
| 350 | + * 计算canvas尺寸 | |
| 351 | + * @author jry <ijry@qq.com> | |
| 352 | + * @param {Object} options - 条码配置选项 | |
| 353 | + */ | |
| 354 | + calculateCanvasSize(options) { | |
| 355 | + // 基础宽度计算 | |
| 356 | + let width = options.width | |
| 357 | + let height = options.height | |
| 358 | + | |
| 359 | + // 考虑边距 | |
| 360 | + const marginLeft = options.marginLeft | |
| 361 | + const marginRight = options.marginRight | |
| 362 | + const marginTop = options.marginTop | |
| 363 | + const marginBottom = options.marginBottom | |
| 364 | + | |
| 365 | + // 考虑文本高度 | |
| 366 | + let textHeight = 0 | |
| 367 | + if (options.displayValue !== false) { | |
| 368 | + textHeight = options.fontSize + options.textMargin | |
| 369 | + } | |
| 370 | + | |
| 371 | + // 根据文本位置调整高度 | |
| 372 | + if (options.textPosition === 'top' || options.textPosition === 'bottom') { | |
| 373 | + height += textHeight | |
| 374 | + } | |
| 375 | + | |
| 376 | + // 添加边距 | |
| 377 | + width += marginLeft + marginRight | |
| 378 | + height += marginTop + marginBottom | |
| 379 | + | |
| 380 | + this.canvasWidth = Math.max(width, 100) | |
| 381 | + this.canvasHeight = Math.max(height, 60 + textHeight) | |
| 382 | + | |
| 383 | + this.calcSizeDone = true | |
| 384 | + }, | |
| 385 | + | |
| 386 | + /** | |
| 387 | + * 编码条形码数据 | |
| 388 | + * @author jry <ijry@qq.com> | |
| 389 | + * @param {String|Number} value - 条码值 | |
| 390 | + * @param {String} format - 条码格式 | |
| 391 | + * @returns {String|null} 条形码编码数据 | |
| 392 | + */ | |
| 393 | + encodeBarcode(value, format) { | |
| 394 | + try { | |
| 395 | + switch (format) { | |
| 396 | + case 'CODE128': | |
| 397 | + case 'auto': | |
| 398 | + return this.encodeCode128(value) | |
| 399 | + case 'CODE39': | |
| 400 | + return this.encodeCode39(value) | |
| 401 | + case 'EAN13': | |
| 402 | + return this.encodeEAN13(value) | |
| 403 | + case 'EAN8': | |
| 404 | + return this.encodeEAN8(value) | |
| 405 | + case 'EAN5': | |
| 406 | + case 'EAN2': | |
| 407 | + return this.encodeEAN52(value, format) | |
| 408 | + case 'UPC': | |
| 409 | + case 'UPCA': | |
| 410 | + return this.encodeUPCA(value) | |
| 411 | + case 'UPCE': | |
| 412 | + return this.encodeUPCE(value) | |
| 413 | + default: | |
| 414 | + // 默认使用CODE128 | |
| 415 | + return this.encodeCode128(value) | |
| 416 | + } | |
| 417 | + } catch (error) { | |
| 418 | + console.error('条码编码失败:', error) | |
| 419 | + throw error | |
| 420 | + } | |
| 421 | + }, | |
| 422 | + | |
| 423 | + /** | |
| 424 | + * 添加右侧安静区(至少2个模块宽度的空白) | |
| 425 | + * @author jry <ijry@qq.com> | |
| 426 | + * @param {String} data - 要编码的数据 | |
| 427 | + * @returns {String|null} 编码后的条形码数据 | |
| 428 | + */ | |
| 429 | + encodeCode128(data) { | |
| 430 | + const CODE128_START_CODE_B = 104 | |
| 431 | + const CODE128_STOP = 106 | |
| 432 | + | |
| 433 | + // CODE128 Code B 字符集 | |
| 434 | + const CODE128_CODE_B_CHARS = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' | |
| 435 | + | |
| 436 | + // 条形码模式 (B 模式) | |
| 437 | + const codes = [] | |
| 438 | + let checksum = CODE128_START_CODE_B | |
| 439 | + | |
| 440 | + // 添加起始字符 | |
| 441 | + codes.push(CODE128_START_CODE_B) | |
| 442 | + | |
| 443 | + // 编码每个字符 | |
| 444 | + for (let i = 0; i < data.length; i++) { | |
| 445 | + const char = data[i] | |
| 446 | + const code = CODE128_CODE_B_CHARS.indexOf(char) | |
| 447 | + | |
| 448 | + if (code === -1) { | |
| 449 | + throw new Error('Invalid character in CODE128: ' + char) | |
| 450 | + } | |
| 451 | + | |
| 452 | + codes.push(code) | |
| 453 | + checksum += code * (i + 1) | |
| 454 | + } | |
| 455 | + | |
| 456 | + // 添加校验字符 | |
| 457 | + codes.push(checksum % 103) | |
| 458 | + | |
| 459 | + // 添加结束字符 | |
| 460 | + codes.push(CODE128_STOP) | |
| 461 | + | |
| 462 | + // 转换为条形码模式 (1 = 黑条, 0 = 白条) | |
| 463 | + let barcode = '' | |
| 464 | + for (let i = 0; i < codes.length; i++) { | |
| 465 | + const code = codes[i] | |
| 466 | + barcode += this.getCode128Pattern(code) | |
| 467 | + } | |
| 468 | + | |
| 469 | + // 添加右侧安静区(至少2个模块宽度的空白) | |
| 470 | + barcode += '00000' | |
| 471 | + | |
| 472 | + return barcode | |
| 473 | + }, | |
| 474 | + | |
| 475 | + /** | |
| 476 | + * 获取CODE128编码模式 | |
| 477 | + * @author jry <ijry@qq.com> | |
| 478 | + * @param {Number} code - 字符编码 | |
| 479 | + * @returns {String} 条形码二进制模式 | |
| 480 | + */ | |
| 481 | + getCode128Pattern(code) { | |
| 482 | + // CODE128编码表 | |
| 483 | + const patterns = [ | |
| 484 | + "11011001100", "11001101100", "11001100110", "10010011000", | |
| 485 | + "10010001100", "10001001100", "10011001000", "10011000100", | |
| 486 | + "10001100100", "11001001000", "11001000100", "11000100100", | |
| 487 | + "10110011100", "10011011100", "10011001110", "10111001100", | |
| 488 | + "10011101100", "10011100110", "11001110010", "11001011100", | |
| 489 | + "11001001110", "11011100100", "11001110100", "11101101110", | |
| 490 | + "11101001100", "11100101100", "11100100110", "11101100100", | |
| 491 | + "11100110100", "11100110010", "11011011000", "11011000110", | |
| 492 | + "11000110110", "10100011000", "10001011000", "10001000110", | |
| 493 | + "10110001000", "10001101000", "10001100010", "11010001000", | |
| 494 | + "11000101000", "11000100010", "10110111000", "10110001110", | |
| 495 | + "10001101110", "10111011000", "10111000110", "10001110110", | |
| 496 | + "11101110110", "11010001110", "11000101110", "11011101000", | |
| 497 | + "11011100010", "11011101110", "11101011000", "11101000110", | |
| 498 | + "11100010110", "11101101000", "11101100010", "11100011010", | |
| 499 | + "11101111010", "11001000010", "11110001010", "10100110000", | |
| 500 | + "10100001100", "10010110000", "10010000110", "10000101100", | |
| 501 | + "10000100110", "10110010000", "10110000100", "10011010000", | |
| 502 | + "10011000010", "10000110100", "10000110010", "11000010010", | |
| 503 | + "11001010000", "11110111010", "11000010100", "10001111010", | |
| 504 | + "10100111100", "10010111100", "10010011110", "10111100100", | |
| 505 | + "10011110100", "10011110010", "11110100100", "11110010100", | |
| 506 | + "11110010010", "11011011110", "11011110110", "11110110110", | |
| 507 | + "10101111000", "10100011110", "10001011110", "10111101000", | |
| 508 | + "10111100010", "11110101000", "11110100010", "10111011110", | |
| 509 | + "10111101110", "11101011110", "11110101110", "11010000100", | |
| 510 | + "11010010000", "11010011100", "11000111010" | |
| 511 | + ]; | |
| 512 | + | |
| 513 | + return patterns[code] || ""; | |
| 514 | + }, | |
| 515 | + | |
| 516 | + /** | |
| 517 | + * CODE39编码实现 | |
| 518 | + * @author jry <ijry@qq.com> | |
| 519 | + * @param {String} data - 要编码的数据 | |
| 520 | + * @returns {String|null} 编码后的条形码数据 | |
| 521 | + */ | |
| 522 | + encodeCode39(data) { | |
| 523 | + const codes = { | |
| 524 | + '0': '101000111011101', | |
| 525 | + '1': '111010001010111', | |
| 526 | + '2': '101110001010111', | |
| 527 | + '3': '111011100010101', | |
| 528 | + '4': '101000111010111', | |
| 529 | + '5': '111010001110101', | |
| 530 | + '6': '101110001110101', | |
| 531 | + '7': '101000101110111', | |
| 532 | + '8': '111010001011101', | |
| 533 | + '9': '101110001011101', | |
| 534 | + 'A': '111010100010111', | |
| 535 | + 'B': '101110100010111', | |
| 536 | + 'C': '111011101000101', | |
| 537 | + 'D': '101011100010111', | |
| 538 | + 'E': '111010111000101', | |
| 539 | + 'F': '101110111000101', | |
| 540 | + 'G': '101010001110111', | |
| 541 | + 'H': '111010100011101', | |
| 542 | + 'I': '101110100011101', | |
| 543 | + 'J': '101011100011101', | |
| 544 | + 'K': '111010101000111', | |
| 545 | + 'L': '101110101000111', | |
| 546 | + 'M': '111011101010001', | |
| 547 | + 'N': '101011101000111', | |
| 548 | + 'O': '111010111010001', | |
| 549 | + 'P': '101110111010001', | |
| 550 | + 'Q': '101010111000111', | |
| 551 | + 'R': '111010101110001', | |
| 552 | + 'S': '101110101110001', | |
| 553 | + 'T': '101011101110001', | |
| 554 | + 'U': '111000101010111', | |
| 555 | + 'V': '100011101010111', | |
| 556 | + 'W': '111000111010101', | |
| 557 | + 'X': '100010111010111', | |
| 558 | + 'Y': '111000101110101', | |
| 559 | + 'Z': '100011101110101', | |
| 560 | + '-': '100010101110111', | |
| 561 | + '.': '111000101011101', | |
| 562 | + ' ': '100011101011101', | |
| 563 | + '*': '100010111011101', // 起始和终止字符 | |
| 564 | + '$': '100010001000101', | |
| 565 | + '/': '100010001010001', | |
| 566 | + '+': '100010100010001', | |
| 567 | + '%': '101000100010001' | |
| 568 | + } | |
| 569 | + | |
| 570 | + // 转为大写 | |
| 571 | + data = data.toUpperCase() | |
| 572 | + | |
| 573 | + // 添加起始和终止字符 | |
| 574 | + let barcode = codes['*'] | |
| 575 | + | |
| 576 | + // 添加数据字符 | |
| 577 | + for (let i = 0; i < data.length; i++) { | |
| 578 | + const char = data[i] | |
| 579 | + if (codes[char]) { | |
| 580 | + barcode += '0' // 字符间隔 | |
| 581 | + barcode += codes[char] | |
| 582 | + } else { | |
| 583 | + throw new Error('Invalid character in CODE39: ' + char) | |
| 584 | + } | |
| 585 | + } | |
| 586 | + | |
| 587 | + // 添加终止字符 | |
| 588 | + barcode += '0' // 字符间隔 | |
| 589 | + barcode += codes['*'] | |
| 590 | + | |
| 591 | + return barcode | |
| 592 | + }, | |
| 593 | + | |
| 594 | + /** | |
| 595 | + * EAN13编码实现 | |
| 596 | + * @author jry <ijry@qq.com> | |
| 597 | + * @param {String} data - 13位数字字符串 | |
| 598 | + * @returns {String|null} 编码后的条形码数据 | |
| 599 | + */ | |
| 600 | + encodeEAN13(data) { | |
| 601 | + // 确保数据是13位数字 | |
| 602 | + if (!/^\d{13}$/.test(data)) { | |
| 603 | + throw new Error('EAN13 must be 13 digits') | |
| 604 | + } | |
| 605 | + | |
| 606 | + // 验证校验位 | |
| 607 | + let sum = 0 | |
| 608 | + for (let i = 0; i < 12; i++) { | |
| 609 | + const digit = parseInt(data[i]) | |
| 610 | + sum += (i % 2 === 0) ? digit : digit * 3 | |
| 611 | + } | |
| 612 | + const checkDigit = (10 - (sum % 10)) % 10 | |
| 613 | + if (parseInt(data[12]) !== checkDigit) { | |
| 614 | + throw new Error('Invalid EAN13 check digit') | |
| 615 | + } | |
| 616 | + | |
| 617 | + // 左侧数据 | |
| 618 | + const leftData = data.substring(1, 7) | |
| 619 | + const rightData = data.substring(7, 13) | |
| 620 | + | |
| 621 | + // 起始符 | |
| 622 | + let barcode = '101' | |
| 623 | + | |
| 624 | + // 左侧数据编码 (根据第一位数字决定编码方式) | |
| 625 | + const firstDigit = parseInt(data[0]) | |
| 626 | + const leftPatterns = [ | |
| 627 | + ['LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', | |
| 628 | + 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL'] | |
| 629 | + ] | |
| 630 | + | |
| 631 | + const pattern = leftPatterns[0][firstDigit] | |
| 632 | + | |
| 633 | + // 左侧奇偶编码模式 | |
| 634 | + const leftOdd = [ | |
| 635 | + '0001101', '0011001', '0010011', '0111101', '0100011', | |
| 636 | + '0110001', '0101111', '0111011', '0110111', '0001011' | |
| 637 | + ] | |
| 638 | + | |
| 639 | + const leftEven = [ | |
| 640 | + '0100111', '0110011', '0011011', '0100001', '0011101', | |
| 641 | + '0111001', '0000101', '0010001', '0001001', '0010111' | |
| 642 | + ] | |
| 643 | + | |
| 644 | + // 编码左侧数据 | |
| 645 | + for (let i = 0; i < leftData.length; i++) { | |
| 646 | + const digit = parseInt(leftData[i]) | |
| 647 | + if (pattern[i] === 'L') { | |
| 648 | + barcode += leftOdd[digit] | |
| 649 | + } else { | |
| 650 | + barcode += leftEven[digit] | |
| 651 | + } | |
| 652 | + } | |
| 653 | + | |
| 654 | + // 中间分隔符 | |
| 655 | + barcode += '01010' | |
| 656 | + | |
| 657 | + // 右侧数据编码 (始终使用右编码) | |
| 658 | + const rightCodes = [ | |
| 659 | + '1110010', '1100110', '1101100', '1000010', '1011100', | |
| 660 | + '1001110', '1010000', '1000100', '1001000', '1110100' | |
| 661 | + ] | |
| 662 | + | |
| 663 | + for (let i = 0; i < rightData.length; i++) { | |
| 664 | + const digit = parseInt(rightData[i]) | |
| 665 | + barcode += rightCodes[digit] | |
| 666 | + } | |
| 667 | + | |
| 668 | + // 结束符 | |
| 669 | + barcode += '101' | |
| 670 | + | |
| 671 | + return barcode | |
| 672 | + }, | |
| 673 | + | |
| 674 | + /** | |
| 675 | + * EAN8编码实现 | |
| 676 | + * @author jry <ijry@qq.com> | |
| 677 | + * @param {String} data - 8位数字字符串 | |
| 678 | + * @returns {String|null} 编码后的条形码数据 | |
| 679 | + */ | |
| 680 | + encodeEAN8(data) { | |
| 681 | + // 确保数据是8位数字 | |
| 682 | + if (!/^\d{8}$/.test(data)) { | |
| 683 | + throw new Error('EAN8 must be 8 digits') | |
| 684 | + } | |
| 685 | + | |
| 686 | + // 验证校验位 | |
| 687 | + let sum = 0 | |
| 688 | + for (let i = 0; i < 7; i++) { | |
| 689 | + const digit = parseInt(data[i]) | |
| 690 | + sum += digit * (i % 2 === 0 ? 3 : 1) | |
| 691 | + } | |
| 692 | + const checkDigit = (10 - (sum % 10)) % 10 | |
| 693 | + if (parseInt(data[7]) !== checkDigit) { | |
| 694 | + throw new Error('Invalid EAN8 check digit') | |
| 695 | + } | |
| 696 | + | |
| 697 | + // 左侧数据(4位) | |
| 698 | + const leftData = data.substring(0, 4) | |
| 699 | + // 右侧数据(4位) | |
| 700 | + const rightData = data.substring(4, 8) | |
| 701 | + | |
| 702 | + // 起始符 | |
| 703 | + let barcode = '101' | |
| 704 | + | |
| 705 | + // 左侧奇偶编码模式 | |
| 706 | + const leftOdd = [ | |
| 707 | + '0001101', '0011001', '0010011', '0111101', '0100011', | |
| 708 | + '0110001', '0101111', '0111011', '0110111', '0001011' | |
| 709 | + ] | |
| 710 | + | |
| 711 | + // 编码左侧数据 | |
| 712 | + for (let i = 0; i < leftData.length; i++) { | |
| 713 | + const digit = parseInt(leftData[i]) | |
| 714 | + barcode += leftOdd[digit] | |
| 715 | + } | |
| 716 | + | |
| 717 | + // 中间分隔符 | |
| 718 | + barcode += '01010' | |
| 719 | + | |
| 720 | + // 右侧数据编码 (始终使用右编码) | |
| 721 | + const rightCodes = [ | |
| 722 | + '1110010', '1100110', '1101100', '1000010', '1011100', | |
| 723 | + '1001110', '1010000', '1000100', '1001000', '1110100' | |
| 724 | + ] | |
| 725 | + | |
| 726 | + // 编码右侧数据 | |
| 727 | + for (let i = 0; i < rightData.length; i++) { | |
| 728 | + const digit = parseInt(rightData[i]) | |
| 729 | + barcode += rightCodes[digit] | |
| 730 | + } | |
| 731 | + | |
| 732 | + // 结束符 | |
| 733 | + barcode += '101' | |
| 734 | + | |
| 735 | + return barcode | |
| 736 | + }, | |
| 737 | + | |
| 738 | + /** | |
| 739 | + * EAN5/EAN2编码实现 | |
| 740 | + * @author jry <ijry@qq.com> | |
| 741 | + * @param {String} data - 2位或5位数字字符串 | |
| 742 | + * @param {String} format - 格式类型(EAN5或EAN2) | |
| 743 | + * @returns {String|null} 编码后的条形码数据 | |
| 744 | + */ | |
| 745 | + encodeEAN52(data, format) { | |
| 746 | + const length = format === 'EAN5' ? 5 : 2 | |
| 747 | + // 确保数据是相应位数的数字 | |
| 748 | + if (!new RegExp(`^\\d{${length}}$`).test(data)) { | |
| 749 | + throw new Error(`${format} must be ${length} digits`) | |
| 750 | + } | |
| 751 | + | |
| 752 | + // EAN5/2编码表 | |
| 753 | + const codes = [ | |
| 754 | + '0001101', '0011001', '0010011', '0111101', '0100011', | |
| 755 | + '0110001', '0101111', '0111011', '0110111', '0001011' | |
| 756 | + ] | |
| 757 | + | |
| 758 | + // 计算校验和 | |
| 759 | + let checksum = 0 | |
| 760 | + for (let i = 0; i < data.length; i++) { | |
| 761 | + checksum += parseInt(data[i]) * (i % 2 === 0 ? 3 : 1) | |
| 762 | + } | |
| 763 | + | |
| 764 | + // 根据校验和确定编码模式 | |
| 765 | + const patterns = format === 'EAN5' ? | |
| 766 | + ['00001', '00010', '00100', '01000', '10000', // 0-4 | |
| 767 | + '00000', '00011', '00101', '00110', '01001', // 5-9 | |
| 768 | + '01010', '01100', '10001', '10010', '10100', // 10-14 | |
| 769 | + '11000', '11001', '11010', '11100'] : // 15-18 | |
| 770 | + ['00', '01', '10', '11'] // EAN2只有4种模式 | |
| 771 | + | |
| 772 | + const pattern = format === 'EAN5' ? | |
| 773 | + patterns[checksum % 10] : | |
| 774 | + patterns[parseInt(data) % 4] | |
| 775 | + | |
| 776 | + // 起始符 | |
| 777 | + let barcode = '1011' | |
| 778 | + | |
| 779 | + // 编码数据 | |
| 780 | + for (let i = 0; i < data.length; i++) { | |
| 781 | + // 添加分隔符(除了第一个字符) | |
| 782 | + if (i > 0) { | |
| 783 | + barcode += '01' // 字符间分隔符 | |
| 784 | + } | |
| 785 | + | |
| 786 | + // 根据模式确定编码方式 | |
| 787 | + const digit = parseInt(data[i]) | |
| 788 | + const code = codes[digit] | |
| 789 | + | |
| 790 | + barcode += code | |
| 791 | + } | |
| 792 | + | |
| 793 | + return barcode | |
| 794 | + }, | |
| 795 | + | |
| 796 | + /** | |
| 797 | + * UPCA编码实现 | |
| 798 | + * @author jry <ijry@qq.com> | |
| 799 | + * @param {String} data - 11位或12位数字字符串 | |
| 800 | + * @returns {String|null} 编码后的条形码数据 | |
| 801 | + */ | |
| 802 | + encodeUPCA(data) { | |
| 803 | + // 如果是11位,计算校验位 | |
| 804 | + if (/^\d{11}$/.test(data)) { | |
| 805 | + let sum = 0 | |
| 806 | + for (let i = 0; i < 11; i++) { | |
| 807 | + const digit = parseInt(data[i]) | |
| 808 | + sum += (i % 2 === 0) ? digit * 3 : digit | |
| 809 | + } | |
| 810 | + const checkDigit = (10 - (sum % 10)) % 10 | |
| 811 | + data += checkDigit | |
| 812 | + } | |
| 813 | + | |
| 814 | + // 确保数据是12位数字 | |
| 815 | + if (!/^\d{12}$/.test(data)) { | |
| 816 | + throw new Error('UPC-A must be 11 or 12 digits') | |
| 817 | + } | |
| 818 | + | |
| 819 | + // UPCA实际上是EAN13的第一个数字为0的特殊情况 | |
| 820 | + return this.encodeEAN13('0' + data) | |
| 821 | + }, | |
| 822 | + | |
| 823 | + /** | |
| 824 | + * UPCE编码实现 | |
| 825 | + * @author jry <ijry@qq.com> | |
| 826 | + * @param {String} data - 6位或8位数字字符串 | |
| 827 | + * @returns {String|null} 编码后的条形码数据 | |
| 828 | + */ | |
| 829 | + encodeUPCE(data) { | |
| 830 | + // 如果是7位,计算校验位 | |
| 831 | + if (/^\d{7}$/.test(data)) { | |
| 832 | + let sum = 0 | |
| 833 | + for (let i = 0; i < 7; i++) { | |
| 834 | + const digit = parseInt(data[i]) | |
| 835 | + sum += (i % 2 === 0) ? digit * 3 : digit | |
| 836 | + } | |
| 837 | + const checkDigit = (10 - (sum % 10)) % 10 | |
| 838 | + data += checkDigit | |
| 839 | + } | |
| 840 | + | |
| 841 | + // 确保数据是8位数字 | |
| 842 | + if (!/^\d{8}$/.test(data)) { | |
| 843 | + throw new Error('UPC-E must be 7 or 8 digits') | |
| 844 | + } | |
| 845 | + | |
| 846 | + // 检查是否是有效的UPC-E格式 | |
| 847 | + if (data[0] !== '0' && data[0] !== '1') { | |
| 848 | + throw new Error('UPC-E must start with 0 or 1') | |
| 849 | + } | |
| 850 | + | |
| 851 | + // UPCE编码表 | |
| 852 | + const leftOdd = [ | |
| 853 | + '0001101', '0011001', '0010011', '0111101', '0100011', | |
| 854 | + '0110001', '0101111', '0111011', '0110111', '0001011' | |
| 855 | + ] | |
| 856 | + | |
| 857 | + const leftEven = [ | |
| 858 | + '0100111', '0110011', '0011011', '0100001', '0011101', | |
| 859 | + '0111001', '0000101', '0010001', '0001001', '0010111' | |
| 860 | + ] | |
| 861 | + | |
| 862 | + // 根据系统数字确定编码模式 | |
| 863 | + const systemDigit = data[0] | |
| 864 | + const checkDigit = data[7] | |
| 865 | + | |
| 866 | + // 提取中间6位 | |
| 867 | + const middleData = data.substring(1, 7) | |
| 868 | + | |
| 869 | + // 确定编码模式 | |
| 870 | + let pattern | |
| 871 | + if (checkDigit === '0' || checkDigit === '1' || checkDigit === '2') { | |
| 872 | + pattern = 'EEEEOO' // 0,1,2 | |
| 873 | + } else if (checkDigit === '3') { | |
| 874 | + pattern = 'EEEEOO' // 3 | |
| 875 | + } else if (checkDigit === '4') { | |
| 876 | + pattern = 'EEEOOO' // 4 | |
| 877 | + } else { | |
| 878 | + pattern = 'EEOOOO' // 5,6,7,8,9 | |
| 879 | + } | |
| 880 | + | |
| 881 | + // 起始符 | |
| 882 | + let barcode = '101' | |
| 883 | + | |
| 884 | + // 编码中间6位数据 | |
| 885 | + for (let i = 0; i < middleData.length; i++) { | |
| 886 | + const digit = parseInt(middleData[i]) | |
| 887 | + if (pattern[i] === 'E') { | |
| 888 | + barcode += leftEven[digit] | |
| 889 | + } else { | |
| 890 | + barcode += leftOdd[digit] | |
| 891 | + } | |
| 892 | + } | |
| 893 | + | |
| 894 | + // 中间分隔符 | |
| 895 | + barcode += '010101' | |
| 896 | + | |
| 897 | + // 结束符 | |
| 898 | + barcode += '101' | |
| 899 | + | |
| 900 | + return barcode | |
| 901 | + }, | |
| 902 | + | |
| 903 | + /** | |
| 904 | + * 绘制条形码 | |
| 905 | + * @author jry <ijry@qq.com> | |
| 906 | + * @param {Object} ctx - canvas上下文 | |
| 907 | + * @param {String} barcodeData - 条形码数据 | |
| 908 | + * @param {Object} options - 条码配置选项 | |
| 909 | + */ | |
| 910 | + drawBarcode(ctx, barcodeData, options) { | |
| 911 | + if (!barcodeData) return | |
| 912 | + | |
| 913 | + const marginLeft = options.marginLeft | |
| 914 | + const marginTop = options.marginTop | |
| 915 | + const marginBottom = options.marginBottom | |
| 916 | + const textHeight = options.displayValue !== false ? options.fontSize + options.textMargin : 0 | |
| 917 | + const height = options.height | |
| 918 | + // 恢复: 根据总长度计算模块宽度 | |
| 919 | + const moduleWidth = Math.max(1, (this.canvasWidth - marginLeft - (options.marginRight || 10)) / barcodeData.length) | |
| 920 | + | |
| 921 | + ctx.setFillStyle(options.lineColor) | |
| 922 | + | |
| 923 | + // 计算条形码绘制的Y坐标 | |
| 924 | + let barcodeY = marginTop | |
| 925 | + // 如果文本在顶部,需要调整条形码绘制位置 | |
| 926 | + if (options.displayValue !== false && options.textPosition === 'top') { | |
| 927 | + barcodeY += textHeight | |
| 928 | + } | |
| 929 | + | |
| 930 | + // 恢复: 使用计算出的模块宽度绘制条形码 | |
| 931 | + let x = marginLeft | |
| 932 | + for (let i = 0; i < barcodeData.length; i++) { | |
| 933 | + if (barcodeData[i] === '1') { | |
| 934 | + // 使用计算的模块宽度绘制每个条 | |
| 935 | + ctx.fillRect(x, barcodeY, moduleWidth, height) | |
| 936 | + } | |
| 937 | + // 每个条/空都占用一个模块宽度 | |
| 938 | + x += moduleWidth | |
| 939 | + } | |
| 940 | + | |
| 941 | + // 绘制文本 | |
| 942 | + if (options.displayValue !== false) { | |
| 943 | + const text = options.text || this.value | |
| 944 | + let textY | |
| 945 | + | |
| 946 | + ctx.setFillStyle(options.lineColor) | |
| 947 | + ctx.setFontSize(options.fontSize) | |
| 948 | + ctx.setTextAlign(options.textAlign) | |
| 949 | + | |
| 950 | + let textX | |
| 951 | + switch (options.textAlign) { | |
| 952 | + case 'left': | |
| 953 | + textX = marginLeft | |
| 954 | + break | |
| 955 | + case 'right': | |
| 956 | + textX = this.canvasWidth - options.marginRight | |
| 957 | + break | |
| 958 | + default: // center | |
| 959 | + textX = this.canvasWidth / 2 | |
| 960 | + } | |
| 961 | + | |
| 962 | + // 根据文本位置确定Y坐标 | |
| 963 | + if (options.textPosition === 'top') { | |
| 964 | + textY = marginTop + options.fontSize - 3 | |
| 965 | + } else { // bottom | |
| 966 | + // 修复:正确计算底部文本位置,确保文本完全显示 | |
| 967 | + textY = barcodeY + height + options.textMargin + options.fontSize | |
| 968 | + // 确保文本不会超出画布边界 | |
| 969 | + if (textY > this.canvasHeight - marginBottom) { | |
| 970 | + textY = this.canvasHeight - marginBottom - 2 | |
| 971 | + } | |
| 972 | + } | |
| 973 | + | |
| 974 | + ctx.fillText(text, textX, textY) | |
| 975 | + } | |
| 976 | + } | |
| 977 | + } | |
| 978 | +} | |
| 979 | +</script> | |
| 980 | + | |
| 981 | +<style scoped> | |
| 982 | +.u-barcode { | |
| 983 | + display: flex; | |
| 984 | + flex-direction: row; | |
| 985 | + justify-content: center; | |
| 986 | + align-items: center; | |
| 987 | +} | |
| 988 | + | |
| 989 | +.error-container { | |
| 990 | + display: flex; | |
| 991 | + justify-content: center; | |
| 992 | + align-items: center; | |
| 993 | + background-color: #f0f0f0; | |
| 994 | + color: #ff0000; | |
| 995 | +} | |
| 996 | + | |
| 997 | +.error-text { | |
| 998 | + font-size: 14px; | |
| 999 | +} | |
| 1000 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-box/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | + | |
| 4 | +export const propsBox = defineMixin({ | |
| 5 | + props: { | |
| 6 | + // 背景色 | |
| 7 | + bgColors: { | |
| 8 | + type: [Array], | |
| 9 | + default: ['#EEFCFF', '#FCF8FF', '#FDF8F2'] | |
| 10 | + }, | |
| 11 | + // 高度 | |
| 12 | + height: { | |
| 13 | + type: [String], | |
| 14 | + default: "160px" | |
| 15 | + }, | |
| 16 | + // 圆角 | |
| 17 | + borderRadius: { | |
| 18 | + type: [String], | |
| 19 | + default: "6px" | |
| 20 | + }, | |
| 21 | + // 间隔 | |
| 22 | + gap: { | |
| 23 | + type: [String], | |
| 24 | + default: "15px" | |
| 25 | + }, | |
| 26 | + } | |
| 27 | +}) | ... | ... |
uni_modules/uview-plus/components/u-box/u-box.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-box" :style="[{height: height}, addStyle(customStyle)]"> | |
| 3 | + <view class="u-box__left" :style="{borderRadius: borderRadius, backgroundColor: bgColors[0]}"> | |
| 4 | + <slot name="left">左</slot> | |
| 5 | + </view> | |
| 6 | + <view class="u-box__gap" :style="{width: gap, height: height}"></view> | |
| 7 | + <view class="u-box__right"> | |
| 8 | + <view class="u-box__right-top" :style="{borderRadius: borderRadius, backgroundColor: bgColors[1]}"> | |
| 9 | + <slot name="rightTop">右上</slot> | |
| 10 | + </view> | |
| 11 | + <view class="u-box__right-gap" :style="{height: gap}"></view> | |
| 12 | + <view class="u-box__right-bottom" :style="{borderRadius: borderRadius, backgroundColor: bgColors[2]}"> | |
| 13 | + <slot name="rightBottom">右下</slot> | |
| 14 | + </view> | |
| 15 | + </view> | |
| 16 | + </view> | |
| 17 | +</template> | |
| 18 | + | |
| 19 | +<script> | |
| 20 | + import { propsBox } from './props'; | |
| 21 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 22 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 23 | + import { addStyle } from '../../libs/function/index'; | |
| 24 | + import test from '../../libs/function/test'; | |
| 25 | + /** | |
| 26 | + * box 盒子 | |
| 27 | + * @description box盒子一般为左边一个盒子,右侧两个等高的半盒组成,常用于App首页座位重点突出。 | |
| 28 | + * @tutorial https://uview-plus.jiangruyi.com/components/box.html | |
| 29 | + * @property {Array} bgColors 背景色 | |
| 30 | + * @property {String} height 高度 | |
| 31 | + * @property {String} borderRadius 圆角 | |
| 32 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 33 | + * | |
| 34 | + * @event {Function} click 点击cell列表时触发 | |
| 35 | + * @example <up-box colors=['blue', 'red', 'yellow'] height="200px"></up-box> | |
| 36 | + */ | |
| 37 | + export default { | |
| 38 | + name: 'up-box', | |
| 39 | + data() { | |
| 40 | + return { | |
| 41 | + } | |
| 42 | + }, | |
| 43 | + mixins: [mpMixin, mixin, propsBox], | |
| 44 | + computed: { | |
| 45 | + }, | |
| 46 | + emits: [], | |
| 47 | + methods: { | |
| 48 | + addStyle, | |
| 49 | + } | |
| 50 | + } | |
| 51 | +</script> | |
| 52 | + | |
| 53 | +<style lang="scss" scoped> | |
| 54 | + @import "../../libs/css/components.scss"; | |
| 55 | + | |
| 56 | + .u-box { | |
| 57 | + /* #ifndef APP-NVUE */ | |
| 58 | + /* #endif */ | |
| 59 | + @include flex(); | |
| 60 | + flex: 1; | |
| 61 | + | |
| 62 | + &__left { | |
| 63 | + @include flex(); | |
| 64 | + justify-content: center; | |
| 65 | + align-items: center; | |
| 66 | + flex: 1; | |
| 67 | + } | |
| 68 | + &__gap { | |
| 69 | + @include flex(); | |
| 70 | + flex-direction: column; | |
| 71 | + } | |
| 72 | + &__right { | |
| 73 | + @include flex(); | |
| 74 | + flex-direction: column; | |
| 75 | + flex: 1; | |
| 76 | + } | |
| 77 | + | |
| 78 | + &__right-top { | |
| 79 | + @include flex(); | |
| 80 | + flex: 1; | |
| 81 | + justify-content: center; | |
| 82 | + align-items: center; | |
| 83 | + } | |
| 84 | + | |
| 85 | + &__right-bottom { | |
| 86 | + @include flex(); | |
| 87 | + flex: 1; | |
| 88 | + justify-content: center; | |
| 89 | + align-items: center; | |
| 90 | + } | |
| 91 | + } | |
| 92 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-button/button.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:51:27 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/button.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // button组件 | |
| 12 | + button: { | |
| 13 | + hairline: false, | |
| 14 | + type: 'info', | |
| 15 | + size: 'normal', | |
| 16 | + shape: 'square', | |
| 17 | + plain: false, | |
| 18 | + disabled: false, | |
| 19 | + loading: false, | |
| 20 | + loadingText: '', | |
| 21 | + loadingMode: 'spinner', | |
| 22 | + loadingSize: 15, | |
| 23 | + openType: '', | |
| 24 | + formType: '', | |
| 25 | + appParameter: '', | |
| 26 | + hoverStopPropagation: true, | |
| 27 | + lang: 'en', | |
| 28 | + sessionFrom: '', | |
| 29 | + sendMessageTitle: '', | |
| 30 | + sendMessagePath: '', | |
| 31 | + sendMessageImg: '', | |
| 32 | + showMessageCard: false, | |
| 33 | + dataName: '', | |
| 34 | + throttleTime: 0, | |
| 35 | + hoverStartTime: 0, | |
| 36 | + hoverStayTime: 200, | |
| 37 | + text: '', | |
| 38 | + icon: '', | |
| 39 | + iconColor: '', | |
| 40 | + color: '', | |
| 41 | + stop: true, | |
| 42 | + } | |
| 43 | +} | ... | ... |
uni_modules/uview-plus/components/u-button/nvue.scss
0 → 100644
| 1 | +$u-button-active-opacity:0.75 !default; | |
| 2 | +$u-button-loading-text-margin-left:4px !default; | |
| 3 | +$u-button-text-color: #FFFFFF !default; | |
| 4 | +$u-button-text-plain-error-color:$u-error !default; | |
| 5 | +$u-button-text-plain-warning-color:$u-warning !default; | |
| 6 | +$u-button-text-plain-success-color:$u-success !default; | |
| 7 | +$u-button-text-plain-info-color:$u-info !default; | |
| 8 | +$u-button-text-plain-primary-color:$u-primary !default; | |
| 9 | +.u-button { | |
| 10 | + &--active { | |
| 11 | + opacity: $u-button-active-opacity; | |
| 12 | + } | |
| 13 | + | |
| 14 | + &--active--plain { | |
| 15 | + background-color: rgb(217, 217, 217); | |
| 16 | + } | |
| 17 | + | |
| 18 | + &__loading-text { | |
| 19 | + margin-left:$u-button-loading-text-margin-left; | |
| 20 | + } | |
| 21 | + | |
| 22 | + &__text, | |
| 23 | + &__loading-text { | |
| 24 | + color:$u-button-text-color; | |
| 25 | + } | |
| 26 | + | |
| 27 | + &__text--plain--error { | |
| 28 | + color:$u-button-text-plain-error-color; | |
| 29 | + } | |
| 30 | + | |
| 31 | + &__text--plain--warning { | |
| 32 | + color:$u-button-text-plain-warning-color; | |
| 33 | + } | |
| 34 | + | |
| 35 | + &__text--plain--success{ | |
| 36 | + color:$u-button-text-plain-success-color; | |
| 37 | + } | |
| 38 | + | |
| 39 | + &__text--plain--info { | |
| 40 | + color:$u-button-text-plain-info-color; | |
| 41 | + } | |
| 42 | + | |
| 43 | + &__text--plain--primary { | |
| 44 | + color:$u-button-text-plain-primary-color; | |
| 45 | + } | |
| 46 | +} | |
| 0 | 47 | \ No newline at end of file | ... | ... |
uni_modules/uview-plus/components/u-button/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 是否细边框 | |
| 6 | + hairline: { | |
| 7 | + type: Boolean, | |
| 8 | + default: () => defProps.button.hairline | |
| 9 | + }, | |
| 10 | + // 按钮的预置样式,info,primary,error,warning,success | |
| 11 | + type: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.button.type | |
| 14 | + }, | |
| 15 | + // 按钮尺寸,large,normal,small,mini | |
| 16 | + size: { | |
| 17 | + type: String, | |
| 18 | + default: () => defProps.button.size | |
| 19 | + }, | |
| 20 | + // 按钮形状,circle(两边为半圆),square(带圆角) | |
| 21 | + shape: { | |
| 22 | + type: String, | |
| 23 | + default: () => defProps.button.shape | |
| 24 | + }, | |
| 25 | + // 按钮是否镂空 | |
| 26 | + plain: { | |
| 27 | + type: Boolean, | |
| 28 | + default: () => defProps.button.plain | |
| 29 | + }, | |
| 30 | + // 是否禁止状态 | |
| 31 | + disabled: { | |
| 32 | + type: Boolean, | |
| 33 | + default: () => defProps.button.disabled | |
| 34 | + }, | |
| 35 | + // 是否加载中 | |
| 36 | + loading: { | |
| 37 | + type: Boolean, | |
| 38 | + default: () => defProps.button.loading | |
| 39 | + }, | |
| 40 | + // 加载中提示文字 | |
| 41 | + loadingText: { | |
| 42 | + type: [String, Number], | |
| 43 | + default: () => defProps.button.loadingText | |
| 44 | + }, | |
| 45 | + // 加载状态图标类型 | |
| 46 | + loadingMode: { | |
| 47 | + type: String, | |
| 48 | + default: () => defProps.button.loadingMode | |
| 49 | + }, | |
| 50 | + // 加载图标大小 | |
| 51 | + loadingSize: { | |
| 52 | + type: [String, Number], | |
| 53 | + default: () => defProps.button.loadingSize | |
| 54 | + }, | |
| 55 | + // 开放能力,具体请看uniapp稳定关于button组件部分说明 | |
| 56 | + // https://uniapp.dcloud.io/component/button | |
| 57 | + openType: { | |
| 58 | + type: String, | |
| 59 | + default: () => defProps.button.openType | |
| 60 | + }, | |
| 61 | + // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 | |
| 62 | + // 取值为submit(提交表单),reset(重置表单) | |
| 63 | + formType: { | |
| 64 | + type: String, | |
| 65 | + default: () => defProps.button.formType | |
| 66 | + }, | |
| 67 | + // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 | |
| 68 | + // 只微信小程序、QQ小程序有效 | |
| 69 | + appParameter: { | |
| 70 | + type: String, | |
| 71 | + default: () => defProps.button.appParameter | |
| 72 | + }, | |
| 73 | + // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效 | |
| 74 | + hoverStopPropagation: { | |
| 75 | + type: Boolean, | |
| 76 | + default: () => defProps.button.hoverStopPropagation | |
| 77 | + }, | |
| 78 | + // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效 | |
| 79 | + lang: { | |
| 80 | + type: String, | |
| 81 | + default: () => defProps.button.lang | |
| 82 | + }, | |
| 83 | + // 会话来源,open-type="contact"时有效。只微信小程序有效 | |
| 84 | + sessionFrom: { | |
| 85 | + type: String, | |
| 86 | + default: () => defProps.button.sessionFrom | |
| 87 | + }, | |
| 88 | + // 会话内消息卡片标题,open-type="contact"时有效 | |
| 89 | + // 默认当前标题,只微信小程序有效 | |
| 90 | + sendMessageTitle: { | |
| 91 | + type: String, | |
| 92 | + default: () => defProps.button.sendMessageTitle | |
| 93 | + }, | |
| 94 | + // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效 | |
| 95 | + // 默认当前分享路径,只微信小程序有效 | |
| 96 | + sendMessagePath: { | |
| 97 | + type: String, | |
| 98 | + default: () => defProps.button.sendMessagePath | |
| 99 | + }, | |
| 100 | + // 会话内消息卡片图片,open-type="contact"时有效 | |
| 101 | + // 默认当前页面截图,只微信小程序有效 | |
| 102 | + sendMessageImg: { | |
| 103 | + type: String, | |
| 104 | + default: () => defProps.button.sendMessageImg | |
| 105 | + }, | |
| 106 | + // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示, | |
| 107 | + // 用户点击后可以快速发送小程序消息,open-type="contact"时有效 | |
| 108 | + showMessageCard: { | |
| 109 | + type: Boolean, | |
| 110 | + default: () => defProps.button.showMessageCard | |
| 111 | + }, | |
| 112 | + // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 | |
| 113 | + dataName: { | |
| 114 | + type: String, | |
| 115 | + default: () => defProps.button.dataName | |
| 116 | + }, | |
| 117 | + // 节流,一定时间内只能触发一次 | |
| 118 | + throttleTime: { | |
| 119 | + type: [String, Number], | |
| 120 | + default: () => defProps.button.throttleTime | |
| 121 | + }, | |
| 122 | + // 按住后多久出现点击态,单位毫秒 | |
| 123 | + hoverStartTime: { | |
| 124 | + type: [String, Number], | |
| 125 | + default: () => defProps.button.hoverStartTime | |
| 126 | + }, | |
| 127 | + // 手指松开后点击态保留时间,单位毫秒 | |
| 128 | + hoverStayTime: { | |
| 129 | + type: [String, Number], | |
| 130 | + default: () => defProps.button.hoverStayTime | |
| 131 | + }, | |
| 132 | + // 按钮文字,之所以通过props传入,是因为slot传入的话 | |
| 133 | + // nvue中无法控制文字的样式 | |
| 134 | + text: { | |
| 135 | + type: [String, Number], | |
| 136 | + default: () => defProps.button.text | |
| 137 | + }, | |
| 138 | + // 按钮图标 | |
| 139 | + icon: { | |
| 140 | + type: String, | |
| 141 | + default: () => defProps.button.icon | |
| 142 | + }, | |
| 143 | + // 按钮图标 | |
| 144 | + iconColor: { | |
| 145 | + type: String, | |
| 146 | + default: () => defProps.button.icon | |
| 147 | + }, | |
| 148 | + // 按钮颜色,支持传入linear-gradient渐变色 | |
| 149 | + color: { | |
| 150 | + type: String, | |
| 151 | + default: () => defProps.button.color | |
| 152 | + }, | |
| 153 | + // 停止冒泡 | |
| 154 | + stop: { | |
| 155 | + type: Boolean, | |
| 156 | + default: () => defProps.button.stop | |
| 157 | + }, | |
| 158 | + } | |
| 159 | +}) | ... | ... |
uni_modules/uview-plus/components/u-button/u-button.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <!-- #ifndef APP-NVUE --> | |
| 3 | + <button | |
| 4 | + :hover-start-time="Number(hoverStartTime)" | |
| 5 | + :hover-stay-time="Number(hoverStayTime)" | |
| 6 | + :form-type="formType" | |
| 7 | + :open-type="openType" | |
| 8 | + :app-parameter="appParameter" | |
| 9 | + :hover-stop-propagation="hoverStopPropagation" | |
| 10 | + :send-message-title="sendMessageTitle" | |
| 11 | + :send-message-path="sendMessagePath" | |
| 12 | + :lang="lang" | |
| 13 | + :data-name="dataName" | |
| 14 | + :session-from="sessionFrom" | |
| 15 | + :send-message-img="sendMessageImg" | |
| 16 | + :show-message-card="showMessageCard" | |
| 17 | + @getphonenumber="getphonenumber" | |
| 18 | + @getuserinfo="getuserinfo" | |
| 19 | + @error="error" | |
| 20 | + @opensetting="opensetting" | |
| 21 | + @launchapp="launchapp" | |
| 22 | + @agreeprivacyauthorization="agreeprivacyauthorization" | |
| 23 | + :hover-class="!disabled && !loading ? 'u-button--active' : ''" | |
| 24 | + class="u-button u-reset-button" | |
| 25 | + :style="[baseColor, addStyle(customStyle)]" | |
| 26 | + @tap="clickHandler" | |
| 27 | + :class="bemClass" | |
| 28 | + > | |
| 29 | + <template v-if="loading"> | |
| 30 | + <u-loading-icon | |
| 31 | + :mode="loadingMode" | |
| 32 | + :size="loadingSize * 1.15" | |
| 33 | + :color="loadingColor" | |
| 34 | + ></u-loading-icon> | |
| 35 | + <text | |
| 36 | + class="u-button__loading-text" | |
| 37 | + :style="[{ fontSize: textSize + 'px' }]" | |
| 38 | + >{{ loadingText || text }}</text | |
| 39 | + > | |
| 40 | + </template> | |
| 41 | + <template v-else> | |
| 42 | + <u-icon | |
| 43 | + v-if="icon" | |
| 44 | + :name="icon" | |
| 45 | + :color="iconColorCom" | |
| 46 | + :size="textSize * 1.35" | |
| 47 | + :customStyle="{ marginRight: '2px' }" | |
| 48 | + ></u-icon> | |
| 49 | + <slot> | |
| 50 | + <text | |
| 51 | + class="u-button__text" | |
| 52 | + :style="[{ fontSize: textSize + 'px' }]" | |
| 53 | + >{{ text }}</text | |
| 54 | + > | |
| 55 | + </slot> | |
| 56 | + </template> | |
| 57 | + </button> | |
| 58 | + <!-- #endif --> | |
| 59 | + | |
| 60 | + <!-- #ifdef APP-NVUE --> | |
| 61 | + <view | |
| 62 | + :hover-start-time="Number(hoverStartTime)" | |
| 63 | + :hover-stay-time="Number(hoverStayTime)" | |
| 64 | + class="u-button" | |
| 65 | + :hover-class=" | |
| 66 | + !disabled && !loading && !color && (plain || type === 'info') | |
| 67 | + ? 'u-button--active--plain' | |
| 68 | + : !disabled && !loading && !plain | |
| 69 | + ? 'u-button--active' | |
| 70 | + : '' | |
| 71 | + " | |
| 72 | + @tap="clickHandler" | |
| 73 | + :class="bemClass" | |
| 74 | + :style="[baseColor, addStyle(customStyle)]" | |
| 75 | + > | |
| 76 | + <template v-if="loading"> | |
| 77 | + <u-loading-icon | |
| 78 | + :mode="loadingMode" | |
| 79 | + :size="loadingSize * 1.15" | |
| 80 | + :color="loadingColor" | |
| 81 | + ></u-loading-icon> | |
| 82 | + <text | |
| 83 | + class="u-button__loading-text" | |
| 84 | + :style="[nvueTextStyle]" | |
| 85 | + :class="[plain && `u-button__text--plain--${type}`]" | |
| 86 | + >{{ loadingText || text }}</text | |
| 87 | + > | |
| 88 | + </template> | |
| 89 | + <template v-else> | |
| 90 | + <u-icon | |
| 91 | + v-if="icon" | |
| 92 | + :name="icon" | |
| 93 | + :color="iconColorCom" | |
| 94 | + :size="textSize * 1.35" | |
| 95 | + ></u-icon> | |
| 96 | + <text | |
| 97 | + class="u-button__text" | |
| 98 | + :style="[ | |
| 99 | + { | |
| 100 | + marginLeft: icon ? '2px' : 0, | |
| 101 | + }, | |
| 102 | + nvueTextStyle, | |
| 103 | + ]" | |
| 104 | + :class="[plain && `u-button__text--plain--${type}`]" | |
| 105 | + >{{ text }}</text | |
| 106 | + > | |
| 107 | + </template> | |
| 108 | + </view> | |
| 109 | + <!-- #endif --> | |
| 110 | +</template> | |
| 111 | + | |
| 112 | +<script lang="ts"> | |
| 113 | +import { buttonMixin } from "../../libs/mixin/button"; | |
| 114 | +import { openType } from "../../libs/mixin/openType"; | |
| 115 | +import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 116 | +import { mixin } from '../../libs/mixin/mixin'; | |
| 117 | +import { props } from "./props"; | |
| 118 | +import { addStyle } from '../../libs/function/index'; | |
| 119 | +import { throttle } from '../../libs/function/throttle'; | |
| 120 | +import color from '../../libs/config/color'; | |
| 121 | +/** | |
| 122 | + * button 按钮 | |
| 123 | + * @description Button 按钮 | |
| 124 | + * @tutorial https://ijry.github.io/uview-plus/components/button.html | |
| 125 | + * | |
| 126 | + * @property {Boolean} hairline 是否显示按钮的细边框 (默认 true ) | |
| 127 | + * @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' ) | |
| 128 | + * @property {String} size 按钮尺寸,large,normal,mini (默认 normal) | |
| 129 | + * @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' ) | |
| 130 | + * @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false) | |
| 131 | + * @property {Boolean} disabled 是否禁用 (默认 false) | |
| 132 | + * @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false) | |
| 133 | + * @property {String | Number} loadingText 加载中提示文字 | |
| 134 | + * @property {String} loadingMode 加载状态图标类型 (默认 'spinner' ) | |
| 135 | + * @property {String | Number} loadingSize 加载图标大小 (默认 15 ) | |
| 136 | + * @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明 | |
| 137 | + * @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 | |
| 138 | + * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效) | |
| 139 | + * @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true ) | |
| 140 | + * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en ) | |
| 141 | + * @property {String} sessionFrom 会话来源,openType="contact"时有效 | |
| 142 | + * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效 | |
| 143 | + * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效 | |
| 144 | + * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效 | |
| 145 | + * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false) | |
| 146 | + * @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 | |
| 147 | + * @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 ) | |
| 148 | + * @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 ) | |
| 149 | + * @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 ) | |
| 150 | + * @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式) | |
| 151 | + * @property {String} icon 按钮图标 | |
| 152 | + * @property {String} iconColor 按钮图标颜色 | |
| 153 | + * @property {String} color 按钮颜色,支持传入linear-gradient渐变色 | |
| 154 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 155 | + * | |
| 156 | + * @event {Function} click 非禁止并且非加载中,才能点击 | |
| 157 | + * @event {Function} getphonenumber open-type="getPhoneNumber"时有效 | |
| 158 | + * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo | |
| 159 | + * @event {Function} error 当使用开放能力时,发生错误的回调 | |
| 160 | + * @event {Function} opensetting 在打开授权设置页并关闭后回调 | |
| 161 | + * @event {Function} launchapp 打开 APP 成功的回调 | |
| 162 | + * @event {Function} agreeprivacyauthorization 用户同意隐私协议事件回调 | |
| 163 | + * @example <u-button>月落</u-button> | |
| 164 | + */ | |
| 165 | +export default { | |
| 166 | + name: "u-button", | |
| 167 | + // #ifdef MP | |
| 168 | + mixins: [mpMixin, mixin, buttonMixin, openType, props], | |
| 169 | + // #endif | |
| 170 | + // #ifndef MP | |
| 171 | + mixins: [mpMixin, mixin, props], | |
| 172 | + // #endif | |
| 173 | + data() { | |
| 174 | + return {}; | |
| 175 | + }, | |
| 176 | + computed: { | |
| 177 | + // 生成bem风格的类名 | |
| 178 | + bemClass() { | |
| 179 | + // this.bem为一个computed变量,在mixin中 | |
| 180 | + if (!this.color) { | |
| 181 | + return this.bem( | |
| 182 | + "button", | |
| 183 | + ["type", "shape", "size"], | |
| 184 | + ["disabled", "plain", "hairline"] | |
| 185 | + ); | |
| 186 | + } else { | |
| 187 | + // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式 | |
| 188 | + return this.bem( | |
| 189 | + "button", | |
| 190 | + ["shape", "size"], | |
| 191 | + ["disabled", "plain", "hairline"] | |
| 192 | + ); | |
| 193 | + } | |
| 194 | + }, | |
| 195 | + loadingColor() { | |
| 196 | + if (this.plain) { | |
| 197 | + // 如果有设置color值,则用color值,否则使用type主题颜色 | |
| 198 | + return this.color | |
| 199 | + ? this.color | |
| 200 | + : color[`u-${this.type}`]; | |
| 201 | + } | |
| 202 | + if (this.type === "info") { | |
| 203 | + return "#c9c9c9"; | |
| 204 | + } | |
| 205 | + return "rgb(200, 200, 200)"; | |
| 206 | + }, | |
| 207 | + iconColorCom() { | |
| 208 | + // 如果是镂空状态,设置了color就用color值,否则使用主题颜色, | |
| 209 | + // u-icon的color能接受一个主题颜色的值 | |
| 210 | + if (this.iconColor) return this.iconColor; | |
| 211 | + if (this.plain) { | |
| 212 | + return this.color ? this.color : this.type; | |
| 213 | + } else { | |
| 214 | + return this.type === "info" ? "#000000" : "#ffffff"; | |
| 215 | + } | |
| 216 | + }, | |
| 217 | + baseColor() { | |
| 218 | + let style = {}; | |
| 219 | + if (this.color) { | |
| 220 | + // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色 | |
| 221 | + style.color = this.plain ? this.color : "white"; | |
| 222 | + if (!this.plain) { | |
| 223 | + // 非镂空,背景色使用自定义的颜色 | |
| 224 | + style["background-color"] = this.color; | |
| 225 | + } | |
| 226 | + if (this.color.indexOf("gradient") !== -1) { | |
| 227 | + // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色 | |
| 228 | + // weex文档说明可以写borderWidth的形式,为什么这里需要分开写? | |
| 229 | + // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效 | |
| 230 | + style.borderTopWidth = 0; | |
| 231 | + style.borderRightWidth = 0; | |
| 232 | + style.borderBottomWidth = 0; | |
| 233 | + style.borderLeftWidth = 0; | |
| 234 | + if (!this.plain) { | |
| 235 | + style.backgroundImage = this.color; | |
| 236 | + } | |
| 237 | + } else { | |
| 238 | + // 非渐变色,则设置边框相关的属性 | |
| 239 | + style.borderColor = this.color; | |
| 240 | + style.borderWidth = "1px"; | |
| 241 | + style.borderStyle = "solid"; | |
| 242 | + } | |
| 243 | + } | |
| 244 | + return style; | |
| 245 | + }, | |
| 246 | + // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置 | |
| 247 | + nvueTextStyle() { | |
| 248 | + let style = {}; | |
| 249 | + // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色 | |
| 250 | + if (this.type === "info") { | |
| 251 | + style.color = "#323233"; | |
| 252 | + } | |
| 253 | + if (this.color) { | |
| 254 | + style.color = this.plain ? this.color : "white"; | |
| 255 | + } | |
| 256 | + style.fontSize = this.textSize + "px"; | |
| 257 | + return style; | |
| 258 | + }, | |
| 259 | + // 字体大小 | |
| 260 | + textSize() { | |
| 261 | + let fontSize = 14, | |
| 262 | + { size } = this; | |
| 263 | + if (size === "large") fontSize = 16; | |
| 264 | + if (size === "normal") fontSize = 14; | |
| 265 | + if (size === "small") fontSize = 12; | |
| 266 | + if (size === "mini") fontSize = 10; | |
| 267 | + return fontSize; | |
| 268 | + }, | |
| 269 | + }, | |
| 270 | + emits: ['click', 'getphonenumber', 'getuserinfo', | |
| 271 | + 'error', 'opensetting', 'launchapp', 'agreeprivacyauthorization'], | |
| 272 | + methods: { | |
| 273 | + addStyle, | |
| 274 | + clickHandler(e: any) { | |
| 275 | + // 非禁止并且非加载中,才能点击 | |
| 276 | + if (!this.disabled && !this.loading) { | |
| 277 | + // 进行节流控制,每this.throttle毫秒内,只在开始处执行 | |
| 278 | + throttle(() => { | |
| 279 | + this.$emit("click", e); | |
| 280 | + }, this.throttleTime); | |
| 281 | + } | |
| 282 | + // 是否阻止事件传播 | |
| 283 | + this.stop && this.preventEvent(e) | |
| 284 | + }, | |
| 285 | + // 下面为对接uniapp官方按钮开放能力事件回调的对接 | |
| 286 | + getphonenumber(res: any) { | |
| 287 | + this.$emit("getphonenumber", res); | |
| 288 | + }, | |
| 289 | + getuserinfo(res: any) { | |
| 290 | + this.$emit("getuserinfo", res); | |
| 291 | + }, | |
| 292 | + error(res: any) { | |
| 293 | + this.$emit("error", res); | |
| 294 | + }, | |
| 295 | + opensetting(res: any) { | |
| 296 | + this.$emit("opensetting", res); | |
| 297 | + }, | |
| 298 | + launchapp(res: any) { | |
| 299 | + this.$emit("launchapp", res); | |
| 300 | + }, | |
| 301 | + agreeprivacyauthorization(res) { | |
| 302 | + this.$emit("agreeprivacyauthorization", res); | |
| 303 | + }, | |
| 304 | + }, | |
| 305 | +}; | |
| 306 | +</script> | |
| 307 | + | |
| 308 | +<style lang="scss" scoped> | |
| 309 | +@import "../../libs/css/components.scss"; | |
| 310 | + | |
| 311 | +/* #ifndef APP-NVUE */ | |
| 312 | +@import "./vue.scss"; | |
| 313 | +/* #endif */ | |
| 314 | + | |
| 315 | +/* #ifdef APP-NVUE */ | |
| 316 | +@import "./nvue.scss"; | |
| 317 | +/* #endif */ | |
| 318 | + | |
| 319 | +$u-button-u-button-height: 40px !default; | |
| 320 | +$u-button-text-font-size: 15px !default; | |
| 321 | +$u-button-loading-text-font-size: 15px !default; | |
| 322 | +$u-button-loading-text-margin-left: 4px !default; | |
| 323 | +$u-button-large-width: 100% !default; | |
| 324 | +$u-button-large-height: 50px !default; | |
| 325 | +$u-button-normal-padding: 0 12px !default; | |
| 326 | +$u-button-large-padding: 0 15px !default; | |
| 327 | +$u-button-normal-font-size: 14px !default; | |
| 328 | +$u-button-small-min-width: 60px !default; | |
| 329 | +$u-button-small-height: 30px !default; | |
| 330 | +$u-button-small-padding: 0px 8px !default; | |
| 331 | +$u-button-mini-padding: 0px 8px !default; | |
| 332 | +$u-button-small-font-size: 12px !default; | |
| 333 | +$u-button-mini-height: 22px !default; | |
| 334 | +$u-button-mini-font-size: 10px !default; | |
| 335 | +$u-button-mini-min-width: 50px !default; | |
| 336 | +$u-button-disabled-opacity: 0.5 !default; | |
| 337 | +$u-button-info-color: #323233 !default; | |
| 338 | +$u-button-info-background-color: #fff !default; | |
| 339 | +$u-button-info-border-color: #ebedf0 !default; | |
| 340 | +$u-button-info-border-width: 1px !default; | |
| 341 | +$u-button-info-border-style: solid !default; | |
| 342 | +$u-button-success-color: #fff !default; | |
| 343 | +$u-button-success-background-color: $u-success !default; | |
| 344 | +$u-button-success-border-color: $u-button-success-background-color !default; | |
| 345 | +$u-button-success-border-width: 1px !default; | |
| 346 | +$u-button-success-border-style: solid !default; | |
| 347 | +$u-button-primary-color: #fff !default; | |
| 348 | +$u-button-primary-background-color: $u-primary !default; | |
| 349 | +$u-button-primary-border-color: $u-button-primary-background-color !default; | |
| 350 | +$u-button-primary-border-width: 1px !default; | |
| 351 | +$u-button-primary-border-style: solid !default; | |
| 352 | +$u-button-error-color: #fff !default; | |
| 353 | +$u-button-error-background-color: $u-error !default; | |
| 354 | +$u-button-error-border-color: $u-button-error-background-color !default; | |
| 355 | +$u-button-error-border-width: 1px !default; | |
| 356 | +$u-button-error-border-style: solid !default; | |
| 357 | +$u-button-warning-color: #fff !default; | |
| 358 | +$u-button-warning-background-color: $u-warning !default; | |
| 359 | +$u-button-warning-border-color: $u-button-warning-background-color !default; | |
| 360 | +$u-button-warning-border-width: 1px !default; | |
| 361 | +$u-button-warning-border-style: solid !default; | |
| 362 | +$u-button-block-width: 100% !default; | |
| 363 | +$u-button-circle-border-top-right-radius: 100px !default; | |
| 364 | +$u-button-circle-border-top-left-radius: 100px !default; | |
| 365 | +$u-button-circle-border-bottom-left-radius: 100px !default; | |
| 366 | +$u-button-circle-border-bottom-right-radius: 100px !default; | |
| 367 | +$u-button-square-border-top-right-radius: 3px !default; | |
| 368 | +$u-button-square-border-top-left-radius: 3px !default; | |
| 369 | +$u-button-square-border-bottom-left-radius: 3px !default; | |
| 370 | +$u-button-square-border-bottom-right-radius: 3px !default; | |
| 371 | +$u-button-icon-min-width: 1em !default; | |
| 372 | +$u-button-plain-background-color: #fff !default; | |
| 373 | +$u-button-hairline-border-width: 0.5px !default; | |
| 374 | + | |
| 375 | +.u-button { | |
| 376 | + height: $u-button-u-button-height; | |
| 377 | + position: relative; | |
| 378 | + align-items: center; | |
| 379 | + justify-content: center; | |
| 380 | + @include flex; | |
| 381 | + /* #ifndef APP-NVUE */ | |
| 382 | + box-sizing: border-box; | |
| 383 | + /* #endif */ | |
| 384 | + flex-direction: row; | |
| 385 | + | |
| 386 | + &__text { | |
| 387 | + font-size: $u-button-text-font-size; | |
| 388 | + } | |
| 389 | + | |
| 390 | + &__loading-text { | |
| 391 | + font-size: $u-button-loading-text-font-size; | |
| 392 | + margin-left: $u-button-loading-text-margin-left; | |
| 393 | + } | |
| 394 | + | |
| 395 | + &--large { | |
| 396 | + /* #ifndef APP-NVUE */ | |
| 397 | + width: $u-button-large-width; | |
| 398 | + /* #endif */ | |
| 399 | + height: $u-button-large-height; | |
| 400 | + padding: $u-button-large-padding; | |
| 401 | + } | |
| 402 | + | |
| 403 | + &--normal { | |
| 404 | + padding: $u-button-normal-padding; | |
| 405 | + font-size: $u-button-normal-font-size; | |
| 406 | + } | |
| 407 | + | |
| 408 | + &--small { | |
| 409 | + /* #ifndef APP-NVUE */ | |
| 410 | + min-width: $u-button-small-min-width; | |
| 411 | + /* #endif */ | |
| 412 | + height: $u-button-small-height; | |
| 413 | + padding: $u-button-small-padding; | |
| 414 | + font-size: $u-button-small-font-size; | |
| 415 | + } | |
| 416 | + | |
| 417 | + &--mini { | |
| 418 | + height: $u-button-mini-height; | |
| 419 | + font-size: $u-button-mini-font-size; | |
| 420 | + /* #ifndef APP-NVUE */ | |
| 421 | + min-width: $u-button-mini-min-width; | |
| 422 | + /* #endif */ | |
| 423 | + padding: $u-button-mini-padding; | |
| 424 | + } | |
| 425 | + | |
| 426 | + &--disabled { | |
| 427 | + opacity: $u-button-disabled-opacity; | |
| 428 | + } | |
| 429 | + | |
| 430 | + &--info { | |
| 431 | + color: $u-button-info-color; | |
| 432 | + background-color: $u-button-info-background-color; | |
| 433 | + border-color: $u-button-info-border-color; | |
| 434 | + border-width: $u-button-info-border-width; | |
| 435 | + border-style: $u-button-info-border-style; | |
| 436 | + } | |
| 437 | + | |
| 438 | + &--success { | |
| 439 | + color: $u-button-success-color; | |
| 440 | + background-color: $u-button-success-background-color; | |
| 441 | + border-color: $u-button-success-border-color; | |
| 442 | + border-width: $u-button-success-border-width; | |
| 443 | + border-style: $u-button-success-border-style; | |
| 444 | + } | |
| 445 | + | |
| 446 | + &--primary { | |
| 447 | + color: $u-button-primary-color; | |
| 448 | + background-color: $u-button-primary-background-color; | |
| 449 | + border-color: $u-button-primary-border-color; | |
| 450 | + border-width: $u-button-primary-border-width; | |
| 451 | + border-style: $u-button-primary-border-style; | |
| 452 | + } | |
| 453 | + | |
| 454 | + &--error { | |
| 455 | + color: $u-button-error-color; | |
| 456 | + background-color: $u-button-error-background-color; | |
| 457 | + border-color: $u-button-error-border-color; | |
| 458 | + border-width: $u-button-error-border-width; | |
| 459 | + border-style: $u-button-error-border-style; | |
| 460 | + } | |
| 461 | + | |
| 462 | + &--warning { | |
| 463 | + color: $u-button-warning-color; | |
| 464 | + background-color: $u-button-warning-background-color; | |
| 465 | + border-color: $u-button-warning-border-color; | |
| 466 | + border-width: $u-button-warning-border-width; | |
| 467 | + border-style: $u-button-warning-border-style; | |
| 468 | + } | |
| 469 | + | |
| 470 | + &--block { | |
| 471 | + @include flex; | |
| 472 | + width: $u-button-block-width; | |
| 473 | + } | |
| 474 | + | |
| 475 | + &--circle { | |
| 476 | + border-top-right-radius: $u-button-circle-border-top-right-radius; | |
| 477 | + border-top-left-radius: $u-button-circle-border-top-left-radius; | |
| 478 | + border-bottom-left-radius: $u-button-circle-border-bottom-left-radius; | |
| 479 | + border-bottom-right-radius: $u-button-circle-border-bottom-right-radius; | |
| 480 | + } | |
| 481 | + | |
| 482 | + &--square { | |
| 483 | + border-bottom-left-radius: $u-button-square-border-top-right-radius; | |
| 484 | + border-bottom-right-radius: $u-button-square-border-top-left-radius; | |
| 485 | + border-top-left-radius: $u-button-square-border-bottom-left-radius; | |
| 486 | + border-top-right-radius: $u-button-square-border-bottom-right-radius; | |
| 487 | + } | |
| 488 | + | |
| 489 | + &__icon { | |
| 490 | + /* #ifndef APP-NVUE */ | |
| 491 | + min-width: $u-button-icon-min-width; | |
| 492 | + line-height: inherit !important; | |
| 493 | + vertical-align: top; | |
| 494 | + /* #endif */ | |
| 495 | + } | |
| 496 | + | |
| 497 | + &--plain { | |
| 498 | + background-color: $u-button-plain-background-color; | |
| 499 | + } | |
| 500 | + | |
| 501 | + &--hairline { | |
| 502 | + border-width: $u-button-hairline-border-width !important; | |
| 503 | + } | |
| 504 | +} | |
| 505 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-button/vue.scss
0 → 100644
| 1 | +// nvue下hover-class无效 | |
| 2 | +$u-button-before-top:50% !default; | |
| 3 | +$u-button-before-left:50% !default; | |
| 4 | +$u-button-before-width:100% !default; | |
| 5 | +$u-button-before-height:100% !default; | |
| 6 | +$u-button-before-transform:translate(-50%, -50%) !default; | |
| 7 | +$u-button-before-opacity:0 !default; | |
| 8 | +$u-button-before-background-color:#000 !default; | |
| 9 | +$u-button-before-border-color:#000 !default; | |
| 10 | +$u-button-active-before-opacity:.15 !default; | |
| 11 | +$u-button-icon-margin-left:4px !default; | |
| 12 | +$u-button-plain-u-button-info-color:$u-info; | |
| 13 | +$u-button-plain-u-button-success-color:$u-success; | |
| 14 | +$u-button-plain-u-button-error-color:$u-error; | |
| 15 | +$u-button-plain-u-button-warning-color:$u-warning; | |
| 16 | + | |
| 17 | +.u-button { | |
| 18 | + width: 100%; | |
| 19 | + white-space: nowrap; | |
| 20 | + | |
| 21 | + &__text { | |
| 22 | + white-space: nowrap; | |
| 23 | + line-height: 1; | |
| 24 | + } | |
| 25 | + | |
| 26 | + &:before { | |
| 27 | + position: absolute; | |
| 28 | + top:$u-button-before-top; | |
| 29 | + left:$u-button-before-left; | |
| 30 | + width:$u-button-before-width; | |
| 31 | + height:$u-button-before-height; | |
| 32 | + border: inherit; | |
| 33 | + border-radius: inherit; | |
| 34 | + transform:$u-button-before-transform; | |
| 35 | + opacity:$u-button-before-opacity; | |
| 36 | + content: " "; | |
| 37 | + background-color:$u-button-before-background-color; | |
| 38 | + border-color:$u-button-before-border-color; | |
| 39 | + } | |
| 40 | + | |
| 41 | + &--active { | |
| 42 | + &:before { | |
| 43 | + opacity: .15 | |
| 44 | + } | |
| 45 | + } | |
| 46 | + | |
| 47 | + &__icon+&__text:not(:empty), | |
| 48 | + &__loading-text { | |
| 49 | + margin-left:$u-button-icon-margin-left; | |
| 50 | + } | |
| 51 | + | |
| 52 | + &--plain { | |
| 53 | + &.u-button--primary { | |
| 54 | + color: $u-primary; | |
| 55 | + } | |
| 56 | + } | |
| 57 | + | |
| 58 | + &--plain { | |
| 59 | + &.u-button--info { | |
| 60 | + color:$u-button-plain-u-button-info-color; | |
| 61 | + } | |
| 62 | + } | |
| 63 | + | |
| 64 | + &--plain { | |
| 65 | + &.u-button--success { | |
| 66 | + color:$u-button-plain-u-button-success-color; | |
| 67 | + } | |
| 68 | + } | |
| 69 | + | |
| 70 | + &--plain { | |
| 71 | + &.u-button--error { | |
| 72 | + color:$u-button-plain-u-button-error-color; | |
| 73 | + } | |
| 74 | + } | |
| 75 | + | |
| 76 | + &--plain { | |
| 77 | + &.u-button--warning { | |
| 78 | + color:$u-button-plain-u-button-warning-color; | |
| 79 | + } | |
| 80 | + } | |
| 81 | +} | ... | ... |
uni_modules/uview-plus/components/u-calendar/calendar.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:52:43 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/calendar.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // calendar 组件 | |
| 12 | + calendar: { | |
| 13 | + title: '日期选择', | |
| 14 | + showTitle: true, | |
| 15 | + showSubtitle: true, | |
| 16 | + mode: 'single', | |
| 17 | + startText: '开始', | |
| 18 | + endText: '结束', | |
| 19 | + customList: [], | |
| 20 | + color: '#3c9cff', | |
| 21 | + minDate: 0, | |
| 22 | + maxDate: 0, | |
| 23 | + defaultDate: null, | |
| 24 | + maxCount: Number.MAX_SAFE_INTEGER, // Infinity | |
| 25 | + rowHeight: 56, | |
| 26 | + formatter: null, | |
| 27 | + showLunar: false, | |
| 28 | + showMark: true, | |
| 29 | + confirmText: '确定', | |
| 30 | + confirmDisabledText: '确定', | |
| 31 | + show: false, | |
| 32 | + closeOnClickOverlay: false, | |
| 33 | + readonly: false, | |
| 34 | + showConfirm: true, | |
| 35 | + maxRange: Number.MAX_SAFE_INTEGER, // Infinity | |
| 36 | + rangePrompt: '', | |
| 37 | + showRangePrompt: true, | |
| 38 | + allowSameDay: false, | |
| 39 | + round: 0, | |
| 40 | + monthNum: 3 | |
| 41 | + } | |
| 42 | +} | ... | ... |
uni_modules/uview-plus/components/u-calendar/header.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-calendar-header u-border-bottom"> | |
| 3 | + <text | |
| 4 | + class="u-calendar-header__title" | |
| 5 | + v-if="showTitle" | |
| 6 | + >{{ title }}</text> | |
| 7 | + <text | |
| 8 | + class="u-calendar-header__subtitle" | |
| 9 | + v-if="showSubtitle" | |
| 10 | + >{{ subtitle }}</text> | |
| 11 | + <view class="u-calendar-header__weekdays"> | |
| 12 | + <text class="u-calendar-header__weekdays__weekday">一</text> | |
| 13 | + <text class="u-calendar-header__weekdays__weekday">二</text> | |
| 14 | + <text class="u-calendar-header__weekdays__weekday">三</text> | |
| 15 | + <text class="u-calendar-header__weekdays__weekday">四</text> | |
| 16 | + <text class="u-calendar-header__weekdays__weekday">五</text> | |
| 17 | + <text class="u-calendar-header__weekdays__weekday">六</text> | |
| 18 | + <text class="u-calendar-header__weekdays__weekday">日</text> | |
| 19 | + </view> | |
| 20 | + </view> | |
| 21 | +</template> | |
| 22 | + | |
| 23 | +<script> | |
| 24 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 25 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 26 | + export default { | |
| 27 | + name: 'u-calendar-header', | |
| 28 | + mixins: [mpMixin, mixin], | |
| 29 | + props: { | |
| 30 | + // 标题 | |
| 31 | + title: { | |
| 32 | + type: String, | |
| 33 | + default: '' | |
| 34 | + }, | |
| 35 | + // 副标题 | |
| 36 | + subtitle: { | |
| 37 | + type: String, | |
| 38 | + default: '' | |
| 39 | + }, | |
| 40 | + // 是否显示标题 | |
| 41 | + showTitle: { | |
| 42 | + type: Boolean, | |
| 43 | + default: true | |
| 44 | + }, | |
| 45 | + // 是否显示副标题 | |
| 46 | + showSubtitle: { | |
| 47 | + type: Boolean, | |
| 48 | + default: true | |
| 49 | + }, | |
| 50 | + }, | |
| 51 | + data() { | |
| 52 | + return { | |
| 53 | + | |
| 54 | + } | |
| 55 | + }, | |
| 56 | + methods: { | |
| 57 | + name() { | |
| 58 | + | |
| 59 | + } | |
| 60 | + }, | |
| 61 | + } | |
| 62 | +</script> | |
| 63 | + | |
| 64 | +<style lang="scss" scoped> | |
| 65 | + @import "../../libs/css/components.scss"; | |
| 66 | + | |
| 67 | + .u-calendar-header { | |
| 68 | + display: flex; | |
| 69 | + flex-direction: column; | |
| 70 | + padding-bottom: 4px; | |
| 71 | + | |
| 72 | + &__title { | |
| 73 | + font-size: 16px; | |
| 74 | + color: $u-main-color; | |
| 75 | + text-align: center; | |
| 76 | + height: 42px; | |
| 77 | + line-height: 42px; | |
| 78 | + font-weight: bold; | |
| 79 | + } | |
| 80 | + | |
| 81 | + &__subtitle { | |
| 82 | + font-size: 14px; | |
| 83 | + color: $u-main-color; | |
| 84 | + height: 40px; | |
| 85 | + text-align: center; | |
| 86 | + line-height: 40px; | |
| 87 | + font-weight: bold; | |
| 88 | + } | |
| 89 | + | |
| 90 | + &__weekdays { | |
| 91 | + @include flex; | |
| 92 | + justify-content: space-between; | |
| 93 | + | |
| 94 | + &__weekday { | |
| 95 | + font-size: 13px; | |
| 96 | + color: $u-main-color; | |
| 97 | + line-height: 30px; | |
| 98 | + flex: 1; | |
| 99 | + text-align: center; | |
| 100 | + } | |
| 101 | + } | |
| 102 | + } | |
| 103 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-calendar/month.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper"> | |
| 3 | + <view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]" | |
| 4 | + :ref="`u-calendar-month-${index}`" :id="`month-${index}`"> | |
| 5 | + <text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text> | |
| 6 | + <view class="u-calendar-month__days"> | |
| 7 | + <view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper"> | |
| 8 | + <text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text> | |
| 9 | + </view> | |
| 10 | + <view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1" | |
| 11 | + :style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)" | |
| 12 | + :class="[item1.selected && 'u-calendar-month__days__day__select--selected']"> | |
| 13 | + <view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]"> | |
| 14 | + <text class="u-calendar-month__days__day__select__info" | |
| 15 | + :class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']" | |
| 16 | + :style="[textStyle(item1)]">{{ item1.day }}</text> | |
| 17 | + <text v-if="getBottomInfo(index, index1, item1)" | |
| 18 | + class="u-calendar-month__days__day__select__buttom-info" | |
| 19 | + :class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']" | |
| 20 | + :style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text> | |
| 21 | + <text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text> | |
| 22 | + </view> | |
| 23 | + </view> | |
| 24 | + </view> | |
| 25 | + </view> | |
| 26 | + </view> | |
| 27 | +</template> | |
| 28 | + | |
| 29 | +<script> | |
| 30 | + // #ifdef APP-NVUE | |
| 31 | + // 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度 | |
| 32 | + const dom = uni.requireNativePlugin('dom') | |
| 33 | + // #endif | |
| 34 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 35 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 36 | + import { addUnit, deepClone, toast, sleep } from '../../libs/function/index'; | |
| 37 | + import { colorGradient } from '../../libs/function/colorGradient'; | |
| 38 | + import test from '../../libs/function/test'; | |
| 39 | + import defProps from '../../libs/config/props'; | |
| 40 | + import dayjs from 'dayjs/esm/index' | |
| 41 | + export default { | |
| 42 | + name: 'u-calendar-month', | |
| 43 | + mixins: [mpMixin, mixin], | |
| 44 | + props: { | |
| 45 | + // 是否显示月份背景色 | |
| 46 | + showMark: { | |
| 47 | + type: Boolean, | |
| 48 | + default: true | |
| 49 | + }, | |
| 50 | + // 主题色,对底部按钮和选中日期有效 | |
| 51 | + color: { | |
| 52 | + type: String, | |
| 53 | + default: '#3c9cff' | |
| 54 | + }, | |
| 55 | + // 月份数据 | |
| 56 | + months: { | |
| 57 | + type: Array, | |
| 58 | + default: () => [] | |
| 59 | + }, | |
| 60 | + // 日期选择类型 | |
| 61 | + mode: { | |
| 62 | + type: String, | |
| 63 | + default: 'single' | |
| 64 | + }, | |
| 65 | + // 日期行高 | |
| 66 | + rowHeight: { | |
| 67 | + type: [String, Number], | |
| 68 | + default: 58 | |
| 69 | + }, | |
| 70 | + // mode=multiple时,最多可选多少个日期 | |
| 71 | + maxCount: { | |
| 72 | + type: [String, Number], | |
| 73 | + default: Infinity | |
| 74 | + }, | |
| 75 | + // mode=range时,第一个日期底部的提示文字 | |
| 76 | + startText: { | |
| 77 | + type: String, | |
| 78 | + default: '开始' | |
| 79 | + }, | |
| 80 | + // mode=range时,最后一个日期底部的提示文字 | |
| 81 | + endText: { | |
| 82 | + type: String, | |
| 83 | + default: '结束' | |
| 84 | + }, | |
| 85 | + // 默认选中的日期,mode为multiple或range是必须为数组格式 | |
| 86 | + defaultDate: { | |
| 87 | + type: [Array, String, Date], | |
| 88 | + default: null | |
| 89 | + }, | |
| 90 | + // 最小的可选日期 | |
| 91 | + minDate: { | |
| 92 | + type: [String, Number], | |
| 93 | + default: 0 | |
| 94 | + }, | |
| 95 | + // 最大可选日期 | |
| 96 | + maxDate: { | |
| 97 | + type: [String, Number], | |
| 98 | + default: 0 | |
| 99 | + }, | |
| 100 | + // 如果没有设置maxDate,则往后推多少个月 | |
| 101 | + maxMonth: { | |
| 102 | + type: [String, Number], | |
| 103 | + default: 2 | |
| 104 | + }, | |
| 105 | + // 是否为只读状态,只读状态下禁止选择日期 | |
| 106 | + readonly: { | |
| 107 | + type: Boolean, | |
| 108 | + default: () => defProps.calendar.readonly | |
| 109 | + }, | |
| 110 | + // 日期区间最多可选天数,默认无限制,mode = range时有效 | |
| 111 | + maxRange: { | |
| 112 | + type: [Number, String], | |
| 113 | + default: Infinity | |
| 114 | + }, | |
| 115 | + // 范围选择超过最多可选天数时的提示文案,mode = range时有效 | |
| 116 | + rangePrompt: { | |
| 117 | + type: String, | |
| 118 | + default: '' | |
| 119 | + }, | |
| 120 | + // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 | |
| 121 | + showRangePrompt: { | |
| 122 | + type: Boolean, | |
| 123 | + default: true | |
| 124 | + }, | |
| 125 | + // 是否允许日期范围的起止时间为同一天,mode = range时有效 | |
| 126 | + allowSameDay: { | |
| 127 | + type: Boolean, | |
| 128 | + default: false | |
| 129 | + } | |
| 130 | + }, | |
| 131 | + data() { | |
| 132 | + return { | |
| 133 | + // 每个日期的宽度 | |
| 134 | + width: 0, | |
| 135 | + // 当前选中的日期item | |
| 136 | + item: {}, | |
| 137 | + selected: [] | |
| 138 | + } | |
| 139 | + }, | |
| 140 | + watch: { | |
| 141 | + selectedChange: { | |
| 142 | + immediate: true, | |
| 143 | + handler(n) { | |
| 144 | + this.setDefaultDate() | |
| 145 | + } | |
| 146 | + } | |
| 147 | + }, | |
| 148 | + computed: { | |
| 149 | + // 多个条件的变化,会引起选中日期的变化,这里统一管理监听 | |
| 150 | + selectedChange() { | |
| 151 | + return [this.minDate, this.maxDate, this.defaultDate] | |
| 152 | + }, | |
| 153 | + dayStyle(index1, index2, item) { | |
| 154 | + return (index1, index2, item) => { | |
| 155 | + const style = {} | |
| 156 | + let week = item.week | |
| 157 | + // 不进行四舍五入的形式保留2位小数 | |
| 158 | + const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1)) | |
| 159 | + // 得出每个日期的宽度 | |
| 160 | + // #ifdef APP-NVUE | |
| 161 | + style.width = addUnit(dayWidth, 'px') | |
| 162 | + // #endif | |
| 163 | + style.height = addUnit(this.rowHeight) | |
| 164 | + if (index2 === 0) { | |
| 165 | + // 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数 | |
| 166 | + week = (week === 0 ? 7 : week) - 1 | |
| 167 | + style.marginLeft = addUnit(week * dayWidth, 'px') | |
| 168 | + } | |
| 169 | + if (this.mode === 'range') { | |
| 170 | + // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug | |
| 171 | + style.paddingLeft = 0 | |
| 172 | + style.paddingRight = 0 | |
| 173 | + style.paddingBottom = 0 | |
| 174 | + style.paddingTop = 0 | |
| 175 | + } | |
| 176 | + return style | |
| 177 | + } | |
| 178 | + }, | |
| 179 | + daySelectStyle() { | |
| 180 | + return (index1, index2, item) => { | |
| 181 | + let date = dayjs(item.date).format("YYYY-MM-DD"), | |
| 182 | + style = {} | |
| 183 | + // 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断 | |
| 184 | + if (this.selected.some(item => this.dateSame(item, date))) { | |
| 185 | + style.backgroundColor = this.color | |
| 186 | + } | |
| 187 | + if (this.mode === 'single') { | |
| 188 | + if (date === this.selected[0]) { | |
| 189 | + // 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等 | |
| 190 | + style.borderTopLeftRadius = '3px' | |
| 191 | + style.borderBottomLeftRadius = '3px' | |
| 192 | + style.borderTopRightRadius = '3px' | |
| 193 | + style.borderBottomRightRadius = '3px' | |
| 194 | + } | |
| 195 | + } else if (this.mode === 'range') { | |
| 196 | + if (this.selected.length >= 2) { | |
| 197 | + const len = this.selected.length - 1 | |
| 198 | + // 第一个日期设置左上角和左下角的圆角 | |
| 199 | + if (this.dateSame(date, this.selected[0])) { | |
| 200 | + style.borderTopLeftRadius = '3px' | |
| 201 | + style.borderBottomLeftRadius = '3px' | |
| 202 | + } | |
| 203 | + // 最后一个日期设置右上角和右下角的圆角 | |
| 204 | + if (this.dateSame(date, this.selected[len])) { | |
| 205 | + style.borderTopRightRadius = '3px' | |
| 206 | + style.borderBottomRightRadius = '3px' | |
| 207 | + } | |
| 208 | + // 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值 | |
| 209 | + if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this | |
| 210 | + .selected[len]))) { | |
| 211 | + style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90] | |
| 212 | + // 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符 | |
| 213 | + style.opacity = 0.7 | |
| 214 | + } | |
| 215 | + } else if (this.selected.length === 1) { | |
| 216 | + // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug | |
| 217 | + // 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现 | |
| 218 | + style.borderTopLeftRadius = '3px' | |
| 219 | + style.borderBottomLeftRadius = '3px' | |
| 220 | + } | |
| 221 | + } else { | |
| 222 | + if (this.selected.some(item => this.dateSame(item, date))) { | |
| 223 | + style.borderTopLeftRadius = '3px' | |
| 224 | + style.borderBottomLeftRadius = '3px' | |
| 225 | + style.borderTopRightRadius = '3px' | |
| 226 | + style.borderBottomRightRadius = '3px' | |
| 227 | + } | |
| 228 | + } | |
| 229 | + return style | |
| 230 | + } | |
| 231 | + }, | |
| 232 | + // 某个日期是否被选中 | |
| 233 | + textStyle() { | |
| 234 | + return (item) => { | |
| 235 | + const date = dayjs(item.date).format("YYYY-MM-DD"), | |
| 236 | + style = {} | |
| 237 | + // 选中的日期,提示文字设置白色 | |
| 238 | + if (this.selected.some(item => this.dateSame(item, date))) { | |
| 239 | + style.color = '#ffffff' | |
| 240 | + } | |
| 241 | + if (this.mode === 'range') { | |
| 242 | + const len = this.selected.length - 1 | |
| 243 | + // 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色 | |
| 244 | + if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this | |
| 245 | + .selected[len]))) { | |
| 246 | + style.color = this.color | |
| 247 | + } | |
| 248 | + } | |
| 249 | + return style | |
| 250 | + } | |
| 251 | + }, | |
| 252 | + // 获取底部的提示文字 | |
| 253 | + getBottomInfo() { | |
| 254 | + return (index1, index2, item) => { | |
| 255 | + const date = dayjs(item.date).format("YYYY-MM-DD") | |
| 256 | + const bottomInfo = item.bottomInfo | |
| 257 | + // 当为日期范围模式时,且选择的日期个数大于0时 | |
| 258 | + if (this.mode === 'range' && this.selected.length > 0) { | |
| 259 | + if (this.selected.length === 1) { | |
| 260 | + // 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始” | |
| 261 | + if (this.dateSame(date, this.selected[0])) return this.startText | |
| 262 | + else return bottomInfo | |
| 263 | + } else { | |
| 264 | + const len = this.selected.length - 1 | |
| 265 | + // 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期 | |
| 266 | + if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) && | |
| 267 | + len === 1) { | |
| 268 | + // 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中 | |
| 269 | + return `${this.startText}/${this.endText}` | |
| 270 | + } else if (this.dateSame(date, this.selected[0])) { | |
| 271 | + return this.startText | |
| 272 | + } else if (this.dateSame(date, this.selected[len])) { | |
| 273 | + return this.endText | |
| 274 | + } else { | |
| 275 | + return bottomInfo | |
| 276 | + } | |
| 277 | + } | |
| 278 | + } else { | |
| 279 | + return bottomInfo | |
| 280 | + } | |
| 281 | + } | |
| 282 | + } | |
| 283 | + }, | |
| 284 | + mounted() { | |
| 285 | + this.init() | |
| 286 | + }, | |
| 287 | + methods: { | |
| 288 | + init() { | |
| 289 | + // 初始化默认选中 | |
| 290 | + this.$emit('monthSelected', this.selected) | |
| 291 | + this.$nextTick(() => { | |
| 292 | + // 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度 | |
| 293 | + // 因为nvue下,$nextTick并不是100%可靠的 | |
| 294 | + sleep(10).then(() => { | |
| 295 | + this.getWrapperWidth() | |
| 296 | + this.getMonthRect() | |
| 297 | + }) | |
| 298 | + }) | |
| 299 | + }, | |
| 300 | + // 判断两个日期是否相等 | |
| 301 | + dateSame(date1, date2) { | |
| 302 | + return dayjs(date1).isSame(dayjs(date2)) | |
| 303 | + }, | |
| 304 | + // 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度 | |
| 305 | + getWrapperWidth() { | |
| 306 | + // #ifdef APP-NVUE | |
| 307 | + dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => { | |
| 308 | + this.width = res.size.width | |
| 309 | + }) | |
| 310 | + // #endif | |
| 311 | + // #ifndef APP-NVUE | |
| 312 | + this.$uGetRect('.u-calendar-month-wrapper').then(size => { | |
| 313 | + this.width = size.width | |
| 314 | + }) | |
| 315 | + // #endif | |
| 316 | + }, | |
| 317 | + getMonthRect() { | |
| 318 | + // 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份 | |
| 319 | + const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise( | |
| 320 | + `u-calendar-month-${index}`)) | |
| 321 | + // 一次性返回 | |
| 322 | + Promise.all(promiseAllArr).then( | |
| 323 | + sizes => { | |
| 324 | + let height = 1 | |
| 325 | + const topArr = [] | |
| 326 | + for (let i = 0; i < this.months.length; i++) { | |
| 327 | + // 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份 | |
| 328 | + topArr[i] = height | |
| 329 | + height += sizes[i].height | |
| 330 | + } | |
| 331 | + // 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出 | |
| 332 | + this.$emit('updateMonthTop', topArr) | |
| 333 | + }) | |
| 334 | + }, | |
| 335 | + // 获取每个月份区域的尺寸 | |
| 336 | + getMonthRectByPromise(el) { | |
| 337 | + // #ifndef APP-NVUE | |
| 338 | + // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://ijry.github.io/uview-plus/js/getRect.html | |
| 339 | + // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同 | |
| 340 | + return new Promise(resolve => { | |
| 341 | + this.$uGetRect(`.${el}`).then(size => { | |
| 342 | + resolve(size) | |
| 343 | + }) | |
| 344 | + }) | |
| 345 | + // #endif | |
| 346 | + | |
| 347 | + // #ifdef APP-NVUE | |
| 348 | + // nvue下,使用dom模块查询元素高度 | |
| 349 | + // 返回一个promise,让调用此方法的主体能使用then回调 | |
| 350 | + return new Promise(resolve => { | |
| 351 | + dom.getComponentRect(this.$refs[el][0], res => { | |
| 352 | + resolve(res.size) | |
| 353 | + }) | |
| 354 | + }) | |
| 355 | + // #endif | |
| 356 | + }, | |
| 357 | + // 点击某一个日期 | |
| 358 | + clickHandler(index1, index2, item) { | |
| 359 | + if (this.readonly) { | |
| 360 | + return; | |
| 361 | + } | |
| 362 | + this.item = item | |
| 363 | + const date = dayjs(item.date).format("YYYY-MM-DD") | |
| 364 | + if (item.disabled) return | |
| 365 | + // 对上一次选择的日期数组进行深度克隆 | |
| 366 | + let selected = deepClone(this.selected) | |
| 367 | + if (this.mode === 'single') { | |
| 368 | + // 单选情况下,让数组中的元素为当前点击的日期 | |
| 369 | + selected = [date] | |
| 370 | + } else if (this.mode === 'multiple') { | |
| 371 | + if (selected.some(item => this.dateSame(item, date))) { | |
| 372 | + // 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果 | |
| 373 | + const itemIndex = selected.findIndex(item => item === date) | |
| 374 | + selected.splice(itemIndex, 1) | |
| 375 | + } else { | |
| 376 | + // 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去 | |
| 377 | + if (selected.length < this.maxCount) selected.push(date) | |
| 378 | + } | |
| 379 | + } else { | |
| 380 | + // 选择区间形式 | |
| 381 | + if (selected.length === 0 || selected.length >= 2) { | |
| 382 | + // 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期 | |
| 383 | + selected = [date] | |
| 384 | + } else if (selected.length === 1) { | |
| 385 | + // 如果已经选择了开始日期 | |
| 386 | + const existsDate = selected[0] | |
| 387 | + // 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期 | |
| 388 | + if (dayjs(date).isBefore(existsDate)) { | |
| 389 | + selected = [date] | |
| 390 | + } else if (dayjs(date).isAfter(existsDate)) { | |
| 391 | + // 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示 | |
| 392 | + if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) { | |
| 393 | + if(this.rangePrompt) { | |
| 394 | + toast(this.rangePrompt) | |
| 395 | + } else { | |
| 396 | + toast(`选择天数不能超过 ${this.maxRange} 天`) | |
| 397 | + } | |
| 398 | + return | |
| 399 | + } | |
| 400 | + // 如果当前日期大于已有日期,将当前的添加到数组尾部 | |
| 401 | + selected.push(date) | |
| 402 | + const startDate = selected[0] | |
| 403 | + const endDate = selected[1] | |
| 404 | + const arr = [] | |
| 405 | + let i = 0 | |
| 406 | + do { | |
| 407 | + // 将开始和结束日期之间的日期添加到数组中 | |
| 408 | + arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD")) | |
| 409 | + i++ | |
| 410 | + // 累加的日期小于结束日期时,继续下一次的循环 | |
| 411 | + } while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate))) | |
| 412 | + // 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来 | |
| 413 | + arr.push(endDate) | |
| 414 | + selected = arr | |
| 415 | + } else { | |
| 416 | + // 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己 | |
| 417 | + if (selected[0] === date && !this.allowSameDay) return | |
| 418 | + selected.push(date) | |
| 419 | + } | |
| 420 | + } | |
| 421 | + } | |
| 422 | + this.setSelected(selected) | |
| 423 | + }, | |
| 424 | + // 设置默认日期 | |
| 425 | + setDefaultDate() { | |
| 426 | + if (!this.defaultDate) { | |
| 427 | + // 如果没有设置默认日期,则将当天日期设置为默认选中的日期 | |
| 428 | + const selected = [dayjs().format("YYYY-MM-DD")] | |
| 429 | + return this.setSelected(selected, false) | |
| 430 | + } | |
| 431 | + let defaultDate = [] | |
| 432 | + const minDate = this.minDate || dayjs().format("YYYY-MM-DD") | |
| 433 | + const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD") | |
| 434 | + if (this.mode === 'single') { | |
| 435 | + // 单选模式,可以是字符串或数组,Date对象等 | |
| 436 | + if (!test.array(this.defaultDate)) { | |
| 437 | + defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")] | |
| 438 | + } else { | |
| 439 | + defaultDate = [this.defaultDate[0]] | |
| 440 | + } | |
| 441 | + } else { | |
| 442 | + // 如果为非数组,则不执行 | |
| 443 | + if (!test.array(this.defaultDate)) return | |
| 444 | + defaultDate = this.defaultDate | |
| 445 | + } | |
| 446 | + // 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素 | |
| 447 | + defaultDate = defaultDate.filter(item => { | |
| 448 | + return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs( | |
| 449 | + maxDate).add(1, 'day')) | |
| 450 | + }) | |
| 451 | + this.setSelected(defaultDate, false) | |
| 452 | + }, | |
| 453 | + setSelected(selected, event = true) { | |
| 454 | + this.selected = selected | |
| 455 | + event && this.$emit('monthSelected', this.selected,'tap') | |
| 456 | + } | |
| 457 | + } | |
| 458 | + } | |
| 459 | +</script> | |
| 460 | + | |
| 461 | +<style lang="scss" scoped> | |
| 462 | + @import "../../libs/css/components.scss"; | |
| 463 | + | |
| 464 | + .u-calendar-month-wrapper { | |
| 465 | + margin-top: 4px; | |
| 466 | + } | |
| 467 | + | |
| 468 | + .u-calendar-month { | |
| 469 | + | |
| 470 | + &__title { | |
| 471 | + display: flex; | |
| 472 | + flex-direction: column; | |
| 473 | + font-size: 14px; | |
| 474 | + line-height: 42px; | |
| 475 | + height: 42px; | |
| 476 | + color: $u-main-color; | |
| 477 | + text-align: center; | |
| 478 | + font-weight: bold; | |
| 479 | + } | |
| 480 | + | |
| 481 | + &__days { | |
| 482 | + position: relative; | |
| 483 | + @include flex; | |
| 484 | + flex-wrap: wrap; | |
| 485 | + | |
| 486 | + &__month-mark-wrapper { | |
| 487 | + position: absolute; | |
| 488 | + top: 0; | |
| 489 | + bottom: 0; | |
| 490 | + left: 0; | |
| 491 | + right: 0; | |
| 492 | + @include flex; | |
| 493 | + justify-content: center; | |
| 494 | + align-items: center; | |
| 495 | + | |
| 496 | + &__text { | |
| 497 | + font-size: 155px; | |
| 498 | + color: rgba(231, 232, 234, 0.83); | |
| 499 | + } | |
| 500 | + } | |
| 501 | + | |
| 502 | + &__day { | |
| 503 | + @include flex; | |
| 504 | + padding: 2px; | |
| 505 | + /* #ifndef APP-NVUE */ | |
| 506 | + // vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移 | |
| 507 | + width: calc(100% / 7); | |
| 508 | + box-sizing: border-box; | |
| 509 | + /* #endif */ | |
| 510 | + | |
| 511 | + &__select { | |
| 512 | + flex: 1; | |
| 513 | + @include flex; | |
| 514 | + align-items: center; | |
| 515 | + justify-content: center; | |
| 516 | + position: relative; | |
| 517 | + | |
| 518 | + &__dot { | |
| 519 | + width: 7px; | |
| 520 | + height: 7px; | |
| 521 | + border-radius: 100px; | |
| 522 | + background-color: $u-error; | |
| 523 | + position: absolute; | |
| 524 | + top: 12px; | |
| 525 | + right: 7px; | |
| 526 | + } | |
| 527 | + | |
| 528 | + &__buttom-info { | |
| 529 | + color: $u-content-color; | |
| 530 | + text-align: center; | |
| 531 | + position: absolute; | |
| 532 | + bottom: 5px; | |
| 533 | + font-size: 10px; | |
| 534 | + text-align: center; | |
| 535 | + left: 0; | |
| 536 | + right: 0; | |
| 537 | + | |
| 538 | + &--selected { | |
| 539 | + color: #ffffff; | |
| 540 | + } | |
| 541 | + | |
| 542 | + &--disabled { | |
| 543 | + color: #cacbcd; | |
| 544 | + } | |
| 545 | + } | |
| 546 | + | |
| 547 | + &__info { | |
| 548 | + text-align: center; | |
| 549 | + font-size: 16px; | |
| 550 | + | |
| 551 | + &--selected { | |
| 552 | + color: #ffffff; | |
| 553 | + } | |
| 554 | + | |
| 555 | + &--disabled { | |
| 556 | + color: #cacbcd; | |
| 557 | + } | |
| 558 | + } | |
| 559 | + | |
| 560 | + &--selected { | |
| 561 | + background-color: $u-primary; | |
| 562 | + @include flex; | |
| 563 | + justify-content: center; | |
| 564 | + align-items: center; | |
| 565 | + flex: 1; | |
| 566 | + border-radius: 3px; | |
| 567 | + } | |
| 568 | + | |
| 569 | + &--range-selected { | |
| 570 | + opacity: 0.3; | |
| 571 | + border-radius: 0; | |
| 572 | + } | |
| 573 | + | |
| 574 | + &--range-start-selected { | |
| 575 | + border-top-right-radius: 0; | |
| 576 | + border-bottom-right-radius: 0; | |
| 577 | + } | |
| 578 | + | |
| 579 | + &--range-end-selected { | |
| 580 | + border-top-left-radius: 0; | |
| 581 | + border-bottom-left-radius: 0; | |
| 582 | + } | |
| 583 | + } | |
| 584 | + } | |
| 585 | + } | |
| 586 | + } | |
| 587 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-calendar/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | + | |
| 4 | +export const props = defineMixin({ | |
| 5 | + props: { | |
| 6 | + // 日历顶部标题 | |
| 7 | + title: { | |
| 8 | + type: String, | |
| 9 | + default: () => defProps.calendar.title | |
| 10 | + }, | |
| 11 | + // 是否显示标题 | |
| 12 | + showTitle: { | |
| 13 | + type: Boolean, | |
| 14 | + default: () => defProps.calendar.showTitle | |
| 15 | + }, | |
| 16 | + // 是否显示副标题 | |
| 17 | + showSubtitle: { | |
| 18 | + type: Boolean, | |
| 19 | + default: () => defProps.calendar.showSubtitle | |
| 20 | + }, | |
| 21 | + // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 | |
| 22 | + mode: { | |
| 23 | + type: String, | |
| 24 | + default: () => defProps.calendar.mode | |
| 25 | + }, | |
| 26 | + // mode=range时,第一个日期底部的提示文字 | |
| 27 | + startText: { | |
| 28 | + type: String, | |
| 29 | + default: () => defProps.calendar.startText | |
| 30 | + }, | |
| 31 | + // mode=range时,最后一个日期底部的提示文字 | |
| 32 | + endText: { | |
| 33 | + type: String, | |
| 34 | + default: () => defProps.calendar.endText | |
| 35 | + }, | |
| 36 | + // 自定义列表 | |
| 37 | + customList: { | |
| 38 | + type: Array, | |
| 39 | + default: () => defProps.calendar.customList | |
| 40 | + }, | |
| 41 | + // 主题色,对底部按钮和选中日期有效 | |
| 42 | + color: { | |
| 43 | + type: String, | |
| 44 | + default: () => defProps.calendar.color | |
| 45 | + }, | |
| 46 | + // 最小的可选日期 | |
| 47 | + minDate: { | |
| 48 | + type: [String, Number], | |
| 49 | + default: () => defProps.calendar.minDate | |
| 50 | + }, | |
| 51 | + // 最大可选日期 | |
| 52 | + maxDate: { | |
| 53 | + type: [String, Number], | |
| 54 | + default: () => defProps.calendar.maxDate | |
| 55 | + }, | |
| 56 | + // 默认选中的日期,mode为multiple或range是必须为数组格式 | |
| 57 | + defaultDate: { | |
| 58 | + type: [Array, String, Date, null], | |
| 59 | + default: () => defProps.calendar.defaultDate | |
| 60 | + }, | |
| 61 | + // mode=multiple时,最多可选多少个日期 | |
| 62 | + maxCount: { | |
| 63 | + type: [String, Number], | |
| 64 | + default: () => defProps.calendar.maxCount | |
| 65 | + }, | |
| 66 | + // 日期行高 | |
| 67 | + rowHeight: { | |
| 68 | + type: [String, Number], | |
| 69 | + default: () => defProps.calendar.rowHeight | |
| 70 | + }, | |
| 71 | + // 日期格式化函数 | |
| 72 | + formatter: { | |
| 73 | + type: [Function, null], | |
| 74 | + default: () => defProps.calendar.formatter | |
| 75 | + }, | |
| 76 | + // 是否显示农历 | |
| 77 | + showLunar: { | |
| 78 | + type: Boolean, | |
| 79 | + default: () => defProps.calendar.showLunar | |
| 80 | + }, | |
| 81 | + // 是否显示月份背景色 | |
| 82 | + showMark: { | |
| 83 | + type: Boolean, | |
| 84 | + default: () => defProps.calendar.showMark | |
| 85 | + }, | |
| 86 | + // 确定按钮的文字 | |
| 87 | + confirmText: { | |
| 88 | + type: String, | |
| 89 | + default: () => defProps.calendar.confirmText | |
| 90 | + }, | |
| 91 | + // 确认按钮处于禁用状态时的文字 | |
| 92 | + confirmDisabledText: { | |
| 93 | + type: String, | |
| 94 | + default: () => defProps.calendar.confirmDisabledText | |
| 95 | + }, | |
| 96 | + // 是否显示日历弹窗 | |
| 97 | + show: { | |
| 98 | + type: Boolean, | |
| 99 | + default: () => defProps.calendar.show | |
| 100 | + }, | |
| 101 | + // 是否允许点击遮罩关闭日历 | |
| 102 | + closeOnClickOverlay: { | |
| 103 | + type: Boolean, | |
| 104 | + default: () => defProps.calendar.closeOnClickOverlay | |
| 105 | + }, | |
| 106 | + // 是否为只读状态,只读状态下禁止选择日期 | |
| 107 | + readonly: { | |
| 108 | + type: Boolean, | |
| 109 | + default: () => defProps.calendar.readonly | |
| 110 | + }, | |
| 111 | + // 是否展示确认按钮 | |
| 112 | + showConfirm: { | |
| 113 | + type: Boolean, | |
| 114 | + default: () => defProps.calendar.showConfirm | |
| 115 | + }, | |
| 116 | + // 日期区间最多可选天数,默认无限制,mode = range时有效 | |
| 117 | + maxRange: { | |
| 118 | + type: [Number, String], | |
| 119 | + default: () => defProps.calendar.maxRange | |
| 120 | + }, | |
| 121 | + // 范围选择超过最多可选天数时的提示文案,mode = range时有效 | |
| 122 | + rangePrompt: { | |
| 123 | + type: String, | |
| 124 | + default: () => defProps.calendar.rangePrompt | |
| 125 | + }, | |
| 126 | + // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 | |
| 127 | + showRangePrompt: { | |
| 128 | + type: Boolean, | |
| 129 | + default: () => defProps.calendar.showRangePrompt | |
| 130 | + }, | |
| 131 | + // 是否允许日期范围的起止时间为同一天,mode = range时有效 | |
| 132 | + allowSameDay: { | |
| 133 | + type: Boolean, | |
| 134 | + default: () => defProps.calendar.allowSameDay | |
| 135 | + }, | |
| 136 | + // 圆角值 | |
| 137 | + round: { | |
| 138 | + type: [Boolean, String, Number], | |
| 139 | + default: () => defProps.calendar.round | |
| 140 | + }, | |
| 141 | + // 最多展示月份数量 | |
| 142 | + monthNum: { | |
| 143 | + type: [Number, String], | |
| 144 | + default: 3 | |
| 145 | + } | |
| 146 | + } | |
| 147 | +}) | ... | ... |
uni_modules/uview-plus/components/u-calendar/u-calendar.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <u-popup | |
| 3 | + :show="show" | |
| 4 | + mode="bottom" | |
| 5 | + closeable | |
| 6 | + @close="close" | |
| 7 | + :round="round" | |
| 8 | + :closeOnClickOverlay="closeOnClickOverlay" | |
| 9 | + > | |
| 10 | + <view class="u-calendar"> | |
| 11 | + <uHeader | |
| 12 | + :title="title" | |
| 13 | + :subtitle="subtitle" | |
| 14 | + :showSubtitle="showSubtitle" | |
| 15 | + :showTitle="showTitle" | |
| 16 | + ></uHeader> | |
| 17 | + <scroll-view | |
| 18 | + :style="{ | |
| 19 | + height: addUnit(listHeight) | |
| 20 | + }" | |
| 21 | + scroll-y | |
| 22 | + @scroll="onScroll" | |
| 23 | + :scroll-top="scrollTop" | |
| 24 | + :scrollIntoView="scrollIntoView" | |
| 25 | + > | |
| 26 | + <uMonth | |
| 27 | + :color="color" | |
| 28 | + :rowHeight="rowHeight" | |
| 29 | + :showMark="showMark" | |
| 30 | + :months="months" | |
| 31 | + :mode="mode" | |
| 32 | + :maxCount="maxCount" | |
| 33 | + :startText="startText" | |
| 34 | + :endText="endText" | |
| 35 | + :defaultDate="defaultDate" | |
| 36 | + :minDate="innerMinDate" | |
| 37 | + :maxDate="innerMaxDate" | |
| 38 | + :maxMonth="monthNum" | |
| 39 | + :readonly="readonly" | |
| 40 | + :maxRange="maxRange" | |
| 41 | + :rangePrompt="rangePrompt" | |
| 42 | + :showRangePrompt="showRangePrompt" | |
| 43 | + :allowSameDay="allowSameDay" | |
| 44 | + ref="month" | |
| 45 | + @monthSelected="monthSelected" | |
| 46 | + @updateMonthTop="updateMonthTop" | |
| 47 | + ></uMonth> | |
| 48 | + </scroll-view> | |
| 49 | + <slot name="footer" v-if="showConfirm"> | |
| 50 | + <view class="u-calendar__confirm"> | |
| 51 | + <u-button | |
| 52 | + shape="circle" | |
| 53 | + :text=" | |
| 54 | + buttonDisabled ? confirmDisabledText : confirmText | |
| 55 | + " | |
| 56 | + :color="color" | |
| 57 | + @click="confirm" | |
| 58 | + :disabled="buttonDisabled" | |
| 59 | + ></u-button> | |
| 60 | + </view> | |
| 61 | + </slot> | |
| 62 | + </view> | |
| 63 | + </u-popup> | |
| 64 | +</template> | |
| 65 | + | |
| 66 | +<script> | |
| 67 | +import uHeader from './header.vue' | |
| 68 | +import uMonth from './month.vue' | |
| 69 | +import { props } from './props.js' | |
| 70 | +import util from './util.js' | |
| 71 | +import dayjs from 'dayjs/esm/index' | |
| 72 | +import Calendar from '../../libs/util/calendar.js' | |
| 73 | +import { mpMixin } from '../../libs/mixin/mpMixin.js' | |
| 74 | +import { mixin } from '../../libs/mixin/mixin.js' | |
| 75 | +import { addUnit, range, error, padZero } from '../../libs/function/index'; | |
| 76 | +import test from '../../libs/function/test'; | |
| 77 | +/** | |
| 78 | + * Calendar 日历 | |
| 79 | + * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中. | |
| 80 | + * @tutorial https://ijry.github.io/uview-plus/components/calendar.html | |
| 81 | + * | |
| 82 | + * @property {String} title 标题内容 (默认 日期选择 ) | |
| 83 | + * @property {Boolean} showTitle 是否显示标题 (默认 true ) | |
| 84 | + * @property {Boolean} showSubtitle 是否显示副标题 (默认 true ) | |
| 85 | + * @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' ) | |
| 86 | + * @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' ) | |
| 87 | + * @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' ) | |
| 88 | + * @property {Array} customList 自定义列表 | |
| 89 | + * @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' ) | |
| 90 | + * @property {String | Number} minDate 最小的可选日期 (默认 0 ) | |
| 91 | + * @property {String | Number} maxDate 最大可选日期 (默认 0 ) | |
| 92 | + * @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式 | |
| 93 | + * @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER ) | |
| 94 | + * @property {String | Number} rowHeight 日期行高 (默认 56 ) | |
| 95 | + * @property {Function} formatter 日期格式化函数 | |
| 96 | + * @property {Boolean} showLunar 是否显示农历 (默认 false ) | |
| 97 | + * @property {Boolean} showMark 是否显示月份背景色 (默认 true ) | |
| 98 | + * @property {String} confirmText 确定按钮的文字 (默认 '确定' ) | |
| 99 | + * @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' ) | |
| 100 | + * @property {Boolean} show 是否显示日历弹窗 (默认 false ) | |
| 101 | + * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false ) | |
| 102 | + * @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false ) | |
| 103 | + * @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效 | |
| 104 | + * @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效 | |
| 105 | + * @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true ) | |
| 106 | + * @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false ) | |
| 107 | + * @property {Number|String} round 圆角值,默认无圆角 (默认 0 ) | |
| 108 | + * @property {Number|String} monthNum 最多展示的月份数量 (默认 3 ) | |
| 109 | + * | |
| 110 | + * @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数 | |
| 111 | + * @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件 | |
| 112 | + * @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm"> | |
| 113 | + </u-calendar> | |
| 114 | + * */ | |
| 115 | +export default { | |
| 116 | + name: 'u-calendar', | |
| 117 | + mixins: [mpMixin, mixin, props], | |
| 118 | + components: { | |
| 119 | + uHeader, | |
| 120 | + uMonth | |
| 121 | + }, | |
| 122 | + data() { | |
| 123 | + return { | |
| 124 | + // 需要显示的月份的数组 | |
| 125 | + months: [], | |
| 126 | + // 在月份滚动区域中,当前视图中月份的index索引 | |
| 127 | + monthIndex: 0, | |
| 128 | + // 月份滚动区域的高度 | |
| 129 | + listHeight: 0, | |
| 130 | + // month组件中选择的日期数组 | |
| 131 | + selected: [], | |
| 132 | + scrollIntoView: '', | |
| 133 | + scrollIntoViewScroll: '', | |
| 134 | + scrollTop:0, | |
| 135 | + // 过滤处理方法 | |
| 136 | + innerFormatter: (value) => value | |
| 137 | + } | |
| 138 | + }, | |
| 139 | + watch: { | |
| 140 | + scrollIntoView: { | |
| 141 | + immediate: true, | |
| 142 | + handler(n) { | |
| 143 | + // console.log('scrollIntoView', n) | |
| 144 | + } | |
| 145 | + }, | |
| 146 | + selectedChange: { | |
| 147 | + immediate: true, | |
| 148 | + handler(n) { | |
| 149 | + this.setMonth() | |
| 150 | + } | |
| 151 | + }, | |
| 152 | + // 打开弹窗时,设置月份数据 | |
| 153 | + show: { | |
| 154 | + immediate: true, | |
| 155 | + handler(n) { | |
| 156 | + if (n) { | |
| 157 | + this.setMonth() | |
| 158 | + } else { | |
| 159 | + // 关闭时重置scrollIntoView,否则会出现二次打开日历,当前月份数据显示不正确。 | |
| 160 | + // scrollIntoView需要有一个值变动过程,才会产生作用。 | |
| 161 | + this.scrollIntoView = '' | |
| 162 | + } | |
| 163 | + } | |
| 164 | + } | |
| 165 | + }, | |
| 166 | + computed: { | |
| 167 | + // 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理 | |
| 168 | + innerMaxDate() { | |
| 169 | + return test.number(this.maxDate) | |
| 170 | + ? Number(this.maxDate) | |
| 171 | + : this.maxDate | |
| 172 | + }, | |
| 173 | + innerMinDate() { | |
| 174 | + return test.number(this.minDate) | |
| 175 | + ? Number(this.minDate) | |
| 176 | + : this.minDate | |
| 177 | + }, | |
| 178 | + // 多个条件的变化,会引起选中日期的变化,这里统一管理监听 | |
| 179 | + selectedChange() { | |
| 180 | + return [this.innerMinDate, this.innerMaxDate, this.defaultDate] | |
| 181 | + }, | |
| 182 | + subtitle() { | |
| 183 | + // 初始化时,this.months为空数组,所以需要特别判断处理 | |
| 184 | + if (this.months.length) { | |
| 185 | + return `${this.months[this.monthIndex].year}年${ | |
| 186 | + this.months[this.monthIndex].month | |
| 187 | + }月` | |
| 188 | + } else { | |
| 189 | + return '' | |
| 190 | + } | |
| 191 | + }, | |
| 192 | + buttonDisabled() { | |
| 193 | + // 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态 | |
| 194 | + if (this.mode === 'range') { | |
| 195 | + if (this.selected.length <= 1) { | |
| 196 | + return true | |
| 197 | + } else { | |
| 198 | + return false | |
| 199 | + } | |
| 200 | + } else { | |
| 201 | + return false | |
| 202 | + } | |
| 203 | + } | |
| 204 | + }, | |
| 205 | + mounted() { | |
| 206 | + this.start = Date.now() | |
| 207 | + this.init() | |
| 208 | + }, | |
| 209 | + emits: ["confirm", "close"], | |
| 210 | + methods: { | |
| 211 | + addUnit, | |
| 212 | + // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用 | |
| 213 | + setFormatter(e) { | |
| 214 | + this.innerFormatter = e | |
| 215 | + }, | |
| 216 | + // month组件内部选择日期后,通过事件通知给父组件 | |
| 217 | + monthSelected(e,scene ='init') { | |
| 218 | + this.selected = e | |
| 219 | + if (!this.showConfirm) { | |
| 220 | + // 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还 | |
| 221 | + if ( | |
| 222 | + this.mode === 'multiple' || | |
| 223 | + this.mode === 'single' || | |
| 224 | + (this.mode === 'range' && this.selected.length >= 2) | |
| 225 | + ) { | |
| 226 | + if( scene === 'init'){ | |
| 227 | + return | |
| 228 | + } | |
| 229 | + if( scene === 'tap') { | |
| 230 | + this.$emit('confirm', this.selected) | |
| 231 | + } | |
| 232 | + } | |
| 233 | + } | |
| 234 | + }, | |
| 235 | + init() { | |
| 236 | + // 校验maxDate,不能小于minDate。 | |
| 237 | + if ( | |
| 238 | + this.innerMaxDate && | |
| 239 | + this.innerMinDate && | |
| 240 | + new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime() | |
| 241 | + ) { | |
| 242 | + return error('maxDate不能小于minDate时间') | |
| 243 | + } | |
| 244 | + // 滚动区域的高度 | |
| 245 | + this.listHeight = this.rowHeight * 5 + 30 | |
| 246 | + this.setMonth() | |
| 247 | + }, | |
| 248 | + close() { | |
| 249 | + this.$emit('close') | |
| 250 | + }, | |
| 251 | + // 点击确定按钮 | |
| 252 | + confirm() { | |
| 253 | + if (!this.buttonDisabled) { | |
| 254 | + this.$emit('confirm', this.selected) | |
| 255 | + } | |
| 256 | + }, | |
| 257 | + // 获得两个日期之间的月份数 | |
| 258 | + getMonths(minDate, maxDate) { | |
| 259 | + const minYear = dayjs(minDate).year() | |
| 260 | + const minMonth = dayjs(minDate).month() + 1 | |
| 261 | + const maxYear = dayjs(maxDate).year() | |
| 262 | + const maxMonth = dayjs(maxDate).month() + 1 | |
| 263 | + return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1 | |
| 264 | + }, | |
| 265 | + // 设置月份数据 | |
| 266 | + setMonth() { | |
| 267 | + // 最小日期的毫秒数 | |
| 268 | + const minDate = this.innerMinDate || dayjs().valueOf() | |
| 269 | + // 如果没有指定最大日期,则往后推3个月 | |
| 270 | + const maxDate = | |
| 271 | + this.innerMaxDate || | |
| 272 | + dayjs(minDate) | |
| 273 | + .add(this.monthNum - 1, 'month') | |
| 274 | + .valueOf() | |
| 275 | + // 最大最小月份之间的共有多少个月份, | |
| 276 | + const months = range( | |
| 277 | + 1, | |
| 278 | + this.monthNum, | |
| 279 | + this.getMonths(minDate, maxDate) | |
| 280 | + ) | |
| 281 | + // 先清空数组 | |
| 282 | + this.months = [] | |
| 283 | + for (let i = 0; i < months; i++) { | |
| 284 | + this.months.push({ | |
| 285 | + date: new Array( | |
| 286 | + dayjs(minDate).add(i, 'month').daysInMonth() | |
| 287 | + ) | |
| 288 | + .fill(1) | |
| 289 | + .map((item, index) => { | |
| 290 | + // 日期,取值1-31 | |
| 291 | + let day = index + 1 | |
| 292 | + // 星期,0-6,0为周日 | |
| 293 | + const week = dayjs(minDate) | |
| 294 | + .add(i, 'month') | |
| 295 | + .date(day) | |
| 296 | + .day() | |
| 297 | + const date = dayjs(minDate) | |
| 298 | + .add(i, 'month') | |
| 299 | + .date(day) | |
| 300 | + .format('YYYY-MM-DD') | |
| 301 | + let bottomInfo = '' | |
| 302 | + if (this.showLunar) { | |
| 303 | + // 将日期转为农历格式 | |
| 304 | + const lunar = Calendar.solar2lunar( | |
| 305 | + dayjs(date).year(), | |
| 306 | + dayjs(date).month() + 1, | |
| 307 | + dayjs(date).date() | |
| 308 | + ) | |
| 309 | + bottomInfo = lunar.IDayCn | |
| 310 | + } | |
| 311 | + let config = { | |
| 312 | + day, | |
| 313 | + week, | |
| 314 | + // 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态 | |
| 315 | + disabled: | |
| 316 | + dayjs(date).isBefore( | |
| 317 | + dayjs(minDate).format('YYYY-MM-DD') | |
| 318 | + ) || | |
| 319 | + dayjs(date).isAfter( | |
| 320 | + dayjs(maxDate).format('YYYY-MM-DD') | |
| 321 | + ), | |
| 322 | + // 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理 | |
| 323 | + date: new Date(date), | |
| 324 | + bottomInfo, | |
| 325 | + dot: false, | |
| 326 | + month: | |
| 327 | + dayjs(minDate).add(i, 'month').month() + 1 | |
| 328 | + } | |
| 329 | + const formatter = | |
| 330 | + this.formatter || this.innerFormatter | |
| 331 | + return formatter(config) | |
| 332 | + }), | |
| 333 | + // 当前所属的月份 | |
| 334 | + month: dayjs(minDate).add(i, 'month').month() + 1, | |
| 335 | + // 当前年份 | |
| 336 | + year: dayjs(minDate).add(i, 'month').year() | |
| 337 | + }) | |
| 338 | + } | |
| 339 | + }, | |
| 340 | + // 滚动到默认设置的月份 | |
| 341 | + scrollIntoDefaultMonth(selected) { | |
| 342 | + // 查询默认日期在可选列表的下标 | |
| 343 | + const _index = this.months.findIndex(({ | |
| 344 | + year, | |
| 345 | + month | |
| 346 | + }) => { | |
| 347 | + month = padZero(month) | |
| 348 | + return `${year}-${month}` === selected | |
| 349 | + }) | |
| 350 | + if (_index !== -1) { | |
| 351 | + // #ifndef MP-WEIXIN | |
| 352 | + this.$nextTick(() => { | |
| 353 | + this.scrollIntoView = `month-${_index}` | |
| 354 | + this.scrollIntoViewScroll = this.scrollIntoView | |
| 355 | + }) | |
| 356 | + // #endif | |
| 357 | + // #ifdef MP-WEIXIN | |
| 358 | + this.scrollTop = this.months[_index].top || 0; | |
| 359 | + // #endif | |
| 360 | + } | |
| 361 | + }, | |
| 362 | + // scroll-view滚动监听 | |
| 363 | + onScroll(event) { | |
| 364 | + // 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值 | |
| 365 | + const scrollTop = Math.max(0, event.detail.scrollTop) | |
| 366 | + // 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引 | |
| 367 | + for (let i = 0; i < this.months.length; i++) { | |
| 368 | + if (scrollTop >= (this.months[i].top || this.listHeight)) { | |
| 369 | + this.monthIndex = i | |
| 370 | + this.scrollIntoViewScroll = `month-${i}` | |
| 371 | + } | |
| 372 | + } | |
| 373 | + }, | |
| 374 | + // 更新月份的top值 | |
| 375 | + updateMonthTop(topArr = []) { | |
| 376 | + // 设置对应月份的top值,用于onScroll方法更新月份 | |
| 377 | + topArr.map((item, index) => { | |
| 378 | + this.months[index].top = item | |
| 379 | + }) | |
| 380 | + | |
| 381 | + // 获取默认日期的下标 | |
| 382 | + if (!this.defaultDate) { | |
| 383 | + // 如果没有设置默认日期,则将当天日期设置为默认选中的日期 | |
| 384 | + const selected = dayjs().format("YYYY-MM") | |
| 385 | + this.scrollIntoDefaultMonth(selected) | |
| 386 | + return | |
| 387 | + } | |
| 388 | + let selected = dayjs().format("YYYY-MM"); | |
| 389 | + // 单选模式,可以是字符串或数组,Date对象等 | |
| 390 | + if (!test.array(this.defaultDate)) { | |
| 391 | + selected = dayjs(this.defaultDate).format("YYYY-MM") | |
| 392 | + } else { | |
| 393 | + selected = dayjs(this.defaultDate[0]).format("YYYY-MM"); | |
| 394 | + } | |
| 395 | + this.scrollIntoDefaultMonth(selected) | |
| 396 | + } | |
| 397 | + } | |
| 398 | +} | |
| 399 | +</script> | |
| 400 | + | |
| 401 | +<style lang="scss" scoped> | |
| 402 | +@import '../../libs/css/components.scss'; | |
| 403 | + | |
| 404 | +.u-calendar { | |
| 405 | + &__confirm { | |
| 406 | + padding: 7px 18px; | |
| 407 | + } | |
| 408 | +} | |
| 409 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-calendar/util.js
0 → 100644
| 1 | +import dayjs from 'dayjs/esm/index' | |
| 2 | +export default { | |
| 3 | + methods: { | |
| 4 | + // 设置月份数据 | |
| 5 | + setMonth() { | |
| 6 | + // 月初是周几 | |
| 7 | + const day = dayjs(this.date).date(1).day() | |
| 8 | + const start = day == 0 ? 6 : day - 1 | |
| 9 | + | |
| 10 | + // 本月天数 | |
| 11 | + const days = dayjs(this.date).endOf('month').format('D') | |
| 12 | + | |
| 13 | + // 上个月天数 | |
| 14 | + const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D') | |
| 15 | + | |
| 16 | + // 日期数据 | |
| 17 | + const arr = [] | |
| 18 | + // 清空表格 | |
| 19 | + this.month = [] | |
| 20 | + | |
| 21 | + // 添加上月数据 | |
| 22 | + arr.push( | |
| 23 | + ...new Array(start).fill(1).map((e, i) => { | |
| 24 | + const day = prevDays - start + i + 1 | |
| 25 | + | |
| 26 | + return { | |
| 27 | + value: day, | |
| 28 | + disabled: true, | |
| 29 | + date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD') | |
| 30 | + } | |
| 31 | + }) | |
| 32 | + ) | |
| 33 | + | |
| 34 | + // 添加本月数据 | |
| 35 | + arr.push( | |
| 36 | + ...new Array(days - 0).fill(1).map((e, i) => { | |
| 37 | + const day = i + 1 | |
| 38 | + | |
| 39 | + return { | |
| 40 | + value: day, | |
| 41 | + date: dayjs(this.date).date(day).format('YYYY-MM-DD') | |
| 42 | + } | |
| 43 | + }) | |
| 44 | + ) | |
| 45 | + | |
| 46 | + // 添加下个月 | |
| 47 | + arr.push( | |
| 48 | + ...new Array(42 - days - start).fill(1).map((e, i) => { | |
| 49 | + const day = i + 1 | |
| 50 | + | |
| 51 | + return { | |
| 52 | + value: day, | |
| 53 | + disabled: true, | |
| 54 | + date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD') | |
| 55 | + } | |
| 56 | + }) | |
| 57 | + ) | |
| 58 | + | |
| 59 | + // 分割数组 | |
| 60 | + for (let n = 0; n < arr.length; n += 7) { | |
| 61 | + this.month.push( | |
| 62 | + arr.slice(n, n + 7).map((e, i) => { | |
| 63 | + e.index = i + n | |
| 64 | + | |
| 65 | + // 自定义信息 | |
| 66 | + const custom = this.customList.find((c) => c.date == e.date) | |
| 67 | + | |
| 68 | + // 农历 | |
| 69 | + if (this.lunar) { | |
| 70 | + const { | |
| 71 | + IDayCn, | |
| 72 | + IMonthCn | |
| 73 | + } = this.getLunar(e.date) | |
| 74 | + e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn | |
| 75 | + } | |
| 76 | + | |
| 77 | + return { | |
| 78 | + ...e, | |
| 79 | + ...custom | |
| 80 | + } | |
| 81 | + }) | |
| 82 | + ) | |
| 83 | + } | |
| 84 | + } | |
| 85 | + } | |
| 86 | +} | ... | ... |
uni_modules/uview-plus/components/u-car-keyboard/carKeyboard.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:53:20 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/carKeyboard.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // 车牌号键盘 | |
| 12 | + carKeyboard: { | |
| 13 | + random: false | |
| 14 | + } | |
| 15 | +} | ... | ... |
uni_modules/uview-plus/components/u-car-keyboard/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | + | |
| 4 | +export const props = defineMixin({ | |
| 5 | + props: { | |
| 6 | + // 是否打乱键盘按键的顺序 | |
| 7 | + random: { | |
| 8 | + type: Boolean, | |
| 9 | + default: false | |
| 10 | + }, | |
| 11 | + // 输入一个中文后,是否自动切换到英文 | |
| 12 | + autoChange: { | |
| 13 | + type: Boolean, | |
| 14 | + default: false | |
| 15 | + } | |
| 16 | + } | |
| 17 | +}) | ... | ... |
uni_modules/uview-plus/components/u-car-keyboard/u-car-keyboard.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view | |
| 3 | + class="u-keyboard" | |
| 4 | + @touchmove.stop.prevent="noop" | |
| 5 | + > | |
| 6 | + <view | |
| 7 | + v-for="(group, i) in abc ? engKeyBoardList : areaList" | |
| 8 | + :key="i" | |
| 9 | + class="u-keyboard__button" | |
| 10 | + :index="i" | |
| 11 | + :class="[i + 1 === 4 && 'u-keyboard__button--center']" | |
| 12 | + > | |
| 13 | + <view | |
| 14 | + v-if="i === 3" | |
| 15 | + class="u-keyboard__button__inner-wrapper" | |
| 16 | + > | |
| 17 | + <view | |
| 18 | + class="u-keyboard__button__inner-wrapper__left" | |
| 19 | + hover-class="u-hover-class" | |
| 20 | + :hover-stay-time="200" | |
| 21 | + @tap="changeCarInputMode" | |
| 22 | + > | |
| 23 | + <text | |
| 24 | + class="u-keyboard__button__inner-wrapper__left__lang" | |
| 25 | + :class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']" | |
| 26 | + >中</text> | |
| 27 | + <text class="u-keyboard__button__inner-wrapper__left__line">/</text> | |
| 28 | + <text | |
| 29 | + class="u-keyboard__button__inner-wrapper__left__lang" | |
| 30 | + :class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']" | |
| 31 | + >英</text> | |
| 32 | + </view> | |
| 33 | + </view> | |
| 34 | + <view | |
| 35 | + class="u-keyboard__button__inner-wrapper" | |
| 36 | + v-for="(item, j) in group" | |
| 37 | + :key="j" | |
| 38 | + > | |
| 39 | + <view | |
| 40 | + class="u-keyboard__button__inner-wrapper__inner" | |
| 41 | + :hover-stay-time="200" | |
| 42 | + @tap="carInputClick(i, j)" | |
| 43 | + hover-class="u-hover-class" | |
| 44 | + > | |
| 45 | + <text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text> | |
| 46 | + </view> | |
| 47 | + </view> | |
| 48 | + <view | |
| 49 | + v-if="i === 3" | |
| 50 | + @touchstart="backspaceClick" | |
| 51 | + @touchend="clearTimer" | |
| 52 | + class="u-keyboard__button__inner-wrapper" | |
| 53 | + > | |
| 54 | + <view | |
| 55 | + class="u-keyboard__button__inner-wrapper__right" | |
| 56 | + hover-class="u-hover-class" | |
| 57 | + :hover-stay-time="200" | |
| 58 | + > | |
| 59 | + <u-icon | |
| 60 | + size="28" | |
| 61 | + name="backspace" | |
| 62 | + color="#303133" | |
| 63 | + ></u-icon> | |
| 64 | + </view> | |
| 65 | + </view> | |
| 66 | + </view> | |
| 67 | + </view> | |
| 68 | +</template> | |
| 69 | + | |
| 70 | +<script> | |
| 71 | + import { props } from './props'; | |
| 72 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 73 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 74 | + import { randomArray, sleep } from '../../libs/function/index'; | |
| 75 | + /** | |
| 76 | + * keyboard 键盘组件 | |
| 77 | + * @description 此为uview-plus自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。 | |
| 78 | + * @tutorial https://uview-plus.jiangruyi.com/components/keyboard.html | |
| 79 | + * @property {Boolean} random 是否打乱键盘的顺序 | |
| 80 | + * @event {Function} change 点击键盘触发 | |
| 81 | + * @event {Function} backspace 点击退格键触发 | |
| 82 | + * @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard> | |
| 83 | + */ | |
| 84 | + export default { | |
| 85 | + name: "u-car-keyboard", | |
| 86 | + mixins: [mpMixin, mixin, props], | |
| 87 | + data() { | |
| 88 | + return { | |
| 89 | + // 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称 | |
| 90 | + abc: false | |
| 91 | + }; | |
| 92 | + }, | |
| 93 | + computed: { | |
| 94 | + areaList() { | |
| 95 | + let data = [ | |
| 96 | + '京', | |
| 97 | + '沪', | |
| 98 | + '粤', | |
| 99 | + '津', | |
| 100 | + '冀', | |
| 101 | + '豫', | |
| 102 | + '云', | |
| 103 | + '辽', | |
| 104 | + '黑', | |
| 105 | + '湘', | |
| 106 | + '皖', | |
| 107 | + '鲁', | |
| 108 | + '苏', | |
| 109 | + '浙', | |
| 110 | + '赣', | |
| 111 | + '鄂', | |
| 112 | + '桂', | |
| 113 | + '甘', | |
| 114 | + '晋', | |
| 115 | + '陕', | |
| 116 | + '蒙', | |
| 117 | + '吉', | |
| 118 | + '闽', | |
| 119 | + '贵', | |
| 120 | + '渝', | |
| 121 | + '川', | |
| 122 | + '青', | |
| 123 | + '琼', | |
| 124 | + '宁', | |
| 125 | + '挂', | |
| 126 | + '藏', | |
| 127 | + '港', | |
| 128 | + '澳', | |
| 129 | + '新', | |
| 130 | + '使', | |
| 131 | + '学' | |
| 132 | + ]; | |
| 133 | + let tmp = []; | |
| 134 | + // 打乱顺序 | |
| 135 | + if (this.random) data = randomArray(data); | |
| 136 | + // 切割成二维数组 | |
| 137 | + tmp[0] = data.slice(0, 10); | |
| 138 | + tmp[1] = data.slice(10, 20); | |
| 139 | + tmp[2] = data.slice(20, 30); | |
| 140 | + tmp[3] = data.slice(30, 36); | |
| 141 | + return tmp; | |
| 142 | + }, | |
| 143 | + engKeyBoardList() { | |
| 144 | + let data = [ | |
| 145 | + 1, | |
| 146 | + 2, | |
| 147 | + 3, | |
| 148 | + 4, | |
| 149 | + 5, | |
| 150 | + 6, | |
| 151 | + 7, | |
| 152 | + 8, | |
| 153 | + 9, | |
| 154 | + 0, | |
| 155 | + 'Q', | |
| 156 | + 'W', | |
| 157 | + 'E', | |
| 158 | + 'R', | |
| 159 | + 'T', | |
| 160 | + 'Y', | |
| 161 | + 'U', | |
| 162 | + 'I', | |
| 163 | + 'O', | |
| 164 | + 'P', | |
| 165 | + 'A', | |
| 166 | + 'S', | |
| 167 | + 'D', | |
| 168 | + 'F', | |
| 169 | + 'G', | |
| 170 | + 'H', | |
| 171 | + 'J', | |
| 172 | + 'K', | |
| 173 | + 'L', | |
| 174 | + 'Z', | |
| 175 | + 'X', | |
| 176 | + 'C', | |
| 177 | + 'V', | |
| 178 | + 'B', | |
| 179 | + 'N', | |
| 180 | + 'M' | |
| 181 | + ]; | |
| 182 | + let tmp = []; | |
| 183 | + if (this.random) data = randomArray(data); | |
| 184 | + tmp[0] = data.slice(0, 10); | |
| 185 | + tmp[1] = data.slice(10, 20); | |
| 186 | + tmp[2] = data.slice(20, 30); | |
| 187 | + tmp[3] = data.slice(30, 36); | |
| 188 | + return tmp; | |
| 189 | + } | |
| 190 | + }, | |
| 191 | + emits: ["change", "backspace"], | |
| 192 | + methods: { | |
| 193 | + // 点击键盘按钮 | |
| 194 | + carInputClick(i, j) { | |
| 195 | + let value = ''; | |
| 196 | + // 不同模式,获取不同数组的值 | |
| 197 | + if (this.abc) value = this.engKeyBoardList[i][j]; | |
| 198 | + else value = this.areaList[i][j]; | |
| 199 | + // 如果允许自动切换,则将中文状态切换为英文 | |
| 200 | + if (!this.abc && this.autoChange) sleep(200).then(() => this.abc = true) | |
| 201 | + this.$emit('change', value); | |
| 202 | + }, | |
| 203 | + // 修改汽车牌键盘的输入模式,中文|英文 | |
| 204 | + changeCarInputMode() { | |
| 205 | + this.abc = !this.abc; | |
| 206 | + }, | |
| 207 | + // 点击退格键 | |
| 208 | + backspaceClick() { | |
| 209 | + this.$emit('backspace'); | |
| 210 | + clearInterval(this.timer); //再次清空定时器,防止重复注册定时器 | |
| 211 | + this.timer = null; | |
| 212 | + this.timer = setInterval(() => { | |
| 213 | + this.$emit('backspace'); | |
| 214 | + }, 250); | |
| 215 | + }, | |
| 216 | + clearTimer() { | |
| 217 | + clearInterval(this.timer); | |
| 218 | + this.timer = null; | |
| 219 | + }, | |
| 220 | + } | |
| 221 | + }; | |
| 222 | +</script> | |
| 223 | + | |
| 224 | +<style lang="scss" scoped> | |
| 225 | + @import "../../libs/css/components.scss"; | |
| 226 | + $u-car-keyboard-background-color: rgb(224, 228, 230) !default; | |
| 227 | + $u-car-keyboard-padding:6px 0 6px !default; | |
| 228 | + $u-car-keyboard-button-inner-width:64rpx !default; | |
| 229 | + $u-car-keyboard-button-inner-background-color:#FFFFFF !default; | |
| 230 | + $u-car-keyboard-button-height:80rpx !default; | |
| 231 | + $u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default; | |
| 232 | + $u-car-keyboard-button-border-radius:4px !default; | |
| 233 | + $u-car-keyboard-button-inner-margin:8rpx 5rpx !default; | |
| 234 | + $u-car-keyboard-button-text-font-size:16px !default; | |
| 235 | + $u-car-keyboard-button-text-color:$u-main-color !default; | |
| 236 | + $u-car-keyboard-center-inner-margin: 0 4rpx !default; | |
| 237 | + $u-car-keyboard-special-button-width:134rpx !default; | |
| 238 | + $u-car-keyboard-lang-font-size:16px !default; | |
| 239 | + $u-car-keyboard-lang-color:$u-main-color !default; | |
| 240 | + $u-car-keyboard-active-color:$u-primary !default; | |
| 241 | + $u-car-keyboard-line-font-size:15px !default; | |
| 242 | + $u-car-keyboard-line-color:$u-main-color !default; | |
| 243 | + $u-car-keyboard-line-margin:0 1px !default; | |
| 244 | + $u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default; | |
| 245 | + | |
| 246 | + .u-keyboard { | |
| 247 | + @include flex(column); | |
| 248 | + justify-content: space-around; | |
| 249 | + background-color: $u-car-keyboard-background-color; | |
| 250 | + align-items: stretch; | |
| 251 | + padding: $u-car-keyboard-padding; | |
| 252 | + | |
| 253 | + &__button { | |
| 254 | + @include flex; | |
| 255 | + justify-content: center; | |
| 256 | + flex: 1; | |
| 257 | + /* #ifndef APP-NVUE */ | |
| 258 | + /* #endif */ | |
| 259 | + | |
| 260 | + &__inner-wrapper { | |
| 261 | + box-shadow: $u-car-keyboard-button-inner-box-shadow; | |
| 262 | + margin: $u-car-keyboard-button-inner-margin; | |
| 263 | + border-radius: $u-car-keyboard-button-border-radius; | |
| 264 | + | |
| 265 | + &__inner { | |
| 266 | + @include flex; | |
| 267 | + justify-content: center; | |
| 268 | + align-items: center; | |
| 269 | + width: $u-car-keyboard-button-inner-width; | |
| 270 | + background-color: $u-car-keyboard-button-inner-background-color; | |
| 271 | + height: $u-car-keyboard-button-height; | |
| 272 | + border-radius: $u-car-keyboard-button-border-radius; | |
| 273 | + | |
| 274 | + &__text { | |
| 275 | + font-size: $u-car-keyboard-button-text-font-size; | |
| 276 | + color: $u-car-keyboard-button-text-color; | |
| 277 | + } | |
| 278 | + } | |
| 279 | + | |
| 280 | + &__left, | |
| 281 | + &__right { | |
| 282 | + border-radius: $u-car-keyboard-button-border-radius; | |
| 283 | + width: $u-car-keyboard-special-button-width; | |
| 284 | + height: $u-car-keyboard-button-height; | |
| 285 | + background-color: $u-car-keyboard-u-hover-class-background-color; | |
| 286 | + @include flex; | |
| 287 | + justify-content: center; | |
| 288 | + align-items: center; | |
| 289 | + box-shadow: $u-car-keyboard-button-inner-box-shadow; | |
| 290 | + } | |
| 291 | + | |
| 292 | + &__left { | |
| 293 | + &__line { | |
| 294 | + font-size: $u-car-keyboard-line-font-size; | |
| 295 | + color: $u-car-keyboard-line-color; | |
| 296 | + margin: $u-car-keyboard-line-margin; | |
| 297 | + } | |
| 298 | + | |
| 299 | + &__lang { | |
| 300 | + font-size: $u-car-keyboard-lang-font-size; | |
| 301 | + color: $u-car-keyboard-lang-color; | |
| 302 | + | |
| 303 | + &--active { | |
| 304 | + color: $u-car-keyboard-active-color; | |
| 305 | + } | |
| 306 | + } | |
| 307 | + } | |
| 308 | + } | |
| 309 | + } | |
| 310 | + } | |
| 311 | + | |
| 312 | + .u-hover-class { | |
| 313 | + background-color: $u-car-keyboard-u-hover-class-background-color; | |
| 314 | + } | |
| 315 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-card/card.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : jry | |
| 3 | + * @Description : | |
| 4 | + * @version : 3.0 | |
| 5 | + * @Date : 2025-04-26 16:37:21 | |
| 6 | + * @LastAuthor : jry | |
| 7 | + * @lastTime : 2025-04-26 16:37:21 | |
| 8 | + * @FilePath : /uview-plus/libs/config/props/card.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // card组件的props | |
| 12 | + card: { | |
| 13 | + full: false, | |
| 14 | + title: '', | |
| 15 | + titleColor: '#303133', | |
| 16 | + titleSize: '15px', | |
| 17 | + subTitle: '', | |
| 18 | + subTitleColor: '#909399', | |
| 19 | + subTitleSize: '13px', | |
| 20 | + border: true, | |
| 21 | + index: '', | |
| 22 | + margin: '15px', | |
| 23 | + borderRadius: '8px', | |
| 24 | + headStyle: {}, | |
| 25 | + bodyStyle: {}, | |
| 26 | + footStyle: {}, | |
| 27 | + headBorderBottom: true, | |
| 28 | + footBorderTop: true, | |
| 29 | + thumb: '', | |
| 30 | + thumbWidth: '30px', | |
| 31 | + thumbCircle: false, | |
| 32 | + padding: '15px', | |
| 33 | + paddingHead: '', | |
| 34 | + paddingBody: '', | |
| 35 | + paddingFoot: '', | |
| 36 | + showHead: true, | |
| 37 | + showFoot: true, | |
| 38 | + boxShadow: 'none' | |
| 39 | + } | |
| 40 | +} | ... | ... |
uni_modules/uview-plus/components/u-card/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | + | |
| 4 | +export const propsCard = defineMixin({ | |
| 5 | + props: { | |
| 6 | + // 与屏幕两侧是否留空隙 | |
| 7 | + full: { | |
| 8 | + type: Boolean, | |
| 9 | + default: false | |
| 10 | + }, | |
| 11 | + // 标题 | |
| 12 | + title: { | |
| 13 | + type: String, | |
| 14 | + default: '' | |
| 15 | + }, | |
| 16 | + // 标题颜色 | |
| 17 | + titleColor: { | |
| 18 | + type: String, | |
| 19 | + default: '#303133' | |
| 20 | + }, | |
| 21 | + // 标题字体大小 | |
| 22 | + titleSize: { | |
| 23 | + type: [Number, String], | |
| 24 | + default: '15px' | |
| 25 | + }, | |
| 26 | + // 副标题 | |
| 27 | + subTitle: { | |
| 28 | + type: String, | |
| 29 | + default: '' | |
| 30 | + }, | |
| 31 | + // 副标题颜色 | |
| 32 | + subTitleColor: { | |
| 33 | + type: String, | |
| 34 | + default: '#909399' | |
| 35 | + }, | |
| 36 | + // 副标题字体大小 | |
| 37 | + subTitleSize: { | |
| 38 | + type: [Number, String], | |
| 39 | + default: '13' | |
| 40 | + }, | |
| 41 | + // 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时) | |
| 42 | + border: { | |
| 43 | + type: Boolean, | |
| 44 | + default: true | |
| 45 | + }, | |
| 46 | + // 用于标识点击了第几个 | |
| 47 | + index: { | |
| 48 | + type: [Number, String, Object], | |
| 49 | + default: '' | |
| 50 | + }, | |
| 51 | + // 用于隔开上下左右的边距,带单位的写法,如:"30px 30px","20px 20px 30px 30px" | |
| 52 | + margin: { | |
| 53 | + type: String, | |
| 54 | + default: '15px' | |
| 55 | + }, | |
| 56 | + // card卡片的圆角 | |
| 57 | + borderRadius: { | |
| 58 | + type: [Number, String], | |
| 59 | + default: '8px' | |
| 60 | + }, | |
| 61 | + // 头部自定义样式,对象形式 | |
| 62 | + headStyle: { | |
| 63 | + type: Object, | |
| 64 | + default() { | |
| 65 | + return {}; | |
| 66 | + } | |
| 67 | + }, | |
| 68 | + // 主体自定义样式,对象形式 | |
| 69 | + bodyStyle: { | |
| 70 | + type: Object, | |
| 71 | + default() { | |
| 72 | + return {}; | |
| 73 | + } | |
| 74 | + }, | |
| 75 | + // 底部自定义样式,对象形式 | |
| 76 | + footStyle: { | |
| 77 | + type: Object, | |
| 78 | + default() { | |
| 79 | + return {}; | |
| 80 | + } | |
| 81 | + }, | |
| 82 | + // 头部是否下边框 | |
| 83 | + headBorderBottom: { | |
| 84 | + type: Boolean, | |
| 85 | + default: true | |
| 86 | + }, | |
| 87 | + // 底部是否有上边框 | |
| 88 | + footBorderTop: { | |
| 89 | + type: Boolean, | |
| 90 | + default: true | |
| 91 | + }, | |
| 92 | + // 标题左边的缩略图 | |
| 93 | + thumb: { | |
| 94 | + type: String, | |
| 95 | + default: '' | |
| 96 | + }, | |
| 97 | + // 缩略图宽高 | |
| 98 | + thumbWidth: { | |
| 99 | + type: [String, Number], | |
| 100 | + default: '30px' | |
| 101 | + }, | |
| 102 | + // 缩略图是否为圆形 | |
| 103 | + thumbCircle: { | |
| 104 | + type: Boolean, | |
| 105 | + default: false | |
| 106 | + }, | |
| 107 | + // 给head,body,foot的内边距 | |
| 108 | + padding: { | |
| 109 | + type: [String, Number], | |
| 110 | + default: '15px' | |
| 111 | + }, | |
| 112 | + paddingHead: { | |
| 113 | + type: [String, Number], | |
| 114 | + default: '' | |
| 115 | + }, | |
| 116 | + paddingBody: { | |
| 117 | + type: [String, Number], | |
| 118 | + default: '' | |
| 119 | + }, | |
| 120 | + paddingFoot: { | |
| 121 | + type: [String, Number], | |
| 122 | + default: '' | |
| 123 | + }, | |
| 124 | + // 是否显示头部 | |
| 125 | + showHead: { | |
| 126 | + type: Boolean, | |
| 127 | + default: true | |
| 128 | + }, | |
| 129 | + // 是否显示尾部 | |
| 130 | + showFoot: { | |
| 131 | + type: Boolean, | |
| 132 | + default: true | |
| 133 | + }, | |
| 134 | + // 卡片外围阴影,字符串形式 | |
| 135 | + boxShadow: { | |
| 136 | + type: String, | |
| 137 | + default: 'none' | |
| 138 | + } | |
| 139 | + } | |
| 140 | +}) | ... | ... |
uni_modules/uview-plus/components/u-card/u-card.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view | |
| 3 | + class="u-card" | |
| 4 | + @tap.stop="click" | |
| 5 | + :class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': getPx(borderRadius) > 0 }" | |
| 6 | + :style="{ | |
| 7 | + borderRadius: addUnit(borderRadius), | |
| 8 | + margin: margin, | |
| 9 | + boxShadow: boxShadow | |
| 10 | + }" | |
| 11 | + > | |
| 12 | + <view | |
| 13 | + v-if="showHead" | |
| 14 | + class="u-card__head" | |
| 15 | + :style="[{padding: addUnit(paddingHead || padding)}, headStyle]" | |
| 16 | + :class="{ | |
| 17 | + 'u-border-bottom': headBorderBottom | |
| 18 | + }" | |
| 19 | + @tap="headClick" | |
| 20 | + > | |
| 21 | + <view v-if="!$slots.head" class="u-flex u-row-between"> | |
| 22 | + <view class="u-card__head--left u-flex u-line-1" v-if="title"> | |
| 23 | + <image | |
| 24 | + :src="thumb" | |
| 25 | + class="u-card__head--left__thumb" | |
| 26 | + mode="aspectFill" | |
| 27 | + v-if="thumb" | |
| 28 | + :style="{ | |
| 29 | + height: addUnit(thumbWidth), | |
| 30 | + width: addUnit(thumbWidth), | |
| 31 | + borderRadius: thumbCircle ? '50px' : '4px' | |
| 32 | + }" | |
| 33 | + ></image> | |
| 34 | + <text | |
| 35 | + class="u-card__head--left__title u-line-1" | |
| 36 | + :style="{ | |
| 37 | + fontSize: addUnit(titleSize), | |
| 38 | + color: titleColor | |
| 39 | + }" | |
| 40 | + > | |
| 41 | + {{ title }} | |
| 42 | + </text> | |
| 43 | + </view> | |
| 44 | + <view class="u-card__head--right u-line-1" v-if="subTitle"> | |
| 45 | + <text | |
| 46 | + class="u-card__head__title__text" | |
| 47 | + :style="{ | |
| 48 | + fontSize: addUnit(subTitleSize), | |
| 49 | + color: subTitleColor | |
| 50 | + }" | |
| 51 | + > | |
| 52 | + {{ subTitle }} | |
| 53 | + </text> | |
| 54 | + </view> | |
| 55 | + </view> | |
| 56 | + <slot name="head" v-else /> | |
| 57 | + </view> | |
| 58 | + <view @tap="bodyClick" class="u-card__body" | |
| 59 | + :style="[{padding: addUnit(paddingBody || padding)}, bodyStyle]"><slot name="body" /></view> | |
| 60 | + <view | |
| 61 | + v-if="showFoot" | |
| 62 | + class="u-card__foot" | |
| 63 | + @tap="footClick" | |
| 64 | + :style="[{padding: $slots.foot ? addUnit(paddingFoot || padding) : 0}, footStyle]" | |
| 65 | + :class="{ | |
| 66 | + 'u-border-top': footBorderTop | |
| 67 | + }" | |
| 68 | + > | |
| 69 | + <slot name="foot" /> | |
| 70 | + </view> | |
| 71 | + </view> | |
| 72 | +</template> | |
| 73 | + | |
| 74 | +<script> | |
| 75 | + import { propsCard } from './props'; | |
| 76 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 77 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 78 | + import { addStyle, addUnit, getPx } from '../../libs/function/index'; | |
| 79 | + /** | |
| 80 | + * card 卡片 | |
| 81 | + * @description 卡片组件一般用于多个列表条目,且风格统一的场景 | |
| 82 | + * @tutorial https://uview-plus.jiangruyi.com/components/card.html | |
| 83 | + * @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false) | |
| 84 | + * @property {String} title 头部左边的标题 | |
| 85 | + * @property {String} title-color 标题颜色(默认#303133) | |
| 86 | + * @property {String | Number} title-size 标题字体大小,单位rpx(默认15px) | |
| 87 | + * @property {String} sub-title 头部右边的副标题 | |
| 88 | + * @property {String} sub-title-color 副标题颜色(默认#909399) | |
| 89 | + * @property {String | Number} sub-title-size 副标题字体大小(默认13px | |
| 90 | + * @property {Boolean} border 是否显示边框(默认true) | |
| 91 | + * @property {String | Number} index 用于标识点击了第几个卡片 | |
| 92 | + * @property {String} box-shadow 卡片外围阴影,字符串形式(默认none) | |
| 93 | + * @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30px 20px"(默认15px) | |
| 94 | + * @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认8px) | |
| 95 | + * @property {Object} head-style 头部自定义样式,对象形式 | |
| 96 | + * @property {Object} body-style 中部自定义样式,对象形式 | |
| 97 | + * @property {Object} foot-style 底部自定义样式,对象形式 | |
| 98 | + * @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true) | |
| 99 | + * @property {Boolean} foot-border-top 是否显示底部的上边框(默认true) | |
| 100 | + * @property {Boolean} show-head 是否显示头部(默认true) | |
| 101 | + * @property {Boolean} show-foot 是否显示尾部(默认true) | |
| 102 | + * @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径 | |
| 103 | + * @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位px(默认30px) | |
| 104 | + * @property {Boolean} thumb-circle 缩略图是否为圆形(默认false) | |
| 105 | + * @event {Function} click 整个卡片任意位置被点击时触发 | |
| 106 | + * @event {Function} head-click 卡片头部被点击时触发 | |
| 107 | + * @event {Function} body-click 卡片主体部分被点击时触发 | |
| 108 | + * @event {Function} foot-click 卡片底部部分被点击时触发 | |
| 109 | + * @example <u-card paddingFoot="2px 15px" title="card"></u-card> | |
| 110 | + */ | |
| 111 | + export default { | |
| 112 | + name: 'up-card', | |
| 113 | + data() { | |
| 114 | + return {}; | |
| 115 | + }, | |
| 116 | + mixins: [mpMixin, mixin, propsCard], | |
| 117 | + emits: ['click', 'head-click', 'body-click', 'foot-click'], | |
| 118 | + methods: { | |
| 119 | + addStyle, | |
| 120 | + addUnit, | |
| 121 | + getPx, | |
| 122 | + click() { | |
| 123 | + this.$emit('click', this.index); | |
| 124 | + }, | |
| 125 | + headClick() { | |
| 126 | + this.$emit('head-click', this.index); | |
| 127 | + }, | |
| 128 | + bodyClick() { | |
| 129 | + this.$emit('body-click', this.index); | |
| 130 | + }, | |
| 131 | + footClick() { | |
| 132 | + this.$emit('foot-click', this.index); | |
| 133 | + } | |
| 134 | + } | |
| 135 | + }; | |
| 136 | +</script> | |
| 137 | + | |
| 138 | +<style lang="scss" scoped> | |
| 139 | +@import "../../libs/css/components.scss"; | |
| 140 | + | |
| 141 | +.u-card { | |
| 142 | + position: relative; | |
| 143 | + overflow: hidden; | |
| 144 | + font-size: 28rpx; | |
| 145 | + background-color: #ffffff; | |
| 146 | + box-sizing: border-box; | |
| 147 | + | |
| 148 | + &-full { | |
| 149 | + // 如果是与屏幕之间不留空隙,应该设置左右边距为0 | |
| 150 | + margin-left: 0 !important; | |
| 151 | + margin-right: 0 !important; | |
| 152 | + width: 100%; | |
| 153 | + } | |
| 154 | + | |
| 155 | + &--border:after { | |
| 156 | + border-radius: 16rpx; | |
| 157 | + } | |
| 158 | + | |
| 159 | + &__head { | |
| 160 | + &--left { | |
| 161 | + color: $u-main-color; | |
| 162 | + | |
| 163 | + &__thumb { | |
| 164 | + margin-right: 16rpx; | |
| 165 | + } | |
| 166 | + | |
| 167 | + &__title { | |
| 168 | + max-width: 400rpx; | |
| 169 | + } | |
| 170 | + } | |
| 171 | + | |
| 172 | + &--right { | |
| 173 | + color: $u-tips-color; | |
| 174 | + margin-left: 6rpx; | |
| 175 | + } | |
| 176 | + } | |
| 177 | + | |
| 178 | + &__body { | |
| 179 | + color: $u-content-color; | |
| 180 | + } | |
| 181 | + | |
| 182 | + &__foot { | |
| 183 | + color: $u-tips-color; | |
| 184 | + } | |
| 185 | +} | |
| 186 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-cascader/u-cascader.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <up-popup :show="popupShow" mode="bottom" :popup="false" | |
| 3 | + :mask="true" :closeable="true" :safe-area-inset-bottom="true" | |
| 4 | + close-icon-color="#ffffff" :z-index="uZIndex" | |
| 5 | + :maskCloseAble="maskCloseAble" @close="close"> | |
| 6 | + <view class="up-p-t-30 up-p-l-20 up-m-b-10" v-if="headerDirection =='column'"> | |
| 7 | + <up-steps v-if="popupShow" dot direction="column" v-model:current="tabsIndex"> | |
| 8 | + <up-steps-item v-for="(item, index) in genTabsList" | |
| 9 | + @click="tabsIndex = index" :title="item.name"></up-steps-item> | |
| 10 | + </up-steps> | |
| 11 | + </view> | |
| 12 | + <view class="up-p-t-20 up-m-b-10" v-else> | |
| 13 | + <up-tabs v-if="popupShow" :list="genTabsList" | |
| 14 | + :scrollable="true" v-model:current="tabsIndex" @change="tabsChange" ref="tabs"></up-tabs> | |
| 15 | + </view> | |
| 16 | + <view class="area-box"> | |
| 17 | + <view class="u-flex" :class="{ 'change':isChange }" | |
| 18 | + :style="{transform: optionsCols == 2 && isChange ? 'translateX(-33.3333333%)' : ''}"> | |
| 19 | + <template v-for="(levelData, levelIndex) in levelList" :key="levelIndex"> | |
| 20 | + <view v-if="optionsCols == 2 || levelIndex == tabsIndex" class="area-item" | |
| 21 | + :style="{ width: optionsCols == 2 ? '33.33333%' : '750rpx'}"> | |
| 22 | + <view class="u-padding-10 u-bg-gray" style="height: 100%;"> | |
| 23 | + <scroll-view :scroll-y="true" style="height: 100%"> | |
| 24 | + <up-cell-group v-if="levelIndex === 0 || selectedValueIndexs[levelIndex - 1] !== undefined"> | |
| 25 | + <up-cell v-for="(item,index) in levelData" | |
| 26 | + :title="item[labelKey]" :arrow="false" | |
| 27 | + :index="index" :key="index" | |
| 28 | + @click="levelChange(levelIndex, index)"> | |
| 29 | + <template v-slot:right-icon> | |
| 30 | + <up-icon v-if="selectedValueIndexs[levelIndex] === index" | |
| 31 | + size="17" name="checkbox-mark"></up-icon> | |
| 32 | + </template> | |
| 33 | + </up-cell> | |
| 34 | + </up-cell-group> | |
| 35 | + </scroll-view> | |
| 36 | + </view> | |
| 37 | + </view> | |
| 38 | + </template> | |
| 39 | + </view> | |
| 40 | + </view> | |
| 41 | + <!-- 添加按钮区域 --> | |
| 42 | + <view class="u-cascader-action up-flex up-flex-between"> | |
| 43 | + <view class="u-padding-20 up-flex-fill"> | |
| 44 | + <up-button @click="handleCancel" type="default">{{ t("up.common.cancel") }}</up-button> | |
| 45 | + </view> | |
| 46 | + <view class="u-padding-20 up-flex-fill"> | |
| 47 | + <up-button @click="handleConfirm" type="primary">{{ t("up.common.confirm") }}</up-button> | |
| 48 | + </view> | |
| 49 | + </view> | |
| 50 | + </up-popup> | |
| 51 | +</template> | |
| 52 | + | |
| 53 | +<script> | |
| 54 | + /** | |
| 55 | + * u-cascader 通用无限级联选择器 | |
| 56 | + * @property {String Number} z-index 弹出时的z-index值(默认1075) | |
| 57 | + * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true) | |
| 58 | + * @property {Array} data 级联数据 | |
| 59 | + * @property {Array} default-value 默认选中的值 | |
| 60 | + * @property {String} valueKey 指定选项的值为选项对象中的哪个属性值 | |
| 61 | + * @property {String} labelKey 指定选项标签为选项对象中的哪个属性值 | |
| 62 | + * @property {String} childrenKey 指定选项的子选项为选项对象中的哪个属性值 | |
| 63 | + * @property {Boolean} autoClose 是否在选择最后一级时自动关闭并触发confirm(默认false) | |
| 64 | + */ | |
| 65 | + import { t } from '../../libs/i18n' | |
| 66 | + export default { | |
| 67 | + name: 'up-cascader', | |
| 68 | + props: { | |
| 69 | + // 通过双向绑定控制组件的弹出与收起 | |
| 70 | + show: { | |
| 71 | + type: Boolean, | |
| 72 | + default: false | |
| 73 | + }, | |
| 74 | + // 级联数据 | |
| 75 | + data: { | |
| 76 | + type: Array, | |
| 77 | + default() { | |
| 78 | + return []; | |
| 79 | + } | |
| 80 | + }, | |
| 81 | + // 默认选中的值 | |
| 82 | + modelValue: { | |
| 83 | + type: Array, | |
| 84 | + default() { | |
| 85 | + return []; | |
| 86 | + } | |
| 87 | + }, | |
| 88 | + // 指定选项的值为选项对象中的哪个属性值 | |
| 89 | + valueKey: { | |
| 90 | + type: String, | |
| 91 | + default: 'value' | |
| 92 | + }, | |
| 93 | + // 指定选项标签为选项对象中的哪个属性值 | |
| 94 | + labelKey: { | |
| 95 | + type: String, | |
| 96 | + default: 'label' | |
| 97 | + }, | |
| 98 | + // 指定选项的子选项为选项对象中的哪个属性值 | |
| 99 | + childrenKey: { | |
| 100 | + type: String, | |
| 101 | + default: 'children' | |
| 102 | + }, | |
| 103 | + // 是否允许通过点击遮罩关闭Picker | |
| 104 | + maskCloseAble: { | |
| 105 | + type: Boolean, | |
| 106 | + default: true | |
| 107 | + }, | |
| 108 | + // 弹出的z-index值 | |
| 109 | + zIndex: { | |
| 110 | + type: [String, Number], | |
| 111 | + default: 0 | |
| 112 | + }, | |
| 113 | + // 是否在选择最后一级时自动关闭并触发confirm | |
| 114 | + autoClose: { | |
| 115 | + type: Boolean, | |
| 116 | + default: false | |
| 117 | + }, | |
| 118 | + // 选中项目的展示方向direction垂直方向适合文字长度过长 | |
| 119 | + headerDirection: { | |
| 120 | + type: String, | |
| 121 | + default: 'row' | |
| 122 | + }, | |
| 123 | + // 选项区域列数,支持1列和2列,默认为2列 | |
| 124 | + optionsCols: { | |
| 125 | + type: [Number], | |
| 126 | + default: 2 | |
| 127 | + } | |
| 128 | + }, | |
| 129 | + data() { | |
| 130 | + return { | |
| 131 | + // 存储每一级的数据 | |
| 132 | + levelList: [], | |
| 133 | + // 存储每一级选中的索引 | |
| 134 | + selectedValueIndexs: [], | |
| 135 | + tabsIndex: 0, | |
| 136 | + popupShow: false, | |
| 137 | + // 新增confirmValues用于存储确认的值 | |
| 138 | + confirmValues: [] | |
| 139 | + } | |
| 140 | + }, | |
| 141 | + watch: { | |
| 142 | + data: { | |
| 143 | + handler() { | |
| 144 | + this.initLevelList(); | |
| 145 | + }, | |
| 146 | + immediate: true | |
| 147 | + }, | |
| 148 | + show() { | |
| 149 | + this.popupShow = this.show; | |
| 150 | + }, | |
| 151 | + modelValue: { | |
| 152 | + handler() { | |
| 153 | + this.init(); | |
| 154 | + }, | |
| 155 | + immediate: true | |
| 156 | + } | |
| 157 | + }, | |
| 158 | + computed: { | |
| 159 | + isChange() { | |
| 160 | + return this.tabsIndex > 1; | |
| 161 | + }, | |
| 162 | + genTabsList() { | |
| 163 | + let tabsList = [{ | |
| 164 | + name: "请选择" | |
| 165 | + }]; | |
| 166 | + | |
| 167 | + // 根据选中的值动态生成tabs | |
| 168 | + for (let i = 0; i < this.selectedValueIndexs.length; i++) { | |
| 169 | + if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) { | |
| 170 | + const selectedItem = this.levelList[i][this.selectedValueIndexs[i]]; | |
| 171 | + if (selectedItem) { | |
| 172 | + tabsList[i] = { | |
| 173 | + name: selectedItem[this.labelKey] | |
| 174 | + }; | |
| 175 | + // 如果还有下一级,则添加"请选择" | |
| 176 | + if (i === this.selectedValueIndexs.length - 1 && | |
| 177 | + selectedItem[this.childrenKey] && | |
| 178 | + selectedItem[this.childrenKey].length > 0) { | |
| 179 | + tabsList.push({ | |
| 180 | + name: "请选择" | |
| 181 | + }); | |
| 182 | + } | |
| 183 | + } | |
| 184 | + } | |
| 185 | + } | |
| 186 | + | |
| 187 | + return tabsList; | |
| 188 | + }, | |
| 189 | + uZIndex() { | |
| 190 | + // 如果用户有传递z-index值,优先使用 | |
| 191 | + return this.zIndex ? this.zIndex : this.$u.zIndex.popup; | |
| 192 | + } | |
| 193 | + }, | |
| 194 | + // 新增confirm事件 | |
| 195 | + emits: ['update:modelValue', 'change', 'confirm'], | |
| 196 | + methods: { | |
| 197 | + t, | |
| 198 | + init() { | |
| 199 | + // 初始化选中值 | |
| 200 | + if (this.modelValue && this.modelValue.length > 0) { | |
| 201 | + this.setDefaultValue(); | |
| 202 | + } | |
| 203 | + }, | |
| 204 | + initLevelList() { | |
| 205 | + // 初始化第一级数据 | |
| 206 | + if (this.data && this.data.length > 0) { | |
| 207 | + this.levelList = [this.data]; | |
| 208 | + this.selectedValueIndexs = []; | |
| 209 | + } | |
| 210 | + }, | |
| 211 | + setDefaultValue() { | |
| 212 | + // 根据默认值设置选中项 | |
| 213 | + // 根据modelValue获取indexs给selectedValueIndexs | |
| 214 | + this.selectedValueIndexs = []; | |
| 215 | + let currentLevelData = this.data; | |
| 216 | + | |
| 217 | + for (let i = 0; i < this.modelValue.length; i++) { | |
| 218 | + const value = this.modelValue[i]; | |
| 219 | + const index = currentLevelData.findIndex(item => item[this.valueKey] === value); | |
| 220 | + | |
| 221 | + if (index !== -1) { | |
| 222 | + this.selectedValueIndexs.push(index); | |
| 223 | + // 更新下一级的数据 | |
| 224 | + if (currentLevelData[index][this.childrenKey]) { | |
| 225 | + currentLevelData = currentLevelData[index][this.childrenKey]; | |
| 226 | + } else { | |
| 227 | + // 如果没有子级数据,则停止处理 | |
| 228 | + break; | |
| 229 | + } | |
| 230 | + } else { | |
| 231 | + // 如果找不到匹配项,则停止处理 | |
| 232 | + break; | |
| 233 | + } | |
| 234 | + } | |
| 235 | + }, | |
| 236 | + close() { | |
| 237 | + this.$emit('update:show', false); | |
| 238 | + }, | |
| 239 | + tabsChange(item) { | |
| 240 | + }, | |
| 241 | + levelChange(levelIndex, index) { | |
| 242 | + // 设置当前级的选中值 | |
| 243 | + this.$set(this.selectedValueIndexs, levelIndex, index); | |
| 244 | + | |
| 245 | + // 清除后续级别的选中值 | |
| 246 | + for (let i = levelIndex + 1; i < this.selectedValueIndexs.length; i++) { | |
| 247 | + this.$set(this.selectedValueIndexs, i, undefined); | |
| 248 | + } | |
| 249 | + | |
| 250 | + // 获取当前选中项 | |
| 251 | + const currentItem = this.levelList[levelIndex][index]; | |
| 252 | + | |
| 253 | + // 如果有子级数据,则初始化下一级 | |
| 254 | + if (currentItem && currentItem[this.childrenKey] && currentItem[this.childrenKey].length > 0) { | |
| 255 | + // 确保levelList数组足够长 | |
| 256 | + if (this.levelList.length <= levelIndex + 1) { | |
| 257 | + this.levelList.push(currentItem[this.childrenKey]); | |
| 258 | + } else { | |
| 259 | + this.$set(this.levelList, levelIndex + 1, currentItem[this.childrenKey]); | |
| 260 | + } | |
| 261 | + // 切换到下一级tab | |
| 262 | + this.tabsIndex = levelIndex + 1; | |
| 263 | + } else { | |
| 264 | + // 没有子级数据,说明是最后一级 | |
| 265 | + if (this.autoClose) { | |
| 266 | + // 如果启用自动关闭,则触发change事件并关闭 | |
| 267 | + this.emitChange(); | |
| 268 | + } else { | |
| 269 | + // 否则只触发change事件,不关闭 | |
| 270 | + this.emitChange(false); | |
| 271 | + } | |
| 272 | + } | |
| 273 | + }, | |
| 274 | + // 修改emitChange方法,增加closePopup参数 | |
| 275 | + emitChange(closePopup = true) { | |
| 276 | + // 构造选中结果 | |
| 277 | + const result = []; | |
| 278 | + for (let i = 0; i < this.selectedValueIndexs.length; i++) { | |
| 279 | + if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) { | |
| 280 | + result.push(this.levelList[i][this.selectedValueIndexs[i]][this.valueKey]); | |
| 281 | + } | |
| 282 | + } | |
| 283 | + | |
| 284 | + // 更新confirmValues | |
| 285 | + this.confirmValues = [...result]; | |
| 286 | + | |
| 287 | + // 触发change事件,返回value数组 | |
| 288 | + this.$emit('change', this.confirmValues); | |
| 289 | + | |
| 290 | + // 根据参数决定是否关闭弹窗 | |
| 291 | + if (closePopup) { | |
| 292 | + this.close(); | |
| 293 | + } | |
| 294 | + }, | |
| 295 | + handleCancel() { | |
| 296 | + this.close(); | |
| 297 | + }, | |
| 298 | + handleConfirm() { | |
| 299 | + // 确认时触发confirm事件 | |
| 300 | + this.$emit('update:modelValue', this.confirmValues); | |
| 301 | + this.$emit('confirm', this.confirmValues); | |
| 302 | + this.close(); | |
| 303 | + } | |
| 304 | + } | |
| 305 | + } | |
| 306 | +</script> | |
| 307 | +<style lang="scss"> | |
| 308 | + .area-box { | |
| 309 | + width: 100%; | |
| 310 | + overflow: hidden; | |
| 311 | + height: 800rpx; | |
| 312 | + | |
| 313 | + >view { | |
| 314 | + width: 150%; | |
| 315 | + transition: transform 0.3s ease-in-out 0s; | |
| 316 | + transform: translateX(0); | |
| 317 | + | |
| 318 | + &.change { | |
| 319 | + // transform: translateX(-33.3333333%); | |
| 320 | + } | |
| 321 | + } | |
| 322 | + | |
| 323 | + .area-item { | |
| 324 | + // width: 750rpx; | |
| 325 | + height: 800rpx; | |
| 326 | + } | |
| 327 | + } | |
| 328 | + | |
| 329 | + // 添加按钮区域样式 | |
| 330 | + .u-cascader-action { | |
| 331 | + border-top: 1px solid #eee; | |
| 332 | + } | |
| 333 | +</style> | |
| 0 | 334 | \ No newline at end of file | ... | ... |
uni_modules/uview-plus/components/u-cate-tab/u-cate-tab.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-cate-tab" :style="{ height: addUnit(height) }"> | |
| 3 | + <view class="u-cate-tab__wrap"> | |
| 4 | + <scroll-view class="u-cate-tab__view u-cate-tab__menu-scroll-view" | |
| 5 | + scroll-y scroll-with-animation :scroll-top="scrollTop" | |
| 6 | + :scroll-into-view="itemId"> | |
| 7 | + <view v-for="(item, index) in tabList" :key="index" class="u-cate-tab__item" | |
| 8 | + :class="[innerCurrent == index ? 'u-cate-tab__item-active' : '']" | |
| 9 | + @tap.stop="swichMenu(index)"> | |
| 10 | + <slot name="tabItem" :item="item"> | |
| 11 | + </slot> | |
| 12 | + <text v-if="!$slots['tabItem']" class="u-line-1">{{item[tabKeyName]}}</text> | |
| 13 | + </view> | |
| 14 | + </scroll-view> | |
| 15 | + <scroll-view :scroll-top="scrollRightTop" scroll-with-animation :scroll-into-view="scrollIntoView" | |
| 16 | + scroll-y class="u-cate-tab__right-box" @scroll="rightScroll"> | |
| 17 | + <view class="u-cate-tab__right-top"> | |
| 18 | + <slot name="rightTop" :tabList="tabList"> | |
| 19 | + </slot> | |
| 20 | + </view> | |
| 21 | + <view class="u-cate-tab__page-view"> | |
| 22 | + <template :key="index" v-for="(item , index) in tabList"> | |
| 23 | + <view v-if="mode == 'follow' || ( mode == 'tab' && index == innerCurrent)" | |
| 24 | + class="u-cate-tab__page-item" :id="'item' + index"> | |
| 25 | + <slot name="itemList" :item="item"> | |
| 26 | + </slot> | |
| 27 | + <template v-if="!$slots['itemList']"> | |
| 28 | + <view class="item-title"> | |
| 29 | + <text>{{item[tabKeyName]}}</text> | |
| 30 | + </view> | |
| 31 | + <view class="item-container"> | |
| 32 | + <template v-for="(item1, index1) in item.children" :key="index1"> | |
| 33 | + <slot name="pageItem" :pageItem="item1"> | |
| 34 | + <view class="thumb-box" > | |
| 35 | + <image class="item-menu-image" :src="item1.icon" mode=""></image> | |
| 36 | + <view class="item-menu-name">{{item1[itemKeyName]}}</view> | |
| 37 | + </view> | |
| 38 | + </slot> | |
| 39 | + </template> | |
| 40 | + </view> | |
| 41 | + </template> | |
| 42 | + </view> | |
| 43 | + </template> | |
| 44 | + </view> | |
| 45 | + </scroll-view> | |
| 46 | + </view> | |
| 47 | + </view> | |
| 48 | +</template> | |
| 49 | +<script> | |
| 50 | + import { addUnit, sleep } from '../../libs/function/index'; | |
| 51 | + export default { | |
| 52 | + name: 'up-cate-tab', | |
| 53 | + props: { | |
| 54 | + mode: { | |
| 55 | + type: String, | |
| 56 | + default: 'follow' // follo跟随联动, tab单一显示。 | |
| 57 | + }, | |
| 58 | + height: { | |
| 59 | + type: String, | |
| 60 | + default: '100%' | |
| 61 | + }, | |
| 62 | + tabList: { | |
| 63 | + type: Array, | |
| 64 | + default: () => { | |
| 65 | + return [] | |
| 66 | + } | |
| 67 | + }, | |
| 68 | + tabKeyName: { | |
| 69 | + type: String, | |
| 70 | + default: 'name' | |
| 71 | + }, | |
| 72 | + itemKeyName: { | |
| 73 | + type: String, | |
| 74 | + default: 'name' | |
| 75 | + }, | |
| 76 | + current: { | |
| 77 | + type: Number, | |
| 78 | + default: 0 | |
| 79 | + } | |
| 80 | + }, | |
| 81 | + watch: { | |
| 82 | + tabList: { | |
| 83 | + deep: true, | |
| 84 | + handler(newVal, oldVal) { | |
| 85 | + // this.observer(); | |
| 86 | + sleep(30); | |
| 87 | + this.getMenuItemTop(); | |
| 88 | + this.leftMenuStatus(this.innerCurrent); | |
| 89 | + } | |
| 90 | + }, | |
| 91 | + current(nval) { | |
| 92 | + this.innerCurrent = nval; | |
| 93 | + this.leftMenuStatus(this.innerCurrent); | |
| 94 | + }, | |
| 95 | + height() { | |
| 96 | + // console.log('height change'); | |
| 97 | + this.getMenuItemTop(); | |
| 98 | + this.leftMenuStatus(this.innerCurrent); | |
| 99 | + } | |
| 100 | + }, | |
| 101 | + emits: ['update:current'], | |
| 102 | + data() { | |
| 103 | + return { | |
| 104 | + scrollTop: 0, //tab标题的滚动条位置 | |
| 105 | + scrollIntoView: '', // 滚动至哪个元素 | |
| 106 | + oldScrollTop: 0, | |
| 107 | + innerCurrent: 0, // 预设当前项的值 | |
| 108 | + menuHeight: 0, // 左边菜单的高度 | |
| 109 | + menuItemHeight: 0, // 左边菜单item的高度 | |
| 110 | + itemId: '', // 栏目右边scroll-view用于滚动的id | |
| 111 | + menuItemPos: [], | |
| 112 | + rects: [], | |
| 113 | + arr: [], | |
| 114 | + scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度 | |
| 115 | + timer: null, // 定时器 | |
| 116 | + } | |
| 117 | + }, | |
| 118 | + mounted() { | |
| 119 | + // this.observer(); | |
| 120 | + this.innerCurrent = this.current; | |
| 121 | + this.leftMenuStatus(this.innerCurrent); | |
| 122 | + this.getMenuItemTop() | |
| 123 | + }, | |
| 124 | + methods: { | |
| 125 | + addUnit, | |
| 126 | + // 点击左边的栏目切换 | |
| 127 | + async swichMenu(index) { | |
| 128 | + if (this.mode == 'follow') { | |
| 129 | + if(this.arr.length == 0) { | |
| 130 | + await this.getMenuItemTop(); | |
| 131 | + } | |
| 132 | + this.scrollIntoView = 'item' + index; | |
| 133 | + } | |
| 134 | + | |
| 135 | + if (index == this.innerCurrent) return; | |
| 136 | + this.$nextTick(function(){ | |
| 137 | + this.innerCurrent = index; | |
| 138 | + this.$emit('update:current', index); | |
| 139 | + }) | |
| 140 | + }, | |
| 141 | + // 获取一个目标元素的高度 | |
| 142 | + getElRect(elClass, dataVal) { | |
| 143 | + return new Promise((resolve, reject) => { | |
| 144 | + const query = uni.createSelectorQuery().in(this); | |
| 145 | + query.select('.' + elClass).fields({ | |
| 146 | + size: true | |
| 147 | + }, res => { | |
| 148 | + // 如果节点尚未生成,res值为null,循环调用执行 | |
| 149 | + if (!res) { | |
| 150 | + setTimeout(() => { | |
| 151 | + this.getElRect(elClass); | |
| 152 | + }, 10); | |
| 153 | + return; | |
| 154 | + } | |
| 155 | + this[dataVal] = res.height; | |
| 156 | + resolve(); | |
| 157 | + }).exec(); | |
| 158 | + }) | |
| 159 | + }, | |
| 160 | + // 观测元素相交状态 | |
| 161 | + async observer() { | |
| 162 | + await this.$nextTick(); | |
| 163 | + // 清除之前的观察器 | |
| 164 | + if (this._observerList) { | |
| 165 | + this._observerList.forEach(observer => { | |
| 166 | + observer.disconnect(); | |
| 167 | + }); | |
| 168 | + } | |
| 169 | + this._observerList = []; | |
| 170 | + | |
| 171 | + this.tabList.map((val, index) => { | |
| 172 | + let observer = uni.createIntersectionObserver(this); | |
| 173 | + this._observerList.push(observer); | |
| 174 | + // 检测相交状态 | |
| 175 | + observer.relativeTo('.u-cate-tab__right-box', { | |
| 176 | + top: 10 | |
| 177 | + }).observe('#item' + index, (res) => { | |
| 178 | + if (res.intersectionRatio > 0) { | |
| 179 | + console.log('res', res); | |
| 180 | + // 修复:确保正确获取索引 | |
| 181 | + let id = res.id ? res.id.substring(4) : index; | |
| 182 | + this.leftMenuStatus(parseInt(id)); | |
| 183 | + } | |
| 184 | + }) | |
| 185 | + }) | |
| 186 | + }, | |
| 187 | + // 设置左边菜单的滚动状态 | |
| 188 | + async leftMenuStatus(index) { | |
| 189 | + this.innerCurrent = index; | |
| 190 | + this.$emit('update:current', index); | |
| 191 | + // 如果为0,意味着尚未初始化 | |
| 192 | + if (this.menuHeight == 0 || this.menuItemHeight == 0) { | |
| 193 | + await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight'); | |
| 194 | + await this.getElRect('u-cate-tab__item', 'menuItemHeight'); | |
| 195 | + } | |
| 196 | + // console.log(this.menuHeight, this.menuItemHeight) | |
| 197 | + // 将菜单活动item垂直居中 | |
| 198 | + this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2; | |
| 199 | + }, | |
| 200 | + // 获取右边菜单每个item到顶部的距离 | |
| 201 | + async getMenuItemTop() { | |
| 202 | + // await this.$nextTick(); | |
| 203 | + // console.log('getMenuItemTop') | |
| 204 | + return new Promise(resolve => { | |
| 205 | + let selectorQuery = uni.createSelectorQuery().in(this); | |
| 206 | + selectorQuery.selectAll('.u-cate-tab__page-item').boundingClientRect((rects) => { | |
| 207 | + // 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行 | |
| 208 | + if(!rects.length) { | |
| 209 | + setTimeout(() => { | |
| 210 | + this.getMenuItemTop(); | |
| 211 | + }, 100); | |
| 212 | + return ; | |
| 213 | + } | |
| 214 | + // console.log(rects) | |
| 215 | + this.rects = rects; | |
| 216 | + this.arr = []; | |
| 217 | + rects.forEach((rect) => { | |
| 218 | + // 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况) | |
| 219 | + this.arr.push(rect.top - rects[0].top); | |
| 220 | + }) | |
| 221 | + // console.log(this.arr) | |
| 222 | + resolve(); | |
| 223 | + }).exec() | |
| 224 | + }) | |
| 225 | + }, | |
| 226 | + // 右边菜单滚动 | |
| 227 | + async rightScroll(e) { | |
| 228 | + if (this.mode !== 'follow') return; | |
| 229 | + this.oldScrollTop = e.detail.scrollTop; | |
| 230 | + // console.log(e.detail.scrollTop) | |
| 231 | + // console.log(JSON.stringify(this.arr)) | |
| 232 | + if(this.arr.length == 0) { | |
| 233 | + await this.getMenuItemTop(); | |
| 234 | + } | |
| 235 | + if(this.timer) return ; | |
| 236 | + if(!this.menuHeight) { | |
| 237 | + await this.getElRect('u-cate-tab__menu-scroll-view', 'menuHeight'); | |
| 238 | + } | |
| 239 | + setTimeout(() => { // 节流 | |
| 240 | + this.timer = null; | |
| 241 | + // scrollHeight为右边菜单垂直中点位置 | |
| 242 | + let scrollHeight = e.detail.scrollTop + 1; | |
| 243 | + // console.log(e.detail.scrollTop) | |
| 244 | + for (let i = 0; i < this.arr.length; i++) { | |
| 245 | + let height1 = this.arr[i]; | |
| 246 | + let height2 = this.arr[i + 1]; | |
| 247 | + // console.log('i', i) | |
| 248 | + // console.log('height1', height1) | |
| 249 | + // console.log('height2', height2) | |
| 250 | + // 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可 | |
| 251 | + if (!height2 || scrollHeight >= height1 && scrollHeight <= height2) { | |
| 252 | + // console.log('scrollHeight', scrollHeight) | |
| 253 | + // console.log('height1', height1) | |
| 254 | + // console.log('height2', height2) | |
| 255 | + this.leftMenuStatus(i); | |
| 256 | + return ; | |
| 257 | + } | |
| 258 | + } | |
| 259 | + }, 100) | |
| 260 | + } | |
| 261 | + } | |
| 262 | + } | |
| 263 | +</script> | |
| 264 | + | |
| 265 | +<style lang="scss" scoped> | |
| 266 | + .u-cate-tab { | |
| 267 | + display: flex; | |
| 268 | + flex-direction: column; | |
| 269 | + } | |
| 270 | + | |
| 271 | + .u-cate-tab__wrap { | |
| 272 | + flex: 1; | |
| 273 | + display: flex; | |
| 274 | + flex-direction: row; | |
| 275 | + overflow: hidden; | |
| 276 | + } | |
| 277 | + | |
| 278 | + .u-search-inner { | |
| 279 | + background-color: rgb(234, 234, 234); | |
| 280 | + border-radius: 100rpx; | |
| 281 | + display: flex; | |
| 282 | + align-items: center; | |
| 283 | + padding: 10rpx 16rpx; | |
| 284 | + } | |
| 285 | + | |
| 286 | + .u-search-text { | |
| 287 | + font-size: 26rpx; | |
| 288 | + color: $u-tips-color; | |
| 289 | + margin-left: 10rpx; | |
| 290 | + } | |
| 291 | + | |
| 292 | + .u-cate-tab__view { | |
| 293 | + width: 200rpx; | |
| 294 | + height: 100%; | |
| 295 | + } | |
| 296 | + | |
| 297 | + .u-cate-tab__item { | |
| 298 | + height: 110rpx; | |
| 299 | + background: #f6f6f6; | |
| 300 | + box-sizing: border-box; | |
| 301 | + display: flex; | |
| 302 | + align-items: center; | |
| 303 | + justify-content: center; | |
| 304 | + font-size: 26rpx; | |
| 305 | + color: #444; | |
| 306 | + font-weight: 400; | |
| 307 | + line-height: 1; | |
| 308 | + } | |
| 309 | + | |
| 310 | + .u-cate-tab__item-active { | |
| 311 | + position: relative; | |
| 312 | + color: #000; | |
| 313 | + font-size: 30rpx; | |
| 314 | + font-weight: 600; | |
| 315 | + background: #fff; | |
| 316 | + } | |
| 317 | + | |
| 318 | + .u-cate-tab__item-active::before { | |
| 319 | + content: ""; | |
| 320 | + position: absolute; | |
| 321 | + border-left: 4px solid $u-primary; | |
| 322 | + height: 32rpx; | |
| 323 | + left: 0; | |
| 324 | + top: 39rpx; | |
| 325 | + } | |
| 326 | + | |
| 327 | + .u-cate-tab__view { | |
| 328 | + height: 100%; | |
| 329 | + } | |
| 330 | + | |
| 331 | + .u-cate-tab__right-box { | |
| 332 | + flex: 1; | |
| 333 | + background-color: rgb(250, 250, 250); | |
| 334 | + } | |
| 335 | + | |
| 336 | + .u-cate-tab__page-view { | |
| 337 | + padding: 16rpx; | |
| 338 | + } | |
| 339 | + | |
| 340 | + .u-cate-tab__page-item { | |
| 341 | + margin-bottom: 30rpx; | |
| 342 | + background-color: #fff; | |
| 343 | + padding: 16rpx; | |
| 344 | + border-radius: 8rpx; | |
| 345 | + } | |
| 346 | + | |
| 347 | + .u-cate-tab__page-item:last-child { | |
| 348 | + min-height: 100vh; | |
| 349 | + } | |
| 350 | + | |
| 351 | + .item-title { | |
| 352 | + font-size: 26rpx; | |
| 353 | + color: $u-main-color; | |
| 354 | + font-weight: bold; | |
| 355 | + } | |
| 356 | + | |
| 357 | + .item-menu-name { | |
| 358 | + font-weight: normal; | |
| 359 | + font-size: 24rpx; | |
| 360 | + color: $u-main-color; | |
| 361 | + } | |
| 362 | + | |
| 363 | + .item-container { | |
| 364 | + display: flex; | |
| 365 | + flex-wrap: wrap; | |
| 366 | + } | |
| 367 | + | |
| 368 | + .thumb-box { | |
| 369 | + width: 33.333333%; | |
| 370 | + display: flex; | |
| 371 | + align-items: center; | |
| 372 | + justify-content: center; | |
| 373 | + flex-direction: column; | |
| 374 | + margin-top: 20rpx; | |
| 375 | + } | |
| 376 | + | |
| 377 | + .item-menu-image { | |
| 378 | + width: 120rpx; | |
| 379 | + height: 120rpx; | |
| 380 | + } | |
| 381 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-cell-group/cellGroup.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:54:16 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/cellGroup.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // cell-group组件的props | |
| 12 | + cellGroup: { | |
| 13 | + title: '', | |
| 14 | + border: true, | |
| 15 | + customStyle: {} | |
| 16 | + } | |
| 17 | +} | ... | ... |
uni_modules/uview-plus/components/u-cell-group/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 分组标题 | |
| 6 | + title: { | |
| 7 | + type: String, | |
| 8 | + default: () => defProps.cellGroup.title | |
| 9 | + }, | |
| 10 | + // 是否显示外边框 | |
| 11 | + border: { | |
| 12 | + type: Boolean, | |
| 13 | + default: () => defProps.cellGroup.border | |
| 14 | + } | |
| 15 | + } | |
| 16 | +}) | ... | ... |
uni_modules/uview-plus/components/u-cell-group/u-cell-group.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view :style="[addStyle(customStyle)]" :class="[customClass]" class="u-cell-group"> | |
| 3 | + <view v-if="title" class="u-cell-group__title"> | |
| 4 | + <slot name="title"> | |
| 5 | + <text class="u-cell-group__title__text">{{ title }}</text> | |
| 6 | + </slot> | |
| 7 | + </view> | |
| 8 | + <view class="u-cell-group__wrapper"> | |
| 9 | + <u-line v-if="border"></u-line> | |
| 10 | + <slot /> | |
| 11 | + </view> | |
| 12 | + </view> | |
| 13 | +</template> | |
| 14 | + | |
| 15 | +<script> | |
| 16 | + import { props } from './props'; | |
| 17 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 18 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 19 | + import { addStyle } from '../../libs/function/index'; | |
| 20 | + /** | |
| 21 | + * cellGroup 单元格 | |
| 22 | + * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。 | |
| 23 | + * @tutorial https://uview-plus.jiangruyi.com/components/cell.html | |
| 24 | + * | |
| 25 | + * @property {String} title 分组标题 | |
| 26 | + * @property {Boolean} border 是否显示外边框 (默认 true ) | |
| 27 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 28 | + * | |
| 29 | + * @event {Function} click 点击cell列表时触发 | |
| 30 | + * @example <u-cell-group title="设置喜好"> | |
| 31 | + */ | |
| 32 | + export default { | |
| 33 | + name: 'u-cell-group', | |
| 34 | + mixins: [mpMixin, mixin, props], | |
| 35 | + methods: { | |
| 36 | + addStyle | |
| 37 | + } | |
| 38 | + } | |
| 39 | +</script> | |
| 40 | + | |
| 41 | +<style lang="scss" scoped> | |
| 42 | + @import "../../libs/css/components.scss"; | |
| 43 | + | |
| 44 | + $u-cell-group-title-padding: 16px 16px 8px !default; | |
| 45 | + $u-cell-group-title-font-size: 15px !default; | |
| 46 | + $u-cell-group-title-line-height: 16px !default; | |
| 47 | + $u-cell-group-title-color: $u-main-color !default; | |
| 48 | + | |
| 49 | + .u-cell-group { | |
| 50 | + flex: 1; | |
| 51 | + | |
| 52 | + &__title { | |
| 53 | + padding: $u-cell-group-title-padding; | |
| 54 | + | |
| 55 | + &__text { | |
| 56 | + font-size: $u-cell-group-title-font-size; | |
| 57 | + line-height: $u-cell-group-title-line-height; | |
| 58 | + color: $u-cell-group-title-color; | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + &__wrapper { | |
| 63 | + position: relative; | |
| 64 | + } | |
| 65 | + } | |
| 66 | +</style> | |
| 67 | + | ... | ... |
uni_modules/uview-plus/components/u-cell/cell.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-23 20:53:09 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/cell.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // cell组件的props | |
| 12 | + cell: { | |
| 13 | + customClass: '', | |
| 14 | + title: '', | |
| 15 | + label: '', | |
| 16 | + value: '', | |
| 17 | + icon: '', | |
| 18 | + disabled: false, | |
| 19 | + border: true, | |
| 20 | + center: false, | |
| 21 | + url: '', | |
| 22 | + linkType: 'navigateTo', | |
| 23 | + clickable: false, | |
| 24 | + isLink: false, | |
| 25 | + required: false, | |
| 26 | + arrowDirection: '', | |
| 27 | + iconStyle: {}, | |
| 28 | + rightIconStyle: {}, | |
| 29 | + rightIcon: 'arrow-right', | |
| 30 | + titleStyle: {}, | |
| 31 | + size: '', | |
| 32 | + stop: true, | |
| 33 | + name: '' | |
| 34 | + } | |
| 35 | +} | ... | ... |
uni_modules/uview-plus/components/u-cell/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 标题 | |
| 6 | + title: { | |
| 7 | + type: [String, Number], | |
| 8 | + default: () => defProps.cell.title | |
| 9 | + }, | |
| 10 | + // 标题下方的描述信息 | |
| 11 | + label: { | |
| 12 | + type: [String, Number], | |
| 13 | + default: () => defProps.cell.label | |
| 14 | + }, | |
| 15 | + // 右侧的内容 | |
| 16 | + value: { | |
| 17 | + type: [String, Number], | |
| 18 | + default: () => defProps.cell.value | |
| 19 | + }, | |
| 20 | + // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址) | |
| 21 | + icon: { | |
| 22 | + type: String, | |
| 23 | + default: () => defProps.cell.icon | |
| 24 | + }, | |
| 25 | + // 是否禁用cell | |
| 26 | + disabled: { | |
| 27 | + type: Boolean, | |
| 28 | + default: () => defProps.cell.disabled | |
| 29 | + }, | |
| 30 | + // 是否显示下边框 | |
| 31 | + border: { | |
| 32 | + type: Boolean, | |
| 33 | + default: () => defProps.cell.border | |
| 34 | + }, | |
| 35 | + // 内容是否垂直居中(主要是针对右侧的value部分) | |
| 36 | + center: { | |
| 37 | + type: Boolean, | |
| 38 | + default: () => defProps.cell.center | |
| 39 | + }, | |
| 40 | + // 点击后跳转的URL地址 | |
| 41 | + url: { | |
| 42 | + type: String, | |
| 43 | + default: () => defProps.cell.url | |
| 44 | + }, | |
| 45 | + // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 | |
| 46 | + linkType: { | |
| 47 | + type: String, | |
| 48 | + default: () => defProps.cell.linkType | |
| 49 | + }, | |
| 50 | + // 是否开启点击反馈(表现为点击时加上灰色背景) | |
| 51 | + clickable: { | |
| 52 | + type: Boolean, | |
| 53 | + default: () => defProps.cell.clickable | |
| 54 | + }, | |
| 55 | + // 是否展示右侧箭头并开启点击反馈 | |
| 56 | + isLink: { | |
| 57 | + type: Boolean, | |
| 58 | + default: () => defProps.cell.isLink | |
| 59 | + }, | |
| 60 | + // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) | |
| 61 | + required: { | |
| 62 | + type: Boolean, | |
| 63 | + default: () => defProps.cell.required | |
| 64 | + }, | |
| 65 | + // 右侧的图标箭头 | |
| 66 | + rightIcon: { | |
| 67 | + type: String, | |
| 68 | + default: () => defProps.cell.rightIcon | |
| 69 | + }, | |
| 70 | + // 右侧箭头的方向,可选值为:left,up,down | |
| 71 | + arrowDirection: { | |
| 72 | + type: String, | |
| 73 | + default: () => defProps.cell.arrowDirection | |
| 74 | + }, | |
| 75 | + // 左侧图标样式 | |
| 76 | + iconStyle: { | |
| 77 | + type: [Object, String], | |
| 78 | + default: () => { | |
| 79 | + return defProps.cell.iconStyle | |
| 80 | + } | |
| 81 | + }, | |
| 82 | + // 右侧箭头图标的样式 | |
| 83 | + rightIconStyle: { | |
| 84 | + type: [Object, String], | |
| 85 | + default: () => { | |
| 86 | + return defProps.cell.rightIconStyle | |
| 87 | + } | |
| 88 | + }, | |
| 89 | + // 标题的样式 | |
| 90 | + titleStyle: { | |
| 91 | + type: [Object, String], | |
| 92 | + default: () => { | |
| 93 | + return defProps.cell.titleStyle | |
| 94 | + } | |
| 95 | + }, | |
| 96 | + // 单位元的大小,可选值为large | |
| 97 | + size: { | |
| 98 | + type: String, | |
| 99 | + default: () => defProps.cell.size | |
| 100 | + }, | |
| 101 | + // 点击cell是否阻止事件传播 | |
| 102 | + stop: { | |
| 103 | + type: Boolean, | |
| 104 | + default: () => defProps.cell.stop | |
| 105 | + }, | |
| 106 | + // 标识符,cell被点击时返回 | |
| 107 | + name: { | |
| 108 | + type: [Number, String], | |
| 109 | + default: () => defProps.cell.name | |
| 110 | + } | |
| 111 | + } | |
| 112 | +}) | ... | ... |
uni_modules/uview-plus/components/u-cell/u-cell.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-cell" :class="[customClass]" :style="[addStyle(customStyle)]" | |
| 3 | + :hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250" | |
| 4 | + @tap="clickHandler"> | |
| 5 | + <view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']"> | |
| 6 | + <view class="u-cell__body__content"> | |
| 7 | + <view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon"> | |
| 8 | + <slot name="icon" v-if="$slots.icon"> | |
| 9 | + </slot> | |
| 10 | + <u-icon v-else :name="icon" | |
| 11 | + :custom-style="iconStyle" | |
| 12 | + :size="size === 'large' ? 22 : 18"></u-icon> | |
| 13 | + </view> | |
| 14 | + <view class="u-cell__title"> | |
| 15 | + <!-- 将slot与默认内容用if/else分开主要是因为微信小程序不支持slot嵌套传递,这样才能解决collapse组件的slot不失效问题,label暂时未用到。 --> | |
| 16 | + <slot name="title" v-if="$slots.title || !title"> | |
| 17 | + </slot> | |
| 18 | + <text v-else class="u-cell__title-text" :style="[titleTextStyle]" | |
| 19 | + :class="[required && 'u-cell--required', disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text> | |
| 20 | + <slot name="label"> | |
| 21 | + <text class="u-cell__label" v-if="label" | |
| 22 | + :class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text> | |
| 23 | + </slot> | |
| 24 | + </view> | |
| 25 | + </view> | |
| 26 | + <slot name="value"> | |
| 27 | + <text class="u-cell__value" | |
| 28 | + :class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']" | |
| 29 | + v-if="!testEmpty(value)">{{ value }}</text> | |
| 30 | + </slot> | |
| 31 | + <view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink" | |
| 32 | + :class="[`u-cell__right-icon-wrap--${arrowDirection}`]"> | |
| 33 | + <u-icon v-if="rightIcon && !$slots['right-icon']" :name="rightIcon" | |
| 34 | + :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'" | |
| 35 | + :size="size === 'large' ? 18 : 16"></u-icon> | |
| 36 | + <slot v-else name="right-icon"> | |
| 37 | + </slot> | |
| 38 | + </view> | |
| 39 | + <view class="u-cell__right-icon-wrap" v-if="$slots['righticon']" | |
| 40 | + :class="[`u-cell__right-icon-wrap--${arrowDirection}`]"> | |
| 41 | + <slot name="righticon"> | |
| 42 | + </slot> | |
| 43 | + </view> | |
| 44 | + </view> | |
| 45 | + <u-line v-if="border"></u-line> | |
| 46 | + </view> | |
| 47 | +</template> | |
| 48 | + | |
| 49 | +<script> | |
| 50 | + import { props } from './props'; | |
| 51 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 52 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 53 | + import { addStyle } from '../../libs/function/index'; | |
| 54 | + import test from '../../libs/function/test'; | |
| 55 | + /** | |
| 56 | + * cell 单元格 | |
| 57 | + * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。 | |
| 58 | + * @tutorial https://uview-plus.jiangruyi.com/components/cell.html | |
| 59 | + * @property {String | Number} title 标题 | |
| 60 | + * @property {String | Number} label 标题下方的描述信息 | |
| 61 | + * @property {String | Number} value 右侧的内容 | |
| 62 | + * @property {String} icon 左侧图标名称,或者图片链接(本地文件建议使用绝对地址) | |
| 63 | + * @property {Boolean} disabled 是否禁用cell | |
| 64 | + * @property {Boolean} border 是否显示下边框 (默认 true ) | |
| 65 | + * @property {Boolean} center 内容是否垂直居中(主要是针对右侧的value部分) (默认 false ) | |
| 66 | + * @property {String} url 点击后跳转的URL地址 | |
| 67 | + * @property {String} linkType 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 (默认 'navigateTo' ) | |
| 68 | + * @property {Boolean} clickable 是否开启点击反馈(表现为点击时加上灰色背景) (默认 false ) | |
| 69 | + * @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 (默认 false ) | |
| 70 | + * @property {Boolean} required 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false ) | |
| 71 | + * @property {String} rightIcon 右侧的图标箭头 (默认 'arrow-right') | |
| 72 | + * @property {String} arrowDirection 右侧箭头的方向,可选值为:left,up,down | |
| 73 | + * @property {Object | String} rightIconStyle 右侧箭头图标的样式 | |
| 74 | + * @property {Object | String} titleStyle 标题的样式 | |
| 75 | + * @property {Object | String} iconStyle 左侧图标样式 | |
| 76 | + * @property {String} size 单位元的大小,可选值为 large,normal,mini | |
| 77 | + * @property {Boolean} stop 点击cell是否阻止事件传播 (默认 true ) | |
| 78 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 79 | + * | |
| 80 | + * @event {Function} click 点击cell列表时触发 | |
| 81 | + * @example 该组件需要搭配cell-group组件使用,见官方文档示例 | |
| 82 | + */ | |
| 83 | + export default { | |
| 84 | + name: 'u-cell', | |
| 85 | + data() { | |
| 86 | + return { | |
| 87 | + | |
| 88 | + } | |
| 89 | + }, | |
| 90 | + mixins: [mpMixin, mixin, props], | |
| 91 | + computed: { | |
| 92 | + titleTextStyle() { | |
| 93 | + return addStyle(this.titleStyle) | |
| 94 | + } | |
| 95 | + }, | |
| 96 | + emits: ['click'], | |
| 97 | + methods: { | |
| 98 | + addStyle, | |
| 99 | + testEmpty: test.empty, | |
| 100 | + // 点击cell | |
| 101 | + clickHandler(e) { | |
| 102 | + if (this.disabled) return | |
| 103 | + this.$emit('click', { | |
| 104 | + name: this.name | |
| 105 | + }) | |
| 106 | + // 如果配置了url(此props参数通过mixin引入)参数,跳转页面 | |
| 107 | + this.openPage() | |
| 108 | + // 是否阻止事件传播 | |
| 109 | + this.stop && this.preventEvent(e) | |
| 110 | + }, | |
| 111 | + } | |
| 112 | + } | |
| 113 | +</script> | |
| 114 | + | |
| 115 | +<style lang="scss" scoped> | |
| 116 | + @import "../../libs/css/components.scss"; | |
| 117 | + | |
| 118 | + $u-cell-padding: 13px 15px !default; | |
| 119 | + $u-cell-font-size: 15px !default; | |
| 120 | + $u-cell-line-height: 24px !default; | |
| 121 | + $u-cell-color: $u-main-color !default; | |
| 122 | + $u-cell-icon-size: 16px !default; | |
| 123 | + $u-cell-title-font-size: 15px !default; | |
| 124 | + $u-cell-title-line-height: 22px !default; | |
| 125 | + $u-cell-title-color: $u-main-color !default; | |
| 126 | + $u-cell-label-font-size: 12px !default; | |
| 127 | + $u-cell-label-color: $u-tips-color !default; | |
| 128 | + $u-cell-label-line-height: 18px !default; | |
| 129 | + $u-cell-value-font-size: 14px !default; | |
| 130 | + $u-cell-value-color: $u-content-color !default; | |
| 131 | + $u-cell-clickable-color: $u-bg-color !default; | |
| 132 | + $u-cell-disabled-color: #c8c9cc !default; | |
| 133 | + $u-cell-padding-top-large: 13px !default; | |
| 134 | + $u-cell-padding-bottom-large: 13px !default; | |
| 135 | + $u-cell-value-font-size-large: 15px !default; | |
| 136 | + $u-cell-label-font-size-large: 14px !default; | |
| 137 | + $u-cell-title-font-size-large: 16px !default; | |
| 138 | + $u-cell-left-icon-wrap-margin-right: 4px !default; | |
| 139 | + $u-cell-right-icon-wrap-margin-left: 4px !default; | |
| 140 | + $u-cell-title-flex:1 !default; | |
| 141 | + $u-cell-label-margin-top:5px !default; | |
| 142 | + | |
| 143 | + | |
| 144 | + .u-cell { | |
| 145 | + &__body { | |
| 146 | + @include flex(); | |
| 147 | + /* #ifndef APP-NVUE */ | |
| 148 | + box-sizing: border-box; | |
| 149 | + /* #endif */ | |
| 150 | + padding: $u-cell-padding; | |
| 151 | + font-size: $u-cell-font-size; | |
| 152 | + color: $u-cell-color; | |
| 153 | + // line-height: $u-cell-line-height; | |
| 154 | + align-items: center; | |
| 155 | + | |
| 156 | + &__content { | |
| 157 | + @include flex(row); | |
| 158 | + align-items: center; | |
| 159 | + flex: 1; | |
| 160 | + } | |
| 161 | + | |
| 162 | + &--large { | |
| 163 | + padding-top: $u-cell-padding-top-large; | |
| 164 | + padding-bottom: $u-cell-padding-bottom-large; | |
| 165 | + } | |
| 166 | + } | |
| 167 | + | |
| 168 | + &__left-icon-wrap, | |
| 169 | + &__right-icon-wrap { | |
| 170 | + @include flex(); | |
| 171 | + align-items: center; | |
| 172 | + // height: $u-cell-line-height; | |
| 173 | + font-size: $u-cell-icon-size; | |
| 174 | + } | |
| 175 | + | |
| 176 | + &__left-icon-wrap { | |
| 177 | + margin-right: $u-cell-left-icon-wrap-margin-right; | |
| 178 | + } | |
| 179 | + | |
| 180 | + &__right-icon-wrap { | |
| 181 | + margin-left: $u-cell-right-icon-wrap-margin-left; | |
| 182 | + transition: transform 0.3s; | |
| 183 | + | |
| 184 | + &--up { | |
| 185 | + transform: rotate(-90deg); | |
| 186 | + } | |
| 187 | + | |
| 188 | + &--down { | |
| 189 | + transform: rotate(90deg); | |
| 190 | + } | |
| 191 | + } | |
| 192 | + | |
| 193 | + &__title { | |
| 194 | + flex: $u-cell-title-flex; | |
| 195 | + display: flex; | |
| 196 | + flex-direction: column; | |
| 197 | + | |
| 198 | + &-text { | |
| 199 | + font-size: $u-cell-title-font-size; | |
| 200 | + line-height: $u-cell-title-line-height; | |
| 201 | + color: $u-cell-title-color; | |
| 202 | + | |
| 203 | + &--large { | |
| 204 | + font-size: $u-cell-title-font-size-large; | |
| 205 | + } | |
| 206 | + } | |
| 207 | + | |
| 208 | + } | |
| 209 | + | |
| 210 | + &__label { | |
| 211 | + margin-top: $u-cell-label-margin-top; | |
| 212 | + font-size: $u-cell-label-font-size; | |
| 213 | + color: $u-cell-label-color; | |
| 214 | + line-height: $u-cell-label-line-height; | |
| 215 | + | |
| 216 | + &--large { | |
| 217 | + font-size: $u-cell-label-font-size-large; | |
| 218 | + } | |
| 219 | + } | |
| 220 | + | |
| 221 | + &__value { | |
| 222 | + text-align: right; | |
| 223 | + /* #ifndef APP-NVUE */ | |
| 224 | + margin-left: auto; | |
| 225 | + /* #endif */ | |
| 226 | + font-size: $u-cell-value-font-size; | |
| 227 | + line-height: $u-cell-line-height; | |
| 228 | + color: $u-cell-value-color; | |
| 229 | + &--large { | |
| 230 | + font-size: $u-cell-value-font-size-large; | |
| 231 | + } | |
| 232 | + } | |
| 233 | + | |
| 234 | + &--required { | |
| 235 | + /* #ifndef APP-NVUE */ | |
| 236 | + overflow: visible; | |
| 237 | + /* #endif */ | |
| 238 | + @include flex; | |
| 239 | + align-items: center; | |
| 240 | + } | |
| 241 | + | |
| 242 | + &--required:before { | |
| 243 | + position: absolute; | |
| 244 | + /* #ifndef APP-NVUE */ | |
| 245 | + content: '*'; | |
| 246 | + /* #endif */ | |
| 247 | + left: -8px; | |
| 248 | + margin-top: 4rpx; | |
| 249 | + font-size: 14px; | |
| 250 | + color: $u-error; | |
| 251 | + } | |
| 252 | + | |
| 253 | + &--clickable { | |
| 254 | + background-color: $u-cell-clickable-color; | |
| 255 | + } | |
| 256 | + | |
| 257 | + &--disabled { | |
| 258 | + color: $u-cell-disabled-color; | |
| 259 | + /* #ifndef APP-NVUE */ | |
| 260 | + cursor: not-allowed; | |
| 261 | + /* #endif */ | |
| 262 | + } | |
| 263 | + | |
| 264 | + &--center { | |
| 265 | + align-items: center; | |
| 266 | + } | |
| 267 | + } | |
| 268 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-checkbox-group/checkboxGroup.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:54:47 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/checkboxGroup.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // checkbox-group组件 | |
| 12 | + checkboxGroup: { | |
| 13 | + name: '', | |
| 14 | + value: [], | |
| 15 | + shape: 'square', | |
| 16 | + disabled: false, | |
| 17 | + activeColor: '#2979ff', | |
| 18 | + inactiveColor: '#c8c9cc', | |
| 19 | + size: 18, | |
| 20 | + placement: 'row', | |
| 21 | + labelSize: 14, | |
| 22 | + labelColor: '#303133', | |
| 23 | + labelDisabled: false, | |
| 24 | + iconColor: '#ffffff', | |
| 25 | + iconSize: 12, | |
| 26 | + iconPlacement: 'left', | |
| 27 | + borderBottom: false | |
| 28 | + } | |
| 29 | +} | ... | ... |
uni_modules/uview-plus/components/u-checkbox-group/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | + | |
| 4 | +export const props = defineMixin({ | |
| 5 | + props: { | |
| 6 | + // 标识符 | |
| 7 | + name: { | |
| 8 | + type: String, | |
| 9 | + default: () => defProps.checkboxGroup.name | |
| 10 | + }, | |
| 11 | + // #ifdef VUE3 | |
| 12 | + // 绑定的值 | |
| 13 | + modelValue: { | |
| 14 | + type: Array, | |
| 15 | + default: () => defProps.checkboxGroup.value | |
| 16 | + }, | |
| 17 | + // #endif | |
| 18 | + // #ifdef VUE2 | |
| 19 | + // 绑定的值 | |
| 20 | + value: { | |
| 21 | + type: Array, | |
| 22 | + default: () => defProps.checkboxGroup.value | |
| 23 | + }, | |
| 24 | + // #endif | |
| 25 | + // 形状,circle-圆形,square-方形 | |
| 26 | + shape: { | |
| 27 | + type: String, | |
| 28 | + default: () => defProps.checkboxGroup.shape | |
| 29 | + }, | |
| 30 | + // 是否禁用全部checkbox | |
| 31 | + disabled: { | |
| 32 | + type: Boolean, | |
| 33 | + default: () => defProps.checkboxGroup.disabled | |
| 34 | + }, | |
| 35 | + | |
| 36 | + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 | |
| 37 | + activeColor: { | |
| 38 | + type: String, | |
| 39 | + default: () => defProps.checkboxGroup.activeColor | |
| 40 | + }, | |
| 41 | + // 未选中的颜色 | |
| 42 | + inactiveColor: { | |
| 43 | + type: String, | |
| 44 | + default: () => defProps.checkboxGroup.inactiveColor | |
| 45 | + }, | |
| 46 | + | |
| 47 | + // 整个组件的尺寸,默认px | |
| 48 | + size: { | |
| 49 | + type: [String, Number], | |
| 50 | + default: () => defProps.checkboxGroup.size | |
| 51 | + }, | |
| 52 | + // 布局方式,row-横向,column-纵向 | |
| 53 | + placement: { | |
| 54 | + type: String, | |
| 55 | + default: () => defProps.checkboxGroup.placement | |
| 56 | + }, | |
| 57 | + // label的字体大小,px单位 | |
| 58 | + labelSize: { | |
| 59 | + type: [String, Number], | |
| 60 | + default: () => defProps.checkboxGroup.labelSize | |
| 61 | + }, | |
| 62 | + // label的字体颜色 | |
| 63 | + labelColor: { | |
| 64 | + type: [String], | |
| 65 | + default: () => defProps.checkboxGroup.labelColor | |
| 66 | + }, | |
| 67 | + // 是否禁止点击文本操作 | |
| 68 | + labelDisabled: { | |
| 69 | + type: Boolean, | |
| 70 | + default: () => defProps.checkboxGroup.labelDisabled | |
| 71 | + }, | |
| 72 | + // 图标颜色 | |
| 73 | + iconColor: { | |
| 74 | + type: String, | |
| 75 | + default: () => defProps.checkboxGroup.iconColor | |
| 76 | + }, | |
| 77 | + // 图标的大小,单位px | |
| 78 | + iconSize: { | |
| 79 | + type: [String, Number], | |
| 80 | + default: () => defProps.checkboxGroup.iconSize | |
| 81 | + }, | |
| 82 | + // 勾选图标的对齐方式,left-左边,right-右边 | |
| 83 | + iconPlacement: { | |
| 84 | + type: String, | |
| 85 | + default: () => defProps.checkboxGroup.iconPlacement | |
| 86 | + }, | |
| 87 | + // 竖向配列时,是否显示下划线 | |
| 88 | + borderBottom: { | |
| 89 | + type: Boolean, | |
| 90 | + default: () => defProps.checkboxGroup.borderBottom | |
| 91 | + } | |
| 92 | + } | |
| 93 | +}) | ... | ... |
uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view | |
| 3 | + class="u-checkbox-group" | |
| 4 | + :class="bemClass" | |
| 5 | + > | |
| 6 | + <slot></slot> | |
| 7 | + </view> | |
| 8 | +</template> | |
| 9 | + | |
| 10 | +<script> | |
| 11 | + import { props } from './props'; | |
| 12 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 13 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 14 | + /** | |
| 15 | + * checkboxGroup 复选框组 | |
| 16 | + * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便 | |
| 17 | + * @tutorial https://ijry.github.io/uview-plus/components/checkbox.html | |
| 18 | + * @property {String} name 标识符 | |
| 19 | + * @property {Array} value 绑定的值 | |
| 20 | + * @property {String} shape 形状,circle-圆形,square-方形 (默认 'square' ) | |
| 21 | + * @property {Boolean} disabled 是否禁用全部checkbox (默认 false ) | |
| 22 | + * @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' ) | |
| 23 | + * @property {String} inactiveColor 未选中的颜色 (默认 '#c8c9cc' ) | |
| 24 | + * @property {String | Number} size 整个组件的尺寸 单位px (默认 18 ) | |
| 25 | + * @property {String} placement 布局方式,row-横向,column-纵向 (默认 'row' ) | |
| 26 | + * @property {String | Number} labelSize label的字体大小,px单位 (默认 14 ) | |
| 27 | + * @property {String} labelColor label的字体颜色 (默认 '#303133' ) | |
| 28 | + * @property {Boolean} labelDisabled 是否禁止点击文本操作 (默认 false ) | |
| 29 | + * @property {String} iconColor 图标颜色 (默认 '#ffffff' ) | |
| 30 | + * @property {String | Number} iconSize 图标的大小,单位px (默认 12 ) | |
| 31 | + * @property {String} iconPlacement 勾选图标的对齐方式,left-左边,right-右边 (默认 'left' ) | |
| 32 | + * @property {Boolean} borderBottom placement为row时,是否显示下边框 (默认 false ) | |
| 33 | + * @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象 | |
| 34 | + * @event {Function} input 修改通过v-model绑定的值时触发,回调为一个对象 | |
| 35 | + * @example <u-checkbox-group></u-checkbox-group> | |
| 36 | + */ | |
| 37 | + export default { | |
| 38 | + name: 'u-checkbox-group', | |
| 39 | + mixins: [mpMixin, mixin,props], | |
| 40 | + computed: { | |
| 41 | + // 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化 | |
| 42 | + // 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group) | |
| 43 | + // 拉取父组件新的变化后的参数 | |
| 44 | + parentData() { | |
| 45 | + return [ | |
| 46 | + // #ifdef VUE2 | |
| 47 | + this.value, | |
| 48 | + // #endif | |
| 49 | + // #ifdef VUE3 | |
| 50 | + this.modelValue, | |
| 51 | + // #endif | |
| 52 | + this.disabled, | |
| 53 | + this.inactiveColor, | |
| 54 | + this.activeColor, | |
| 55 | + this.size, | |
| 56 | + this.labelDisabled, | |
| 57 | + this.shape, | |
| 58 | + this.iconSize, | |
| 59 | + this.borderBottom, | |
| 60 | + this.placement, | |
| 61 | + ]; | |
| 62 | + }, | |
| 63 | + bemClass() { | |
| 64 | + // this.bem为一个computed变量,在mixin中 | |
| 65 | + return this.bem('checkbox-group', ['placement']) | |
| 66 | + }, | |
| 67 | + }, | |
| 68 | + watch: { | |
| 69 | + // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件 | |
| 70 | + parentData: { | |
| 71 | + handler() { | |
| 72 | + if (this.children.length) { | |
| 73 | + this.children.map((child) => { | |
| 74 | + // 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值) | |
| 75 | + typeof child.init === "function" && child.init(); | |
| 76 | + }); | |
| 77 | + } | |
| 78 | + }, | |
| 79 | + deep: true, | |
| 80 | + }, | |
| 81 | + }, | |
| 82 | + data() { | |
| 83 | + return { | |
| 84 | + | |
| 85 | + } | |
| 86 | + }, | |
| 87 | + created() { | |
| 88 | + this.children = [] | |
| 89 | + }, | |
| 90 | + // #ifdef VUE3 | |
| 91 | + emits: ['update:modelValue', 'change'], | |
| 92 | + // #endif | |
| 93 | + methods: { | |
| 94 | + // 将其他的checkbox设置为未选中的状态 | |
| 95 | + unCheckedOther(childInstance) { | |
| 96 | + const values = [] | |
| 97 | + this.children.map(child => { | |
| 98 | + // 将被选中的checkbox,放到数组中返回 | |
| 99 | + if (child.isChecked) { | |
| 100 | + values.push(child.name) | |
| 101 | + } | |
| 102 | + }) | |
| 103 | + // 发出事件 | |
| 104 | + this.$emit('change', values) | |
| 105 | + // 修改通过v-model绑定的值 | |
| 106 | + // #ifdef VUE3 | |
| 107 | + this.$emit("update:modelValue", values); | |
| 108 | + // #endif | |
| 109 | + // #ifdef VUE2 | |
| 110 | + this.$emit("input", values); | |
| 111 | + // #endif | |
| 112 | + }, | |
| 113 | + } | |
| 114 | + } | |
| 115 | +</script> | |
| 116 | + | |
| 117 | +<style lang="scss" scoped> | |
| 118 | + @import "../../libs/css/components.scss"; | |
| 119 | + | |
| 120 | + .u-checkbox-group { | |
| 121 | + | |
| 122 | + &--row { | |
| 123 | + /* #ifndef APP-NVUE */ | |
| 124 | + display: flex; | |
| 125 | + /* #endif */ | |
| 126 | + flex-flow: row wrap; | |
| 127 | + } | |
| 128 | + | |
| 129 | + &--column { | |
| 130 | + @include flex(column); | |
| 131 | + } | |
| 132 | + } | |
| 133 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-checkbox/checkbox.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-23 21:06:59 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/checkbox.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // checkbox组件 | |
| 12 | + checkbox: { | |
| 13 | + name: '', | |
| 14 | + shape: '', | |
| 15 | + size: '', | |
| 16 | + checkbox: false, | |
| 17 | + disabled: '', | |
| 18 | + activeColor: '', | |
| 19 | + inactiveColor: '', | |
| 20 | + iconSize: '', | |
| 21 | + iconColor: '', | |
| 22 | + label: '', | |
| 23 | + labelSize: '', | |
| 24 | + labelColor: '', | |
| 25 | + labelDisabled: '' | |
| 26 | + } | |
| 27 | +} | ... | ... |
uni_modules/uview-plus/components/u-checkbox/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // checkbox的名称 | |
| 6 | + name: { | |
| 7 | + type: [String, Number, Boolean], | |
| 8 | + default: () => defProps.checkbox.name | |
| 9 | + }, | |
| 10 | + // 形状,square为方形,circle为圆型 | |
| 11 | + shape: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.checkbox.shape | |
| 14 | + }, | |
| 15 | + // 整体的大小 | |
| 16 | + size: { | |
| 17 | + type: [String, Number], | |
| 18 | + default: () => defProps.checkbox.size | |
| 19 | + }, | |
| 20 | + // 是否默认选中 | |
| 21 | + checked: { | |
| 22 | + type: Boolean, | |
| 23 | + default: () => defProps.checkbox.checked | |
| 24 | + }, | |
| 25 | + // 是否禁用 | |
| 26 | + disabled: { | |
| 27 | + type: [String, Boolean], | |
| 28 | + default: () => defProps.checkbox.disabled | |
| 29 | + }, | |
| 30 | + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 | |
| 31 | + activeColor: { | |
| 32 | + type: String, | |
| 33 | + default: () => defProps.checkbox.activeColor | |
| 34 | + }, | |
| 35 | + // 未选中的颜色 | |
| 36 | + inactiveColor: { | |
| 37 | + type: String, | |
| 38 | + default: () => defProps.checkbox.inactiveColor | |
| 39 | + }, | |
| 40 | + // 图标的大小,单位px | |
| 41 | + iconSize: { | |
| 42 | + type: [String, Number], | |
| 43 | + default: () => defProps.checkbox.iconSize | |
| 44 | + }, | |
| 45 | + // 图标颜色 | |
| 46 | + iconColor: { | |
| 47 | + type: String, | |
| 48 | + default: () => defProps.checkbox.iconColor | |
| 49 | + }, | |
| 50 | + // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式 | |
| 51 | + label: { | |
| 52 | + type: [String, Number], | |
| 53 | + default: () => defProps.checkbox.label | |
| 54 | + }, | |
| 55 | + // label的字体大小,px单位 | |
| 56 | + labelSize: { | |
| 57 | + type: [String, Number], | |
| 58 | + default: () => defProps.checkbox.labelSize | |
| 59 | + }, | |
| 60 | + // label的颜色 | |
| 61 | + labelColor: { | |
| 62 | + type: String, | |
| 63 | + default: () => defProps.checkbox.labelColor | |
| 64 | + }, | |
| 65 | + // 是否禁止点击提示语选中复选框 | |
| 66 | + labelDisabled: { | |
| 67 | + type: [String, Boolean], | |
| 68 | + default: () => defProps.checkbox.labelDisabled | |
| 69 | + }, | |
| 70 | + // 是否独立使用 | |
| 71 | + usedAlone: { | |
| 72 | + type: [Boolean], | |
| 73 | + default: () => false | |
| 74 | + } | |
| 75 | + } | |
| 76 | +}) | ... | ... |
uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view | |
| 3 | + class="u-checkbox cursor-pointer" | |
| 4 | + :style="[checkboxStyle]" | |
| 5 | + @tap.stop="wrapperClickHandler" | |
| 6 | + :class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']" | |
| 7 | + > | |
| 8 | + <view | |
| 9 | + class="u-checkbox__icon-wrap cursor-pointer" | |
| 10 | + @tap.stop="iconClickHandler" | |
| 11 | + :class="iconClasses" | |
| 12 | + :style="[iconWrapStyle]" | |
| 13 | + > | |
| 14 | + <slot name="icon"> | |
| 15 | + <u-icon | |
| 16 | + class="u-checkbox__icon-wrap__icon" | |
| 17 | + name="checkbox-mark" | |
| 18 | + :size="elIconSize" | |
| 19 | + :color="elIconColor" | |
| 20 | + /> | |
| 21 | + </slot> | |
| 22 | + </view> | |
| 23 | + <view class="u-checkbox__label-wrap cursor-pointer" @tap.stop="labelClickHandler"> | |
| 24 | + <slot name="label" :label="label" :elDisabled="elDisabled"> | |
| 25 | + <text | |
| 26 | + :style="{ | |
| 27 | + color: elDisabled ? elInactiveColor : elLabelColor, | |
| 28 | + fontSize: elLabelSize, | |
| 29 | + lineHeight: elLabelSize | |
| 30 | + }" | |
| 31 | + >{{label}}</text> | |
| 32 | + </slot> | |
| 33 | + </view> | |
| 34 | + </view> | |
| 35 | +</template> | |
| 36 | + | |
| 37 | +<script> | |
| 38 | + import { props } from './props'; | |
| 39 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 40 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 41 | + import { addStyle, addUnit, deepMerge, formValidate, error } from '../../libs/function/index'; | |
| 42 | + import test from '../../libs/function/test'; | |
| 43 | + /** | |
| 44 | + * checkbox 复选框 | |
| 45 | + * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便 | |
| 46 | + * @tutorial https://uview-plus.jiangruyi.com/components/checkbox.html | |
| 47 | + * @property {String | Number | Boolean} name checkbox组件的标示符 | |
| 48 | + * @property {String} shape 形状,square为方形,circle为圆型 | |
| 49 | + * @property {String | Number} size 整体的大小 | |
| 50 | + * @property {Boolean} checked 是否默认选中 | |
| 51 | + * @property {String | Boolean} disabled 是否禁用 | |
| 52 | + * @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 | |
| 53 | + * @property {String} inactiveColor 未选中的颜色 | |
| 54 | + * @property {String | Number} iconSize 图标的大小,单位px | |
| 55 | + * @property {String} iconColor 图标颜色 | |
| 56 | + * @property {String | Number} label label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式 | |
| 57 | + * @property {String} labelColor label的颜色 | |
| 58 | + * @property {String | Number} labelSize label的字体大小,px单位 | |
| 59 | + * @property {String | Boolean} labelDisabled 是否禁止点击提示语选中复选框 | |
| 60 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 61 | + * | |
| 62 | + * @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象 | |
| 63 | + * @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox> | |
| 64 | + */ | |
| 65 | + export default { | |
| 66 | + name: "u-checkbox", | |
| 67 | + mixins: [mpMixin, mixin, props], | |
| 68 | + data() { | |
| 69 | + return { | |
| 70 | + isChecked: false, | |
| 71 | + // 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式 | |
| 72 | + // 故只能使用如此方法 | |
| 73 | + parentData: { | |
| 74 | + iconSize: 12, | |
| 75 | + labelDisabled: null, | |
| 76 | + disabled: null, | |
| 77 | + shape: 'square', | |
| 78 | + activeColor: null, | |
| 79 | + inactiveColor: null, | |
| 80 | + size: 18, | |
| 81 | + // #ifdef VUE2 | |
| 82 | + value: null, | |
| 83 | + // #endif | |
| 84 | + // #ifdef VUE3 | |
| 85 | + modelValue: null, | |
| 86 | + // #endif | |
| 87 | + iconColor: null, | |
| 88 | + placement: 'row', | |
| 89 | + borderBottom: false, | |
| 90 | + iconPlacement: 'left' | |
| 91 | + } | |
| 92 | + } | |
| 93 | + }, | |
| 94 | + computed: { | |
| 95 | + // 是否禁用,如果父组件u-radios-group禁用的话,将会忽略子组件的配置 | |
| 96 | + elDisabled() { | |
| 97 | + return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false; | |
| 98 | + }, | |
| 99 | + // 是否禁用label点击 | |
| 100 | + elLabelDisabled() { | |
| 101 | + return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled : | |
| 102 | + false; | |
| 103 | + }, | |
| 104 | + // 组件尺寸,对应size的值,默认值为21px | |
| 105 | + elSize() { | |
| 106 | + return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21); | |
| 107 | + }, | |
| 108 | + // 组件的勾选图标的尺寸,默认12px | |
| 109 | + elIconSize() { | |
| 110 | + return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12); | |
| 111 | + }, | |
| 112 | + // 组件选中激活时的颜色 | |
| 113 | + elActiveColor() { | |
| 114 | + return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff'); | |
| 115 | + }, | |
| 116 | + // 组件选未中激活时的颜色 | |
| 117 | + elInactiveColor() { | |
| 118 | + return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor : | |
| 119 | + '#c8c9cc'); | |
| 120 | + }, | |
| 121 | + // label的颜色 | |
| 122 | + elLabelColor() { | |
| 123 | + return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266') | |
| 124 | + }, | |
| 125 | + // 组件的形状 | |
| 126 | + elShape() { | |
| 127 | + return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle'); | |
| 128 | + }, | |
| 129 | + // label大小 | |
| 130 | + elLabelSize() { | |
| 131 | + return addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize : | |
| 132 | + '15')) | |
| 133 | + }, | |
| 134 | + elIconColor() { | |
| 135 | + const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor : | |
| 136 | + '#ffffff'); | |
| 137 | + // 图标的颜色 | |
| 138 | + if (this.elDisabled) { | |
| 139 | + // disabled状态下,已勾选的checkbox图标改为elInactiveColor | |
| 140 | + return this.isChecked ? this.elInactiveColor : 'transparent' | |
| 141 | + } else { | |
| 142 | + return this.isChecked ? iconColor : 'transparent' | |
| 143 | + } | |
| 144 | + }, | |
| 145 | + iconClasses() { | |
| 146 | + let classes = [] | |
| 147 | + // 组件的形状 | |
| 148 | + classes.push('u-checkbox__icon-wrap--' + this.elShape) | |
| 149 | + if (this.elDisabled) { | |
| 150 | + classes.push('u-checkbox__icon-wrap--disabled') | |
| 151 | + } | |
| 152 | + if (this.isChecked && this.elDisabled) { | |
| 153 | + classes.push('u-checkbox__icon-wrap--disabled--checked') | |
| 154 | + } | |
| 155 | + // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效 | |
| 156 | + // #ifdef MP-ALIPAY || MP-TOUTIAO | |
| 157 | + classes = classes.join(' ') | |
| 158 | + // #endif | |
| 159 | + return classes | |
| 160 | + }, | |
| 161 | + iconWrapStyle() { | |
| 162 | + // checkbox的整体样式 | |
| 163 | + const style = {} | |
| 164 | + style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff' | |
| 165 | + style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor | |
| 166 | + style.width = addUnit(this.elSize) | |
| 167 | + style.height = addUnit(this.elSize) | |
| 168 | + // 如果是图标在右边的话,移除它的右边距 | |
| 169 | + if (!this.usedAlone) { | |
| 170 | + if (this.parentData.iconPlacement === 'right') { | |
| 171 | + style.marginRight = 0 | |
| 172 | + } | |
| 173 | + } | |
| 174 | + return style | |
| 175 | + }, | |
| 176 | + checkboxStyle() { | |
| 177 | + const style = {} | |
| 178 | + if (!this.usedAlone) { | |
| 179 | + if (this.parentData.borderBottom && this.parentData.placement === 'row') { | |
| 180 | + error('检测到您将borderBottom设置为true,需要同时将u-checkbox-group的placement设置为column才有效') | |
| 181 | + } | |
| 182 | + // 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔 | |
| 183 | + if (this.parentData.borderBottom && this.parentData.placement === 'column') { | |
| 184 | + style.paddingBottom = '8px' | |
| 185 | + } | |
| 186 | + } | |
| 187 | + return deepMerge(style, addStyle(this.customStyle)) | |
| 188 | + } | |
| 189 | + }, | |
| 190 | + mounted() { | |
| 191 | + this.init() | |
| 192 | + }, | |
| 193 | + emits: ["change", "update:checked"], | |
| 194 | + methods: { | |
| 195 | + init() { | |
| 196 | + if (!this.usedAlone) { | |
| 197 | + // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用 | |
| 198 | + this.updateParentData() | |
| 199 | + if (!this.parent) { | |
| 200 | + error('u-checkbox必须搭配u-checkbox-group组件使用') | |
| 201 | + } | |
| 202 | + // #ifdef VUE2 | |
| 203 | + const value = this.parentData.value | |
| 204 | + // #endif | |
| 205 | + // #ifdef VUE3 | |
| 206 | + const value = this.parentData.modelValue | |
| 207 | + // #endif | |
| 208 | + // 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断 | |
| 209 | + if (this.checked) { | |
| 210 | + this.isChecked = true | |
| 211 | + } else if (!this.usedAlone && test.array(value)) { | |
| 212 | + // 查找数组是是否存在this.name元素值 | |
| 213 | + this.isChecked = value.some(item => { | |
| 214 | + return item === this.name | |
| 215 | + }) | |
| 216 | + } | |
| 217 | + } else { | |
| 218 | + if (this.checked) { | |
| 219 | + this.isChecked = true | |
| 220 | + } | |
| 221 | + } | |
| 222 | + }, | |
| 223 | + updateParentData() { | |
| 224 | + this.getParentData('u-checkbox-group') | |
| 225 | + }, | |
| 226 | + // 横向两端排列时,点击组件即可触发选中事件 | |
| 227 | + wrapperClickHandler(e) { | |
| 228 | + if (!this.usedAlone) { | |
| 229 | + this.parentData.iconPlacement === 'right' && this.iconClickHandler(e) | |
| 230 | + } else { | |
| 231 | + this.iconClickHandler(e) | |
| 232 | + } | |
| 233 | + }, | |
| 234 | + // 点击图标 | |
| 235 | + iconClickHandler(e) { | |
| 236 | + this.preventEvent(e) | |
| 237 | + // 如果整体被禁用,不允许被点击 | |
| 238 | + if (!this.elDisabled) { | |
| 239 | + this.setRadioCheckedStatus() | |
| 240 | + } | |
| 241 | + }, | |
| 242 | + // 点击label | |
| 243 | + labelClickHandler(e) { | |
| 244 | + this.preventEvent(e) | |
| 245 | + // 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态 | |
| 246 | + if (!this.elLabelDisabled && !this.elDisabled) { | |
| 247 | + this.setRadioCheckedStatus() | |
| 248 | + } | |
| 249 | + }, | |
| 250 | + emitEvent() { | |
| 251 | + this.$emit('change', this.isChecked) | |
| 252 | + // 双向绑定 | |
| 253 | + if (this.usedAlone) { | |
| 254 | + this.$emit('update:checked', this.isChecked) | |
| 255 | + } | |
| 256 | + // 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时 | |
| 257 | + this.$nextTick(() => { | |
| 258 | + formValidate(this, 'change') | |
| 259 | + }) | |
| 260 | + }, | |
| 261 | + // 改变组件选中状态 | |
| 262 | + // 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-checkbox实例 | |
| 263 | + // 将本组件外的其他u-checkbox的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态 | |
| 264 | + setRadioCheckedStatus() { | |
| 265 | + // 将本组件标记为与原来相反的状态 | |
| 266 | + this.isChecked = !this.isChecked | |
| 267 | + this.emitEvent() | |
| 268 | + if (!this.usedAlone) { | |
| 269 | + typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this) | |
| 270 | + } | |
| 271 | + } | |
| 272 | + }, | |
| 273 | + watch:{ | |
| 274 | + checked(newValue, oldValue){ | |
| 275 | + if (newValue !== this.isChecked) { | |
| 276 | + this.isChecked = newValue | |
| 277 | + } | |
| 278 | + } | |
| 279 | + } | |
| 280 | + } | |
| 281 | +</script> | |
| 282 | + | |
| 283 | +<style lang="scss" scoped> | |
| 284 | + @import "../../libs/css/components.scss"; | |
| 285 | + $u-checkbox-icon-wrap-margin-right:6px !default; | |
| 286 | + $u-checkbox-icon-wrap-font-size:6px !default; | |
| 287 | + $u-checkbox-icon-wrap-border-width:1px !default; | |
| 288 | + $u-checkbox-icon-wrap-border-color:#c8c9cc !default; | |
| 289 | + $u-checkbox-icon-wrap-icon-line-height:0 !default; | |
| 290 | + $u-checkbox-icon-wrap-circle-border-radius:100% !default; | |
| 291 | + $u-checkbox-icon-wrap-square-border-radius:3px !default; | |
| 292 | + $u-checkbox-icon-wrap-checked-color:#fff !default; | |
| 293 | + $u-checkbox-icon-wrap-checked-background-color:red !default; | |
| 294 | + $u-checkbox-icon-wrap-checked-border-color:#2979ff !default; | |
| 295 | + $u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default; | |
| 296 | + $u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default; | |
| 297 | + $u-checkbox-label-margin-left:5px !default; | |
| 298 | + $u-checkbox-label-margin-right:12px !default; | |
| 299 | + $u-checkbox-label-color:$u-content-color !default; | |
| 300 | + $u-checkbox-label-font-size:15px !default; | |
| 301 | + $u-checkbox-label-disabled-color:#c8c9cc !default; | |
| 302 | + | |
| 303 | + .u-checkbox { | |
| 304 | + /* #ifndef APP-NVUE */ | |
| 305 | + @include flex(row); | |
| 306 | + /* #endif */ | |
| 307 | + overflow: hidden; | |
| 308 | + flex-direction: row; | |
| 309 | + align-items: center; | |
| 310 | + margin-bottom: 5px; | |
| 311 | + margin-top: 5px; | |
| 312 | + | |
| 313 | + &-label--left { | |
| 314 | + flex-direction: row | |
| 315 | + } | |
| 316 | + | |
| 317 | + &-label--right { | |
| 318 | + flex-direction: row-reverse; | |
| 319 | + justify-content: space-between | |
| 320 | + } | |
| 321 | + | |
| 322 | + &__icon-wrap { | |
| 323 | + /* #ifndef APP-NVUE */ | |
| 324 | + box-sizing: border-box; | |
| 325 | + // nvue下,border-color过渡有问题 | |
| 326 | + transition-property: border-color, background-color, color; | |
| 327 | + transition-duration: 0.2s; | |
| 328 | + /* #endif */ | |
| 329 | + color: $u-content-color; | |
| 330 | + @include flex; | |
| 331 | + align-items: center; | |
| 332 | + justify-content: center; | |
| 333 | + color: transparent; | |
| 334 | + text-align: center; | |
| 335 | + margin-right: $u-checkbox-icon-wrap-margin-right; | |
| 336 | + | |
| 337 | + font-size: $u-checkbox-icon-wrap-font-size; | |
| 338 | + border-width: $u-checkbox-icon-wrap-border-width; | |
| 339 | + border-color: $u-checkbox-icon-wrap-border-color; | |
| 340 | + border-style: solid; | |
| 341 | + | |
| 342 | + /* #ifdef MP-TOUTIAO */ | |
| 343 | + // 头条小程序兼容性问题,需要设置行高为0,否则图标偏下 | |
| 344 | + &__icon { | |
| 345 | + line-height: $u-checkbox-icon-wrap-icon-line-height; | |
| 346 | + } | |
| 347 | + | |
| 348 | + /* #endif */ | |
| 349 | + | |
| 350 | + &--circle { | |
| 351 | + border-radius: $u-checkbox-icon-wrap-circle-border-radius; | |
| 352 | + } | |
| 353 | + | |
| 354 | + &--square { | |
| 355 | + border-radius: $u-checkbox-icon-wrap-square-border-radius; | |
| 356 | + } | |
| 357 | + | |
| 358 | + &--checked { | |
| 359 | + color: $u-checkbox-icon-wrap-checked-color; | |
| 360 | + background-color: $u-checkbox-icon-wrap-checked-background-color; | |
| 361 | + border-color: $u-checkbox-icon-wrap-checked-border-color; | |
| 362 | + } | |
| 363 | + | |
| 364 | + &--disabled { | |
| 365 | + background-color: $u-checkbox-icon-wrap-disabled-background-color !important; | |
| 366 | + } | |
| 367 | + | |
| 368 | + &--disabled--checked { | |
| 369 | + color: $u-checkbox-icon-wrap-disabled-checked-color !important; | |
| 370 | + } | |
| 371 | + } | |
| 372 | + | |
| 373 | + &__label { | |
| 374 | + /* #ifndef APP-NVUE */ | |
| 375 | + word-wrap: break-word; | |
| 376 | + /* #endif */ | |
| 377 | + margin-left: $u-checkbox-label-margin-left; | |
| 378 | + margin-right: $u-checkbox-label-margin-right; | |
| 379 | + color: $u-checkbox-label-color; | |
| 380 | + font-size: $u-checkbox-label-font-size; | |
| 381 | + | |
| 382 | + &--disabled { | |
| 383 | + color: $u-checkbox-label-disabled-color; | |
| 384 | + } | |
| 385 | + } | |
| 386 | + } | |
| 387 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-choose/u-choose.vue
0 → 100644
| 1 | +<style scoped lang="scss"> | |
| 2 | + .up-choose { | |
| 3 | + ::v-deep .up-tag { | |
| 4 | + font-weight: 600; | |
| 5 | + } | |
| 6 | + &:last-child { | |
| 7 | + margin-right: 0; | |
| 8 | + } | |
| 9 | + } | |
| 10 | + | |
| 11 | + .up-choose-wrap { | |
| 12 | + flex-wrap: wrap; | |
| 13 | + } | |
| 14 | + | |
| 15 | + .up-choose-nowrap { | |
| 16 | + flex-wrap: nowrap; | |
| 17 | + white-space: nowrap; | |
| 18 | + } | |
| 19 | +</style> | |
| 20 | + | |
| 21 | +<template> | |
| 22 | + <scroll-view | |
| 23 | + :scroll-x="wrap === false" | |
| 24 | + :class="['up-choose', wrap ? 'up-choose-wrap' : 'up-choose-nowrap']"> | |
| 25 | + <template :key="item.id" v-for="(item,index) in options"> | |
| 26 | + <view :style="{width: itemWidth, display: 'inline-block'}"> | |
| 27 | + <slot :item="item" :index="index"> | |
| 28 | + <up-tag :type="index == currentIndex ? 'primary' : 'info'" | |
| 29 | + size="large" :plain="index == currentIndex ? false : true" | |
| 30 | + :class="currentIndex === index ? 'active': ''" :height="itemHeight" | |
| 31 | + :style="{width: itemWidth, padding: itemPadding}" | |
| 32 | + @click="change(index)"> | |
| 33 | + {{item[labelName]}} | |
| 34 | + </up-tag> | |
| 35 | + </slot> | |
| 36 | + </view> | |
| 37 | + </template> | |
| 38 | + </scroll-view> | |
| 39 | +</template> | |
| 40 | + | |
| 41 | +<script> | |
| 42 | + export default { | |
| 43 | + name: 'up-choose', | |
| 44 | + props: { | |
| 45 | + options:{ | |
| 46 | + type: Array, | |
| 47 | + default: ()=>{ | |
| 48 | + return []; | |
| 49 | + } | |
| 50 | + }, | |
| 51 | + modelValue: { | |
| 52 | + type: [Number,String,Array], | |
| 53 | + default: false | |
| 54 | + }, | |
| 55 | + type: { | |
| 56 | + type: [String], | |
| 57 | + default: 'radio' | |
| 58 | + }, | |
| 59 | + itemWidth: { | |
| 60 | + type: [String], | |
| 61 | + default: 'auto' | |
| 62 | + }, | |
| 63 | + itemHeight: { | |
| 64 | + type: [String], | |
| 65 | + default: '50px' | |
| 66 | + }, | |
| 67 | + itemPadding: { | |
| 68 | + type: [String], | |
| 69 | + default: '8px' | |
| 70 | + }, | |
| 71 | + labelName: { | |
| 72 | + type: String, | |
| 73 | + default: 'title' | |
| 74 | + }, | |
| 75 | + valueName: { | |
| 76 | + type: String, | |
| 77 | + default: 'value' | |
| 78 | + }, | |
| 79 | + customClick: { | |
| 80 | + type: Boolean, | |
| 81 | + default: false | |
| 82 | + }, | |
| 83 | + // 是否换行 | |
| 84 | + wrap: { | |
| 85 | + type: Boolean, | |
| 86 | + default: true | |
| 87 | + } | |
| 88 | + }, | |
| 89 | + data() { | |
| 90 | + return { | |
| 91 | + currentIndex: '' | |
| 92 | + } | |
| 93 | + }, | |
| 94 | + created: function () { | |
| 95 | + this.currentIndex = this.modelValue; | |
| 96 | + }, | |
| 97 | + emits: ['update:modelValue', 'custom-click'], | |
| 98 | + methods: { | |
| 99 | + change(index){ | |
| 100 | + if (this.customClick) { | |
| 101 | + this.$emit('custom-click', index); | |
| 102 | + } else { | |
| 103 | + this.currentIndex = index; | |
| 104 | + this.$emit('update:modelValue', index); | |
| 105 | + } | |
| 106 | + } | |
| 107 | + } | |
| 108 | + } | |
| 109 | +</script> | |
| 0 | 110 | \ No newline at end of file | ... | ... |
uni_modules/uview-plus/components/u-circle-progress/circleProgress.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:55:02 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/circleProgress.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // circleProgress 组件 | |
| 12 | + circleProgress: { | |
| 13 | + percentage: 30 | |
| 14 | + } | |
| 15 | +} | ... | ... |
uni_modules/uview-plus/components/u-circle-progress/props.js
0 → 100644
uni_modules/uview-plus/components/u-circle-progress/u-circle-progress.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-circle-progress"> | |
| 3 | + <view class="u-circle-progress__left"> | |
| 4 | + <view | |
| 5 | + class="u-circle-progress__left__circle" | |
| 6 | + :style="[leftSyle]" | |
| 7 | + ref="left-circle" | |
| 8 | + > | |
| 9 | + | |
| 10 | + </view> | |
| 11 | + </view> | |
| 12 | + <view | |
| 13 | + class="u-circle-progress__right" | |
| 14 | + > | |
| 15 | + <view | |
| 16 | + class="u-circle-progress__right__circle" | |
| 17 | + ref="right-circle" | |
| 18 | + :style="[rightSyle]" | |
| 19 | + > | |
| 20 | + | |
| 21 | + </view> | |
| 22 | + </view> | |
| 23 | + <view class="u-circle-progress__circle"> | |
| 24 | + | |
| 25 | + </view> | |
| 26 | + </view> | |
| 27 | +</template> | |
| 28 | + | |
| 29 | +<script> | |
| 30 | + import { props } from './props'; | |
| 31 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 32 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 33 | + import {sleep } from '../../libs/function/index'; | |
| 34 | + // #ifdef APP-NVUE | |
| 35 | + const animation = uni.requireNativePlugin('animation') | |
| 36 | + // #endif | |
| 37 | + /** | |
| 38 | + * CircleProgress 圆形进度条 TODO: 待完善 | |
| 39 | + * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。 | |
| 40 | + * @tutorial https://ijry.github.io/uview-plus/components/circleProgress.html | |
| 41 | + * @property {String | Number} percentage 圆环进度百分比值,为数值类型,0-100 (默认 30 ) | |
| 42 | + * @example | |
| 43 | + */ | |
| 44 | + export default { | |
| 45 | + name: 'u-circle-progress', | |
| 46 | + mixins: [mpMixin, mixin, props], | |
| 47 | + data() { | |
| 48 | + return { | |
| 49 | + leftBorderColor: 'rgb(200, 200, 200)', | |
| 50 | + rightBorderColor: 'rgb(200, 200, 200)', | |
| 51 | + } | |
| 52 | + }, | |
| 53 | + computed: { | |
| 54 | + leftSyle() { | |
| 55 | + const style = {} | |
| 56 | + style.borderTopColor = this.leftBorderColor | |
| 57 | + style.borderRightColor = this.leftBorderColor | |
| 58 | + return style | |
| 59 | + }, | |
| 60 | + rightSyle() { | |
| 61 | + const style = {} | |
| 62 | + style.borderLeftColor = this.rightBorderColor | |
| 63 | + style.borderBottomColor = this.rightBorderColor | |
| 64 | + return style | |
| 65 | + } | |
| 66 | + }, | |
| 67 | + mounted() { | |
| 68 | + sleep().then(() => { | |
| 69 | + this.rightBorderColor = 'rgb(66, 185, 131)' | |
| 70 | + // this.init() | |
| 71 | + }) | |
| 72 | + }, | |
| 73 | + methods: { | |
| 74 | + init() { | |
| 75 | + animation.transition(this.$refs['right-circle'].ref, { | |
| 76 | + styles: { | |
| 77 | + transform: 'rotate(45deg)', | |
| 78 | + transformOrigin: 'center center' | |
| 79 | + }, | |
| 80 | + }, () => { | |
| 81 | + this.rightBorderColor = 'rgb(66, 185, 131)' | |
| 82 | + // animation.transition(this.$refs['right-circle'].ref, { | |
| 83 | + // styles: { | |
| 84 | + // transform: 'rotate(225deg)', | |
| 85 | + // transformOrigin: 'center center' | |
| 86 | + // }, | |
| 87 | + // duration: 3000, | |
| 88 | + // }, () => { | |
| 89 | + // animation.transition(this.$refs['left-circle'].ref, { | |
| 90 | + // styles: { | |
| 91 | + // transform: 'rotate(45deg)', | |
| 92 | + // transformOrigin: 'center center' | |
| 93 | + // }, | |
| 94 | + // }, () => { | |
| 95 | + // this.leftBorderColor = 'rgb(66, 185, 131)' | |
| 96 | + // animation.transition(this.$refs['left-circle'].ref, { | |
| 97 | + // styles: { | |
| 98 | + // transform: 'rotate(225deg)', | |
| 99 | + // transformOrigin: 'center center' | |
| 100 | + // }, | |
| 101 | + // duration: 1500, | |
| 102 | + // }, () => { | |
| 103 | + | |
| 104 | + // }) | |
| 105 | + // }) | |
| 106 | + // }) | |
| 107 | + }) | |
| 108 | + | |
| 109 | + } | |
| 110 | + }, | |
| 111 | + } | |
| 112 | +</script> | |
| 113 | + | |
| 114 | +<style lang="scss" scoped> | |
| 115 | + @import "../../libs/css/components.scss"; | |
| 116 | + | |
| 117 | + .u-circle-progress { | |
| 118 | + @include flex(row); | |
| 119 | + position: relative; | |
| 120 | + border-radius: 100px; | |
| 121 | + height: 100px; | |
| 122 | + width: 100px; | |
| 123 | + // transform: rotate(0deg); | |
| 124 | + // background-color: rgb(66, 185, 131); | |
| 125 | + background-color: rgb(200, 200, 200); | |
| 126 | + overflow: hidden; | |
| 127 | + justify-content: space-between; | |
| 128 | + | |
| 129 | + &__circle { | |
| 130 | + border-radius: 100px; | |
| 131 | + height: 90px; | |
| 132 | + width: 90px; | |
| 133 | + transform: translate(-50%, -50%); | |
| 134 | + background-color: rgb(255, 255, 255); | |
| 135 | + left: 50px; | |
| 136 | + top: 50px; | |
| 137 | + position: absolute; | |
| 138 | + } | |
| 139 | + | |
| 140 | + &__left { | |
| 141 | + position: absolute; | |
| 142 | + left: 0; | |
| 143 | + width: 50px; | |
| 144 | + height: 100px; | |
| 145 | + overflow: hidden; | |
| 146 | + box-sizing: border-box; | |
| 147 | + // background-color: rgb(66, 185, 131); | |
| 148 | + // background-color: rgb(200, 200, 200); | |
| 149 | + // transform-origin: left center; | |
| 150 | + | |
| 151 | + &__circle { | |
| 152 | + box-sizing: border-box; | |
| 153 | + // background-color: red; | |
| 154 | + border-left-color: transparent; | |
| 155 | + border-bottom-color: transparent; | |
| 156 | + border-top-left-radius: 50px; | |
| 157 | + border-top-right-radius: 50px; | |
| 158 | + border-bottom-right-radius: 50px; | |
| 159 | + // border-left-color: rgb(66, 185, 131); | |
| 160 | + // border-bottom-color: rgb(66, 185, 131); | |
| 161 | + border-top-color: rgb(66, 185, 131); | |
| 162 | + border-right-color: rgb(66, 185, 131); | |
| 163 | + border-width: 5px; | |
| 164 | + width: 100px; | |
| 165 | + height: 100px; | |
| 166 | + transform: rotate(225deg); | |
| 167 | + // border-radius: 100px; | |
| 168 | + } | |
| 169 | + } | |
| 170 | + | |
| 171 | + &__right { | |
| 172 | + position: absolute; | |
| 173 | + right: 0; | |
| 174 | + width: 50px; | |
| 175 | + height: 100px; | |
| 176 | + overflow: hidden; | |
| 177 | + | |
| 178 | + &__circle { | |
| 179 | + position: absolute; | |
| 180 | + right: 0; | |
| 181 | + box-sizing: border-box; | |
| 182 | + // background-color: red; | |
| 183 | + border-top-color: transparent; | |
| 184 | + border-right-color: transparent; | |
| 185 | + border-top-left-radius: 50px; | |
| 186 | + border-bottom-left-radius: 50px; | |
| 187 | + border-bottom-right-radius: 50px; | |
| 188 | + // border-left-color: rgb(66, 185, 131); | |
| 189 | + // border-bottom-color: rgb(66, 185, 131); | |
| 190 | + border-left-color: rgb(200, 200, 200); | |
| 191 | + border-bottom-color: rgb(200, 200, 200); | |
| 192 | + border-width: 5px; | |
| 193 | + width: 100px; | |
| 194 | + height: 100px; | |
| 195 | + transform: rotate(45deg); | |
| 196 | + transform-origin: center center; | |
| 197 | + // border-radius: 100px; | |
| 198 | + } | |
| 199 | + } | |
| 200 | + } | |
| 201 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-city-locate/u-city-locate.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-city-locate"> | |
| 3 | + <up-index-list :indexList="indexList"> | |
| 4 | + <template #header> | |
| 5 | + <view class="u-current-city-wrap"> | |
| 6 | + <view class="u-current-city-title">{{ t("up.cityLocate.locateCity") }}</view> | |
| 7 | + <view class="u-current-city-item" @tap="location"> | |
| 8 | + <view class="u-location-city">{{locationCity}}</view> | |
| 9 | + </view> | |
| 10 | + </view> | |
| 11 | + </template> | |
| 12 | + <template :key="index" v-for="(item, index) in cityList"> | |
| 13 | + <!-- #ifdef APP-NVUE --> | |
| 14 | + <up-index-anchor :text="indexList[index]"></up-index-anchor> | |
| 15 | + <!-- #endif --> | |
| 16 | + <up-index-item> | |
| 17 | + <!-- #ifndef APP-NVUE --> | |
| 18 | + <up-index-anchor :text="indexList[index]"></up-index-anchor> | |
| 19 | + <!-- #endif --> | |
| 20 | + <view class="hot-city-list" v-if="index == 0"> | |
| 21 | + <view class="" v-for="(item1, index1) in item" @tap="selectedCity(item1)"> | |
| 22 | + <view class="hot-city-item">{{ item1[nameKey] }}</view> | |
| 23 | + </view> | |
| 24 | + </view> | |
| 25 | + <view v-else class="item-list" v-for="(item1, index1) in item" :key="index1"> | |
| 26 | + <view class="list__item" @tap="selectedCity(item1)"> | |
| 27 | + <text class="list__item__city-name">{{item1[nameKey]}}</text> | |
| 28 | + </view> | |
| 29 | + <up-line></up-line> | |
| 30 | + </view> | |
| 31 | + </up-index-item> | |
| 32 | + </template> | |
| 33 | + <template #footer> | |
| 34 | + <view class="u-safe-area-inset--bottom"> | |
| 35 | + <text class="list__footer"></text> | |
| 36 | + </view> | |
| 37 | + </template> | |
| 38 | + </up-index-list> | |
| 39 | + </view> | |
| 40 | +</template> | |
| 41 | + | |
| 42 | +<script> | |
| 43 | + import { t } from '../../libs/i18n' | |
| 44 | + export default{ | |
| 45 | + name: 'u-city-locate', | |
| 46 | + props:{ | |
| 47 | + indexList: { | |
| 48 | + type: Array, | |
| 49 | + default: ['🔥'] | |
| 50 | + }, | |
| 51 | + cityList:{ | |
| 52 | + type: Array, | |
| 53 | + default: () => { | |
| 54 | + return [ | |
| 55 | + [{ | |
| 56 | + name: '北京', | |
| 57 | + value: 'beijing' | |
| 58 | + }, | |
| 59 | + { | |
| 60 | + name: '上海', | |
| 61 | + value: 'shanghai' | |
| 62 | + }, | |
| 63 | + { | |
| 64 | + name: '广州', | |
| 65 | + value: 'guangzhou' | |
| 66 | + }, | |
| 67 | + { | |
| 68 | + name: '深圳', | |
| 69 | + value: 'shenzhen' | |
| 70 | + }, | |
| 71 | + { | |
| 72 | + name: '杭州', | |
| 73 | + value: 'hangzhou' | |
| 74 | + }] | |
| 75 | + ] | |
| 76 | + } | |
| 77 | + }, | |
| 78 | + locationType: { | |
| 79 | + type: String, | |
| 80 | + default: 'wgs84' | |
| 81 | + }, | |
| 82 | + currentCity: { | |
| 83 | + type: String, | |
| 84 | + default: '' | |
| 85 | + }, | |
| 86 | + nameKey: { | |
| 87 | + type: String, | |
| 88 | + default: 'name' | |
| 89 | + } | |
| 90 | + }, | |
| 91 | + computed:{ | |
| 92 | + }, | |
| 93 | + watch:{ | |
| 94 | + currentCity(val) { | |
| 95 | + this.locationCity = val; | |
| 96 | + } | |
| 97 | + }, | |
| 98 | + data(){ | |
| 99 | + return{ | |
| 100 | + locationCity: t("up.cityLocate.locating") + '....' | |
| 101 | + } | |
| 102 | + }, | |
| 103 | + emits: ['location-success', 'select-city'], | |
| 104 | + methods:{ | |
| 105 | + t, | |
| 106 | + // 获取城市 | |
| 107 | + selectedCity(city){ | |
| 108 | + this.locationCity = city[this.nameKey]; | |
| 109 | + this.$emit('select-city', { | |
| 110 | + locationCity: this.locationCity | |
| 111 | + }); | |
| 112 | + }, | |
| 113 | + // 定位操作 | |
| 114 | + location(){ | |
| 115 | + let That = this; | |
| 116 | + uni.getLocation({ | |
| 117 | + type: this.locationType, | |
| 118 | + geocode:true, | |
| 119 | + success(res){ | |
| 120 | + console.log(res); | |
| 121 | + That.locationCity = res.address && res.address.city; | |
| 122 | + That.$emit('location-success', { | |
| 123 | + ...res, | |
| 124 | + locationCity: That.locationCity | |
| 125 | + }); | |
| 126 | + }, | |
| 127 | + fail(){ | |
| 128 | + That.locationCity = t("up.cityLocate.fail"); | |
| 129 | + } | |
| 130 | + }); | |
| 131 | + }, | |
| 132 | + }, | |
| 133 | + // 页面挂载后进行异步操作 | |
| 134 | + created(){ | |
| 135 | + }, | |
| 136 | + mounted(){ | |
| 137 | + this.location(); | |
| 138 | + } | |
| 139 | + } | |
| 140 | +</script> | |
| 141 | + | |
| 142 | +<style lang="scss"> | |
| 143 | + .list__item { | |
| 144 | + padding: 8px 1px; | |
| 145 | + } | |
| 146 | + .u-current-city-title { | |
| 147 | + color: grey; | |
| 148 | + margin-bottom: 5px; | |
| 149 | + } | |
| 150 | + .u-current-city-item { | |
| 151 | + height: 30px; | |
| 152 | + } | |
| 153 | + .hot-city-list { | |
| 154 | + display: flex !important; | |
| 155 | + flex-direction: row !important; | |
| 156 | + padding: 12px 0; | |
| 157 | + .hot-city-item { | |
| 158 | + padding: 6px 12px; | |
| 159 | + margin: 5px; | |
| 160 | + border: 1px solid #ededed; | |
| 161 | + } | |
| 162 | + } | |
| 163 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-code-input/codeInput.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:55:58 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/codeInput.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // codeInput 组件 | |
| 12 | + codeInput: { | |
| 13 | + adjustPosition: true, | |
| 14 | + maxlength: 6, | |
| 15 | + dot: false, | |
| 16 | + mode: 'box', | |
| 17 | + hairline: false, | |
| 18 | + space: 10, | |
| 19 | + value: '', | |
| 20 | + focus: false, | |
| 21 | + bold: false, | |
| 22 | + color: '#606266', | |
| 23 | + fontSize: 18, | |
| 24 | + size: 35, | |
| 25 | + disabledKeyboard: false, | |
| 26 | + borderColor: '#c9cacc', | |
| 27 | + disabledDot: true | |
| 28 | + } | |
| 29 | +} | ... | ... |
uni_modules/uview-plus/components/u-code-input/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 键盘弹起时,是否自动上推页面 | |
| 6 | + adjustPosition: { | |
| 7 | + type: Boolean, | |
| 8 | + default: () => defProps.codeInput.adjustPosition | |
| 9 | + }, | |
| 10 | + // 最大输入长度 | |
| 11 | + maxlength: { | |
| 12 | + type: [String, Number], | |
| 13 | + default: () => defProps.codeInput.maxlength | |
| 14 | + }, | |
| 15 | + // 是否用圆点填充 | |
| 16 | + dot: { | |
| 17 | + type: Boolean, | |
| 18 | + default: () => defProps.codeInput.dot | |
| 19 | + }, | |
| 20 | + // 显示模式,box-盒子模式,line-底部横线模式 | |
| 21 | + mode: { | |
| 22 | + type: String, | |
| 23 | + default: () => defProps.codeInput.mode | |
| 24 | + }, | |
| 25 | + // 是否细边框 | |
| 26 | + hairline: { | |
| 27 | + type: Boolean, | |
| 28 | + default: () => defProps.codeInput.hairline | |
| 29 | + }, | |
| 30 | + // 字符间的距离 | |
| 31 | + space: { | |
| 32 | + type: [String, Number], | |
| 33 | + default: () => defProps.codeInput.space | |
| 34 | + }, | |
| 35 | + // #ifdef VUE3 | |
| 36 | + // 预置值 | |
| 37 | + modelValue: { | |
| 38 | + type: [String, Number], | |
| 39 | + default: () => defProps.codeInput.value | |
| 40 | + }, | |
| 41 | + // #endif | |
| 42 | + // #ifdef VUE2 | |
| 43 | + // 预置值 | |
| 44 | + value: { | |
| 45 | + type: [String, Number], | |
| 46 | + default: () => defProps.codeInput.value | |
| 47 | + }, | |
| 48 | + // #endif | |
| 49 | + // 是否自动获取焦点 | |
| 50 | + focus: { | |
| 51 | + type: Boolean, | |
| 52 | + default: () => defProps.codeInput.focus | |
| 53 | + }, | |
| 54 | + // 字体是否加粗 | |
| 55 | + bold: { | |
| 56 | + type: Boolean, | |
| 57 | + default: () => defProps.codeInput.bold | |
| 58 | + }, | |
| 59 | + // 字体颜色 | |
| 60 | + color: { | |
| 61 | + type: String, | |
| 62 | + default: () => defProps.codeInput.color | |
| 63 | + }, | |
| 64 | + // 字体大小 | |
| 65 | + fontSize: { | |
| 66 | + type: [String, Number], | |
| 67 | + default: () => defProps.codeInput.fontSize | |
| 68 | + }, | |
| 69 | + // 输入框的大小,宽等于高 | |
| 70 | + size: { | |
| 71 | + type: [String, Number], | |
| 72 | + default: () => defProps.codeInput.size | |
| 73 | + }, | |
| 74 | + // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true | |
| 75 | + disabledKeyboard: { | |
| 76 | + type: Boolean, | |
| 77 | + default: () => defProps.codeInput.disabledKeyboard | |
| 78 | + }, | |
| 79 | + // 边框和线条颜色 | |
| 80 | + borderColor: { | |
| 81 | + type: String, | |
| 82 | + default: () => defProps.codeInput.borderColor | |
| 83 | + }, | |
| 84 | + // 是否禁止输入"."符号 | |
| 85 | + disabledDot: { | |
| 86 | + type: Boolean, | |
| 87 | + default: () => defProps.codeInput.disabledDot | |
| 88 | + } | |
| 89 | + } | |
| 90 | +}) | ... | ... |
uni_modules/uview-plus/components/u-code-input/u-code-input.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-code-input"> | |
| 3 | + <view | |
| 4 | + class="u-code-input__item" | |
| 5 | + :style="[itemStyle(index)]" | |
| 6 | + v-for="(item, index) in codeLength" | |
| 7 | + :key="index" | |
| 8 | + > | |
| 9 | + <view | |
| 10 | + class="u-code-input__item__dot" | |
| 11 | + v-if="dot && codeArray.length > index" | |
| 12 | + ></view> | |
| 13 | + <text | |
| 14 | + v-else | |
| 15 | + :style="{ | |
| 16 | + fontSize: addUnit(fontSize), | |
| 17 | + fontWeight: bold ? 'bold' : 'normal', | |
| 18 | + color: color | |
| 19 | + }" | |
| 20 | + >{{codeArray[index]}}</text> | |
| 21 | + <view | |
| 22 | + class="u-code-input__item__line" | |
| 23 | + v-if="mode === 'line'" | |
| 24 | + :style="[lineStyle]" | |
| 25 | + ></view> | |
| 26 | + <!-- #ifndef APP-NVUE --> | |
| 27 | + <view v-if="isFocus && codeArray.length === index" | |
| 28 | + :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view> | |
| 29 | + <!-- #endif --> | |
| 30 | + <!-- #ifdef APP-NVUE --> | |
| 31 | + <view v-if="isFocus && codeArray.length === index" | |
| 32 | + :style="{backgroundColor: color, opacity: opacity}" class="u-code-input__item__cursor"></view> | |
| 33 | + <!-- #endif --> | |
| 34 | + </view> | |
| 35 | + <input | |
| 36 | + :disabled="disabledKeyboard" | |
| 37 | + type="number" | |
| 38 | + :focus="focus" | |
| 39 | + :value="inputValue" | |
| 40 | + :maxlength="maxlength" | |
| 41 | + :adjustPosition="adjustPosition" | |
| 42 | + class="u-code-input__input" | |
| 43 | + @input="inputHandler" | |
| 44 | + :style="{ | |
| 45 | + height: addUnit(size) | |
| 46 | + }" | |
| 47 | + @focus="isFocus = true" | |
| 48 | + @blur="isFocus = false" | |
| 49 | + /> | |
| 50 | + </view> | |
| 51 | +</template> | |
| 52 | + | |
| 53 | +<script> | |
| 54 | + import { props } from './props'; | |
| 55 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 56 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 57 | + import { addUnit, getPx } from '../../libs/function/index'; | |
| 58 | + /** | |
| 59 | + * CodeInput 验证码输入 | |
| 60 | + * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uview-plus的键盘组件使用 | |
| 61 | + * @tutorial https://ijry.github.io/uview-plus/components/codeInput.html | |
| 62 | + * @property {String | Number} maxlength 最大输入长度 (默认 6 ) | |
| 63 | + * @property {Boolean} dot 是否用圆点填充 (默认 false ) | |
| 64 | + * @property {String} mode 显示模式,box-盒子模式,line-底部横线模式 (默认 'box' ) | |
| 65 | + * @property {Boolean} hairline 是否细边框 (默认 false ) | |
| 66 | + * @property {String | Number} space 字符间的距离 (默认 10 ) | |
| 67 | + * @property {String | Number} value 预置值 | |
| 68 | + * @property {Boolean} focus 是否自动获取焦点 (默认 false ) | |
| 69 | + * @property {Boolean} bold 字体和输入横线是否加粗 (默认 false ) | |
| 70 | + * @property {String} color 字体颜色 (默认 '#606266' ) | |
| 71 | + * @property {String | Number} fontSize 字体大小,单位px (默认 18 ) | |
| 72 | + * @property {String | Number} size 输入框的大小,宽等于高 (默认 35 ) | |
| 73 | + * @property {Boolean} disabledKeyboard 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false ) | |
| 74 | + * @property {String} borderColor 边框和线条颜色 (默认 '#c9cacc' ) | |
| 75 | + * @property {Boolean} disabledDot 是否禁止输入"."符号 (默认 true ) | |
| 76 | + * | |
| 77 | + * @event {Function} change 输入内容发生改变时触发,具体见上方说明 value:当前输入的值 | |
| 78 | + * @event {Function} finish 输入字符个数达maxlength值时触发,见上方说明 value:当前输入的值 | |
| 79 | + * @example <u-code-input v-model="value4" :focus="true"></u-code-input> | |
| 80 | + */ | |
| 81 | + export default { | |
| 82 | + name: 'u-code-input', | |
| 83 | + mixins: [mpMixin, mixin, props], | |
| 84 | + data() { | |
| 85 | + return { | |
| 86 | + inputValue: '', | |
| 87 | + isFocus: this.focus, | |
| 88 | + timer: null, | |
| 89 | + opacity: 1 | |
| 90 | + } | |
| 91 | + }, | |
| 92 | + watch: { | |
| 93 | + // #ifdef VUE2 | |
| 94 | + value: { | |
| 95 | + immediate: true, | |
| 96 | + handler(val) { | |
| 97 | + // 转为字符串,超出部分截掉 | |
| 98 | + this.inputValue = String(val).substring(0, this.maxlength) | |
| 99 | + } | |
| 100 | + }, | |
| 101 | + // #endif | |
| 102 | + // #ifdef VUE3 | |
| 103 | + modelValue: { | |
| 104 | + immediate: true, | |
| 105 | + handler(val) { | |
| 106 | + // 转为字符串,超出部分截掉 | |
| 107 | + this.inputValue = String(val).substring(0, this.maxlength) | |
| 108 | + } | |
| 109 | + }, | |
| 110 | + // #endif | |
| 111 | + isFocus: { | |
| 112 | + handler(val) { | |
| 113 | + // #ifdef APP-NVUE | |
| 114 | + if (val) { | |
| 115 | + this.timer = setInterval(() => { | |
| 116 | + this.opacity = Math.abs(this.opacity - 1) | |
| 117 | + }, 600) | |
| 118 | + } else { | |
| 119 | + clearInterval(this.timer) | |
| 120 | + } | |
| 121 | + // #endif | |
| 122 | + } | |
| 123 | + } | |
| 124 | + }, | |
| 125 | + created() { | |
| 126 | + | |
| 127 | + }, | |
| 128 | + beforeUnmount() { | |
| 129 | + // #ifdef APP-NVUE | |
| 130 | + clearInterval(this.timer) | |
| 131 | + // #endif | |
| 132 | + }, | |
| 133 | + computed: { | |
| 134 | + // 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for | |
| 135 | + codeLength() { | |
| 136 | + return new Array(Number(this.maxlength)) | |
| 137 | + }, | |
| 138 | + // 循环item的样式 | |
| 139 | + itemStyle() { | |
| 140 | + return index => { | |
| 141 | + const style = { | |
| 142 | + width: addUnit(this.size), | |
| 143 | + height: addUnit(this.size) | |
| 144 | + } | |
| 145 | + // 盒子模式下,需要额外进行处理 | |
| 146 | + if (this.mode === 'box') { | |
| 147 | + // 设置盒子的边框,如果是细边框,则设置为0.5px宽度 | |
| 148 | + style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}` | |
| 149 | + // 如果盒子间距为0的话 | |
| 150 | + if (getPx(this.space) === 0) { | |
| 151 | + // 给第一和最后一个盒子设置圆角 | |
| 152 | + if (index === 0) { | |
| 153 | + style.borderTopLeftRadius = '3px' | |
| 154 | + style.borderBottomLeftRadius = '3px' | |
| 155 | + } | |
| 156 | + if (index === this.codeLength.length - 1) { | |
| 157 | + style.borderTopRightRadius = '3px' | |
| 158 | + style.borderBottomRightRadius = '3px' | |
| 159 | + } | |
| 160 | + // 最后一个盒子的右边框需要保留 | |
| 161 | + if (index !== this.codeLength.length - 1) { | |
| 162 | + style.borderRight = 'none' | |
| 163 | + } | |
| 164 | + } | |
| 165 | + } | |
| 166 | + if (index !== this.codeLength.length - 1) { | |
| 167 | + // 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框 | |
| 168 | + style.marginRight = addUnit(this.space) | |
| 169 | + } else { | |
| 170 | + // 最后一个盒子的有边框需要保留 | |
| 171 | + style.marginRight = 0 | |
| 172 | + } | |
| 173 | + | |
| 174 | + return style | |
| 175 | + } | |
| 176 | + }, | |
| 177 | + // 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素 | |
| 178 | + codeArray() { | |
| 179 | + return String(this.inputValue).split('') | |
| 180 | + }, | |
| 181 | + // 下划线模式下,横线的样式 | |
| 182 | + lineStyle() { | |
| 183 | + const style = {} | |
| 184 | + style.height = this.hairline ? '2px' : '4px' | |
| 185 | + style.width = addUnit(this.size) | |
| 186 | + // 线条模式下,背景色即为边框颜色 | |
| 187 | + style.backgroundColor = this.borderColor | |
| 188 | + return style | |
| 189 | + } | |
| 190 | + }, | |
| 191 | + emits: ["change", 'finish', "update:modelValue"], | |
| 192 | + methods: { | |
| 193 | + addUnit, | |
| 194 | + // 监听输入框的值发生变化 | |
| 195 | + inputHandler(e) { | |
| 196 | + const value = e.detail.value | |
| 197 | + this.inputValue = value | |
| 198 | + // 是否允许输入“.”符号 | |
| 199 | + if(this.disabledDot) { | |
| 200 | + this.$nextTick(() => { | |
| 201 | + this.inputValue = value.replace('.', '') | |
| 202 | + }) | |
| 203 | + } | |
| 204 | + // 未达到maxlength之前,发送change事件,达到后发送finish事件 | |
| 205 | + this.$emit('change', value) | |
| 206 | + // 修改通过v-model双向绑定的值 | |
| 207 | + // #ifdef VUE3 | |
| 208 | + this.$emit("update:modelValue", value); | |
| 209 | + // #endif | |
| 210 | + // #ifdef VUE2 | |
| 211 | + this.$emit("input", value); | |
| 212 | + // #endif | |
| 213 | + // 达到用户指定输入长度时,发出完成事件 | |
| 214 | + if (String(value).length >= Number(this.maxlength)) { | |
| 215 | + this.$emit('finish', value) | |
| 216 | + } | |
| 217 | + } | |
| 218 | + } | |
| 219 | + } | |
| 220 | +</script> | |
| 221 | + | |
| 222 | +<style lang="scss" scoped> | |
| 223 | + @import "../../libs/css/components.scss"; | |
| 224 | + $u-code-input-cursor-width: 1px; | |
| 225 | + $u-code-input-cursor-height: 20px; | |
| 226 | + $u-code-input-cursor-animation-duration: 1s; | |
| 227 | + $u-code-input-cursor-animation-name: u-cursor-flicker; | |
| 228 | + | |
| 229 | + .u-code-input { | |
| 230 | + @include flex; | |
| 231 | + position: relative; | |
| 232 | + overflow: hidden; | |
| 233 | + | |
| 234 | + &__item { | |
| 235 | + @include flex; | |
| 236 | + justify-content: center; | |
| 237 | + align-items: center; | |
| 238 | + position: relative; | |
| 239 | + | |
| 240 | + &__text { | |
| 241 | + font-size: 15px; | |
| 242 | + color: $u-content-color; | |
| 243 | + } | |
| 244 | + | |
| 245 | + &__dot { | |
| 246 | + width: 7px; | |
| 247 | + height: 7px; | |
| 248 | + border-radius: 100px; | |
| 249 | + background-color: $u-content-color; | |
| 250 | + } | |
| 251 | + | |
| 252 | + &__line { | |
| 253 | + position: absolute; | |
| 254 | + bottom: 0; | |
| 255 | + height: 4px; | |
| 256 | + border-radius: 100px; | |
| 257 | + width: 40px; | |
| 258 | + background-color: $u-content-color; | |
| 259 | + } | |
| 260 | + &__cursor { | |
| 261 | + position: absolute; | |
| 262 | + /* #ifndef APP-NVUE */ | |
| 263 | + top: 50%; | |
| 264 | + left: 50%; | |
| 265 | + opacity: 1; | |
| 266 | + transform: translate(-50%,-50%); | |
| 267 | + /* #endif */ | |
| 268 | + width: $u-code-input-cursor-width; | |
| 269 | + height: $u-code-input-cursor-height; | |
| 270 | + animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite; | |
| 271 | + } | |
| 272 | + } | |
| 273 | + | |
| 274 | + &__input { | |
| 275 | + // 之所以需要input输入框,是因为有它才能唤起键盘 | |
| 276 | + // 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容 | |
| 277 | + position: absolute; | |
| 278 | + left: -750rpx; | |
| 279 | + width: 1500rpx; | |
| 280 | + top: 0; | |
| 281 | + background-color: transparent; | |
| 282 | + text-align: left; | |
| 283 | + } | |
| 284 | + } | |
| 285 | + | |
| 286 | + /* #ifndef APP-NVUE */ | |
| 287 | + @keyframes u-cursor-flicker { | |
| 288 | + 0% { | |
| 289 | + opacity: 0; | |
| 290 | + } | |
| 291 | + 50% { | |
| 292 | + opacity: 1; | |
| 293 | + } | |
| 294 | + 100% { | |
| 295 | + opacity: 0; | |
| 296 | + } | |
| 297 | + } | |
| 298 | + /* #endif */ | |
| 299 | + | |
| 300 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-code/code.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 3.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : jry | |
| 7 | + * @lastTime : 2025-12-19 08:55:21 | |
| 8 | + * @FilePath : /uview-plus/libs/config/props/code.js | |
| 9 | + */ | |
| 10 | +import { t } from '../../libs/i18n' | |
| 11 | +export default { | |
| 12 | + // code 组件 | |
| 13 | + code: { | |
| 14 | + seconds: 60, | |
| 15 | + startText: t("up.code.send"), | |
| 16 | + changeText: t("up.code.resendAfter"), | |
| 17 | + endText: t("up.code.resend"), | |
| 18 | + keepRunning: false, | |
| 19 | + uniqueKey: '' | |
| 20 | + } | |
| 21 | +} | ... | ... |
uni_modules/uview-plus/components/u-code/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 倒计时总秒数 | |
| 6 | + seconds: { | |
| 7 | + type: [String, Number], | |
| 8 | + default: () => defProps.code.seconds | |
| 9 | + }, | |
| 10 | + // 尚未开始时提示 | |
| 11 | + startText: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.code.startText | |
| 14 | + }, | |
| 15 | + // 正在倒计时中的提示 | |
| 16 | + changeText: { | |
| 17 | + type: String, | |
| 18 | + default: () => defProps.code.changeText | |
| 19 | + }, | |
| 20 | + // 倒计时结束时的提示 | |
| 21 | + endText: { | |
| 22 | + type: String, | |
| 23 | + default: () => defProps.code.endText | |
| 24 | + }, | |
| 25 | + // 是否在H5刷新或各端返回再进入时继续倒计时 | |
| 26 | + keepRunning: { | |
| 27 | + type: Boolean, | |
| 28 | + default: () => defProps.code.keepRunning | |
| 29 | + }, | |
| 30 | + // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了 | |
| 31 | + uniqueKey: { | |
| 32 | + type: String, | |
| 33 | + default: () => defProps.code.uniqueKey | |
| 34 | + } | |
| 35 | + } | |
| 36 | +}) | ... | ... |
uni_modules/uview-plus/components/u-code/u-code.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-code"> | |
| 3 | + <!-- 此组件功能由js完成,无需写html逻辑 --> | |
| 4 | + </view> | |
| 5 | +</template> | |
| 6 | + | |
| 7 | +<script> | |
| 8 | +import { props } from './props'; | |
| 9 | +import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 10 | +import { mixin } from '../../libs/mixin/mixin'; | |
| 11 | +/** | |
| 12 | + * Code 验证码输入框 | |
| 13 | + * @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景 | |
| 14 | + * @tutorial https://uview-plus.jiangruyi.com/components/code.html | |
| 15 | + * @property {String | Number} seconds 倒计时所需的秒数(默认 60 ) | |
| 16 | + * @property {String} startText 开始前的提示语,见官网说明(默认 '获取验证码' ) | |
| 17 | + * @property {String} changeText 倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' ) | |
| 18 | + * @property {String} endText 倒计结束的提示语,见官网说明(默认 '重新获取' ) | |
| 19 | + * @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时( 默认false ) | |
| 20 | + * @property {String} uniqueKey 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了 | |
| 21 | + * | |
| 22 | + * @event {Function} change 倒计时期间,每秒触发一次 | |
| 23 | + * @event {Function} start 开始倒计时触发 | |
| 24 | + * @event {Function} end 结束倒计时触发 | |
| 25 | + * @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code> | |
| 26 | + */ | |
| 27 | +export default { | |
| 28 | + name: "u-code", | |
| 29 | + mixins: [mpMixin, mixin,props], | |
| 30 | + data() { | |
| 31 | + return { | |
| 32 | + secNum: this.seconds, | |
| 33 | + timer: null, | |
| 34 | + canGetCode: true, // 是否可以执行验证码操作 | |
| 35 | + } | |
| 36 | + }, | |
| 37 | + mounted() { | |
| 38 | + this.checkKeepRunning() | |
| 39 | + }, | |
| 40 | + watch: { | |
| 41 | + seconds: { | |
| 42 | + immediate: true, | |
| 43 | + handler(n) { | |
| 44 | + this.secNum = n | |
| 45 | + } | |
| 46 | + } | |
| 47 | + }, | |
| 48 | + emits: ["start", "end", "change"], | |
| 49 | + methods: { | |
| 50 | + checkKeepRunning() { | |
| 51 | + // 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空 | |
| 52 | + let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp')) | |
| 53 | + if(!lastTimestamp) return this.changeEvent(this.startText) | |
| 54 | + // 当前秒的时间戳 | |
| 55 | + let nowTimestamp = Math.floor((+ new Date()) / 1000) | |
| 56 | + // 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳 | |
| 57 | + if(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) { | |
| 58 | + // 剩余尚未执行完的倒计秒数 | |
| 59 | + this.secNum = lastTimestamp - nowTimestamp | |
| 60 | + // 清除本地保存的变量 | |
| 61 | + uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp') | |
| 62 | + // 开始倒计时 | |
| 63 | + this.start() | |
| 64 | + } else { | |
| 65 | + // 如果不存在需要继续上一次的倒计时,执行正常的逻辑 | |
| 66 | + this.changeEvent(this.startText) | |
| 67 | + } | |
| 68 | + }, | |
| 69 | + // 开始倒计时 | |
| 70 | + start() { | |
| 71 | + // 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱 | |
| 72 | + if(this.timer) { | |
| 73 | + clearInterval(this.timer) | |
| 74 | + this.timer = null | |
| 75 | + } | |
| 76 | + this.$emit('start') | |
| 77 | + this.canGetCode = false | |
| 78 | + // 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示 | |
| 79 | + this.changeEvent(this.changeText.replace(/x|X/, this.secNum)) | |
| 80 | + this.timer = setInterval(() => { | |
| 81 | + if (--this.secNum) { | |
| 82 | + // 用当前倒计时的秒数替换提示字符串中的"x"字母 | |
| 83 | + this.changeEvent(this.changeText.replace(/x|X/, this.secNum)) | |
| 84 | + } else { | |
| 85 | + clearInterval(this.timer) | |
| 86 | + this.timer = null | |
| 87 | + this.changeEvent(this.endText) | |
| 88 | + this.secNum = this.seconds | |
| 89 | + this.$emit('end') | |
| 90 | + this.canGetCode = true | |
| 91 | + } | |
| 92 | + }, 1000) | |
| 93 | + this.setTimeToStorage() | |
| 94 | + }, | |
| 95 | + // 重置,可以让用户再次获取验证码 | |
| 96 | + reset() { | |
| 97 | + this.canGetCode = true | |
| 98 | + clearInterval(this.timer) | |
| 99 | + this.secNum = this.seconds | |
| 100 | + this.changeEvent(this.endText) | |
| 101 | + }, | |
| 102 | + changeEvent(text) { | |
| 103 | + this.$emit('change', text) | |
| 104 | + }, | |
| 105 | + // 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来 | |
| 106 | + setTimeToStorage() { | |
| 107 | + if(!this.keepRunning || !this.timer) return | |
| 108 | + // 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时 | |
| 109 | + // 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理 | |
| 110 | + if(this.secNum > 0 && this.secNum <= this.seconds) { | |
| 111 | + // 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分 | |
| 112 | + let nowTimestamp = Math.floor((+ new Date()) / 1000) | |
| 113 | + // 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数 | |
| 114 | + uni.setStorage({ | |
| 115 | + key: this.uniqueKey + '_$uCountDownTimestamp', | |
| 116 | + data: nowTimestamp + Number(this.secNum) | |
| 117 | + }) | |
| 118 | + } | |
| 119 | + } | |
| 120 | + }, | |
| 121 | + // 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除 | |
| 122 | + beforeUnmount() { | |
| 123 | + this.setTimeToStorage() | |
| 124 | + clearTimeout(this.timer) | |
| 125 | + this.timer = null | |
| 126 | + } | |
| 127 | +} | |
| 128 | +</script> | |
| 129 | + | |
| 130 | +<style lang="scss" scoped> | |
| 131 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-col/col.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:56:12 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/col.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // col 组件 | |
| 12 | + col: { | |
| 13 | + span: 12, | |
| 14 | + offset: 0, | |
| 15 | + justify: 'start', | |
| 16 | + align: 'stretch', | |
| 17 | + textAlign: 'left' | |
| 18 | + } | |
| 19 | +} | ... | ... |
uni_modules/uview-plus/components/u-col/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 占父容器宽度的多少等分,总分为12份 | |
| 6 | + span: { | |
| 7 | + type: [String, Number], | |
| 8 | + default: () => defProps.col.span | |
| 9 | + }, | |
| 10 | + // 指定栅格左侧的间隔数(总12栏) | |
| 11 | + offset: { | |
| 12 | + type: [String, Number], | |
| 13 | + default: () => defProps.col.offset | |
| 14 | + }, | |
| 15 | + // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) | |
| 16 | + justify: { | |
| 17 | + type: String, | |
| 18 | + default: () => defProps.col.justify | |
| 19 | + }, | |
| 20 | + // 垂直对齐方式,可选值为top、center、bottom、stretch | |
| 21 | + align: { | |
| 22 | + type: String, | |
| 23 | + default: () => defProps.col.align | |
| 24 | + }, | |
| 25 | + // 文字对齐方式 | |
| 26 | + textAlign: { | |
| 27 | + type: String, | |
| 28 | + default: () => defProps.col.textAlign | |
| 29 | + } | |
| 30 | + } | |
| 31 | +}) | ... | ... |
uni_modules/uview-plus/components/u-col/u-col.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view | |
| 3 | + class="u-col" | |
| 4 | + ref="u-col" | |
| 5 | + :class="[ | |
| 6 | + 'u-col-' + span | |
| 7 | + ]" | |
| 8 | + :style="[colStyle]" | |
| 9 | + @tap="clickHandler" | |
| 10 | + > | |
| 11 | + <slot></slot> | |
| 12 | + </view> | |
| 13 | +</template> | |
| 14 | + | |
| 15 | +<script> | |
| 16 | + import { props } from './props'; | |
| 17 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 18 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 19 | + import { addStyle, addUnit, deepMerge, getPx } from '../../libs/function/index'; | |
| 20 | + /** | |
| 21 | + * CodeInput 栅格系统的列 | |
| 22 | + * @description 该组件一般用于Layout 布局 通过基础的 12 分栏,迅速简便地创建布局 | |
| 23 | + * @tutorial https://ijry.github.io/uview-plus/components/Layout.html | |
| 24 | + * @property {String | Number} span 栅格占据的列数,总12等份 (默认 12 ) | |
| 25 | + * @property {String | Number} offset 分栏左边偏移,计算方式与span相同 (默认 0 ) | |
| 26 | + * @property {String} justify 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) (默认 'start' ) | |
| 27 | + * @property {String} align 垂直对齐方式,可选值为top、center、bottom、stretch (默认 'stretch' ) | |
| 28 | + * @property {String} textAlign 文字水平对齐方式 (默认 'left' ) | |
| 29 | + * @property {Object} customStyle 定义需要用到的外部样式 | |
| 30 | + * @event {Function} click col被点击,会阻止事件冒泡到row | |
| 31 | + * @example <u-col span="3" offset="3" > <view class="demo-layout bg-purple"></view> </u-col> | |
| 32 | + */ | |
| 33 | + export default { | |
| 34 | + name: 'u-col', | |
| 35 | + mixins: [mpMixin, mixin, props], | |
| 36 | + data() { | |
| 37 | + return { | |
| 38 | + width: 0, | |
| 39 | + parentData: { | |
| 40 | + gutter: 0 | |
| 41 | + }, | |
| 42 | + gridNum: 12 | |
| 43 | + } | |
| 44 | + }, | |
| 45 | + // 微信小程序中 options 选项 | |
| 46 | + options: { | |
| 47 | + virtualHost: true // 将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等 | |
| 48 | + }, | |
| 49 | + computed: { | |
| 50 | + uJustify() { | |
| 51 | + if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify | |
| 52 | + else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify | |
| 53 | + else return this.justify | |
| 54 | + }, | |
| 55 | + uAlignItem() { | |
| 56 | + if (this.align == 'top') return 'flex-start' | |
| 57 | + if (this.align == 'bottom') return 'flex-end' | |
| 58 | + else return this.align | |
| 59 | + }, | |
| 60 | + colStyle() { | |
| 61 | + const style = { | |
| 62 | + // 这里写成"padding: 0 10px"的形式是因为nvue的需要 | |
| 63 | + paddingLeft: addUnit(getPx(this.parentData.gutter)/2), | |
| 64 | + paddingRight: addUnit(getPx(this.parentData.gutter)/2), | |
| 65 | + alignItems: this.uAlignItem, | |
| 66 | + justifyContent: this.uJustify, | |
| 67 | + textAlign: this.textAlign, | |
| 68 | + // #ifndef APP-NVUE | |
| 69 | + // 在非nvue上,使用百分比形式 | |
| 70 | + flex: `0 0 ${100 / this.gridNum * this.span}%`, | |
| 71 | + marginLeft: 100 / 12 * this.offset + '%', | |
| 72 | + // #endif | |
| 73 | + // #ifdef APP-NVUE | |
| 74 | + // 在nvue上,由于无法使用百分比单位,这里需要获取父组件的宽度,再计算得出该有对应的百分比尺寸 | |
| 75 | + width: addUnit(Math.floor(this.width / this.gridNum * Number(this.span))), | |
| 76 | + marginLeft: addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))), | |
| 77 | + // #endif | |
| 78 | + } | |
| 79 | + return deepMerge(style, addStyle(this.customStyle)) | |
| 80 | + } | |
| 81 | + }, | |
| 82 | + mounted() { | |
| 83 | + this.init() | |
| 84 | + }, | |
| 85 | + emits: ["click"], | |
| 86 | + methods: { | |
| 87 | + async init() { | |
| 88 | + // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用 | |
| 89 | + this.updateParentData() | |
| 90 | + this.width = await this.parent.getComponentWidth() | |
| 91 | + }, | |
| 92 | + updateParentData() { | |
| 93 | + this.getParentData('u-row') | |
| 94 | + }, | |
| 95 | + clickHandler(e) { | |
| 96 | + this.$emit('click'); | |
| 97 | + } | |
| 98 | + }, | |
| 99 | + } | |
| 100 | +</script> | |
| 101 | + | |
| 102 | +<style lang="scss" scoped> | |
| 103 | + @import "../../libs/css/components.scss"; | |
| 104 | + | |
| 105 | + .u-col { | |
| 106 | + padding: 0; | |
| 107 | + /* #ifndef APP-NVUE */ | |
| 108 | + box-sizing:border-box; | |
| 109 | + /* #endif */ | |
| 110 | + /* #ifdef MP */ | |
| 111 | + display: block; | |
| 112 | + /* #endif */ | |
| 113 | + } | |
| 114 | + | |
| 115 | + // nvue下百分比无效 | |
| 116 | + /* #ifndef APP-NVUE */ | |
| 117 | + .u-col-0 { | |
| 118 | + width: 0; | |
| 119 | + } | |
| 120 | + | |
| 121 | + .u-col-1 { | |
| 122 | + width: calc(100%/12); | |
| 123 | + } | |
| 124 | + | |
| 125 | + .u-col-2 { | |
| 126 | + width: calc(100%/12 * 2); | |
| 127 | + } | |
| 128 | + | |
| 129 | + .u-col-3 { | |
| 130 | + width: calc(100%/12 * 3); | |
| 131 | + } | |
| 132 | + | |
| 133 | + .u-col-4 { | |
| 134 | + width: calc(100%/12 * 4); | |
| 135 | + } | |
| 136 | + | |
| 137 | + .u-col-5 { | |
| 138 | + width: calc(100%/12 * 5); | |
| 139 | + } | |
| 140 | + | |
| 141 | + .u-col-6 { | |
| 142 | + width: calc(100%/12 * 6); | |
| 143 | + } | |
| 144 | + | |
| 145 | + .u-col-7 { | |
| 146 | + width: calc(100%/12 * 7); | |
| 147 | + } | |
| 148 | + | |
| 149 | + .u-col-8 { | |
| 150 | + width: calc(100%/12 * 8); | |
| 151 | + } | |
| 152 | + | |
| 153 | + .u-col-9 { | |
| 154 | + width: calc(100%/12 * 9); | |
| 155 | + } | |
| 156 | + | |
| 157 | + .u-col-10 { | |
| 158 | + width: calc(100%/12 * 10); | |
| 159 | + } | |
| 160 | + | |
| 161 | + .u-col-11 { | |
| 162 | + width: calc(100%/12 * 11); | |
| 163 | + } | |
| 164 | + | |
| 165 | + .u-col-12 { | |
| 166 | + width: calc(100%/12 * 12); | |
| 167 | + } | |
| 168 | + | |
| 169 | + /* #endif */ | |
| 170 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-collapse-item/collapseItem.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:56:42 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/collapseItem.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // collapseItem 组件 | |
| 12 | + collapseItem: { | |
| 13 | + title: '', | |
| 14 | + value: '', | |
| 15 | + label: '', | |
| 16 | + disabled: false, | |
| 17 | + isLink: true, | |
| 18 | + clickable: true, | |
| 19 | + border: true, | |
| 20 | + align: 'left', | |
| 21 | + name: '', | |
| 22 | + icon: '', | |
| 23 | + duration: 300, | |
| 24 | + showRight: true | |
| 25 | + } | |
| 26 | +} | ... | ... |
uni_modules/uview-plus/components/u-collapse-item/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 标题 | |
| 6 | + title: { | |
| 7 | + type: String, | |
| 8 | + default: () => defProps.collapseItem.title | |
| 9 | + }, | |
| 10 | + // 标题右侧内容 | |
| 11 | + value: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.collapseItem.value | |
| 14 | + }, | |
| 15 | + // 标题下方的描述信息 | |
| 16 | + label: { | |
| 17 | + type: String, | |
| 18 | + default: () => defProps.collapseItem.label | |
| 19 | + }, | |
| 20 | + // 是否禁用折叠面板 | |
| 21 | + disabled: { | |
| 22 | + type: Boolean, | |
| 23 | + default: () => defProps.collapseItem.disabled | |
| 24 | + }, | |
| 25 | + // 是否展示右侧箭头并开启点击反馈 | |
| 26 | + isLink: { | |
| 27 | + type: Boolean, | |
| 28 | + default: () => defProps.collapseItem.isLink | |
| 29 | + }, | |
| 30 | + // 是否开启点击反馈 | |
| 31 | + clickable: { | |
| 32 | + type: Boolean, | |
| 33 | + default: () => defProps.collapseItem.clickable | |
| 34 | + }, | |
| 35 | + // 是否显示内边框 | |
| 36 | + border: { | |
| 37 | + type: Boolean, | |
| 38 | + default: () => defProps.collapseItem.border | |
| 39 | + }, | |
| 40 | + // 标题的对齐方式 | |
| 41 | + align: { | |
| 42 | + type: String, | |
| 43 | + default: () => defProps.collapseItem.align | |
| 44 | + }, | |
| 45 | + // 唯一标识符 | |
| 46 | + name: { | |
| 47 | + type: [String, Number], | |
| 48 | + default: () => defProps.collapseItem.name | |
| 49 | + }, | |
| 50 | + // 标题左侧图片,可为绝对路径的图片或内置图标 | |
| 51 | + icon: { | |
| 52 | + type: String, | |
| 53 | + default: () => defProps.collapseItem.icon | |
| 54 | + }, | |
| 55 | + // 面板展开收起的过渡时间,单位ms | |
| 56 | + duration: { | |
| 57 | + type: Number, | |
| 58 | + default: () => defProps.collapseItem.duration | |
| 59 | + }, | |
| 60 | + // 显示右侧图标 | |
| 61 | + showRight: { | |
| 62 | + type: Boolean, | |
| 63 | + default: () => defProps.collapseItem.showRight | |
| 64 | + }, | |
| 65 | + } | |
| 66 | +}) | ... | ... |
uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-collapse-item"> | |
| 3 | + <u-cell | |
| 4 | + :title="$slots.title ? '' : title" | |
| 5 | + :value="value" | |
| 6 | + :label="label" | |
| 7 | + :icon="icon" | |
| 8 | + :isLink="isLink" | |
| 9 | + :clickable="clickable" | |
| 10 | + :border="parentData.border && showBorder" | |
| 11 | + @click="clickHandler" | |
| 12 | + :arrowDirection="expanded ? 'up' : 'down'" | |
| 13 | + :disabled="disabled" | |
| 14 | + > | |
| 15 | + <!-- 微信小程序不支持,因为微信中不支持 <slot name="title" #title />的写法 --> | |
| 16 | + <template #title> | |
| 17 | + <slot name="title"> | |
| 18 | + <text v-if="!$slots.title && title"> | |
| 19 | + {{title}} | |
| 20 | + </text> | |
| 21 | + </slot> | |
| 22 | + </template> | |
| 23 | + <template #icon> | |
| 24 | + <slot name="icon"> | |
| 25 | + <u-icon v-if="!$slots.icon && icon" :size="22" :name="icon"></u-icon> | |
| 26 | + </slot> | |
| 27 | + </template> | |
| 28 | + <template #value> | |
| 29 | + <slot name="value"> | |
| 30 | + <text v-if="!$slots.value && value"> | |
| 31 | + {{value}} | |
| 32 | + </text> | |
| 33 | + </slot> | |
| 34 | + </template> | |
| 35 | + <template #right-icon> | |
| 36 | + <template v-if="showRight"> | |
| 37 | + <u-icon v-if="!$slots['right-icon']" :size="16" name="arrow-right"></u-icon> | |
| 38 | + <slot name="right-icon"> | |
| 39 | + </slot> | |
| 40 | + </template> | |
| 41 | + </template> | |
| 42 | + </u-cell> | |
| 43 | + <view | |
| 44 | + class="u-collapse-item__content" | |
| 45 | + :animation="animationData" | |
| 46 | + ref="animation" | |
| 47 | + > | |
| 48 | + <view | |
| 49 | + class="u-collapse-item__content__text content-class" | |
| 50 | + :id="elId" | |
| 51 | + :ref="elId" | |
| 52 | + ><slot /></view> | |
| 53 | + </view> | |
| 54 | + <u-line v-if="parentData.border"></u-line> | |
| 55 | + </view> | |
| 56 | +</template> | |
| 57 | + | |
| 58 | +<script> | |
| 59 | + import { props } from './props.js'; | |
| 60 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 61 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 62 | + import { nextTick } from 'vue'; | |
| 63 | + import { guid, sleep, error } from '../../libs/function/index'; | |
| 64 | + import test from '../../libs/function/test'; | |
| 65 | + // #ifdef APP-NVUE | |
| 66 | + const animation = uni.requireNativePlugin('animation') | |
| 67 | + const dom = uni.requireNativePlugin('dom') | |
| 68 | + // #endif | |
| 69 | + /** | |
| 70 | + * collapseItem 折叠面板Item | |
| 71 | + * @description 通过折叠面板收纳内容区域(搭配u-collapse使用) | |
| 72 | + * @tutorial https://ijry.github.io/uview-plus/components/collapse.html | |
| 73 | + * @property {String} title 标题 | |
| 74 | + * @property {String} value 标题右侧内容 | |
| 75 | + * @property {String} label 标题下方的描述信息 | |
| 76 | + * @property {Boolean} disbled 是否禁用折叠面板 ( 默认 false ) | |
| 77 | + * @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 ( 默认 true ) | |
| 78 | + * @property {Boolean} clickable 是否开启点击反馈 ( 默认 true ) | |
| 79 | + * @property {Boolean} border 是否显示内边框 ( 默认 true ) | |
| 80 | + * @property {String} align 标题的对齐方式 ( 默认 'left' ) | |
| 81 | + * @property {String | Number} name 唯一标识符 | |
| 82 | + * @property {String} icon 标题左侧图片,可为绝对路径的图片或内置图标 | |
| 83 | + * @event {Function} change 某个item被打开或者收起时触发 | |
| 84 | + * @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item> | |
| 85 | + */ | |
| 86 | + export default { | |
| 87 | + name: "u-collapse-item", | |
| 88 | + mixins: [mpMixin, mixin, props], | |
| 89 | + data() { | |
| 90 | + return { | |
| 91 | + elId: guid(), | |
| 92 | + // uni.createAnimation的导出数据 | |
| 93 | + animationData: {}, | |
| 94 | + // 是否展开状态 | |
| 95 | + expanded: false, | |
| 96 | + // 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时 | |
| 97 | + showBorder: false, | |
| 98 | + // 是否动画中,如果是则不允许继续触发点击 | |
| 99 | + animating: false, | |
| 100 | + // 父组件u-collapse的参数 | |
| 101 | + parentData: { | |
| 102 | + accordion: false, | |
| 103 | + border: false | |
| 104 | + } | |
| 105 | + }; | |
| 106 | + }, | |
| 107 | + watch: { | |
| 108 | + expanded(n) { | |
| 109 | + clearTimeout(this.timer) | |
| 110 | + this.timer = null | |
| 111 | + // 这里根据expanded的值来进行一定的延时,是为了cell的下划线更好的显示效果 | |
| 112 | + this.timer = setTimeout(() => { | |
| 113 | + this.showBorder = n | |
| 114 | + }, n ? 10 : 290) | |
| 115 | + } | |
| 116 | + }, | |
| 117 | + mounted() { | |
| 118 | + this.init() | |
| 119 | + // console.log('$slots', this.$slots) | |
| 120 | + }, | |
| 121 | + methods: { | |
| 122 | + // 异步获取内容,或者动态修改了内容时,需要重新初始化 | |
| 123 | + async init() { | |
| 124 | + // 初始化数据 | |
| 125 | + this.updateParentData() | |
| 126 | + if (!this.parent) { | |
| 127 | + return error('u-collapse-item必须要搭配u-collapse组件使用') | |
| 128 | + } | |
| 129 | + const { | |
| 130 | + value, | |
| 131 | + accordion, | |
| 132 | + children = [] | |
| 133 | + } = this.parent | |
| 134 | + | |
| 135 | + if (accordion) { | |
| 136 | + if (test.array(value)) { | |
| 137 | + return error('手风琴模式下,u-collapse组件的value参数不能为数组') | |
| 138 | + } | |
| 139 | + this.expanded = this.name == value | |
| 140 | + } else { | |
| 141 | + if (!test.array(value) && value !== null) { | |
| 142 | + return error('非手风琴模式下,u-collapse组件的value参数必须为数组') | |
| 143 | + } | |
| 144 | + this.expanded = (value || []).some(item => item == this.name) | |
| 145 | + } | |
| 146 | + // 设置组件的展开或收起状态 | |
| 147 | + await nextTick() | |
| 148 | + this.setContentAnimate() | |
| 149 | + }, | |
| 150 | + updateParentData() { | |
| 151 | + // 此方法在mixin中 | |
| 152 | + this.getParentData('u-collapse') | |
| 153 | + }, | |
| 154 | + async setContentAnimate() { | |
| 155 | + // 每次面板打开或者收起时,都查询元素尺寸 | |
| 156 | + // 好处是,父组件从服务端获取内容后,变更折叠面板后可以获得最新的高度 | |
| 157 | + const rect = await this.queryRect() | |
| 158 | + const height = this.expanded ? rect.height : 0 | |
| 159 | + this.animating = true | |
| 160 | + // #ifdef APP-NVUE | |
| 161 | + const ref = this.$refs['animation'].ref | |
| 162 | + animation.transition(ref, { | |
| 163 | + styles: { | |
| 164 | + height: height + 'px' | |
| 165 | + }, | |
| 166 | + duration: this.duration, | |
| 167 | + // 必须设置为true,否则会到面板收起或展开时,页面其他元素不会随之调整它们的布局 | |
| 168 | + needLayout: true, | |
| 169 | + timingFunction: 'ease-in-out', | |
| 170 | + }, () => { | |
| 171 | + this.animating = false | |
| 172 | + }) | |
| 173 | + // #endif | |
| 174 | + | |
| 175 | + // #ifndef APP-NVUE | |
| 176 | + const animation = uni.createAnimation({ | |
| 177 | + timingFunction: 'ease-in-out', | |
| 178 | + }); | |
| 179 | + animation | |
| 180 | + .height(height) | |
| 181 | + .step({ | |
| 182 | + duration: this.duration, | |
| 183 | + }) | |
| 184 | + .step() | |
| 185 | + // 导出动画数据给面板的animationData值 | |
| 186 | + this.animationData = animation.export() | |
| 187 | + // 标识动画结束 | |
| 188 | + sleep(this.duration).then(() => { | |
| 189 | + this.animating = false | |
| 190 | + }) | |
| 191 | + // #endif | |
| 192 | + }, | |
| 193 | + // 点击collapsehead头部 | |
| 194 | + clickHandler() { | |
| 195 | + if (this.disabled && this.animating) return | |
| 196 | + // 设置本组件为相反的状态 | |
| 197 | + this.parent && this.parent.onChange(this) | |
| 198 | + }, | |
| 199 | + // 查询内容高度 | |
| 200 | + queryRect() { | |
| 201 | + // #ifndef APP-NVUE | |
| 202 | + // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://ijry.github.io/uview-plus/js/getRect.html | |
| 203 | + // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同 | |
| 204 | + return new Promise(resolve => { | |
| 205 | + this.$uGetRect(`#${this.elId}`).then(size => { | |
| 206 | + resolve(size) | |
| 207 | + }) | |
| 208 | + }) | |
| 209 | + // #endif | |
| 210 | + | |
| 211 | + // #ifdef APP-NVUE | |
| 212 | + // nvue下,使用dom模块查询元素高度 | |
| 213 | + // 返回一个promise,让调用此方法的主体能使用then回调 | |
| 214 | + return new Promise(resolve => { | |
| 215 | + dom.getComponentRect(this.$refs[this.elId], res => { | |
| 216 | + resolve(res.size) | |
| 217 | + }) | |
| 218 | + }) | |
| 219 | + // #endif | |
| 220 | + } | |
| 221 | + }, | |
| 222 | + }; | |
| 223 | +</script> | |
| 224 | + | |
| 225 | +<style lang="scss" scoped> | |
| 226 | + @import "../../libs/css/components.scss"; | |
| 227 | + | |
| 228 | + .u-collapse-item { | |
| 229 | + | |
| 230 | + &__content { | |
| 231 | + overflow: hidden; | |
| 232 | + height: 0; | |
| 233 | + | |
| 234 | + &__text { | |
| 235 | + padding: 12px 15px; | |
| 236 | + color: $u-content-color; | |
| 237 | + font-size: 14px; | |
| 238 | + line-height: 18px; | |
| 239 | + } | |
| 240 | + } | |
| 241 | + } | |
| 242 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-collapse/collapse.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:56:30 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/collapse.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // collapse 组件 | |
| 12 | + collapse: { | |
| 13 | + value: null, | |
| 14 | + accordion: false, | |
| 15 | + border: true | |
| 16 | + } | |
| 17 | +} | ... | ... |
uni_modules/uview-plus/components/u-collapse/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number | |
| 6 | + value: { | |
| 7 | + type: [String, Number, Array, null], | |
| 8 | + default: () => defProps.collapse.value | |
| 9 | + }, | |
| 10 | + // 是否手风琴模式 | |
| 11 | + accordion: { | |
| 12 | + type: Boolean, | |
| 13 | + default: () => defProps.collapse.accordion | |
| 14 | + }, | |
| 15 | + // 是否显示外边框 | |
| 16 | + border: { | |
| 17 | + type: Boolean, | |
| 18 | + default: () => defProps.collapse.border | |
| 19 | + } | |
| 20 | + } | |
| 21 | +}) | ... | ... |
uni_modules/uview-plus/components/u-collapse/u-collapse.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-collapse"> | |
| 3 | + <u-line v-if="border"></u-line> | |
| 4 | + <slot /> | |
| 5 | + </view> | |
| 6 | +</template> | |
| 7 | + | |
| 8 | +<script> | |
| 9 | + import { props } from './props'; | |
| 10 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 11 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 12 | + /** | |
| 13 | + * collapse 折叠面板 | |
| 14 | + * @description 通过折叠面板收纳内容区域 | |
| 15 | + * @tutorial https://ijry.github.io/uview-plus/components/collapse.html | |
| 16 | + * @property {String | Number | Array} value 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number | |
| 17 | + * @property {Boolean} accordion 是否手风琴模式( 默认 false ) | |
| 18 | + * @property {Boolean} border 是否显示外边框 ( 默认 true ) | |
| 19 | + * @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array) | |
| 20 | + * @example <u-collapse></u-collapse> | |
| 21 | + */ | |
| 22 | + export default { | |
| 23 | + name: "u-collapse", | |
| 24 | + mixins: [mpMixin, mixin,props], | |
| 25 | + watch: { | |
| 26 | + needInit() { | |
| 27 | + this.init() | |
| 28 | + }, | |
| 29 | + // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件 | |
| 30 | + parentData() { | |
| 31 | + if (this.children.length) { | |
| 32 | + this.children.map(child => { | |
| 33 | + // 判断子组件(u-checkbox)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值) | |
| 34 | + typeof(child.updateParentData) === 'function' && child.updateParentData() | |
| 35 | + }) | |
| 36 | + } | |
| 37 | + } | |
| 38 | + }, | |
| 39 | + created() { | |
| 40 | + this.children = [] | |
| 41 | + }, | |
| 42 | + computed: { | |
| 43 | + needInit() { | |
| 44 | + // 通过computed,同时监听accordion和value值的变化 | |
| 45 | + // 再通过watch去执行init()方法,进行再一次的初始化 | |
| 46 | + return [this.accordion, this.value] | |
| 47 | + } | |
| 48 | + }, | |
| 49 | + emits: ["open", "close", "change"], | |
| 50 | + methods: { | |
| 51 | + // 重新初始化一次内部的所有子元素 | |
| 52 | + init() { | |
| 53 | + this.children.map(child => { | |
| 54 | + child.init() | |
| 55 | + }) | |
| 56 | + }, | |
| 57 | + /** | |
| 58 | + * collapse-item被点击时触发,由collapse统一处理各子组件的状态 | |
| 59 | + * @param {Object} target 被操作的面板的实例 | |
| 60 | + */ | |
| 61 | + onChange(target) { | |
| 62 | + let changeArr = [] | |
| 63 | + this.children.map((child, index) => { | |
| 64 | + // 如果是手风琴模式,将其他的折叠面板收起来 | |
| 65 | + if (this.accordion) { | |
| 66 | + child.expanded = child === target ? !target.expanded : false | |
| 67 | + child.setContentAnimate() | |
| 68 | + } else { | |
| 69 | + if(child === target) { | |
| 70 | + child.expanded = !child.expanded | |
| 71 | + child.setContentAnimate() | |
| 72 | + } | |
| 73 | + } | |
| 74 | + // 拼接change事件中,数组元素的状态 | |
| 75 | + changeArr.push({ | |
| 76 | + // 如果没有定义name属性,则默认返回组件的index索引 | |
| 77 | + name: child.name || index, | |
| 78 | + status: child.expanded ? 'open' : 'close' | |
| 79 | + }) | |
| 80 | + }) | |
| 81 | + | |
| 82 | + this.$emit('change', changeArr) | |
| 83 | + this.$emit(target.expanded ? 'open' : 'close', target.name) | |
| 84 | + } | |
| 85 | + } | |
| 86 | + } | |
| 87 | +</script> | |
| 88 | + | |
| 89 | +<style lang="scss" scoped> | |
| 90 | + @import "../../libs/css/components.scss"; | |
| 91 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-color-picker/u-color-picker.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="up-color-picker"> | |
| 3 | + <view clas="up-color-picker__trigger" @click="show = true" | |
| 4 | + :style="{backgroundColor: value}" > | |
| 5 | + <slot></slot> | |
| 6 | + </view> | |
| 7 | + <up-popup :show="show" mode="bottom" round="10" @close="close" :closeOnClickOverlay="true"> | |
| 8 | + <view class="up-color-picker__content"> | |
| 9 | + <view class="up-color-picker__header"> | |
| 10 | + <text class="up-color-picker__title">选择颜色</text> | |
| 11 | + </view> | |
| 12 | + | |
| 13 | + <!-- 纯色/渐变色切换 --> | |
| 14 | + <view class="up-color-picker__switch"> | |
| 15 | + <up-subsection | |
| 16 | + :list="[{ name: '纯色' }, { name: '渐变' }]" | |
| 17 | + :current="colorTypeIndex" | |
| 18 | + @change="changeColorType" | |
| 19 | + fontSize="14" | |
| 20 | + ></up-subsection> | |
| 21 | + </view> | |
| 22 | + | |
| 23 | + <!-- 渐变色选择器 --> | |
| 24 | + <view v-if="colorTypeIndex == 1" class="up-color-picker__gradient"> | |
| 25 | + <!-- 渐变色控制条 --> | |
| 26 | + <view | |
| 27 | + class="up-color-picker__gradient-track" | |
| 28 | + :style="{ background: gradientStyle }" | |
| 29 | + > | |
| 30 | + <view | |
| 31 | + class="up-color-picker__gradient-pointer" | |
| 32 | + v-for="(item, index) in gradientColors" | |
| 33 | + :key="index" | |
| 34 | + :style="{ left: getGradientPointerPosition(index) + 'px' }" | |
| 35 | + @click="openColorPickerForGradient(index)" | |
| 36 | + @touchstart="onPointerTouchStart($event, index)" | |
| 37 | + @touchmove.stop="onPointerTouchMove" | |
| 38 | + @touchend.stop="onPointerTouchEnd" | |
| 39 | + > | |
| 40 | + <view class="up-color-picker__gradient-pointer-inner" :style="{ backgroundColor: item.color }"></view> | |
| 41 | + </view> | |
| 42 | + </view> | |
| 43 | + | |
| 44 | + <view class="up-color-picker__gradient-controls"> | |
| 45 | + <up-button | |
| 46 | + type="primary" | |
| 47 | + size="mini" | |
| 48 | + plain | |
| 49 | + @click="addGradientColor" | |
| 50 | + class="up-color-picker__add-btn" | |
| 51 | + > | |
| 52 | + 添加颜色 | |
| 53 | + </up-button> | |
| 54 | + </view> | |
| 55 | + | |
| 56 | + <!-- 圆形方向选择器 --> | |
| 57 | + <view class="up-color-picker__gradient-direction"> | |
| 58 | + <text>方向:</text> | |
| 59 | + <view class="up-color-picker__gradient__direction-circle" | |
| 60 | + @touchstart="onDirectionTouchStart" | |
| 61 | + @touchmove="onDirectionTouchMove" | |
| 62 | + @touchend="onDirectionTouchEnd"> | |
| 63 | + <view | |
| 64 | + class="up-color-picker__direction-pointer" | |
| 65 | + :style="{ | |
| 66 | + left: directionPointer.x + 'px', | |
| 67 | + top: directionPointer.y + 'px' | |
| 68 | + }" | |
| 69 | + ></view> | |
| 70 | + </view> | |
| 71 | + </view> | |
| 72 | + </view> | |
| 73 | + | |
| 74 | + <!-- 纯色选择器 --> | |
| 75 | + <view class="up-color-picker__solid"> | |
| 76 | + <!-- 饱和度和明度选择区域 --> | |
| 77 | + <view | |
| 78 | + class="up-color-picker__saturation" | |
| 79 | + :style="{ backgroundColor: `hsl(${hue}, 100%, 50%)` }" | |
| 80 | + @touchstart="onSaturationTouchStart" | |
| 81 | + @touchmove="onSaturationTouchMove" | |
| 82 | + @touchend="onSaturationTouchEnd" | |
| 83 | + > | |
| 84 | + <view | |
| 85 | + class="up-color-picker__saturation-pointer" | |
| 86 | + :style="{ | |
| 87 | + left: saturationPosition.x + 'px', | |
| 88 | + top: saturationPosition.y + 'px' | |
| 89 | + }" | |
| 90 | + ></view> | |
| 91 | + </view> | |
| 92 | + | |
| 93 | + <!-- 色相选择 --> | |
| 94 | + <view | |
| 95 | + class="up-color-picker__hue" | |
| 96 | + @touchstart="onHueTouchStart" | |
| 97 | + @touchmove="onHueTouchMove" | |
| 98 | + @touchend="onHueTouchEnd" | |
| 99 | + > | |
| 100 | + <view | |
| 101 | + class="up-color-picker__hue-pointer" | |
| 102 | + :style="{ left: huePosition + 'px' }" | |
| 103 | + ></view> | |
| 104 | + </view> | |
| 105 | + | |
| 106 | + <!-- 透明度选择 --> | |
| 107 | + <view v-if="colorTypeIndex == 0" | |
| 108 | + class="up-color-picker__alpha" | |
| 109 | + @touchstart="onAlphaTouchStart" | |
| 110 | + @touchmove="onAlphaTouchMove" | |
| 111 | + @touchend="onAlphaTouchEnd" | |
| 112 | + > | |
| 113 | + <view class="up-color-picker__alpha-bg"></view> | |
| 114 | + <view | |
| 115 | + class="up-color-picker__alpha-pointer" | |
| 116 | + :style="{ left: alphaPosition + 'px' }" | |
| 117 | + ></view> | |
| 118 | + </view> | |
| 119 | + </view> | |
| 120 | + | |
| 121 | + <!-- 常用颜色 --> | |
| 122 | + <view v-if="commonColors && commonColors.length" class="up-color-picker__common"> | |
| 123 | + <text class="up-color-picker__common-title">常用颜色</text> | |
| 124 | + <view class="up-color-picker__common-list"> | |
| 125 | + <view | |
| 126 | + v-for="(color, index) in commonColors" | |
| 127 | + :key="index" | |
| 128 | + class="up-color-picker__common-item" | |
| 129 | + :style="{ backgroundColor: color }" | |
| 130 | + @click="selectCommonColor(color)" | |
| 131 | + ></view> | |
| 132 | + </view> | |
| 133 | + </view> | |
| 134 | + | |
| 135 | + <!-- 颜色预览和操作按钮 --> | |
| 136 | + <view class="up-color-picker__footer"> | |
| 137 | + <view class="up-color-picker__preview"> | |
| 138 | + <view | |
| 139 | + class="up-color-picker__preview-color" | |
| 140 | + :style="{ backgroundColor: displayColor }" | |
| 141 | + ></view> | |
| 142 | + <text class="up-color-picker__preview-text">{{ displayColor }}</text> | |
| 143 | + </view> | |
| 144 | + <view class="up-color-picker__actions"> | |
| 145 | + <up-button | |
| 146 | + type="primary" | |
| 147 | + size="small" | |
| 148 | + @click="confirm" | |
| 149 | + class="up-color-picker__btn" | |
| 150 | + > | |
| 151 | + 确定 | |
| 152 | + </up-button> | |
| 153 | + <up-button | |
| 154 | + type="info" | |
| 155 | + size="small" | |
| 156 | + @click="close" | |
| 157 | + class="up-color-picker__btn" | |
| 158 | + > | |
| 159 | + 取消 | |
| 160 | + </up-button> | |
| 161 | + </view> | |
| 162 | + </view> | |
| 163 | + </view> | |
| 164 | + </up-popup> | |
| 165 | + </view> | |
| 166 | +</template> | |
| 167 | + | |
| 168 | +<script> | |
| 169 | +export default { | |
| 170 | + name: 'up-color-picker', | |
| 171 | + props: { | |
| 172 | + // 初始颜色值 | |
| 173 | + modelValue: { | |
| 174 | + type: String, | |
| 175 | + default: '#ff0000' | |
| 176 | + }, | |
| 177 | + // 常用颜色列表 | |
| 178 | + commonColors: { | |
| 179 | + type: Array, | |
| 180 | + default: () => [ | |
| 181 | + ] | |
| 182 | + } | |
| 183 | + }, | |
| 184 | + data() { | |
| 185 | + return { | |
| 186 | + show: false, | |
| 187 | + // 颜色类型索引:0-纯色,1-渐变色 | |
| 188 | + colorTypeIndex: 0, | |
| 189 | + // 纯色相关数据 | |
| 190 | + hue: 0, | |
| 191 | + saturation: 100, | |
| 192 | + lightness: 50, | |
| 193 | + alpha: 1, | |
| 194 | + saturationPosition: { x: 150, y: 0 }, | |
| 195 | + huePosition: 0, | |
| 196 | + alphaPosition: 0, // 将在initColor中设置为最右侧 | |
| 197 | + // 渐变色相关数据 | |
| 198 | + gradientColors: [ | |
| 199 | + { color: '#ff0000', percent: 0 }, | |
| 200 | + { color: '#0000ff', percent: 1 } | |
| 201 | + ], | |
| 202 | + gradientDirections: [ | |
| 203 | + { label: '从左到右', value: 'to right' }, | |
| 204 | + { label: '从上到下', value: 'to bottom' }, | |
| 205 | + { label: '从左上到右下', value: 'to bottom right' }, | |
| 206 | + { label: '从右上到左下', value: 'to bottom left' } | |
| 207 | + ], | |
| 208 | + currentDirection: { label: '从左到右', value: 'to right' }, | |
| 209 | + showDirectionPicker: false, | |
| 210 | + // 当前选中颜色 | |
| 211 | + currentColor: '#ff0000', | |
| 212 | + // 渐变色控制相关 | |
| 213 | + draggingPointerIndex: -1, | |
| 214 | + directionPointer: { x: 20, y: 20 }, // 默认向右(在圆的右侧) | |
| 215 | + // 渐变节点颜色修改相关 | |
| 216 | + editingGradientIndex: -1, | |
| 217 | + // 保存纯色和渐变色的各自状态 | |
| 218 | + solidColorState: { | |
| 219 | + hue: 0, | |
| 220 | + saturation: 100, | |
| 221 | + lightness: 50, | |
| 222 | + alpha: 1, | |
| 223 | + saturationPosition: { x: 150, y: 0 }, | |
| 224 | + huePosition: 0, | |
| 225 | + alphaPosition: 0, // 将在initColor中设置为最右侧 | |
| 226 | + currentColor: '#ff0000' | |
| 227 | + }, | |
| 228 | + gradientColorState: { | |
| 229 | + gradientColors: [ | |
| 230 | + { color: '#ff0000', percent: 0 }, | |
| 231 | + { color: '#0000ff', percent: 1 } | |
| 232 | + ], | |
| 233 | + currentDirection: { label: '从左到右', value: 'to right' }, | |
| 234 | + directionPointer: { x: 100, y: 0 } | |
| 235 | + }, | |
| 236 | + // 区分预览类型:solid-纯色预览,gradient-整体渐变预览,gradient-point-渐变点预览 | |
| 237 | + previewType: 'solid' | |
| 238 | + } | |
| 239 | + }, | |
| 240 | + computed: { | |
| 241 | + // 渐变色样式 | |
| 242 | + gradientStyle() { | |
| 243 | + const colors = this.gradientColors.map(item => `${item.color} ${Math.round(item.percent * 100)}%`).join(', ') | |
| 244 | + return `linear-gradient(${this.currentDirection.value}, ${colors})` | |
| 245 | + }, | |
| 246 | + // 显示的颜色预览值 | |
| 247 | + displayColor() { | |
| 248 | + if (this.previewType === 'gradient-point' && this.editingGradientIndex >= 0) { | |
| 249 | + return this.gradientColors[this.editingGradientIndex].color; | |
| 250 | + } | |
| 251 | + console.log(this.editingGradientIndex) | |
| 252 | + return this.currentColor; | |
| 253 | + } | |
| 254 | + }, | |
| 255 | + watch: { | |
| 256 | + show(newVal) { | |
| 257 | + if (newVal) { | |
| 258 | + this.initColor() | |
| 259 | + } | |
| 260 | + }, | |
| 261 | + colorTypeIndex(newVal) { | |
| 262 | + if (newVal == 0) { | |
| 263 | + this.editingGradientIndex = -1 | |
| 264 | + } | |
| 265 | + }, | |
| 266 | + }, | |
| 267 | + mounted() { | |
| 268 | + this.initColor() | |
| 269 | + }, | |
| 270 | + emits: ['update:modelValue', 'confirm', 'close'], | |
| 271 | + methods: { | |
| 272 | + // 初始化颜色 | |
| 273 | + initColor() { | |
| 274 | + if (this.modelValue) { | |
| 275 | + this.currentColor = this.modelValue | |
| 276 | + if (this.modelValue.includes('linear-gradient')) { | |
| 277 | + // 解析渐变色 | |
| 278 | + this.colorTypeIndex = 1 | |
| 279 | + this.parseGradientColor(this.modelValue) | |
| 280 | + this.previewType = 'gradient'; | |
| 281 | + } else { | |
| 282 | + // 解析纯色 | |
| 283 | + this.colorTypeIndex = 0 | |
| 284 | + this.parseSolidColor(this.modelValue) | |
| 285 | + this.previewType = 'solid'; | |
| 286 | + } | |
| 287 | + } | |
| 288 | + // 初始化方向指针位置 | |
| 289 | + this.initDirectionPointer(); | |
| 290 | + // 初始化alphaPosition为最右侧 | |
| 291 | + this.initAlphaPosition(); | |
| 292 | + }, | |
| 293 | + | |
| 294 | + // 初始化alpha位置为最右侧 | |
| 295 | + async initAlphaPosition() { | |
| 296 | + const query = uni.createSelectorQuery().in(this); | |
| 297 | + query.select('.up-color-picker__alpha').boundingClientRect(); | |
| 298 | + await this.$nextTick(); | |
| 299 | + query.exec(res => { | |
| 300 | + const rect = res[0]; | |
| 301 | + if (rect) { | |
| 302 | + this.alphaPosition = rect.width || 150; // 默认150像素 | |
| 303 | + } else { | |
| 304 | + this.alphaPosition = 150; // 默认值 | |
| 305 | + } | |
| 306 | + | |
| 307 | + // 同步solidColorState中的alphaPosition | |
| 308 | + this.solidColorState.alphaPosition = this.alphaPosition; | |
| 309 | + this.updateSolidColor(); | |
| 310 | + }); | |
| 311 | + }, | |
| 312 | + | |
| 313 | + // 初始化方向指针位置 | |
| 314 | + initDirectionPointer() { | |
| 315 | + const angle = this.getDirectionAngle(this.currentDirection.value); | |
| 316 | + this.setDirectionPointerByAngle(angle); | |
| 317 | + }, | |
| 318 | + | |
| 319 | + // 根据方向值获取角度 | |
| 320 | + getDirectionAngle(direction) { | |
| 321 | + switch(direction) { | |
| 322 | + case 'to right': return 0; | |
| 323 | + case 'to bottom': return 90; | |
| 324 | + case 'to left': return 180; | |
| 325 | + case 'to top': return 270; | |
| 326 | + case 'to bottom right': return 45; | |
| 327 | + case 'to bottom left': return 135; | |
| 328 | + case 'to top left': return 225; | |
| 329 | + case 'to top right': return 315; | |
| 330 | + default: return 0; | |
| 331 | + } | |
| 332 | + }, | |
| 333 | + | |
| 334 | + // 根据角度设置方向指针位置 | |
| 335 | + setDirectionPointerByAngle(angle) { | |
| 336 | + const radian = angle * Math.PI / 180; | |
| 337 | + const radius = 20; // 圆半径 | |
| 338 | + this.directionPointer = { | |
| 339 | + x: radius * Math.cos(radian) + 20, // 20是圆心位置 | |
| 340 | + y: radius * Math.sin(radian) + 20 | |
| 341 | + }; | |
| 342 | + }, | |
| 343 | + | |
| 344 | + // 打开颜色选择器以修改渐变节点颜色 | |
| 345 | + openColorPickerForGradient(index) { | |
| 346 | + this.editingGradientIndex = index; | |
| 347 | + // 设置当前颜色为选中节点的颜色 | |
| 348 | + const color = this.gradientColors[index].color; | |
| 349 | + this.currentColor = color; | |
| 350 | + // 设置预览类型为渐变点预览 | |
| 351 | + this.previewType = 'gradient-point'; | |
| 352 | + | |
| 353 | + // 解析颜色并设置选择器位置 | |
| 354 | + if (!color.includes('linear-gradient')) { | |
| 355 | + this.colorTypeIndex = 0; | |
| 356 | + this.parseSolidColor(color); | |
| 357 | + // 保存当前渐变状态 | |
| 358 | + this.gradientColorState = { | |
| 359 | + gradientColors: [...this.gradientColors], | |
| 360 | + currentDirection: {...this.currentDirection}, | |
| 361 | + directionPointer: {...this.directionPointer} | |
| 362 | + }; | |
| 363 | + } | |
| 364 | + }, | |
| 365 | + | |
| 366 | + // 解析纯色 | |
| 367 | + parseSolidColor(color) { | |
| 368 | + // 简化处理,实际项目中可以使用更复杂的颜色解析 | |
| 369 | + this.currentColor = color | |
| 370 | + // 如果正在编辑渐变节点,则更新该节点颜色 | |
| 371 | + if (this.editingGradientIndex >= 0) { | |
| 372 | + this.gradientColors[this.editingGradientIndex].color = color; | |
| 373 | + // 切换回渐变模式并恢复渐变状态 | |
| 374 | + this.colorTypeIndex = 1; | |
| 375 | + Object.assign(this, this.solidColorState); | |
| 376 | + this.previewType = 'gradient-point'; | |
| 377 | + } else { | |
| 378 | + this.previewType = 'solid'; | |
| 379 | + } | |
| 380 | + }, | |
| 381 | + | |
| 382 | + // 解析渐变色 | |
| 383 | + parseGradientColor(gradient) { | |
| 384 | + // 简化处理,实际项目中可以使用更复杂的渐变解析 | |
| 385 | + this.currentColor = gradient | |
| 386 | + // this.previewType = 'gradient'; | |
| 387 | + }, | |
| 388 | + | |
| 389 | + // 切换颜色类型 | |
| 390 | + changeColorType(index) { | |
| 391 | + // 保存当前状态 | |
| 392 | + if (this.colorTypeIndex === 0) { | |
| 393 | + // 保存纯色状态 | |
| 394 | + this.solidColorState = { | |
| 395 | + hue: this.hue, | |
| 396 | + saturation: this.saturation, | |
| 397 | + lightness: this.lightness, | |
| 398 | + alpha: this.alpha, | |
| 399 | + saturationPosition: {...this.saturationPosition}, | |
| 400 | + huePosition: this.huePosition, | |
| 401 | + alphaPosition: this.alphaPosition, | |
| 402 | + currentColor: this.currentColor | |
| 403 | + }; | |
| 404 | + } else { | |
| 405 | + // 保存渐变状态 | |
| 406 | + this.gradientColorState = { | |
| 407 | + gradientColors: [...this.gradientColors], | |
| 408 | + currentDirection: {...this.currentDirection}, | |
| 409 | + directionPointer: {...this.directionPointer} | |
| 410 | + }; | |
| 411 | + } | |
| 412 | + | |
| 413 | + this.colorTypeIndex = index; | |
| 414 | + | |
| 415 | + // 恢复目标状态 | |
| 416 | + if (index === 0) { | |
| 417 | + Object.assign(this, this.solidColorState); | |
| 418 | + this.previewType = 'solid'; | |
| 419 | + } else { | |
| 420 | + Object.assign(this, this.gradientColorState); | |
| 421 | + // 确保gradientColors是响应式的 | |
| 422 | + this.gradientColors = [...this.gradientColorState.gradientColors]; | |
| 423 | + this.previewType = 'gradient'; | |
| 424 | + } | |
| 425 | + }, | |
| 426 | + | |
| 427 | + // 饱和度和明度触摸开始 | |
| 428 | + onSaturationTouchStart(e) { | |
| 429 | + this.updateSaturationPosition(e) | |
| 430 | + }, | |
| 431 | + | |
| 432 | + // 饱和度和明度触摸移动 | |
| 433 | + onSaturationTouchMove(e) { | |
| 434 | + this.updateSaturationPosition(e) | |
| 435 | + }, | |
| 436 | + | |
| 437 | + // 饱和度和明度触摸结束 | |
| 438 | + onSaturationTouchEnd(e) { | |
| 439 | + this.updateSaturationPosition(e) | |
| 440 | + }, | |
| 441 | + | |
| 442 | + // 更新饱和度和明度位置 | |
| 443 | + updateSaturationPosition(e) { | |
| 444 | + const touch = e.touches[0] || e.changedTouches[0] | |
| 445 | + const target = e.currentTarget | |
| 446 | + const query = uni.createSelectorQuery().in(this); | |
| 447 | + query.select('.up-color-picker__saturation').boundingClientRect() | |
| 448 | + | |
| 449 | + query.exec(res => { | |
| 450 | + const rect = res[0]; | |
| 451 | + if (rect) { | |
| 452 | + let x = touch.clientX - rect.left | |
| 453 | + let y = touch.clientY - rect.top | |
| 454 | + | |
| 455 | + // 边界处理 | |
| 456 | + x = Math.max(0, Math.min(x, rect.width)) | |
| 457 | + y = Math.max(0, Math.min(y, rect.height)) | |
| 458 | + | |
| 459 | + this.saturationPosition = { x, y } | |
| 460 | + this.updateSolidColor() | |
| 461 | + } | |
| 462 | + }) | |
| 463 | + }, | |
| 464 | + | |
| 465 | + // 色相触摸开始 | |
| 466 | + onHueTouchStart(e) { | |
| 467 | + this.updateHuePosition(e) | |
| 468 | + }, | |
| 469 | + | |
| 470 | + // 色相触摸移动 | |
| 471 | + onHueTouchMove(e) { | |
| 472 | + this.updateHuePosition(e) | |
| 473 | + }, | |
| 474 | + | |
| 475 | + // 色相触摸结束 | |
| 476 | + onHueTouchEnd(e) { | |
| 477 | + this.updateHuePosition(e) | |
| 478 | + }, | |
| 479 | + | |
| 480 | + // 更新色相位置 | |
| 481 | + updateHuePosition(e) { | |
| 482 | + const touch = e.touches[0] || e.changedTouches[0] | |
| 483 | + const target = e.currentTarget | |
| 484 | + const query = uni.createSelectorQuery().in(this); | |
| 485 | + query.select('.up-color-picker__hue').boundingClientRect() | |
| 486 | + | |
| 487 | + query.exec(res => { | |
| 488 | + const rect = res[0]; | |
| 489 | + if (rect) { | |
| 490 | + let x = touch.clientX - rect.left | |
| 491 | + x = Math.max(0, Math.min(x, rect.width)) | |
| 492 | + this.huePosition = x | |
| 493 | + this.hue = Math.round((x / rect.width) * 360) | |
| 494 | + this.updateSolidColor() | |
| 495 | + } | |
| 496 | + }) | |
| 497 | + }, | |
| 498 | + | |
| 499 | + // 透明度触摸开始 | |
| 500 | + onAlphaTouchStart(e) { | |
| 501 | + this.updateAlphaPosition(e) | |
| 502 | + }, | |
| 503 | + | |
| 504 | + // 透明度触摸移动 | |
| 505 | + onAlphaTouchMove(e) { | |
| 506 | + this.updateAlphaPosition(e) | |
| 507 | + }, | |
| 508 | + | |
| 509 | + // 透明度触摸结束 | |
| 510 | + onAlphaTouchEnd(e) { | |
| 511 | + this.updateAlphaPosition(e) | |
| 512 | + }, | |
| 513 | + | |
| 514 | + // 更新透明度位置 | |
| 515 | + updateAlphaPosition(e) { | |
| 516 | + const touch = e.touches[0] || e.changedTouches[0] | |
| 517 | + const target = e.currentTarget | |
| 518 | + const query = uni.createSelectorQuery().in(this); | |
| 519 | + query.select('.up-color-picker__alpha').boundingClientRect() | |
| 520 | + | |
| 521 | + query.exec(res => { | |
| 522 | + const rect = res[0]; | |
| 523 | + if (rect) { | |
| 524 | + let x = touch.clientX - rect.left | |
| 525 | + x = Math.max(0, Math.min(x, rect.width)) | |
| 526 | + this.alphaPosition = x | |
| 527 | + this.alpha = x / rect.width | |
| 528 | + this.updateSolidColor() | |
| 529 | + } | |
| 530 | + }) | |
| 531 | + }, | |
| 532 | + | |
| 533 | + // 更新纯色 | |
| 534 | + updateSolidColor() { | |
| 535 | + // 使用实际元素尺寸替代硬编码的150 | |
| 536 | + const query = uni.createSelectorQuery().in(this); | |
| 537 | + query.select('.up-color-picker__saturation').boundingClientRect(); | |
| 538 | + | |
| 539 | + query.exec(res => { | |
| 540 | + const rect = res[0]; | |
| 541 | + const size = rect ? Math.min(rect.width, rect.height) : 150; // 默认150作为后备值 | |
| 542 | + | |
| 543 | + const s = (this.saturationPosition.x / size) * 100 | |
| 544 | + const l = 100 - (this.saturationPosition.y / size) * 100 | |
| 545 | + this.saturation = s | |
| 546 | + this.lightness = l | |
| 547 | + | |
| 548 | + // 使用正确的HSL转RGB算法 | |
| 549 | + if (this.colorTypeIndex == 0) { | |
| 550 | + this.currentColor = this.hslToRgb(this.hue, this.saturation, this.lightness, this.alpha) | |
| 551 | + } else if (this.colorTypeIndex == 1) { | |
| 552 | + this.gradientColors[this.editingGradientIndex].color | |
| 553 | + = this.hslToRgb(this.hue, this.saturation, this.lightness, this.alpha) | |
| 554 | + } | |
| 555 | + }); | |
| 556 | + }, | |
| 557 | + | |
| 558 | + // 添加渐变色 | |
| 559 | + addGradientColor() { | |
| 560 | + if (this.gradientColors.length < 5) { | |
| 561 | + this.gradientColors.push({ | |
| 562 | + color: '#ffffff', | |
| 563 | + percent: 1 | |
| 564 | + }) | |
| 565 | + } | |
| 566 | + }, | |
| 567 | + | |
| 568 | + // 删除渐变色 | |
| 569 | + removeGradientColor(index) { | |
| 570 | + if (this.gradientColors.length > 2) { | |
| 571 | + this.gradientColors.splice(index, 1) | |
| 572 | + } | |
| 573 | + }, | |
| 574 | + | |
| 575 | + // 获取渐变控制点位置 | |
| 576 | + getGradientPointerPosition(index) { | |
| 577 | + const trackWidth = 280; // 需要与样式中的宽度一致 | |
| 578 | + return this.gradientColors[index].percent * trackWidth; | |
| 579 | + }, | |
| 580 | + | |
| 581 | + // 更新渐变色 | |
| 582 | + updateGradientColor(e) { | |
| 583 | + const touch = e.touches[0] || e.changedTouches[0]; | |
| 584 | + const query = uni.createSelectorQuery().in(this); | |
| 585 | + query.select('.up-color-picker__gradient-track').boundingClientRect(); | |
| 586 | + | |
| 587 | + query.exec(res => { | |
| 588 | + const rect = res[0]; | |
| 589 | + if (rect) { | |
| 590 | + let x = touch.clientX - rect.left; | |
| 591 | + // 边界处理 | |
| 592 | + x = Math.max(0, Math.min(x, rect.width)); | |
| 593 | + const percent = x / rect.width; | |
| 594 | + | |
| 595 | + // 如果正在拖动控制点,则更新该控制点位置 | |
| 596 | + if (this.draggingPointerIndex >= 0) { | |
| 597 | + this.gradientColors[this.draggingPointerIndex].percent = percent; | |
| 598 | + // 保持控制点顺序 | |
| 599 | + this.gradientColors.sort((a, b) => a.percent - b.percent); | |
| 600 | + } else { | |
| 601 | + // 否则添加新的控制点(此处可选功能) | |
| 602 | + } | |
| 603 | + } | |
| 604 | + }); | |
| 605 | + }, | |
| 606 | + | |
| 607 | + // 控制点触摸开始 | |
| 608 | + onPointerTouchStart(e, index) { | |
| 609 | + this.draggingPointerIndex = index; | |
| 610 | + // 点击控制点时更新预览颜色 | |
| 611 | + this.currentColor = this.gradientColors[index].color; | |
| 612 | + this.previewType = 'gradient-point'; | |
| 613 | + this.editingGradientIndex = index; // 添加这行以正确设置编辑索引 | |
| 614 | + // 阻止事件冒泡 | |
| 615 | + e.stopPropagation(); | |
| 616 | + }, | |
| 617 | + | |
| 618 | + // 控制点触摸移动 | |
| 619 | + onPointerTouchMove(e) { | |
| 620 | + if (this.draggingPointerIndex === -1) return; | |
| 621 | + | |
| 622 | + const touch = e.touches[0] || e.changedTouches[0]; | |
| 623 | + const query = uni.createSelectorQuery().in(this); | |
| 624 | + query.select('.up-color-picker__gradient-track').boundingClientRect(); | |
| 625 | + | |
| 626 | + query.exec(res => { | |
| 627 | + const rect = res[0]; | |
| 628 | + if (rect) { | |
| 629 | + let x = touch.clientX - rect.left; | |
| 630 | + // 边界处理 | |
| 631 | + x = Math.max(0, Math.min(x, rect.width)); | |
| 632 | + let percent = x / rect.width; | |
| 633 | + | |
| 634 | + // 处理边界情况,确保能精确到达两端 | |
| 635 | + if (x === 0) percent = 0; | |
| 636 | + if (x === rect.width) percent = 1; | |
| 637 | + | |
| 638 | + // 更新控制点位置 | |
| 639 | + this.gradientColors[this.draggingPointerIndex].percent = percent; | |
| 640 | + // 保持控制点顺序 | |
| 641 | + this.gradientColors.sort((a, b) => a.percent - b.percent); | |
| 642 | + // 更新拖拽索引以匹配排序后的索引 | |
| 643 | + this.draggingPointerIndex = this.gradientColors.findIndex((item, index) => { | |
| 644 | + // 使用更精确的比较方法,处理浮点数精度问题 | |
| 645 | + return Math.abs(item.percent - percent) < 0.0001; | |
| 646 | + }); | |
| 647 | + } | |
| 648 | + }); | |
| 649 | + }, | |
| 650 | + | |
| 651 | + // 控制点触摸结束 | |
| 652 | + onPointerTouchEnd() { | |
| 653 | + this.draggingPointerIndex = -1; | |
| 654 | + }, | |
| 655 | + | |
| 656 | + // 方向选择器触摸开始 | |
| 657 | + onDirectionTouchStart(e) { | |
| 658 | + this.updateDirection(e); | |
| 659 | + }, | |
| 660 | + | |
| 661 | + // 方向选择器触摸移动 | |
| 662 | + onDirectionTouchMove(e) { | |
| 663 | + this.updateDirection(e); | |
| 664 | + }, | |
| 665 | + | |
| 666 | + // 方向选择器触摸结束 | |
| 667 | + onDirectionTouchEnd(e) { | |
| 668 | + this.updateDirection(e); | |
| 669 | + }, | |
| 670 | + | |
| 671 | + // 更新方向 | |
| 672 | + updateDirection(e) { | |
| 673 | + const touch = e.touches[0] || e.changedTouches[0]; | |
| 674 | + const query = uni.createSelectorQuery().in(this); | |
| 675 | + query.select('.up-color-picker__gradient__direction-circle').boundingClientRect(); | |
| 676 | + | |
| 677 | + query.exec(res => { | |
| 678 | + const rect = res[0]; | |
| 679 | + if (rect) { | |
| 680 | + const centerX = rect.left + rect.width / 2; | |
| 681 | + const centerY = rect.top + rect.height / 2; | |
| 682 | + const x = touch.clientX - centerX; | |
| 683 | + const y = touch.clientY - centerY; | |
| 684 | + const distance = Math.sqrt(x * x + y * y); | |
| 685 | + const maxDistance = rect.width / 2; | |
| 686 | + | |
| 687 | + // 限制在圆内 | |
| 688 | + if (distance <= maxDistance) { | |
| 689 | + this.directionPointer = { | |
| 690 | + x: x + rect.width / 2, | |
| 691 | + y: y + rect.height / 2 | |
| 692 | + }; | |
| 693 | + } else { | |
| 694 | + // 限制在圆周上 | |
| 695 | + const ratio = maxDistance / distance; | |
| 696 | + this.directionPointer = { | |
| 697 | + x: x * ratio + rect.width / 2, | |
| 698 | + y: y * ratio + rect.height / 2 | |
| 699 | + }; | |
| 700 | + } | |
| 701 | + | |
| 702 | + // 计算角度并更新方向 | |
| 703 | + const angle = Math.atan2(y, x) * 180 / Math.PI; | |
| 704 | + console.log(angle) | |
| 705 | + this.updateGradientDirection(angle); | |
| 706 | + } | |
| 707 | + }); | |
| 708 | + }, | |
| 709 | + | |
| 710 | + // 根据角度更新渐变方向 | |
| 711 | + updateGradientDirection(angle) { | |
| 712 | + // 角度转换为标准方向 | |
| 713 | + if (angle < 0) angle += 360; | |
| 714 | + | |
| 715 | + if (angle >= 315 || angle < 45) { | |
| 716 | + this.currentDirection = { label: '从左到右', value: 'to right' }; | |
| 717 | + } else if (angle >= 45 && angle < 135) { | |
| 718 | + this.currentDirection = { label: '从上到下', value: 'to bottom' }; | |
| 719 | + } else if (angle >= 135 && angle < 225) { | |
| 720 | + this.currentDirection = { label: '从右到左', value: 'to left' }; | |
| 721 | + } else { | |
| 722 | + this.currentDirection = { label: '从下到上', value: 'to top' }; | |
| 723 | + } | |
| 724 | + }, | |
| 725 | + | |
| 726 | + // 确认方向选择 | |
| 727 | + confirmDirection(e) { | |
| 728 | + this.currentDirection = this.gradientDirections[e.index] | |
| 729 | + this.showDirectionPicker = false | |
| 730 | + }, | |
| 731 | + | |
| 732 | + // 选择常用颜色 | |
| 733 | + selectCommonColor(color) { | |
| 734 | + this.currentColor = color | |
| 735 | + if (this.colorTypeIndex === 0) { | |
| 736 | + this.parseSolidColor(color) | |
| 737 | + } else { | |
| 738 | + // 如果是渐变模式,将常用颜色作为渐变的一个节点 | |
| 739 | + this.gradientColors[this.editingGradientIndex].color = color | |
| 740 | + } | |
| 741 | + }, | |
| 742 | + | |
| 743 | + // 确认选择 | |
| 744 | + confirm() { | |
| 745 | + let color = this.currentColor | |
| 746 | + if (this.colorTypeIndex === 1) { | |
| 747 | + color = this.gradientStyle | |
| 748 | + } | |
| 749 | + this.$emit('update:modelValue', color) | |
| 750 | + this.show = false; | |
| 751 | + this.$emit('confirm', color) | |
| 752 | + // 重置编辑状态 | |
| 753 | + this.editingGradientIndex = -1; | |
| 754 | + this.previewType = this.colorTypeIndex === 0 ? 'solid' : 'gradient'; | |
| 755 | + this.close() | |
| 756 | + }, | |
| 757 | + | |
| 758 | + // 关闭选择器 | |
| 759 | + close() { | |
| 760 | + this.show = false; | |
| 761 | + this.$emit('close') | |
| 762 | + }, | |
| 763 | + | |
| 764 | + // HSL转RGB辅助函数 | |
| 765 | + hslToRgb(h, s, l, a = 1) { | |
| 766 | + h = h / 360; | |
| 767 | + s = s / 100; | |
| 768 | + l = l / 100; | |
| 769 | + | |
| 770 | + let r, g, b; | |
| 771 | + | |
| 772 | + if (s === 0) { | |
| 773 | + r = g = b = l; // achromatic | |
| 774 | + } else { | |
| 775 | + const hue2rgb = (p, q, t) => { | |
| 776 | + if (t < 0) t += 1; | |
| 777 | + if (t > 1) t -= 1; | |
| 778 | + if (t < 1/6) return p + (q - p) * 6 * t; | |
| 779 | + if (t < 1/2) return q; | |
| 780 | + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; | |
| 781 | + return p; | |
| 782 | + }; | |
| 783 | + | |
| 784 | + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
| 785 | + const p = 2 * l - q; | |
| 786 | + r = hue2rgb(p, q, h + 1/3); | |
| 787 | + g = hue2rgb(p, q, h); | |
| 788 | + b = hue2rgb(p, q, h - 1/3); | |
| 789 | + } | |
| 790 | + | |
| 791 | + const round = (value) => Math.round(value * 255); | |
| 792 | + return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${a.toFixed(2)})`; | |
| 793 | + } | |
| 794 | + } | |
| 795 | +} | |
| 796 | +</script> | |
| 797 | + | |
| 798 | +<style lang="scss" scoped> | |
| 799 | +.up-color-picker { | |
| 800 | + &__content { | |
| 801 | + width: 100%; | |
| 802 | + padding: 20px; | |
| 803 | + background-color: #fff; | |
| 804 | + } | |
| 805 | + | |
| 806 | + &__header { | |
| 807 | + text-align: center; | |
| 808 | + margin-bottom: 20px; | |
| 809 | + } | |
| 810 | + | |
| 811 | + &__title { | |
| 812 | + font-size: 18px; | |
| 813 | + font-weight: bold; | |
| 814 | + color: #333; | |
| 815 | + } | |
| 816 | + | |
| 817 | + &__switch { | |
| 818 | + margin-bottom: 20px; | |
| 819 | + } | |
| 820 | + | |
| 821 | + &__saturation { | |
| 822 | + position: relative; | |
| 823 | + width: 100%; | |
| 824 | + height: 150px; | |
| 825 | + border-radius: 4px; | |
| 826 | + margin-bottom: 15px; | |
| 827 | + overflow: hidden; | |
| 828 | + | |
| 829 | + &:after { | |
| 830 | + content: ''; | |
| 831 | + position: absolute; | |
| 832 | + top: 0; | |
| 833 | + left: 0; | |
| 834 | + right: 0; | |
| 835 | + bottom: 0; | |
| 836 | + background: linear-gradient(to right, #fff, rgba(255,255,255,0)); | |
| 837 | + } | |
| 838 | + | |
| 839 | + &:before { | |
| 840 | + content: ''; | |
| 841 | + position: absolute; | |
| 842 | + top: 0; | |
| 843 | + left: 0; | |
| 844 | + right: 0; | |
| 845 | + bottom: 0; | |
| 846 | + background: linear-gradient(to top, #000, rgba(0,0,0,0)); | |
| 847 | + } | |
| 848 | + } | |
| 849 | + | |
| 850 | + &__saturation-pointer { | |
| 851 | + position: absolute; | |
| 852 | + width: 12px; | |
| 853 | + height: 12px; | |
| 854 | + border: 2px solid #fff; | |
| 855 | + border-radius: 50%; | |
| 856 | + transform: translate(-50%, -50%); | |
| 857 | + box-shadow: 0 0 2px rgba(0,0,0,0.5); | |
| 858 | + pointer-events: none; | |
| 859 | + } | |
| 860 | + | |
| 861 | + &__hue, | |
| 862 | + &__alpha { | |
| 863 | + position: relative; | |
| 864 | + width: 100%; | |
| 865 | + height: 12px; | |
| 866 | + border-radius: 6px; | |
| 867 | + margin-bottom: 15px; | |
| 868 | + cursor: pointer; | |
| 869 | + } | |
| 870 | + | |
| 871 | + &__hue { | |
| 872 | + background: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%); | |
| 873 | + } | |
| 874 | + | |
| 875 | + &__alpha { | |
| 876 | + position: relative; | |
| 877 | + overflow: hidden; | |
| 878 | + | |
| 879 | + &-bg { | |
| 880 | + position: absolute; | |
| 881 | + top: 0; | |
| 882 | + left: 0; | |
| 883 | + right: 0; | |
| 884 | + bottom: 0; | |
| 885 | + background: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%), | |
| 886 | + linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%); | |
| 887 | + background-size: 10px 10px; | |
| 888 | + background-position: 0 0, 5px 5px; | |
| 889 | + } | |
| 890 | + | |
| 891 | + &:after { | |
| 892 | + content: ''; | |
| 893 | + position: absolute; | |
| 894 | + top: 0; | |
| 895 | + left: 0; | |
| 896 | + right: 0; | |
| 897 | + bottom: 0; | |
| 898 | + background: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,1)); | |
| 899 | + } | |
| 900 | + } | |
| 901 | + | |
| 902 | + &__hue-pointer, | |
| 903 | + &__alpha-pointer { | |
| 904 | + position: absolute; | |
| 905 | + width: 4px; | |
| 906 | + height: 16px; | |
| 907 | + background: #fff; | |
| 908 | + border: 1px solid #ccc; | |
| 909 | + border-radius: 2px; | |
| 910 | + transform: translateX(-50%); | |
| 911 | + top: -2px; | |
| 912 | + box-shadow: 0 0 2px rgba(0,0,0,0.5); | |
| 913 | + pointer-events: none; | |
| 914 | + } | |
| 915 | + | |
| 916 | + &__gradient { | |
| 917 | + &-bar { | |
| 918 | + width: 100%; | |
| 919 | + height: 40px; | |
| 920 | + border-radius: 4px; | |
| 921 | + } | |
| 922 | + | |
| 923 | + &-track { | |
| 924 | + position: relative; | |
| 925 | + margin-top: 20px; | |
| 926 | + width: 100%; | |
| 927 | + height: 32px; | |
| 928 | + border-radius: 4px; | |
| 929 | + margin-bottom: 15px; | |
| 930 | + cursor: pointer; | |
| 931 | + } | |
| 932 | + | |
| 933 | + &-pointer { | |
| 934 | + position: absolute; | |
| 935 | + top: -10px; | |
| 936 | + transform: translateX(-50%); | |
| 937 | + width: 0; | |
| 938 | + height: 0; | |
| 939 | + border-left: 8px solid transparent; | |
| 940 | + border-right: 8px solid transparent; | |
| 941 | + border-top: 10px solid #333; | |
| 942 | + z-index: 10; | |
| 943 | + } | |
| 944 | + | |
| 945 | + &-pointer-inner { | |
| 946 | + position: absolute; | |
| 947 | + top: -25px; | |
| 948 | + left: -10px; | |
| 949 | + width: 20px; | |
| 950 | + height: 20px; | |
| 951 | + border-radius: 50%; | |
| 952 | + border: 2px solid #fff; | |
| 953 | + box-shadow: 0 0 2px rgba(0,0,0,0.5); | |
| 954 | + } | |
| 955 | + | |
| 956 | + &-controls { | |
| 957 | + display: flex; | |
| 958 | + flex-direction: row; | |
| 959 | + flex-wrap: wrap; | |
| 960 | + align-items: center; | |
| 961 | + margin-bottom: 15px; | |
| 962 | + } | |
| 963 | + | |
| 964 | + &-item { | |
| 965 | + display: flex; | |
| 966 | + flex-direction: row; | |
| 967 | + align-items: center; | |
| 968 | + margin-right: 10px; | |
| 969 | + margin-bottom: 10px; | |
| 970 | + } | |
| 971 | + | |
| 972 | + &-color-preview { | |
| 973 | + width: 20px; | |
| 974 | + height: 20px; | |
| 975 | + border-radius: 50%; | |
| 976 | + margin-right: 5px; | |
| 977 | + border: 1px solid #eee; | |
| 978 | + } | |
| 979 | + | |
| 980 | + &-percent { | |
| 981 | + font-size: 12px; | |
| 982 | + color: #666; | |
| 983 | + margin-right: 5px; | |
| 984 | + } | |
| 985 | + | |
| 986 | + &-direction { | |
| 987 | + display: flex; | |
| 988 | + flex-direction: row; | |
| 989 | + align-items: center; | |
| 990 | + margin: 10px 0; | |
| 991 | + } | |
| 992 | + | |
| 993 | + &__direction-circle { | |
| 994 | + width: 40px; | |
| 995 | + height: 40px; | |
| 996 | + border-radius: 50%; | |
| 997 | + background: conic-gradient( | |
| 998 | + #ff0000 0deg, | |
| 999 | + #ffff00 60deg, | |
| 1000 | + #00ff00 120deg, | |
| 1001 | + #00ffff 180deg, | |
| 1002 | + #0000ff 240deg, | |
| 1003 | + #ff00ff 300deg, | |
| 1004 | + #ff0000 360deg | |
| 1005 | + ); | |
| 1006 | + position: relative; | |
| 1007 | + margin: 10px auto; | |
| 1008 | + cursor: pointer; | |
| 1009 | + border: 2px solid #eee; | |
| 1010 | + } | |
| 1011 | + | |
| 1012 | + &__direction-pointer { | |
| 1013 | + position: absolute; | |
| 1014 | + width: 6px; | |
| 1015 | + height: 6px; | |
| 1016 | + background: #fff; | |
| 1017 | + border: 2px solid #333; | |
| 1018 | + border-radius: 50%; | |
| 1019 | + transform: translate(-50%, -50%); | |
| 1020 | + box-shadow: 0 0 2px rgba(0,0,0,0.5); | |
| 1021 | + z-index: 10; | |
| 1022 | + pointer-events: none; | |
| 1023 | + } | |
| 1024 | + } | |
| 1025 | + | |
| 1026 | + &__add-btn { | |
| 1027 | + margin-top: 10px; | |
| 1028 | + } | |
| 1029 | + | |
| 1030 | + &__common { | |
| 1031 | + margin-top: 20px; | |
| 1032 | + | |
| 1033 | + &-title { | |
| 1034 | + display: block; | |
| 1035 | + margin-bottom: 10px; | |
| 1036 | + font-size: 14px; | |
| 1037 | + color: #666; | |
| 1038 | + } | |
| 1039 | + | |
| 1040 | + &-list { | |
| 1041 | + display: flex; | |
| 1042 | + flex-direction: row; | |
| 1043 | + flex-wrap: wrap; | |
| 1044 | + } | |
| 1045 | + | |
| 1046 | + &-item { | |
| 1047 | + width: 24px; | |
| 1048 | + height: 24px; | |
| 1049 | + border-radius: 50%; | |
| 1050 | + margin-right: 10px; | |
| 1051 | + margin-bottom: 10px; | |
| 1052 | + border: 1px solid #eee; | |
| 1053 | + cursor: pointer; | |
| 1054 | + } | |
| 1055 | + } | |
| 1056 | + | |
| 1057 | + &__footer { | |
| 1058 | + margin-top: 20px; | |
| 1059 | + } | |
| 1060 | + | |
| 1061 | + &__preview { | |
| 1062 | + display: flex; | |
| 1063 | + flex-direction: row; | |
| 1064 | + align-items: center; | |
| 1065 | + margin-bottom: 15px; | |
| 1066 | + } | |
| 1067 | + | |
| 1068 | + &__preview-color { | |
| 1069 | + width: 40px; | |
| 1070 | + height: 40px; | |
| 1071 | + border-radius: 4px; | |
| 1072 | + border: 1px solid #eee; | |
| 1073 | + margin-right: 10px; | |
| 1074 | + } | |
| 1075 | + | |
| 1076 | + &__preview-text { | |
| 1077 | + font-size: 14px; | |
| 1078 | + color: #333; | |
| 1079 | + flex: 1; | |
| 1080 | + overflow: hidden; | |
| 1081 | + text-overflow: ellipsis; | |
| 1082 | + white-space: nowrap; | |
| 1083 | + } | |
| 1084 | + | |
| 1085 | + &__actions { | |
| 1086 | + display: flex; | |
| 1087 | + flex-direction: row; | |
| 1088 | + justify-content: flex-end; | |
| 1089 | + | |
| 1090 | + & .up-color-picker__btn { | |
| 1091 | + margin-left: 10px; | |
| 1092 | + } | |
| 1093 | + } | |
| 1094 | +} | |
| 1095 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-column-notice/columnNotice.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:57:16 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/columnNotice.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // columnNotice 组件 | |
| 12 | + columnNotice: { | |
| 13 | + text: '', | |
| 14 | + icon: 'volume', | |
| 15 | + mode: '', | |
| 16 | + color: '#f9ae3d', | |
| 17 | + bgColor: '#fdf6ec', | |
| 18 | + fontSize: 14, | |
| 19 | + speed: 80, | |
| 20 | + step: false, | |
| 21 | + duration: 1500, | |
| 22 | + disableTouch: true | |
| 23 | + } | |
| 24 | +} | ... | ... |
uni_modules/uview-plus/components/u-column-notice/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 显示的内容,字符串 | |
| 6 | + text: { | |
| 7 | + type: [Array], | |
| 8 | + default: () => defProps.columnNotice.text | |
| 9 | + }, | |
| 10 | + // 是否显示左侧的音量图标 | |
| 11 | + icon: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.columnNotice.icon | |
| 14 | + }, | |
| 15 | + // 通告模式,link-显示右箭头,closable-显示右侧关闭图标 | |
| 16 | + mode: { | |
| 17 | + type: String, | |
| 18 | + default: () => defProps.columnNotice.mode | |
| 19 | + }, | |
| 20 | + // 文字颜色,各图标也会使用文字颜色 | |
| 21 | + color: { | |
| 22 | + type: String, | |
| 23 | + default: () => defProps.columnNotice.color | |
| 24 | + }, | |
| 25 | + // 背景颜色 | |
| 26 | + bgColor: { | |
| 27 | + type: String, | |
| 28 | + default: () => defProps.columnNotice.bgColor | |
| 29 | + }, | |
| 30 | + // 字体大小,单位px | |
| 31 | + fontSize: { | |
| 32 | + type: [String, Number], | |
| 33 | + default: () => defProps.columnNotice.fontSize | |
| 34 | + }, | |
| 35 | + // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度 | |
| 36 | + speed: { | |
| 37 | + type: [String, Number], | |
| 38 | + default: () => defProps.columnNotice.speed | |
| 39 | + }, | |
| 40 | + // direction = row时,是否使用步进形式滚动 | |
| 41 | + step: { | |
| 42 | + type: Boolean, | |
| 43 | + default: () => defProps.columnNotice.step | |
| 44 | + }, | |
| 45 | + // 滚动一个周期的时间长,单位ms | |
| 46 | + duration: { | |
| 47 | + type: [String, Number], | |
| 48 | + default: () => defProps.columnNotice.duration | |
| 49 | + }, | |
| 50 | + // 是否禁止用手滑动切换 | |
| 51 | + // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 | |
| 52 | + disableTouch: { | |
| 53 | + type: Boolean, | |
| 54 | + default: () => defProps.columnNotice.disableTouch | |
| 55 | + } | |
| 56 | + } | |
| 57 | +}) | ... | ... |
uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view | |
| 3 | + class="u-notice" | |
| 4 | + @tap="clickHandler" | |
| 5 | + > | |
| 6 | + <slot name="icon"> | |
| 7 | + <view | |
| 8 | + class="u-notice__left-icon" | |
| 9 | + v-if="icon" | |
| 10 | + > | |
| 11 | + <u-icon | |
| 12 | + :name="icon" | |
| 13 | + :color="color" | |
| 14 | + size="19" | |
| 15 | + ></u-icon> | |
| 16 | + </view> | |
| 17 | + </slot> | |
| 18 | + <swiper | |
| 19 | + :disable-touch="disableTouch" | |
| 20 | + :vertical="step ? false : true" | |
| 21 | + circular | |
| 22 | + :interval="duration" | |
| 23 | + :autoplay="true" | |
| 24 | + class="u-notice__swiper" | |
| 25 | + @change="noticeChange" | |
| 26 | + > | |
| 27 | + <swiper-item | |
| 28 | + v-for="(item, index) in text" | |
| 29 | + :key="index" | |
| 30 | + class="u-notice__swiper__item" | |
| 31 | + > | |
| 32 | + <text | |
| 33 | + class="u-notice__swiper__item__text u-line-1" | |
| 34 | + :style="[textStyle]" | |
| 35 | + >{{ item }}</text> | |
| 36 | + </swiper-item> | |
| 37 | + </swiper> | |
| 38 | + <view | |
| 39 | + class="u-notice__right-icon" | |
| 40 | + v-if="['link', 'closable'].includes(mode)" | |
| 41 | + > | |
| 42 | + <u-icon | |
| 43 | + v-if="mode === 'link'" | |
| 44 | + name="arrow-right" | |
| 45 | + :size="17" | |
| 46 | + :color="color" | |
| 47 | + ></u-icon> | |
| 48 | + <u-icon | |
| 49 | + v-if="mode === 'closable'" | |
| 50 | + name="close" | |
| 51 | + :size="16" | |
| 52 | + :color="color" | |
| 53 | + @click="close" | |
| 54 | + ></u-icon> | |
| 55 | + </view> | |
| 56 | + </view> | |
| 57 | +</template> | |
| 58 | + | |
| 59 | +<script> | |
| 60 | + import { props } from './props'; | |
| 61 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 62 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 63 | + import { addUnit, error } from '../../libs/function/index'; | |
| 64 | + import test from '../../libs/function/test'; | |
| 65 | + /** | |
| 66 | + * ColumnNotice 滚动通知中的垂直滚动 内部组件 | |
| 67 | + * @description 该组件用于滚动通告场景,是其中的垂直滚动方式 | |
| 68 | + * @tutorial https://ijry.github.io/uview-plus/components/noticeBar.html | |
| 69 | + * @property {Array} text 显示的内容,字符串 | |
| 70 | + * @property {String} icon 是否显示左侧的音量图标 ( 默认 'volume' ) | |
| 71 | + * @property {String} mode 通告模式,link-显示右箭头,closable-显示右侧关闭图标 | |
| 72 | + * @property {String} color 文字颜色,各图标也会使用文字颜色 ( 默认 '#f9ae3d' ) | |
| 73 | + * @property {String} bgColor 背景颜色 ( 默认 '#fdf6ec' ) | |
| 74 | + * @property {String | Number} fontSize 字体大小,单位px ( 默认 14 ) | |
| 75 | + * @property {String | Number} speed 水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度 ( 默认 80 ) | |
| 76 | + * @property {Boolean} step direction = row时,是否使用步进形式滚动 ( 默认 false ) | |
| 77 | + * @property {String | Number} duration 滚动一个周期的时间长,单位ms ( 默认 1500 ) | |
| 78 | + * @property {Boolean} disableTouch 是否禁止用手滑动切换 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 ( 默认 true ) | |
| 79 | + * @example | |
| 80 | + */ | |
| 81 | + export default { | |
| 82 | + mixins: [mpMixin, mixin, props], | |
| 83 | + watch: { | |
| 84 | + text: { | |
| 85 | + immediate: true, | |
| 86 | + handler(newValue, oldValue) { | |
| 87 | + if(!test.array(newValue)) { | |
| 88 | + error('noticebar组件direction为column时,要求text参数为数组形式') | |
| 89 | + } | |
| 90 | + } | |
| 91 | + } | |
| 92 | + }, | |
| 93 | + computed: { | |
| 94 | + // 文字内容的样式 | |
| 95 | + textStyle() { | |
| 96 | + let style = {} | |
| 97 | + style.color = this.color | |
| 98 | + style.fontSize = addUnit(this.fontSize) | |
| 99 | + return style | |
| 100 | + }, | |
| 101 | + // 垂直或者水平滚动 | |
| 102 | + vertical() { | |
| 103 | + if (this.mode == 'horizontal') return false | |
| 104 | + else return true | |
| 105 | + }, | |
| 106 | + }, | |
| 107 | + data() { | |
| 108 | + return { | |
| 109 | + index:0 | |
| 110 | + } | |
| 111 | + }, | |
| 112 | + emits: ["click", "close"], | |
| 113 | + methods: { | |
| 114 | + noticeChange(e){ | |
| 115 | + this.index = e.detail.current | |
| 116 | + }, | |
| 117 | + // 点击通告栏 | |
| 118 | + clickHandler() { | |
| 119 | + this.$emit('click', this.index) | |
| 120 | + }, | |
| 121 | + // 点击关闭按钮 | |
| 122 | + close() { | |
| 123 | + this.$emit('close') | |
| 124 | + } | |
| 125 | + } | |
| 126 | + }; | |
| 127 | +</script> | |
| 128 | + | |
| 129 | +<style lang="scss" scoped> | |
| 130 | + @import "../../libs/css/components.scss"; | |
| 131 | + | |
| 132 | + .u-notice { | |
| 133 | + @include flex; | |
| 134 | + align-items: center; | |
| 135 | + justify-content: space-between; | |
| 136 | + | |
| 137 | + &__left-icon { | |
| 138 | + align-items: center; | |
| 139 | + margin-right: 5px; | |
| 140 | + } | |
| 141 | + | |
| 142 | + &__right-icon { | |
| 143 | + margin-left: 5px; | |
| 144 | + align-items: center; | |
| 145 | + } | |
| 146 | + | |
| 147 | + &__swiper { | |
| 148 | + height: 16px; | |
| 149 | + @include flex; | |
| 150 | + align-items: center; | |
| 151 | + flex: 1; | |
| 152 | + | |
| 153 | + &__item { | |
| 154 | + @include flex; | |
| 155 | + align-items: center; | |
| 156 | + overflow: hidden; | |
| 157 | + | |
| 158 | + &__text { | |
| 159 | + font-size: 14px; | |
| 160 | + color: $u-warning; | |
| 161 | + } | |
| 162 | + } | |
| 163 | + } | |
| 164 | + } | |
| 165 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-copy/u-copy.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view @click="handleClick"> | |
| 3 | + <slot>复制</slot> | |
| 4 | + </view> | |
| 5 | +</template> | |
| 6 | +<script> | |
| 7 | +export default { | |
| 8 | + name: "up-copy", | |
| 9 | + props: { | |
| 10 | + content: { | |
| 11 | + type: String, | |
| 12 | + default: '' | |
| 13 | + }, | |
| 14 | + alertStyle: { | |
| 15 | + type: String, | |
| 16 | + default: 'toast' | |
| 17 | + }, | |
| 18 | + notice: { | |
| 19 | + type: String, | |
| 20 | + default: '复制成功' | |
| 21 | + } | |
| 22 | + }, | |
| 23 | + emits: ['success'], | |
| 24 | + methods: { | |
| 25 | + handleClick() { | |
| 26 | + let content = this.content; | |
| 27 | + if (!content) { | |
| 28 | + uni.showToast({ | |
| 29 | + title: '暂无', | |
| 30 | + icon: 'none', | |
| 31 | + duration: 2000, | |
| 32 | + }); | |
| 33 | + return false; | |
| 34 | + } | |
| 35 | + content = typeof content === 'string' ? content : content.toString() // 复制内容,必须字符串,数字需要转换为字符串 | |
| 36 | + /** | |
| 37 | + * 小程序端 和 app端的复制逻辑 | |
| 38 | + */ | |
| 39 | + let that = this; | |
| 40 | + uni.setClipboardData({ | |
| 41 | + data: content, | |
| 42 | + success: function() { | |
| 43 | + if (that.alertStyle == 'modal') { | |
| 44 | + uni.showModal({ | |
| 45 | + title: '提示', | |
| 46 | + content: that.notice | |
| 47 | + }); | |
| 48 | + } else { | |
| 49 | + uni.showToast({ | |
| 50 | + title: that.notice, | |
| 51 | + icon: 'none' | |
| 52 | + }); | |
| 53 | + } | |
| 54 | + that.$emit('success'); | |
| 55 | + }, | |
| 56 | + fail:function(){ | |
| 57 | + uni.showToast({ | |
| 58 | + title: '复制失败', | |
| 59 | + icon: 'none', | |
| 60 | + duration:3000, | |
| 61 | + }); | |
| 62 | + } | |
| 63 | + }); | |
| 64 | + } | |
| 65 | + } | |
| 66 | +} | |
| 67 | +</script> | |
| 68 | + | |
| 69 | +<style lang="scss" scoped> | |
| 70 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-count-down/countDown.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 17:11:29 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/countDown.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // u-count-down 计时器组件 | |
| 12 | + countDown: { | |
| 13 | + time: 0, | |
| 14 | + format: 'HH:mm:ss', | |
| 15 | + autoStart: true, | |
| 16 | + millisecond: false | |
| 17 | + } | |
| 18 | +} | ... | ... |
uni_modules/uview-plus/components/u-count-down/props.js
0 → 100644
| 1 | +import { defineMixin } from '../../libs/vue' | |
| 2 | +import defProps from '../../libs/config/props.js' | |
| 3 | +export const props = defineMixin({ | |
| 4 | + props: { | |
| 5 | + // 倒计时时长,单位ms | |
| 6 | + time: { | |
| 7 | + type: [String, Number], | |
| 8 | + default: () => defProps.countDown.time | |
| 9 | + }, | |
| 10 | + // 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒 | |
| 11 | + format: { | |
| 12 | + type: String, | |
| 13 | + default: () => defProps.countDown.format | |
| 14 | + }, | |
| 15 | + // 是否自动开始倒计时 | |
| 16 | + autoStart: { | |
| 17 | + type: Boolean, | |
| 18 | + default: () => defProps.countDown.autoStart | |
| 19 | + }, | |
| 20 | + // 是否展示毫秒倒计时 | |
| 21 | + millisecond: { | |
| 22 | + type: Boolean, | |
| 23 | + default: () => defProps.countDown.millisecond | |
| 24 | + } | |
| 25 | + } | |
| 26 | +}) | ... | ... |
uni_modules/uview-plus/components/u-count-down/u-count-down.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="u-count-down"> | |
| 3 | + <slot> | |
| 4 | + <text class="u-count-down__text">{{ formattedTime }}</text> | |
| 5 | + </slot> | |
| 6 | + </view> | |
| 7 | +</template> | |
| 8 | + | |
| 9 | +<script> | |
| 10 | + import { props } from './props'; | |
| 11 | + import { mpMixin } from '../../libs/mixin/mpMixin'; | |
| 12 | + import { mixin } from '../../libs/mixin/mixin'; | |
| 13 | + import { | |
| 14 | + isSameSecond, | |
| 15 | + parseFormat, | |
| 16 | + parseTimeData | |
| 17 | + } from './utils'; | |
| 18 | + /** | |
| 19 | + * u-count-down 倒计时 | |
| 20 | + * @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。 | |
| 21 | + * @tutorial https://uview-plus.jiangruyi.com/components/countDown.html | |
| 22 | + * @property {String | Number} time 倒计时时长,单位ms (默认 0 ) | |
| 23 | + * @property {String} format 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒 (默认 'HH:mm:ss' ) | |
| 24 | + * @property {Boolean} autoStart 是否自动开始倒计时 (默认 true ) | |
| 25 | + * @property {Boolean} millisecond 是否展示毫秒倒计时 (默认 false ) | |
| 26 | + * @event {Function} finish 倒计时结束时触发 | |
| 27 | + * @event {Function} change 倒计时变化时触发 | |
| 28 | + * @event {Function} start 开始倒计时 | |
| 29 | + * @event {Function} pause 暂停倒计时 | |
| 30 | + * @event {Function} reset 重设倒计时,若 auto-start 为 true,重设后会自动开始倒计时 | |
| 31 | + * @example <u-count-down :time="time"></u-count-down> | |
| 32 | + */ | |
| 33 | + export default { | |
| 34 | + name: 'u-count-down', | |
| 35 | + mixins: [mpMixin, mixin, props], | |
| 36 | + data() { | |
| 37 | + return { | |
| 38 | + timer: null, | |
| 39 | + // 各单位(天,时,分等)剩余时间 | |
| 40 | + timeData: parseTimeData(0), | |
| 41 | + // 格式化后的时间,如"03:23:21" | |
| 42 | + formattedTime: '0', | |
| 43 | + // 倒计时是否正在进行中 | |
| 44 | + runing: false, | |
| 45 | + endTime: 0, // 结束的毫秒时间戳 | |
| 46 | + remainTime: 0, // 剩余的毫秒时间 | |
| 47 | + } | |
| 48 | + }, | |
| 49 | + watch: { | |
| 50 | + time(n) { | |
| 51 | + this.reset() | |
| 52 | + } | |
| 53 | + }, | |
| 54 | + mounted() { | |
| 55 | + this.init() | |
| 56 | + }, | |
| 57 | + emits: ["change", "finish"], | |
| 58 | + methods: { | |
| 59 | + init() { | |
| 60 | + this.reset() | |
| 61 | + }, | |
| 62 | + // 开始倒计时 | |
| 63 | + start() { | |
| 64 | + if (this.runing) return | |
| 65 | + // 标识为进行中 | |
| 66 | + this.runing = true | |
| 67 | + // 结束时间戳 = 此刻时间戳 + 剩余的时间 | |
| 68 | + this.endTime = Date.now() + this.remainTime | |
| 69 | + this.toTick() | |
| 70 | + }, | |
| 71 | + // 根据是否展示毫秒,执行不同操作函数 | |
| 72 | + toTick() { | |
| 73 | + if (this.millisecond) { | |
| 74 | + this.microTick() | |
| 75 | + } else { | |
| 76 | + this.macroTick() | |
| 77 | + } | |
| 78 | + }, | |
| 79 | + macroTick() { | |
| 80 | + this.clearTimeout() | |
| 81 | + // 每隔一定时间,更新一遍定时器的值 | |
| 82 | + // 同时此定时器的作用也能带来毫秒级的更新 | |
| 83 | + this.timer = setTimeout(() => { | |
| 84 | + // 获取剩余时间 | |
| 85 | + const remain = this.getRemainTime() | |
| 86 | + // 重设剩余时间 | |
| 87 | + if (!isSameSecond(remain, this.remainTime) || remain === 0) { | |
| 88 | + this.setRemainTime(remain) | |
| 89 | + } | |
| 90 | + // 如果剩余时间不为0,则继续检查更新倒计时 | |
| 91 | + if (this.remainTime !== 0) { | |
| 92 | + this.macroTick() | |
| 93 | + } | |
| 94 | + }, 30) | |
| 95 | + }, | |
| 96 | + microTick() { | |
| 97 | + this.clearTimeout() | |
| 98 | + this.timer = setTimeout(() => { | |
| 99 | + this.setRemainTime(this.getRemainTime()) | |
| 100 | + if (this.remainTime !== 0) { | |
| 101 | + this.microTick() | |
| 102 | + } | |
| 103 | + }, 50) | |
| 104 | + }, | |
| 105 | + // 获取剩余的时间 | |
| 106 | + getRemainTime() { | |
| 107 | + // 取最大值,防止出现小于0的剩余时间值 | |
| 108 | + return Math.max(this.endTime - Date.now(), 0) | |
| 109 | + }, | |
| 110 | + // 设置剩余的时间 | |
| 111 | + setRemainTime(remain) { | |
| 112 | + this.remainTime = remain | |
| 113 | + // 根据剩余的毫秒时间,得出该有天,小时,分钟等的值,返回一个对象 | |
| 114 | + const timeData = parseTimeData(remain) | |
| 115 | + this.$emit('change', timeData) | |
| 116 | + // 得出格式化后的时间 | |
| 117 | + this.formattedTime = parseFormat(this.format, timeData) | |
| 118 | + // 如果时间已到,停止倒计时 | |
| 119 | + if (remain <= 0) { | |
| 120 | + this.pause() | |
| 121 | + this.$emit('finish') | |
| 122 | + } | |
| 123 | + }, | |
| 124 | + // 重置倒计时 | |
| 125 | + reset() { | |
| 126 | + this.pause() | |
| 127 | + this.remainTime = this.time | |
| 128 | + this.setRemainTime(this.remainTime) | |
| 129 | + if (this.autoStart) { | |
| 130 | + this.start() | |
| 131 | + } | |
| 132 | + }, | |
| 133 | + // 暂停倒计时 | |
| 134 | + pause() { | |
| 135 | + this.runing = false; | |
| 136 | + this.clearTimeout() | |
| 137 | + }, | |
| 138 | + // 清空定时器 | |
| 139 | + clearTimeout() { | |
| 140 | + clearTimeout(this.timer) | |
| 141 | + this.timer = null | |
| 142 | + } | |
| 143 | + }, | |
| 144 | + beforeUnmount() { | |
| 145 | + this.clearTimeout() | |
| 146 | + } | |
| 147 | + } | |
| 148 | +</script> | |
| 149 | + | |
| 150 | +<style | |
| 151 | + lang="scss" | |
| 152 | + scoped | |
| 153 | +> | |
| 154 | + @import "../../libs/css/components.scss"; | |
| 155 | + $u-count-down-text-color:$u-content-color !default; | |
| 156 | + $u-count-down-text-font-size:15px !default; | |
| 157 | + $u-count-down-text-line-height:22px !default; | |
| 158 | + | |
| 159 | + .u-count-down { | |
| 160 | + &__text { | |
| 161 | + color: $u-count-down-text-color; | |
| 162 | + font-size: $u-count-down-text-font-size; | |
| 163 | + line-height: $u-count-down-text-line-height; | |
| 164 | + } | |
| 165 | + } | |
| 166 | +</style> | ... | ... |
uni_modules/uview-plus/components/u-count-down/utils.js
0 → 100644
| 1 | +// 补0,如1 -> 01 | |
| 2 | +function padZero(num, targetLength = 2) { | |
| 3 | + let str = `${num}` | |
| 4 | + while (str.length < targetLength) { | |
| 5 | + str = `0${str}` | |
| 6 | + } | |
| 7 | + return str | |
| 8 | +} | |
| 9 | +const SECOND = 1000 | |
| 10 | +const MINUTE = 60 * SECOND | |
| 11 | +const HOUR = 60 * MINUTE | |
| 12 | +const DAY = 24 * HOUR | |
| 13 | +export function parseTimeData(time) { | |
| 14 | + const days = Math.floor(time / DAY) | |
| 15 | + const hours = Math.floor((time % DAY) / HOUR) | |
| 16 | + const minutes = Math.floor((time % HOUR) / MINUTE) | |
| 17 | + const seconds = Math.floor((time % MINUTE) / SECOND) | |
| 18 | + const milliseconds = Math.floor(time % SECOND) | |
| 19 | + return { | |
| 20 | + days, | |
| 21 | + hours, | |
| 22 | + minutes, | |
| 23 | + seconds, | |
| 24 | + milliseconds | |
| 25 | + } | |
| 26 | +} | |
| 27 | +export function parseFormat(format, timeData) { | |
| 28 | + let { | |
| 29 | + days, | |
| 30 | + hours, | |
| 31 | + minutes, | |
| 32 | + seconds, | |
| 33 | + milliseconds | |
| 34 | + } = timeData | |
| 35 | + // 如果格式化字符串中不存在DD(天),则将天的时间转为小时中去 | |
| 36 | + if (format.indexOf('DD') === -1) { | |
| 37 | + hours += days * 24 | |
| 38 | + } else { | |
| 39 | + // 对天补0 | |
| 40 | + format = format.replace('DD', padZero(days)) | |
| 41 | + } | |
| 42 | + // 其他同理于DD的格式化处理方式 | |
| 43 | + if (format.indexOf('HH') === -1) { | |
| 44 | + minutes += hours * 60 | |
| 45 | + } else { | |
| 46 | + format = format.replace('HH', padZero(hours)) | |
| 47 | + } | |
| 48 | + if (format.indexOf('mm') === -1) { | |
| 49 | + seconds += minutes * 60 | |
| 50 | + } else { | |
| 51 | + format = format.replace('mm', padZero(minutes)) | |
| 52 | + } | |
| 53 | + if (format.indexOf('ss') === -1) { | |
| 54 | + milliseconds += seconds * 1000 | |
| 55 | + } else { | |
| 56 | + format = format.replace('ss', padZero(seconds)) | |
| 57 | + } | |
| 58 | + return format.replace('SSS', padZero(milliseconds, 3)) | |
| 59 | +} | |
| 60 | +export function isSameSecond(time1, time2) { | |
| 61 | + return Math.floor(time1 / 1000) === Math.floor(time2 / 1000) | |
| 62 | +} | ... | ... |
uni_modules/uview-plus/components/u-count-to/countTo.js
0 → 100644
| 1 | +/* | |
| 2 | + * @Author : LQ | |
| 3 | + * @Description : | |
| 4 | + * @version : 1.0 | |
| 5 | + * @Date : 2021-08-20 16:44:21 | |
| 6 | + * @LastAuthor : LQ | |
| 7 | + * @lastTime : 2021-08-20 16:57:32 | |
| 8 | + * @FilePath : /u-view2.0/uview-ui/libs/config/props/countTo.js | |
| 9 | + */ | |
| 10 | +export default { | |
| 11 | + // countTo 组件 | |
| 12 | + countTo: { | |
| 13 | + startVal: 0, | |
| 14 | + endVal: 0, | |
| 15 | + duration: 2000, | |
| 16 | + autoplay: true, | |
| 17 | + decimals: 0, | |
| 18 | + useEasing: true, | |
| 19 | + decimal: '.', | |
| 20 | + color: '#606266', | |
| 21 | + fontSize: 22, | |
| 22 | + bold: false, | |
| 23 | + separator: '' | |
| 24 | + } | |
| 25 | +} | ... | ... |