前言
这只是个人的经验总结和分享,并不是,也没时间做一个完整的教程。
分享的是我在实际项目开发中碰到的杂七杂八的问题,仅供参考。
一个简单的图片压缩工具
在线地址: 图片压缩工具-友间共享
在工作过程中,难免会碰到需要为同一个图片生成多种分辨率的情形,比如打包app之前需要准备好的各类图标,虽然简单,但是费点时间在所难免,就自己做了一个小工具,主要功能如下:
- 可以自定义处理后的图片的分辨率和压缩方式等,自定义的样式自动保存,下次可以重复使用。
- 一键上传图片,根据预设的样式批量生成处理结果,根据需要再自行下载
开发环境
- vue+mui+webpack+npm+macos
- 后台服务: 七牛云存储
运行环境
在线服务,可在支持h5的浏览器上运行,电脑,手机平板理论上都可以
在线地址: 图片压缩工具-友间共享
实现原理
其实很简单,就是先上传图片到七牛云服务
然后在页面上提供配置页面,保存用户配置的格式,然后把每个格式都转化成七牛的图片转化服务的格式化字符串,最后把这个字符串和图片的原地址合并成图片下载链接,就可以在点击链接时调用七牛的图片处理服务,按照我们设定的格式下载转化后的图片。
项目目录结构
** 详细目录结构介绍和开发环境部署参考 怎么搭建vue和Mui的多页面开发环境
主要代码
引入vue,mui等库
import LContext from 'lui/context.js'
import Vue from 'vue'
import 'babel-polyfill'
import Vuex from 'vuex'
import $ from './mui/mui.min'
require('../css/mui.min.css')
require('../css/icons-extra.css')
//require('../css/iconfont.css')
//require('../css/app.css')
require('../css/app.less')
window.Vue = Vue
window.Vuex = Vuex
window.$ = $
window.mui = $
require('./mui/mui.pullToRefresh')
require('./mui/mui.pullToRefresh.material')
//加载BASE64
require('exports-loader!./util/base64.js')
Vue.use(Vuex)
//import LoginDialog from 'app/Login'
if(process.env.NODE_ENV === 'production') {
Vue.config.productionTip = false
}
有点偷懒,部分变量其实是可以在环境配置时引入的,这里就先忽略了,反正效果也一样
其中的库可根据需要引入,因为是从我自己的架构上COPY下来的,有些文件和库在这个项目中是可以不用引入的。引用的路径和webpack环境配置有关,以下是我的配置
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: { //定义别名,简易方式,可以在require调用时缩短路径
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
"page$": resolve('src/js/page.js'),
'app': resolve('src/components/'),
'lui': resolve('src/js/lui/'),
'mui': resolve('src/js/mui/'),
'util': resolve('src/js/util/')
}
},
output.js代码
import Page from 'page'
import Uploader from 'lui/upload.js'
var formVue = new Vue({
el: '.mui-content',
data: {
src: '',
formats: [],
formatBuff: {
facs: 9,
scaleType: '按指定宽高值强行缩略,可能导致目标图片变形,width和height取值范围1-9999'
}, //当前正在编辑的图片类型
collapsePanels: {
scale: false
},
logined: false
},
computed: {
showScale: function() {
switch(this.formatBuff.facs) {
case 1:
return true;
case 2:
return true;
case 3:
return true;
default:
break;
}
return false;
},
showHeight: function() {
switch(this.formatBuff.facs) {
case 5:
return true;
case 7:
return true;
case 8:
return true;
case 9:
return true;
case 10:
return true;
case 11:
return true;
default:
break;
}
return false;
},
showWidth: function() {
switch(this.formatBuff.facs) {
case 4:
return true;
case 7:
return true;
case 8:
return true;
case 9:
return true;
case 10:
return true;
case 11:
return true;
default:
break;
}
return false
},
showArea: function() {
switch(this.formatBuff.facs) {
case 12:
return true;
default:
break;
}
return false;
},
showInput: function() {
if(this.formatBuff.facs > 0) {
return true
}
return false
},
srcUri: function() {
if(this.src) {
return LContext.fileServer + this.src + LContext.image.Thumbnail
}
return ''
},
srcThumbnail: function() {
if(this.src) {
return LContext.fileServer + this.src +
LContext.image.Thumbnail
}
return ''
},
isEdit: function() {
if(this.formatBuff.uid) {
return true
}
return false
},
saveBtnText: function() {
console.log(this.isEdit + ' edit')
if(this.isEdit) {
return '保存修改'
} else {
return '保存新建样式'
}
}
},
methods: {
downLoad: function(format) {
var _format = this.decodeFormat(format)
if(_format.length > 2) {
lpage.open(LContext.fileServer + this.src + '?imageMogr2' + _format, undefined, 'new')
} else {
lpage.alert('下载格式无效!')
}
},
downUrl: function(format) {
var _format = this.decodeFormat(format)
if(_format.length > 2) {
return LContext.fileServer + this.src + '?imageMogr2' + _format
} else {
return false
}
},
deleteFormat: function(format) {
for(var i = 0; i < this.formats.length; i++) {
if(this.formats[i].uid === format.uid) {
this.formats.splice(i, 1)
lpage.saveFormats()
}
}
},
setFacs: function(facs, event) {
// this.formatBuff.facs = parseInt(facs, 10)
Vue.set(this.formatBuff, 'facs', parseInt(facs, 10))
Vue.set(this.formatBuff, 'scaleType', event.currentTarget.innerText)
Vue.nextTick(function() {
this.closePanel('scale')
}.bind(this))
// console.log(JSON.stringify(this.formatBuff))
},
decodeFormat: function(format) {
//缩放
var formatStr = '/thumbnail/'
switch(format.facs) {
case 1:
formatStr += '!' + format.scale + 'p'
break;
case 2:
formatStr += '!' + format.scale + 'px'
break;
case 3:
formatStr += '!x' + format.scale + 'p'
break;
case 4:
formatStr += format.width + 'x'
break;
case 5:
formatStr += 'x' + format.height
break;
case 7:
formatStr += format.width + 'x' + format.height
break;
case 8:
formatStr += '!' + format.width + 'x' + format.height
break;
case 9:
formatStr += format.width + 'x' + format.height + '!'
break;
case 10:
formatStr += format.width + 'x' + format.height + '>'
break;
case 11:
formatStr += format.width + 'x' + format.height + '<'
break;
case 12:
formatStr += format.area + '@'
break;
default:
formatStr = ''
break;
}
//格式转换
if(format.suffix !== 'auto') {
formatStr += '/format/' + format.suffix
}
return formatStr
},
isActiveFacs: function(facs) {
if(this.formatBuff.facs === facs) {
return true
}
return false
},
panelActive: function(collapseList) {
return this.collapsePanels[collapseList]
},
togglePanel: function(collapseList) {
Vue.set(this.collapsePanels, collapseList, !this.collapsePanels[collapseList])
},
closePanel: function(collapseList) {
Vue.set(this.collapsePanels, collapseList, false)
},
submitNewFormat: function() {
var newFormat = {
uid: 'format_' + new Date().getTime(),
height: this.formatBuff.height,
width: this.formatBuff.width,
scale: this.formatBuff.scale,
area: this.formatBuff.area,
facs: this.formatBuff.facs,
desc: this.formatBuff.desc,
scaleType: this.formatBuff.scaleType,
suffix: this.formatBuff.suffix ? this.formatBuff.suffix : 'auto', //图片类型
quality: this.formatBuff.quality ? this.formatBuff.quality : 75 //图片质量
}
var change = false
if(this.formatBuff.uid) {
for(var i = 0; i < this.formats.length; i++) {
if(this.formats[i].uid === this.formatBuff.uid) {
newFormat.uid = this.formatBuff.uid
this.formatBuff.uid = undefined
Vue.set(this.formats, i, newFormat)
change = true
}
}
}!change && this.formats.push(newFormat)
lpage.saveFormats()
},
editFormat: function(format) {
Vue.set(this.formatBuff, 'uid', format.uid)
Vue.set(this.formatBuff, 'height', format.height)
Vue.set(this.formatBuff, 'width', format.width)
Vue.set(this.formatBuff, 'scale', format.scale)
Vue.set(this.formatBuff, 'area', format.area)
Vue.set(this.formatBuff, 'facs', format.facs)
Vue.set(this.formatBuff, 'desc', format.desc)
Vue.set(this.formatBuff, 'scaleType', format.scaleType)
Vue.set(this.formatBuff, 'suffix', format.suffix === 'auto' ? undefined : format.suffix)
Vue.set(this.formatBuff, 'quality', format.quality ? format.quality : 75)
}
}
})
var pcPage = {
initUI: function() {
this.initUploader()
},
initData: function() {
// this.user.isLogin(function(logined) {
// if(logined) {
// formVue.logined = true
lpage.getFormats()
// } else {
// formVue.logined = false
// lpage.user.login()
// }
// })
},
initUploader: function() {
new Uploader({
selector: 'image_uploader',
token: {
mode: 'imagescale',
image: { //图片
prefix: '/utils/imagescale/'
}
},
success: function(data) {
formVue.src = data.key
lpage.saveFormats()
}
}).init()
},
saveFormats: function() {
lpage.setItem('_myFormats', JSON.stringify(formVue.formats))
lpage.setItem('_lastSrc', formVue.src)
},
getFormats: function() {
var _fmt = lpage.getItem('_myFormats')
if(!_fmt) {
_fmt = [{
'uid': 'format_1504082048978',
'height': '2208',
'width': '1242',
'scale': '50',
'facs': 9,
'desc': '苹果IphoneApp审核用的截屏图片1242*2208',
'scaleType': '按指定宽高值强行缩略,可能导致目标图片变形,width和height取值范围1-9999\n',
'suffix': 'auto',
'quality': '100'
}, {
'uid': 'format_1504144877541',
'height': '2732',
'width': '2048',
'scale': '50',
'facs': 9,
'desc': '苹果IpadApp审核用的截屏图片2048*2732',
'scaleType': '按指定宽高值强行缩略,可能导致目标图片变形,width和height取值范围1-9999\n',
'suffix': 'auto',
'quality': '100'
}]
Vue.set(formVue, 'formats', _fmt)
} else
if(_fmt) {
try {
_fmt = JSON.parse(_fmt)
Vue.set(formVue, 'formats', _fmt)
} catch(e) {
//TODO handle the exception
console.log('err')
}
}
_fmt = lpage.getItem('_lastSrc')
if(_fmt) {
formVue.src = _fmt
}
}
}
Page.init({
pc: pcPage
})
output.html源码
<!DOCTYPE html>
<html class="feedback">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>友间共享-图片处理</title>
<style>
.mui-content .mui-table-view-radio .mui-table-view-cell>a:not(.mui-btn) {
text-align: left;
white-space: normal;
}
</style>
</head>
<body>
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-btn mui-btn-link mui-btn-nav mui-pull-left"><span class=" mui-icon mui-icon-left-nav "></span> </a>
<h1 class="mui-title">友间共享-图片处理</h1>
</header>
<div class="mui-content">
<p class="mui-content-padded">可将上传的图片一次性转化输出成多个指定高度/宽度的图片,每次上传文件会扣除一定数额的积分,积分可通过多种方式获取
<a l-href="https://www.betweenfriends.cn/view/mine/bonusrule.html">积分获取/使用规则</a>
</p>
<div class="mui-content-padded ">
<img :src="srcUri" style="max-width: 250px;" />
<div class="image-list">
<div class="image-item space">
<button type="button" class="mui-btn mui-btn-blue">点击上传</button>
<div class="file">
<form id="image_uploader"></form>
</div>
</div>
</div>
</div>
<p class="mui-content-padded">转换后的图片下载</p>
<ul class="mui-table-view">
<li v-for="format in formats" class="mui-table-view-cell mui-media">
<img class="mui-media-object mui-pull-left" :src="srcThumbnail">
<div class="mui-media-body">
<p>{{format.desc}}</p>
<button type="button" class="mui-btn mui-btn-link" @tap="deleteFormat(format)">删除样式</button>
<button type="button" class="mui-btn mui-btn-link" @tap="editFormat(format)">编辑样式</button>
<a class="mui-btn mui-btn-blue" :href="downUrl(format)" download="格式化图片">下载图片</a>
</div>
</li>
</ul>
<hr />
<p class="mui-content-padded" style="margin-top: 20px;">图片处理参数配置,可在此处新增图片样式</p>
<ul class="mui-table-view">
<li class="mui-table-view-cell mui-collapse" :class="{'mui-active':panelActive('scale')}" @tap="togglePanel('scale')">
<a class="mui-navigate-right">
缩放模式</a>
<ul class="mui-table-view mui-table-view-radio">
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(9)}" @tap="setFacs(9,$event)">
<a class="mui-navigate-right">
按指定宽高值强行缩略,可能导致目标图片变形,width和height取值范围1-9999
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(1)}" @tap="setFacs(1,$event)">
<a class="mui-navigate-right">
基于原图大小,按指定百分比缩放。Scale取值范围1-999
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(2)}" @tap="setFacs(2,$event)">
<a class="mui-navigate-right">
以百分比形式指定目标图片宽度,高度不变。Scale取值范围1-999
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(3)}" @tap="setFacs(3,$event)">
<a class="mui-navigate-right">
以百分比形式指定目标图片高度,宽度不变。Scale取值范围1-999
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(4)}" @tap="setFacs(4,$event)">
<a class="mui-navigate-right">
指定目标图片宽度,高度等比缩放,Width取值范围1-9999
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(5)}" @tap="setFacs(5,$event)">
<a class="mui-navigate-right">
指定目标图片高度,宽度等比缩放,Height取值范围1-9999
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(7)}" @tap="setFacs(7,$event)">
<a class="mui-navigate-right">
等比缩放,比例值为宽缩放比和高缩放比的较小值,Width 和 Height 取值范围1-9999。 注意:宽缩放比:目标宽/原图宽 高缩放比:目标高/原图高
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(8)}" @tap="setFacs(8,$event)">
<a class="mui-navigate-right">
等比缩放,比例值为宽缩放比和高缩放比的较大值,Width 和 Height 取值范围1-9999。 注意:宽缩放比:目标宽/原图宽 高缩放比:目标高/原图高
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(10)}" @tap="setFacs(10,$event)">
<a class="mui-navigate-right">
等比缩小,比例值为宽缩放比和高缩放比的较小值。如果目标宽和高都大于原图宽和高,则不变,Width 和 Height 取值范围1-9999。 注意:宽缩放比:目标宽/原图宽 高缩放比:目标高/原图高;
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(11)}" @tap="setFacs(11,$event)">
<a class="mui-navigate-right">
等比放大,比例值为宽缩放比和高缩放比的较小值。如果目标宽(高)小于原图宽(高),则不变,Width 和 Height 取值范围1-9999。 注意: 宽缩放比:目标宽/原图宽 高缩放比:目标高/原图高
</a>
</li>
<li class="mui-table-view-cell" :class="{'mui-selected':isActiveFacs(12)}" @tap="setFacs(12,$event)">
<a class="mui-navigate-right">
按原图高宽比例等比缩放,缩放后的像素数量不超过指定值,Area取值范围1-24999999
</a>
</li>
</ul>
</li>
</ul>
<p class="mui-content-padded" v-show="showInput">{{formatBuff.scaleType}}</p>
<form class="mui-input-group">
<div class="mui-input-row" v-show="showScale">
<label>缩放百分比</label>
<input type="number" v-model="formatBuff.scale" class="mui-input-clear" min="1" max="999" step="1" placeholder="取值1到999">
</div>
<div class="mui-input-row" v-show="showHeight">
<label>高度</label>
<input type="number" v-model="formatBuff.height" class="mui-input-clear" placeholder="高度,0表示根据宽度等比例缩放">
</div>
<div class="mui-input-row" v-show="showWidth">
<label>宽度</label>
<input type="number" v-model="formatBuff.width" class="mui-input-clear" placeholder="宽度,0表示根据高度等比例缩放">
</div>
<div class="mui-input-row" v-show="showArea">
<label>像素限制</label>
<input type="number" v-model="formatBuff.area" class="mui-input-clear" placeholder="只对jpg有效">
</div>
<div class="mui-input-row" v-show="showInput">
<label>图片质量</label>
<input type="number" v-model="formatBuff.quality" class="mui-input-clear" min="1" max="100" placeholder="从1到100,默认75">
</div>
<div class="mui-input-row" v-show="showInput">
<label>输出文件格式</label>
<input type="text" class="mui-input-clear" v-model="formatBuff.suffix" placeholder="默认为原图格式,如jpg,非法格式可能导致出错">
</div>
<div class="mui-input-row" v-show="showInput">
<label>格式说明</label>
<input type="text" class="mui-input-clear" v-model="formatBuff.desc" placeholder="格式说明,用途说明等,将作为该格式的备注显示">
</div>
<div class="mui-button-row">
<button type="button" class="mui-btn mui-btn-link " v-show="showInput&&isEdit" @tap="submitNewFormat">放弃修改</button>
<button type="button" class="mui-btn mui-btn-blue " v-show="showInput" @tap="submitNewFormat">{{saveBtnText}}</button>
</div>
</form>
</div>
<div class="mui-text-center" style="margin-top: 30px; bottom: 0px;width: 100%;">
© 2017 -
<a l-href="https://www.betweenfriends.cn"> 友间共享 </a>
<a l-href="https://blog.betweenfriends.cn/about"> 关于我们 </a> - 闽ICP备17012098号-1
</div>
</body>
</html>
代码解读
- 利用mui控制UI的布局和渲染展示
- vue负责绑定ui和后台数据,并跟踪数据变化
- Page是我自己定义的框架,源码就不方便分享了,简单的实现参考怎么实现一站式跨平台开发
原文地址:https://blog.betweenfriends.cn/post/crossdomaindev.html
2 个评论
要回复文章请先登录或注册
二八中医 (作者)
Sinea