{"id":23864575,"url":"https://github.com/zlinni/milestone","last_synced_at":"2026-06-15T06:34:15.569Z","repository":{"id":108679580,"uuid":"455766112","full_name":"Zlinni/MileStone","owner":"Zlinni","description":"基于vue2+vuetify-ui的一个可以快速开始计划的网站","archived":false,"fork":false,"pushed_at":"2022-02-25T03:20:56.000Z","size":3616,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-22T13:23:55.197Z","etag":null,"topics":["echarts","pubsubjs","vue","vue2","vuecli","vuerouter","vuetify","vuex"],"latest_commit_sha":null,"homepage":"","language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Zlinni.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-02-05T03:36:15.000Z","updated_at":"2022-02-25T03:59:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"54b274cc-671f-48a3-a26c-bf70c411a0af","html_url":"https://github.com/Zlinni/MileStone","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Zlinni/MileStone","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zlinni%2FMileStone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zlinni%2FMileStone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zlinni%2FMileStone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zlinni%2FMileStone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Zlinni","download_url":"https://codeload.github.com/Zlinni/MileStone/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zlinni%2FMileStone/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34351448,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["echarts","pubsubjs","vue","vue2","vuecli","vuerouter","vuetify","vuex"],"created_at":"2025-01-03T08:26:41.446Z","updated_at":"2026-06-15T06:34:15.553Z","avatar_url":"https://github.com/Zlinni.png","language":"Vue","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MileStone\n基于vue2+vuetify-ui的一个可以快速开始计划的网站\n\n# 效果展示\n![](https://cdn.jsdelivr.net/gh/Zlinni/Pic/img/milestone预览.gif)\n\u003cbr\u003e\n项目演示地址:[传送门](https://zlinni.github.io/MileStone/#/)\n\u003cbr\u003e\n获取当前时间\n匹配当前时间和所有任务 找到最近的任务\n\n# 构建工具+技术栈\n1. 编译软件：vscode\n2. 开发框架: vue2\n3. 打包工具：vue-cli\n4. UI：vuetify\n5. icon：阿里巴巴\n6. 其他：vuex， vue-router，git\n7. 插件：pubsub，nanoid，less-loader，vuex-persist\n\n# 项目难点\n本次项目综合了很多方面的问题，比如vuex模块化加数据持久化，对所有任务的时间排序，以及vuetify的栅格系统等等，各种方面的问题哈哈，但好在最后都能够解决出来\n## vuex模块化结合本地化存储\nvuex是一个很方便的官方提供的数据管理插件，但在使用的时候一遇到刷新就会导致数据丢失的问题。于是想到了`localStorage`结合本地存储的方式进行数据持久化。\n{% tabs test, 1 %}\n\u003c!-- tab 方案一 localStorage --\u003e\n原理很简单 对于自己设置的数据先使用`JSON.parse(localStorage.getItem('xxx'))`\n然后在mutations里面对于需要增删改查的操作最后进行一次`localStorage.setItem(\"xxx\", JSON.stringify(state.xxx));`\n部分代码：\n```js\nvar todoObj =  JSON.parse(localStorage.getItem(\"todoObj\")) || [\n  {id: \"X5DX4v8Wz4r97xjs\", title: \"阿斯顿\", done: false}\n];\n------------------------------------------------------------------------\n    updatedType(state, [people, subject, typeId, typeValue]) {\n      console.log(typeValue);\n      state.dataList.forEach(data =\u003e {\n        if (data.people === people) {\n          data.kemu.forEach(kemu =\u003e {\n            if (kemu.subject === subject) {\n              kemu.typeList.forEach(typelist =\u003e {\n                if (typelist.id === typeId) {\n                  Vue.set(typelist, 'type', typeValue[0]);\n                  Vue.set(typelist, 'time', typeValue[2]);\n                  Vue.set(typelist, 'remarks', typeValue[1]);\n                }\n              })\n            }\n          })\n        }\n      });\n       localStorage.setItem('dataList', JSON.stringify(state.dataList));\n    },\n```\n但是这个方法也有一个弊端，就是每次执行增删改查的操作都需要进行一次手动存储，在操作多的情况下非常不适合。于是找到了这个vuex的插件`vuex-presist`来帮助模块进行本地存储\n\u003c!-- endtab --\u003e\n\u003c!-- tab 方案2 vuex-presist --\u003e\n实质上就是进行了本地存储，当然也有新的类型比如session和cookie\n安装：\n```bash\nnpm install --save vuex-persist\n```\n引入\n```js\nimport VuexPersistence from 'vuex-persist'\n```\n定义 (此处是根据我的两个模块来使用)\n```js\nconst vuexLocal = new VuexPersistence({\n  storage: window.localStorage,\n  modules: ['dataListOptions', 'todoOptions']\n})\n```\n引入到vuex\n```js\n// 创建并暴露store\nexport default new vuex.Store({\n  modules: {\n    dataListOptions,\n    todoOptions\n  },\n  plugins: [vuexLocal.plugin]\n})\n```\n注意此处的引入位置\n\u003c!-- endtab --\u003e\n{% endtabs %}\n\n## vuex模块化踩坑\n{% note primary flat %}\n当vuex要使用很多options的时候，会采用模块化的方式来写代码会比较方便管理。\n比如定义一个todoOptions 里面就要有store的全部方法 以及一个`namespaced`属性\n{% endnote %}\n\n```js\nconst todoOptions ={\n  namespaced: true,\n  actions: {\n\n  },\n  mutations:{\n  \n  },\n  state: {\n    todoObj: []\n  },\n}\n...\nexport default new vuex.Store({\n  modules: {\n    dataListOptions,\n    todoOptions\n  },\n})\n```\n到这一步其实大多数时候都没有问题 出问题在于使用到mutation或者actions的时候\n```js\n//此处需要使用名字/方法否则不行\n this.$store.dispatch(\n        \"dataListOptions/transType\",\n        this.$route.params.people\n      );\n      \n```\n如果用到了mapState插件或者其他的，也需要加名字 注意是要用双引号\n```js\n...mapState(\"todoOptions\", [\"todoObj\"]),\n```\n以及最后其实vuex的value是可以传多个参数的，用数组或者对象的形式\n```js\n      this.$store.commit(\"dataListOptions/addType\", [\n        this.$route.params.people,\n        this.subject,\n      ]);\n```\n## 准确获取时区\n{% note primary flat %}\n一般大家使用时间都是直接用`new Date()`但这个方法获取的并不是完全准确的时区时间，采用下面的方法可以获取更为准确的时间\n{% endnote %}\n```js\n    selectTimezone() {\n      var d = new Date();\n      //得到1970年一月一日到现在的秒数\n      var len = d.getTime();\n      //确定时区\n      var timezone = -d.getTimezoneOffset() / 60;\n      //本地时间与GMT时间的时间偏移差\n      var offset = d.getTimezoneOffset() * 60000;\n      //得到现在的格林尼治时间\n      var utcTime = len + offset;\n      return new Date(utcTime + 3600000 * timezone);\n    },\n```\n## 时间排序\n{% note primary flat %}\n项目中需要将自定义的任务进行一个时间的排序最后呈现到学习计划页面\n{% endnote %}\n预览\n![](https://cdn.jsdelivr.net/gh/Zlinni/Pic/img/20220222122336.png)\n当一开始的时间格式是`14:00-15:00`这样的\n```js\n        subject: \"数学\",\n        typeList: [{\n            id: '001',\n            type: \"高等数学\",\n            time: '14:00-15:00',\n            remarks: '暂无'\n          },\n          {\n            id: '002',\n            type: \"线性代数\",\n            time: '15:00-16:00',\n            remarks: '暂无'\n          },\n          {\n            id: '003',\n            type: \"概率论\",\n            time: '16:00-17:00',\n            remarks: '暂无'\n          }\n        ]\n```\n于是想到了`split`方法去分割第一个时间点，\n```js\n      let res = [];\n      let indexArr = [];\n      this.$store.state.dataListOptions.typeList.forEach((item, index) =\u003e {\n        let hhmm = item.time.split(\"-\", 1)[0];\n        let hh = hhmm.split(\":\");\n        let sum = hh.reduce((total, val) =\u003e {\n          return (total += val);\n        }, \"\");\n        res.push(parseInt(sum));\n      });\n```\n然后将第一个时间点化为`1400`的形式，以此类推。只要对比第一个时间点就可以完成排序，这样一来就可以使用sort方法，但问题又出现了，这样比较之后需要将时间还原回去才能在原本的数组里面找到对应的任务名称。或者是知道数组下标的形式？那么如何知道排序后原先时间数组的下标呢？\n```js\n      let typeMap = new Map();\n       typeMap.set(parseInt(sum), index);\n```\n使用到map类里面的set方法，将原先的数据和下标传入到map中，最后遍历排序后的时间数组，通过get方法去查找数据原先对应的下标\n```js\n    typeList() {\n      let res = [];\n      let typeMap = new Map();\n      let indexArr = [];\n      this.$store.state.dataListOptions.typeList.forEach((item, index) =\u003e {\n        let hhmm = item.time.split(\"-\", 1)[0];\n        let hh = hhmm.split(\":\");\n        let sum = hh.reduce((total, val) =\u003e {\n          return (total += val);\n        }, \"\");\n        res.push(parseInt(sum));\n        typeMap.set(parseInt(sum), index);\n      });\n      console.log(\"排序前的res\", res);\n      res.sort(function (a, b) {\n        return a - b;\n      });\n      console.log(\"排序后的res\", res);\n      res.forEach((item) =\u003e {\n        indexArr.push(typeMap.get(item));\n      });\n      console.log(\"下标\", indexArr);\n      let finalArr = [];\n      indexArr.forEach((item) =\u003e {\n        this.$store.state.dataListOptions.typeList.forEach((data, index) =\u003e {\n          if (index === item) {\n            finalArr.push(data);\n          }\n        });\n      });\n      console.log(\"最终数组\", finalArr);\n      return finalArr;\n    },\n```\n## 时间排序2\n项目中还有一个地方需要用到时间排序，就是页面中这个introduce的地方，它会随着任务的执行判断时间，然后返回相应的内容到页面上\n预览\n![](https://cdn.jsdelivr.net/gh/Zlinni/Pic/img/20220222122836.png)\n需求是有三种判断 \n一是任务没有开始 那么就显示下个任务还有xx时xx分开始，如果不到1小时，则返回xx分\n二是任务正在执行中 那么显示该任务还有xx时xx分结束，如果不到1小时，则返回xx分\n三是任务都执行完了 此时显示今日任务已完毕，查看今日总结\n\n获取任务数组这块就不说了 用的是消息的订阅和发布，收到的是已经排序好时间的任务数组\n那么为了执行现在的时间是否在某个任务时间段内，就需要三个变量，一个是现在时间，一个是当前任务开始时间，结束时间\n```js\n  this.pid = pubsub.subscribe(\n      \"judgeTime\",\n      (msg, [timeArr, typeNameArr, len]) =\u003e {\n        try {\n          timeArr.forEach((item, index) =\u003e {\n            var hhmm = item.split(\"-\");\n            var strTime1 = hhmm[0].split(\":\");\n            var strTime2 = hhmm[1].split(\":\");\n            var b = this.selectTimezone();\n            var e = this.selectTimezone();\n            var n = this.selectTimezone();\n            b.setHours(strTime1[0]);\n            b.setMinutes(strTime1[1]);\n            b.setSeconds(0);\n            b.setMilliseconds(0);\n            e.setHours(strTime2[0]);\n            e.setMinutes(strTime2[1]);\n            e.setSeconds(0);\n            e.setMilliseconds(0);\n\n            if (b.getTime() - n.getTime() \u003e 0) {\n              this.typeName = \"还未到学习时间\";\n              console.log(b);\n              this.beginTime = b;\n              this.state = \"start\";\n······\n```\n然后比较它们的时间戳，细节上为了准确的比较还得把秒和毫秒设置0\n```js\n    calTime() {\n      var n = this.selectTimezone();\n      var chazhi = this.beginTime - n;\n      this.minutes = Math.floor((chazhi / 1000 / 60) % 60);\n      this.hours = Math.floor((chazhi / 1000 / 60 / 60) % 24);\n      if (this.hours \u003e 0) {\n        if (this.state === \"start\") {\n          this.endTime = `距离下一个任务开始还有${this.hours}小时${this.minutes}分钟`;\n        } else if (this.state === \"end\") {\n          this.endTime = `距离学习结束还有${this.hours}小时${this.minutes}分钟`;\n        }\n      } else {\n        if (this.state === \"start\") {\n          this.endTime = `距离下一个任务开始还有${this.minutes}分钟`;\n        } else if (this.state === \"end\") {\n          this.endTime = `距离学习结束还有${this.minutes}分钟`;\n        }\n      }\n      if (chazhi \u003e 0) {\n        setTimeout(() =\u003e {\n          this.calTime();\n        }, 1000);\n      } else {\n        this.$router.go(0);\n      }\n    },\n```\n此处有个难点，就是在定时器代码那里，如果使用的是`setInterval`那么可能在下个时间段的时候，前面的代码计算比较复杂，他会跳过当前的这个时间应该执行的任务（代码队列之前的任务没有处理完，js引擎只能允许一份未执行的代码），这方面的问题要去了解js的事件循环机制，于是这里采用了`setTimeout`来反复调用自身，最后当差值小于或等于0的时候利用routergo刷新页面\n最后在补充一下剩下的判断代码\n```js\n            if (b.getTime() - n.getTime() \u003e 0) {\n              this.typeName = \"还未到学习时间\";\n              console.log(b);\n              this.beginTime = b;\n              this.state = \"start\";\n              this.calTime();\n              localStorage.removeItem(\"dayEnd\");\n              throw new Error(\"end\");\n            } else if (\n              n.getTime() - b.getTime() \u003e= 0 \u0026\u0026\n              n.getTime() - e.getTime() \u003c 0\n            ) {\n              this.typeName = typeNameArr[index];\n              this.beginTime = e;\n              this.state = \"end\";\n              this.calTime();\n              localStorage.removeItem(\"dayEnd\");\n              throw new Error(\"end\");\n            } else if (index === len - 1 \u0026\u0026 n.getTime() - e.getTime() \u003e= 0) {\n              this.typeName = \"今日任务已完成\";\n              this.endTime = \"点击查看今日总结~\";\n              localStorage.setItem(\"dayEnd\", \"end\");\n              throw new Error(\"end\");\n            }\n```\n\n# 结语\n本次项目是对vue2练手的独立项目，从该项目中我收获到了有关vuex模块化以及数据持久化的处理，时间算法和事件循环的操作，并加深了vuetify-ui的使用，尤其是栅格系统和断点系统，最后针对项目进行了打包优化，结合cdn提升了访问速度。如果对你有帮助，记得点个star并关注我的博客：zlinni.github.io  会持续更新前端相关的知识\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzlinni%2Fmilestone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzlinni%2Fmilestone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzlinni%2Fmilestone/lists"}