全局工程化导入注册公共组件
iconfont使用
自定义导航
slot插槽
uni.request请求封装
一:登录页面
1.1.template:
<template>
<view class="body" :style="{height:height+'px'}">
<view class="header" :style="{height:statusBarHeight+'px'}">
</view>
<!-- <navigation>
<template v-slot:navigation1>
取消注册,返回首页
</template>
</navigation> -->
<view class="imgView">
<img src="static/logo.png" alt="" />
</view>
<view class="formView">
<view class="nameView">
<input v-model="form.username" placeholder-style="color:#fff" class="uni-input" placeholder="请输入平台账号" />
</view>
<view class="pwdView">
<input v-model="form.userpwd" placeholder-style="color:#fff" class="uni-input" password placeholder="请输入你的密码" />
</view>
<view class="btnView">
<button type="default" @click="btnLoginUser">登录</button>
</view>
<view class="textView">
<view class="face">
输入太麻烦,那就人脸识别登录
</view>
<view class="sign">
没有账号?立即注册新账号1
</view>
</view>
</view>
</view>
</template>
这里有两个注意的地方,一个是公共组件,一个是icon图标
1.2.公共组件问题:
全局自动注册加载组件
创建component文件夹
在这里创建自己的公共组件,然后创建一个index.js
在index.js中,通过require.context()工程化引入组件
// require.context有三个参数,第一个是路径指向 第二个是boolean数据类型,代表是否向下检索 第三个参数是设置检索什么文件
// 得到的这个element相当于是一个对象类型的数据
const element = require.context('./', false, /\.vue$/)
export default {
// install是vue供我们开发其他插件、注册全局组件的方法
// 有两个参数,一个是vue的构造器 一个是option
install(Vue) {
// element.keys获取到基于require.context()检索的符合条件的组件文件对应的属性名
element.keys().forEach(item => {
// 给组件设置组件名
// 前面有讲到过element的值类似对象数据,那么在经过keys()和循环后,item已经是代表着每个文件的属性名
// 属性名的值: ./aa/aa.vue
// 现在截取数据获取到aa文件名
// item.split('/') 得到 ['.','aa','aa.vue']
// item.split.('/').pop() 得到 'aa.vue'
// item.split('/').pop().replace(/\.\w+$/, '') 得到 aa
// 正则表达式解读 \是转义符 意在告诉这里列出的这些元字符当作普通的字符来进行匹配,因为前面pop之后得到了aa.vue,这个就是文件后缀名的.,而不是正则里面的.
// \w+ 意思是匹配数字和字母下划线的多个字符
const componentName = item.split('/').pop().replace(/\.\w+$/, '');
// 在这遍历过程中,每一次通过属性名指向com中的每一个属性值,然后给到Vue.conponent
const elementItem = element(item).default
Vue.component(elementItem.name, elementItem)
})
}
}
把index.js文件引入到main.js文件,挂载到vue上去
import components from '@/components/index.js'
//Vue.use()主要是调用插件内部的install方法,并将Vue实例作为参数传入
Vue.use(components)
现在可以在页面中使用了
<template>
<component :is="navigation"></component>
</template>
我是小白,用到哪,学到哪,要是有知道其他组件注册的方法和正则应用的大哥,麻烦留言告知一下,谢谢
1.3uniapp中iconfont的使用
1.正常选中自己需要的文件保存到项目然后下载下来,把文件里的iconfont.css复制到项目中
2.在下载的时候选择unicode,可能会提示更新之类的,然后复制代码,替换文件中的原本的内容
3.在替换完原本的内容后,在替换的内容中,有at开头的地址指向,在at前面加上'https://',不然在调试的时候,手机模拟器无法显示
4.icon使用(伪类选择器before)
5.现在我的icon假如要在class为navgation的view中显示
.identity input::before {
content:'\e608';
font-family: iconfont;
padding: 0 20upx;
line-height: 60upx;
color:#fff;
}
1.4自定义导航栏
1.先去除原本的导航栏
pages.json:
// 在pages.json中找到globalStyle,设置navigationStyle为custom,但是这样的话每个页面都没了原本的导航栏
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
// "navigationStyle": "custom"
},
// 如果只想某些单独的页面使用自定义导航栏,就在pages.json中找到那个页面的设置
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
到这里,再加上之前全局自动注册的组件,引入对象的组件就行了
1.5slot插槽
// 默认插槽(也叫匿名插槽)
// 公共组件
<template>
<view class="navigation">
<view class="navigation1 components">
组件本身的内容
// slot里不设置name值就是默认插槽
<slot>插槽内容</slot>
</view>
</view>
</template>
// 父组件
<template>
<view class="feather">
<view class="featherView">
父组件特意设置的内容字体
<navgation>
修改默认插槽的内容
</navgation>
</view>
</view>
</template>
// 这个时候修改默认插槽内容的字体和公共组件中的插槽内容字体就会显示(但是,假如公共组件中没有插槽内容,在navgation中设置的字体就会无效,不会显示,影响不到公共组件)
// 具名插槽
// 公共组件
<template>
<view class="navigation">
<view class="navigation1 components">
组件本身的内容
<slot>默认插槽的内容</slot>
<slot name="header">具名插槽header的内容</slot>
<slot name="footer">具名插槽footer的内容</slot>
</view>
</view>
</template>
// 父组件
<template>
<view class="feather">
<view class="featherView">
父组件特意设置的内容字体
<navgation>
修改默认插槽的内容
<view slot="default">修改具名插槽default的内容</view>
<view slot="header">修改具名插槽header的内容</view>
<view slot="footer">修改具名插槽footer的内容</view>
其他字体1
其他字体2
</navgation>
</view>
</view>
</template>
// 这个时候修改默认 插槽的内容字体 和 修改具名插槽header的内容字体 和 修改具名插槽footer的内容字体就会显示,而其他字体1 和 其他字体2 不会显示
// 具名插槽传值
// 公共组件
<template>
<view class="navigation">
<view class="navigation1 components">
组件本身的内容
<slot>默认插槽的内容</slot>
<slot name="header" :userdata="user">具名插槽header的内容</slot>
<slot name="footer">具名插槽footer的内容</slot>
</view>
</view>
</template>
<script>
export default = {
data(){
user:{
name:'aa'
}
}
}
</script>
// 父组件
<template>
<view class="feather">
<view class="featherView">
父组件特意设置的内容字体
<navgation>
修改默认插槽的内容
// 这个users名是自定义的
<template v-slot:header="users">
{{users.name}}
修改具名插槽default的内容
</template>
<template v-slot:default="default">修改具名插槽default的内容</template >
<template v-slot:footer="footer">修改具名插槽footer的内容</template >
其他字体1
其他字体2
</navgation>
</view>
</view>
</template>
1.6.script
<script>
import navigation from '../../components/navigation.vue'
import {request} from '@/utils/request.js'
import loginVue from './login.vue';
export default {
data() {
return {
statusBarHeight :48,
height:0,
navigation:'navigation',
form:{
username:'',
userpwd:''
},
location:""
}
},
components:{
navigation
},
onShow() {
uni.getLocation({
type:"gcj02",
geocode:true,
highAccuracyExpireTime:4000,
accuracy:"high",
isHighAccuracy:true,
success:(res) => {
console.log('getLocation',res);
// this.location = {
// latitude: res.latitude,
// longitude: res.longitude,
// country: res.address.country,
// province: res.address.province,
// city: res.address.city,
// district: res.address.district,
// street: res.address.street,
// streetNum: res.address.streetNum,
// poiName: res.address.poiName
// }
res.keys().forEach(val => {
this.location.val = res.val
})
console.log('location',this.location);
},
fail:(err) => {
console.log(err);
}
})
},
methods: {
async btnLoginUser(){
let obj = Object.assign({},this.form)
const get = await request({url:`/login/get/accnum/${obj.username}/${obj.userpwd}`,method:'post',data:this.location})
console.log('get',get);
if(get.status == 200) {
uni.setStorageSync('USER_TOKEN',get.token)
uni.showToast({
title:"账号检测成功,登录中..",
duration: 2000,
})
} else {
uni.showModal({
content:"账号无效,是否前往注册?",
success:(res) => {
if(res.confirm) {
uni.navigateTo({
url:"/pages/identity/identity"
})
} else if(res.cancel) {}
}
})
}
},
},
async created() {
const res = uni.getSystemInfo({
success:(res=>{
this.height = res.windowHeight
if(res.osName == 'android') {
this.statusBarHeight = res.statusBarHeight || 48
} else {
this.statusBarHeight = res.statusBarHeight || 44
}
console.log('res',res);
})
});
}
}
</script>
这里就没啥好说的了,就是正常将用户输入的用户名账号和密码传给后端,后端去处理,正常的发起请求,等待后端返回结果
1.7nodejs,处理前端传来的用户提交的账号信息,调数据库数据判断是否存在
router.post('/get/accnum/:username/:userpwd',(req,res) => {
try {
let body = req.body
let params = req.params
let status;
let token;
const login = orm.model('login')
login.sql(`select * from login where username="${params.username}" and userpwd="${params.userpwd}"`,(err,data) => {
if(err) {
console.log('login-get',err);
return res.status(500)
}
let datas = ''
if(data[0]) {
datas = data[0]
console.log('datas',datas);
status = 200
// jwt.sign 生成token,第一个参数是数据 第二个参数是签名,这个随便你去定义 第三个参数是设置token过期时间
token = jwt.sign(JSON.parse(JSON.stringify(datas)),config.SERECRT,{expiresIn:60 * 60 * 24})
login.update(`id=${datas.id}`,body,(err,das) => {
if(err) {
console.log('login-accnum-location-update',err);
return res.status(500)
}
console.log('login-accnum-location-update-data',das);
})
} else {
datas = {}
status = 202
}
res.send({code:200,msg:'账号检测成功!',data:datas,status,token})
})
} catch(err) {
return res.status(500)
}
})
二:注册页面--身份认证
这个先要去那些平台认证成企业用户,才能调用相对应的接口,我的百度云的姓名与身份证认证接口,不查身份证有效期,那个要钱,妈的。
2.1.uniapp中:
<template>
<view class="identityView" :style="{height:height+'px'}">
<!-- <view class="" style="margin-top: 400upx;">
</view> -->
<view class="identity">
<input v-model="user.identity" class="uni-input" placeholder-style="color:#fff;" maxlength="18" type="number" placeholder="请输入身份证号" />
</view>
<view class="name">
<input v-model="user.name" class="uni-input" placeholder-style="color:#fff;" type="text" placeholder="请输入真实姓名" />
</view>
<view class="btn">
<button type="default" @click="btnBaidu">下一步</button>
</view>
<view class="">
{{res}}
</view>
</view>
</template>
<script>
import { request } from '@/utils/request.js'
import { baiduyun } from '@/utils/baiduyun.js'
export default {
data() {
return {
height:0,
user:{
identity:'',
name:''
},
res:{},
access_token:''
}
},
methods: {
// 点击下一步
// 这里我没有在前端发起百度云的请求,把接口要求的参数传到nodejs,再发请求
async btnBaidu(){
console.log('点击了');
const identity = await request({url:`/login/baidu/token/${this.user.identity}/${this.user.name}`,method:'POST'})
console.log('identity',identity);
this.res = identity
uni.showToast({
title:identity.msg,
duration:2000,
icon:'none'
})
if(identity.msg == '身份认证通过') {
uni.showToast({
title:'下一步:人脸认证',
duration:1000,
})
let user = Object.assign({},this.user)
// btoa()返回一个 base-64 编码的字符串
// encodeURIComponent返回原字串作为 URI 组成部分被被编码后的新字符串
const str = btoa(encodeURIComponent(JSON.stringify(user)))
uni.navigateTo({
url:`/pages/plusVideo/plusVideo?str=${str}`
})
}
},
},
created(){
this.height = uni.getSystemInfoSync().windowHeight
},
}
</script>
2.2.nodejs中:
// 调用百度云接口时,要求先调接口获取access_token才能调用其他的接口,懒得去保存access_token数据,把获取access_token的请求写到了方法里,然后在请求中引入,每次发请求获取access_token
const config = require('../config/index')
const axios = require('axios')
exports.identityFun = async () => {
let grant_type = config.baiduToken.grant_type
let client_id = config.baiduToken.client_id
let client_secret = config.baiduToken.client_secret
const identity = await axios({
url:`https://aip.baidubce.com/oauth/2.0/token?grant_type=${grant_type}&client_id=${client_id}&client_secret=${client_secret}`,
method:'POST',
header:{ 'Content-Type': 'application/json', }
})
return identity
}
// 获取百度云token 鉴别身份信息
router.post('/baidu/token/:identity/:name',async (req,res) => {
try {
let params = req.params
const baiduToken = config.baiduToken
const token = await identity_face.identityFun()
let msg;
let status;
if(token.status == 200) {
let access_token = token.data.access_token
const identity = await axios({
url:`https://aip.baidubce.com/rest/2.0/face/v3/person/idmatch?access_token=${access_token}`,
method:'POST',
header:{ 'Content-Type': 'application/json' },
data:{
id_card_number: params.identity,
name: params.name
}
})
if(identity.data.error_code == 0) {
msg = '身份认证通过'
status = 200
} else {
msg = '请输入正确的身份信息'
status = 202
}
res.send({code:200, msg,status})
} else {
return res.status(500)
}
} catch(err) {
console.log('baidu-post-token',err);
res.status(500)
}
})
为了更好知道那里的代码出错了,可以下载个morgan包,这样报错也能知道错哪了
nodejs项目我是用的vscode,可以下载个code runer插件,右键app.js快速运行文件
三:uniapp,人脸数据采集
3.1.uniapp中:
<template>
<view class="body" :style="{height:height+'px'}">
<img :src="src" ref="img" class="img-data" />
<view class="">
imgData:{{imgData}}
</view>
<view class="">
tag:{{tag}}
</view>
<view class="">
src:{{src}}
</view>
</view>
</template>
<script>
import { request } from '../../utils/request';
export default {
data() {
return {
imgData: '',
pusher: null,
scanWin: null,
snapshotTimeoutNumber: 3000,
faceInitTimeout: null,
snapshTimeout: null,
user:{},
face:{},
tag:'',
src:''
};
},
created() {
uni.getSystemInfo({
success: (res) => {
// this.height = res.windowHeight
this.height= 200
}
})
},
onLoad(option) {
uni.showToast({
title: "正在打开摄像头,请稍后",
icon: "none"
})
//#ifdef APP-PLUS
this.faceInit();
//#endif
let user = option.str
user = JSON.parse(decodeURIComponent(atob(user)))
this.user = user
},
onHide() {
this.faceInitTimeout && clearTimeout(this.faceInitTimeout);
this.snapshTimeout && clearTimeout(this.snapshTimeout);
this.scanWin.hide();
},
methods: {
// (1)通过plus.video.createLivePusher创建直播推流
pusherInit() {
//获取当前窗口对象
const currentWebview = this.$mp.page.$getAppWebview();
//创建视频推流对象,url是视频上传到的服务器地址,不填表示不传视频
this.pusher = plus.video.createLivePusher('livepusher', {
url: '',
top: '29%',
mode:'FHD',
beauty:1,
whiteness:3,
left: '30%',
width: '170px',
height: '172px',
position: 'absolute',
aspect: '9:16',
'z-index':777
});
// 将推流对象append到当前页面中
currentWebview.append(this.pusher);
// 预览摄像头采集数据
this.pusher.preview();
},
faceInit() {
this.faceInitTimeout = setTimeout(() => {
this.pusherInit();
// 覆盖在视频之上的内容,-比如扫描框
// 扫描框的页面优先与当前页面,会始终覆盖
覆盖的扫描框html页面名就叫hybrids,不然好像会报错,不知道是不是我没有及时关闭项目再启动的原因,刷新没用,当文件名是其他的时候,这个扫描框就会不显示,有知道原因的大哥麻烦告诉一下谢谢
// 然后如果想在覆盖的扫描框页面实现圆形视频流窗口就用css去实现吧,通过plus的属性和方法行不通,可能是我没找到方法的原因,有知道麻烦告诉一下,谢谢
// (2)利用plus.webview.create将扫描框页面及扫描动画(hybrids.html)覆盖在视频之上;
this.scanWin = plus.webview.create('/hybrid/html/scan.html', '', {borderRaduis: '50%',top:'160px',bottom:'0px',background: 'transparent', replacewebapi:{geolocation:'auto'}});
// 显示视频播放控件
this.scanWin.show();
this.snapshotPusher();
uni.hideToast();
}, 200);
},
// (3)利用liverPusher对象的snapshot方法创建视频快照
snapshotPusher() {
// 切换摄像头
this.pusher.switchCamera();
this.snapshTimeout = setTimeout(() => {
this.pusher.snapshot(
e => {
// 关闭直播推流控件
this.pusher.close();
// 隐藏视频播放控件
this.scanWin.hide();
var src = e.tempImagePath;
// 获取截取的视频流图片地址
this.src = e.tempImagePath
console.log('this.src',this.src);
this.identityFun()
// 使用plus.zip.compressImage压缩图片
// this.getMinImage(src);
},
function(e) {
plus.nativeUI.alert('snapshot error: ' + JSON.stringify(e));
}
);
}, this.snapshotTimeoutNumber);
},
// (4)使用plus.zip.compressImage压缩图片
// getMinImage(imgPath) {
// plus.zip.compressImage({
// src: imgPath,
// dst: imgPath,
// overwrite: true,
// quality: 40
// },
// zipRes => {
// console.log('zipRes',zipRes);
// setTimeout(() => {
// // IO模块管理本地文件系统,用于对文件系统的目录浏览、文件的读取、文件的写入等操作。通过plus.io可获取文件系统管理对象
// // 文件系统中的读取文件对象,用于获取文件的内容
// var reader = new plus.io.FileReader();
// // 文件读取操作完成时的回调函数
// reader.onloadend = res => {
// console.log('res',res);
// this.tag = res.target
// console.log('tag',this.tag);
// var speech = res.target.result; //base64图片
// this.imgData = speech;
// };
// //一定要使用plus.io.convertLocalFileSystemURL将target地址转换为本地文件地址,否则readAsDataURL会找不到文件
// reader.readAsDataURL(plus.io.convertLocalFileSystemURL(zipRes.target));
// }, 1000);
// },
// function(error) {
// console.log('Compress error!', error);
// }
// );
// },
// 通过直播推流获取到的人脸数据,调用百度云api,判断是否本人
async identityFun(){
console.log('0000000');
let user = Object.assign({},this.user)
const str = btoa(encodeURIComponent(JSON.stringify(user)))
console.log('11111');
// name值设置好,nodejs中接收处理图片需要用到
// 上传图片这里,本来想用FormData,但是在uniapp中不行,大哥们,有没有知道其他方式的,麻烦告诉一下
uni.uploadFile({
url:'http://192.168.55.49:3000/api/login/identity/face',
filePath:this.src,
name:'faceImg',
formData:{str},
success: (uploadFileRes) => {
console.log('uploadFileRes',uploadFileRes);
this.face = uploadFileRes
},
fail:(err) => {
console.log('err',err);
}
})
console.log('22222');
}
},
};
</script>
3.2.nodejs中:
// 前端传来了图片,nodejs接收图片,需要下载nulter包来处理接收
const multer = require('multer')
let date = new Date()
// 实名认证接口要求的其中一个参数就是图片,我选择了base64格式数据的图片数据,需要导入fs和path模块来读取文件和路径问题
const fs = require('fs')
const path = require('path')
let time = date.getTime()
let originalname;
let urlStr;
let obj;
let arr = [];
let faceUrl = ''
// 这里是multer包给出的方法,详细去搜文档看看,我也忘了
let storage = multer.diskStorage({
// 在这里确定图片保存到哪个文件夹
destination: function(req, file, cb) {
// console.log('req',req.body);
cb(null, 'faceUpload');
},
// 在这里处理其他想处理的问题,我在这处理的是文件名修改
filename: function(req, file, cb) {
// let user = JSON.parse(decodeURIComponent(atob(req.body.str)))
// Buffer.from创建一个用指定字符串、数组或缓冲区填充的新缓冲区 file.originalname
urlStr = time + '-' + req.body.str + '.jpg'
originalname = Buffer.from(urlStr, "latin1").toString("utf8"); // 解决接收文件的文件名中文乱码问题
// 设置这个地址是因为在app.js已经做了静态资源托管,'http://localhost:3000/upload/'这个地址再加上文件名,前端就可以进行访问了
faceUrl = 'http://localhost:3000/upload/' + originalname
obj = {
faceUrl,
}
arr.push(obj)
cb(null, originalname)
}
})
// 记得diskStorage用完后,放到multer里面来
let upload = multer({ storage: storage });
处理完接收图片的操作后,处理请求
// array处理前端上传多张图片 single处理前端上传单张图片
router.post('/identity/face',upload.single('faceImg'),async(req,res) => {
try {
console.log('---------------------body---------------------',req.body);
console.log('---------------------file---------------------',req.file);
let user = req.body.str
let file = req.file
fs.readFile(path.join(__dirname,`../faceUpload/${urlStr}`),async (err,data) => {
if(err) {
return console.log('login-identity-readfile-err',err);
}
console.log('read-file-data',data);
user = JSON.parse(decodeURIComponent(atob(req.body.str)))
// 下面这两个都是百度云接口要求参数,详细看百度云接口文档
user.image_type = 'BASE64'
user.image = data
console.log('user,',user);
let token = await identity_face.identityFun()
const identity = await axios({
url:`https://aip.baidubce.com/rest/2.0/face/v3/person/verify?access_token=${token.data.access_token}`,
method:'POST',
header:{'Content-Type': 'application/json' },
data:user
})
console.log('identity--face',identity);
})
} catch(err) {
console.log('identity-catch',err);
return res.status(500)
}
})