HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

弹出 mui-modal 标题出现显示不正常的问题

modal

<div id="transferModal" class="mui-modal">
<header class="mui-bar mui-bar-nav" style="background-color: #FFFFFF;border-bottom: 1px #F4F4F4 solid;">
<a class="mui-icon mui-pull-left" href="#transferModal">
<img src="../public/img/backArrow_gray19x19@3x.png" width="15" style="margin-left:3px;margin-top:4px;" />
</a>
<h1 class="mui-title">标题</h1>
</header>
<div class="mui-content" style="height: 100%;">
</div>
</div>

比如这个modal 弹出后,发现标题 header 直接没了。各种原因找了,发现加上样式:

.mui-modal header{overflow: hidden;}

就可以解决了。

继续阅读 »

<div id="transferModal" class="mui-modal">
<header class="mui-bar mui-bar-nav" style="background-color: #FFFFFF;border-bottom: 1px #F4F4F4 solid;">
<a class="mui-icon mui-pull-left" href="#transferModal">
<img src="../public/img/backArrow_gray19x19@3x.png" width="15" style="margin-left:3px;margin-top:4px;" />
</a>
<h1 class="mui-title">标题</h1>
</header>
<div class="mui-content" style="height: 100%;">
</div>
</div>

比如这个modal 弹出后,发现标题 header 直接没了。各种原因找了,发现加上样式:

.mui-modal header{overflow: hidden;}

就可以解决了。

收起阅读 »

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

图片裁剪 Vue

前言

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

一个简单的图片压缩工具

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

在工作过程中,难免会碰到需要为同一个图片生成多种分辨率的情形,比如打包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

继续阅读 »

前言

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

一个简单的图片压缩工具

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

在工作过程中,难免会碰到需要为同一个图片生成多种分辨率的情形,比如打包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

收起阅读 »

分享自动生成ios离线打包所需各种大小icon的gulp代码

图标 离线打包 App离线打包

离线打包时需要各种大小分辨率不同的启动图标,手动一个个制作太麻烦了。
经过我半天的研究,终于写出了可以自动生成各种所需启动图标的gulp代码,如下:

var gulp = require('gulp')  
var imageResize = require('gulp-image-resize')  
var rename = require('gulp-rename')  

gulp.task('icon-ios', () => {  
    var sizes = [29, 40, 50, 57, 58, 72, 76, 80, 87, 100, 114, 120, 144, 152, 180]  
    sizes.forEach((size, index) => {  
        gulp.src('icon.png')  
            .pipe(imageResize({  
                width: size,  
                height: size,  
                corp: true  
            }))  
            .pipe(rename('icon'+size+'.png'))  
            .pipe(gulp.dest('dist'))  
    })  
})

需要注意的是gulp-image-resize是需要额外安装两个依赖的,可以去官网查看:
gulp-image-resize

希望官方能把这段经验加到离线打包的文档里,防止后人走弯路

继续阅读 »

离线打包时需要各种大小分辨率不同的启动图标,手动一个个制作太麻烦了。
经过我半天的研究,终于写出了可以自动生成各种所需启动图标的gulp代码,如下:

var gulp = require('gulp')  
var imageResize = require('gulp-image-resize')  
var rename = require('gulp-rename')  

gulp.task('icon-ios', () => {  
    var sizes = [29, 40, 50, 57, 58, 72, 76, 80, 87, 100, 114, 120, 144, 152, 180]  
    sizes.forEach((size, index) => {  
        gulp.src('icon.png')  
            .pipe(imageResize({  
                width: size,  
                height: size,  
                corp: true  
            }))  
            .pipe(rename('icon'+size+'.png'))  
            .pipe(gulp.dest('dist'))  
    })  
})

需要注意的是gulp-image-resize是需要额外安装两个依赖的,可以去官网查看:
gulp-image-resize

希望官方能把这段经验加到离线打包的文档里,防止后人走弯路

收起阅读 »

怎么搭建vue和Mui的多页面开发环境

打包 Vue

现在有很多的打包工具可以选择,grunt,gulp,webpack等等。vue官方建议是使用webpack,而且webpack相对其他打包工具的好处是代码动态编译,可以根据需要提取出公共部分的代码。因此我们也选择webpack作为打包工具。

目录结构

alt

开发目录 src
  1. assets 静态资源,如图片等的保存
  2. css 样式文件,css,less等
  3. fonts 字体文件,根据需要添加自定义字体
  4. js js框架,公共部分代码,自定义组件等
    5.view html页面文件,页面入口js文件
build 打包参数配置

主要用于对打包过程的控制

  1. webpack.base.conf.js 基础配置参数,开发和生成代码打包共用
  2. webpack.dev.conf.js 开发环境下的打包参数配置
  3. webpack.prod.conf.js 生产环境的打包参数配置
config 打包模式配置

主要是打包相关的基础参数配置,如文件存放目录,打包后的访问根目录,是否压缩等

assets 生产环境下的js/css/图片等文件保存路径

可在config中指定

view 生产环境下的页面文件Html保存路径

和src/view下的html文件一一对应

package.json 打包的环境依赖配置

使用方式

  1. 开发 npm run dev 开启本地web服务,方便调试页面和代码

  2. 生产环境打包 npm run build 将src下的代码打包到根目录下的 assets和view目录下

功能

  1. 支持vue开发的调试,支持webpack打包的调试和自动更新,不需要手动刷新代码

  2. 支持代码和语法检查,可在根目录的.eslintrc下配置检查规则

3.支持webpack的多页面(多入口)开发,相比单入口模式,逻辑更清楚

使用说明

1.view下的页面必须放在view的下级目录,比如view/index/index.html,同时每个页面文件必须搭配一个同名的js文件作为页面入口,比如view/index/index.js

2.可根据需要配置本地调试服务器的端口,默认8000,
在config/index下配置

3.可配置打包路径等,同样在config/index下配置

初始化说明

  1. 在根目录下执行 npm install (如果不是root用户的话,sudo npm install)

  2. 打包或者调试时,如果npm 命令提示错误,一般是某个模块未安装,可执行 npm install ***(模块名称) 安装相关模块

  3. npm run dev (调试)

  4. npm run build (正式打包)

下载完整目录

原文链接:https://blog.betweenfriends.cn/post/howtodevwithvueandmui.html

继续阅读 »

现在有很多的打包工具可以选择,grunt,gulp,webpack等等。vue官方建议是使用webpack,而且webpack相对其他打包工具的好处是代码动态编译,可以根据需要提取出公共部分的代码。因此我们也选择webpack作为打包工具。

目录结构

alt

开发目录 src
  1. assets 静态资源,如图片等的保存
  2. css 样式文件,css,less等
  3. fonts 字体文件,根据需要添加自定义字体
  4. js js框架,公共部分代码,自定义组件等
    5.view html页面文件,页面入口js文件
build 打包参数配置

主要用于对打包过程的控制

  1. webpack.base.conf.js 基础配置参数,开发和生成代码打包共用
  2. webpack.dev.conf.js 开发环境下的打包参数配置
  3. webpack.prod.conf.js 生产环境的打包参数配置
config 打包模式配置

主要是打包相关的基础参数配置,如文件存放目录,打包后的访问根目录,是否压缩等

assets 生产环境下的js/css/图片等文件保存路径

可在config中指定

view 生产环境下的页面文件Html保存路径

和src/view下的html文件一一对应

package.json 打包的环境依赖配置

使用方式

  1. 开发 npm run dev 开启本地web服务,方便调试页面和代码

  2. 生产环境打包 npm run build 将src下的代码打包到根目录下的 assets和view目录下

功能

  1. 支持vue开发的调试,支持webpack打包的调试和自动更新,不需要手动刷新代码

  2. 支持代码和语法检查,可在根目录的.eslintrc下配置检查规则

3.支持webpack的多页面(多入口)开发,相比单入口模式,逻辑更清楚

使用说明

1.view下的页面必须放在view的下级目录,比如view/index/index.html,同时每个页面文件必须搭配一个同名的js文件作为页面入口,比如view/index/index.js

2.可根据需要配置本地调试服务器的端口,默认8000,
在config/index下配置

3.可配置打包路径等,同样在config/index下配置

初始化说明

  1. 在根目录下执行 npm install (如果不是root用户的话,sudo npm install)

  2. 打包或者调试时,如果npm 命令提示错误,一般是某个模块未安装,可执行 npm install ***(模块名称) 安装相关模块

  3. npm run dev (调试)

  4. npm run build (正式打包)

下载完整目录

原文链接:https://blog.betweenfriends.cn/post/howtodevwithvueandmui.html

收起阅读 »

布局理念 - NView模板 - wap2app教程

NView模板

布局容器

在NView模板中,<nview>标签是布局容器,其它控件都必须包裹在<nview>容器中;<nview>默认为块级元素(宽度为100%屏幕宽度),自上而下排列布局,现阶段必须声明nview的高度,不支持高度自适应。如下是一个示例

    <nviews cachemaxage="86400">  
        <nview id="nview1" style="height:50px;backgroundColor:#56C1FF"></nview>  
        <nview id="nview2" style="height:100px;backgroundColor:#61D836"></nview>  
        <nview id="nview3" style="height:50px;backgroundColor:#EF5FA7"></nview>  
    </nviews>

如上代码布局效果如下(顶部自带原生标题栏):

除了默认的自上而下顺序布局,nview也支持绝对定位布局,如下示例中,将nview1设置为绝对定位,在窗口底部显示;

    <nviews cachemaxage ="86400">  
        <nview id="nview1" style="position:absolute;bottom:0;height:50px;backgroundColor:#56C1FF"></nview>  
        <nview id="nview2" style="height:100px;backgroundColor:#61D836"></nview>  
        <nview id="nview3" style="height:50px;backgroundColor:#EF5FA7"></nview>  
    </nviews>

设置nview1为绝对定位后,运行示意图如下:

从运行结果可以看出,nview1设置为绝对定位后,脱离文档流自行定位;但nview2、nview3继续使用默认的自上而下的布局方式。

在nview布局容器下,可以嵌套imageslider、list、canvas、richtext四个子标签,子标签默认和nview容器具备相同的宽高;其中:

  • imageslider/list是封装好的组件,标签及布局方式固定;
  • canvas/richtext是布局控件,分别表示绝对布局/流式布局

接下来重点介绍 richtext/canvas两种布局控件

布局控件

先看如下代码,有两个nview,分别嵌套流式布局的richtext和绝对布局的canvas:

    <nviews cachemaxage ="86400">  
        <nview id="nview_richtext" style="height:100px;backgroundColor:#56C1FF">  
            <richtext>  
                <font>Hello</font>  
                <font>World</font>  
            </richtext>  
        </nview>  
        <nview id="nview_canvas" style="height:100px;backgroundColor:#EF5FA7">  
            <canvas>  
                <font>Hello</font>  
                <font>World</font>  
            </canvas>  
        </nview>  
    </nviews>

运行结果如下:

从图中可以,richtext下的两个font流式排版,显示正常;但canvas下的两个font重叠在一起,这是因为canvas下的子标签默认为绝对定位,不进入文档流,多个子标签则会以多层的方式重叠排版;给canvas下子标签设置位置坐标即可正常布局,代码如下:

<nviews cachemaxage ="86400">  
    <nview id="nview_richtext" style="height:100px;backgroundColor:#56C1FF">  
        <richtext>  
            <font>Hello</font>  
            <font>World</font>  
        </richtext>  
    </nview>  
    <nview id="nview_canvas" style="height:100px;backgroundColor:#EF5FA7">  
        <canvas>  
            <!--设置坐标位置-->  
            <font style="left:5px;width:40px;">Hello</font>  
            <font style="left:45px;width:40px;">World</font>  
        </canvas>  
    </nview>  
</nviews>

对应运行结果如下:

canvas绝对布局

canvas是绝对布局的容器标签,具有以下特点:

  • 支持a、font、img、button、input、hr子标签
  • 子标签全部绝对定位,需要声明标签相对父元素的相对坐标值
  • 子标签大小由坐标固定,不会随着内容自动变化,若内容过长则自动裁剪(超出部分不可见)

如若nview标签下仅有一个canvas子标签,则canvas和nview默认具有相同的的大小/宽高信息。 如下是一个代码示例,演示canvas子标签如何定位:

    <nviews cachemaxage ="86400">  
        <nview id="nview1" style="height:150px;background-color: #56C1FF;">  
            <canvas>  
                <font style="left:35px;top:35px;height:22px;align:left;font-size:17px;color:#ffffff">  
                    这里是文字  
                </font>  
                <img src="_www/logo.png" style="top:60px;right:60px;width:60px;height:60px"/>  
            </canvas>  
        </nview>  
        <nview id="nview2" style="height:150px;background-color: #EF5FA7;">  
            <canvas>  
                <button style="left:50px;right:50px;top:70px;height:30px;background-color:rgba(0,0,0,0);border-color:#ffffff;color:#ffffff;">按钮</button>  
            </canvas>  
        </nview>  
    </nviews>

运行结果如下(为方便理解,标注了每个元素的坐标值,坐标值对应上面代码中的left/right/top等定义):

从上图可以看出:canvas布局控件下的元素,全部为精确定位,坐标值是相对于父容器的坐标(不是屏幕坐标)。

每个元素均需声明位置坐标

canvas布局控件下的每个子标签均需要声明坐标位置,否则每个标签将作为单独一层重叠绘制,如下是一个示例,创建两个nview,显示相同的元素,第一个不设置坐标位置,第二个设置坐标位置,示例代码如下:

    <nviews cachemaxage ="86400">  
        <nview id="nview1" style="height:100px;background-color: #56C1FF;">  
            <canvas>  
                <!--未设置坐标位置-->  
                <font style="height:20px;align:left;color:#ffffff;font-size:17px;">作者:CHB</font>  
                <button style="width:50px;height:25px;background-color:#007aff;color:#ffffff;border-color:#007aff;">关注</button>  
            </canvas>  
        </nview>  
        <nview id="nview2" style="height:100px;background-color: #EF5FA7;">  
            <canvas>  
                <!--设置坐标位置-->  
                <font style="left:10px;top:10px;height:20px;align:left;color:#ffffff;font-size:17px;">作者:CHB</font>  
                <button style="left:100px;top:10px;width:50px;height:25px;background-color:#007aff;color:#ffffff;border-color:#007aff;">关注</button>  
            </canvas>  
        </nview>  
    </nviews>

运行结果如下:

因为每个元素均需要声明位置信息,因此canvas布局适合精确定位的场景,比如:居右显示;

逐层渲染,非流式

另一方面,canvas布局不适合可变长度的场景,如上示例中,若将"作者:CHB"替换为“作者:George R.R. Martin”,默认就会和右侧关注按钮重叠,代码如下:

    <nviews cachemaxage ="86400">  
        <nview id="nview1" style="height:100px;background-color: #56C1FF;">  
            <canvas>  
                <font style="left:10px;top:10px;height:20px;align:left;color:#ffffff;font-size:17px;">作者:CHB</font>  
                <button style="left:100px;top:10px;width:50px;height:25px;background-color:#007aff;color:#ffffff;border-color:#007aff;">关注</button>  
            </canvas>  
        </nview>  
        <nview id="nview2" style="height:100px;background-color: #EF5FA7;">  
            <canvas>  
                <!--名字较长时,会重叠绘制-->  
                <font style="left:10px;top:10px;height:20px;align:left;color:#ffffff;font-size:17px;">作者:George R.R. Martin</font>  
                <button style="left:100px;top:10px;width:50px;height:25px;background-color:#007aff;color:#ffffff;border-color:#007aff;">关注</button>  
            </canvas>  
        </nview>  
    </nviews>

运行结果如下:

从上图可看出,关注按钮重叠在"George R.R. Martin”名字上方,这有如下两个原因:

  • canvas下元素定位,类似于HTML5中的absolute定位,每个元素是按照坐标位置逐层绘制的,故关注按钮会和作者名字重叠,而不会像流式布局那样自动向右侧迁移;
  • canvas下font标签默认宽度为100%,故可以将"George R.R. Martin”完整显示出来,而没有出现裁剪隐藏的情况;

richtext流式布局

richtext是流式布局的容器标签,有以下特点:

  • 支持font、img、a、hr、br子标签;
  • 各个子标签自左向右、自上向下流式排版;
  • 子标签宽度和高度随内容自动变化,设置width/height属性无效(img标签例外),碰到较长文本时会自动换行;
  • 支持通过空格( )来设置元素前后间距
  • 设置位置坐标(left/top等)无效

如下是一个richtext示例:

    <nviews cacheMaxAge="86400">  
        <nview id="nview_richtext" style="height:250px;background-color: #56C1FF;">  
            <richtext style="left:10px;right:10px;width:340px;top:10px">  
                <font style="font-size:23px;color:#ffffff;font-weight: bold;">这是一级标题,内容较长时会自动换行</font>  
                <br/><br/>  
                <img src="_www/logo.png" width="20px" height="20px"></img>  
                <font style="font-size: 17px;color:#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;作者:CHB&nbsp;&nbsp;&nbsp;&nbsp;</font>  
                <font style="font-size: 15px;color:#ffffff">2017-09-04</font>  
                <br/>  
                <hr style="border-color:#EF5FA7"/>  
                <br/>  
                <font style="font-size:14px;color:#ffffff">原文链接:</font><a style="font-size:14px;color:#ffffff">http://www.example.com</a>  
                <br/><br/>  
                <font style="font-size:14px;color:#ffffff">下面是详细内容,文字较长时会自动进行换行,可变长度的文字推荐使用richtext布局控件...</font>  
            </richtext>  
        </nview>  
    </nviews>

如上代码中,头像信息、作者信息、发布时间是顺序编写的,最终自动从左到右绘制在一行,运行结果如下:

因为设置位置坐标无效,故无法实现精确定位,比如要求发布时间居右显示,则在richtext中无法实现;

两种布局适用场景

通过如上分析,可以理解两种布局适用的场景不同:

  • 需要多个元素自左向右显示,元素长度不固定,则使用richtext
  • 文本长度不确定,需要自动换行,则使用richtext
  • 图文混排,则使用richtext
  • 需要精确定位(如右侧对齐),则使用canvas
继续阅读 »

布局容器

在NView模板中,<nview>标签是布局容器,其它控件都必须包裹在<nview>容器中;<nview>默认为块级元素(宽度为100%屏幕宽度),自上而下排列布局,现阶段必须声明nview的高度,不支持高度自适应。如下是一个示例

    <nviews cachemaxage="86400">  
        <nview id="nview1" style="height:50px;backgroundColor:#56C1FF"></nview>  
        <nview id="nview2" style="height:100px;backgroundColor:#61D836"></nview>  
        <nview id="nview3" style="height:50px;backgroundColor:#EF5FA7"></nview>  
    </nviews>

如上代码布局效果如下(顶部自带原生标题栏):

除了默认的自上而下顺序布局,nview也支持绝对定位布局,如下示例中,将nview1设置为绝对定位,在窗口底部显示;

    <nviews cachemaxage ="86400">  
        <nview id="nview1" style="position:absolute;bottom:0;height:50px;backgroundColor:#56C1FF"></nview>  
        <nview id="nview2" style="height:100px;backgroundColor:#61D836"></nview>  
        <nview id="nview3" style="height:50px;backgroundColor:#EF5FA7"></nview>  
    </nviews>

设置nview1为绝对定位后,运行示意图如下:

从运行结果可以看出,nview1设置为绝对定位后,脱离文档流自行定位;但nview2、nview3继续使用默认的自上而下的布局方式。

在nview布局容器下,可以嵌套imageslider、list、canvas、richtext四个子标签,子标签默认和nview容器具备相同的宽高;其中:

  • imageslider/list是封装好的组件,标签及布局方式固定;
  • canvas/richtext是布局控件,分别表示绝对布局/流式布局

接下来重点介绍 richtext/canvas两种布局控件

布局控件

先看如下代码,有两个nview,分别嵌套流式布局的richtext和绝对布局的canvas:

    <nviews cachemaxage ="86400">  
        <nview id="nview_richtext" style="height:100px;backgroundColor:#56C1FF">  
            <richtext>  
                <font>Hello</font>  
                <font>World</font>  
            </richtext>  
        </nview>  
        <nview id="nview_canvas" style="height:100px;backgroundColor:#EF5FA7">  
            <canvas>  
                <font>Hello</font>  
                <font>World</font>  
            </canvas>  
        </nview>  
    </nviews>

运行结果如下:

从图中可以,richtext下的两个font流式排版,显示正常;但canvas下的两个font重叠在一起,这是因为canvas下的子标签默认为绝对定位,不进入文档流,多个子标签则会以多层的方式重叠排版;给canvas下子标签设置位置坐标即可正常布局,代码如下:

<nviews cachemaxage ="86400">  
    <nview id="nview_richtext" style="height:100px;backgroundColor:#56C1FF">  
        <richtext>  
            <font>Hello</font>  
            <font>World</font>  
        </richtext>  
    </nview>  
    <nview id="nview_canvas" style="height:100px;backgroundColor:#EF5FA7">  
        <canvas>  
            <!--设置坐标位置-->  
            <font style="left:5px;width:40px;">Hello</font>  
            <font style="left:45px;width:40px;">World</font>  
        </canvas>  
    </nview>  
</nviews>

对应运行结果如下:

canvas绝对布局

canvas是绝对布局的容器标签,具有以下特点:

  • 支持a、font、img、button、input、hr子标签
  • 子标签全部绝对定位,需要声明标签相对父元素的相对坐标值
  • 子标签大小由坐标固定,不会随着内容自动变化,若内容过长则自动裁剪(超出部分不可见)

如若nview标签下仅有一个canvas子标签,则canvas和nview默认具有相同的的大小/宽高信息。 如下是一个代码示例,演示canvas子标签如何定位:

    <nviews cachemaxage ="86400">  
        <nview id="nview1" style="height:150px;background-color: #56C1FF;">  
            <canvas>  
                <font style="left:35px;top:35px;height:22px;align:left;font-size:17px;color:#ffffff">  
                    这里是文字  
                </font>  
                <img src="_www/logo.png" style="top:60px;right:60px;width:60px;height:60px"/>  
            </canvas>  
        </nview>  
        <nview id="nview2" style="height:150px;background-color: #EF5FA7;">  
            <canvas>  
                <button style="left:50px;right:50px;top:70px;height:30px;background-color:rgba(0,0,0,0);border-color:#ffffff;color:#ffffff;">按钮</button>  
            </canvas>  
        </nview>  
    </nviews>

运行结果如下(为方便理解,标注了每个元素的坐标值,坐标值对应上面代码中的left/right/top等定义):

从上图可以看出:canvas布局控件下的元素,全部为精确定位,坐标值是相对于父容器的坐标(不是屏幕坐标)。

每个元素均需声明位置坐标

canvas布局控件下的每个子标签均需要声明坐标位置,否则每个标签将作为单独一层重叠绘制,如下是一个示例,创建两个nview,显示相同的元素,第一个不设置坐标位置,第二个设置坐标位置,示例代码如下:

    <nviews cachemaxage ="86400">  
        <nview id="nview1" style="height:100px;background-color: #56C1FF;">  
            <canvas>  
                <!--未设置坐标位置-->  
                <font style="height:20px;align:left;color:#ffffff;font-size:17px;">作者:CHB</font>  
                <button style="width:50px;height:25px;background-color:#007aff;color:#ffffff;border-color:#007aff;">关注</button>  
            </canvas>  
        </nview>  
        <nview id="nview2" style="height:100px;background-color: #EF5FA7;">  
            <canvas>  
                <!--设置坐标位置-->  
                <font style="left:10px;top:10px;height:20px;align:left;color:#ffffff;font-size:17px;">作者:CHB</font>  
                <button style="left:100px;top:10px;width:50px;height:25px;background-color:#007aff;color:#ffffff;border-color:#007aff;">关注</button>  
            </canvas>  
        </nview>  
    </nviews>

运行结果如下:

因为每个元素均需要声明位置信息,因此canvas布局适合精确定位的场景,比如:居右显示;

逐层渲染,非流式

另一方面,canvas布局不适合可变长度的场景,如上示例中,若将"作者:CHB"替换为“作者:George R.R. Martin”,默认就会和右侧关注按钮重叠,代码如下:

    <nviews cachemaxage ="86400">  
        <nview id="nview1" style="height:100px;background-color: #56C1FF;">  
            <canvas>  
                <font style="left:10px;top:10px;height:20px;align:left;color:#ffffff;font-size:17px;">作者:CHB</font>  
                <button style="left:100px;top:10px;width:50px;height:25px;background-color:#007aff;color:#ffffff;border-color:#007aff;">关注</button>  
            </canvas>  
        </nview>  
        <nview id="nview2" style="height:100px;background-color: #EF5FA7;">  
            <canvas>  
                <!--名字较长时,会重叠绘制-->  
                <font style="left:10px;top:10px;height:20px;align:left;color:#ffffff;font-size:17px;">作者:George R.R. Martin</font>  
                <button style="left:100px;top:10px;width:50px;height:25px;background-color:#007aff;color:#ffffff;border-color:#007aff;">关注</button>  
            </canvas>  
        </nview>  
    </nviews>

运行结果如下:

从上图可看出,关注按钮重叠在"George R.R. Martin”名字上方,这有如下两个原因:

  • canvas下元素定位,类似于HTML5中的absolute定位,每个元素是按照坐标位置逐层绘制的,故关注按钮会和作者名字重叠,而不会像流式布局那样自动向右侧迁移;
  • canvas下font标签默认宽度为100%,故可以将"George R.R. Martin”完整显示出来,而没有出现裁剪隐藏的情况;

richtext流式布局

richtext是流式布局的容器标签,有以下特点:

  • 支持font、img、a、hr、br子标签;
  • 各个子标签自左向右、自上向下流式排版;
  • 子标签宽度和高度随内容自动变化,设置width/height属性无效(img标签例外),碰到较长文本时会自动换行;
  • 支持通过空格( )来设置元素前后间距
  • 设置位置坐标(left/top等)无效

如下是一个richtext示例:

    <nviews cacheMaxAge="86400">  
        <nview id="nview_richtext" style="height:250px;background-color: #56C1FF;">  
            <richtext style="left:10px;right:10px;width:340px;top:10px">  
                <font style="font-size:23px;color:#ffffff;font-weight: bold;">这是一级标题,内容较长时会自动换行</font>  
                <br/><br/>  
                <img src="_www/logo.png" width="20px" height="20px"></img>  
                <font style="font-size: 17px;color:#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;作者:CHB&nbsp;&nbsp;&nbsp;&nbsp;</font>  
                <font style="font-size: 15px;color:#ffffff">2017-09-04</font>  
                <br/>  
                <hr style="border-color:#EF5FA7"/>  
                <br/>  
                <font style="font-size:14px;color:#ffffff">原文链接:</font><a style="font-size:14px;color:#ffffff">http://www.example.com</a>  
                <br/><br/>  
                <font style="font-size:14px;color:#ffffff">下面是详细内容,文字较长时会自动进行换行,可变长度的文字推荐使用richtext布局控件...</font>  
            </richtext>  
        </nview>  
    </nviews>

如上代码中,头像信息、作者信息、发布时间是顺序编写的,最终自动从左到右绘制在一行,运行结果如下:

因为设置位置坐标无效,故无法实现精确定位,比如要求发布时间居右显示,则在richtext中无法实现;

两种布局适用场景

通过如上分析,可以理解两种布局适用的场景不同:

  • 需要多个元素自左向右显示,元素长度不固定,则使用richtext
  • 文本长度不确定,需要自动换行,则使用richtext
  • 图文混排,则使用richtext
  • 需要精确定位(如右侧对齐),则使用canvas
收起阅读 »

mui两种预加载详解和爬坑

预加载

官方文档上的解释如下:
方式一:通过mui.init方法中的preloadPages参数进行配置.

mui.init({
preloadPages:[
{
url:prelaod-page-url,
id:preload-page-id,
styles:{},//窗口参数
extras:{},//自定义扩展参数
subpages:[{},{}]//预加载页面的子页面
}
],
preloadLimit:5//预加载窗口数量限制(一旦超出,先进先出)默认不限制
});

方式二:通过mui.preload方法预加载.

var page = mui.preload({
url:new-page-url,
id:new-page-id,//默认使用当前页面的url作为id
styles:{},//窗口参数
extras:{}//自定义扩展参数
});

方式一多页面加载问题如下


方式二preload需要放在plusready里面,解决如下

继续阅读 »

官方文档上的解释如下:
方式一:通过mui.init方法中的preloadPages参数进行配置.

mui.init({
preloadPages:[
{
url:prelaod-page-url,
id:preload-page-id,
styles:{},//窗口参数
extras:{},//自定义扩展参数
subpages:[{},{}]//预加载页面的子页面
}
],
preloadLimit:5//预加载窗口数量限制(一旦超出,先进先出)默认不限制
});

方式二:通过mui.preload方法预加载.

var page = mui.preload({
url:new-page-url,
id:new-page-id,//默认使用当前页面的url作为id
styles:{},//窗口参数
extras:{}//自定义扩展参数
});

方式一多页面加载问题如下


方式二preload需要放在plusready里面,解决如下

收起阅读 »

mui.init多页面写法


mui多页面预加载写法,原文档里面没有写法也没有实例,很蛋疼。


mui多页面预加载写法,原文档里面没有写法也没有实例,很蛋疼。

刚看到的一篇关于手机设置禁止横屏的解决方案,值得收藏!!!

解决横屏问题:
(转载)
真机运行时,manifest并不会实时生效,要打包才生效。
横屏有3个层级:

  1. 手机禁止横屏
  2. 手机允许横屏,但manifest禁止横屏
  3. 手机允许横屏、manifest允许横屏,但页面代码禁止横屏。

js里控制:
//仅支持横屏显示
plus.screen.lockOrientation("landscape-primary");
//仅支持横屏反方向显示
plus.screen.lockOrientation('landscape-secondary');
//仅支持竖屏显示
plus.screen.lockOrientation("portrait-primary");
//仅支持竖屏反方向显示
plus.screen.lockOrientation("portrait-secondary");

或(该方式未经测试)
// 锁定屏幕为竖屏模式,不能设备如何旋转,屏幕都不会切换到横屏模式。
window.screen.lockOrientation([“portrait-primary”,“portrait-secondary”]);
// 锁定屏幕为横屏模式,无能设备如何旋转,屏幕都不会切换到竖屏模式。
window.screen.lockOrientation([“landscape-primary”,“landscape-secondary”]);
// 取消屏幕的锁屏,屏幕回到原始状态,
window.screen.unlockOrientation();

manifest.json里控制:
"orientation": [
"portrait-primary",
"portrait-secondary"
]
该文转载地址:http://ask.dcloud.net.cn/article/522

继续阅读 »

解决横屏问题:
(转载)
真机运行时,manifest并不会实时生效,要打包才生效。
横屏有3个层级:

  1. 手机禁止横屏
  2. 手机允许横屏,但manifest禁止横屏
  3. 手机允许横屏、manifest允许横屏,但页面代码禁止横屏。

js里控制:
//仅支持横屏显示
plus.screen.lockOrientation("landscape-primary");
//仅支持横屏反方向显示
plus.screen.lockOrientation('landscape-secondary');
//仅支持竖屏显示
plus.screen.lockOrientation("portrait-primary");
//仅支持竖屏反方向显示
plus.screen.lockOrientation("portrait-secondary");

或(该方式未经测试)
// 锁定屏幕为竖屏模式,不能设备如何旋转,屏幕都不会切换到横屏模式。
window.screen.lockOrientation([“portrait-primary”,“portrait-secondary”]);
// 锁定屏幕为横屏模式,无能设备如何旋转,屏幕都不会切换到竖屏模式。
window.screen.lockOrientation([“landscape-primary”,“landscape-secondary”]);
// 取消屏幕的锁屏,屏幕回到原始状态,
window.screen.unlockOrientation();

manifest.json里控制:
"orientation": [
"portrait-primary",
"portrait-secondary"
]
该文转载地址:http://ask.dcloud.net.cn/article/522

收起阅读 »

承接MUI App,软件开发,微信开发

mui 招聘 外包 移动APP

腾印网络致力于中小企业电商服务,软件开发,系统开发,提供一站式整体解决方案。
服务范围包括:网站建设,APP开发,微信开发,软件定制,系统定制 等

服务产品
PC端:网站建设,行业网站,电商平台
移动端:手机网站,手机APP
微信开发:微官网,微商城,公众号,小程序
系统开发:直播系统,点播系统,进销存系统 等

成功案例:电商类、视频类、在线教育类、生活服务类、游戏下载类等APP

QQ:1776609688

继续阅读 »

腾印网络致力于中小企业电商服务,软件开发,系统开发,提供一站式整体解决方案。
服务范围包括:网站建设,APP开发,微信开发,软件定制,系统定制 等

服务产品
PC端:网站建设,行业网站,电商平台
移动端:手机网站,手机APP
微信开发:微官网,微商城,公众号,小程序
系统开发:直播系统,点播系统,进销存系统 等

成功案例:电商类、视频类、在线教育类、生活服务类、游戏下载类等APP

QQ:1776609688

收起阅读 »

mui(准确的说是html5+)Android原生日历提醒插入案例踩坑经历

消息提醒 日历 安卓 原生分享 HTML5+
  • 缘起
    需要在app里弄个定时提醒功能
  • 过程
    在问答社区看到一个大佬分享的安卓插入日历提醒的按理
    点击这里过去看看???
  • 但是
    看得有点晕,很多代码都不知道在干嘛(毕竟不懂原生)
    开始的时候就原样复制了一遍代码,也没怎么看,心想着能把项目完成了再说
  • 后来发现
    没登录日历账号的手机不能设置提醒,反正就是各种报错
    好了,不说心路历程了
    直接上干货
    (html和引入date picker就省略了哈)
    (function($) {  
    var setcalendar = function() {  
        $.toast('功能加载中,请稍后', {  
            type: 'div',  
            duration: 1000  
        });  
    };  
    $.plusReady(function() {  
        var calanderURL = 'content://com.android.calendar/calendars',  
            ContentValues = plus.android.importClass("android.content.ContentValues"),  
            Uri = plus.android.importClass('android.net.Uri'),  
            Calendar = plus.android.importClass('java.util.Calendar'),  
            main = plus.android.runtimeMainActivity(),  
            userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null),  
            userCursor_count = plus.android.invoke(userCursor, 'getCount'),  
            TimeZone = plus.android.importClass('java.util.TimeZone'),  
            TimeZone_str = plus.android.invoke(TimeZone.getDefault(), 'getID');  
        setcalendar = function(title, description, date_str) {  
            if(userCursor_count <= 0) {//如果没有日历账户  
                var account = new ContentValues(),  
                    buildUpon = plus.android.invoke(Uri.parse(calanderURL), 'buildUpon'),  
                    CalendarContract = plus.android.importClass('android.provider.CalendarContract');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', CalendarContract.CALLER_IS_SYNCADAPTER, 'true');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', 'account_name', 'someone@something.com');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', 'account_type', 'com.android.exchange');  
                //设置账户信息  
                account.put('name', 'someone');  
                account.put('account_name', 'someone@something.com');  
                account.put('account_type', 'com.android.exchange');  
                account.put('calendar_displayName', 'someone_calendar');  
                account.put('visible', 1);  
                account.put('calendar_color', '-9206951');  
                account.put('calendar_access_level', '700');  
                account.put('sync_events', 1);  
                account.put('calendar_timezone', TimeZone_str);  
                account.put('ownerAccount', 'someone@something.com');  
                account.put('canOrganizerRespond', 0);  
                //保存账户信息  
                plus.android.invoke(main.getContentResolver(), 'insert', plus.android.invoke(buildUpon, 'build'), account);  
                //重新定义userCursor  
                userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null);  
                //重新定义userCursor_count  
                userCursor_count++;  
            }  
            plus.android.invoke(userCursor, 'moveToLast');  
            var calId = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', '_id')),  
                events = new ContentValues(),  
                mCalendar = Calendar.getInstance(),  
                date = date_str.split(/\s{1}|:|-/g);  
            plus.android.invoke(mCalendar, 'set', Calendar.YEAR, ~~date[0]);  
            plus.android.invoke(mCalendar, 'set', Calendar.MONTH, ((~~date[1]) - 1));  
            plus.android.invoke(mCalendar, 'set', Calendar.DATE, ~~date[2]);  
            plus.android.invoke(mCalendar, 'set', Calendar.HOUR_OF_DAY, ~~date[3]);  
            plus.android.invoke(mCalendar, 'set', Calendar.MINUTE, ~~date[4]);  
            var start = plus.android.invoke(plus.android.invoke(mCalendar, 'getTime'), 'getTime'),  
                  end = plus.android.invoke(plus.android.invoke(mCalendar, 'getTime'), 'getTime');  
            //设置日历事件  
            events.put('title', title);  
            events.put('description', description);  
            events.put('calendar_id', calId);  
            events.put('dtstart', start);  
            events.put('dtend', end);  
            events.put('hasAlarm', 1);  
            events.put('eventTimezone', TimeZone_str);  
            var newEvent = plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/events'), events);  
            var id = plus.android.invoke(newEvent, 'getLastPathSegment');  
            var values = new ContentValues();  
            values.put('event_id', id);  
            values.put('minutes', '5');  
            values.put('method', '1');  
            plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/reminders'), values);  
            $.toast('设置提醒成功');  
        }  
    });  
    $.ready(function() {  
        $('.mui-content').on('tap', 'button.mui-btn', function() {  
            var picker = new $.DtPicker();  
            picker.show(function(rs) {  
                setcalendar('测试提醒标题', '测试提醒内容', rs.text);  
                picker.dispose();  
            });  
        });  
    });  
    })(mui);
  • 最后说一句
  • 打包的时候记得勾选日历权限
  • 这个问题坑死我了
继续阅读 »
  • 缘起
    需要在app里弄个定时提醒功能
  • 过程
    在问答社区看到一个大佬分享的安卓插入日历提醒的按理
    点击这里过去看看???
  • 但是
    看得有点晕,很多代码都不知道在干嘛(毕竟不懂原生)
    开始的时候就原样复制了一遍代码,也没怎么看,心想着能把项目完成了再说
  • 后来发现
    没登录日历账号的手机不能设置提醒,反正就是各种报错
    好了,不说心路历程了
    直接上干货
    (html和引入date picker就省略了哈)
    (function($) {  
    var setcalendar = function() {  
        $.toast('功能加载中,请稍后', {  
            type: 'div',  
            duration: 1000  
        });  
    };  
    $.plusReady(function() {  
        var calanderURL = 'content://com.android.calendar/calendars',  
            ContentValues = plus.android.importClass("android.content.ContentValues"),  
            Uri = plus.android.importClass('android.net.Uri'),  
            Calendar = plus.android.importClass('java.util.Calendar'),  
            main = plus.android.runtimeMainActivity(),  
            userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null),  
            userCursor_count = plus.android.invoke(userCursor, 'getCount'),  
            TimeZone = plus.android.importClass('java.util.TimeZone'),  
            TimeZone_str = plus.android.invoke(TimeZone.getDefault(), 'getID');  
        setcalendar = function(title, description, date_str) {  
            if(userCursor_count <= 0) {//如果没有日历账户  
                var account = new ContentValues(),  
                    buildUpon = plus.android.invoke(Uri.parse(calanderURL), 'buildUpon'),  
                    CalendarContract = plus.android.importClass('android.provider.CalendarContract');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', CalendarContract.CALLER_IS_SYNCADAPTER, 'true');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', 'account_name', 'someone@something.com');  
                plus.android.invoke(buildUpon, 'appendQueryParameter', 'account_type', 'com.android.exchange');  
                //设置账户信息  
                account.put('name', 'someone');  
                account.put('account_name', 'someone@something.com');  
                account.put('account_type', 'com.android.exchange');  
                account.put('calendar_displayName', 'someone_calendar');  
                account.put('visible', 1);  
                account.put('calendar_color', '-9206951');  
                account.put('calendar_access_level', '700');  
                account.put('sync_events', 1);  
                account.put('calendar_timezone', TimeZone_str);  
                account.put('ownerAccount', 'someone@something.com');  
                account.put('canOrganizerRespond', 0);  
                //保存账户信息  
                plus.android.invoke(main.getContentResolver(), 'insert', plus.android.invoke(buildUpon, 'build'), account);  
                //重新定义userCursor  
                userCursor = plus.android.invoke(main.getContentResolver(), 'query', Uri.parse(calanderURL), null, null, null, null);  
                //重新定义userCursor_count  
                userCursor_count++;  
            }  
            plus.android.invoke(userCursor, 'moveToLast');  
            var calId = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', '_id')),  
                events = new ContentValues(),  
                mCalendar = Calendar.getInstance(),  
                date = date_str.split(/\s{1}|:|-/g);  
            plus.android.invoke(mCalendar, 'set', Calendar.YEAR, ~~date[0]);  
            plus.android.invoke(mCalendar, 'set', Calendar.MONTH, ((~~date[1]) - 1));  
            plus.android.invoke(mCalendar, 'set', Calendar.DATE, ~~date[2]);  
            plus.android.invoke(mCalendar, 'set', Calendar.HOUR_OF_DAY, ~~date[3]);  
            plus.android.invoke(mCalendar, 'set', Calendar.MINUTE, ~~date[4]);  
            var start = plus.android.invoke(plus.android.invoke(mCalendar, 'getTime'), 'getTime'),  
                  end = plus.android.invoke(plus.android.invoke(mCalendar, 'getTime'), 'getTime');  
            //设置日历事件  
            events.put('title', title);  
            events.put('description', description);  
            events.put('calendar_id', calId);  
            events.put('dtstart', start);  
            events.put('dtend', end);  
            events.put('hasAlarm', 1);  
            events.put('eventTimezone', TimeZone_str);  
            var newEvent = plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/events'), events);  
            var id = plus.android.invoke(newEvent, 'getLastPathSegment');  
            var values = new ContentValues();  
            values.put('event_id', id);  
            values.put('minutes', '5');  
            values.put('method', '1');  
            plus.android.invoke(main.getContentResolver(), 'insert', Uri.parse('content://com.android.calendar/reminders'), values);  
            $.toast('设置提醒成功');  
        }  
    });  
    $.ready(function() {  
        $('.mui-content').on('tap', 'button.mui-btn', function() {  
            var picker = new $.DtPicker();  
            picker.show(function(rs) {  
                setcalendar('测试提醒标题', '测试提醒内容', rs.text);  
                picker.dispose();  
            });  
        });  
    });  
    })(mui);
  • 最后说一句
  • 打包的时候记得勾选日历权限
  • 这个问题坑死我了
收起阅读 »

分享一个图片在线缩放压缩工具

图片加工 图片压缩

功能
使用场景
工具入口
功能
上次图片,可自动生成多种分辨率、大小的图片
可自由的增加、删除和修改要生成的图片的分辨率和大小
设置的图片生成分辨率和大小可重复使用,只需上传不同的图片,即可针对当前图片生成配置好的图片分辨率和样式
多种压缩方式可以选择,操作简便

alt
使用场景
在平时的工作中,经常会碰到需要对同一张图片生成多种分辨率,特别是需要对好多图片都生成同样的分辨率的时候,就需要重复无数次傻瓜的操作。

针对这种情况,使用可配置的图片批量缩放处理工具,可以极大的提高工作效率,特别是对于不熟悉各类图片处理软件的朋友来说,会有较大帮助。

友间共享-图片处理工具

本文链接:https://blog.betweenfriends.cn/post/onlinecompressimage.html

继续阅读 »

功能
使用场景
工具入口
功能
上次图片,可自动生成多种分辨率、大小的图片
可自由的增加、删除和修改要生成的图片的分辨率和大小
设置的图片生成分辨率和大小可重复使用,只需上传不同的图片,即可针对当前图片生成配置好的图片分辨率和样式
多种压缩方式可以选择,操作简便

alt
使用场景
在平时的工作中,经常会碰到需要对同一张图片生成多种分辨率,特别是需要对好多图片都生成同样的分辨率的时候,就需要重复无数次傻瓜的操作。

针对这种情况,使用可配置的图片批量缩放处理工具,可以极大的提高工作效率,特别是对于不熟悉各类图片处理软件的朋友来说,会有较大帮助。

友间共享-图片处理工具

本文链接:https://blog.betweenfriends.cn/post/onlinecompressimage.html

收起阅读 »

原生轮播示例(slider-native)

HTML5+ mui ImageSlider

native模式的图片轮播是的subNViews属性配置而成的,目前支持循环播放、点击预览、双指放大功能,不支持自动轮播;可以通过5+API手动添加图片、获取当前图片轮播控件显示的图片索引值、设置图片轮播控件的图片;native模式的图片轮播适用于展示商品详情的场景,下面是效果图:

实现方案

创建webview时,配置subnview节点即可,代码如下:

var webview = plus.webview.create("slider-native.html", "slider-native", {  
    titleNView:{//配置标题  
        'backgroundColor': '#f7f7f7',//导航栏背景色  
        'titleText': 'slider(native模式)',//导航栏标题  
        'titleColor': '#000000',//文字颜色  
        autoBackButton: true//自动绘制返回箭头  
    },  
    subNViews:[{ //配置图片轮播  
        id: 'slider-native',  
        type: 'ImageSlider',  
        styles: {//这里的left和top是控制控件的位置;Width和height控制控件大小  
            left: 0,  
            top: 0,  
            width:'100%',  
            height: '200px',  
            position: 'static',//static正常定位随窗口滚动,absolute时不随页面滚动  
            type:'transparent',//透明渐变样式标题栏,可以使slider通顶,更加美观  
            loop: true,//是否循环播放  
            images: [{//图片路径和图片大小  
                src: '_www/images/yuantiao.jpg',  
                width: '100%'  
            }, {  
                src: '_www/images/shuijiao.jpg',  
                width: '100%'  
            }, {  
                src: '_www/images/muwu.jpg',  
                width: '100%'  
            }, {  
                src: '_www/images/cbd.jpg',  
                width: '100%'  
            }]  
        }  
    }]  
});  
webview.show("slide-in-right", 300);//显示webview

原生图片轮播(ImageSlider)相关5+api地址:http://www.html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.ImageSlider

体验及源码

下载最新版Hello MUI可体验效果:Hello MUI首页--> gallery slider(图片轮播) --> 默认样式(native模式)。
native模式的图片轮播是在创建webview的时候一同创建的,所以想要在hello mui的源码中找相关代码,不能在slider-native.html里面找,得在index.html中创建slider-native.html的位置查找。
附件是本示例所用代码。

继续阅读 »

native模式的图片轮播是的subNViews属性配置而成的,目前支持循环播放、点击预览、双指放大功能,不支持自动轮播;可以通过5+API手动添加图片、获取当前图片轮播控件显示的图片索引值、设置图片轮播控件的图片;native模式的图片轮播适用于展示商品详情的场景,下面是效果图:

实现方案

创建webview时,配置subnview节点即可,代码如下:

var webview = plus.webview.create("slider-native.html", "slider-native", {  
    titleNView:{//配置标题  
        'backgroundColor': '#f7f7f7',//导航栏背景色  
        'titleText': 'slider(native模式)',//导航栏标题  
        'titleColor': '#000000',//文字颜色  
        autoBackButton: true//自动绘制返回箭头  
    },  
    subNViews:[{ //配置图片轮播  
        id: 'slider-native',  
        type: 'ImageSlider',  
        styles: {//这里的left和top是控制控件的位置;Width和height控制控件大小  
            left: 0,  
            top: 0,  
            width:'100%',  
            height: '200px',  
            position: 'static',//static正常定位随窗口滚动,absolute时不随页面滚动  
            type:'transparent',//透明渐变样式标题栏,可以使slider通顶,更加美观  
            loop: true,//是否循环播放  
            images: [{//图片路径和图片大小  
                src: '_www/images/yuantiao.jpg',  
                width: '100%'  
            }, {  
                src: '_www/images/shuijiao.jpg',  
                width: '100%'  
            }, {  
                src: '_www/images/muwu.jpg',  
                width: '100%'  
            }, {  
                src: '_www/images/cbd.jpg',  
                width: '100%'  
            }]  
        }  
    }]  
});  
webview.show("slide-in-right", 300);//显示webview

原生图片轮播(ImageSlider)相关5+api地址:http://www.html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.ImageSlider

体验及源码

下载最新版Hello MUI可体验效果:Hello MUI首页--> gallery slider(图片轮播) --> 默认样式(native模式)。
native模式的图片轮播是在创建webview的时候一同创建的,所以想要在hello mui的源码中找相关代码,不能在slider-native.html里面找,得在index.html中创建slider-native.html的位置查找。
附件是本示例所用代码。

收起阅读 »