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咨詢、用戶體驗公司、軟件界面設計公司
B 端設計離不開設計規范這個話題,而做好設計規范是一個龐大復雜工程,很多人對這些處于一知半解狀態,在這個系列文章里我通過結合自己平時的項目案例來談談自己對 B 端設計規范的一些理解,希望可以帶來一些啟發。
本篇先談談設計規范制作的指導思想--設計原則,后續文章再展開講一下常見各種組件的設計規范。
設計規范作為 B 端設計中非常重要的一環,它的作用主要體現在以下三個方面:
在日常工作中,當項目組收到一個新的需求時,如果已經具備了成熟的設計規范體系,其工作效率往往會得到很好的提升。最后上線的頁面不用走查還原度。以下是具體工作流程:
通過前面內容我們知道了設計規范對于產品設計意義重大,那么制定設計規范制定依據又是什么呢?這里就要引出設計原則這個話題,設計原則是設計規范的總的綱領,所有的設計規范都應當以設計原則為基準。設計原則主要包含以下內容:
接下來就圍繞設計原則清晰、高效、友好、可控這四個方面展開講解。
清晰原則主要從視覺角度讓界面信息傳達合理,提高用戶信息獲取效率。主要包含對比,親密,對齊,重復四個方面。
① 對比:
對比是指界面中為了區分信息層級,強化元素對比度,使用的很常見的一種手段,例如下圖中利用大色塊按鈕與線框按鈕形成對比來凸顯關鍵按鈕;又比如通過對文字字號加大,字體加粗,顏色加深來與弱文案形成對比,凸顯關鍵文字信息。
② 親密:
如果信息之間關聯性越高,它們之間的距離就應該越接近,也越像一個視覺單元;反之,則它們的距離就應該越遠,也越像多個視覺單元。親密性的根本目的是實現組織性,讓用戶對頁面結構和信息層次一目了然。
③ 對齊:
在界面設計中,將元素進行對齊,既符合了用戶的認知特點(我們往往傾向使知覺對象的直線延續還是直線,曲線延續還是曲線),也能引導視用戶視覺流,讓用戶更流暢地接收信息。
④ 重復:
重復是指相同的元素在項目中重復引用,作用是可以有效降低用戶的學習成本,同時提高這些元素之間的關聯性。
高效原則體現在便捷、輕量、簡化、一致幾個方面,目的是通過合理的方式讓產品操作更加便捷;交互體驗與內容更加輕量和簡化;以及產品風格保持一致。下面結合幾個常見案例說明如何應用這一原則。
① 合理利用拖拽--便捷、輕量:
在涉及到諸如上傳文件,排序,滑動輸入,搭建等需求時,合理采用拖拽交互往往可以打造更加便捷用戶體驗。
② 盡量減少不必要的跳轉--便捷、輕量:
用戶操作過程盡量減少跳轉,以實現交互減步長,從而使用戶操作更高效輕量。例如能用原位操作就不考慮展開收起;能用展開收起就不用氣泡...依次類推(優先級從高到低:原位 > 展開收起 > 氣泡 > 彈窗 > 抽屜 > 新頁面)
③ 放大點擊熱區--便捷:
放大可點擊按鈕熱區,相對于較小點擊熱區,具備更絲滑操作體驗。
④ 懸停即現--輕量:
利用懸停即現,避免信息過于重復啰嗦,簡化頁面提高閱讀體驗。
⑤ 折疊次要功能--簡化:
頁面功能按鈕過多時,可將次要按鈕收納到一起,點擊時再展開,外面只展示高頻操作或重要按鈕,保證頁面內容簡潔。
⑥ 統一樣式--一致:
一致性是指在不同頁面中相同操作應保持一致視覺與交互樣式,可有降低用戶學習成本與企業開發成本。
友好原則應貫穿用戶操作前,操作中以及操作后三個階段,給予用戶及時反饋與幫助。
① 操作前:
在用戶操作前給到合適的引導與幫助,有效減少用戶迷茫感。
② 操作中:
通過交互效果以及頁面樣式讓用戶可以清晰感知到自己當前操作。
③ 操作后:
利用界面中元素變化清晰直觀展示當前的狀態
可控主要體現在自由和導航兩個方面。
① 自由:
自由即指用戶可以自由完成一些操作,例如回退,撤銷,終止等。
② 導航:
導航是指用戶隨時知曉當前所在位置,而且可以利用導航隨意到達目標頁面。
通過本篇內容我們大概知道了制作設計規范主要方向,那么具體到每個組件上,我們該如何去設計呢?后續篇章將細分聊聊如何去設計 B 端常見組件的一些內容。
部分參考資料:
作者:huang。亮 來源:優設網
藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加微信ban_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~
希望得到建議咨詢、商務合作,也請與我們聯系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者. 免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
談到 B 端組件,很多人的印象是多且雜,想要全面準確的熟悉這些它們,需要我們對它有個合理的歸納總結。可能每個人會從不同的視角去做這件事情,我一般是按照使用場景去對組件分類整理。
下面我們根據這個分類框架來逐個聊聊這些組件。
說到基礎組件,我想再將其細分成兩個大類:一類是通用組件;一類是柵格/導航。怎么去更深刻的理解這兩類的區別呢,我們可以打個這樣的形象比方:
通過這個比方我們不難理解,柵格/導航是先給頁面定下了基本框架,而通用組件則是在這個框架基礎上搭建頁面的所用到基本元素。
常見通用組件一般包含:色彩/字體/間距/圓角/分割線/按鈕。需要注意的是,通用組件看起來似乎很簡單,但卻是決定產品品牌調性、界面細節品質的重要因素,在設計過程中需要引起我們的高度重視。
色彩
色彩可分為主色,功能色,中性色三類。下面談談這三類顏色如何配置,以及如何定義這些顏色梯度。
① 主色
主色的選取
主色作為產品的主要色調,在選取時應當優先選擇品牌色,但有一點要注意的是 B 端和 C 端不一樣,B 端一般不適合采用暖色系作為主色,如果品牌色為暖色調,則盡量考慮另選取一個冷色系作為主色,原因有兩點:一是為了避免和警告、錯誤色沖突,產生歧義;二是冷色系帶有商務、專業、冷靜的情感,更符合 B 端產品調性。
同時主色選取應避免高亮、熒光色、灰調高的顏色。
主色的應用
主色在設計中常見的應用包括可交互、選中狀態、可視化、插圖、圖標等場景。
② 功能色
功能色主要用于頁面表征狀態,常見有成功色、警告色、報錯色等。
成功色
主要用于操作結果成功提示以及標簽配色等。
警告色
主要用于警告提醒功能以及標簽配色等。
報錯色
主要用于系統報錯提示、圓點提示、以及標簽配色等。
③ 中性色
中性色在頁面設計中應用非常廣泛,從線條到文字再到圖形等等都可以見到它的影子。
Tips:無論我們主色調是什么,中性色在調色時建議可加入適量的藍色調,可讓頁面觀感更清爽。
④ 顏色梯度
選取好了顏色后,怎么去更合理的定義每個顏色的梯度呢?(這里主要指對主色以及功能色定義梯度)
我的方法是給顏色加一層半透明黑/白遮罩,當我們需要淺色,通過調整白色遮罩透明度得到合適顏色;而當我們需要深色時,則通過調整黑色遮罩透明度得到合適顏色。
這樣定義顏色梯優點是后續如果需要更改配色,只需一鍵替換全局色即可,大大提高工作效率。
文字
文字規范包含字體、字號、字重、行高等。
① 字體/字重
B 端字體/字重一般按照如下規范即可:
② 字號
不同于 C 端,B 端在字號選擇上應當盡量保持克制。研究表明,Web 端上正文字號為 14 時,可以帶來最佳閱讀體驗。因此在字號選取上應盡量優先選取 14 號字。如果想要區分文字層級,優先級從高到低的手法應該是顏色、字重、字號,也就是說一般盡量不采用字號大小區分文字層級。
③ 行高
行高可以參照下面的公式或行高參照表快速獲得,如果通過公式算出行高非整數或非偶數,可就近取偶整數。
間距
關于間距取值,在目前主流屏幕分辨率下,只有 4 或 8 被整除率最高,考慮到 4 的顆粒度偏小,因此可優先考慮 8px 的倍數作為間距值,在一些特殊場景可采用 4px 的倍數間距值。
分割線
分割線寬度一般統一為 1px 即可,同時注意顏色不可過深,以免干擾主體信息。如果需要不同層級分割線,可用顏色深淺來區分。
圓角
圓角大小一般根據使用場景控制在 2-3 個梯度即可,既不能全部統一一個圓角值,同時也不適合出現過多圓角值。同時圓角值不要過大,建議控制在 2-6px,以符合 B 端產品嚴謹專業調性。
按鈕
這里從按鈕的大小/狀態/排放位置幾個緯度來講。
① 按鈕尺寸
按鈕高度一般情況下可以設置 3-4 種尺寸按鈕,足以滿足各種使用場景。至于按鈕寬度,我們一般定義一個最小值,當超過最小值時,可設置 padding 值,按鈕寬度根據內容自適應。
② 按鈕狀態
操作按鈕過程中,按鈕會呈現不同的交互狀態。
③ 按鈕位置
對于主次按鈕組合,主次按鈕排放位置應該怎么規定呢?可分為兩種場景:一是當為從左到右閱讀順序時,主要按鈕應當排在次要按鈕左側。二是當為從右到左閱讀順序時,主要按鈕應當排在次要按鈕右側。而當一些特殊場景與這個原則沖突時,應權衡優先級做出取舍。
熟悉通用組件后,我們要通柵格/導航來確定產品頁面框架。
柵格
柵格可以有效地保證設計的一致性、讓頁面布局更具規律,并提高團隊協作效率。應該如何設計柵格呢?
① 柵格區域的劃定
我們一般習慣將頁面從下到上劃分為背景層、全局控制層、內容層、臨時層,而柵格區應當用在內容層。以下為常見幾種頁面布局柵格區的劃定。
② 柵格自適應規則
隨著頁面寬度變化,一般來說是通過欄寬變化實現自適應。
③ 柵格欄數的確定
根據頁面內容豐富程度,柵格欄數一般定 12 或者 24 欄,考慮到 B 端產品功能往往比較復雜,建議采用 24 欄即可。
④ 上下布局柵格
⑤ 左右布局柵格
導航
導航可分為全局導航與局部導航。
① 全局導航
全局導航包含頂部導航、側邊導航、混合導航。
這三種導航樣式各具特點,應結合產品特性選擇合適的導航樣式。這里要注意的一點是,當產品可用性和用戶體驗產生沖突時,應優先保證產品可用性。
② 局部導航
局部導航包含面包屑、標簽頁、步驟條、分頁器。
面包屑
面包屑導航的作用是告訴用戶當前頁面在系統層級結構中的位置以及父子級頁面間的關系,并且可以快速回到上幾級導航。
標簽頁
標簽頁可以幫助用戶在一個頁面實現快速切換不同內容,提升單個頁面內容擴展性??煞譃榛緲邮?、標簽樣式、卡片樣式。
步驟條
當任務復雜或者存在先后關系時,可將其分解成一系列步驟,這里就會用上步驟條。步驟條是引導用戶按照流程完成任務的導航條,作用包含 3 點:一是讓用戶對操作流程長度和步驟有個預期,二是明確知道自己目前所在步驟,三是可以對用戶的任務完成度有明確的度量。
步驟條一般分為橫向與縱向兩種樣式。
步驟條小 Tips:當步驟條中有操作難度偏大的內容時,為了提高用戶操作完成率,我們可以考慮把其放在靠后的步驟中,原因是用戶前面已經完成了大部分簡單操作,后面碰到高難度操作后,出于損失厭惡心理,不會輕易放棄操作。
分頁器
當有大量內容需要展現時進行分頁加載處理,分頁器作用是可以讓用戶清楚的知道自己所要瀏覽的內容有多少頁、當前所在頁碼、快捷前往目標頁碼。
分頁器一般分為迷你、簡易、自定義三種樣式。
妙用分頁器小 Tips:當表格中需要對數據全選操作時,為了提高操作效率,可將自定義每頁跳數調到最大。例如一共 100 條數據,默認每頁 10 條數據,要完成全選需要點擊 9 次翻頁,10 次全選(表格的全選框勾選后一般只選中當前頁面全部數據,而不是所有頁面總數據),當把每頁條數調整為 50 后,則只需翻頁 1 次,點擊 2 次全選即可。
到這里關于 B 端的基礎組件就全部梳理完了,后續我們就來揭開展示組件的神秘面紗。
部分參考資料:
作者:huang。亮 來源:優設網
藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加微信ban_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~
希望得到建議咨詢、商務合作,也請與我們聯系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者. 免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
SaaS,英文全稱 Software as a Service,意思為軟件即服務。是通過網絡提供軟件服務,可以理解為一種軟件交付模式,因為交付模式的不同也決定了和傳統軟件的差別。
在Saas之前傳統軟件需要購買后本地化部署,兩者最大的區別在于,賣軟件是將軟件作為一個軟件實體賣給客戶了;而賣SaaS 軟件的所有權還在廠商所有,提供的是“軟件服務”。
Saas模式的提出者是Salesforce創始人——馬克·貝尼奧夫(Marc Benioff) 在98年的時候提出。有兩個原因促使馬克·貝尼奧夫思考并提出Saas:
1. 傳統軟件部署實施交付的失敗率非常高;
2. 軟件的售賣價格非常高,很多中型、中小企業有需求但無法承擔高昂的費用。
基于以上兩個痛點及當時的現狀賣軟件給企業造成的影響,已經形成了惡性循環,市場受到嚴重的阻礙(據Gartner 高德納公司(美國咨詢公司)的調查研究曾表明:在所有CRM項目中大約55%沒有達到軟件用戶的完整交付和預期目的,通俗的說是實施失敗。)
從賣軟件變成賣服務,其中的技術方式的改變、交易模式的改變,促成了軟件時代的變革,對于傳統軟件公司來說,是一次大革命。原來賣軟件給客戶,一次性(或者分幾次)收到錢了;改為賣服務后,這筆錢就不能在短周期內一次性收了,現行的SaaS模式通常是按照用戶的使用年費來收取。
兩者差別在于,軟件商需要先主動改變可以短期的一次性高收入,從短期收入向長期收入的轉變;
所以SaaS是長期主義的事情。
失敗的Saas生意會出現一個問題:把長期生意做成了短期不可持續的生意;而短期生意帶來的就是經營成本的劇增,導致生意不可持續。
所以,Saas模式決定了需要客戶長期使用你的產品,才可以長期可持續賺錢,也就是通常意義上的客戶終身價值(LTV)。
那如何做到客戶長期使用,其實只有一種方式:長期為客戶創造價值,做到幫客戶成功(幫助客戶的業務成功),從而持續續約。
吳昊老師,在《SaaS創業路線圖》中的講到:SaaS的本質是續費。這個觀點我比較贊同,SaaS的經營本質在于可持續。
那么,決定SaaS的成功因素是什么呢?
我認為決定性因素有三個方面:產品強大且靈活、用戶體驗優質、服務的有效支持。產品強大和服務的有效支持不言而喻,具體我們來聊聊用戶體驗的價值。
傳統軟件在一次交付實施后,客戶付80%的錢,剩下的20%能不能收回來就不那么重要。但SaaS模式,客戶每年進行續費,若產品使用及用戶體驗滿意度低,帶來的影響和后果可能是客戶終止續費。
因此,和傳統軟件相比SaaS的用戶體驗的價值就更為重要,它直接影響SaaS的續費和流失。
相信“用戶體驗”這個詞大家應該非常熟悉,接下來我們從新認識什么是用戶體驗?
官方的定義是:用戶在使用產品過程中建立起來的一種主觀感受。“用戶體驗”這一概念是唐納德·諾曼(Donald Norman)在20世紀90年代中期提出的。產品大神俞軍老師說過用戶體驗的本質是“ 用戶最小成本滿足需求 ”。
基于俞軍老師的定義、我的理解和思考,我認為是幫助產品和用戶:最小成本滿足需求,同時創造「美·好」體驗。
怎么理解,因為用戶體驗是滿足商業目標的一種行為手段,我們來看下用戶體驗的需要考慮的雙邊關系,就比較好理解了。
如下圖:左邊是用戶最小成本滿足需求,右邊是我們企業最高效的服務用戶。
企業的本質就是創造商業價值,商業價值來源于用戶價值,同時考慮實現商業價值的效果和效率。我們常常會聽到“投入產出比”或者叫“投資回報率”;對于商業行為中的一環用戶體驗也如此。
所以,用戶體驗的核心的就是:平衡用戶最小成本滿足需求,及企業最小成本服務用戶。完成價值交換同時,滿足持續性。
由此,可以看出在SaaS的產品設計中,用戶體驗其實承擔著一個比較重要的責任,因為它關系到成本的邊際和規模化的影響,一頭是產品一頭是用戶。
那么作為產品體驗設計師,我們需要具備一定成本意識,做好“成本管控的設計”,更本質來說設計過程中我們應該:把復雜留給自己,把簡單留給用戶。
因為我們在設計的過程中把握產品的交互方式、使用流程,在用戶認知和效率層面有較強的把控空間,充分做到的以“用戶體驗”出發;那后續銷售、交付、客戶成功的全鏈路服務過程的學習效率和服務效率會呈指數級上升。
產品設計,應該把復雜留給自己、把簡單留給用戶。
關于用戶體驗,就不得不介紹一下“ 用戶體驗要素”模型,個人認為這是所有產品經理和設計師可以貫穿整個職業生涯中都需要經常性、反復對照思考的設計模型。
用戶體驗設計的價值遠不止于讓產品的視覺、顏值提升,設計更大的價值在于深入業務、洞察用戶,幫助業務梳理產品信息架構、任務流程、交互體驗。
構建系統的用戶使用方式和工作模式,從而達成用戶目標;通過達成用戶目標完成價值交換、以此完成商業交易達成商業目標。
回顧Saas的商業模式,Saas的商業模式決定我們提供的這個服務不是靠人海戰術,因為Saas軟件即服務的含義是所提供的軟件就等于提供自助化的服務,應該提供的是自助服務、開箱即用、開箱易用的服務。
那么Saas服務設計策略上,應該從降低系統使用門檻和提升用戶的自主化服務兩維度出發,根據這兩個核心維度,我們構建了每刻SaaS產品體驗的設計策略模型。
第一,設計“系統服務自動化”,這里的服務是功能使用的操作,符合“低認知、易上手”,那么從設計整個體系 需要遵守“易發現、低認知、高效率、有溫度”的設計原則展開,以用戶使用行為出發設計符合用戶心智認知的功能形態和交互流程。
第二,設計“系統幫助自助化”,什么意思在全系統中構建幫助引導的自助化體系,用戶需要幫助的時候提供人性化的引導幫助,我們從發現的維度、認知的維度,系統認知的維度,綜合考慮用戶系統的幫助引導。
設計原則,是指導我們做正確設計的方針。
設計原則的建立基于對用戶使用體驗全流程的基礎上,以每刻報銷的設計原則,圍繞用戶旅程、認知及行為出發構建。
1. 當用戶接觸系統從看出發,看見系統界面、發現操作入口;(發現)
2. 帶著操作任務用戶進入系統、看見導航、看見文字、看見按鈕,都需要理解認知;(認知)
3.用戶從何開始、如何操作,在完成整個業務事項的過程需要進行填寫、選擇、交互過程就產生了生產效率問題;(操作)
4. 當出現誤操作或系統出錯時,需要系統糾錯提醒、容錯的設計、幫助及引導,當完成整個業務事項后,用戶產生一種的情緒感受,這個感受即是印象、評價、口碑。(感受)
緊緊圍繞產品業務、用戶處理特性業務的基礎上,以終為始,回歸到用戶、業務、系統進行思考歸納的產物。
設計策略,是指導我們如何進行做正確的設計。
在SaaS產品分類上,常見的SaaS產品主要分為3類,行業SaaS、職能SaaS和通用SaaS。
每刻報銷產品從核心業務來說是職能類Saas,從提升財務人員報銷的發票審批、費用審核等效率展開,但報銷的來源又源于普通員工的業務報銷,業務報銷發生又和所在行業的費用發生行為特征有一定相關性,所以是結合職能和行業Saas的屬性,從用戶體驗的設計上需要考慮不同角色用戶對于系統的業務理解、功能交織使用的不同訴求,這個設計過程探索研究是相對比較有難度的,以后有機會可以展開聊。
下圖是每刻系統經過6年過程中統計的問題分布,分布比率呈現:認知問題 60%,效率問題 30%、情感問題10%。
我們在訪談客戶調研發現,企業服務雖然客戶已經用了好幾年我們的系統,但財務部門還是經常性會碰到新入職員工的系統使用問題,甚至財務部門來新人時 以前系統發生的使用問題會從新出現一遍,所以企業服務有一個現象,客戶是老客戶,但新用戶不斷增加,新人一旦增加第一個遇到的問題就是認知問題,也輔證了我們對于Saas系統認知問題是用戶體驗的第一大問題。
幫助體系建立可以從系統層面體系化有效降低用戶使用的認知成本,圍繞用戶角色的核心業務操作使用流程、洞察用戶旅程上的疑惑和障礙點。用戶首次進入系統要建立介紹和引導,足夠簡單、降低陌生感,來減少人力咨詢回復的投入。
相比人,系統的自助化和自動化的服務,更具有復用性和規模效應。
SaaS的商業模式,按年使用賬號來收費和傳統軟件的付費模式區別非常大,因為需要先主動放棄自己本來可以唾手可得的收入,從短期收入向長期收入的轉變。
在SaaS模式的時代,商業模式決定其必須關注客戶成功、客戶持續續費、LTV客戶長期價值。
SaaS的產品更需要重視用戶體驗,用戶體驗的優劣決定其產品的長期發展,SaaS的用戶體驗設計則關注用戶使用認知和行為效率,以及系統服務自主化設計和系統幫助自動化設計,用戶體驗將在產品成長期后半程,逐漸成為SaaS商業模式不可或缺的產品競爭力。
作者:周大蝦07 來源:站酷
藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加微信ban_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~
希望得到建議咨詢、商務合作,也請與我們聯系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者. 免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
藍藍設計的小編 http://www.syprn.cn