├── .babelrc ├── .gitignore ├── README.md ├── index.html ├── package.json ├── public ├── favicon.ico └── img │ ├── loading.svg │ ├── logo.jpg │ └── userLogo.jpeg ├── src ├── App.vue ├── assets │ ├── BiaoChenXuYing.png │ ├── FrontEndGitHub.png │ ├── bg.jpg │ ├── loading.svg │ ├── logo.jpg │ ├── logo.png │ ├── user.png │ ├── user.svg │ └── userLogo.jpeg ├── components │ ├── ArrowUp.vue │ ├── Comment.vue │ ├── CommentList.vue │ ├── CustomSlider.vue │ ├── Footer.vue │ ├── HelloWorld.vue │ ├── LoadEnd.vue │ ├── Loading.vue │ ├── Nav.vue │ └── RegisterAndLogin.vue ├── less │ ├── index.less │ ├── mobile.less │ └── monokai_sublime.less ├── main.ts ├── mixins │ └── index.ts ├── router │ └── index.ts ├── shims-vue.d.ts ├── store │ ├── index.ts │ ├── modules │ │ └── user.ts │ └── types.ts ├── types │ └── index.d.ts ├── utils │ ├── config.ts │ ├── https.ts │ ├── markdown.ts │ ├── mockTest.ts │ ├── urls.ts │ └── utils.ts └── views │ ├── Archive.vue │ ├── ArticleDetail.vue │ ├── Articles.vue │ ├── Home.vue │ ├── Message.vue │ ├── Project.vue │ └── Timeline.vue ├── tsconfig.json └── vite.config.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "component", 5 | { 6 | "libraryName": "element-plus", 7 | "styleLibraryName": "theme-chalk" 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | yarn.lock 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://upload-images.jianshu.io/upload_images/12890819-527034962df50506.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 2 | 3 | 4 | ## 简介 5 | 6 | **项目已经用 Vue3 + TypeScript + Vite2 + Vuex4 + Vue-Router4 + element-plus 重构完啦!** 7 | 8 | Vue2 版本请点这里:[基于 Vue2 全家桶 + TypeScript + Element-UI](https://github.com/biaochenxuying/blog-vue-typescript/tree/vue2) 或者 [v2.0.0](https://github.com/biaochenxuying/blog-vue-typescript/releases/tag/v2.0.0) 9 | 10 | --- 11 | 12 | 此项目是基于 Vue3 全家桶 + TypeScript + element-plus 技术栈的简洁时尚博客网站。 13 | 14 | 项目详情请猛戳该文章: 15 | 16 | - [Vue3 全家桶 + TS+ Vite2 + element-plus 搭建简洁时尚的博客网站实战及踩坑记](https://juejin.cn/post/6924687052005081095) 17 | 18 | 19 | ## 效果 20 | 21 | 效果图: 22 | 23 | - pc 端 24 | 25 | ![](https://upload-images.jianshu.io/upload_images/12890819-9f5f1b384a27c6ff.gif?imageMogr2/auto-orient/strip) 26 | 27 | 28 | - 移动端 29 | 30 | ![](https://upload-images.jianshu.io/upload_images/12890819-5370ed6dfbe61051.gif?imageMogr2/auto-orient/strip) 31 | 32 | 33 | 34 | 35 | 36 | ## 功能 37 | 38 | ### 已经完成功能 39 | 40 | - [x] 登录 41 | - [x] 注册 42 | - [x] 文章列表 43 | - [x] 文章归档 44 | - [x] 标签 45 | - [x] 关于 46 | - [x] 点赞与评论 47 | - [x] 留言 48 | - [x] 历程 49 | - [x] 文章详情(支持代码语法高亮) 50 | - [x] 文章详情目录 51 | - [x] 移动端适配 52 | - [x] github 授权登录 53 | 54 | 55 | [⬆️ 返回顶部](##简介) 56 | 57 | ## 前端主要技术 58 | 59 | 所有技术都是当前最新的。 60 | 61 | - vue: ^3.0.5 62 | - typescript : ^4.1.3 63 | - element-plus: ^1.0.2-beta.41 64 | - vue-router : ^4.0.6 65 | - vite: ^2.2.3 66 | - vuex: ^4.0.0 67 | - axios: ^0.21.1 68 | - highlight.js: ^10.7.2 69 | - marked:^2.0.3 70 | 71 | 72 | [⬆️ 返回顶部](##简介) 73 | 74 | ## Build Setup 75 | 76 | ``` 77 | # clone 78 | git clone https://github.com/biaochenxuying/blog-vue-typescript.git 79 | ``` 80 | 81 | ``` 82 | # cd 83 | cd blog-vue-typescript 84 | ``` 85 | 86 | ``` 87 | # install dependencies 88 | npm install 89 | ``` 90 | 91 | ``` 92 | # Compiles and hot-reloads for development 93 | npm run dev 94 | ``` 95 | 96 | ``` 97 | # Compiles and minifies for production 98 | npm run build 99 | ``` 100 | 101 | 102 | 如果要看有后台数据完整的效果,是要和后台项目 **[blog-node](https://github.com/biaochenxuying/blog-node)** 一起运行才行的,不然接口请求会失败。 103 | 104 | 105 | 106 | 107 | [⬆️ 返回顶部](##简介) 108 | 109 | 110 | ## 项目地址与文档教程 111 | 112 | **项目地址:** 113 | 114 | > [vue 前台展示: https://github.com/biaochenxuying/blog-vue-typescript](https://github.com/biaochenxuying/blog-vue-typescript) 115 | 116 | > [react 前台展示: https://github.com/biaochenxuying/blog-react](https://github.com/biaochenxuying/blog-react) 117 | 118 | > [管理后台:https://github.com/biaochenxuying/blog-react-admin](https://github.com/biaochenxuying/blog-react-admin) 119 | 120 | > [后端:https://github.com/biaochenxuying/blog-node](https://github.com/biaochenxuying/blog-node) 121 | 122 | > [blog:https://github.com/biaochenxuying/blog](https://github.com/biaochenxuying/blog) 123 | 124 | **本博客系统的系列文章:** 125 | 126 | - [0. Vue3 全家桶 + TS+ Vite2 + element-plus 搭建简洁时尚的博客网站实战及踩坑记](https://juejin.cn/post/6959174069577220110) 127 | - [0. Vue3 全家桶 + Element Plus + Vite + TypeScript + Eslint 项目配置最佳实践](https://juejin.cn/post/6924687052005081095) 128 | - [1. react + node + express + ant + mongodb 的简洁兼时尚的博客网站](https://juejin.cn/post/6844903718345768973) 129 | - [2. react + Ant Design + 支持 markdown 的 blog-react 项目文档说明](https://juejin.cn/post/6844903719260127239) 130 | - [3. 基于 node + express + mongodb 的 blog-node 项目文档说明](https://juejin.cn/post/6844903721680240653) 131 | - [4. 服务器小白的我,是如何将 node+mongodb 项目部署在服务器上并进行性能优化的](https://juejin.cn/post/6844903721827041293) 132 | - [5. github 授权登录教程与如何设计第三方授权登录的用户表](https://juejin.cn/post/6844903789091094542) 133 | - [6. 一次网站的性能优化之路 -- 天下武功,唯快不破](https://juejin.cn/post/6844903798692020237) 134 | - [7. Vue + TypeScript + Element 搭建简洁时尚的博客网站及踩坑记](https://juejin.cn/post/6844903810457042957) 135 | - [8. 前端解决第三方图片防盗链的办法 - html referrer 访问图片资源403问题](https://juejin.cn/post/6844903869755949069) 136 | 137 | 138 | [⬆️ 返回顶部](##简介) 139 | 140 | 141 | ## 服务器 142 | 143 | 笔者觉得每个开发者都应该拥有自己的网站和服务器,这可是很酷的事情,学习 Linux、跑跑脚本、建站、搭博客啥的都行啊。 144 | 145 | 因为笔者就有自己的服务器,而且有两台了,用于平时的学习,还搭建了自己的网站。 146 | 147 | 有不少读者问过我,为什么我学的那么快的呢 ? 怎么在一年内学了那么知识的... 148 | 149 | 其实也没什么秘决,就是平时有自己的服务器了,就爱折腾,学到的知识能很快得到验证,所以学起来兴致高一点。 150 | 151 | 特别是大三和大四的学生,买了服务器,搭建个项目给面试官看也香,对找工作和面试都加分,还可以熟悉技术栈。 152 | 153 | 阿里云、腾讯云、华为云 都有,这里购买就是最优惠: [低于 1 折、89/年、229/3年,比学生机还便宜](https://github.com/biaochenxuying/biaochenxuying/issues/1)) 154 | 155 | 比如笔者的两个网站: 156 | 157 | 158 | 159 | > https://www.kwgg2020.com/ 160 | 161 | 162 | [⬆️ 返回顶部](##简介) 163 | 164 | 165 | ## 项目推荐 166 | 167 | 168 | > 专注于挖掘优秀的前端开源项目,抹平你的前端信息不对称的项目:https://github.com/FrontEndGitHub/FrontEndGitHub 169 | 170 | > 以最优惠的方式购买极客时间课程:https://github.com/biaochenxuying/preferential-courses ,涵盖了后端、架构、前端、移动、人工智能、大数据、产品、运营、运维、测试等 171 | 172 | 173 | [⬆️ 返回顶部](##简介) 174 | 175 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 夜尽天明的个人博客网站 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 加载中... 21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-vue-ts-vite", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vue-tsc --noEmit && vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.21.1", 11 | "element-plus": "^1.0.2-beta.41", 12 | "highlight.js": "^10.7.2", 13 | "marked": "^2.0.3", 14 | "vue": "^3.0.5", 15 | "vue-router": "^4.0.6", 16 | "vuex": "^4.0.0" 17 | }, 18 | "devDependencies": { 19 | "@types/marked": "^2.0.2", 20 | "@vitejs/plugin-vue": "^1.2.2", 21 | "@vue/compiler-sfc": "^3.0.5", 22 | "less": "^4.1.1", 23 | "typescript": "^4.1.3", 24 | "vite": "^2.2.3", 25 | "vite-plugin-style-import": "^0.10.0", 26 | "vue-tsc": "^0.0.24" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/public/favicon.ico -------------------------------------------------------------------------------- /public/img/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/public/img/logo.jpg -------------------------------------------------------------------------------- /public/img/userLogo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/public/img/userLogo.jpeg -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 95 | 96 | 113 | -------------------------------------------------------------------------------- /src/assets/BiaoChenXuYing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/src/assets/BiaoChenXuYing.png -------------------------------------------------------------------------------- /src/assets/FrontEndGitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/src/assets/FrontEndGitHub.png -------------------------------------------------------------------------------- /src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/src/assets/bg.jpg -------------------------------------------------------------------------------- /src/assets/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/src/assets/logo.jpg -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/src/assets/user.png -------------------------------------------------------------------------------- /src/assets/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/userLogo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-vue-typescript/f55b2609aa630195d6c070fdf70750df2134b6e2/src/assets/userLogo.jpeg -------------------------------------------------------------------------------- /src/components/ArrowUp.vue: -------------------------------------------------------------------------------- 1 | 2 | 11 | 47 | 66 | 67 | -------------------------------------------------------------------------------- /src/components/Comment.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 153 | 158 | -------------------------------------------------------------------------------- /src/components/CommentList.vue: -------------------------------------------------------------------------------- 1 | 2 | 100 | 186 | 307 | 308 | -------------------------------------------------------------------------------- /src/components/CustomSlider.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 103 | 104 | 105 | 256 | -------------------------------------------------------------------------------- /src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 2 | 12 | 19 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 41 | -------------------------------------------------------------------------------- /src/components/LoadEnd.vue: -------------------------------------------------------------------------------- 1 | 4 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/Loading.vue: -------------------------------------------------------------------------------- 1 | 9 | 16 | 22 | -------------------------------------------------------------------------------- /src/components/Nav.vue: -------------------------------------------------------------------------------- 1 | 191 | 192 | 420 | 421 | 422 | 565 | -------------------------------------------------------------------------------- /src/components/RegisterAndLogin.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 234 | -------------------------------------------------------------------------------- /src/less/index.less: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 10px; 3 | margin: 0; 4 | } 5 | a { 6 | text-decoration: none; 7 | } 8 | .clearfix:before, 9 | .clearfix:after { 10 | display: table; 11 | content: ''; 12 | } 13 | .layout { 14 | display: flex; 15 | } 16 | .right { 17 | // float: right; 18 | width: 350px; 19 | } 20 | .left { 21 | // float: left; 22 | // width: 830px; 23 | flex: 1; 24 | padding-right: 20px !important; 25 | } 26 | .clearfix:after { 27 | clear: both; 28 | } 29 | .fl { 30 | float: left; 31 | } 32 | .fr { 33 | float: right; 34 | } 35 | 36 | /*对 markdown 样式的补充*/ 37 | pre { 38 | display: block; 39 | padding: 10px; 40 | margin: 0 0 10px; 41 | font-size: 14px; 42 | line-height: 1.42857143; 43 | color: #abb2bf; 44 | background: #23241f; 45 | word-break: break-all; 46 | word-wrap: break-word; 47 | overflow: auto; 48 | border-radius: 5px; 49 | } 50 | 51 | h1, 52 | h2, 53 | h3, 54 | h4, 55 | h5, 56 | h6 { 57 | margin-top: 1em; 58 | } 59 | strong { 60 | font-weight: bold; 61 | } 62 | 63 | p > code:not([class]) { 64 | padding: 2px 4px; 65 | font-size: 90%; 66 | color: #c7254e; 67 | background-color: #f9f2f4; 68 | border-radius: 4px; 69 | } 70 | 71 | img { 72 | max-width: 100%; 73 | } 74 | .container { 75 | width: 1200px; 76 | margin: 0 auto; 77 | } 78 | .article-detail { 79 | img { 80 | /* 图片居中 */ 81 | display: flex; 82 | max-width: 100%; 83 | margin: 0 auto; 84 | } 85 | table { 86 | text-align: center; 87 | border: 1px solid #eee; 88 | margin-bottom: 1.5em; 89 | } 90 | th, 91 | td { 92 | // text-align: center; 93 | padding: 0.5em; 94 | } 95 | tr:nth-child(2n) { 96 | background: #f7f7f7; 97 | } 98 | } 99 | 100 | .article-detail { 101 | font-size: 16px; 102 | line-height: 30px; 103 | } 104 | 105 | .article-detail .desc ul, 106 | .article-detail .desc ol { 107 | color: #333333; 108 | margin: 1.5em 0 0 25px; 109 | } 110 | 111 | .article-detail .desc h1, 112 | .article-detail .desc h2 { 113 | border-bottom: 1px solid #eee; 114 | padding-bottom: 10px; 115 | } 116 | 117 | .article-detail .desc a { 118 | color: #009a61; 119 | } 120 | 121 | .article-detail blockquote { 122 | margin: 0 0 1em; 123 | background-color: rgb(220, 230, 240); 124 | padding: 1em 0 0.5em 0.5em; 125 | border-left: 6px solid rgb(181, 204, 226); 126 | } 127 | -------------------------------------------------------------------------------- /src/less/mobile.less: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 780px) { 2 | .container { 3 | font-size: 0.3rem; 4 | width: 100% !important; 5 | } 6 | .ant-layout-content { 7 | padding: 0 !important; 8 | } 9 | .Layouts .layout { 10 | width: 100% !important; 11 | } 12 | .nav { 13 | text-align: center; 14 | .logo { 15 | margin-top: 0 !important; 16 | margin-bottom: 5px !important; 17 | } 18 | } 19 | .left { 20 | flex: 1; 21 | padding-right: 0 !important; 22 | } 23 | 24 | // 文章 25 | li img { 26 | /* 图片居中 */ 27 | display: flex; 28 | max-width: 100%; 29 | margin: 0 auto; 30 | } 31 | 32 | .article-detail { 33 | img { 34 | /* 图片居中 */ 35 | display: flex; 36 | width: 100%; 37 | max-width: 100%; 38 | margin: 0 auto; 39 | } 40 | } 41 | 42 | // 导航 43 | .logo { 44 | height: 50px; 45 | img { 46 | height: 100%; 47 | margin-left: 10px; 48 | } 49 | } 50 | .drawer { 51 | text-align: center; 52 | p { 53 | color: #000; 54 | padding: 10px; 55 | border-bottom: 1px solid #eee; 56 | margin-bottom: 0; 57 | &:last-child { 58 | border-bottom: none; 59 | } 60 | a { 61 | color: #000; 62 | display: inline-block; 63 | width: 100%; 64 | } 65 | } 66 | } 67 | .user-name { 68 | position: relative; 69 | top: -10px; 70 | left: -10px; 71 | } 72 | .nav-title { 73 | font-size: 26px; 74 | } 75 | .ant-drawer-body { 76 | padding: 0 !important; 77 | } 78 | 79 | // 留言 80 | .message { 81 | padding: 10px !important; 82 | } 83 | 84 | // 文章标签 85 | .article .header .tags { 86 | float: left !important; 87 | } 88 | // 项目 89 | .left .project { 90 | padding: 0; 91 | span { 92 | float: static; 93 | width: 100% !important; 94 | } 95 | } 96 | // 历程 97 | .el-timeline { 98 | padding-inline-start: 0; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/less/monokai_sublime.less: -------------------------------------------------------------------------------- 1 | .hljs { 2 | display: block; 3 | padding: 0.5em; 4 | background: #23241f; 5 | } 6 | 7 | .hljs, 8 | .hljs-tag, 9 | .css .hljs-rules, 10 | .css .hljs-value, 11 | .css .hljs-function .hljs-preprocessor, 12 | .hljs-pragma { 13 | color: #f8f8f2; 14 | } 15 | 16 | .hljs-strongemphasis, 17 | .hljs-strong, 18 | .hljs-emphasis { 19 | color: #a8a8a2; 20 | } 21 | 22 | .hljs-bullet, 23 | .hljs-blockquote, 24 | .hljs-horizontal_rule, 25 | .hljs-number, 26 | .hljs-regexp, 27 | .alias .hljs-keyword, 28 | .hljs-literal, 29 | .hljs-hexcolor { 30 | color: #ae81ff; 31 | } 32 | 33 | .hljs-tag .hljs-value, 34 | .hljs-code, 35 | .hljs-title, 36 | .css .hljs-class, 37 | .hljs-class .hljs-title:last-child { 38 | color: #a6e22e; 39 | } 40 | 41 | .hljs-link_url { 42 | font-size: 80%; 43 | } 44 | 45 | .hljs-strong, 46 | .hljs-strongemphasis { 47 | font-weight: bold; 48 | } 49 | 50 | .hljs-emphasis, 51 | .hljs-strongemphasis, 52 | .hljs-class .hljs-title:last-child { 53 | font-style: italic; 54 | } 55 | 56 | .hljs-keyword, 57 | .hljs-function, 58 | .hljs-change, 59 | .hljs-winutils, 60 | .hljs-flow, 61 | .lisp .hljs-title, 62 | .clojure .hljs-built_in, 63 | .nginx .hljs-title, 64 | .tex .hljs-special, 65 | .hljs-header, 66 | .hljs-attribute, 67 | .hljs-symbol, 68 | .hljs-symbol .hljs-string, 69 | .hljs-tag .hljs-title, 70 | .hljs-value, 71 | .alias .hljs-keyword:first-child, 72 | .css .hljs-tag, 73 | .css .unit, 74 | .css .hljs-important { 75 | color: #f92672; 76 | } 77 | 78 | .hljs-function .hljs-keyword, 79 | .hljs-class .hljs-keyword:first-child, 80 | .hljs-constant, 81 | .css .hljs-attribute { 82 | color: #66d9ef; 83 | } 84 | 85 | .hljs-variable, 86 | .hljs-params, 87 | .hljs-class .hljs-title { 88 | color: #f8f8f2; 89 | } 90 | 91 | .hljs-string, 92 | .css .hljs-id, 93 | .hljs-subst, 94 | .haskell .hljs-type, 95 | .ruby .hljs-class .hljs-parent, 96 | .hljs-built_in, 97 | .sql .hljs-aggregate, 98 | .django .hljs-template_tag, 99 | .django .hljs-variable, 100 | .smalltalk .hljs-class, 101 | .django .hljs-filter .hljs-argument, 102 | .smalltalk .hljs-localvars, 103 | .smalltalk .hljs-array, 104 | .hljs-attr_selector, 105 | .hljs-pseudo, 106 | .hljs-addition, 107 | .hljs-stream, 108 | .hljs-envvar, 109 | .apache .hljs-tag, 110 | .apache .hljs-cbracket, 111 | .tex .hljs-command, 112 | .hljs-prompt, 113 | .hljs-link_label, 114 | .hljs-link_url { 115 | color: #e6db74; 116 | } 117 | 118 | .hljs-comment, 119 | .hljs-javadoc, 120 | .java .hljs-annotation, 121 | .python .hljs-decorator, 122 | .hljs-template_comment, 123 | .hljs-pi, 124 | .hljs-doctype, 125 | .hljs-deletion, 126 | .hljs-shebang, 127 | .apache .hljs-sqbracket, 128 | .tex .hljs-formula { 129 | color: #75715e; 130 | } 131 | 132 | .coffeescript .javascript, 133 | .javascript .xml, 134 | .tex .hljs-formula, 135 | .xml .javascript, 136 | .xml .vbscript, 137 | .xml .css, 138 | .xml .hljs-cdata, 139 | .xml .php, 140 | .php .xml { 141 | opacity: 0.5; 142 | } 143 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { store, key } from './store' 4 | import router from "./router"; 5 | import service from "./utils/https"; 6 | import urls from "./utils/urls"; 7 | import mixin from "./mixins"; 8 | import { 9 | ElButton, 10 | ElDialog, 11 | ElForm, 12 | ElFormItem, 13 | ElInput, 14 | ElMessage, 15 | ElMenu, 16 | ElMenuItem, 17 | ElRow, 18 | ElCol, 19 | ElDropdown, 20 | ElDropdownMenu, 21 | ElDropdownItem, 22 | ElLoading, 23 | ElTimeline, 24 | ElTimelineItem, 25 | ElCard, 26 | ElTag, 27 | ElIcon, 28 | ElCollapseTransition 29 | } from 'element-plus'; 30 | 31 | const app = createApp(App) 32 | // app.mixin(mixin); 33 | 34 | app.component(ElButton.name, ElButton); 35 | app.component(ElDialog.name, ElDialog); 36 | app.component(ElForm.name, ElForm); 37 | app.component(ElFormItem.name, ElFormItem); 38 | app.component(ElInput.name, ElInput); 39 | app.component(ElMessage.name, ElMessage); 40 | app.component(ElMenu.name, ElMenu); 41 | app.component(ElMenuItem.name, ElMenuItem); 42 | app.component(ElRow.name, ElRow); 43 | app.component(ElCol.name, ElCol); 44 | app.component(ElDropdownMenu.name, ElDropdownMenu); 45 | app.component(ElTimeline.name, ElTimeline); 46 | app.component(ElTimelineItem.name, ElTimelineItem); 47 | app.component(ElDropdownItem.name, ElDropdownItem); 48 | app.component(ElDropdown.name, ElDropdown); 49 | app.component(ElCard.name, ElCard); 50 | app.component(ElTag.name, ElTag); 51 | app.component(ElIcon.name, ElIcon); 52 | app.component(ElCollapseTransition.name, ElCollapseTransition); 53 | 54 | app.config.globalProperties.$message = ElMessage; 55 | app.config.globalProperties.$loading = ElLoading.service; 56 | // app.config.globalProperties.productionTip = false; 57 | app.config.globalProperties.$https = service; 58 | app.config.globalProperties.$urls = urls; 59 | 60 | app.use(store, key) 61 | app.use(router) 62 | app.mount('#app'); 63 | -------------------------------------------------------------------------------- /src/mixins/index.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/vuejs/vue-class-component#using-mixins 2 | import { timestampToTime } from "../utils/utils"; 3 | 4 | const mixin = { 5 | methods: { 6 | formatTime(value: string | Date): string { 7 | return timestampToTime(value, true); 8 | } 9 | } 10 | }; 11 | export default mixin; 12 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; 2 | import HelloWorld from "../components/HelloWorld.vue"; 3 | import Home from "../views/Home.vue"; 4 | 5 | const routes: Array = [ 6 | { 7 | path: "/", 8 | name: "Home", 9 | component: Home, 10 | }, 11 | { 12 | path: "/helloWorld", 13 | name: "HelloWorld", 14 | component: HelloWorld, 15 | }, 16 | { 17 | path: "/articles", 18 | name: "articles", 19 | // route level code-splitting 20 | // this generates a separate chunk (articles.[hash].js) for this route 21 | // which is lazy-loaded when the route is visited. 22 | component: () => 23 | import(/* webpackChunkName: "articles" */ "../views/Articles.vue") 24 | }, 25 | { 26 | path: "/archive", 27 | name: "archive", 28 | component: () => 29 | import(/* webpackChunkName: "archive" */ "../views/Archive.vue") 30 | }, 31 | { 32 | path: "/timeline", 33 | name: "timeline", 34 | component: () => 35 | import(/* webpackChunkName: "timeline" */ "../views/Timeline.vue") 36 | }, 37 | { 38 | path: "/project", 39 | name: "project", 40 | component: () => 41 | import(/* webpackChunkName: "project" */ "../views/Project.vue") 42 | }, 43 | { 44 | path: "/message", 45 | name: "message", 46 | component: () => 47 | import(/* webpackChunkName: "message" */ "../views/Message.vue") 48 | }, 49 | { 50 | path: "/about", 51 | name: "about", 52 | component: () => 53 | import(/* webpackChunkName: "about" */ "../views/ArticleDetail.vue") 54 | }, 55 | { 56 | path: "/articleDetail", 57 | name: "articleDetail", 58 | component: () => 59 | import(/* webpackChunkName: "articleDetail" */ "../views/ArticleDetail.vue") 60 | } 61 | ]; 62 | 63 | const router = createRouter({ 64 | // history: createWebHistory(process.env.BASE_URL), 65 | history: createWebHistory(import.meta.env.BASE_URL), 66 | routes, 67 | }); 68 | 69 | export default router; 70 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | 2 | // import Vue from "vue"; 3 | // import VueRouter, { Route } from "vue-router"; 4 | 5 | declare module '*.vue' { 6 | import { DefineComponent } from 'vue' 7 | interface Vue { 8 | // $router: VueRouter; // 这表示this下有这个东西 9 | // $route: Route; 10 | $https: any; 11 | $urls: any; 12 | $Message: any; 13 | $Modal: any; 14 | } 15 | const component: DefineComponent<{}, {}, Vue, any> 16 | // const component: DefineComponent<{}, {}, any> 17 | export default component 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey } from 'vue' 2 | import { createStore, Store } from 'vuex' 3 | import user from "./modules/user"; 4 | 5 | export interface State { 6 | count: number 7 | } 8 | 9 | export const key: InjectionKey> = Symbol() 10 | 11 | export const store = createStore({ 12 | state() { 13 | return { 14 | count: 0 15 | } 16 | }, 17 | modules: { 18 | user 19 | }, 20 | mutations: { 21 | increment(state) { 22 | state.count++ 23 | } 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | import * as types from "../types"; 2 | 3 | const initPageState = () => { 4 | return { 5 | userInfo: { 6 | _id: "", 7 | name: "", 8 | avatar: "" 9 | } 10 | }; 11 | }; 12 | const user = { 13 | state: initPageState(), 14 | mutations: { 15 | [types.SAVE_USER](state: object | any, pageState: object | any) { 16 | for (const prop in pageState) { 17 | state[prop] = pageState[prop]; 18 | } 19 | } 20 | }, 21 | actions: {} 22 | }; 23 | 24 | export default user; 25 | -------------------------------------------------------------------------------- /src/store/types.ts: -------------------------------------------------------------------------------- 1 | 2 | // 用户 3 | export const SAVE_USER = "SAVE_USER"; 4 | 5 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | // 基础 2 | export interface Meta { 3 | views: number; 4 | likes: number; 5 | comments: number; 6 | } 7 | 8 | export interface ToUser { 9 | user_id: string; 10 | name: string; 11 | avatar: string; 12 | type: number; 13 | } 14 | export interface Params { 15 | keyword: string; 16 | pageNum: number; 17 | pageSize: number; 18 | } 19 | 20 | // 登录 21 | export interface LoginParams { 22 | email: string; 23 | password: string; 24 | } 25 | export interface UserInfo { 26 | _id: string; 27 | name: string; 28 | avatar: string | any; 29 | } 30 | export interface RegAndLogParams { 31 | email: string; 32 | name: string; 33 | password: string; 34 | phone: string; 35 | desc: string; 36 | } 37 | 38 | 39 | // 导航 nav 40 | export interface NavListItem { 41 | index: string; 42 | path: string; 43 | name: string; 44 | } 45 | 46 | 47 | 48 | // 文章归档 49 | export interface ParamsArchive { 50 | keyword: string; 51 | likes: string; // 是否是热门文章 52 | state: number; // 文章发布状态 => 0 草稿,1 已发布,'' 代表所有文章 53 | article: number; 54 | tag_id: string; 55 | category_id: string; 56 | pageNum: number; 57 | pageSize: number; 58 | } 59 | export interface ArchiveListItem { 60 | create_time: string; 61 | title: string; 62 | _id: string; 63 | } 64 | export interface ArchiveList { 65 | year: string; 66 | list: ArchiveListItem[]; 67 | } 68 | export interface ArchiveData { 69 | count: number; 70 | list: ArchiveList | any; 71 | } 72 | 73 | 74 | // 文章详情 75 | export interface OtherComments { 76 | content: string; 77 | create_time: string; 78 | likes: number; 79 | state: number; 80 | to_user: ToUser; 81 | user: ToUser; 82 | _id: string; 83 | } 84 | export interface Comments { 85 | article_id: string; 86 | content: string; 87 | create_time: string; 88 | id: number; 89 | is_handle: number; 90 | is_top: boolean; 91 | likes: number; 92 | other_comments: OtherComments[]; 93 | state: number; 94 | update_time: string; 95 | user: ToUser; 96 | user_id: string; 97 | __v: number; 98 | _id: string; 99 | } 100 | 101 | export interface ArticleDetailIF { 102 | toc: string; 103 | _id: string; 104 | author: string; 105 | category: Array; 106 | comments: Array; 107 | create_time: string; 108 | desc: string; 109 | content: string; 110 | id: number; 111 | img_url: string; 112 | numbers: number; 113 | keyword: Array; 114 | like_users: Array; 115 | meta: Meta; 116 | origin: number; 117 | state: number; 118 | tags: Array; 119 | title: string; 120 | update_time: string; 121 | } 122 | export interface ArticleDetailParams { 123 | id: string | string[]; 124 | type: number; 125 | } 126 | export interface LikeParams { 127 | id: string; 128 | user_id: string; 129 | } 130 | 131 | // 文章列表 132 | export interface ArticlesParams { 133 | keyword: string; 134 | likes: string; // 是否是热门文章 135 | state: number; // 文章发布状态 => 0 草稿,1 已发布,'' 代表所有文章 136 | tag_id: string; 137 | category_id: string; 138 | pageNum: number; 139 | pageSize: number; 140 | } 141 | 142 | export interface List { 143 | category: string[]; 144 | create_time: string; 145 | desc: string; 146 | img_url: string; 147 | meta: Meta; 148 | tags: string[]; 149 | title: string; 150 | _id: string; 151 | } 152 | export interface ArticlesData { 153 | count: number; 154 | list: List | any; 155 | } 156 | 157 | 158 | // 留言 159 | export interface MessageParams { 160 | email: string; 161 | phone: string; 162 | name: string; 163 | content: string; 164 | } 165 | export interface RulesItem { 166 | validator: Function; 167 | trigger: string; 168 | } 169 | export interface Rules { 170 | email: RulesItem[]; 171 | phone: RulesItem[]; 172 | name: RulesItem[]; 173 | content: RulesItem[]; 174 | } 175 | 176 | 177 | // 项目 178 | export interface ProjectList { 179 | content: string; 180 | end_time: string; 181 | img: string; 182 | start_time: string; 183 | title: string; 184 | url: string; 185 | _id: string; 186 | } 187 | export interface ProjectsData { 188 | count: number; 189 | list: ProjectList | any; 190 | } 191 | 192 | 193 | // 历程 194 | export interface TimelineList { 195 | content: string; 196 | end_time: string; 197 | start_time: string; 198 | title: string; 199 | state: number; 200 | _id: string; 201 | } 202 | export interface TimelinesData { 203 | count: number; 204 | list: TimelineList | any; 205 | } 206 | 207 | 208 | // 标签 209 | export interface TagList { 210 | name: string; 211 | _id: string; 212 | } 213 | export interface TagsData { 214 | count: number; 215 | list: TagList | any; 216 | } -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | interface Config { 2 | oauth_uri: string; 3 | redirect_uri: string; 4 | client_id: string; 5 | client_secret: string; 6 | } 7 | const config: Config = { 8 | oauth_uri: "https://github.com/login/oauth/authorize", 9 | redirect_uri: "https://biaochenxuying.cn/login", // 请修改成你的信息 10 | client_id: "XXXXXXXXX", // 请修改成你的信息 11 | client_secret: "XXXXXXXXX" // 请修改成你的信息 12 | }; 13 | 14 | // 本地开发环境下 (如下的信息在本地开发可以当测试用) 15 | if (import.meta.env.MODE === "development") { 16 | config.redirect_uri = "http://localhost:3001/login"; 17 | config.client_id = "502176cec65773057a9e"; 18 | config.client_secret = "65d444de381a026301a2c7cffb6952b9a86ac235"; 19 | } 20 | export default config; 21 | -------------------------------------------------------------------------------- /src/utils/https.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 2 | import { ElMessage } from "element-plus"; 3 | 4 | export interface ResponseData { 5 | code: number; 6 | data?: any; 7 | message: string; 8 | } 9 | 10 | 11 | // console.log('import.meta.env: ', import.meta.env); 12 | 13 | // 创建 axios 实例 14 | let service: AxiosInstance | any; 15 | if (import.meta.env.MODE === "development") { 16 | service = axios.create({ 17 | baseURL: "/api", // api 的 base_url 18 | timeout: 50000 // 请求超时时间 19 | }); 20 | } else { 21 | // 生产环境下 22 | service = axios.create({ 23 | baseURL: "/api", 24 | timeout: 50000 25 | }); 26 | } 27 | 28 | // request 拦截器 axios 的一些配置 29 | service.interceptors.request.use( 30 | (config: AxiosRequestConfig) => { 31 | return config; 32 | }, 33 | (error: any) => { 34 | // Do something with request error 35 | console.error("error:", error); // for debug 36 | Promise.reject(error); 37 | } 38 | ); 39 | 40 | // respone 拦截器 axios 的一些配置 41 | service.interceptors.response.use( 42 | (res: AxiosResponse) => { 43 | // Some example codes here: 44 | // code == 0: success 45 | if (res.status === 200) { 46 | const data: ResponseData = res.data 47 | if (data.code === 0) { 48 | return data.data; 49 | } else { 50 | ElMessage({ 51 | message: data.message, 52 | type: "error" 53 | }); 54 | } 55 | } else { 56 | ElMessage({ 57 | message: "网络错误!", 58 | type: "error" 59 | }); 60 | return Promise.reject(new Error(res.data.message || "Error")); 61 | } 62 | }, 63 | (error: any) => Promise.reject(error) 64 | ); 65 | 66 | export default service; 67 | -------------------------------------------------------------------------------- /src/utils/markdown.ts: -------------------------------------------------------------------------------- 1 | // https://www.cherylgood.cn/detail/5bdaf4722382b4646c27143b.html 2 | import highlight from 'highlight.js' 3 | import marked from 'marked' 4 | // const highlight = require("highlight.js"); 5 | // const marked = require("marked"); 6 | const tocObj = { 7 | add: function (text: any, level: any) { 8 | var anchor = `#toc${level}${++this.index}`; 9 | this.toc.push({ anchor: anchor, level: level, text: text }); 10 | return anchor; 11 | }, 12 | // 使用堆栈的方式处理嵌套的ul,li,level即ul的嵌套层次,1是最外层 13 | //
    14 | //
  • 15 | //
      16 | //
    • 17 | //
    18 | //
  • 19 | //
20 | toHTML: function () { 21 | let levelStack: any = []; 22 | let result = ""; 23 | const addStartUL = () => { 24 | result += '
    '; 25 | }; 26 | const addEndUL = () => { 27 | result += "
\n"; 28 | }; 29 | const addLI = (anchor: any, text: any) => { 30 | result += 31 | '
  • ' + text + "
  • \n"; 32 | }; 33 | 34 | this.toc.forEach(function (item: any) { 35 | let levelIndex = levelStack.indexOf(item.level); 36 | // 没有找到相应level的ul标签,则将li放入新增的ul中 37 | if (levelIndex === -1) { 38 | levelStack.unshift(item.level); 39 | addStartUL(); 40 | addLI(item.anchor, item.text); 41 | } // 找到了相应level的ul标签,并且在栈顶的位置则直接将li放在此ul下 42 | else if (levelIndex === 0) { 43 | addLI(item.anchor, item.text); 44 | } // 找到了相应level的ul标签,但是不在栈顶位置,需要将之前的所有level出栈并且打上闭合标签,最后新增li 45 | else { 46 | while (levelIndex--) { 47 | levelStack.shift(); 48 | addEndUL(); 49 | } 50 | addLI(item.anchor, item.text); 51 | } 52 | }); 53 | // 如果栈中还有level,全部出栈打上闭合标签 54 | while (levelStack.length) { 55 | levelStack.shift(); 56 | addEndUL(); 57 | } 58 | // 清理先前数据供下次使用 59 | this.toc = []; 60 | this.index = 0; 61 | return result; 62 | }, 63 | toc: [] as any, 64 | index: 0 65 | }; 66 | 67 | class MarkUtils { 68 | private rendererMD: any; 69 | 70 | constructor() { 71 | this.rendererMD = new marked.Renderer() as any; 72 | this.rendererMD.heading = function (text: any, level: any, raw: any) { 73 | var anchor = tocObj.add(text, level); 74 | return `${text}\n`; 75 | }; 76 | this.rendererMD.table = function (header: any, body: any) { 77 | return '' + header + body + '
    ' 78 | } 79 | highlight.configure({ useBR: true }); 80 | marked.setOptions({ 81 | renderer: this.rendererMD, 82 | headerIds: false, 83 | gfm: true, 84 | // tables: true, 85 | breaks: false, 86 | pedantic: false, 87 | sanitize: false, 88 | smartLists: true, 89 | smartypants: false, 90 | highlight: function (code: any) { 91 | return highlight.highlightAuto(code).value; 92 | } 93 | }); 94 | } 95 | 96 | async marked(data: any) { 97 | if (data) { 98 | let content = await marked(data); 99 | let toc = tocObj.toHTML(); 100 | return { content: content, toc: toc }; 101 | } else { 102 | return null; 103 | } 104 | } 105 | } 106 | 107 | const markdown: any = new MarkUtils(); 108 | 109 | export default markdown; 110 | -------------------------------------------------------------------------------- /src/utils/mockTest.ts: -------------------------------------------------------------------------------- 1 | // import Mock from "mockjs"; 2 | // import URLs from "./urls.js"; 3 | 4 | // const DEBUG = true; // debug 总开关 5 | // /** 6 | // * debug: false | true, 7 | // * data: {} 8 | // */ 9 | // const mockData: object = { 10 | // getLineList: { 11 | // debug: true, 12 | // data: { 13 | // status: "100", 14 | // message: "操作成功", 15 | // data: { 16 | // count: "@natural(10, 100)", 17 | // "list|7-10": [ 18 | // { 19 | // date: '@date("MMdd")', 20 | // ringRatio: "@natural(10, 50)", 21 | // rate: "@natural(-10, 50)", 22 | // name: "@city", 23 | // rank: "@natural(1, 50)", 24 | // value: "@natural(1, 100)" 25 | // } 26 | // ] 27 | // } 28 | // } 29 | // }, 30 | // getClassroomInformList: { 31 | // debug: true, 32 | // data: { 33 | // status: "100", 34 | // message: "操作成功", 35 | // data: { 36 | // count: "@natural(10, 100000)", 37 | // "list|1-10": [ 38 | // { 39 | // id: "@natural(10, 100000)", 40 | // classroom_id: "@natural(10, 100000)", 41 | // student_id: "@natural(10, 100000)", 42 | // student_name: "@string", 43 | // user_name: "@string", 44 | // student_url: "//www.baidu.com/img/bd_logo1.png", 45 | // head_portrait: "//www.baidu.com/img/bd_logo1.png", 46 | // assignment_type: "@natural(1, 2)", 47 | // own: "@natural(10, 100000)", 48 | // article: "@string", 49 | // score: "@natural(10, 100000)", 50 | // integral: "@natural(10, 100000)", 51 | // time: "@natural(10, 100000)", 52 | // task_time: "@natural(10, 100000)", 53 | // "comment|1-10": [ 54 | // { 55 | // id: "@natural(10, 100000)", 56 | // name: "@string", 57 | // comment: "@string" 58 | // } 59 | // ], 60 | // "seal|1-10": [ 61 | // { 62 | // id: "@natural(10, 100000)", 63 | // user_id: "@natural(10, 100000)", 64 | // name: "@string", 65 | // colour: "@string", 66 | // style: "@string" 67 | // } 68 | // ] 69 | // } 70 | // ] 71 | // } 72 | // } 73 | // } 74 | // }; 75 | 76 | // Mock.setup({ 77 | // timeout: 500 78 | // }); 79 | 80 | // const mockTest: Function = function(urls: object) { 81 | // if (!DEBUG) { 82 | // return false; 83 | // } 84 | // for (const key in mockData) { 85 | // if (mockData[key].debug === true && urls[key] !== undefined) { 86 | // Mock.mock(urls[key], mockData[key].data); 87 | // // console.log('urls[key]',urls[key]) 88 | // } 89 | // } 90 | // }; 91 | 92 | // mockTest(URLs); 93 | -------------------------------------------------------------------------------- /src/utils/urls.ts: -------------------------------------------------------------------------------- 1 | // url的链接 2 | export const urls: any = { 3 | login: "login", 4 | logout: "logout", 5 | register: "register", 6 | getUser: "getUser", 7 | 8 | addComment: "addComment", 9 | addThirdComment: "addThirdComment", 10 | getCommentList: "getCommentList", 11 | 12 | getArticleList: "getArticleList", 13 | likeArticle: "likeArticle", 14 | getArticleDetail: "getArticleDetail", 15 | 16 | addMessage: "addMessage", 17 | getMessageList: "getMessageList", 18 | getMessageDetail: "getMessageDetail", 19 | 20 | getLinkList: "getLinkList", 21 | 22 | getTagList: "getTagList", 23 | 24 | getCategoryList: "getCategoryList", 25 | 26 | getTimeAxisList: "getTimeAxisList", 27 | getTimeAxisDetail: "getTimeAxisDetail", 28 | 29 | getProjectList: "getProjectList", 30 | getProjectDetail: "getProjectDetail" 31 | }; 32 | 33 | export default urls; 34 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | // fn是我们需要包装的事件回调, delay是时间间隔的阈值 2 | export function throttle(fn: Function, delay: number) { 3 | // last为上一次触发回调的时间, timer是定时器 4 | let last = 0, 5 | timer: any = null; 6 | // 将throttle处理结果当作函数返回 7 | return function () { 8 | const that: any = (this as any); 9 | // 保留调用时的this上下文 10 | let context = that; 11 | // 保留调用时传入的参数 12 | let args = arguments; 13 | // 记录本次触发回调的时间 14 | let now = +new Date(); 15 | 16 | // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值 17 | if (now - last < delay) { 18 | // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器 19 | clearTimeout(timer); 20 | timer = setTimeout(function () { 21 | last = now; 22 | fn.apply(context, args); 23 | }, delay); 24 | } else { 25 | // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应 26 | last = now; 27 | fn.apply(context, args); 28 | } 29 | }; 30 | } 31 | 32 | export function setCookie(cName: string, value: any, expiredays: any) { 33 | if (expiredays > 0 && expiredays !== "100") { 34 | let exdate = new Date(); 35 | exdate.setDate(exdate.getDate() + expiredays); 36 | document.cookie = 37 | cName + 38 | "=" + 39 | escape(value) + 40 | // (expiredays == null ? '' : ';expires=' + exdate.toGMTString()); 41 | (expiredays == null ? "" : ";expires=" + exdate.toUTCString()); 42 | } 43 | if (expiredays === "100") { 44 | let exdate = new Date("2118-01-01 00:00:00"); 45 | document.cookie = 46 | cName + 47 | "=" + 48 | escape(value) + 49 | // (expiredays == null ? '' : ';expires=' + exdate.toGMTString()); 50 | (expiredays == null ? "" : ";expires=" + exdate.toUTCString()); 51 | } 52 | } 53 | export function getCookie(cName: string) { 54 | if (document.cookie.length > 0) { 55 | let cStart = document.cookie.indexOf(cName + "="); 56 | if (cStart !== -1) { 57 | cStart = cStart + cName.length + 1; 58 | let cEnd = document.cookie.indexOf(";", cStart); 59 | if (cEnd === -1) cEnd = document.cookie.length; 60 | return unescape(document.cookie.substring(cStart, cEnd)); 61 | } 62 | } 63 | return ""; 64 | } 65 | 66 | export function delCookie(name: string) { 67 | let exp = new Date(); 68 | exp.setTime(exp.getTime() - 1); 69 | let cval = getCookie(name); 70 | if (cval != null) 71 | // document.cookie = name + '=' + cval + ';expires=' + exp.toGMTString(); 72 | document.cookie = name + "=" + cval + ";expires=" + exp.toUTCString(); 73 | } 74 | 75 | //清除cookie 76 | export function clearCookie(name: string) { 77 | setCookie(name, "", -1); 78 | } 79 | 80 | //获取QueryString的数组 81 | export function getQueryString() { 82 | let result = window.location.search.match( 83 | new RegExp("[?&][^?&]+=[^?&]+", "g") 84 | ); 85 | if (result == null) { 86 | return ""; 87 | } 88 | for (let i = 0; i < result.length; i++) { 89 | result[i] = result[i].substring(1); 90 | } 91 | return result; 92 | } 93 | //根据 QueryString 参数名称获取值 94 | export function getQueryStringByName(name: string) { 95 | let result = window.location.search.match( 96 | new RegExp("[?&]" + name + "=([^&]+)", "i") 97 | ); 98 | if (result == null || result.length < 1) { 99 | return ""; 100 | } 101 | return result[1]; 102 | } 103 | //获取页面顶部被卷起来的高度 104 | export function getScrollTop() { 105 | return Math.max( 106 | //chrome 107 | document.body.scrollTop, 108 | //firefox/IE 109 | document.documentElement.scrollTop 110 | ); 111 | } 112 | //获取页面文档的总高度 113 | export function getDocumentHeight() { 114 | //现代浏览器(IE9+和其他浏览器)和IE8的document.body.scrollHeight和document.documentElement.scrollHeight都可以 115 | return Math.max( 116 | document.body.scrollHeight, 117 | document.documentElement.scrollHeight 118 | ); 119 | } 120 | //页面浏览器视口的高度 121 | export function getWindowHeight() { 122 | return document.compatMode === "CSS1Compat" 123 | ? document.documentElement.clientHeight 124 | : document.body.clientHeight; 125 | } 126 | //// 时间 格式化成 2018-12-12 12:12:00 127 | export function timestampToTime(timestamp: Date | any, dayMinSecFlag: boolean) { 128 | const date = new Date(timestamp); 129 | const Y = date.getFullYear() + "-"; 130 | const M = 131 | (date.getMonth() + 1 < 10 132 | ? "0" + (date.getMonth() + 1) 133 | : date.getMonth() + 1) + "-"; 134 | const D = 135 | date.getDate() < 10 ? "0" + date.getDate() + " " : date.getDate() + " "; 136 | const h = 137 | date.getHours() < 10 ? "0" + date.getHours() + ":" : date.getHours() + ":"; 138 | const m = 139 | date.getMinutes() < 10 140 | ? "0" + date.getMinutes() + ":" 141 | : date.getMinutes() + ":"; 142 | const s = 143 | date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds(); 144 | if (!dayMinSecFlag) { 145 | return Y + M + D; 146 | } 147 | return Y + M + D + h + m + s; 148 | } 149 | 150 | //判断是移动端还是 pc 端 ,true 表示是移动端,false 表示是 pc 端 151 | export function isMobileOrPc() { 152 | if (/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) { 153 | return true; 154 | } else { 155 | return false; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/views/Archive.vue: -------------------------------------------------------------------------------- 1 | 31 | 91 | 112 | 113 | -------------------------------------------------------------------------------- /src/views/ArticleDetail.vue: -------------------------------------------------------------------------------- 1 | 110 | 338 | 442 | 443 | -------------------------------------------------------------------------------- /src/views/Articles.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 1373 | 1374 | 1482 | 1483 | -------------------------------------------------------------------------------- /src/views/Message.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 196 | 201 | -------------------------------------------------------------------------------- /src/views/Project.vue: -------------------------------------------------------------------------------- 1 | 39 | 114 | 131 | 132 | -------------------------------------------------------------------------------- /src/views/Timeline.vue: -------------------------------------------------------------------------------- 1 | 27 | 100 | 120 | 121 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | // 忽略 this 的类型检查, Raise error on this expressions with an implied any type. 10 | "noImplicitThis": false, 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "lib": ["esnext", "dom"], 14 | "types": ["vite/client"] 15 | }, 16 | "include": ["/src/**/*.ts", "/src/**/*.d.ts", "/src/**/*.tsx", "/src/**/*.vue"], 17 | // ts 排除的文件 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import styleImport from 'vite-plugin-style-import' 4 | import path from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | styleImport({ 11 | libs: [ 12 | { 13 | libraryName: 'element-plus', 14 | esModule: true, 15 | ensureStyleFile: true, 16 | resolveStyle: (name) => { 17 | return `element-plus/lib/theme-chalk/${name}.css`; 18 | }, 19 | resolveComponent: (name) => { 20 | return `element-plus/lib/${name}`; 21 | }, 22 | } 23 | ] 24 | }) 25 | ], 26 | 27 | /** 28 | * 在生产中服务时的基本公共路径。 29 | * @default '/' 30 | */ 31 | base: './', 32 | /** 33 | * 与“根”相关的目录,构建输出将放在其中。如果目录存在,它将在构建之前被删除。 34 | * @default 'dist' 35 | */ 36 | // outDir: 'dist', 37 | server: { 38 | // hostname: '0.0.0.0', 39 | host: "localhost", 40 | port: 3001, 41 | // // 是否自动在浏览器打开 42 | // open: true, 43 | // // 是否开启 https 44 | // https: false, 45 | // // 服务端渲染 46 | // ssr: false, 47 | proxy: { 48 | '/api': { 49 | target: 'http://localhost:3333/', 50 | changeOrigin: true, 51 | ws: true, 52 | rewrite: (pathStr) => pathStr.replace('/api', '') 53 | }, 54 | }, 55 | }, 56 | resolve: { 57 | // 导入文件夹别名 58 | alias: { 59 | '@': path.resolve(__dirname, './src'), 60 | views: path.resolve(__dirname, './src/views'), 61 | components: path.resolve(__dirname, './src/components'), 62 | utils: path.resolve(__dirname, './src/utils'), 63 | less: path.resolve(__dirname, "./src/less"), 64 | assets: path.resolve(__dirname, "./src/assets"), 65 | com: path.resolve(__dirname, "./src/components"), 66 | store: path.resolve(__dirname, "./src/store"), 67 | mixins: path.resolve(__dirname, "./src/mixins") 68 | }, 69 | } 70 | }) 71 | --------------------------------------------------------------------------------