创建项目目录 1 2 cd 项目根目录vue init webpack 客户端项目目录
例如,我要把项目保存在 ~/Desktop/renran
目录中,可以如下操作:
1 2 cd ~/Desktop/renranvue init webpack renran_pc
这次在选择 vue-router 时,我们可以选择直接安装。
1 2 3 4 5 6 7 8 9 10 ? Project name renran_pc ? Project description A Vue.js project ? Author Shuo Liu <liushuo432@outlook.com> ? Vue build standalone ? Install vue-router? Yes ? Use ESLint to lint your code? No ? Set up unit tests No ? Setup e2e tests with Nightwatch? No ? Should we run `npm install` for you after the project has been created? (recom mended) npm
把 vue 项目构建好了,运行测试服务器。
打开项目已经,在 Python 的终端下运行 vue 项目,查看效果。
接下来,我们根据终端上效果显示的对应地址来访问项目(如果有多个vue项目在运行,8080端口被占据了,服务器会自动改端口,所以根据自己实际在操作中看到的地址来访问)。
访问:http://localost:8080
,效果:
我们也可以把我们的前端项目进行 git 源代码管。
初始化前端项目 清除默认的 HelloWorld 组件和 APP.vue
中的默认样式
还有 router 中导入进来的组件:
接下来,我们可以查看效果了,一张白纸~
安装路由 vue-router 下载路由组件 如果前面没有选择安装 vue-router,则使用以下命令安装路由组件:
执行效果:
配置路由 初始化路由对象 在 src 目录下创建 routers 路由目录,在 routers 目录下创建 index.js
路由文件
index.js
路由文件中,编写初始化路由对象的代码 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Vue from "vue" import Router from "vue-router" Vue.use(Router); export default new Router({ mode : "history" , routes :[ { path : '/' , } ] })
注册路由信息 打开 main.js
文件,把 router 对象注册到 vue中。代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import Vue from 'vue' import App from './App' import router from './routers/index' ;Vue.config.productionTip = false new Vue({ el : '#app' , router, components : { App }, template : '<App/>' });
在视图中显示路由对应的内容 在 App.vue
组件中,添加显示路由对应的内容。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <div id="app"> <router-view/> </div> </template> <script> export default { name: 'App', components: { } } </script> <style> </style>
创建并提供前端首页的组件 routers/index.js
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import Vue from "vue" import Router from "vue-router" import Home from "../components/Home" Vue.use(Router); export default new Router({ mode : "history" , routes :[ { path :"/" , name :"Home" , component :Home, }, ] })
创建 Home 组件 components/Home.vue
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <div id="home"> 前端首页 </div> </template> <script> export default { name:"Home", data(){ return { } } } </script> <style scoped> </style>
前端初始化全局变量和全局方法 在 src 目录下创建 settings.js
站点开发配置文件:
1 2 3 export default { Host :"http://127.0.0.1" , }
在 main.js
中引入新创建的 settings.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false import settings from "./settings" ;Vue.prototype.$settings = settings new Vue({ el : '#app' , router, components : { App }, template : '<App/>' })
引入 ElementUI 先在 renran_pc 目录下安装 ElementUI,使用命令:
上面的命令等同于
1 npm install element-ui --save
执行命令效果:
配置 ElementUI 到项目中 在 main.js
中导入 ElementUI,并调用。代码:
1 2 3 4 5 import ElementUI from 'element-ui' ;import "element-ui/lib/theme-chalk/index.css" ;Vue.use(ElementUI);
效果:
首页代码导入 成功引入了 ElementUI 以后,接下来我们就可以开始进入前端页面开发,首先是首页。
接下来我们把之前完成的首页,直接拿过来使用(注意除了组件以外,还有静态文件也需要拿过来,包括 App.vue
里面的公共样式),并运行项目。
创建 static/css/reset.css
,全局 css 初始化代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 body ,h1 ,h2 ,h3 ,h4 ,h5 ,ul ,p { padding : 0 ; margin :0 ; font-weight : normal; } a { text-decoration : none; color : #4a4a4a ; } a :hover { color : #000 ; } input { outline : none; } ul { list-style : none; } img { width : 100% ; } .header .el-menu li .el-submenu__title { height : 26px !important ; line-height : 26px !important ; } .el-menu--popup { min-width : 140px ; } .el-checkbox__inner { width :16px ; height : 16px ; border : 1px solid #999 ; } .el-checkbox__inner :after{ width: 6px ; height : 8px ; } .el-form-item__content { margin-left :0px !important ; width : 50px ; } .full-left { float : left; } .full-right { float : right; }
main.js
中将重置样式的 reset.css
引入进来:
1 2 3 4 5 6 ... Vue.use(ElementUI); import "../static/css/reset.css" ;
Home.vue
中添加首页模板代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 <template> <div id="home"> <Header></Header> <div class="container"> <div class="row"> <div class="main"> <!-- Banner --> <div class="banner"> <el-carousel height="272px" indicator-position="none" :interval="2000"> <el-carousel-item v-for="item in 4" :key="item"> <h3 class="small">{{ item }}</h3> </el-carousel-item> </el-carousel> </div> <div id="list-container"> <!-- 文章列表模块 --> <ul class="note-list"> <li class=""> <div class="content"> <a class="title" target="_blank" href="">常做此运动,让你性福加倍</a> <p class="abstract">运动,是人类在发展过程中有意识地对自己身体素质的培养的各种活动 运动的方式多种多样 不仅仅是我们常知的跑步,球类,游泳等 今天就为大家介绍一种男...</p> <div class="meta"> <span class="jsd-meta"> <img src="/static/image/paid1.svg" alt=""> 4.8 </span> <a class="nickname" target="_blank" href="">上班族也健身</a> <a target="_blank" href=""> <img src="/static/image/comment.svg" alt=""> 4 </a> <span><img src="/static/image/like.svg" alt=""> 31</span> </div> </div> </li> <li class="have-img"> <a class="wrap-img" href="" target="_blank"> <img class="img-blur-done" src="/static/image/10907624-107943365323e5b9.jpeg" /> </a> <div class="content"> <a class="title" target="_blank" href="">“不耻下问”,正在毁掉你的人生</a> <p class="abstract"> 在过去,遇到不懂的问题,你不耻下问,找个人问问就行;在现在,如果你还这么干,多半会被认为是“搜商低”。 昨天,35岁的表姐把我拉黑了。 表姐是医... </p> <div class="meta"> <span class="jsd-meta"> <img src="/static/image/paid1.svg" alt=""> 6.7 </span> <a class="nickname" target="_blank" href="">_飞鱼</a> <a target="_blank" href=""> <img src="/static/image/comment.svg" alt=""> 33 </a> <span><img src="/static/image/like.svg" alt=""> 113</span> <span><img src="/static/image/shang.svg" alt=""> 2</span> </div> </div> </li> </ul> <!-- 文章列表模块 --> </div> <a href="" class="load-more">阅读更多</a></div> <div class="aside"> <!-- 推荐作者 --> <div class="recommended-author-wrap"> <!----> <div class="recommended-authors"> <div class="title"> <span>推荐作者</span> <a class="page-change"><img class="icon-change" src="/static/image/exchange-rate.svg" alt="">换一批</a> </div> <ul class="list"> <li> <a href="" target="_blank" class="avatar"> <img src="/static/image/avatar.webp" /> </a> <a class="follow" state="0"><img src="/static/image/follow.svg" alt="" />关注</a> <a href="" target="_blank" class="name">董克平日记</a> <p>写了807.1k字 · 2.5k喜欢</p> </li> <li> <a href="" target="_blank" class="avatar"> <img src="/static/image/avatar.webp" /> </a> <a class="follow" state="0"><img src="/static/image/follow.svg" alt="" />关注</a> <a href="" target="_blank" class="name">董克平日记</a> <p>写了807.1k字 · 2.5k喜欢</p> </li> </ul> <a href="" target="_blank" class="find-more">查看全部 ></a> <!----> </div> </div> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header"; import Footer from "./common/Footer"; export default { name:"Home", data(){ return { } }, components:{ Header, Footer, } } </script> <style scoped> .container{ width: 960px; margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px; box-sizing: border-box; } .container:after, .container:before { content: " "; display: table; } .row { margin-left: -15px; margin-right: -15px; } .row:after, .row:before { content: " "; display: table; } .main { padding-top: 30px; padding-right: 0; position: relative; min-height: 1px; padding-left: 15px; width: 66.66667%; float: left; box-sizing: border-box; } .main .banner{ width: 640px; height: 272px; } .note-list { margin: 0; padding: 0; list-style: none; } .note-list li { position: relative; width: 100%; margin: 0 0 15px; padding: 15px 2px 20px 0; border-bottom: 1px solid #f0f0f0; word-wrap: break-word; line-height: 20px; } .note-list li.have-img { min-height: 140px; } .note-list .have-img .wrap-img { position: absolute; top: 50%; margin-top: -60px; right: 0; width: 150px; height: 100px; } .note-list .have-img .wrap-img img { width: 100%; height: 100%; border-radius: 4px; border: 1px solid #f0f0f0; vertical-align: middle; } .main .note-list .have-img .content { padding-right: 165px; box-sizing: border-box; } .note-list .title { margin: -7px 0 4px; display: inherit; font-size: 18px; font-weight: 700; line-height: 1.5; color: #333; } .note-list .title:hover{ text-decoration: underline; } .note-list .abstract { margin: 0 0 8px; font-size: 13px; line-height: 24px; color: #999; } .note-list .meta { padding-right: 0!important; font-size: 12px; font-weight: 400; line-height: 20px; } .note-list .meta span { margin-right: 10px; color: #b4b4b4; } .jsd-meta { color: #ea6f5a!important; } .note-list .meta a, .note-list .meta a:hover { transition: .1s ease-in; } .note-list .meta a { margin-right: 10px; color: #b4b4b4; } .note-list .meta img{ width: 15px; vertical-align: middle; } .main .load-more { width: 100%; border-radius: 20px; background-color: #a5a5a5; margin: 30px auto 60px; padding: 10px 15px; text-align: center; font-size: 15px; color: #fff; display: block; line-height: 1.42857; box-sizing: border-box; } .main .load-more:hover { background-color: #9b9b9b; } .aside { padding: 30px 0 0; margin-left: 4.16667%; width: 29.16667%; float: left; position: relative; min-height: 1px; box-sizing: border-box; } .recommended-authors { margin-bottom: 20px; padding-top: 0; font-size: 13px; text-align: center; } .recommended-authors .title { text-align: left; } .recommended-authors .title span { font-size: 14px; color: #969696; } .recommended-authors .title .page-change { float: right; display: inline-block; font-size: 16px; color: #969696; } .icon-change{ width: 16px; vertical-align: middle; } .recommended-authors .list { margin: 0 0 20px; text-align: left; list-style: none; } .recommended-authors .list li { margin-top: 15px; line-height: 20px; } .recommended-authors .list .avatar { float: left; width: 48px; height: 48px; margin-right: 10px; } .avatar { width: 24px; height: 24px; display: block; cursor: pointer; } .avatar img { width: 100%; height: 100%; border: 1px solid #ddd; border-radius: 50%; } .follow{ font-size: 14px; color: #42c02e; border-color: #42c02e; font-weight: 400; line-height: normal; } .follow img{ width: 14px; } .recommended-authors .list .follow, .recommended-authors .list .follow-cancel, .recommended-authors .list .follow-each, .recommended-authors .list .following { float: right; margin-top: 5px; padding: 0; font-size: 13px; color: #42c02e; box-sizing: border-box; } .recommended-authors .list .name { padding-top: 5px; margin-right: 60px; font-size: 14px; display: block; box-sizing: border-box; } .recommended-authors .list p { font-size: 12px; color: #969696; margin: 0 0 10px; box-sizing: border-box; } .recommended-authors .find-more { position: absolute; padding: 7px 7px 7px 12px; left: 0; width: 100%; font-size: 13px; color: #787878; background-color: #f7f7f7; border: 1px solid #dcdcdc; border-radius: 4px; } .row:after { clear: both; } .el-carousel__item h3 { color: #475669; font-size: 14px; opacity: 0.75; line-height: 150px; margin: 0; } .el-carousel__item:nth-child(2n) { background-color: #99a9bf; } .el-carousel__item:nth-child(2n+1) { background-color: #d3dce6; } </style>
将 iconfont.css
和 iconfont.eot
两个图标文件放到 static/css
目录下,并在 main.js
将它们引入进来:
1 2 3 4 5 6 7 8 import "../static/css/reset.css" ;import "../static/css/iconfont.css" import "../static/css/iconfont.eot"
创建 components/common/Header.vue
头部组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 <template> <div class="header"> <nav class="navbar"> <div class="width-limit"> <!-- 左上方 Logo --> <a class="logo" href="/"><img src="/static/image/nav-logo.png" /></a> <!-- 右上角 --> <!-- 未登录显示登录/注册/写文章 --> <a class="btn write-btn" target="_blank" href="/writer"><img class="icon-write" src="/static/image/write.svg">写文章</a> <a class="btn sign-up" id="sign_up" href="/register">注册</a> <a class="btn log-in" id="sign_in" href="/login">登录</a> <div class="container"> <div class="collapse navbar-collapse" id="menu"> <ul class="nav navbar-nav"> <li class="tab active"> <a href="/"> <img class="menu-icon" src="/static/image/menu.svg"> <span class="menu-text">首页</span> </a> </li> <li class="search"> <form target="_blank" action="/search" accept-charset="UTF-8" method="get"> <input type="text" name="q" id="q" value="" autocomplete="off" placeholder="搜索" class="search-input"> <a class="search-btn" href="javascript:void(0)"></a> </form> </li> </ul> </div> </div> <!-- 如果用户登录,显示下拉菜单 --> </div> </nav> </div> </template> <script> export default { name: "Header" } </script> <style scoped> .header{ height: 56px; } .container { width: 960px; margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px; } .container:after, .container:before { content: " "; display: table; } .container:after { clear: both; } .navbar { background-color: #fff; border-color: #f0f0f0; top: 0; border-width: 0 0 1px; border-radius: 0; } .navbar-nav { float: left; margin: 0; } .navbar:after, .navbar:before { content: " "; display: table; box-sizing: border-box; } .nav:after, .nav:before { content: " "; display: table; } nav .width-limit { min-width: 768px; max-width: 1440px; margin: 0 auto; } nav .logo { float: left; height: 56px; padding: 0; } nav .logo img { height: 100%; vertical-align: middle; border: 0; } .btn { display: inline-block; margin-bottom: 0; font-weight: 400; text-align: center; vertical-align: middle; touch-action: manipulation; cursor: pointer; background-image: none; border: 1px solid transparent; white-space: nowrap; padding: 6px 12px; font-size: 14px; line-height: 1.42857; border-radius: 4px; } nav .write-btn { float: right; width: 100px; height: 24px; line-height: 24px; margin: 8px 12px 0; border-radius: 20px; font-size: 15px; color: #fff; background-color: #ea6f5a; text-decoration: none; } nav .log-in, nav .log-in:hover { color: #969696; } nav .log-in { float: right; margin: 11px 6px 0 10px; font-size: 15px; } nav .sign-up { float: right; width: 80px; height: 24px; line-height: 24px; margin: 9px 5px 0 15px; border: 1px solid rgba(236,97,73,.7); border-radius: 20px; font-size: 15px; color: #ea6f5a; background-color: transparent; } nav .icon-write { margin-right: 3px; width: 19px; height: 19px; vertical-align: middle; } nav .menu-text{ font-size: 17px; color: #ea6f5a; } nav .menu-icon { width: 20px; height: 20px; vertical-align: sub; margin-right: 3px; } nav .nav .tab a { height: 56px; line-height: 26px; padding: 15px; color: #ea6f5a; background: none; } nav .navbar-nav li { margin-right: 10px; float: left; position: relative; display: block; box-sizing: border-box; height: 56px; line-height: 56px; } .navbar-nav { float: left; margin: 0; } nav form { position: relative; top: 9px; margin: 0 0 20px; box-sizing: border-box; line-height: 20px; } nav form .search-input { padding: 0 40px 0 20px; height: 38px; font-size: 14px; border: 1px solid #eee; border-radius: 40px; background: #eee; transition: width .5s; width: 240px; outline: none; } nav form .search-input:focus { width: 320px; outline: none; } .navbar-default .navbar-collapse, .navbar-default .navbar-form { border-color: #e7e7e7; padding-left: 0; padding-right: 0; box-sizing: border-box; width: auto; border-top: 0; box-shadow: none; } .navbar { background-color: #fff; top: 0; border-radius: 0; position: fixed; right: 0; left: 0; z-index: 1030; min-height: 50px; margin-bottom: 20px; border-bottom: 1px solid #f0f0f0; } nav { height: 56px; } .navbar:after, .navbar:before { content: " "; display: table; } nav form .search-btn { position: absolute; display: block; top: 0; right: 10px; width: 30px; height: 30px; padding: 0; margin: 5px -1px 0 0; background: transparent url("../../../static/image/search-focus.svg") no-repeat 6px 6px; background-size: 20px; } nav form .search-input:focus~a{ border-radius: 50%; background-color: #696969; background-image: url("../../../static/image/search-blur.svg"); } nav .sign-up:hover { color: #ec6149; border-color: #ec6149; background-color: rgba(236,97,73,.05); } nav .write-btn:focus, nav .write-btn:hover { color: #fff; background-color: #ec6149; } </style>
再创建 components/common/Footer.vue
脚部组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <template> <footer class="container"> <div class="row"> <div class="main"> <a target="_blank" href="">关于荏苒</a> <em> · </em> <a target="_blank" href="">联系我们</a> <em> · </em> <a target="_blank" href="">加入我们</a> <em> · </em> <a target="_blank" href="">帮助中心</a> <em> · </em> <a target="_blank" href="http://www.jianshu.com/p/cabc8fa39830">合作伙伴</a> <div class="icp">©2016-2019 广州荏苒信息科技有限公司 / 荏苒 / 粤ICP备16018329号-5 /</div> </div> </div> </footer> </template> <script> export default { name: "Footer" } </script> <style scoped> .container { width: 960px; margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px; margin-bottom: 20px; box-sizing: border-box; } .container:after, .container:before { content: " "; display: table; } footer .row { padding-top: 25px; box-sizing: border-box; margin-left: -15px; margin-right: -15px; } footer .main { padding-right: 0; font-size: 13px; color: #969696; width: 70.83333%; } footer .icp, footer .icp a { color: #c8c8c8; } footer .icp { margin-top: 10px; font-size: 12px; } footer .main a { color: #969696; display: inline-block; } .row:after { clear: both; } </style>
然后将图片添加到 static/images
目录下,即可初见成效:
编写开发文档 使用 git 提交代码修改后,即可编写今天的开发文档,在 renran/docs
目录下,创建名为 开发文档.md
的文件。参考内容如下:
2020-02-27 版本:8bf86c5 开发人员:刘硕
提交版本代码需要修复 2 个 BUG
因为我们用的是 Django 2.2.0 版本,执行数据库迁移命令会导致错误,所以需要修改一下源代码。
数据库版本检测导致的错误:
数据库的版本检测代码注释掉:
重启项目,又出现了另一个关于 bytes 转换编码的问题,错误如下:
第二个错误也是因为数据库版本的默认编码导致,query 返回的内容格式使用有误。
修改一行代码,把 query 查询结果转换格式改为 bytes类型
1 2 3 query = query.decode(errors='replace' ) query = query.encode(errors='replace' )
执行数据库迁移指令:
1 2 python manage.py makemigrations python manage.py migrate