本文是 Django 全栈开发教程的第四篇
目录在这里,已经更新的文章如下
本文需要完成三件事情:
国产框架 + 语法简洁是我入坑 VueJS 初衷。
后来却是 Vue 的丰富的生态和简洁的语法吸引了继续用下去。
这里要感谢为 VueJS 持续贡献代码的人,从 Vue 本身,到 VueCLI, 到 Router, 到 VueX, 如果没有那么多人为之贡献代码,可能今天这一小节就变成了,『为什么是 React 了』逃。
Vue 自称为 Vue 渐进式 JavaScript 框架。
什么是渐进式?
就是你可以逐步按照 Vue 的方式逐渐引入一些 Vue 的组件到项目中。没有必要上来就是 Vue 全家桶,依据场景逐步引入。
参考链接 https://www.zhihu.com/question/51907207
然而,依据我的经验,vue 全家桶用起来还是很舒服的。这里必须要感谢 Vue 社区。
模板语法,数据驱动,双向绑定。写起代码来简直就是一个字,爽。
从项目角度,我们想想前端项目有哪些地方是需要注意的:
限于篇幅,我就不一一讲解了。挑在 YaDjangoBlog 中使用到的技术简单介绍一下。
再次感谢 Vue 社区出品的 VueCLI 以及 Webpack 模板。
下面依次介绍:
首先,YaDjangoBlog 文件的前端目录如下:
.
├── README.md
├── build
│ ├── build.js
│ ├── build_iconfont.js # 构建 iconfont 脚本
│ ├── check-versions.js
│ ├── logo.png
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js # 增加了 AutoDllPlugin 用于自动打包 DLL
│ └── webpack.prod.conf.js
├── config
│ ├── dev.env.js # 可以在这里添加开发环境的环境变脸
│ ├── index.js
│ ├── prod.env.js
│ └── test.env.js
├── extra
│ └── svg-icon # 这里存放需要生成 iconfont 的字体文件。
├── index.html
├── package.json # 这里添加了一些构建脚本
├── packages
│ └── theme-future # 注意,这里是另一个子项目,使用 Gulp 构建的纯 CSS 子项目。
├── src
│ ├── App.vue
│ ├── api # 对 axios 进行初步封装
│ ├── assets # 从使用 Gulp 生成的 CSS 可以放在这里。
│ ├── components # 跨页面的组件放在这里
│ ├── directives # 指令
│ ├── filters # 过滤器
│ ├── main.js # 初始化 Vue 实例
│ ├── pages # 页面
│ ├── router # 路由
│ ├── store # vuex
│ └── utils # 常用工具类
├── static
│ ├── hightlight # hightlight 脚本
│ ├── iconfont # 本地构建的 iconfont
│ ├── images
│ └── js
├── test # 没写测试,大家开源项目不要学我.... 逃
│ ├── e2e
│ └── unit
└── yarn.lock
静态资源管理,主要涉及到 JS/CSS/ 图片 / 字体
首先,由于使用了 VueCli 的模板,所以大可以按照 VueCli 提供的写法来写。
<template>
<div id="app">
<header></header>
<router-view />
<footer></footer>
</div>
</template>
<script>
export default {
name: "app",
components: {
Header: () => import("./pages/commons/Header.vue"),
Footer: () => import("./pages/commons/Footer.vue"),
},
}
</script>
<style lang="scss">
$primary-color: #37b24d;
$dark-color: #2b5732;
$body-bg: #f9f9f9;
@import "~spectre.css/src/spectre-icons.scss";
@import "~spectre.css/src/spectre.scss";
@import "~spectre.css/src/spectre-exp.scss";
@import "./assets/theme-future/index.css";
a {
&:focus,
&:hover,
&:active,
&.active {
text-decoration: none;
box-shadow: 0 0 0 0;
}
}
#app {
}
</style>
依据我个人经验,做了一部分的微调:
第一 在代码中新建一个主题 CSS, 单独用于处理 SCSS 编译 CSS. 即除了 App.vue, 其他地方的 CSS 直接写在同一个地方。
第二 对于字体文件,不引入 IconFont 在线字体,而是使用 SVG 本地编译字体。这样减少对 iconfont cdn 的依赖,可以以后直接迁移这个字体到其他 CDN 上。
第三 对于依赖库管理,分为 npm 依赖库和外部 JS 依赖库两种
对于 NPM 依赖库,如果有使用过 ECharts3.0 的 SPA 开发者应该对于万恶的 DLL 非常熟悉了。最早的时候,我们是这样做的:
这种恶心的配置随着 autodll-webpack-plugin 的出现从而得到缓解,于是现在的你只需要配置:
new AutoDllPlugin({
inject: true, // will inject the DLL bundles to index.html
debug: true,
filename: '[name]_[hash].js',
entry: {
vendor: [
'@antv/data-set',
'@antv/g2',
'@antv/g6',
'highlight.js',
'markdown-it',
'markdown-it-abbr',
'markdown-it-deflist',
'markdown-it-emoji',
'markdown-it-footnote',
'markdown-it-ins',
'markdown-it-katex',
'markdown-it-mark',
'markdown-it-sub',
'markdown-it-sup',
'markdown-it-task-lists',
'markdown-it-toc-and-anchor',
'typed.js',
'vue',
'vue-router',
'vuex'
]
},
plugins: [new webpack.optimize.UglifyJsPlugin()],
})
当然,如果你用了 ECharts, 有的时候会出现莫名其妙的
__DEV__ is not defined
解决方法就是在这上面的插件里面加个插件定义一个 Global 的变量
new webpack.DefinePlugin({
__DEV__: false
}),
PS: 去年的版本由于依赖库的一个路径问题导致 autodll-webpack-plugin 不能在 Windows 上使用,今年可以啦。还不快快用起来?
对于外部的 JS/CSS 依赖库:
博客项目,实际上路由比较简单。
export default new Router({
mode: 'history',
base: '/',
// 注释掉这里是因为和引入的 smooth-scroll 冲突
// scrollBehavior (to, from, savedPosition) {
// return { x: 0, y: 0 }
// },
routes: [
{
path: '/',
name: 'home',
component: () => import('../pages/Home.vue')
},
// 解决手贱带来的问题
{
path: '/index:suffix*',
name: 'index',
component: () => import('../pages/Home.vue')
},
{
path: '/blog',
name: 'blog',
component: () => import('../pages/Blog.vue')
},
{
path: '/blog/post/:title',
name: 'post',
component: () => import('../pages/Blog/ArticlePost.vue')
},
{
path: '/blog/:category(category/\\d+)?/:tags(tags/\\d+)?/:page(page/\\d+)?',
name: 'blogposts',
component: () => import('../pages/Blog.vue')
},
{
path: '/archive',
name: 'archive',
component: () => import('../pages/Archive.vue')
},
{
path: '/gallery',
name: 'gallery',
component: () => import('../pages/Gallery.vue')
},
{
path: '/works',
name: 'works',
component: () => import('../pages/Works.vue')
},
{
path: '/about',
name: 'about',
component: () => import('../pages/About.vue')
}
]
})
除了 import 语法之外,需要注意的就是 ‘/blog/:category(category/\d+)?/:tags(tags/\d+)?/:page(page/\d+)?’ 这个奇怪的表达式。
这个表达式可以用于匹配下面的路由
/blog/category/1/tags/2/page/3
/blog/category/1/page/3
/blog/tags/3/page/3
/blog/page/3
匹配完毕之后,就可以拿到 categroy tags page 的值然后提交数据库拿数据咯。
博客里面需要注意的就三个组件
第一个,ArticlePost 组件
<template>
<div class="p-article-post">
<div class="columns">
<div class="col-1 hide-xl"></div>
<div class="col-2 col-xl-3">
<div class="g-sidebar">
<h4>本文目录</h4>
<div v-html="articleToc"></div>
</div>
</div>
<div class="col-6 col-xl-8">
<ArticleCard
:article="article"
@articleTocReady="initArticleToc"
></ArticleCard>
</div>
<div class="col-2 col-xl-3">
<div class="g-sidebar">
<h4>公告</h4>
<div>
<p>MG 的编程小屋,其实就是我整理笔记,写写文章的地方。</p>
<p>
专注 Python / JavaScript , 爱折腾的全干工程师 (Full Stuff
Engineer)
</p>
<p>如果我的文章给您的日常开发带来很大帮助的话</p>
<p>您可以关注我的公众号</p>
<div>
<img
src="/static/images/mp_wechat.jpg"
alt=""
style="width: 200px"
/>
</div>
<p>也可以扫描二维码进行投喂</p>
<div>
<img
src="/static/images/tips_wechat.jpeg"
alt=""
style="width: 200px"
/>
</div>
<p>听说关注或者进行投喂的人,技术都越来越牛咯。</p>
</div>
</div>
</div>
<div class="col-1 hide-xl"></div>
</div>
</div>
</template>
<script>
import { fetchBlogPost } from "../../api/blog"
export default {
name: "BlogPage",
components: {
ArticleCard: () => import("../../components/Common/ArticleCard.vue"),
},
data() {
return {
article: "",
articleToc: undefined,
}
},
watch: {
"$route.params": function () {
this.initArticle()
},
},
created() {
this.initArticle()
},
mounted() {},
methods: {
initArticleToc: function (v) {
this.articleToc = v
},
initArticle: function () {
let title = this.$route.params.title
fetchBlogPost(title).then(res => {
this.article = res
})
},
},
}
</script>
嗯,其实就是监听 url, 如果匹配上 url 的话,从 url 中取 title, 然后发送请求,接着取回响应的内容交给子组件处理。子组件处理完毕会 emit 出一个 toc 的值,将这个值赋值给左侧 toc 即可。
见地址吧 https://github.com/twocucao/YaVueBlog/blob/master/src/components/Common/Pagination.vue
终端的样式,当然是抄别人的 CSS, 打字效果,来源于 typed.js 依赖库
对于其他的实现,自然是要多多看代码咯。
其实前端工程化是一个很广的概念,本文没有提到代码风格、团队开发工作流、CSS 编写规范、组件优化、Webpack 详细配置等等。这都需要在日常开发中多多练习的。
笔者最近换了份工作,以 React 为技术栈。 加上篇幅和精力有限,也就是不在以 Vue 为前端这一块详细展开了。
下面的文章还是聚焦在后端上面。
还犹豫啥,Django 前后端分离最佳实践,点赞后,快上车吧
ChangeLog: