二八中医
二八中医
  • 发布:2017-09-07 14:03
  • 更新:2017-09-07 14:03
  • 阅读:7869

用vue和mui整合开发的一个图片压缩服务示例

分类:MUI

前言

这只是个人的经验总结和分享,并不是,也没时间做一个完整的教程。
分享的是我在实际项目开发中碰到的杂七杂八的问题,仅供参考。

一个简单的图片压缩工具

在线地址: 图片压缩工具-友间共享

在工作过程中,难免会碰到需要为同一个图片生成多种分辨率的情形,比如打包app之前需要准备好的各类图标,虽然简单,但是费点时间在所难免,就自己做了一个小工具,主要功能如下:

  1. 可以自定义处理后的图片的分辨率和压缩方式等,自定义的样式自动保存,下次可以重复使用。
  2. 一键上传图片,根据预设的样式批量生成处理结果,根据需要再自行下载

alt

开发环境

  1. vue+mui+webpack+npm+macos
  2. 后台服务: 七牛云存储

运行环境

在线服务,可在支持h5的浏览器上运行,电脑,手机平板理论上都可以

在线地址: 图片压缩工具-友间共享

实现原理

其实很简单,就是先上传图片到七牛云服务
然后在页面上提供配置页面,保存用户配置的格式,然后把每个格式都转化成七牛的图片转化服务的格式化字符串,最后把这个字符串和图片的原地址合并成图片下载链接,就可以在点击链接时调用七牛的图片处理服务,按照我们设定的格式下载转化后的图片。

项目目录结构

alt

** 详细目录结构介绍和开发环境部署参考 怎么搭建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>
代码解读
  1. 利用mui控制UI的布局和渲染展示
  2. vue负责绑定ui和后台数据,并跟踪数据变化
  3. Page是我自己定义的框架,源码就不方便分享了,简单的实现参考怎么实现一站式跨平台开发

原文地址:https://blog.betweenfriends.cn/post/crossdomaindev.html

1 关注 分享
Reinhardt

要回复文章请先登录注册

二八中医

二八中医 (作者)

后台七牛的接口地址错了,已修复
2017-09-08 09:00
Sinea

Sinea

在线demo提示未知错误
2017-09-07 17:14