Day37:万年历

120 阅读4分钟

总体要求

在项目开始的时候,新建一个与存放子组件的components文件夹同级的utils文件夹,在其中新建一个index.js文件,用来封装工具方法。

工具方法为不直接渲染视图到页面的方法,可以抽象出来放在单独的文件中封装,能够提高复用性。

需要使用其中数据或方法时,需要从index.js文件的方法中return属性才能调用,调用方法可以直接在app.vue中import { initList, initTime } from './utils'

遍历与初始化年月

为百年日历建立一个长度为101的空数组用于存放年份,建立一个长度为12的空数组用于存放月份

  1. 通过new Array(n) 得到了是一个长度为n的一个空数组(其中的属性为空属性)
  2. 空属性的特点,空属性会跳过数组的遍历
  3. fill可以用来填充数组
export function formatYears () {
  // 这个方法最终返回一个数组,从1950到2050。创造了一个长度为101的空数组
  let years = new Array(101)
  // fill可以用来填充数组,填充后重新赋值
  years = years.fill()
  years = years.map((item, index) => {
    item = 1950 + index // 1950-2050年
    return item // 一定要return,将年份返回数组
  })
  return years // 一定要return,返回年份列表
}
export function formatMonths () {
  let months = new Array(12)
  months = months.fill()
  months = months.map((item, index) => {
    item = 1 + index // 1-12月
    return item // 一定要return
  })
  return months // 一定要return
}
import { formatYears, formatMonths } from '../utils'
export default {
  data () {
    return {
      years: [], // 存放从1950-2050的100年
      months: [] // 存放从1-12月
    }
  },
  mounted () {
    // 加载后数组初始化
    this.years = formatYears() // 调用方法,创建填充遍历
    this.months = formatMonths()
  }

加载之后,初始化当前的年月日

// 初始化当前时间的方法
export function initTime () {
  // 如何获得当前的年月日 获取当前日期
  const currentDate = new Date()
  const year = currentDate.getFullYear()
  const month = currentDate.getMonth() + 1
  const day = currentDate.getDate()
  return {
    year, month, day // 返回当前时间的年月日给组件
  }
}
import { formatYears, formatMonths, initTime } from '../utils'
export default {
  data () {
    return {
      year: '',
      mounth: ''
    }
  },
mounted () {
  // 组件加载后,初始化年月(这里后面的year不能有this
  this.year = initTime().year
  this.month = initTime().month
}

给下拉选择框动态绑定年和月两个数组

:value="item" 动态绑定value值为item项 v-for="(item, index) in years"遍历年和月中的元素和索引值 :key="index" 将索引值动态赋值为唯一key值 <div class="top-select"> <div class="data-select"> <select v-model="year"> <option :value="item" v-for="(item, index) in years" :key="index"> {{ item }}年 </option> </select> <select v-model="month"> <option :value="item" v-for="(item, index) in months" :key="index"> {{ item }}月 </option> </select> </div> <div class="today"> <button>今天</button> </div> </div> 渲染日历表组件 将month和year两个数据交给app(自定义事件,watch,监听year和month的变化,只要变化,即会把这两个值传给app进行下一步操作 app拿到数据后,把获取到的数据在app中存一份(this.curYear)。 计算出需要渲染的list app将list交给日历表组件让其渲染 回顾:自定义事件的调用——this.$emit(自定义事件名) 在父组件上定义一个方法,然后在父组件的子组件标签上通过绑定自定义事件,来传递方法。 在子组件中不需要props来接收(因为不是属性),但是调用时需要通过this.$emit(自定义事件名)来调用 watch: { year () { // 只要year变化,触发一个自定义事件,将year和month给app this.$emit('renderList', { year: this.year, month: this.month }) }, month () { this.$emit('renderList', { year: this.year, month: this.month }) } } <template> <div id="app"> <div class="main"> <MyTitle @renderList = 'renderList'/> <MyCalendar/> <MyFooter/> </div> </div> </template> <script> export default { methods: { renderList (payload) { this.curYear = payload.year this.curMonth = payload.month // 实现最核心的方法,根据年月计算出需要渲染的日期表格 this.list = initList(payload.year, payload.month) } } 表格总共42格 首先找出每个月的第一天是周几 每个月的第一天一定是在第一行 需要知道上个月有多少天,来定第一天 需要知道这个月有多少天,来定最后一天在哪 // 核心方法,计算渲染的日期表 export function initList (year, month) { // 这个月第一天是星期几 const firstDate = new Date(year, month - 1, 1) let day = firstDate.getDay() // 这里要写let,因为要改数据 // 如果是星期天,day是0 if (day === 0) { day = 7 } // 上个月有多少天 const lastDate = new Date(year, month - 1, 0) // month是索引,month-1为本月的天数,0则代表上个月最后一天,所以得到上个月的天数 const lastDays = lastDate.getDate() console.log(lastDays, 123) // 这个月有多少天(下个月的上个月有多少天,month-1+1) const nextDate = new Date(year, month, 0) // month-1+1,最后一项为0,从下个月看上个月有多少天,即本月有多少天 const nextDays = nextDate.getDate() console.log(nextDays, 123) } 写计算每个月渲染日期的方法 export function initList (year, month) { // 这个月第一天是星期几 const firstDate = new Date(year, month - 1, 1) let day = firstDate.getDay() // 这里为本月第一天的星期数。要写let,因为要改数据 // 如果是星期天,day是0 if (day === 0) { day = 7 } // 上个月有多少天 const lastDate = new Date(year, month - 1, 0) // 返回上个月最后一天 const lastDays = lastDate.getDate() // 返回上个月的天数 // 这个月有多少天(下个月的上个月有多少天,month-1+1) const nextDate = new Date(year, month, 0) // 返回下个月的上个月的最后一天 const nextDays = nextDate.getDate() // 得到本月的天数 const arr = [] // 42个数组元素,分为上个月,本月,和下个月的三个部分 // 算上个月剩余的天数。——算每个月在42天中所占的天数,并输出不同的type值 // i为index,j为事实的日期 for (let i = 0, j = lastDays - day + 2; i <= day - 2; i++, j++) { // 从第一格开始i=0, arr[i] = { value: j, type: 0 // 0表示上个月 } } // 算本月的天数 for (let i = day - 1, j = 1; i <= day + nextDays - 2; i++, j++) { arr[i] = { value: j, type: 1 // 1表示本月 } } // 算下个月的天数 for (let i = day + nextDays - 1, j = 1; i <= 41; i++, j++) { arr[i] = { value: j, type: 2 // 2表示下个月 } } return arr } let i = 0, j = lastDays - day + 2;i <= day - 2;i++,j++ 因为本月第一天的index为day-1,所以上个月最后一天的index为day-2 i是索引值,j是索引值对饮的日期 定义了一个名为 initList 的函数,它接受两个参数:year 和 month。根据给定的年份和月份,该函数返回一个数组,该数组表示该月份的日期列表。 代码的逻辑如下: 创建一个 firstDate 变量,表示给定月份的第一天。通过将 year、month - 1 和 1 作为参数传递给 new Date() 构造函数来创建一个表示该日期的对象。 使用 getDay() 方法获取 firstDate 的星期几,并将其存储在变量 day 中。该方法返回一个介于 0(星期日)到 6(星期六)之间的整数。 如果 day 等于 0,则将其替换为 7,以保持逻辑的一致性。 创建一个 lastDate 变量,表示上个月最后一天的日期。通过将 year、month - 1 和 0 作为参数传递给 new Date() 构造函数来创建一个表示该日期的对象。 使用 getDate() 方法获取 lastDate 的日期,并将其存储在变量 lastDays 中。该方法返回一个介于 1 到 31 之间的整数。 创建一个 nextDate 变量,表示下个月的日期。通过将 year、month 和 0 作为参数传递给 new Date() 构造函数来创建一个表示该日期的对象。 使用 getDate() 方法获取 nextDate 的日期,并将其存储在变量 nextDays 中。该方法返回一个介于 1 到 31 之间的整数。 创建一个名为 arr 的数组,长度为 42。这个数组用于存储每个月的日期列表。 使用三个循环来填充数组: 首先,循环计算上个月的剩余天数,并将它们添加到数组的前面,以表示上个月的天数。循环的起始索引是 i = 0,终止索引是 i <= day - 2,每次循环增加 i 并计算对应的日期值 j。每个元素的值存储在 arr[i].value 中,类型设置为 0 表示上个月。 然后,循环计算本月的天数,并将它们添加到数组的中间部分,以表示本月的天数。循环的起始索引是 i = day - 1,终止索引是 i <= day + nextDays - 2,每次循环增加 i 并计算对应的日期值 j。每个元素的值存储在 arr[i].value 中,类型设置为 1 表示本月。 最后,循环计算下个月的天数,并将它们添加到数组的后面部分,以表示下个月的天数。循环的起始索引是 i = day + nextDays - 1,终止索引是 i <= 41,每次循环增加 i 并计算对应的日期值 j。每个元素的值存储在 arr[i].value 中,类型设置为 2 表示下个月。 最后,返回填充好的数组 arr。 vue是一种用数据表达视图的模式,所以需要在数组中区分每个月的内容 渲染日历页面 将list与存储的curDay传入日历页面组件 表头为星期内容,定死,即可直接写七个 在list-2中遍历传入的list的item和index,给每一项都给定唯一key=index。并为每一项日期都添加鼠标经过样式与鼠标点击样式 将并非本月的日数写成灰色,定义动态绑定:class中的off,其中type不是1的样式都为灰色 定义动态绑定:class中的active,使每个月当前日都会高亮显示(改进,只高亮本月当前日 <template> <div class="table-list"> <div class="list-1"> <ul class="weekDay"> <li>一</li> <li>二</li> <li>三</li> <li>四</li> <li>五</li> <li>六</li> <li>日</li> </ul> </div> <div class="list-2"> <span v-for="item, index in list" :key="index" :class="{ off: item.type !== 1, active: item.value === curDay && item.type === 1 }">{{ item.value }}</span> </div> <div class="days"></div> </div> </template> <script> export default { props: ['list', 'curDay'] } </script> <style lang="scss" scoped> .list-2 { display: grid; grid-template-columns: repeat(7, 1fr); grid-template-rows: repeat(6, 1fr); gap: 20px; height: 400px; span { display: flex; align-items: center; justify-content: center; border-radius: 12px; &:hover { background-color: skyblue; cursor: pointer; } &.off { color: #bebec2; } &.active { background-color: #4e6ef2; } } } </style> 数据接口 得到的网址为—— http://v.juhe.cn/calendar/day?key=keyvalue&date=2023-13-32 (keyvalue和date根据自己的需要改 在终端安装axios npm i axios 有可能会有依赖冲突报错,报错信息中会有解决方法,在npm i axios后面添加 --force即可 export default { mounted () { axios.get('http://v.juhe.cn/calendar/day?key=keyvalue&date=2023-13-32').then(res => ) console.log(res,123) } } 直接发送请求会出现一个网络报错问题,它是由浏览器的同源策略引起的跨域问题。 同源策略:浏览器发送http请求,必须报称协议(http)、域名(localhost)、端口号(8080) 完全一致,否则这个请求发送不了,会产生跨域问题 同源策略引起的跨域问题在绝大多数情况下,都被后端接口解决了。但聚合数据的api没有在后端接口解决同源策略问题,因此需要在前端层面解决跨域问题。 在同一个域名、端口的web下启动一个服务器,再向其他服务器发起请求,即可解决同源策略。 基于Vue-cli启动一个本地服务器 vue.config.js是项目总的配置文件,在其中可以启动一个代理服务器 devServer: { proxy: 'https://v.juhe.cn/' } export default { mounted () { // 网址的协议、域名、端口号改为与本地内容一致 axios.get('http://localhost:8080/calendar/day?key=keyvalue&date=2023').then(res => { console.log(res, 123) }) } } axios.get相当于返回了一个Promise,可以直接嗲用then方法返回resolve中的data 实现点击切换上下月(年) 在日历组件中写一个方法(并接收)来触发自定义事件以修改curDay export default { props: ['list', 'curDay'], methods: { change (item) { // 触发一个自定义事件,来修改curDay this.$emit('changeCurDay', item) } } } 在父组件app中写核心方法changeCurDay changeCurDay (item) { // 核心方法 // console.log(item) // 区分点击的是上个月,本月还是下个月的日期 if (item.type === 1) { this.curDay = item.value // 把被点击赋值 } if (item.type === 0) { this.curDay = item.value if (this.curMonth === 1) { this.curMonth = 12 this.curYear-- // 1月处理 return } this.curMonth-- // 点击上个月自减 } if (item.type === 2) { this.curDay = item.value if (this.curMonth === 12) { this.curMonth = 1 this.curYear++ // 12月处理 return } this.curMonth++ // 点击下个月自加 /* curMonth变了,month需要监听 */ } this.initContent(this.curYear, this.curMonth, this.curDay) // 最后需要再调用一下封装好的发送请求代码 } 将方法传给top      <TopSelect @renderList = 'renderList' :curMonth="curMonth" :curYear="curYear"/> 在top中接收自定义方法,并监听,监听到的值就是newValue,将其赋值给视图中显示的值year和month export default { props: ['curYear', 'curMonth'], watch: { curYear (newValue) { // 这里的newvalue是形式参数,代表最新监听的值,可以写为this.curYear this.year = newValue }, curMonth (newValue) { this.month = newValue }, year () { // 只要year变化,触发一个自定义事件,将year和month给app this.$emit('renderList', { year: this.year, month: this.month }) }, month () { this.$emit('renderList', { year: this.year, month: this.month }) } } 封装发送请求 在app中定义一个方法,封装发送万年历阴历的请求, methods: { initContent (year, month, day) { /* axios.get(`http://localhost:8080/calendar/day?key=60ec893bcd01c950090b69258d436de6&date=${year}-${month}-${day}`).then(res => { */ axios.get(`http://localhost:8080/calendar/day?key=65b7ef7c24b07aa1c0b12d6ebcccac53&date=${year}-${month}-${day}`).then(res => { // console.log(this.content) // 定义一个方法 this.content = res.data.result.data }) } }, 把封装好的方法在LenderList中调用(此步出了大问题,不停地调用,等会记得修改 methods: { renderList (payload) { this.curYear = payload.year this.curMonth = payload.month // 实现最核心的方法 根据month year计算出需要渲染的日期表格 this.list = initList(payload.year, payload.month) this.initContent(this.curYear, this.curMonth, this.curDay) }, 把conten传到底部组件去 data () { return { curYear: '', curMonth: '', curDay: '', list: [], // 需要渲染的日期表格 content: '' } }, 在底部组件中接收一下,再使用{{}}渲染到页面中 <template> <div class="Header"> <div class="footer-con"> <div class="left"> <h3>{{content.lunar}}</h3> <h5>{{content.lunarYear}}&nbsp;&nbsp; {{content.animalsYear}}</h5> </div> <div class="right"> <p><span class="blue">宜</span>{{content.suit}}</p> <p><span class="black">忌</span>{{content.avoid}}</p> </div> </div> </div> </template> <script> export default { props: ['content'] } </script>