index.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <template>
  2. <!--
  3. ss-datetime-picker 智能日期时间选择组件
  4. 基于 uview-plus 的 datetime-picker
  5. 自动绑定ValidatedTd的事件处理函数
  6. -->
  7. <view class="smart-datetime-picker">
  8. <up-datetime-picker
  9. ref="datetimePicker"
  10. :show="show"
  11. v-model="innerValue"
  12. :mode="mode"
  13. :min-date="minDate"
  14. :max-date="maxDate"
  15. :formatter="formatter"
  16. :filter="filter"
  17. :default-index="defaultIndex"
  18. :item-height="itemHeight"
  19. :cancel-text="cancelText"
  20. :confirm-text="confirmText"
  21. :cancel-color="cancelColor"
  22. :confirm-color="confirmColor"
  23. :visible-item-count="visibleItemCount"
  24. :close-on-click-overlay="closeOnClickOverlay"
  25. :safe-area-inset-bottom="safeAreaInsetBottom"
  26. @confirm="handleConfirm"
  27. @cancel="handleCancel"
  28. @close="handleClose"
  29. />
  30. <!-- 显示区域 -->
  31. <view
  32. class="smart-datetime-picker__display"
  33. @click="openPicker"
  34. >
  35. <text
  36. v-if="displayValue"
  37. class="smart-datetime-picker__value"
  38. >
  39. {{ displayValue }}
  40. </text>
  41. <text
  42. v-else
  43. class="smart-datetime-picker__placeholder"
  44. >
  45. {{ placeholder }}
  46. </text>
  47. <Icon name="icon-xiangxiajiantou" size="32" color="#999"/>
  48. </view>
  49. </view>
  50. </template>
  51. <script setup>
  52. import { ref, computed, inject, watch } from 'vue'
  53. import Icon from '@/components/icon/index.vue';
  54. import dayjs from 'dayjs'
  55. const props = defineProps({
  56. modelValue: {
  57. type: [String, Number, Date],
  58. default: ''
  59. },
  60. // 选择器模式:date-日期,time-时间,datetime-日期时间
  61. mode: {
  62. type: String,
  63. default: 'date',
  64. validator: (value) => ['date', 'time', 'datetime'].includes(value)
  65. },
  66. // 最小日期
  67. minDate: {
  68. type: [String, Number, Date],
  69. default: () => dayjs().subtract(10, 'year').valueOf()
  70. },
  71. // 最大日期
  72. maxDate: {
  73. type: [String, Number, Date],
  74. default: () => dayjs().add(10, 'year').valueOf()
  75. },
  76. // 占位符
  77. placeholder: {
  78. type: String,
  79. default: '请选择'
  80. },
  81. // 格式化函数
  82. formatter: {
  83. type: Function,
  84. default: null
  85. },
  86. // 过滤函数
  87. filter: {
  88. type: Function,
  89. default: null
  90. },
  91. // 默认选中的索引
  92. defaultIndex: {
  93. type: Array,
  94. default: () => []
  95. },
  96. // 选项高度
  97. itemHeight: {
  98. type: [String, Number],
  99. default: 44
  100. },
  101. // 取消按钮文字
  102. cancelText: {
  103. type: String,
  104. default: '取消'
  105. },
  106. // 确认按钮文字
  107. confirmText: {
  108. type: String,
  109. default: '确认'
  110. },
  111. // 取消按钮颜色
  112. cancelColor: {
  113. type: String,
  114. default: '#909193'
  115. },
  116. // 确认按钮颜色
  117. confirmColor: {
  118. type: String,
  119. default: '#3c9cff'
  120. },
  121. // 可见选项数量
  122. visibleItemCount: {
  123. type: [String, Number],
  124. default: 5
  125. },
  126. // 点击遮罩是否关闭
  127. closeOnClickOverlay: {
  128. type: Boolean,
  129. default: true
  130. },
  131. // 是否开启底部安全区适配
  132. safeAreaInsetBottom: {
  133. type: Boolean,
  134. default: false
  135. },
  136. // 显示格式
  137. displayFormat: {
  138. type: String,
  139. default: ''
  140. }
  141. })
  142. const emit = defineEmits(['update:modelValue', 'confirm', 'cancel', 'change'])
  143. // 从ValidatedTd注入事件处理函数(兼容旧方式)
  144. const onInput = inject('onInput', null)
  145. const onBlur = inject('onBlur', null)
  146. // 控制选择器显示
  147. const show = ref(false)
  148. // 内部值
  149. const innerValue = ref(Date.now())
  150. // 日期选择器引用
  151. const datetimePicker = ref(null)
  152. // 初始化内部值
  153. const initInnerValue = () => {
  154. if (props.mode === 'time') {
  155. // 时间模式下,直接使用时间字符串
  156. if (props.modelValue) {
  157. innerValue.value = props.modelValue
  158. } else {
  159. // 如果没有值,使用当前时间
  160. const now = new Date()
  161. const hours = now.getHours().toString().padStart(2, '0')
  162. const minutes = now.getMinutes().toString().padStart(2, '0')
  163. innerValue.value = `${hours}:${minutes}`
  164. }
  165. } else {
  166. // 日期和日期时间模式
  167. if (props.modelValue) {
  168. if (typeof props.modelValue === 'string') {
  169. innerValue.value = dayjs(props.modelValue).valueOf()
  170. } else if (typeof props.modelValue === 'number') {
  171. innerValue.value = props.modelValue
  172. } else if (props.modelValue instanceof Date) {
  173. innerValue.value = props.modelValue.getTime()
  174. }
  175. } else {
  176. innerValue.value = dayjs().valueOf()
  177. }
  178. }
  179. }
  180. // 监听 modelValue 变化
  181. watch(() => props.modelValue, () => {
  182. initInnerValue()
  183. }, { immediate: true })
  184. // 计算显示值
  185. const displayValue = computed(() => {
  186. if (!props.modelValue) return ''
  187. if (props.mode === 'time') {
  188. // 时间模式下直接显示值
  189. return props.modelValue
  190. }
  191. let format = props.displayFormat
  192. if (!format) {
  193. switch (props.mode) {
  194. case 'date':
  195. format = 'YYYY-MM-DD'
  196. break
  197. case 'datetime':
  198. format = 'YYYY-MM-DD HH:mm'
  199. break
  200. default:
  201. format = 'YYYY-MM-DD'
  202. }
  203. }
  204. const date = dayjs(props.modelValue)
  205. return date.isValid() ? date.format(format) : props.modelValue
  206. })
  207. // 打开选择器
  208. const openPicker = () => {
  209. show.value = true
  210. console.log('打开日期选择器', show.value)
  211. }
  212. // 确认选择
  213. const handleConfirm = (value) => {
  214. console.log('原始值:', value)
  215. let formattedValue = ''
  216. try {
  217. if (props.mode === 'time') {
  218. // 时间模式下,value.value 已经是 "HH:mm" 格式
  219. formattedValue = value.value
  220. console.log('处理后的时间值:', formattedValue)
  221. } else {
  222. // 日期和日期时间模式
  223. const date = dayjs(value.value)
  224. switch (props.mode) {
  225. case 'date':
  226. formattedValue = date.format('YYYY-MM-DD')
  227. break
  228. case 'datetime':
  229. formattedValue = date.format('YYYY-MM-DD HH:mm')
  230. break
  231. default:
  232. formattedValue = date.format('YYYY-MM-DD')
  233. }
  234. }
  235. // 1. 支持v-model
  236. emit('update:modelValue', formattedValue)
  237. // 2. 兼容旧的inject方式
  238. if (onInput) {
  239. onInput({ detail: { value: formattedValue } })
  240. }
  241. // 3. 触发确认事件
  242. emit('confirm', { value: formattedValue, timestamp: value.value })
  243. emit('change', formattedValue)
  244. } catch (error) {
  245. console.error('时间处理错误:', error)
  246. formattedValue = ''
  247. }
  248. show.value = false
  249. console.log(`SsDatetimePicker handleConfirm: ${formattedValue}`)
  250. }
  251. // 取消选择
  252. const handleCancel = () => {
  253. show.value = false
  254. emit('cancel')
  255. }
  256. // 关闭选择器
  257. const handleClose = () => {
  258. show.value = false
  259. // 触发失焦事件
  260. if (onBlur) {
  261. onBlur()
  262. }
  263. }
  264. // 初始化
  265. initInnerValue()
  266. </script>
  267. <style lang="scss" scoped>
  268. .smart-datetime-picker {
  269. width: 100%;
  270. &__display {
  271. width: 100%;
  272. height: 60rpx;
  273. display: flex;
  274. align-items: center;
  275. justify-content: space-between;
  276. font-size: 32rpx;
  277. line-height: 60rpx;
  278. color: #333;
  279. background-color: transparent;
  280. border: none;
  281. outline: none;
  282. box-sizing: border-box;
  283. cursor: pointer;
  284. }
  285. &__value {
  286. flex: 1;
  287. color: #333;
  288. font-size: 32rpx;
  289. }
  290. &__placeholder {
  291. flex: 1;
  292. color: #999;
  293. font-size: 32rpx;
  294. }
  295. &__icon {
  296. margin-left: 16rpx;
  297. transition: transform 0.3s ease;
  298. }
  299. &__display:active &__icon {
  300. transform: rotate(180deg);
  301. }
  302. }
  303. </style>