Commit 252962bb389ab84380d60d93a4cc8a6302d3fa27

Authored by 刘淇
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 +[![stars](https://img.shields.io/github/stars/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus)
  8 +[![forks](https://img.shields.io/github/forks/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus)
  9 +[![issues](https://img.shields.io/github/issues/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus/issues)
  10 +[![release](https://img.shields.io/github/v/release/ijry/uview-plus?style=flat-square)](https://gitee.com/jry/uview-plus/releases)
  11 +[![license](https://img.shields.io/github/license/ijry/uview-plus?style=flat-square)](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
  1 +import { defineMixin } from '../../libs/vue'
  2 +import defProps from '../../libs/config/props.js'
  3 +export const props = defineMixin({
  4 + props: {
  5 + percentage: {
  6 + type: [String, Number],
  7 + default: () => defProps.circleProgress.percentage
  8 + }
  9 + }
  10 +})
... ...
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 +}
... ...