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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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?!癝taking經濟”在 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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
藍藍設計的小編 http://www.syprn.cn