1. <!-- 弹出框 --> <el-dialog :title="titleMap[dialogStatus]" :visible.sync="dialogFormVisible"> <el-form :model="form" :rules="rules" ref="form"> <el-form-item label="商品名称" prop='goodsName' style="width:40%"> <el-input v-model="form.goodsName" auto-complete="off" placeholder="请输入商品名称" size="medium"></el-input> </el-form-item> <el-form-item label="商品图片" prop='goodsImg'> <el-upload action="uploadAction" list-type="picture-card" :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :limit="1" :show-file-list="true" name="img" ref="upload" :data="form" accept="image/png,image/gif,image/jpg,image/jpeg" :headers="headers" :on-exceed="handleExceed" :auto-upload="false" :on-error="uploadError" :before-upload="handleBeforeUpload" :on-change="changeFile"> <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogVisible"> <img width="50px" :src="dialogImageUrl" alt=""> </el-dialog> </el-form-item> <el-form-item label="商品对应积分" prop='goodsIntegral'> <el-input v-model="form.goodsIntegral" auto-complete="off" placeholder="请输入积分" size="medium" type="number"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="cancel">取 消</el-button> <el-button type="primary" @click="uploadFileFun('form')">确 定</el-button> "注意uploadFileFun('form') 括号内记得带引号不然可能会报validate is not defined" </div> </el-dialog>
2.data部分
data() { return { //新增和编辑弹框标题 titleMap: { addStore: '新增商品', editStore: "修改商品" }, dialogStatus: "", //新增编辑弹出框 dialogFormVisible: false, dialogVisible: false, //表格数据 tableData: [], form: { id: '', goodsName: '', goodsImg: '', goodsIntegral: '', }, rules: { goodsName: [{ required: true, message: '请输入商品名称', trigger: 'blur' }], goodsIntegral: [{ required: true, message: '请输入商品积分', trigger: 'blur' }], }, headers: { Authorization: 'Bearer ' + window.localStorage.getItem("token") }, dialogImageUrl: '', //图片回显 uploadFiles: '', //formData img 文件 goodsId: '', //判断是新增还是编辑 },
// 上传文件之前的钩子 handleBeforeUpload(file) { console.log('按钮', this.titleMap) if (!(file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/jpg' || file.type === 'image/jpeg')) { this.$notify.warning({ title: '警告', message: '请上传格式为image/png, image/gif, image/jpg, image/jpeg的图片' }) } let size = file.size / 1024 / 1024 / 2 if (size > 2) { this.$notify.warning({ title: '警告', message: '图片大小必须小于2M' }) } }, //图片上传超过数量限制 handleExceed(files, fileList) { this.$message.error("上传图片不能超过1张!"); }, handleRemove(file, fileList) { this.$message.error("删除成功!"); }, // 图片上传失败时 uploadError() { this.$message.error("图片上传失败!"); }, //预览图片 handlePictureCardPreview(file) { this.dialogImageUrl = file.url; this.dialogVisible = true; }, //文件改变时 on-change方法 ,fileList[0].raw 就是传给后端的值 //filelist这个对象里面有很多属性,我们上传文件时,实际上传的是filelist列表中每一项的raw,只有raw可以正常上传, 获取到文件后,需要定义变量保存文件。所以需要获取filelist中的raw进行保存。 //这里我用的formdata上传多文件,console打印formdata,文件在控制台中显示的格式为binary。 changeFile(file, fileList) { this.uploadFiles = fileList[0].raw }, uploadFileFun(formName) { this.$refs[formName].validate((valid) => { let fd = new FormData(); fd.append('id', this.form.id); fd.append('goodsName', this.form.goodsName); fd.append('goodsIntegral', this.form.goodsIntegral); fd.append('img', this.uploadFiles); let config = { headers: { 'Content-Type': 'multipart/form-data' } } 根据goodsID判断是编辑提交还是新增提交,主要针对新增编辑使用同一个弹框 if (valid && !this.goodsId) { this.$axios.post("接口地址", fd, config).then(res => { if (res.data.success == true) { this.dialogFormVisible = false this.$message({ message: res.data.msg, type: 'success' }); //新增完清空表单内容 setTimeout(() => { this.$refs.form.resetFields(); }, 200) this.reload() } else { this.$message.error(res.data.msg); } }).catch(res => { console.log(res) }) } else { this.$axios.post("接口地址", fd, config).then(res => { if (res.data.success == true) { this.dialogFormVisible = false this.$message({ message: res.data.msg, type: 'success' }); //编辑完清空表单内容 setTimeout(() => { this.$refs.form.resetFields(); }, 200) this.reload() } else { this.$message.error(res.data.msg); } }).catch(res => { console.log(res) }) } }) }, add() { this.dialogStatus = "addStore" this.dialogFormVisible = true this.goodsId = "" //新增商品是商品ID为空 }, // 取消 cancel() { this.dialogFormVisible = false this.$refs[formName].resetFields() }, //编辑 handleEdit(index, row) { this.form = this.tableData[index] this.dialogStatus = "editStore" this.goodsId = row.id this.currentIndex = index this.dialogFormVisible = true },
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
在我上一篇写的Node.js实现简单的POST请求
里面POST请求接受参数需要写两个事件,这难免有些不太方便
如果我们用formidable来接受参数的话,会变得特别方便。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <form class="upload" action="shangchuan" enctype="multipart/form-data" method="post"><!-- 上传接口是/shangchuan --> <p> 请上传一个头像 <input type="file" name="wenjian"> </p> <p> <input type="submit" value="提交"> </p> </form> </body> </html>
![]()
安装依赖,执行下面这三句npm语句
npm install finalhandler --save
npm install serve-static --save
npm install formidable --save
之后会自动生成下面这个package.json文件
{
"dependencies": {
"finalhandler": "^1.1.1",
"formidable": "^1.2.1",
"serve-static": "^1.13.2"
}
}
var finalhandler = require('finalhandler') var http = require('http') var serveStatic = require('serve-static') var url = require('url') var fs = require('fs') var querystring = require('querystring') var formidable = require('formidable') var path = require('path') // Serve up public/ftp folder //配置静态资源服务器,将public文件夹静态化出来 var serve = serveStatic('public', {'index': ['index.html', 'index.htm']}) // Create server var server = http.createServer(function onRequest (req, res) { //路由 var pathname = url.parse(req.url).pathname; if(pathname == '/shangchuan'){ //创建一个表单的实例,formidable var form = new formidable.IncomingForm(); //设置上传的文件存放在哪里 form.uploadDir = './uploads'; //处理表单 form.parse(req,(err,fields,files) => { //fields 表示普通控件 //files 表示文件控件 if(!files.wenjian){ return; } if(!files.wenjian.name){ return; } var extname = path.extname(files.wenjian.name);获取文件的扩展名,便于下面修改上传后的文件名字 //改名 fs.rename(files.wenjian.path, files.wenjian.path + extname,() => { res.end('上传成功') }) // console.log(fields); }) return; } serve(req, res, finalhandler(req, res)) }) // Listen server.listen(3000); console.log('服务已经启动在3000端口');
![]()
会看到这个页面
然后选择任意文件点击提交
会发现在很短的时间内你的文件会提交成功在你的uploads文件夹下。
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
model
: 绑定整个表单model值
rules
: 整个表单校验规则
ref
:获取该表单form组件
prop
: 绑定每个表单的规则,写在el-form-item
上
validate
: 对整个表单进行校验的方法
valid
: 每个必填表单项都提交为true,否则为false
//使用element-ui 页面组件 <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="活动名称" prop="name"> <el-input v-model="ruleForm.name"></el-input> </el-form-item> <el-form-item label="活动区域" prop="region"> <el-select v-model="ruleForm.region" placeholder="请选择活动区域"> <el-option label="区域一" value="shanghai"></el-option> <el-option label="区域二" value="beijing"></el-option> </el-select> </el-form-item> </el-form> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button> </el-form-item> //data中定义form表单中每项表单model值、和每个表单校验的规则 <script> export default { data() { return { ruleForm: { name: '', region: '', }, rules: { name: [ { required: true, message: '请输入活动名称', trigger: 'blur' }, { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } ], region: [ { required: true, message: '请选择活动区域', trigger: 'change' } ], }; }, //定义表单提交方法 methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { alert('submit!'); } else { console.log('error submit!!'); return false; } }); }, } }
<el-form v-if="fromHtmlList.deviceInfo.sitsb.length" :model="fasj" :rules="rules" label-position="left" label-width="125px" ref='sitsb'> <el-form-item label-width="100%" > <div slot="label" class="titleInfo">局端设备</div> </el-form-item> <el-form-item v-for="(item, i) in fromHtmlList.deviceInfo.sitsb" :key="`F_key_${i}`" :label="`${item.label}:`" :class="item.class" :label-width="item.labelWidth" :prop="`${item.rule ? 'deviceInfo.sitsb.'+item.rule : ''}`"> <el-select v-if="item.el === 'slt'" v-model="fasj.deviceInfo.sitsb[item.model]" placeholder="请选择" style="width:200px;" clearable> <el-option v-for="(op, n) in options[item.option]" :key="`vop_key_${n}`" :label="op.label" :value="op.value"> </el-option> </el-select> <el-input v-if="item.el === 'ipt'" v-model="fasj.deviceInfo.sitsb[item.model]" @focus="()=>{ item.focus && handleFocus('sitsb', item.model)}" :placeholder="`${item.placeholder ? item.placeholder : '请输入'}`" style="width:200px;"></el-input> <span v-if="item.el === 'txt'">{{fasj.base[item.model]}}</span> </el-form-item> </el-form> <div class="btn-list"> <el-button type="primary" @click="checkRequired">提 交</el-button> </div> <script> export default { data() { return { fasj: { base: {}, // 基本信息 deviceInfo: {}, }, //定义 页面表单结构 fromHtmlList: { baseInfo: [], deviceInfo: { ywzsb: [], sitsb: [] } }, //定义 表单校验规则 rules: { 'deviceInfo.sitsb.IPType': [ { required: true, message: '请输入', trigger: 'blur' } ], 'deviceInfo.sitsb.IPAddr': [ { required: true, message: '请输入', trigger: 'blur' } ], 'deviceInfo.sitsb.netmask': [ { required: true, message: '请输入', trigger: 'blur' } ], 'deviceInfo.sitsb.vlanId': [ { required: true, message: '请输入', trigger: 'blur' } ], }, options: { IPType: [ {label: 'ipv4', value: 'ipv4'}, {label: 'ipv6', value: 'ipv6'} ], rmSite: [], room: [], }, } }, created(){ const orderType = '业务开通' ;//页面初始加载用到的变量值,可通过组件传值获取 //created中初始定义页面表单结构 this.fromHtmlList = { baseInfo: orderType === '业务开通' ? [ {label: '接入方式', labelWidth: '100px', rule: '' , model: 'serviceType', option: '', el: 'txt', class: 'w2'}, {label: '标准九级地址', labelWidth: '120px', rule: '', model: 'aendAdreesName', option: '', el: 'txt', class: 'w2'}, {label: '城乡类型', labelWidth: '100px', rule: 'cityType', model: 'cityType', option: 'cityType', el: 'slt', class: 'w2'}, {label: '方案设计附件', labelWidth: '120px', rule: '', model: 'fileId', option: '', el: 'btn', class: 'w2'}, ] : orderType === '移机' ? [ {label: '接入方式', labelWidth: '100px', rule: '' , model: 'serviceType', option: '', el: 'txt', class: 'w2'}, {label: '标准九级地址', labelWidth: '120px', rule: '', model: 'aendAdreesName', option: '', el: 'txt', class: 'w2'}, {label: '城乡类型', labelWidth: '100px', rule: 'cityType', model: 'cityType', option: 'cityType', el: 'slt', class: 'w2'}, {label: '方案设计附件', labelWidth: '120px', rule: '', model: 'fileId', option: '', el: 'btn', class: 'w2'}, {label: '移机场景', labelWidth: '100px', rule: 'moveScene', model: 'moveScene', option: 'moveScene', el: 'slt', class: 'w2'}, {label: '是否需要网络调整', labelWidth: '', rule: 'netChange', model: 'netChange', option: 'netChange', el: 'slt', class: 'w2'} ] : [ {label: '接入方式', labelWidth: '100px', rule: '' , model: 'serviceType', option: '', el: 'txt', class: 'w2'}, {label: '标准九级地址', labelWidth: '120px', rule: '', model: 'aendAdreesName', option: '', el: 'txt', class: 'w2'}, {label: '城乡类型', labelWidth: '100px', rule: 'cityType', model: 'cityType', option: 'cityType', el: 'slt', class: 'w2'}, {label: '方案设计附件', labelWidth: '120px', rule: '', model: 'fileId', option: '', el: 'btn', class: 'w2'}, {label: '是否需要网络调整', labelWidth: '', rule: 'netChange', model: 'netChange', option: 'netChange', el: 'slt', class: 'w2'}, {label: '是否更换末端设备', labelWidth: '', rule: 'devChange', model: 'devChange', option: 'devChange', el: 'slt', class: 'w2'} ], deviceInfo: { ywzsb: serviceType === 'PON' ? [ // 默认PON {label: '机房', labelWidth: '90px', rule: '' , model: 'siteRoom', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: '设备名称', labelWidth: '90px', rule: '', model: 'deviceName', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: '设备端口', labelWidth: '90px', rule: '', model: 'devicePort', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: '延伸设备', labelWidth: '90px', rule: '', model: 'deviceExtend', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'} ] : [ // 裸光纤、传输下沉、传输延伸(ywzsb) {label: '机房', labelWidth: '', rule: '' , model: 'siteRoom', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: '站点', labelWidth: '', rule: '', model: 'site', option: '', el: 'ipt', class: 'w2'}, {label: '设备名称', labelWidth: '', rule: '', model: 'deviceName', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: '设备端口', labelWidth: '', rule: '', model: 'devicePort', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, ], sitsb: serviceType === 'PON' ? [ {label: '机房名称', labelWidth: '', rule: '' , model: 'siteRoom', option: '', el: 'ipt', class: 'w2'}, {label: '站点名称', labelWidth: '', rule: '', model: 'site', option: '', el: 'ipt', class: 'w2'}, {label: '汇聚交换机', labelWidth: '', rule: '', model: 'switch', option: '', el: 'ipt', class: 'w2'}, {label: '汇聚交换机端口', labelWidth: '', rule: '', model: 'switchPort', option: '', el: 'ipt', class: 'w2'}, {label: 'SR/BARS', labelWidth: '', rule: '' , model: 'srbars', option: '', el: 'ipt', class: 'w2'}, {label: 'SR/BARS端口', labelWidth: '', rule: '', model: 'srbarsPort', option: '', el: 'ipt', class: 'w2'}, {label: 'IP地址类型', labelWidth: '', rule: 'IPType', model: 'IPType', option: 'IPType', el: 'slt', class: 'w2'}, {label: 'IP地址', labelWidth: '', rule: 'IPAddr', model: 'IPAddr', option: '', el: 'ipt', class: 'w2'}, {label: '子网掩码', labelWidth: '', rule: 'netmask', model: 'netmask', option: '', el: 'ipt', class: 'w2'}, {label: 'VLAN ID', labelWidth: '', rule: 'vlanId', model: 'vlanId', option: '', el: 'ipt', class: 'w2'} ] : serviceType === '裸光纤' ? [ // 裸光纤 {label: '机房名称', labelWidth: '', rule: '' , model: 'siteRoom', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: '站点名称', labelWidth: '', rule: '', model: 'site', option: '', el: 'ipt', class: 'w2'}, {label: 'SR', labelWidth: '', rule: '', model: 'SR', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: 'SR端口', labelWidth: '', rule: '', model: 'SRPort', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: 'BARS', labelWidth: '', rule: '' , model: 'BARS', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, {label: 'BARS端口', labelWidth: '', rule: '', model: 'BARSPort', option: '', el: 'ipt', class: 'w2', focus: true, placeholder: '点击查询'}, ] : [ // 传输下沉、传输延伸 ] } } }, methods:{ async checkRequired() { let checkName = ['base','sitsb'];//表单校验定义ref let text; for(let key of checkName){ if(this.$refs[key]){ this.$refs[key].validate((valid) => { if(!valid){ text = '请检查必填项'; }else { return false; } }); } } if(text){ this.$message.warning(text); return false; } let _res_ = await this.saveFasjData('', true);//页面表单要保存后才能提交 if(!!_res_ && _res_.code === 200){ this.commit(_param, _url);//保存表单再提交 }else{ this.$message.error(_res_.msg) } }, async commit(param, url) { this.loadings.body = true; let _res = await postDataRequest(url, param); this.loadings.body = false; if (_res && _res.code === 200) { this.$message.success("提交成功"); this.$router.go(-1); } } } } </script>
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
Vite 是 vue 的作者尤雨溪在开发 vue3.0 的时候开发的一个 web 开发构建工具。由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动。
Vite 优势:
Vite 劣势:
其他区别:
1. 打包原理的区别
2. 项目入口文件的区别
项目根目录的 index.html 是 Vite 项目的入口文件,而 webpack 的入口文件是 webpack 配置 entry 中指定的 js 文件。
Esbuild
是一款基于 Go
语言开发的 javascript
打包工具,最大的一个特征就是快。
通过官网提供的一张图,我们可以清晰的看到 Esbuild
的表现是多么优秀:
Esbuild
之所以能这么快,主要原因有两个:
Go
语言开发,可以多线程打包,代码直接编译成机器码;
Webpack
一直被人诟病构建速度慢,主要原因是在打包构建过程中,存在大量的 resolve
、load
、transform
、parse
操作(详见 为什么有人说 vite 快,有人却说 vite 慢?- 快速的冷启动 ),而这些操作通常是通过 javascript
代码来执行的。要知道,javascript
并不是什么高效的语言,在执行过程中要先编译后执行,还是单线程并且不能利用多核 cpu
优势,和 Go
语言相比,效率很低。
可充分利用多核 cpu
优势;
关键 API - transfrom & build
Esbuild
并不复杂,它对外提供了两个 API:
transform
和 build
,使用起来非常简单。
transfrom
,转换的意思,将 ts
、jsx
、tsx
等格式的内容转化为 js
。 transfrom
只负责文件内容转换,并不会生成一个新的文件。
build
,构建的意思,根据指定的单个或者多个入口,分析依赖,并使用 loader
将不同格式的内容转化为 js 内容,生成一个或多个 bundle
文件。
我们来看看Vite
是怎么利用 Esbuild
来做预构建和内容转换的。
预构建
为什么要做预构建?原因有两点:
将非 ESM
规范的代码转换为符合 ESM
规范的代码;
将第三方依赖内部的多个文件合并为一个,减少 http
请求数量;
middlewares 中内容转换
Vite
中源文件的转换是在 dev server
启动以后通过 middlewares
实现的。
当浏览器发起请求以后,dev sever
会通过相应的 middlewares
对请求做处理,然后将处理以后的内容返回给浏览器。
middlewares
对源文件的处理,分为 resolve
、load
、transform
、parser
四个过程:
resolve
- 解析 url
,找到源文件的绝对路径;
load
- 加载源文件。如果是第三方依赖,直接将预构建内容返回给浏览器;如果是业务代码,继续 transform
、parser
。
transfrom
- 对源文件内容做转换,即 ts
-> js
, less
-> css
等。转换完成的内容可以直接返回给浏览器了。
parser
- 对转换以后的内容做分析,找到依赖模块,对依赖模块做预转换 - pre transform
操作,即重复 1
- 4
。
pre transform
是 Vite
做的一个优化点。预转换的内容会先做缓存,等浏览器发起请求以后,如果已经完成转换,直接将缓存的内容返回给浏览器。
Vite
在处理步骤 3
时,是通过 esbuild.transform
实现的,对比 Webpack
使用各个 loader
处理源文件,那是非常简单、快捷的。
经验法则:对于应用使用 webpack,对于类库使用 Rollup。
如果你需要代码拆分(Code Splitting),或者你有很多静态资源需要处理,再或者你构建的项目需要引入很多CommonJS模块的依赖,那么 webpack 是个很不错的选择。
如果您的代码库是基于 ES2015 模块的,而且希望你写的代码能够被其他人直接使用,你需要的打包工具可能是 Rollup 。
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
-
<div class="add">
-
<p>商品名称: <input type="text" class="productName"></p>
-
<p>商品价格: <input type="number" class="price" ></p>
-
<!-- multiple: 允许同时上传多张图片 -->
-
<!-- <p>商品图片: <input type="file" class="file" multiple="multiple"></p> -->
-
<p>商品图片: <input type="file" class="file"></p>
-
<p><button class="add-btn">添加商品</button></p>
-
</div>
-
<script src="js/axios.js"></script>
-
<script src="js/util.js"></script>
-
<script src="js/upload.js"></script>
-
// 新建表单数据对象,用来存储上传的文件及上传的其它数据
-
let param = new FormData()
-
-
$(".file").onchange = function(){
-
//获取图片信息
-
let file = this.files[0]
-
//"file"为前后端约定vb的属性名
-
param.append("file",file)
-
}
-
$(".add-btn").onclick = function(){
-
let productName = $(".productName").value;
-
let price = $(".price").value;
-
param.append("productName",productName)
-
param.append("price",price)
-
-
axios.post("/product",param,{
-
headers: {
-
// 默认提交的类型
-
// "content-type": "application/json"
-
// 复杂的表单数据(只要上传文件,就必须是下面的类型)
-
"content-type": "multipart/form-data"
-
}
-
})
-
.then((res)=>{
-
console.log(res.data);
-
})
-
}
下载
npm i multer -S
引入
-
const multer = require("multer")
-
const path = require("path")
配置
注意: 该文件在/router文件夹中,而uploads存放上传图片文件夹在根目录
-
var storage = multer.diskStorage({
-
// 配置文件上传后存储的路径
-
destination: function (req, file, cb) {
-
// NodeJS的两个全局变量
-
// console.log(__dirname); //获取当前文件在服务器上的完整目录
-
// console.log(__filename); //获取当前文件在服务器上的完整路径
-
cb(null, path.join(__dirname,'../uploads'))
-
},
-
// 配置文件上传后存储的路径和文件名
-
filename: function (req, file, cb) {
-
console.log('file',file);
-
cb(null, Date.now() + path.extname(file.originalname))
-
}
-
})
-
var upload = multer({ storage: storage })
在路由中使用
-
//添加商品
-
router.post("/product",upload.single("file1"),(req,res)=>{
-
//接收普通文本参数
-
let {productName,price} = req.body;
-
、接收上传文件数据 -->
-
let imgUrl = req.file.filename;
-
let sql = "insert into product (productName,price,imgUrl) values (?,?,?)"
-
conn.query(sql,[productName, price , imgUrl],(err,result)=>{
-
if (err){
-
console.log('增加失败');
-
return;
-
}
-
let data;
-
if (result.affectedRows === 1){
-
data = {
-
code: 0,
-
msg: '添加成功'
-
}
-
}else{
-
data = {
-
code: 1,
-
msg: '添加失败'
-
}
-
}
-
res.send(data)
-
})
-
})
如果抽离路由模块中的处理函数upload.single("file1")写在Router模块
实现写在抽取的模块
如果req.body为空,可以用nodejs后台文件上传模块connect-multiparty
使用方法如下:
1. 安装模块
npm install connect-multiparty --save
2. 引入模块
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
3. 使用模块
const express = require('express')
const router = express.Router()
router.post('/formdata',multipartMiddleware, function (req,res) {
console.log(req.query)
//分别返回body,文件属性,以及文件存放地址
});
————————————————
原文链接:https://blog.csdn.net/zjwengyidong/article/details/51407903
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
在HappyBirthday/HappyBirthday.html中的title换成相应人的名字
-
<head>
-
<meta charset="utf-8">
-
<title>XXX生日快乐</title>
-
-
<style>
-
html,body{
-
margin:0px;
-
width:100%;
-
height:100%;
-
overflow:hidden;
-
background:linear-gradient(to left top,blue, #ffc0cb);
-
}
-
</style>
-
<link href="favicon.ico" rel="shortcut icon">
-
</head>
在粒子展示祝福的名字进行更换
在HappyBirthday/js/index.js#44行处修改
-
if (i !== -1) {
-
S.UI.simulate(decodeURI(action).substring(i + 3));
-
} else {
-
S.UI.simulate('|#countdown 3||祝|XXX|生日快乐|祝你|生日快乐|祝你幸福|祝你健康|前途光明|祝你|生日快乐!|#icon heart|#icon heart-empty|#icon heart');
-
}
修改粒子动画展示的颜色,视频中使用了粉色(255,192,203)
HappyBirthday/js/index.js#417行处修改
-
S.Dot = function (x, y) {
-
this.p = new S.Point({
-
x: x,
-
y: y,
-
z: 5,
-
a: 1,
-
h: 0
-
});
-
-
this.e = 0.07;
-
this.s = true;
-
-
this.c = new S.Color(255, 192, 203, this.p.a);
-
-
this.t = this.clone();
-
this.q = [];
-
};
在原版代码中,仅仅在电脑浏览器有一个较为好的展示效果,在手机浏览器上字显示效果不佳以及延时不足,但是无法正常显示,主要调整了粒子间距和延时时间
粒子间距:先设置默认间距为8(手机较好显示),然后判断屏幕是否大于手机一般尺寸,调整大一点13(平板和电脑较好显示)。
粒子间距变小,数量变多,加载起来就慢。
HappyBirthday/js/index.js#525行处修改
-
if ((window.innerWidth>500 && window.innerHeight>500)){
-
gap = 13;
-
}
延时时间:当粒子数量变多,加载慢, 按照原作者设置的时间来展示,可能上一个字没展示完就要去展示下一个字,导致变成一坨。
HappyBirthday/js/index.js#119行处修改
HappyBirthday/js/index.js#177行处修改
-
// 118行
-
var delay1,delay2;
-
delay1 = 3000;
-
delay2 = 5000;
-
-
-
// 177行
-
if (window.innerWidth>500 && window.innerHeight>500){
-
delay1 = 1000;
-
delay2 = 2000;
-
}
由于在某些设备上,无法自动播放音乐,需要通过点击触发,增加点击爱心,开始播放。
通过部署在阿里云,可以通过网址进行访问。
我租了一个阿里云,通过简单部署静态页面就可以访问。
(如果有兄弟紧急使用,也可以叫我帮忙部署一下,哈
找到自己的实例,点击完全组,配置开放一个80端口
开放80端口
yum -y install httpd
-
service httpd start
-
service httpd status
启动之后可以看到如下画面
默认会发布var/www/html下面的网页
cp /etc/httpd/conf/httpd.conf /var/www/html
# 解压压缩包 unzip HappyBirthday.zip # 删除压缩包 rm -rf HappyBirthday.zip
service httpd restart
http://8.130.106.21/HappyBirthday/HappyBirthday.html
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
目录
2. getCurrentInstance 组合式API引入
如果只是简单几个页面的使用,无需太过复杂的配置就可以直接再 main.js 中进行挂载
-
import Vue from "vue";
-
-
/* 第一步下载 axios 命令:npm i axios 或者yarn add axios 或者pnpm i axios */
-
/* 第二步引入axios */
-
import axios from 'axios'
-
-
-
// 挂载一个自定义属性$http
-
Vue.prototype.$http = axios
-
// 全局配置axios请求根路径(axios.默认配置.请求根路径)
-
axios.defaults.baseURL = 'http://yufei.shop:3000'
-
页面使用
-
methods:{
-
-
-
getData(){
-
-
this.$http.get('/barry').then(res=>{
-
-
console.log('res',res)
-
)}
-
}
-
-
}
① 新建 util/request.js (配置全局的Axios,请求拦截、响应拦截等)
关于 VFrame 有疑问的同学可以移步 前端不使用 il8n,如何优雅的实现多语言?
-
import axios from "axios";
-
import { Notification, MessageBox, Message } from "element-ui";
-
import store from "@/store";
-
import { getToken } from "@/utils/auth";
-
import errorCode from "@/utils/errorCode";
-
import Cookies from "js-cookie";
-
import VFrame from "../framework/VFrame.js";
-
import CONSTANT from '@/CONSTANT.js'
-
-
axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
-
// 创建axios实例
-
const service = axios.create({
-
// axios中请求配置有baseURL选项,表示请求URL公共部分
-
baseURL: process.env.VUE_APP_BASE_API,
-
// 超时
-
timeout: 120000
-
});
-
// request拦截器
-
service.interceptors.request.use(
-
config => {
-
// 是否需要设置 token
-
const isToken = (config.headers || {}).isToken === false;
-
if (getToken() && !isToken) {
-
config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
-
}
-
var cultureName = Cookies.get(CONSTANT.UX_LANGUAGE);
-
if (cultureName) {
-
config.headers[CONSTANT.UX_LANGUAGE] = cultureName; // 让每个请求携带自定义token 请根据实际情况自行修改
-
}
-
// get请求映射params参数
-
if (config.method === "get" && config.params) {
-
let url = config.url + "?";
-
for (const propName of Object.keys(config.params)) {
-
const value = config.params[propName];
-
var part = encodeURIComponent(propName) + "=";
-
if (value !== null && typeof value !== "undefined") {
-
if (typeof value === "object") {
-
for (const key of Object.keys(value)) {
-
let params = propName + "[" + key + "]";
-
var subPart = encodeURIComponent(params) + "=";
-
url += subPart + encodeURIComponent(value[key]) + "&";
-
}
-
} else {
-
url += part + encodeURIComponent(value) + "&";
-
}
-
}
-
}
-
url = url.slice(0, -1);
-
config.params = {};
-
config.url = url;
-
}
-
return config;
-
},
-
error => {
-
console.log(error);
-
Promise.reject(error);
-
}
-
);
-
-
// 响应拦截器
-
service.interceptors.response.use(
-
res => {
-
// 未设置状态码则默认成功状态
-
const code = res.data.code || 200;
-
// 获取错误信息
-
const msg = errorCode[code] || res.data.msg || errorCode["default"];
-
if (code === 401) {
-
MessageBox.alert(
-
VFrame.l("SessionExpired"),
-
VFrame.l("SystemInfo"),
-
{
-
confirmButtonText: VFrame.l("Relogin"),
-
type: "warning"
-
}
-
).then(() => {
-
store.dispatch("LogOut").then(() => {
-
location.href = "/index";
-
});
-
});
-
} else if (code === 500) {
-
Message({
-
message: msg,
-
type: "error"
-
});
-
if (res.data.data) {
-
console.error(res.data.data)
-
}
-
return Promise.reject(new Error(msg));
-
} else if (code !== 200) {
-
Notification.error({
-
title: msg
-
});
-
return Promise.reject("error");
-
} else {
-
if (res.data.uxApi) {
-
if (res.data.success) {
-
return res.data.result;
-
} else {
-
Notification.error({ title: res.data.error });
-
console.error(res);
-
return Promise.reject(res.data.error);
-
}
-
} else {
-
return res.data;
-
}
-
}
-
},
-
error => {
-
console.log("err" + error);
-
let { message } = error;
-
if (message == "Network Error") {
-
message = VFrame.l("TheBackEndPortConnectionIsAbnormal");
-
} else if (message.includes("timeout")) {
-
message = VFrame.l("TheSystemInterfaceRequestTimedOut");
-
} else if (message.includes("Request failed with status code")) {
-
message =
-
VFrame.l("SystemInterface") +
-
message.substr(message.length - 3) +
-
VFrame.l("Abnormal");
-
}
-
Message({
-
message: VFrame.l(message),
-
type: "error",
-
duration: 5 * 1000
-
});
-
return Promise.reject(error);
-
}
-
);
-
-
export default service;
② 新建 api/login.js (配置页面所需使用的 api)
-
import request from '@/utils/request'
-
-
// 登录方法
-
export function login(username, password,shopOrgId,counter, code, uuid) {
-
const data = {
-
username,
-
password,
-
shopOrgId,
-
counter,
-
uuid
-
}
-
return request({
-
url: '/login',
-
method: 'post',
-
data: data
-
})
-
}
-
-
// 获取用户详细信息
-
export function getInfo() {
-
return request({
-
url: '/getInfo',
-
method: 'get'
-
})
-
}
-
-
// 退出方法
-
export function logout() {
-
return request({
-
url: '/logout',
-
method: 'post'
-
})
-
}
③ 页面使用引入
-
import { login } from "@/api/login.js"
-
-
接下来不用多说,相信大家已经会使用了
上面回顾完 Vue2 中使用 axios 我们来一起看看 Vue3 中axios的使用( 简单Demo,前台使用Vue3,后台使用 node.js ),仅供学习!
① main.js 中 使用 provide 传入
-
import {
-
createApp
-
} from 'vue'
-
import App from './App.vue'
-
import router from './router'
-
import store from './store'
-
import "lib-flexible/flexible.js"
-
-
import axios from "@/util/request.js"
-
-
const app = createApp(App);
-
-
-
-
app.provide('$axios', axios)
-
app.use(store).use(router).mount('#app');
② 需要用到的页面使用 inject 接受
-
import { ref, reactive, inject, onMounted} from "vue";
-
-
export default {
-
setup() {
-
-
const $axios = inject("$axios");
-
-
const getData = async () => {
-
data = await $axios({ url: "/one/data" });
-
console.log("data", data);
-
};
-
-
onMounted(() => {
-
-
getData()
-
-
})
-
-
-
return { getData }
-
-
}
-
-
}
这个就是借助 provide 做一个派发,和 Vue2 中的差距使用方法差距不大
① main.js 中挂载
-
import {
-
createApp
-
} from 'vue'
-
import App from './App.vue'
-
import router from './router'
-
import store from './store'
-
import "lib-flexible/flexible.js"
-
-
import axios from "@/util/request.js"
-
-
const app = createApp(App);
-
-
/* 挂载全局对象 */
-
app.config.globalProperties.$axios = axios;
-
-
-
app.use(store).use(router).mount('#app');
/* 挂载全局对象 */
app.config.globalProperties.$axios = axios;
重点就是上面这句
② 需要用的页面使用 Composition Api -- getCurrentInstance 拿到
-
<script>
-
import { reactive, onMounted, getCurrentInstance } from "vue";
-
export default {
-
setup() {
-
let data = reactive([]);
-
/**
-
* 1. 通过getCurrentInstance方法获取当前实例
-
* 再根据当前实例找到全局实例对象appContext,进而拿到全局实例的config.globalProperties。
-
*/
-
const currentInstance = getCurrentInstance();
-
const { $axios } = currentInstance.appContext.config.globalProperties;
-
-
/**
-
* 2. 通过getCurrentInstance方法获取上下文,这里的proxy就相当于this。
-
*/
-
-
const { proxy } = currentInstance;
-
-
-
const getData = async () => {
-
data = await $axios({ url: "/one/data" });
-
console.log("data", data);
-
};
-
-
const getData2 = async () => {
-
data = await proxy.$axios({ url: "/one/data" });
-
console.log("data2", data);
-
};
-
-
onMounted(() => {
-
-
getData()
-
-
});
-
return { getData };
-
},
-
};
-
</script>
下图可以看到我们确实调用了 2次 API
其实通过 Composition API 中的 getCurrentInstance 方法也是有两种方式的
1. 通过 getCurrentInstance 方法获取当前实例,再根据当前实例找到全局实例对象appContext,进而拿到全局实例的config.globalProperties。
const currentInstance = getCurrentInstance(); const { $axios } = currentInstance.appContext.config.globalProperties;2. 通过getCurrentInstance方法获取上下文,这里的proxy就相当于this。
const currentInstance = getCurrentInstance(); const { proxy } = currentInstance; const getData2 = async () => { data = await proxy.$axios({ url: "/one/data" }); console.log("data2", data); };
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
如今HTML5 移动应用或 Web app 中越来越普遍的使用了离线浏览技术,所以用 JavaScript 检测浏览器在线/离线状态非常常见。
无论浏览器是否在线,navigator.onLine
属性都会提供一个布尔值。 如果浏览器在线,则设置为 true
,否则设置为 false
。
if(navigator.onLine) { // true|false // ... }
online 和 offline 事件:
当浏览器脱机或上线时,浏览器还支持 online
和 offline
事件。
window.addEventListener('online', function(e){console.log('online')});
window.addEventListener('offline', function(e){console.log('offline');});
你可以使用几种熟悉的方式来注册事件:
window
,document
,或 document.body
上使用 addEventListener
document
或 document.body
的 ononline
或 onoffline
属性设置为一个 JavaScript Function 对象。(注意:由于兼容性原因,不能使用 window.ononline
或 window.onoffline
。)
body
标签上指定 οnοnline=”…” 或 οnοffline=”…” 特性。
注意事项:
实例代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用 JavaScript 检测浏览器在线/离线状态(JavaScript API — navigator.onLine)</title> <style type="text/css"> #status { position: fixed; width: 100%; font: bold 1em sans-serif; color: #FFF; padding: 0.5em; } #log { padding: 2.5em 0.5em 0.5em; font: 1em sans-serif; } .online { background: green; } .offline { background: red; } </style> </head> <body> <div id="status"></div> <div id="log"></div> <button type="button" id="test">检查状态</button> <script> window.addEventListener('load', function () { var testBtn = document.getElementById("test"); var status = document.getElementById("status"); var log = document.getElementById("log"); function updateOnlineStatus(event) { var condition = navigator.onLine ? "online" : "offline"; status.className = condition; status.innerHTML = condition.toUpperCase(); log.insertAdjacentHTML("beforeend", "Event: " + (event?event.type:"-") + "; Status: " + condition+ " | "); } window.addEventListener('online', updateOnlineStatus); window.addEventListener('offline', updateOnlineStatus); testBtn.addEventListener("click", updateOnlineStatus); updateOnlineStatus(); }); </script> </body> </html>![]()
总结:
1、navigator.online属性提供浏览器是否在线的布尔值
2、浏览器脱机或上线还支持online和offline事件(IE8需要给document.body绑定事件而不是window)
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
这里是iconfont的网址链接: iconfon官网
![]()
不 要 勾 选 彩 色 , 会 导 致 在 项 目 中 无 法 修 改 字 体 颜 色 及 样 式 , 本 人 亲 测 , 找 了 半 天 解 决 办 法 最 终 悔 恨 不 已 \textcolor{red} {不要勾选彩色,会导致在项目中无法修改字体颜色及样式,本人亲测,找了半天解决办法最终悔恨不已}不要勾选彩色,会导致在项目中无法修改字体颜色及样式,本人亲测,找了半天解决办法最终悔恨不已
选择自己需要的图标加入购物车再添加到项目中
下载项目并解压
将iconfont.css文件复制放到我们的项目中去,一般放在static静态文件目录下
需要注意的是,当我们在项目中新添加了图标后,需要重新复制修改iconfont.css中的内容,要不然新添加的图标是找不到的
在移动端引用的时候要在App.vue文件中进行全局注册,而不是main.js中
在开发中我们常用的有两种方式,这两种方式以及注意事项我在以下内容都有演示:
两种方式代码的获取方式如下图所示:
- 使用uniCode码
- Font Class 名称
使用iconfont图标的文件内容(忽略css样式):
需要配合static目录下的iconfont.tff文件,这个文件在我们下载到本地的时候那个目录中,与iconfont.css在一个目录中:
pages.json文件中配置iconfont图标:
这三步完成,我们配置自定义原生导航栏的自定义图标就完成啦!
iconfont.css
文件
iconfont.ttf
文件使用
作者:彩云sky
来源:人人都是产品经理
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
我们所说的共识,是指达成了广泛的一致。 比如,一群人去吃晚饭, 如果他们对于提议“吃某火锅”没有任何异议,那么就可以说共识达成了。 如果存在异议,那么他们就必须通过某种方法决定吃什么。 在极端情况下,这群人就会分开。
区块链要成为一个难以攻破的、公开的、不可篡改数据记录的去中心化诚实可信系统,需要在尽可能短的时间内做到分布式数据记录的安全、明确及不可逆,提供一个最坚实且去中心化的系统。共识机制在区块链中成为了重要的因素之一。
区块链分布式记账的方式使得每个人手上都有一本完整的账本,全网共有。但是随着节点的不断增多,数据越多,账本也越安全,难以摧毁。除此之外,任意一个或者部分节点的账本被篡改,都不可能被全网认同,除非你能控制51%的节点,即51%攻击,但是这耗能巨大,几乎是不可能的。同时随着节点不断增加,谁来记账,如何选择合适的人来记账成为一个问题,而制定一个记账人的选择方式以及规定,让大家来遵守这个规定,达成共识,这就是区块链里面的共识机制。
共识机制是区块链节点就区块信息达成全网一致共识的机制,说得更直白一些就是要解决所谓去中心化的信任问题,因为每个节点之间默认是不认识且不可靠的,同时每个节点都不能知道其他节点是否宕机或者背叛的情况下,尽可能的保证记录信息的准确性以及安全性。同时节点越分散,效率越低,网络对于信息的满意度越高,越安全。
区块链作为一种按时间顺序存储数据的数据结构,可支持不同的共识机制。共识机制是区块链技术的重要组件。区块链共识机制的目标是使所有的诚实节点保存一致的区块链视图,同时满足两个性质:
1)一致性:所有诚实节点保存的区块链的前缀部分完全相同。
2)有效性:由某诚实节点发布的信息终将被其他所有诚实节点记录在自己的区块链中。
在分布式系统中,各个不同的主机通过异步通信方式组成网络集群。为了保证每个主机达成一致的状态共识,就需要在主机之间进行状态复制。异步系统中,可能会出现各样的问题,例如主机出现故障无法通信,或者性能下降,而网络也可能发生拥堵延迟,类似的种种故障有可能会发生错误信息在系统内传播。因此需要在默认不可靠的异步网络中定义容错协议,以确保各主机达成安全可靠的状态共识。所以,利用区块链构造基于互联网的去中心化账本,需要解决的首要问题是如何实现不同账本节点上的账本数据的一致性和正确性。
这就需要借鉴已有的在分布式系统中实现状态共识的算法,确定网络中选择记账节点的机制,以及如何保障账本数据在全网中形成正确、一致的共识。
没有一种共识机制是完美无缺的,各共识机制都有其优缺点,有些共识机制是为解决一些特定的问题而生。
BTC作为区块链的第一个应用,它的共识机制PoW共识机制曾经一枝独秀,但是随着区块链技术的不断发展,各类不同的共识机制开始不断涌现,各有千秋,各有拥趸。
常见的共识就机制包括:POW(工作量证明机制)、POS(权益证明机制)、POW+POS(混合共识机制)、DPOS(股份授权证明)等等,另外还有Pool验证池、Ripple瑞波共识协议、PBFT(使用拜占庭容错算法)等等
最早(也是第一个)被应用的共识机制,最先被BTC采用并且获取了巨大成功,它支撑了BTC系统长达 10 多年无重大故障使其平稳运行。而且 PoW 构想也符合创始人中本聪最初的设想:人人皆可挖矿、按劳分配、公平公正。
PoW 属于按劳分配,多劳多得,就如同大家在BTC系统中一起进行数学运算,最先运算出的才能获得奖励。是一种衡量计算机工作量的共识机制。BTC使用的就是工作量证明机制。
工作量证明主要通过哈希计算找出合理数据的步骤来完成:将区块头数据带入哈希函数计算公式,不断调整区块头数据中的随机数,直到计算出满足特定标准的哈希值,工作量证明就会完成。
简单来说就是多劳多得,谁的算力强,计算得就更快,获得记账权的概率就越高,算力竞争的胜者将获得相应区块记账权和BTC奖励。 因此,矿机芯片的算力越高,挖矿的时间更长,就可以获得更多的数字货币。进行运算获得奖励的过程称之为挖矿,参与挖矿的人们称之为矿工。这种证明方式决定了其验证过程需要大量的数据计算,而其他节点却很容易验证计算结果是否正确,因此 区块链系统无法被恶意节点所欺骗。但是这种证明方式需要消耗大量能源(电力及计算硬件损耗),很不 环保。并且在理论上,如果集合了全网51%的算力即可对区块链网络进行有效攻击,因此许多基于比特币 代码产生的、市值较小的山寨币很容易遭受攻击。
代表token:BTC、BCH、LTC等。
因 PoW 存在的问题,PoS 在主流算法一路畅通的“杀了出来”,成为了最具有挑战者。近几年,基于 PoS共识打造的区块链项目越来越多,如目前市值保持第二的ETH也加入了 PoS。“Staking经济”在 2019年成为了热门词语,同时也被交易所和钱包大力追捧。
POS机制采用类似股权证明与投票的机制,选出记帐人,由它来创建区块。持有股权愈多则有较大的特权,且需负担更多的责任来产生区块,同时也获得更多收益的权力。 POS 机制中一般用币龄来计算记账权,每个币持有一天算一个币龄,比如 持有 100 个币,总共持有了 30 天,那么此时的币龄就为 3000。在 POS机制下,如果记账人发现一个 POS 区块, 他的币龄就会被清空为 0,每被清空 365 币龄,将会从区块中获得 0.05 个币的利息(可理解为年利率 5%)。
PoS权益证明同样需要通过计算找出合理的哈希值来完成。 但不同的是权益证明机制通过节点持有加密货币的时间和数量来判断节点的权益大小。根据权益大小不同,用户之间看到的计算目标值也不同。权益大的节点,获得目标值更加简单,更容易获得下一个区块的记账权。 这种方式不需要每个节点都进行大量的运算,节省了电力能源。同时全网51%的算力攻击在权益证明机制下是无效的,因为发起这种攻击反而会损害自身的利益。但是可能会出现币种持有数量大的节点权力过 大,对区块链记账享有绝对支配权的情况,容易引发信任问题。
在 PoS 机制中,是不需要消耗电力来进行运算,而是通过抵押 token 来获得打包区块的权利。当一笔交易发生时,系统会对打包区块和验证区块的节点来进行奖励,奖励则是增发或者解锁的 token。
代表token:ADA、ONT、ATOM等。
DPoS 机制是在 PoS 的基础上进行了改良,举例来说就是大家公认的投出选票,选举出一定数量的代表,让这些代表进行验证和记账等,可以理解为PoS 的升级版。与PoS的主要区别在于持币者投出一定数量的节点,代理他们进行验证和记账。其合规监管、性能、资源消耗和容错性与PoS相似。
DPoS的工作原理为:每个股东按其持股比例拥有影响力,51%股东投票的结果将是不可逆且有约束力的。其挑战是通过及时而高效的方法达到51%批准。为达到这个目标,每个股东可以将其投票权授予一名代表。获票数最多的前100位代表按既定时间表轮流产生区块。每名代表分配到一个时间段来生产区块。所有的代表将收到等同于一个平均水平的区块所含交易费的10%作为报酬。如果一个平均水平的区块含有100股作为交易费,一名代表将获得1股作为报酬。DPoS的投票模式可以每30秒产生一个新区块。
简单点说:DPoS 委托权益证明通过由持币人投票选举出一定数量的代表来达成共识。 每个持币人的投票所占的比重 与他持有的币种数量有关,持有的越多,所占的比重越大。被选出的代表可拥有记账权,轮流进行记账;未能很好履行职责的代表还会被投票除名。这一任期结束后,新的代表会再次通过投票产生。
代表token:EOS、TRX等。
PoC 机制早在 2014年存在了,但只是一直处于“落魄阶段”,简单说就是没火,无人问津。2019年随着POC一大公链Yottachain的崛起,越来越多的矿工加入了POC硬盘挖矿这个行业大军了。它是POW共识机制的一种,以硬盘作为共识参与者,它的特点是牺牲性能获得安全可信,相对POW减少了非常多的安全和信任成本,更低成本解决了全局信任和安全,几乎不耗电力资源,并且可共享和复用的信任生态。
PoC 机制是通过普通硬盘挖矿的共识机制。简单来说就是利用计算机硬盘中的闲置空间来进行存储进行挖矿获取收益,硬盘空间越大,存储的内容越多获得的收益就越大。 它更多地关注内存而不是处理能力。 从某种意义上说,这是对PoW的改进,即使在挖掘开始之前,容量证明也要求节点将预先计算的哈希值存储在其硬盘驱动器和其他内存单元上,这个过程称为绘图,绘图使容量证明成为比工作证明更快的机制。这种方法的另一个优点是它可以节省大量能源,这与工作量证明机制不同。更不用说,硬盘存储更多哈希值的任何技术改进也将为不在区块链中的人改进技术,这与许多制造商制造的专用芯片不同,后者除了采矿之外什么都不做。
IPFS 也类似,但不同的是 IPFS 衍生的区块链项目(激励层Filecoin)是一种去中心化存储服务的 Marketplace(撮合交易的市场),它的重点在于如何在系统参与者互不信任的条件下,实现存储和检索工作的量化;PoC 是一种底层共识机制,与 PoW、PoS一样都是去中心化网络达成一致性状态的算法。由此来看,两者是完全不同的概念,唯一的共同点就是都可以使用硬盘向网络贡献价值来换取收益。
代表token:BTT、BHD等。
有向无环图是计算机科学中众所周知的数据结构。事实上,区块链也是DAG的一个例子,因为它有一个明确的方向,没有任何循环,并且是一个图。1OTA使用的Tangle也是DAG共识机制的一种形式。在这种机制中,每个块必须有两个父块。所以,为了通过DAG共识机制完成一笔交易,用户需要验证自己之前的两笔交易。这种机制的最大优势是它可以减少延迟和交易费用。然而,这种共识模型对提高可扩展性几乎没有任何作用,而且极易受到攻击,因为任何攻击只需要34%的哈希算力就可以破坏系统。
DAG最初出现就是为了解决区块链的效率问题。其通过改变区块的链式存储结构,通过DAG的拓扑结构来存储区块。在区块打包时间不变的情况下,网络中可以并行的打包N个区块,网络中的交易就可以容纳N倍。
之后DAG发展成为脱离区块链,提出了blockless无区块的概念。新交易发起时,只需要选择网络中已经存在的并且比较新的交易作为链接确认,这一做法解决了网络宽度问题,大大加快了交易速度。
代表token:IOTA、byteball等。
前段时间国内首个基于DAG的物联网区块链项目ITC万物链也取得了不小的涨幅。
实用拜占庭容错在保证活性和安全性(liveness & safety)的前提下提供了(n-1)/3的容错性。
在分布式计算上,不同的计算机透过讯息交换,尝试达成共识;但有时候,系统上协调计算机(Coordinator / Commander)或成员计算机 (Member /Lieutanent)可能因系统错误并交换错的讯息,导致影响最终的系统一致性。拜占庭将军问题就根据错误计算机的数量,寻找可能的解决办法,这无法找到一个绝对的答案,但只可以用来验证一个机制的有效程度。而拜占庭问题的可能解决方法为:在 N ≥ 3F + 1 的情况下一致性是可能解决。其中,N为计算机总数,F为有问题计算机总数。信息在计算机间互相交换后,各计算机列出所有得到的信息,以大多数的结果作为解决办法。
实用拜占庭容错主要应用于央行的数字货币以及布萌区块链。
小蚁采用的dBFT机制,是由权益来选出记账人,然后记账人之间通过拜占庭容错算法来达成共识。dBFT和PBFT的关系类似于PoS和DPoS的关系。
dBFT在PBFT基础上进行了以下改进:
以上总结来说,dBFT机制最核心的一点,就是最大限度地确保系统的最终性,使区块链能够适用于真正的金融应用场景。
基于传统的分布式一致性技术,加上数据验证机制;之前曾是行业链大范围在使用的共识机制,但是随着私有链项目的逐渐减少渐渐开始势微。
不需要token也可以工作,在成熟的分布式一致性算法(Pasox、Raft)基础上,实现秒级共识验证。
去中心化程度不如bictoin;更适合多方参与的多中心商业模式。
拜占庭将军问题是一个协议问题,拜占庭帝国军队的将军们必须全体一致的决定是否攻击某一支敌军。问题是这些将军在地理上是分隔开来的,并且将军中存在叛徒。叛徒可以任意行动以达到以下目标:欺骗某些将军采取进攻行动;促成一个不是所有将军都同意的决定,如当将军们不希望进攻时促成进攻行动;或者迷惑某些将军,使他们无法做出决定。如果叛徒达到了这些目的之一,则任何攻击行动的结果都是注定要失败的,只有完全达成一致的努力才能获得胜利。
这一问题是一种对现实世界的模型化,尤指网络当中由于软硬件错误、网络阻塞及恶意攻击导致的各种未知行为。
显然,在此处默认了将军们在达成一致的过程中正确的传递出了自己的决定,也就是说叛徒只存在于将军当中,不存在于传令兵当中。故要让拜占庭将军问题有解,必须要具备一个重要前提,即信道必须是安全可靠的。关于信道可靠问题,会引出两军问题。两军问题的结论是,在一个不可靠的通信链路上试图通过通信以达成一致是基本不可能或者十分困难的。
拜占庭将军问题提出后,有很多的算法被提出用于解决这个问题。这类算法统称拜占庭容错算法(BFT: Byzantine Fault Tolerance)。简略来说,拜占庭容错(BFT)不是某一个具体算法,而是能够抵抗拜占庭将军问题导致的一系列失利的系统特点。 这意味着即使某些节点出现缺点或恶意行为,拜占庭容错系统也能够继续运转。本质上来说,拜占庭容错方案就是少数服从多数。
拜占庭将军问题的原始论文给出了一些解决思路,但其更注重理论上的可行性。算法效率不高,算法复杂度为指数级,且文中明确指出时间成本及消息传递数量很大。因此不具备太大的实用价值。
拜占庭容错系统需要达成如下两个指标:
● 安全性:任何已经完成的请求都不会被更改,它可以在以后请求看到。在区块链系统中,可以理解为,已经生成的账本不可篡改,并且可以被节点随时查看。
● 活性:可以接受并且执行非拜占庭客户端的请求,不会被任何因素影响而导致非拜占庭客户端的请求不能执行。在区块链系统中,可以理解为,系统需要持续生成区块,为用户记账,这主要靠挖矿的激励机制来保证。
拜占庭系统目前普遍采用的假设条件包括:
● 拜占庭节点的行为可以是任意的,拜占庭节点之间可以共谋;
● 节点之间的错误是不相关的;
● 节点之间通过异步网络连接,网络中的消息可能丢失、乱序、延时到达;
● 服务器之间传递的信息,第三方可以知晓 ,但是不能窜改、伪造信息的内容和验证信息的完整性;
作者:彩云sky
来源:人人都是产品经理
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
蓝蓝设计的小编 http://www.lanlanwork.com