项目目录
项目的创建和环境的配置
主要参考了
https://github.com/michaelbukachi/django-vuejs-tutorial/wiki/Django-Vue.js-Integration-Tutorial这个教程做环境的搭建
最后创建webpack.config.js确定webpack的最终配置
如何运行webpack来构建(build)项目
这种webpack配置不适用于生产环境
生产环境的配置教程:https://moduscreate.com/blog/optimizing-react-es6-webpack-production-build/
python manage.py runserver运行报错:
原因:
webpack配置文件写完之后我是直接这样运行的:node_modules\.bin webpack,这样虽然命令行上会有提示,但是无法生成打包文件,也就是说django无法找到关于webpack的任何配置
正确的命令是:
node_modules\.bin\webpack --config webpack.config.js
这个命令可以按照webpack.config.js配置文件中写的,在指定目录assets/bundles下生成生成打包文件app.js,同时在根目录下生成mapping文件 webpack-stats.json
在电脑上全局安装了webpack后,可以直接用node_modules\.bin\webpack命令进行打包。
如果需要热部署,可以输入如下命令
node_modules\.bin\webpack --config webpack.config.js --watch
此时启动django,将会自动按mapping文件来寻找相应的打包文件
此时的webpack.config.js:
//webpack.config.js
const path = require('path');
const webpack = require('webpack');
const BundleTracker = require('webpack-bundle-tracker');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
mode: 'development',
context: __dirname,
//在这里,我们设置了入口点,并为webpack构建的输出设置了文件名和目录。
entry: './assets/js/index',
output: {
path: path.resolve('./assets/bundles/'),
filename: 'app.js'
},
plugins: [
new BundleTracker({filename: './webpack-stats.json'}),
new VueLoaderPlugin(),
],
//这里,我们配置了webpack要使用的加载器。检查文件的扩展名,并使用适当的加载器。
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
],
},
//这确保您可以在代码中使用vue模板。如果没有这部分,就无法渲染组件,会呈现空白
resolve: {
alias: {vue: 'vue/dist/vue.js'}
},
};
数据库的相关配置
navicat+mysql
在navicat中连接mysql不成功,报错:
1045 Access denied for user 'root'@'localhost' (using password:YES)
原因:没有在当前上访问的权限,需要修改权限
解决方法:
1.mysql -u你的用户名 -p你的密码 这个命令进入mysql
2.mysql> 对用户赋予权限的格式:grant 权限 on 数据库.* to 用户名@登录主机 identified by "密码"
mysql> 如,增加一个用户user1密码为password1,让其可以在本机上登录, 并对所有数据库有查询、插入、修改、删除的权限。首先用以root用户连入mysql,然后键入以下命令:
mysql> grant select,insert,update,delete on *.* to user1@localhost Identified by "password1";
如果希望该用户能够在任何机器上登陆mysql,则将localhost改为"%"。
如果你不想user1有密码,可以再打一个命令将密码去掉。
grant select,insert,update,delete on mydb.* to user1@localhost identified by "";
3.flush privileges
问题解决,navicat可以连接
(参考https://blog.csdn.net/weixin_39168678/article/details/80089520)
项目文件的配置
settings.py中databases的配置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # mysql驱动
'NAME': 'searchprojectdb', # 数据库名
'USER': 'root', # 链接数据库的用户名
'PASSWORD': '', # 连接数据库用的密码
'HOST': '127.0.0.1', # 服务器地址
'PORT': '3306', # 服务器端口
}
}
直接用命令python manage.py runserver 0.0.0.0:8000运行项目报错:
原因和解决办法:
在 python2 中,使用 pip install mysql-python 进行安装连接MySQL的库,使用时 import MySQLdb 进行使用
在 python3 中,改变了连接库,改为了 pymysql 库,使用pip install pymysql 进行安装,直接导入即可使用
但是在 Django 中, 连接数据库时使用的是 MySQLdb 库,这在与 python3 的合作中就会报以下错误了
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named 'MySQLdb'
解决方法:在 __init__.py 文件中添加以下代码即可。
import pymysql
pymysql.install_as_MySQLdb()
顾名思义应该是让 Django 把 pymysql 当成 MySQLdb 来使用吧
前后端数据通信
做前后端数据联通测试时,点击按钮期望的结果是“默认列表数据”变为后端传过来的“后台列表数据”,而实际执行报错如下:
用UrlSearchParams做数据传递,前端代码如下:
<template>
<div class="about">
<h1>This is DataTest</h1>
{{d2}}
<p v-for="d in d1">
{{d}}
</p>
<button @click="init">点击获取数据</button>
</div>
</template>
<script>
export default {
name: "Demo",
data() {
return {
keyword: '',//v-model绑定的输入框的value
displayitem: {'title': null, 'price': null},
d1: ['默认列表数据1', '默认列表数据2'],
d2: '默认字符串',
}
},
methods: {
clearInput: function () {
this.keyword = '';
},
init: function () {
let _this = this;
let param = new URLSearchParams()
param.append('username', 'admin')
param.append('pwd', 'admin')
this.$axios({
method: 'post',
url: 'query/',
data: param
}).then(function (response) {
_this.d1 = response.data
}).catch(function (response) {
console.log(response)
})
},
},
}
</script>
如果想直接向后台传递字典数据:
import Qs from 'qs'
export default {
name: 'Demo',
data: function data() {
return {
listQuery: {
page: 1,
limit: 20,
unit: undefined,
title: undefined,
price: undefined,
type: undefined,
amount: undefined,
payment: undefined,
itemnumber: undefined,
model: undefined,
singlenumber: undefined,
sort: '+id'
},
keyword:undefined,
searchres: null,
resultNum:null,
priceOptions: ['0-10', '10-50', '50-100', '100-1000', '1000-10000'],
paymentOptions: ['0-100', '100-1000', '1000-10000'],
};
},
methods: {
getList() {
var _this = this;
console.log(this.listQuery);
this.$axios({
method: 'post',
url: 'query/',
transformRequest: [function (data) {
// 对 data 进行任意转换处理
return Qs.stringify(data)
}],
data: _this.listQuery //data直接赋值为listQuery这个字典
}).then(function (response) {
_this.searchres = response.data;
_this.resultNum=response.data.length;
console.log(_this.searchres);
console.log(_this.resultNum);
}).catch(function (response) {
console.log(response);
});
}
}
URLSearchParams使用示例:
var paramsString = "q=URLUtils.searchParams&topic=api"
var searchParams = new URLSearchParams(paramsString);
for (let p of searchParams) {
console.log(p);
}
searchParams.has("topic") === true; // true
searchParams.get("topic") === "api"; // true
searchParams.getAll("topic"); // ["api"]
searchParams.get("foo") === ""; // true
searchParams.append("topic", "webdev");
searchParams.toString(); // "q=URLUtils.searchParams&topic=api&topic=webdev"
searchParams.set("topic", "More webdev");
searchParams.toString(); // "q=URLUtils.searchParams&topic=More+webdev"
searchParams.delete("topic");
searchParams.toString(); // "q=URLUtils.searchParams"
urls.py中的代码:
from django.conf.urls import url
from django.views.generic import TemplateView
from myapp import views
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='index.html'), name='index'),
url(r'^query/$',views.query),
]
views.py中的代码:
from django.http import JsonResponse
def query(request):
print(request.POST.get('username'))
return JsonResponse(['后台列表数据1','后台列表数据2'],safe=False)
如果不添加safe=False,报错:
'In order to allow non-dict objects to be serialized set the ' 'safe parameter to False.'
查看源码:
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super(JsonResponse, self).__init__(content=data, **kwargs)
如果传递的不是字典类型的话,需要设置safe=False
Django csrf验证的问题
用axios做前端异步请求,除了GET请求之外的django都会返回403状态码,403 Forbidden 是HTTP协议中的一个HTTP状态码。可以简单的理解为没有权限访问此站,服务器收到请求但拒绝提供服务。
在浏览器的network项目中发现是因为csrf验证没有通过,所以请求被拒绝提交。
django会在浏览器的cookie里面保存一项csrf的值
这个是从django生成的,django会检查每个http请求的headers 里面的 X-CSRFToken 项的值是否和cookie里面保存的值相同,如果不相同或者缺失,就拒绝这个请求,如果相同,说明这次请求是从真实用户发起的。
所以要做的就是在每次请求的 headers 里面加上X-CSRFToken:csrftoken值
具体如下,在入口文件index.js中:
//拦截request,
axios.interceptors.request.use((config) => {
config.headers['X-Requested-With'] = 'XMLHttpRequest';
let regex = /.*csrftoken=([^;.]*).*$/; // 用于从cookie中匹配 csrftoken值
config.headers['X-CSRFToken'] = document.cookie.match(regex) === null ? null : document.cookie.match(regex)[1];
return config
});
其中 /.*csrftoken=([^;.]*).*$/ 是一个正则表达式,用于从cookie中获取csrftoken的值 ,([^;.]*) 是命名捕获,表示从匹配到的内容中 只获得 ()内的值。
string.match(regex) 得到的是一个数组, 第0项是匹配到的全部内容,第1项是通过命名捕获得到的内容,在这里就是csrftoken的值。
这样就完成了使用axios发送请求的正确配置了,同时保证了网站免受csrf攻击的影响
(这部分参考:https://blog.csdn.net/clark_fitz817/article/details/79226661)
分别console.log了JsonResponse的response,response.data和response.data.columns
后端
sql中判断一个字符串包含另一个字符串:
CHARINDEX('substring','string')返回字符串的索引位置,没有返回0
或者用模糊匹配:where string like '%substring%'(%可以表示0,1或多个字符)
让mysql的select语句返回字典类型,只需要这样在建立游标时加一个参数,这样会将每行返回的值放在字典里面,然后再放进一个list里面。
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
结果的样例如下:
[{'货品标题': '装订铁圈双线铁圈台历圈挂历铁圈 6.4mm-25.4mm 银色34齿23齿包邮 规格: 14.3mm 银色 100支 34齿', '单价': '40', '数量': '1', '实付款': None, '单位': '支', '货号': '', '型号': '', '单品货号': '', '货品种类': None}, {'货品标题': '装订铁圈双线铁圈台历圈挂历铁圈 6.4mm-25.4mm 银色34齿23齿包邮 规格: 9.5mm 银色 100支 34齿', '单价': '26', '数量': '1', '实付款': None, '单位': '支', '货号': '', '型号': '', '单品货号': '', '货品种类': None}]
前端
Vue.js使用Webpack打包
不能识别vue文件中的css代码的解决方案:
使用vue-cli搭建项目有内置的webpack方案,但是没有使用scss。vue提供了初始化的webpack模板,其中使用了vue-loader。vue-loader默认只支持sass,要是想要使用scss,必须安装node-sass和sass-loader,并修改相关webpack配置。具体操作如下:
- 依次执行以下命令 安装node-sass和sass-loader
npm install node-sass --save-dev
npm install sass-loader --save-dev
安装node-sass遇到报警:
npm WARN sass-loader@8.0.0 requires a peer of sass@^1.3.0 but none is installed. You must install peer dependencies yourself.
这是因为当前sass的版本太高,webpack编译时出现了错误,这个时候只需要换成低版本的就行,下面说一下修改方法,很简单,如下,找到package.json文件,里面的 "sass-loader"的版本更换掉 就行了。
我本地是将 "sass-loader": "^8.0.0",更换成了 "sass-loader": "^7.3.1"。
卸载当前版本sass-loader命令:npm uninstall sass-loader
安装低版本的sass-loader命令:npm install sass-loader@7.3.1 --save-dev
若npm安装太慢,可以使用cnpm安装
2.修改相关webpack配置 打开webpack.base.config.js, 在module里的rules中加上:
{
test: /\.scss$/,
loaders: ["style-loader", "css-loader", "sass-loader"]
//注意这里要加-loader,因为:
//It's no longer allowed to omit the '-loader' suffix when using loaders
}
3.如果要在vue文件中的style使用scss,则在 style处声明:
<style rel="stylesheet/scss" lang="scss"></style>
最好加上scoped,这样可以保证当前style只对当前组件有效,避免组件间样式相互影响。即:
<style rel="stylesheet/scss" lang="scss" scoped> ... </style>
从上到下依次打印了JsonResponse的response,response.data和response.data.columns
图标字体的获取和引用:
从icomoon下载图标字体,压缩包中包含svg,woff, ttl,eot格式的fonts文件和自动生成的style.css文件。只有一个search图标时的style.css文件如下:
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?b5uopg');
src: url('fonts/icomoon.eot?b5uopg#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?b5uopg') format('truetype'),
url('fonts/icomoon.woff?b5uopg') format('woff'),
url('fonts/icomoon.svg?b5uopg#icomoon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-search:before {
content: "\e986";
}
Demo.vue 的style标签中引入style.css,或者在index.js文件中引入style.css,注意相对路径的写法
import './icons/style.css'//index.js
@import '../icons/style.css';//Demo.vue中的style标签内
网页效果:
报错:
注意这里报错的内容是找不到以hash码为文件名的woff和ttf文件!!!
调整后的效果:
这个错误和webpack.config.js文件中webpack-loaders的配置有关。
Webpack loaders
webpack的loaders是一块很重要的组成部分。我们都知道webpack是用于资源打包的,里面的所有资源都是“模块”,内部实现了对模块资源进行加载的机制。但是Webpack本身只能处理 js模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。 Loader 可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果,例如可以使用loader加载器可以快速编译预处理器(less,sass,coffeeScript)。 Loader 可以在require()引用模块的时候添加,也可以在 webpack 全局配置中进行绑定,还可以通过命令行的方式使用。
file-loaders可以把 JavaScript 和 CSS 中导入图片的语句替换成正确的地址,并同时把文件输出到对应的位置。默认情况下,生成的文件的文件名就是文件内容的 MD5 哈希值并会保留所引用资源的原始扩展名。
例如项目中这部分代码,经过file-loader处理变成下面这样,并且在输出文件夹bundles中多出了引用的文件,其中文件名用原名转化成的hash值命名。
src: url('fonts/icomoon.eot?b5uopg');
src: url('fonts/icomoon.eot?b5uopg#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?b5uopg') format('truetype'),
url('fonts/icomoon.woff?b5uopg') format('woff'),
url('fonts/icomoon.svg?b5uopg#icomoon') format('svg');
src: url(2dad638c5bf5628f1bbc3a69d67584e7.eot);
src: url(2dad638c5bf5628f1bbc3a69d67584e7.eot) format('embedded-opentype'),
url(d9dbf780aac370efed096247ee86a82f.ttf) format('truetype'),
url(a9f97f21133f416450932b66ed98c9f9.woff) format('woff'),
url(ab09a44a063ed7a1109730c77c3ab28d.svg) format('svg');
可以使用查询参数名为文件配置自定义文件名模板。例如,要将一个文件从上下文目录复制到保留完整目录结构的输出目录中,可以使用以下命令。
//webpack.config.js
{
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
}
}
加上这条语句之后,载入图标字体成功。
当el-table元素中注入data对象数组后,在el-table-column中用prop属性来对应对象中的键名即可填入数据,用label属性来定义表格的列名。可以使用width属性来定义列宽。这样可以实现按列打印数据。
<el-table
:data="searchres"
highlight-current-row
style="width: 100%;"
>
<el-table-column label="货品标题" prop="货品标题" align="center" width="300">
<template slot-scope="scope">
<span>{{ scope.row.货品标题 }}</span>
</template>
</el-table-column>
<el-table-column label="单价" prop="单价" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.单价 }}</span>
</template>
</el-table-column>
<el-table-column label="数量" prop="数量" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.数量 }}</span>
</template>
</el-table-column>
<el-table-column label="实付款" prop="实付款" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.实付款 }}</span>
</template>
</el-table-column>
<el-table-column label="单位" prop="单位" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.单位 }}</span>
</template>
</el-table-column>
<el-table-column label="货号" prop="货号" align="center" width="150">
<template slot-scope="scope">
<span>{{ scope.row.货号 }}</span>
</template>
</el-table-column>
<el-table-column label="型号" prop="型号" align="center" width="150">
<template slot-scope="scope">
<span>{{ scope.row.型号 }}</span>
</template>
</el-table-column>
<el-table-column label="单品货号" prop="单品货号" align="center" width="150">
<template slot-scope="scope">
<span>{{ scope.row.单品货号 }}</span>
</template>
</el-table-column>
<el-table-column label="货品种类" prop="货品种类" align="center" width="150">
<template slot-scope="scope">
<span>{{ scope.row.货品种类 }}</span>
</template>
</el-table-column>
</el-table>
CSS语法相关
inline-block类型元素的居中:
上一级元素设置text-align:center
block类型元素的居中:
直接margin:0 auto;