cl-calendar.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <template>
  2. <cl-popup border-radius="20rpx 20rpx 0 0" :visible.sync="visible" direction="bottom">
  3. <view class="cl-calendar">
  4. <view class="cl-calendar__header">
  5. <text class="cl-calendar__title">{{ title }}</text>
  6. <text class="cl-calendar__close cl-icon-close" @tap="cancel"></text>
  7. </view>
  8. <view class="cl-calendar__container">
  9. <view class="cl-calendar__selector">
  10. <!-- 左箭头 -->
  11. <view class="cl-calendar__selector-icon is-prev" @tap="prevMonth">
  12. <cl-icon :size="36" name="arrow-left"></cl-icon>
  13. </view>
  14. <!-- 当前日期 -->
  15. <text class="cl-calendar__selector-date">{{ date | format_date }}</text>
  16. <!-- 右箭头 -->
  17. <view class="cl-calendar__selector-icon is-next" @tap="nextMonth">
  18. <cl-icon :size="36" name="arrow-right"></cl-icon>
  19. </view>
  20. <!-- 回到今天 -->
  21. <button class="cl-calendar__today-btn" @tap="toToday">回到今天</button>
  22. </view>
  23. <!-- 数据面板 -->
  24. <view class="cl-calendar__table">
  25. <view class="cl-calendar__tr">
  26. <view class="cl-calendar__td">一</view>
  27. <view class="cl-calendar__td">二</view>
  28. <view class="cl-calendar__td">三</view>
  29. <view class="cl-calendar__td">四</view>
  30. <view class="cl-calendar__td">五</view>
  31. <view class="cl-calendar__td">六</view>
  32. <view class="cl-calendar__td">日</view>
  33. </view>
  34. <!-- 行 -->
  35. <view class="cl-calendar__tr" v-for="(tr, index) in table" :key="index">
  36. <!-- 列 -->
  37. <view
  38. class="cl-calendar__td"
  39. v-for="td in tr"
  40. :key="td.index"
  41. :class="{
  42. 'is-disabled': td.disabled,
  43. 'is-start': isStart(td),
  44. 'is-end': isEnd(td),
  45. 'is-through': isThrough(td),
  46. }"
  47. @tap="selectDate(td)"
  48. >
  49. <!-- 日 -->
  50. <view class="cl-calendar__value">
  51. {{ td.value }}
  52. </view>
  53. <!-- 小点 -->
  54. <text
  55. class="cl-calendar__dot"
  56. :style="{
  57. backgroundColor: td.color,
  58. }"
  59. ></text>
  60. <!-- 备注 -->
  61. <text class="cl-calendar__remark" v-if="td.remark || td.lunar">
  62. {{ td.remark || td.lunar }}
  63. </text>
  64. </view>
  65. </view>
  66. </view>
  67. </view>
  68. <view class="cl-calendar__footer">
  69. <cl-row :gutter="20">
  70. <cl-col :span="12">
  71. <cl-button fill round @tap="cancel">取消</cl-button>
  72. </cl-col>
  73. <cl-col :span="12">
  74. <cl-button fill round type="primary" :disabled="!isConfirm" @tap="confirm"
  75. >确定</cl-button
  76. >
  77. </cl-col>
  78. </cl-row>
  79. </view>
  80. </view>
  81. </cl-popup>
  82. </template>
  83. <script>
  84. import { isArray, isString } from "../../utils";
  85. import dayjs from "../../utils/dayjs";
  86. import Calendar from "./calendar";
  87. /**
  88. * calendar 日历
  89. * @description 支持单选,多选,自定义日期
  90. * @tutorial https://docs.cool-js.com/uni/components/advanced/calendar.html
  91. * @property {String, Array} value 绑定值
  92. * @property {String} type 类型 date | daterange,默认date
  93. * @property {String} title 标题
  94. * @property {Array} customList 自定义列表
  95. * @property {Boolean} lunar 是否显示农历
  96. * @event {Function} change 值改变时触发
  97. * @event {Function} cancel 取消时时触发
  98. * @example <cl-calendar ref="calendar" v-model="date" />
  99. */
  100. export default {
  101. name: "cl-calendar",
  102. props: {
  103. // 绑定值
  104. value: [String, Array],
  105. // 类型
  106. type: {
  107. type: String,
  108. default: "date", // date | daterange
  109. },
  110. // 标题
  111. title: {
  112. type: String,
  113. default: "选择日期",
  114. },
  115. // 自定义列表
  116. customList: {
  117. type: Array,
  118. default: () => [],
  119. },
  120. // 是否显示农历
  121. lunar: {
  122. type: Boolean,
  123. default: false,
  124. },
  125. },
  126. data() {
  127. return {
  128. // 是否可见
  129. visible: false,
  130. // 表格数据
  131. table: [],
  132. // 当前日期
  133. date: null,
  134. // 选择数据
  135. selects: {
  136. // 多选下开始日期
  137. start: null,
  138. // 多选下结束日期
  139. end: null,
  140. },
  141. };
  142. },
  143. filters: {
  144. format_date(val, fmt) {
  145. return val ? dayjs(val).format(fmt || "YYYY年MM月") : "";
  146. },
  147. },
  148. computed: {
  149. isConfirm() {
  150. switch (this.type) {
  151. case "date":
  152. return this.selects.start;
  153. case "daterange":
  154. return this.selects.start && this.selects.end;
  155. default:
  156. return false;
  157. }
  158. },
  159. },
  160. methods: {
  161. // 打开
  162. open() {
  163. this.visible = true;
  164. this.$nextTick(() => {
  165. // 绑定值
  166. let date = this.value;
  167. // 设置年月
  168. let start = null;
  169. let end = null;
  170. if (isArray(date)) {
  171. start = date[0];
  172. end = date[1];
  173. } else if (isString(date)) {
  174. start = date;
  175. }
  176. if (start) {
  177. this.date = dayjs(start).format("YYYY-MM-DD");
  178. } else {
  179. this.date = dayjs().format("YYYY-MM-DD");
  180. }
  181. // 设置表格
  182. this.setTable();
  183. // 找到对应的日期
  184. this.selects.start = this.findDate(start);
  185. if (isArray(date)) {
  186. this.selects.end = this.findDate(end);
  187. }
  188. });
  189. },
  190. // 关闭
  191. close() {
  192. this.visible = false;
  193. },
  194. // 取消
  195. cancel() {
  196. this.close();
  197. this.$emit("cancel");
  198. },
  199. // 确定选择
  200. confirm() {
  201. let { start, end } = this.selects;
  202. if (this.type == "date") {
  203. this.$emit("input", start.date);
  204. this.$emit("change", start);
  205. } else if (this.type == "daterange") {
  206. this.$emit("input", [start.date, end.date]);
  207. this.$emit("change", [start, end]);
  208. }
  209. this.close();
  210. },
  211. // 回到今天
  212. toToday() {
  213. this.setDate();
  214. },
  215. // 上一个月
  216. prevMonth() {
  217. this.date = dayjs(this.date).subtract(1, "month").format("YYYY-MM");
  218. this.setTable();
  219. },
  220. // 下一个月
  221. nextMonth() {
  222. this.date = dayjs(this.date).add(1, "month").format("YYYY-MM");
  223. this.setTable();
  224. },
  225. // 选择日期
  226. selectDate(item) {
  227. if (item.disabled) {
  228. return false;
  229. }
  230. if (this.selects.start && this.selects.start.index == item.index) {
  231. return false;
  232. }
  233. // 日期多选
  234. if (this.type == "daterange") {
  235. if (this.selects.start && this.selects.end) {
  236. this.selects.start = item;
  237. this.selects.end = null;
  238. } else {
  239. if (!this.selects.start) {
  240. this.selects.start = item;
  241. } else if (!this.selects.end) {
  242. this.selects.end = item;
  243. }
  244. }
  245. // 调整开始和结束的位置
  246. if (this.selects.start && this.selects.end) {
  247. if (dayjs(this.selects.end.date).isBefore(this.selects.start.date)) {
  248. let d = this.selects.start;
  249. this.selects.start = this.selects.end;
  250. this.selects.end = d;
  251. }
  252. }
  253. }
  254. // 日期单选
  255. else if (this.type == "date") {
  256. this.selects.start = item;
  257. }
  258. },
  259. // 设置表格
  260. setTable() {
  261. // 月初是周几
  262. let day = dayjs(this.date).date(1).day();
  263. let start = day == 0 ? 6 : day - 1;
  264. // 本月天数
  265. let days = dayjs(this.date).endOf("month").format("D");
  266. // 上个月天数
  267. let prevDays = dayjs(this.date).endOf("month").subtract(1, "month").format("D");
  268. // 日期数据
  269. let arr = [];
  270. // 清空表格
  271. this.table = [];
  272. // 添加上月数据
  273. arr.push(
  274. ...new Array(start).fill(1).map((e, i) => {
  275. let day = prevDays - start + i + 1;
  276. return {
  277. value: day,
  278. disabled: true,
  279. date: dayjs(this.date).subtract(1, "month").date(day).format("YYYY-MM-DD"),
  280. };
  281. })
  282. );
  283. // 添加本月数据
  284. arr.push(
  285. ...new Array(days - 0).fill(1).map((e, i) => {
  286. let day = i + 1;
  287. return {
  288. value: day,
  289. date: dayjs(this.date).date(day).format("YYYY-MM-DD"),
  290. };
  291. })
  292. );
  293. // 添加下个月
  294. arr.push(
  295. ...new Array(42 - days - start).fill(1).map((e, i) => {
  296. let day = i + 1;
  297. return {
  298. value: day,
  299. disabled: true,
  300. date: dayjs(this.date).add(1, "month").date(day).format("YYYY-MM-DD"),
  301. };
  302. })
  303. );
  304. // 分割数组
  305. for (let n = 0; n < arr.length; n += 7) {
  306. this.table.push(
  307. arr.slice(n, n + 7).map((e, i) => {
  308. e.index = i + n;
  309. // 自定义信息
  310. let custom = this.customList.find((c) => c.date == e.date);
  311. // 农历
  312. if (this.lunar) {
  313. let { IDayCn, IMonthCn } = this.getLunar(e.date);
  314. e.lunar = IDayCn == "初一" ? IMonthCn : IDayCn;
  315. }
  316. return {
  317. ...e,
  318. ...custom,
  319. };
  320. })
  321. );
  322. }
  323. },
  324. // 设置日期
  325. setDate(date) {
  326. this.date = date || dayjs().format("YYYY-MM-DD");
  327. this.setTable();
  328. this.selects.start = this.findDate(this.date);
  329. this.selects.end = null;
  330. },
  331. // 从表格找日期
  332. findDate(date) {
  333. let d = null;
  334. this.table.forEach((tr) => {
  335. tr.forEach((td) => {
  336. if (td.date == date) {
  337. d = td;
  338. }
  339. });
  340. });
  341. return d;
  342. },
  343. // 获取农历
  344. getLunar(date) {
  345. let [year, month, day] = date.split("-");
  346. return Calendar.solar2lunar(year, month, day);
  347. },
  348. // 选择开始
  349. isStart({ disabled, date }) {
  350. return disabled
  351. ? false
  352. : this.selects.start
  353. ? dayjs(date).isSame(this.selects.start.date)
  354. : false;
  355. },
  356. // 选择结束
  357. isEnd({ disabled, date }) {
  358. return disabled
  359. ? false
  360. : this.selects.end
  361. ? dayjs(date).isSame(this.selects.end.date)
  362. : false;
  363. },
  364. // 选择路径
  365. isThrough({ disabled, date }) {
  366. if (disabled) {
  367. return false;
  368. }
  369. if (this.selects.start && this.selects.end) {
  370. return (
  371. dayjs(date).add(1, "second").isAfter(this.selects.start.date) &&
  372. dayjs(date).subtract(1, "second").isBefore(this.selects.end.date)
  373. );
  374. }
  375. },
  376. },
  377. };
  378. </script>