用户管理
密码修改
后端 通过id查询出相应的数据获得数据库的哈希值,根据哈希值比对前台给回的密码。
mongoHelper.findOneById("test",_id).then(user_id=> { //查询当前修改的id
if(user_id.password == req.body.password) { // 数据库密码 比 当前获取到的密码
mongoHelper.updateOneById("test",req.params.id,UserFields) //密码未修改直接储存
.then(user_edit => {
res.json(user_edit);
})
.catch(err => res.status(404).json(err));
} else {
bcrypt.genSalt(10, function(err, salt) { // 哈希值加密
bcrypt.hash(UserFields.password, salt, async(err, hash) => {
if(err) throw err;
UserFields.password = hash;
console.log(hash)
mongoHelper.updateOneById("test",req.params.id,UserFields)
.then(user_edit => {
res.json(user_edit);
})
.catch(err => res.status(404).json(err));
});
});
}
列表模糊搜索
用户管理模块搜索执行模糊搜索(此处的i,表达无视大小)
const name = req.body.name;
mongoHelper.find("test",{name:eval("/"+name+"/i")}).then(user => {
前端API统一管理
- 对请求的链接api统一管理,在引用的地方抛出,这个目的是一方面是便于api请求的后期修改的便利。
- 此处由于添加编辑的请求不同,可执行的操作是一样的便可以用链式的方式动态执行函数名
import axios from 'axios'
const baseUrl=''
// 添加信息的接口
export const add = params => {
return axios.post(`${baseUrl}/api/profile/add`, params)
}
// 编辑信息的接口
export const edit = params => {
return axios.post(`${baseUrl}/api/profile/edit`, params)
}
//相应的页面
import {add,edit} from '../api/profile'
const url = this.dialog.option == "add" ? add : edit;
url(this.form).then(res => {}
权限管理
一般后台的管理系统对操作管理限制,我们可以根据开发者,管理员,员工做权限管理,编辑删除添加均可分配权限操作。现在目前阶段是直接根据后台的管理权限进行写死控制操作,后期可开发出权限列表,在用户信息管理上加上权限管理的操作,这个灵活性会更强,精确到每个人员的权限分配上,通过编辑一些操作,勾选中的人员便可进行某个功能上的操作(不限于编辑删除添加等操作)。
删除数据不携带在请求链接上
运用了delete方法删除数据,将本来id放在请求链接中更改为传值的形式 前端需要传值需要携带data
delete_user({data:{id:row._id}}).then(res => {})
链接需要携带id获取请求方式
需求:点击列表页,跳转到详情页,详情页链接携带id请求
接口(coupon_id是需要动态获取的):
(`${baseUrl}/index.php?m=Home&c=api.Goods&a=goodsList&coupon_id=coupon_id`, params)
在详情页中获取链接上的id
this.paramer.coupon_id = this.$route.query.coupon_id;
列表页
:href="'/goods/classify_list?id='+itemss.id"
商家入驻
分页查询,查询现有几条数据
skip().limit()的使用有点像字段截取类似,输入的页数为1,则减1的目的是为0-4之间的取前四条数据
col.find(condition).skip((page-1)*4).limit(4).toArray();
条数计算
await col.find(condition).count()
多条数据的模糊查询
const title = req.body.title;
const category_list = {$or:[{title:eval("/"+title+"/i")},{ category:eval("/"+title+"/i")}]}
mongoHelper.find("business",category_list).then(search_list => {})
Vue中使用百度编辑器Uetor
资料文献: 百度富文本编辑器文档,下载ueditor官网,Vue + UEditor + v-model双向绑定 此处使用到编辑器的地方在编辑商家的时候需要用到,故本想直接根据文档上引入编辑器开发,谁知看着看着感觉步骤甚多,其次根据网上提供的资料使用有点难受,便看到了github上有大神封装了此方法,便使用上了,这边我列出我引用封装好的了的问题
- 配置引用文件的时候,我们现开发都是3.x以上了,路径使用为
UEDITOR_HOME_URL: '/UEditor/' - ueditor的文件还是需要引用进去的,可以官网下载,也可以用作者的,一般放到public即可
- 此处我用子组件式的方式引用,你会发现传值过去,在编辑会报错
图片上传
demo单独实现
如何上传,什么格式上传,如何回显,又该如何下手,搜索了一大堆,琳琅满目的资料也是一大堆。
但是终于在两个博主中找到了跑到巨人肩上的方法了,博主1,博主2,其实还是缺点想要的感觉,而我想要实现的效果是两个不一样的字段,一个上传用户图片,一个上传轮播图的需要,从前两个博主想要这样实现有点难,故在博主3看到希望了,博主3的实现是var cpUpload = upload.fields([{name:'avatar',maxCount:1},{name:'gallery',maxCount:8}])
现在为大家呈现下运用node+express中的Multer文件上传,前端是用vue中的Element框架(不得不说在这也花了点时间)
是先把前端做了还是后端做了,其实先后差不多的,如果你的前端用了Element框架中的form表单带偏了,基于博主1,这边直接实现是基于表单提交的方式实现
前端: 运用了Element上传中的照片墙上传多张图片,以及直接用H5 input上传控件上传用户头像。
<form action="/api/upload/addPicture" method="post" enctype="multipart/form-data"> //主角
<input type="text" name="picTitle" placeholder="必填" class="form-control" required=""> //配角
//主角 多图--此处我们可以自定义name,在后端实现是需要拿到的,multiple可以多图上传
<el-upload
action="#"
list-type="picture-card"
:auto-upload="false"
multiple
name='user2'
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove">
// 用户图片上传
<input type="file" name="user" class="form-control">
//表单提交
<input type="submit" value="提交">
后端: 此处你需要安装下npm install multer --save,里面的写法可以根据博主1走,而我们需要上传两个不同字段的数据,故我们需要根据博主3提供的两个并列字段
var cpUpload = upload.fields([{name:'user2',maxCount:4},{name:'user',maxCount:2}])
router.post('/addPicture',cpUpload,function(req,res,next){
console.log(req.body.picTitle)//console.log(req.query.picTitle);//get
console.log(req.files.user2)//req.file文件的具体信息
console.log(req.files.user)
res.send({ret_code: datatime});
});
实际开发实现
此处由于已经搭建好element框架的详情页,节省修改时间,故这里我是用边上传边储存数据,在这当中也遇到问题就是
- 是否全部提交在做图片处理还是边上传边储存数据,还是边上传来得快点,其次也是提交数据响应快点。
- 上传图片后重新返回的文件路径,拼上域名访问不了,这种需要再路由上重新定义一下,访问路径才能识别到这个是图片的路径格式,可参考看下博主
- 提交数据后重新回去需要把数据库的数据展示出来,此处的可以添加前端框架上这个控件的属性
:file-list="List2",那么接下来获取删除添加图片都是在这个集合里面操作 - 其次会遇到vue存在的定义了空数组会存在有返回的data中的对象{ob: Observer},在上传的时候会有出现两个一个为实际的一个为空的占位
表格导出
nodejs+excel-export导出数据库数据到excel表,直接下载,巨人的肩膀巨献,
在巨人的肩膀上我们理清实现的结构,将我们需要的数据整理下如图
//node的写法
//导出所有用户信息到excel
router.get('/exportExcel', function (req, res) {
//创建一个写入格式map,其中cols(表头),rows(每一行的数据);
let conf = {};
conf.name = "mysheet";
//手动创建表头中的内容
let cols = ['合作商家', '类型','手机号','省份','城市','地址'];
//在conf中添加cols
conf.cols = [];
for(let i = 0; i < cols.length; i++) {
//创建表头数据所对应的类型,其中包括 caption内容 type类型
let tits = {};
//添加内容
tits.caption = cols[i];
//添加对应类型,这类型对应数据库中的类型,入number,data但一般导出的都是转换为string类型的
tits.type = 'string';
//将每一个表头加入cols中
conf.cols.push(tits);
}
//mongoose查询数据库的用户信息
mongoHelper.find('business','').then(data=>{
//创建一个和表头对应且名称与数据库字段对应数据,便于循环取出数据
let rows = ['title','category','phone','province','city','address'];
//用于承载数据库中的数据
let datas =[];
//循环数据库得到的数据
for(let i = 0; i < data.length; i++){
let row =[];//用来装载每次得到的数据
//内循环取出每个字段的数据
for(let j = 0; j < rows.length; j++){
row.push(data[i][rows[j]].toString());
}
//将每一个{ }中的数据添加到承载中
datas.push(row);
}
conf.rows = datas;
//将所有数据写入nodeExcel中
let result =nodeExcel.execute(conf);
//设置响应头,在Content-Type中加入编码格式为utf-8即可实现文件内容支持中文
res.setHeader('Content-Type', 'application/vnd.openxmlformats;charset=utf-8');
//设置下载文件命名,中文文件名可以通过编码转化写入到header中。
res.setHeader("Content-Disposition", "attachment; filename="+ encodeURI('商家信息表') + ".xlsx");
//将文件内容传入
res.end(result,'binary');
}).catch(err => res.status(404).json(err))
})
由于这个npm是直接访问接口就执行下载的,那么具体就不深抛他了,需要的话可以看看这块拓展方法的解决方法的我们在前端直接也用接口的方式跳转直接下载
this.$message("导出成功");
let url = '/api/business/exportExcel'
window.location.href = url;
文章搜索实现
此功能主要实现搜索,本地存储记录,清空,组件化共用管理
搜索后端实现
由于前面已经实现过了用户搜索的相关功能,故我们不再重复,这次我们试下多条件的实现,我们这次用模糊搜索标题和类别,满足一个都直接请求相应的数据处理
const category_list = {$or:[{title:eval("/"+title+"/i")},{ category:eval("/"+title+"/i")}]}
运用小程序的properties组件页面的通信
由于我们这边的列表是首页跟搜索列表页是共用的,故我们通过封装成一个组件的方式进行传递值,此块可以看下之前的推文,有介绍到这块的写法,这里还有注意到的点就是,首页推送的是置顶的推文,而搜索这边是不需要的,故我们需要传多个值过去,通过 || 可以判断到我们需要哪个地方显示置顶推文。
小程序的缓存储存功能
小程序有两种储存方式,一种是同步存储setStorageSync,一种是异步储存 setStorage,同步就是执行完才能执行下一个。
此处实现的是搜索历史记录的遍历(我们需要考虑就是每次搜索都能存到数组中且不能存入数组含有的数据)和清空(不仅要清除页面显示数据,还要记得清除缓存的数据),以及点击历史记录直接进行搜索.
//历史记录遍历 这边用了includes去检测是否含有该数据
const findStorage = wx.getStorageSync('storageArr') || []
if(!findStorage.includes(this.data.searchValue)) {
findStorage.unshift(this.data.searchValue)
wx.setStorageSync('storageArr', findStorage)
this.setData({
storageArr: findStorage
})
}
// 清空数据
clearStorage() {
wx.setStorageSync('storageArr', [])
this.setData({
storageArr: []
})
},
合作商家经纬度
实现通过切换不同的附近商家的距离,结合自己授权的地理经度查出在不同距离中存在的商家
router.post("/distance",async(req,res) => {
let x = req.body.x //当前你的坐标经度
let y = req.body.y //当前你的坐标纬度
let distance = req.body.distance //距离
console.log(x)
try {
db.createIndex({locs:'2dsphere'}) //索引值
let r = await mongoHelper.find("business",{locs: {
$nearSphere: {
$geometry: { //地理定位
type: 'Point', //类型,点
coordinates:[Number(x),Number(y)] //指定坐标 体育西路小学
},
$maxDistance: Number(distance)//接受最远的距离 1公里
}
}})
res.json({status: 1,msg: "获取成功",result: r});
} catch (error) {
console.log(error)
}
})
后台VUe页面的实现,由于手动输入会存在偏差或是不准确的现象,故我运用了地图的形式进行存坐标字段,再将存取到的坐标添加到数组中,以数组的形式显示出来,便于后期后端的查询
npm install vue-baidu-map --save
通过子传父的传值形式赋值到表单的数据中
小程序如何识别富文本呈现出来
在这过程中如果遇到图片没显示出来的,看下图片是否是第三方复制链接被保护了,或是图片的地址不对
import WxParse from '../../wxParse/wxParse.js';
//获取详情页
WxParse.wxParse('infoconten', 'html', this.data.infoconten, this, 0);
//页面
<view class="content">
<template is="wxParse" data="{{wxParseData:infoconten.nodes}}" />
</view>
瞎折腾bug系列
超时处理
不断超时30000ms,真的没下手之地,代码也没改什么,真的是束手无策,网上的方法都试了下,真的是健康上网了下才找到问题
报错点1.
DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
报错点2.
MongoTimeoutError: Server selection timed out after 30000 ms
解决: 需要将原本声明的这个删除
经纬度查询附近商家
报错1 planner returned error :: caused by :: unable to find index for $geoNear query
遇到最多就是这个错误,这个百度上是说明缺少索引值,故我们需要再这基础上添加索引值,由于我的查询方法是封装在mongoHelper中,故只能将索引值写在我要请求的方法中
locs这个命名这个是跟你数据库上的名称一致的
报错2 文档中显示查询坐标的格式是[a,b],而存进去的是 ['a,b'],导致查不到报错
一开始我的做法是经纬度分别存的,由于文档中的查询方式是经纬连在一起,故只能合并存储,但是存入数据库中显示出来的数据是以字符串的格式存在,那么我们查询应根据查询的格式数组的格式去存取,所以我在实现的时候获取到的数据,将他定义成一个数组的格式,再赋值到input框中
编辑器图片上传
-
上传一直在报后端未配置,不能使用这个功能的提示问题 此处中我们在刷新页面会加载一个重要的点就是config.json这块,这是上传图片功能的主要文件,这边的你在下载php或是其他版本的ueditor中,记得把全部文件都得放进去我们的静态文件中
-
其次是开发了,附上代码,我们需要安装相应的npm包
安装 $npm install ueditor --save
$npm path ueditor --save
代码如下,此处需要注意的点有 config.json 的路径指向, 图片保存的路径 ,
var ueditor = require("ueditor");
var path = require('path');
router.use("/morePicture",ueditor(path.join(process.cwd(),'public'), function (req, res, next) {
// ueditor 客户发起上传图片请求
if (req.query.action === 'uploadimage') {
var foo = req.ueditor;
var imgname = req.ueditor.filename;
var img_url = '/ueditor/business'; //保存的图片地址
res.ue_up(img_url); //你只要输入要保存的地址 。保存操作交给ueditor来做
res.setHeader('Content-Type', 'text/html');//IE8下载需要设置返回头尾text/html 不然json返回文件会被直接下载打开
}
// 客户端发起图片列表请求
else if (req.query.action === 'listimage') {
var dir_url = '/ueditor/business';
res.ue_list(dir_url); // 客户端会列出 dir_url 目录下的所有图片
}
// 客户端发起其它请求
else {
// console.log('config.json')
res.setHeader('Content-Type', 'application/json');
res.redirect('/UEditor/php/config.json');
}
}));
复制这个代码进去很开心了,图片也能上传了,但是又会发现了一个问题,图片迟迟不能显示出来,原来还要配置一系列的东西,这里也包括了将接口放进去的操作
config.json
此处的访问路径需要更改,以及图片的路径
ueditor.config.js
这里是配置请求地址的接口
待遗漏修复的问题
图片路径问题
此处由于上传图片都是拼上端口号http://localhost:5000/进行让图片显示,这里到后续需要动态化实现,此处包括 upload.js接口编写,UEditor/config.json 路径
后台商家入驻筛选完善
加上 时间+类别的组合多级筛选