Ueditor中支持音頻

已有不少小伙伴給出了Ueditor中拓展支持插入音頻功能的方法橱健,但還是存在一些特例性的問(wèn)題斯撮。踩完數(shù)個(gè)坑后總算把音頻功能基本搞順暢,這里整理匯總留個(gè)記錄础锐,以防年長(zhǎng)健忘嗓节。

1. 修改ueditor.config.js文件,增加插入音頻功能入口:

(1)在toolbars中增加'insertaudio'

toolbars: [[
    'source', '|', 'undo', 'redo', '|',
    ...
    'simpleupload',  'insertimage',  'emotion', 'insertaudio', 'insertvideo', 
    '|',
    'horizontal', 'date', 'time', 'spechars', '|', 'wordimage'
]]

(2)在labelMap中增加'insertaudio'對(duì)應(yīng)的提示文字

labelMap: {
    'anchor' : '', 
    'vaecolor' : '自定義字體顏色',
    'insertaudio' : '音頻',
}

2. 修改ueditor.all.js文件郁稍,增加插入音頻頁(yè)面和命令入口:

(1)在iframeUrlMap中增加插入音頻頁(yè)面的路徑

var iframeUrlMap = {
    ...
    'insertaudio':'~/dialogs/audio/audio.html',
    'insertvideo':'~/dialogs/video/video.html',
    ...
    };

(2)在btnCmds中增加點(diǎn)擊插入音頻觸發(fā)的命令

var btnCmds = ['undo', 'redo', 'formatmatch',
    ...
    'insertaudio'];

(3)在dialogBtns的ok屬性中增加插入音頻對(duì)話框

var dialogBtns = {
    noOk: ['searchreplace', 'help', 'spechars', 'webapp','preview'],
    ok: ['attachment', 'anchor', 'link', 'insertimage', 'map', 'gmap', 
        ...
        'insertaudio', 'vaecolor']
};

3. 增加audio.html:

(1)在ueditor/dialogs目錄下增加audio目錄赦政,并增加audio.html,實(shí)現(xiàn)點(diǎn)擊插入音頻按鈕時(shí)彈出的插入音頻頁(yè)面(此處可根據(jù)自身業(yè)務(wù)需求參照video.html或image.html來(lái)實(shí)現(xiàn))

注:此頁(yè)面中提供了插入音頻和上傳音頻兩種方式耀怜,現(xiàn)只實(shí)現(xiàn)了上傳音頻的功能恢着。插入音頻相對(duì)比較簡(jiǎn)單,不詳述

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>音頻對(duì)話框</title>
    <script type="text/javascript" src="../internal.js"></script>
    <!-- jquery -->
    <script type="text/javascript" src="../../third-party/jquery-1.10.2.min.js"></script>
    <script type="text/javascript" src="../../../mains/public/public.js"></script>
    <!-- webuploader -->
    <script src="../../third-party/webuploader/webuploader.min.js"></script>
    <link rel="stylesheet" type="text/css" href="../../third-party/webuploader/webuploader.css">
    <!-- image dialog -->
    <link rel="stylesheet" href="audio.css" type="text/css" />
</head>
<body>
    <div class="wrapper">
        <div id="tabhead" class="tabhead">
            <span class="tab focus" data-content-id="upload"><var id="lang_tab_upload"></var></span>
        </div>
        <div id="tabbody" class="tabbody">

            <!-- 插入音頻 -->
            <div id="remote" class="panel">
                <div class="top">
                    <div class="row">
                        <label for="url"><var id="lang_input_url"></var></label>
                        <span><input class="text" id="url" type="text"/></span>
                    </div>
                </div>
                <div class="left">
                    <div class="row">
                        <label><var id="lang_input_size"></var></label>
                        <span><var id="lang_input_width">&nbsp;&nbsp;</var><input class="text" type="text" id="width"/>px </span>
                        <span><var id="lang_input_height">&nbsp;&nbsp;</var><input class="text" type="text" id="height"/>px </span>
                        <span><input id="lock" type="checkbox" disabled="disabled"><span id="lockicon"></span></span>
                    </div>
                    <div class="row">
                        <label><var id="lang_input_border"></var></label>
                        <span><input class="text" type="text" id="border"/>px </span>
                    </div>
                    <div class="row">
                        <label><var id="lang_input_vhspace"></var></label>
                        <span><input class="text" type="text" id="vhSpace"/>px </span>
                    </div>
                    <div class="row">
                        <label><var id="lang_input_title"></var></label>
                        <span><input class="text" type="text" id="title"/></span>
                    </div>
                </div>
                <div class="right"><div id="preview"></div></div>
            </div>

            <!-- 上傳音頻 -->
            <div id="upload" class="panel focus">
                <div class="titleBar">
                    <label>標(biāo)題</label>
                    <input type="text" class="uploadAudioTitle"/>
                </div>
                <div id="queueList" class="queueList">
                    <div class="statusBar element-invisible">
                        <div class="progress">
                            <span class="text">0%</span>
                            <span class="percentage"></span>
                        </div><div class="info"></div>
                        <div class="btns">
                            <div id="filePickerBtn"></div>
                            <div class="uploadBtn"><var id="lang_start_upload"></var></div>
                        </div>
                    </div>
                    <div id="dndArea" class="placeholder">
                        <div class="filePickerContainer">
                            <div id="filePickerReady"></div>
                        </div>
                    </div>
                    <ul class="filelist element-invisible">
                        <li id="filePickerBlock" class="filePickerBlock"></li>
                    </ul>
                </div>
            </div>
        </div>
    </div>
    <script type="text/javascript" src="audio.js"></script>
</body>
</html>

(2)增加audio.css用以實(shí)現(xiàn)audio.html中的相關(guān)樣式(此處可照搬video.css或image.css后對(duì)應(yīng)修改下)

/* 上傳音頻 */
.tabbody #upload.panel {
    width: 0;
    height: 0;
    overflow: hidden;
    position: absolute !important;
    clip: rect(1px, 1px, 1px, 1px);
    background: #fff;
    display: block;
}

.tabbody #upload.panel.focus {
    width: 100%;
    height: 300px;
    display: block;
    clip: auto;
}

#upload .titleBar {
    width: 100%;
    margin: 10px;
    font-size: 14px;
}

#upload .titleBar .uploadAudioTitle {
    margin-left: 5px;
    width: 88%;
}

// 后面省略财破,照搬后修改即可
...

(3)增加audio.js用以實(shí)現(xiàn)audio.html中的上傳音頻掰派、插入音頻代碼等操作(同樣的,照搬vedio.js或image.js后對(duì)應(yīng)修改即可)

注:涉及到幾個(gè)特例化的地方:①音頻上傳至服務(wù)器或云存儲(chǔ)左痢,改造過(guò)圖片上傳的應(yīng)該駕輕就熟了靡羡;②因要求給插入的音頻配上標(biāo)題系洛,此處上傳為單個(gè)音頻文件上傳,故要禁用多選批量上傳略步;③開(kāi)始上傳描扯、暫停上傳、取消上傳的相應(yīng)功能趟薄;④最終insertList的結(jié)構(gòu)绽诚,要傳遞給ueditor.all.js中的insertaudio命令以向編輯器內(nèi)插入音頻控件代碼

(function () {

    var insertaudio,
        uploadaudio;
    // 音頻文件key前綴
    var keyPrefix = editor.getOpt('keyPrefix') + '/audio';
    
    window.onload = function () {
        initTabs();
        initButtons();
    };

    /* 初始化tab標(biāo)簽 */
    function initTabs() {
        var tabs = $G('tabhead').children;
        var audio = editor.selection.getRange().getClosedNode();
        var id = tabs[0].getAttribute('data-content-id');
        for (var i = 0; i < tabs.length; i++) {
            domUtils.on(tabs[i], "click", function (e) {
                var j, bodyId, target = e.target || e.srcElement;
                id = target.getAttribute('data-content-id');
                for (j = 0; j < tabs.length; j++) {
                    bodyId = tabs[j].getAttribute('data-content-id');
                    if(tabs[j] == target){
                        domUtils.addClass(tabs[j], 'focus');
                        domUtils.addClass($G(bodyId), 'focus');
                    }else {
                        domUtils.removeClasses(tabs[j], 'focus');
                        domUtils.removeClasses($G(bodyId), 'focus');
                    }
                }
            });
        }
        switch (id) {
        case 'remote':  // 插入音頻/遠(yuǎn)程音頻(預(yù)留)
            insertaudio = insertaudio || new RemoteAudio();
            break;
        case 'upload':  // 上傳音頻(主要)
            uploadaudio = uploadaudio || new UploadAudio('queueList');
            break;
        }
    }

    /* 初始化onok事件 */
    function initButtons() {
        dialog.onok = function () {
            var remote = false, list = [], id, tabs = $G('tabhead').children;
            for (var i = 0; i < tabs.length; i++) {
                if (domUtils.hasClass(tabs[i], 'focus')) {
                    id = tabs[i].getAttribute('data-content-id');
                    break;
                }
            }
            switch (id) {
                case 'remote':
                    list = insertaudio.getInsertList();
                    break;
                case 'upload':
                    list = uploadaudio.getInsertList();
                    var count = uploadaudio.getQueueCount();
                    if (count) {
                        $('.info', '#queueList').html('<span style="color:red;">' + '還有2個(gè)未上傳文件'.replace(/[\d]/, count) + '</span>');
                        return false;
                    }
                    
                    // 配上標(biāo)題
                    var title = $('.uploadAudioTitle').val();
                    if (!title || $.trim(title) == '') {
                        alert('請(qǐng)?zhí)顚憳?biāo)題');
                        $('.uploadAudioTitle').focus();
                        return false;
                    }
                    if(list) {
                        for(var i = 0; i < list.length; i++) {
                            var f = list[i];
                            f['title'] = title;
                            list[i] = f;
                        }
                    }
                    break;
            }
            
            if(list) {
                editor.execCommand('insertaudio', list);
                remote && editor.fireEvent("catchRemoteAudio");
            }
        };
    }

    /* 上傳音頻 */
    function UploadAudio(target) {
        this.$wrap = target.constructor == String ? $('#' + target) : $(target);
        this.init();
    }
    UploadAudio.prototype = {
        init: function () {
            this.audioList = [];
            this.initContainer();
            this.initUploader();
        },
        initContainer: function () {
            this.$queue = this.$wrap.find('.filelist');
        },
        /* 初始化容器 */
        initUploader: function () {
            var _this = this,
                $ = jQuery,    // just in case. Make sure it's not an other libaray.
                $wrap = _this.$wrap,
            // 文件容器
                $queue = $wrap.find('.filelist'),
            // 狀態(tài)欄,包括進(jìn)度和控制按鈕
                $statusBar = $wrap.find('.statusBar'),
            // 文件總體選擇信息杭煎。
                $info = $statusBar.find('.info'),
            // 上傳按鈕
                $upload = $wrap.find('.uploadBtn'),
            // 上傳按鈕
                $filePickerBtn = $wrap.find('.filePickerBtn'),
            // 上傳按鈕
                $filePickerBlock = $wrap.find('.filePickerBlock'),
            // 沒(méi)選擇文件之前的內(nèi)容恩够。
                $placeHolder = $wrap.find('.placeholder'),
            // 總體進(jìn)度條
                $progress = $statusBar.find('.progress').hide(),
            // 添加的文件數(shù)量
                fileCount = 0,
            // 添加的文件總大小
                fileSize = 0,
            // 優(yōu)化retina, 在retina下這個(gè)值是2
                ratio = window.devicePixelRatio || 1,
            // 縮略圖大小
                thumbnailWidth = 550 * ratio,
                thumbnailHeight = 113 * ratio,
            // 可能有pedding, ready, uploading, confirm, done.
                state = '',
            // 所有文件的進(jìn)度信息,key為file id
                percentages = {},
                supportTransition = (function () {
                    var s = document.createElement('p').style,
                        r = 'transition' in s ||
                            'WebkitTransition' in s ||
                            'MozTransition' in s ||
                            'msTransition' in s ||
                            'OTransition' in s;
                    s = null;
                    return r;
                })(),
            // WebUploader實(shí)例
                uploader,
                actionUrl = editor.getActionUrl(editor.getOpt('audioActionName')),
                acceptExtensions = (editor.getOpt('audioAllowFiles') || []).join('').replace(/\./g, ',').replace(/^[,]/, ''),
                audioMaxSize = editor.getOpt('audioMaxSize'),
                imageCompressBorder = editor.getOpt('imageCompressBorder');

            if (!WebUploader.Uploader.support()) {
                $('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();
                return;
            } else if (!editor.getOpt('audioActionName')) {
                $('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();
                return;
            }

            uploader = _this.uploader = WebUploader.create({
                pick: {
                    id: '#filePickerReady',
                    label: lang.uploadSelectFile,
                    multiple: false // 限制為單選
                },
                accept: {
                    title: 'Audios',
                    extensions: acceptExtensions,
                    mimeTypes: 'audio/mp3,audio/amr,audio/wma,audio/wav'
                },
                swf: '../../third-party/webuploader/Uploader.swf',
                server: actionUrl,
                fileVal: editor.getOpt('audioFieldName'),
                duplicate: false,
                fileNumLimit: 1,    // 限制為單個(gè)文件
                fileSingleSizeLimit: audioMaxSize    // 默認(rèn) 30 M
            });
            uploader.addButton({
                id: '#filePickerBlock'
            });
//            uploader.addButton({
//                id: '#filePickerBtn',
//                label: lang.uploadAddFile
//            });

            setState('pedding');

            // 當(dāng)有文件添加進(jìn)來(lái)時(shí)執(zhí)行羡铲,負(fù)責(zé)view的創(chuàng)建
            function addFile(file) {
                var $li = $('<li id="' + file.id + '">' +
                        '<p class="title">' + file.name + '</p>' +
                        '<p class="progress"><span></span></p>' +
                        '</li>'),

                    $btns = $('<div class="file-panel">' +
                        '<span class="cancel">' + lang.uploadDelete + '</span>' +
                        '<span class="rotateRight">' + lang.uploadTurnRight + '</span>' +
                        '<span class="rotateLeft">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),
                    $prgress = $li.find('p.progress span'),
                    $wrap = $li.find('p.imgWrap'),
                    $info = $('<p class="error"></p>').hide().appendTo($li),

                    showError = function (code) {
                        switch (code) {
                            case 'exceed_size':
                                text = lang.errorExceedSize;
                                break;
                            case 'interrupt':
                                text = lang.errorInterrupt;
                                break;
                            case 'http':
                                text = lang.errorHttp;
                                break;
                            case 'not_allow_type':
                                text = lang.errorFileType;
                                break;
                            default:
                                text = lang.errorUploadRetry;
                                break;
                        }
                        $info.text(text).show();
                    };

                if (file.getStatus() === 'invalid') {
                    showError(file.statusText);
                } else {
                    percentages[ file.id ] = [ file.size, 0 ];
                    file.rotation = 0;

                    /* 檢查文件格式 */
                    if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {
                        showError('not_allow_type');
                        uploader.removeFile(file);
                    }
                }

                file.on('statuschange', function (cur, prev) {
                    if (prev === 'progress') {
                        $prgress.hide().width(0);
                    } else if (prev === 'queued') {
                        $li.off('mouseenter mouseleave');
                        $btns.remove();
                    }
                    // 成功
                    if (cur === 'error' || cur === 'invalid') {
                        showError(file.statusText);
                        percentages[ file.id ][ 1 ] = 1;
                    } else if (cur === 'interrupt') {
                        showError('interrupt');
                    } else if (cur === 'queued') {
                        percentages[ file.id ][ 1 ] = 0;
                    } else if (cur === 'progress') {
                        $info.hide();
                        $prgress.css('display', 'block');
                    } else if (cur === 'complete') {
                    }

                    $li.removeClass('state-' + prev).addClass('state-' + cur);
                });

                $li.on('mouseenter', function () {
                    $btns.stop().animate({height: 30});
                });
                $li.on('mouseleave', function () {
                    $btns.stop().animate({height: 0});
                });

                $btns.on('click', 'span', function () {
                    var index = $(this).index(),
                        deg;

                    switch (index) {
                        case 0:
                            uploader.removeFile(file);
                            return;
                        case 1:
                            file.rotation += 90;
                            break;
                        case 2:
                            file.rotation -= 90;
                            break;
                    }

                    if (supportTransition) {
                        deg = 'rotate(' + file.rotation + 'deg)';
                        $wrap.css({
                            '-webkit-transform': deg,
                            '-mos-transform': deg,
                            '-o-transform': deg,
                            'transform': deg
                        });
                    } else {
                        $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');
                    }

                });

                $li.insertBefore($filePickerBlock);
                // 隱藏繼續(xù)添加控件蜂桶,設(shè)置為單個(gè)文件上傳
                $filePickerBlock.hide();
            }
            
            // 取消上傳
            function cancelFile(file) {
                 var $li = $('#' + file.id);
                 var spans = $progress.children();
                 spans.eq(0).text('0%');
                 spans.eq(1).css('width', '0%');
                 $progress.css('display', 'none');
                 $('.statusBar').children('.info').css('display', 'inline-block');
                 $('.error').remove();
                 var upBtn = $('.uploadBtn');
                 upBtn.removeClass('state-paused disabled');
                 upBtn.addClass('state-ready');
                 upBtn.html(lang.uploadStart);
            }

            // 負(fù)責(zé)view的銷毀
            function removeFile(file) {
                var $li = $('#' + file.id);
                delete percentages[ file.id ];
                updateTotalProgress();
                $li.off().find('.file-panel').off().end().remove();
                // 顯示繼續(xù)添加控件
                $filePickerBlock.show();
            }

            function updateTotalProgress() {
                var loaded = 0,
                    total = 0,
                    spans = $progress.children(),
                    percent;

                $.each(percentages, function (k, v) {
                    total += v[ 0 ];
                    loaded += v[ 0 ] * v[ 1 ];
                });

                percent = total ? loaded / total : 0;

                spans.eq(0).text(Math.round(percent * 100) + '%');
                spans.eq(1).css('width', Math.round(percent * 100) + '%');
                updateStatus();
            }

            function setState(val, files) {

                if (val != state) {

                    var stats = uploader.getStats();

                    $upload.removeClass('state-' + state);
                    $upload.addClass('state-' + val);

                    switch (val) {

                        /* 未選擇文件 */
                        case 'pedding':
                            $queue.addClass('element-invisible');
                            $statusBar.addClass('element-invisible');
                            $placeHolder.removeClass('element-invisible');
                            $progress.hide(); $info.hide();
                            uploader.refresh();
                            break;

                        /* 可以開(kāi)始上傳 */
                        case 'ready':
                            $placeHolder.addClass('element-invisible');
                            $queue.removeClass('element-invisible');
                            $statusBar.removeClass('element-invisible');
                            $progress.hide(); $info.show();
                            $upload.text(lang.uploadStart);
                            uploader.refresh();
                            break;

                        /* 上傳中 */
                        case 'uploading':
                            $progress.show(); $info.hide();
//                            $upload.text(lang.uploadPause);
                            $upload.text(lang.uploadCancel);
                            break;

                        /* 暫停上傳 */
                        case 'paused':
                            $progress.show(); $info.hide();
                            $upload.text(lang.uploadContinue);
                            break;
                        
                        /* 取消上傳 */
                        case 'cancel':
                            $placeHolder.addClass('element-invisible');
                            $queue.removeClass('element-invisible');
                            $statusBar.removeClass('element-invisible');
                            $progress.hide(); $info.show();
                            $upload.text(lang.uploadStart);
                            uploader.refresh();
                            break;
                            
                        case 'confirm':
                            $progress.show(); $info.hide();
                            $upload.text(lang.uploadStart);

                            stats = uploader.getStats();
                            if (stats.successNum && !stats.uploadFailNum) {
                                setState('finish');
                                return;
                            }
                            break;
                        
                        case 'finish':
                            $progress.hide(); $info.show();
                            if (stats.uploadFailNum) {
                                $upload.text(lang.uploadRetry);
                            } else {
                                $upload.text(lang.uploadStart);
                            }
                            break;
                    }

                    state = val;
                    updateStatus();

                }

                if (!_this.getQueueCount()) {
                    $upload.addClass('disabled')
                } else {
                    $upload.removeClass('disabled')
                }

            }

            function updateStatus() {
                var text = '', stats;

                if (state === 'ready') {
                    text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));
                } else if (state === 'confirm') {
                    stats = uploader.getStats();
                    if (stats.uploadFailNum) {
                        text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);
                    }
                } else {
                    stats = uploader.getStats();
                    text = lang.updateStatusFinish.replace('_', fileCount).
                        replace('_KB', WebUploader.formatSize(fileSize)).
                        replace('_', stats.successNum);

                    if (stats.uploadFailNum) {
                        text += lang.updateStatusError.replace('_', stats.uploadFailNum);
                    }
                }

                $info.html(text);
            }

            uploader.on('fileQueued', function (file) {
                fileCount++;
                fileSize += file.size;

                if (fileCount === 1) {
                    $placeHolder.addClass('element-invisible');
                    $statusBar.show();
                }

                addFile(file);
            });

            uploader.on('fileDequeued', function (file) {
                fileCount--;
                fileSize -= file.size;

                removeFile(file);
                updateTotalProgress();
            });

            uploader.on('filesQueued', function (file) {
                if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {
                    setState('ready');
                }
                updateTotalProgress();
            });

            uploader.on('all', function (type, files) {
                switch (type) {
                    case 'uploadFinished':
                        setState('confirm', files);
                        break;
                    case 'startUpload':
                        /* 添加額外的GET參數(shù) */
                        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '';
                            //url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);
                        uploader.option('server', editor.getOpt('imageUrl'));
                        
                        setState('uploading', files);
                        break;
                    case 'stopUpload':
                        setState('paused', files);
                        break;
                }
            });

            uploader.on('uploadBeforeSend', function (file, data, header) {
                //這里可以通過(guò)data對(duì)象添加POST參數(shù)
                header['X_Requested_With'] = 'XMLHttpRequest';
                
                // 上傳token
                var token = getUploadToken4UE();
                if (token == null) {
                    alert('獲取上傳token異常,請(qǐng)稍后再試~');
                    return false;
                }
                data['token'] = token;
                // 文件key
                data['key'] = keyPrefix + '/' + uuid();
            });

            uploader.on('uploadProgress', function (file, percentage) {
                
                var $li = $('#' + file.id),
                    $percent = $li.find('.progress span');

                $percent.css('width', percentage * 100 + '%');
                percentages[ file.id ][ 1 ] = percentage;
                updateTotalProgress();
            });

            uploader.on('uploadSuccess', function (file, ret) {
            
                var $file = $('#' + file.id);
                try {
                    var responseText = (ret._raw || ret),
                        json = utils.str2json(responseText);
                    if (json.state == 'SUCCESS') {
                        //_this.audioList.push(json);
                        _this.audioList[$file.index()] = json;   //按選擇好的文件列表順序存儲(chǔ)
                        $file.append('<span class="success"></span>');
                    } else {
                        $file.find('.error').text(json.state).show();
                    }
                } catch (e) {
                    $file.find('.error').text(lang.errorServerUpload).show();
                }
            });

            uploader.on('uploadError', function (file, code) {
            });
            uploader.on('error', function (code, file) {
                if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {
                    addFile(file);
                }
            });
            uploader.on('uploadComplete', function (file, ret) {
            });

            $upload.on('click', function () {
                if ($(this).hasClass('disabled')) {
                    return false;
                }

                if (state === 'ready') {
                    uploader.upload();
                } else if (state === 'paused') {
                    uploader.upload();
                } else if (state === 'cancel') {
                    uploader.upload();
                } else if (state === 'uploading') {
//                  uploader.stop();    
                    // 調(diào)整為取消上傳
                    var file = uploader.getFiles()[0];
                    uploader.stop(file);
//                  removeFile(file);
                    cancelFile(file);
//                    setState('cancel');
                }
            });

            $upload.addClass('state-' + state);
            updateTotalProgress();
        },
        getQueueCount: function () {
            var file, i, status, readyFile = 0, files = this.uploader.getFiles();
            for (i = 0; file = files[i++]; ) {
                status = file.getStatus();
                if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;
            }
            return readyFile;
        },
        destroy: function () {
            this.$wrap.remove();
        },
        getInsertList: function () {
            var i, data, list = [],
                prefix = editor.getOpt('audioUrlPrefix');
            for (i = 0; i < this.audioList.length; i++) {
                data = this.audioList[i];
                if(data == undefined){
                    continue;
                }
                //修改END
                list.push({
                    src: prefix + data.key,
                    key: + new Date()   // 以時(shí)間戳作為音頻控件父div的id
                });
            }
            return list;
        }
    };

})();

(4)修改ueditor.css文件也切,增加插入音頻按鈕圖標(biāo)及頁(yè)面窗口的相關(guān)樣式:

.edui-default  .edui-for-insertaudio .edui-icon {
    background-position: -320px -20px;
}

/*audio-dialog*/
.edui-default .edui-for-insertaudio .edui-dialog-content {
    width: 590px;
    height: 390px;
}

/* audio*/
.edui-default  .edui-for-insertaudio .edui-icon {
    background-image: url(../images/audio.png) !important;
}

音頻按鈕圖標(biāo)放在ueditor目錄的themes/default/images下扑媚。

(5)修改zh-cn.js文件,增加insertaudio的相關(guān)配置(類同insertvideo):

'insertaudio' : {
        'static' : {
            'lang_tab_remote' : "插入音頻",
            'lang_tab_upload' : "上傳音頻",
        },
        'uploadSelectFile' : '點(diǎn)擊選擇音頻文件',
        'uploadAddFile' : '繼續(xù)添加',
        'uploadStart' : '開(kāi)始上傳',
        'uploadPause' : '暫停上傳',
        'uploadContinue' : '繼續(xù)上傳',
        'uploadCancel' : '取消上傳',
        'uploadRetry' : '重試上傳',
        'uploadDelete' : '刪除',
        'uploadTurnLeft' : '向左旋轉(zhuǎn)',
        'uploadTurnRight' : '向右旋轉(zhuǎn)',
        'uploadPreview' : '預(yù)覽中',
        'uploadNoPreview' : '不能預(yù)覽',
        'updateStatusReady' : '選中_個(gè)音頻文件贾费,共_KB钦购。',
        'updateStatusConfirm' : '已成功上傳_個(gè)音頻文件檐盟,_個(gè)音頻文件上傳失敗',
        'updateStatusFinish' : '共_個(gè)(_KB)褂萧,_個(gè)上傳成功',
        'updateStatusError' : ',_個(gè)音頻文件上傳失敗葵萎。',
        'errorNotSupport' : 'WebUploader 不支持您的瀏覽器导犹!如果你使用的是IE瀏覽器,請(qǐng)嘗試升級(jí) flash 播放器羡忘。',
        'errorLoadConfig' : '后端配置項(xiàng)沒(méi)有正常加載谎痢,上傳插件不能正常使用!',
        'errorExceedSize' : '文件大小超出',
        'errorFileType' : '文件格式不允許',
        'errorInterrupt' : '文件傳輸中斷',
        'errorUploadRetry' : '上傳失敗卷雕,請(qǐng)重試',
        'errorHttp' : 'http請(qǐng)求錯(cuò)誤',
        'errorServerUpload' : '服務(wù)器返回出錯(cuò)',
        'remoteLockError' : "寬高不正確,不能所定比例",
        'numError' : "請(qǐng)輸入正確的長(zhǎng)度或者寬度值节猿!例如:123,400",
        'audioUrlError' : "不允許的音頻格式或者圖片域漫雕!",
        'audioLoadError' : "音頻加載失敱踔觥!請(qǐng)檢查鏈接地址或網(wǎng)絡(luò)狀態(tài)浸间!",
        'searchRemind' : "請(qǐng)輸入搜索關(guān)鍵詞",
        'searchLoading' : "音頻加載中太雨,請(qǐng)稍后……",
        'searchRetry' : " :( ,抱歉魁蒜,沒(méi)有找到音頻囊扳!請(qǐng)重試一次吩翻!"
    },

(6)修改config.json文件,增加音頻上傳的相關(guān)配置

/* 上傳音頻配置項(xiàng) */
"audioActionName": "uploadaudio", /* 執(zhí)行上傳音頻的action名稱 */
"audioFieldName": "file", /* 提交的音頻表單名稱 */
"audioMaxSize": 30720000, /* 上傳大小限制锥咸,單位B */
"audioAllowFiles": [".mp3", ".wma", ".wav", ".amr"], /* 上傳音頻格式限制 */
"audioUrlPrefix": "http://audio.ushallnotpass.com/" /* 音頻訪問(wèn)路徑前綴 */

4. 修改ueditor.all.js文件狭瞎,增加audio插件

<audio>標(biāo)簽自帶樣式不太好看,此處audio插件對(duì)插入的音頻控件樣式進(jìn)行了改造搏予,并實(shí)現(xiàn)了相關(guān)的播放控制事件脚作。感謝Dandelion_drq
分享的解決方案:H5 <audio> 音頻標(biāo)簽自定義樣式修改以及添加播放控制事件

audio插件相關(guān)代碼:

   /**
     * audio插件,為UEditor提供音頻插入支持 
     */
    UE.plugins['audio'] = function (){
        var me = this;
        // 從publis.js中獲取的靜態(tài)文件路徑缔刹,需自行修改設(shè)置
        var playicon = staticpath + 'audio/play.png';
        var pauseicon = staticpath + 'audio/pause.png';
        
        // 內(nèi)容填入后初始化音頻控件
        me.addListener("afterSetContent", function() {
            var audioArr = me.document.getElementsByTagName('audio');
            if(audioArr) {
                $.each(audioArr, function(i, a) {
                    var aDiv = domUtils.findParent(a, function(node) {
                        return node.className === 'audio-wrapper';
                    });
                    if(aDiv) {
                        initAudioEvent(aDiv);
                    }
                });
            }
        });
        
        /**
         * 插入音頻
         * @command insertaudio
         * @method execCommand
         * @param { String } cmd 命令字符串
         * @param { Object } audioObjs 鍵值對(duì)對(duì)象球涛, 描述一個(gè)音頻的所有屬性
         * @example
         * ```javascript
         *
         * var audioObjs = {
         *      // 音頻地址
         *      src: 'http://www.xxx.com/yyy',
         *      // 音頻標(biāo)題
         *      title: 'this is a title'
         * };
         *
         * //editor 是編輯器實(shí)例
         * //向編輯器插入單個(gè)音頻
         * editor.execCommand( 'insertaudio', audioObjs );
         * ```
         */
        UE.commands["insertaudio"] = {
            execCommand: function (cmd, audioObjs) {
                audioObjs = utils.isArray(audioObjs) ? audioObjs : [audioObjs];
                if (!audioObjs) {
                    return false;
                }
                var html = [];
                for (var i = 0; i < audioObjs.length; i++) {
                    var src = createAudioHtml(audioObjs[i].key, audioObjs[i].src, audioObjs[i].title);
                    html.push(src);
                }
                me.execCommand("inserthtml", html.join(""));
                // 初始化音頻控件
                initAudio(audioObjs);
                me.focus();
            }
        };
        
        /**
         * 構(gòu)造音頻控件html
         * 
         * @param {string} audioDivId - 音頻控件父div的id
         * @param {string} audioSrc - 音頻控件地址
         * @param {string} audioTitle - 音頻標(biāo)題
         */
        function createAudioHtml(audioDivId, audioSrc, audioTitle) {
            var src = '<div class="audio-wrapper" id="' + audioDivId + '" style="background-color: #fcfcfc;margin: 10px auto;max-width: 670px;height: 90px;border: 1px solid #e0e0e0;">' 
                +'<audio><source src="' + audioSrc + '"></audio>' 
                +'<div class="audio-left" style="float: left;text-align: center;width: 22%;height: 100%;">'  
                +'<img src="' + playicon + '" class="playicon" style="width: 40px;position: relative;top: 25px;margin: 0;display: initial;cursor: pointer;"/>'  
                +'<img src="' + pauseicon + '" class="pauseicon" style="width: 40px;position: relative;top: 25px;margin: 0;display: none;cursor: pointer;"/>'  
                +'</div>'  
                +'<div class="audio-right" style="margin-right: 5%;float: right;width: 73%;height: 100%;">'
                +'<p class="audio-title" style="max-width: 536px;font-size: 15px;height: 35%;margin: 8px 0;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">' + audioTitle + '</p>' 
                +'<div class="progress-bar-bg" style="background-color: #d9d9d9;position: relative;height: 2px;cursor: pointer;">'  
                +'<span class="progressDot" style="content: \' \';width: 12px;height: 12px;border-radius: 50%;-moz-border-radius: 50%;-webkit-border-radius: 50%;background-color: #3e87e8;position: absolute;left: 0;top: -5px;margin-left: 0px;cursor: pointer;"></span>' 
                +'<div class="progressBar" style="background-color: #649fec;width: 0;height: 2px;"></div>' 
                +'</div>' 
                +'<div class="audio-time" style="overflow: hidden;margin-top: -1px;">'  
                +'<span class="audioCurTime" style="float: left;margin-top:10px;font-size: 12px;color: #969696">00:00</span><span class="audioTotalTime" style="float: right;margin-top:10px;font-size: 12px;color: #969696">00:00</span>' 
                +'</div>'
                +'</div>' 
                +'</div><br/>';
            return src;
        }
        
        /**
         * 初始化音頻控件
         * 
         * @param {array} audioObjs - 音頻父div數(shù)組
         */
        function initAudio(audioObjs) {
            if(audioObjs) {
                for(var i = 0; i < audioObjs.length; i++) {
                    var audioDiv = me.document.getElementById(audioObjs[i].key);
                    initAudioEvent(audioDiv);
                }
            }
        }

        /**
         * 初始化音頻控制事件
         * 
         * @param {object} audioDiv - 音頻父div
         */
        function initAudioEvent(audioDiv) {
            // div子節(jié)點(diǎn)
            var divArr = domUtils.getElementsByTagName(audioDiv, 'div');
            // audio控件
            var audio = domUtils.getElementsByTagName(audioDiv, 'audio')[0];
            // 控制音頻文件名顯示寬度
            var audioRight = domUtils.filterNodeList(divArr, function(node) {
                return node.className === 'audio-right';
            });
            var title = domUtils.getElementsByTagName(audioRight, 'p')[0];
            domUtils.setStyle(title, 'max-width', domUtils.getComputedStyle(audioRight, 'width'));
            
            // 右側(cè)div組
            var rightDivArr = domUtils.getElementsByTagName(audioRight, 'div');
            // 進(jìn)度條div
            var progressDiv = domUtils.filterNodeList(rightDivArr, function(node) {
                return node.className === 'progress-bar-bg';
            });
            // 已播放進(jìn)度條
            var progressBar = domUtils.getElementsByTagName(progressDiv, 'div')[0]; 
            domUtils.setStyle(progressBar, 'width', 0); // 初始化為未播放狀態(tài)
            // 進(jìn)度條上控制點(diǎn)
            var progressDot = domUtils.getElementsByTagName(progressDiv, 'span')[0];
            domUtils.setStyle(progressDot, 'left', 0);  // 初始化為未播放狀態(tài)
            // 時(shí)間div
            var timeDiv = domUtils.filterNodeList(rightDivArr, function(node) {
                return node.className === 'audio-time';
            });
            // 時(shí)間span組
            var timeSpanArr = domUtils.getElementsByTagName(timeDiv, 'span');
            // 已播放時(shí)間
            var audioCurTime = domUtils.filterNodeList(timeSpanArr, function(node) {
                return node.className === 'audioCurTime';
            });
            audioCurTime.innerHTML = '00:00';   // 初始化為0
            // 總時(shí)間
            var audioTotalTime = domUtils.filterNodeList(timeSpanArr, function(node) {
                return node.className === 'audioTotalTime';
            });
            
            // 點(diǎn)擊播放/暫停圖片時(shí),控制音樂(lè)的播放與暫停
            var playerDiv = domUtils.filterNodeList(divArr, function(node) {
                return node.className === 'audio-left';
            });
            // 播放圖標(biāo)校镐、暫停圖標(biāo)
            var playerImgs = domUtils.getElementsByTagName(playerDiv, 'img');
            var playImg = domUtils.filterNodeList(playerImgs, function(node) {
                return node.className === 'playicon';
            });
            var pauseImg = domUtils.filterNodeList(playerImgs, function(node) {
                return node.className === 'pauseicon';
            });
            // 初始化播放圖標(biāo)
            domUtils.setStyle(playImg, 'display', 'initial');
            domUtils.setStyle(pauseImg, 'display', 'none');
            
            // 音頻準(zhǔn)備就緒后執(zhí)行
            audio.addEventListener('canplay', function() {
                audioTotalTime.innerHTML = transTime(audio.duration);   // 初始化為音頻總時(shí)長(zhǎng)
            });
            
            // 播放事件
            domUtils.on(playImg, 'click', function(e) {
                // 禁止事件冒泡
                if (e && e.stopPropagation) {
                    e.stopPropagation();
                } else {
                    window.event.cancelBubble = true;
                }
                
                // 監(jiān)聽(tīng)音頻播放時(shí)間并更新進(jìn)度條
                audio.addEventListener('timeupdate', function () {
                    updateProgress(audio, progressBar, progressDot, audioCurTime);
                }, false);

                // 監(jiān)聽(tīng)播放完成事件
                audio.addEventListener('ended', function () {
                    audioEnded(progressBar, progressDot, 
 audioCurTime, playImg, pauseImg)
                }, false);
                // 播放
                audio.play();
                // 切換播放暫停圖標(biāo)
                domUtils.setStyle(playImg, 'display', 'none');
                domUtils.setStyle(pauseImg, 'display', 'initial');
                // 暫停其他正在播放的音頻
                var audios = me.document.getElementsByTagName('audio');
                for (var i = 0; i < audios.length; i++) {
                    var parentDiv = domUtils.findParent(audios[i], function(node) {
                        return node.className === 'audio-wrapper';
                    });
                    if (parentDiv.id != audioDiv.id && !audios[i].paused) {
                        audios[i].pause();
                        var playerDiv = domUtils.getNextDomNode(audios[i]);
                        var players = domUtils.getElementsByTagName(playerDiv, 'img');
                        var play = domUtils.filterNodeList(players, function(node) {
                            return node.className === 'playicon';
                        });
                        var pause = domUtils.filterNodeList(players, function(node) {
                            return node.className === 'pauseicon';
                        });
                        domUtils.setStyle(play, 'display', 'initial');
                        domUtils.setStyle(pause, 'display', 'none');
                    }
                }
            });
            
            // 暫停事件
            domUtils.on(pauseImg, 'click', function(e) {
                // 禁止事件冒泡
                if (e && e.stopPropagation) {
                    e.stopPropagation();
                } else {
                    window.event.cancelBubble = true;
                }
                // 暫停
                audio.pause();
                // 切換播放暫停圖標(biāo)
                domUtils.setStyle(playImg, 'display', 'initial');
                domUtils.setStyle(pauseImg, 'display', 'none');
            });
            
            // 點(diǎn)擊進(jìn)度條跳到指定點(diǎn)播放
            domUtils.on(progressDiv, 'mousedown', function(e) {
                // 只有音樂(lè)開(kāi)始播放后才可以調(diào)節(jié)亿扁,已經(jīng)播放過(guò)但暫停了的也可以
                if (!audio.paused || audio.currentTime != 0) {
                    var pgsWidth = parseInt(domUtils.getComputedStyle(progressDiv, 'width'));
                    var rate = e.offsetX / pgsWidth;
                    audio.currentTime = audio.duration * rate;
                    updateProgress(audio, progressBar, progressDot, audioCurTime);
                }
            });

            // 鼠標(biāo)拖動(dòng)進(jìn)度點(diǎn)時(shí)可以調(diào)節(jié)進(jìn)度
            // 只有音樂(lè)開(kāi)始播放后才可以調(diào)節(jié),已經(jīng)播放過(guò)但暫停了的也可以
            // 鼠標(biāo)按下時(shí)
            domUtils.on(progressDot, 'mousedown', function(e) {
                if (!audio.paused || audio.currentTime != 0) {
                    var oriLeft = progressDot.offsetLeft;
                    var mouseX = e.clientX;
                    var maxLeft = oriLeft; // 向左最大可拖動(dòng)距離
                    var maxRight = progressDiv.offsetWidth - oriLeft; // 向右最大可拖動(dòng)距離

                    // 禁止默認(rèn)的選中事件(避免鼠標(biāo)拖拽進(jìn)度點(diǎn)的時(shí)候選中文字)
                    if (e.preventDefault) {
                        e.preventDefault();
                    } else {
                        e.returnValue = false;
                    }
                    // 禁止事件冒泡
                    if (e && e.stopPropagation) {
                        e.stopPropagation();
                    } else {
                        window.event.cancelBubble = true;
                    }
                    // 開(kāi)始拖動(dòng)
                    me.document.onmousemove = function (e) {
                        var length = e.clientX - mouseX;
                        if (length > maxRight) {
                            length = maxRight;
                        } else if (length < -maxLeft) {
                            length = -maxLeft;
                        }
                        var pgsWidth = parseInt(domUtils.getComputedStyle(progressDiv, 'width'));
                        var rate = (oriLeft + length) / pgsWidth;
                        audio.currentTime = audio.duration * rate;
                        updateProgress(audio, progressBar, progressDot, audioCurTime);
                    };
                    // 拖動(dòng)結(jié)束
                    me.document.onmouseup = function () {
                        me.document.onmousemove = null;
                        me.document.onmouseup = null;
                    };
                }
            });
        }

        /**
         * 更新進(jìn)度條與當(dāng)前播放時(shí)間
         * 
         * @param {object} audio - audio對(duì)象
         * @param {object} progressBar - 進(jìn)度條對(duì)象
         * @param {object} progressDot - 進(jìn)度條控制點(diǎn)對(duì)象
         * @param {object} audioCurTime - 當(dāng)前播放時(shí)間對(duì)象
         */
        function updateProgress(audio, progressBar, progressDot, audioCurTime) {
            var value = audio.currentTime / audio.duration;
            domUtils.setStyle(progressBar, 'width', value * 100 + '%');
            domUtils.setStyle(progressDot, 'left', value * 100 + '%');
            audioCurTime.innerHTML = transTime(audio.currentTime);
        }

        /**
         * 播放完成時(shí)把進(jìn)度調(diào)回開(kāi)始的位置
         * 
         * @param {object} progressBar - 進(jìn)度條對(duì)象
         * @param {object} progressDot - 進(jìn)度條控制點(diǎn)對(duì)象
         * @param {object} audioCurTime - 當(dāng)前播放時(shí)間對(duì)象
             * @param {object} playImg- 播放按鈕圖標(biāo)
             * @param {object} pauseImg- 暫停按鈕圖標(biāo)
         */
        function audioEnded(progressBar, progressDot, audioCurTime, playImg, pauseImg) {
            domUtils.setStyle(progressBar, 'width', 0);
            domUtils.setStyle(progressDot, 'left', 0);
            domUtils.setStyle(playImg, 'display', 'initial');
            domUtils.setStyle(pauseImg, 'display', 'none');
            audioCurTime.innerHTML = '00:00';
        }

        /**
         * 音頻播放時(shí)間換算
         * 
         * @param {number} value - 音頻當(dāng)前播放時(shí)間鸟廓,單位秒
         */
        function transTime(value) {
            var time = "";
            var h = parseInt(value / 3600);
            value %= 3600;
            var m = parseInt(value / 60);
            var s = parseInt(value % 60);
            if (h > 0) {
                time = formatTime(h + ":" + m + ":" + s);
            } else {
                time = formatTime(m + ":" + s);
            }

            return time;
        }

        /**
         * 格式化時(shí)間顯示从祝,補(bǔ)零對(duì)齊
         * 
         * eg:2:4  -->  02:04
         * @param {string} value - 形如 h:m:s 的字符串 
         */
        function formatTime(value) {
            var time = "";
            var s = value.split(':');
            var i = 0;
            for (; i < s.length - 1; i++) {
                time += s[i].length == 1 ? ("0" + s[i]) : s[i];
                time += ":";
            }
            time += s[i].length == 1 ? ("0" + s[i]) : s[i];

            return time;
        }
    };

5. 解決ueditor復(fù)制粘貼沖突

上述幾個(gè)步驟完成后,插入音頻功能就已實(shí)現(xiàn)了引谜。但在編輯器中插入音頻后再粘貼其他內(nèi)容時(shí)牍陌,會(huì)發(fā)現(xiàn)音頻控件的進(jìn)度條樣式出了問(wèn)題。這是因?yàn)閡editor在粘貼后會(huì)對(duì)空內(nèi)容div進(jìn)行刪除员咽,而audio插件所插入的音頻控件中有空內(nèi)容div毒涧。可修改相應(yīng)方法將音頻控件中的div排除贝室。

在ueditor.all.js中找到filter(div)方法契讲,再找到以下這段代碼:

if (browser.webkit) {
    var br = root.lastChild();
    if (br && br.type == 'element' && br.tagName == 'br') {
        root.removeChild(br)
    }
    utils.each(me.body.querySelectorAll('div'), function (node) {
        if (domUtils.isEmptyBlock(node)) {
            domUtils.remove(node,true)
        }
    })
 }

對(duì)刪除div的判定條件進(jìn)行修改,排除音頻控件的相關(guān)div:

if (domUtils.isEmptyBlock(node) && node.className != 'progress-bar-bg' && node.className != 'progressBar') {
    domUtils.remove(node,true)
 }

6. 文章頁(yè)面音頻控件初始化

在ueditor中編輯完文章內(nèi)容之后滑频,在展示文章時(shí)也需要對(duì)文章內(nèi)容中的音頻控件進(jìn)行初始化捡偏,故需要提供相應(yīng)的初始化方法供頁(yè)面調(diào)用。實(shí)現(xiàn)方式比較簡(jiǎn)單峡迷,和audio插件中的方法基本一致银伟,不詳述。

另若需支持移動(dòng)端的訪問(wèn)绘搞,則需要對(duì)點(diǎn)擊彤避、滑動(dòng)等事件進(jìn)行區(qū)分處理,對(duì)PC端訪問(wèn)要響應(yīng)mouse事件看杭,對(duì)移動(dòng)端訪問(wèn)要響應(yīng)touch事件:

// 定義不同端事件調(diào)用
var hasTouch = 'ontouchstart' in window,
startEvent = hasTouch ? 'touchstart' : 'mousedown',
moveEvent = hasTouch ? 'touchmove' : 'mousemove',
endEvent = hasTouch ? 'touchend' : 'mouseup',
cancelEvent = hasTouch ? 'touchcancel' : 'mouseup';

7. 效果圖

(1)Ueditor工具欄上音頻圖標(biāo)

1.png

(2)上傳音頻

2.png

(3)Ueditor內(nèi)傳入音頻后效果

3.png

(4)移動(dòng)端文章內(nèi)音頻效果

4.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忠藤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子楼雹,更是在濱河造成了極大的恐慌模孩,老刑警劉巖尖阔,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異榨咐,居然都是意外死亡介却,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門块茁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)齿坷,“玉大人,你說(shuō)我怎么就攤上這事数焊∮捞剩” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵佩耳,是天一觀的道長(zhǎng)遂蛀。 經(jīng)常有香客問(wèn)我,道長(zhǎng)干厚,這世上最難降的妖魔是什么李滴? 我笑而不...
    開(kāi)封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蛮瞄,結(jié)果婚禮上所坯,老公的妹妹穿的比我還像新娘。我一直安慰自己挂捅,他們只是感情好芹助,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著籍凝,像睡著了一般周瞎。 火紅的嫁衣襯著肌膚如雪苗缩。 梳的紋絲不亂的頭發(fā)上饵蒂,一...
    開(kāi)封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音酱讶,去河邊找鬼退盯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泻肯,可吹牛的內(nèi)容都是我干的渊迁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼灶挟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼琉朽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起稚铣,我...
    開(kāi)封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤箱叁,失蹤者是張志新(化名)和其女友劉穎墅垮,沒(méi)想到半個(gè)月后从媚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體捂寿,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年怎顾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了螟够。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灾梦。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖妓笙,靈堂內(nèi)的尸體忽然破棺而出若河,到底是詐尸還是另有隱情,我是刑警寧澤寞宫,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布牡肉,位于F島的核電站,受9級(jí)特大地震影響淆九,放射性物質(zhì)發(fā)生泄漏统锤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一炭庙、第九天 我趴在偏房一處隱蔽的房頂上張望饲窿。 院中可真熱鬧,春花似錦焕蹄、人聲如沸逾雄。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸦泳。三九已至,卻和暖如春永品,著一層夾襖步出監(jiān)牢的瞬間做鹰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工鼎姐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钾麸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓炕桨,卻偏偏與公主長(zhǎng)得像饭尝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子献宫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)钥平、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評(píng)論 4 62
  • 第一天的培訓(xùn)結(jié)束了姊途,已經(jīng)很久沒(méi)試過(guò)這種學(xué)習(xí)模式了涉瘾,感覺(jué)又回到學(xué)校那會(huì)奈惑。雖然入行已有10年,但是掌握的知識(shí)點(diǎn)都很零碎...
    胡文濤_bc54閱讀 261評(píng)論 0 4
  • 君生我未生,我生君已老秽浇,君恨我生遲浮庐,我恨君生早。君生我未生柬焕,我生君已老审残,恨不生同時(shí),日日與君好斑举。不是我不愛(ài)你搅轿,也不...
    路小賴閱讀 580評(píng)論 0 0
  • 2018年水性鋁銀漿發(fā)展如何 2017年各地區(qū)轟轟烈烈的環(huán)保法制定、修改富玷,以及環(huán)保稅的增加璧坟,使得油性涂料企業(yè)人心惶...
    山東銀箭鋁銀漿閱讀 93評(píng)論 0 0
  • 奇怪的邏輯運(yùn)算 一般語(yǔ)言的邏輯運(yùn)算結(jié)果都是 true或者false但是js就很個(gè)性,我就是其中一個(gè)操作數(shù)的值赎懦。對(duì)于...
    雨墨心閱讀 196評(píng)論 1 2