picker.uvue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <template>
  2. <view
  3. class="cl-calendar-picker"
  4. :class="{
  5. 'is-dark': isDark
  6. }"
  7. v-if="visible"
  8. >
  9. <view class="cl-calendar-picker__header">
  10. <view
  11. class="cl-calendar-picker__prev"
  12. :class="{
  13. 'is-dark': isDark
  14. }"
  15. @tap="prev"
  16. >
  17. <cl-icon name="arrow-left-double-line"></cl-icon>
  18. </view>
  19. <view class="cl-calendar-picker__date" @tap="toMode('year')">
  20. <cl-text
  21. :pt="{
  22. className: 'text-lg'
  23. }"
  24. >
  25. {{ title }}
  26. </cl-text>
  27. </view>
  28. <view
  29. class="cl-calendar-picker__next"
  30. :class="{
  31. 'is-dark': isDark
  32. }"
  33. @tap="next"
  34. >
  35. <cl-icon name="arrow-right-double-line"></cl-icon>
  36. </view>
  37. </view>
  38. <view class="cl-calendar-picker__list">
  39. <view
  40. class="cl-calendar-picker__item"
  41. v-for="item in list"
  42. :key="item.value"
  43. @tap="select(item.value)"
  44. >
  45. <cl-text
  46. :pt="{
  47. className: parseClass([[item.value == value, 'text-primary-500']])
  48. }"
  49. >{{ item.label }}</cl-text
  50. >
  51. </view>
  52. </view>
  53. </view>
  54. </template>
  55. <script lang="ts" setup>
  56. import { first, isDark, last, parseClass } from "@/cool";
  57. import { t } from "@/locale";
  58. import { computed, ref } from "vue";
  59. defineOptions({
  60. name: "cl-calendar-picker"
  61. });
  62. // 定义日历选择器的条目类型
  63. type Item = {
  64. label: string; // 显示的标签,如"1月"、"2024"
  65. value: number; // 对应的数值,如1、2024
  66. };
  67. // 定义组件接收的属性:年份和月份,均为数字类型,默认值为0
  68. const props = defineProps({
  69. year: {
  70. type: Number,
  71. default: 0
  72. },
  73. month: {
  74. type: Number,
  75. default: 0
  76. }
  77. });
  78. // 定义组件可触发的事件,这里只定义了"change"事件
  79. const emit = defineEmits(["change"]);
  80. // 当前选择的模式,"year"表示选择年份,"month"表示选择月份,默认是"month"
  81. const mode = ref<"year" | "month">("month");
  82. // 当前选中的年份
  83. const year = ref(0);
  84. // 当前选中的月份
  85. const month = ref(0);
  86. // 当前年份选择面板的起始年份(如2020-2029,则startYear为2020)
  87. const startYear = ref(0);
  88. // 当前选中的值,若为月份模式则为月份,否则为年份
  89. const value = computed(() => {
  90. return mode.value == "month" ? month.value : year.value;
  91. });
  92. // 计算可供选择的列表:
  93. // - 若为月份模式,返回1-12月
  94. // - 若为年份模式,返回以startYear为起点的连续10年
  95. const list = computed(() => {
  96. if (mode.value == "month") {
  97. return [
  98. { label: t("1月"), value: 1 },
  99. { label: t("2月"), value: 2 },
  100. { label: t("3月"), value: 3 },
  101. { label: t("4月"), value: 4 },
  102. { label: t("5月"), value: 5 },
  103. { label: t("6月"), value: 6 },
  104. { label: t("7月"), value: 7 },
  105. { label: t("8月"), value: 8 },
  106. { label: t("9月"), value: 9 },
  107. { label: t("10月"), value: 10 },
  108. { label: t("11月"), value: 11 },
  109. { label: t("12月"), value: 12 }
  110. ] as Item[];
  111. } else {
  112. const years: Item[] = [];
  113. // 生成10个连续年份
  114. for (let i = 0; i < 10; i++) {
  115. years.push({
  116. label: `${startYear.value + i}`,
  117. value: startYear.value + i
  118. });
  119. }
  120. return years;
  121. }
  122. });
  123. // 计算标题内容:
  124. // - 月份模式下显示“xxxx年”
  125. // - 年份模式下显示“起始年 - 结束年”
  126. const title = computed(() => {
  127. return mode.value == "month"
  128. ? `${year.value}`
  129. : `${first(list.value)?.label} - ${last(list.value)?.label}`;
  130. });
  131. // 控制选择器弹窗的显示与隐藏
  132. const visible = ref(false);
  133. /**
  134. * 打开选择器,并初始化年份、月份、起始年份
  135. */
  136. function open() {
  137. visible.value = true;
  138. // 初始化当前年份和月份为传入的props
  139. year.value = props.year;
  140. month.value = props.month;
  141. // 计算当前年份所在的十年区间的起始年份
  142. startYear.value = Math.floor(year.value / 10) * 10;
  143. }
  144. /**
  145. * 关闭选择器
  146. */
  147. function close() {
  148. visible.value = false;
  149. }
  150. /**
  151. * 切换选择模式(年份/月份)
  152. * @param val "year" 或 "month"
  153. */
  154. function toMode(val: "year" | "month") {
  155. mode.value = val;
  156. }
  157. /**
  158. * 选择某个值(年份或月份)
  159. * @param val 选中的值
  160. */
  161. function select(val: number) {
  162. if (mode.value == "month") {
  163. // 选择月份后,关闭弹窗并触发change事件
  164. month.value = val;
  165. close();
  166. emit("change", [year.value, month.value]);
  167. } else {
  168. // 选择年份后,切换到月份选择模式
  169. year.value = val;
  170. toMode("month");
  171. }
  172. }
  173. /**
  174. * 切换到上一个区间
  175. * - 月份模式下,年份减1
  176. * - 年份模式下,起始年份减10
  177. */
  178. function prev() {
  179. if (mode.value == "month") {
  180. year.value -= 1;
  181. } else {
  182. startYear.value -= 10;
  183. }
  184. }
  185. /**
  186. * 切换到下一个区间
  187. * - 月份模式下,年份加1
  188. * - 年份模式下,起始年份加10
  189. */
  190. function next() {
  191. if (mode.value == "month") {
  192. year.value += 1;
  193. } else {
  194. startYear.value += 10;
  195. }
  196. }
  197. defineExpose({
  198. open,
  199. close
  200. });
  201. </script>
  202. <style lang="scss" scoped>
  203. .cl-calendar-picker {
  204. @apply flex flex-col absolute left-0 top-0 w-full h-full bg-white z-10;
  205. &__header {
  206. @apply flex flex-row items-center justify-between w-full p-3;
  207. }
  208. &__prev,
  209. &__next {
  210. @apply flex flex-row items-center justify-center rounded-full bg-surface-100;
  211. width: 60rpx;
  212. height: 60rpx;
  213. &.is-dark {
  214. @apply bg-surface-700;
  215. }
  216. }
  217. &__date {
  218. @apply flex flex-row items-center justify-center;
  219. }
  220. &__list {
  221. @apply flex flex-row flex-wrap;
  222. }
  223. &__item {
  224. @apply flex flex-row items-center justify-center;
  225. height: 100rpx;
  226. width: 25%;
  227. &-bg {
  228. @apply px-4 py-2;
  229. &.is-active {
  230. @apply bg-primary-500 rounded-xl;
  231. }
  232. }
  233. }
  234. &.is-dark {
  235. @apply bg-surface-800 rounded-2xl;
  236. }
  237. }
  238. </style>