使用了 el-upload 的插件,但是一直都是报 404 ,在这个过程中,请注意您使用的 request header 中的 content-type 不同的类型需要的 NodeJS 的 request 解析方式不一样。在 Node 服务端接到用户的数据之后,就需要使用 fs 模块把图片解析成可以保存的文件流,存入磁盘中或者调用后台接口保存。调试了一个小时之后成功通过。把代码留下来,方便日后进行查询。

Vue 层
-------------------------------------
<el-upload class="avatar-uploader" accept="image/jpeg" action="agency/uploadPicForAgency" :data="{agencyId:'1',picFlag:'1'}" :multiple="false" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeBackgroundAvatarUpload">
  <img v-if="backgroundUrl" :src="backgroundUrl" width="100%" height="100%" class="avatar">
  <i v-else="" class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>

express 层
-----------------------------------
/**
 * @api {post} agency/uploadPicForAgency 代理商图片上传接口,返回图片存入服务器的URL
 * @apiName agency/uploadPicForAgency
 * @apiGroup agency
 * 
 * @apiParam   {FileObject}   imgInfo    图片文件对象
 * @apiParam   {String}       agencyId   代理商ID
 * @apiParam   {String}       picFlag    图片标记(1-登录页logo图,2-登录页背景图,3-内容页logo图)
 * 
 * @apiParamExample  {type} Request-Example:
 * 传统的FORM表单提交
 * Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzJ4Ys0x4h70Z8heR
 * 
 * ------WebKitFormBoundaryzJ4Ys0x4h70Z8heR
    Content-Disposition: form-data; name="agencyId"

    000192
    ------WebKitFormBoundaryzJ4Ys0x4h70Z8heR
    Content-Disposition: form-data; name="picFlag"

    1
    ------WebKitFormBoundaryzJ4Ys0x4h70Z8heR
    Content-Disposition: form-data; name="file"; filename="Intel-logo.jpg"
    Content-Type: image/jpeg


    ------WebKitFormBoundaryzJ4Ys0x4h70Z8heR--
 * 
 * @apiSuccessExample {Object} Success-Response:
 * {
 *    message: ""
 *    success: true
 *    data: {
 *      imageStr: "/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAXA.............."
        uploadPath: "agency/2019/05/54e4c76df1db41bbbca80c9a7ef16c5f.jpg"
      }
 * }
 */
router.post('/uploadPicForAgency', function (req, res, next) {
  const methodName = 'MAgencyPlatformDataAPI.uploadPicForAgency';
  var form = new formidable.IncomingForm();

  form.encoding = 'utf-8'; //设置编辑
  form.keepExtensions = true; //保留后缀
  form.maxFieldsSize = 2 * 1024 * 1024; //设置单文件大小限制
  form.parse(req, function (err, fields, files) {
    const agencyId = fields.agencyId;
    const picFlag = fields.picFlag;
    var imageBuf = fs.readFileSync(files.file.path);
    var imagebase = imageBuf.toString("base64");
    var imgInfo = {
      imgName: files.file.name,
      imgStr: imagebase,
    }
    util.JSONRPC(methodName, [imgInfo,agencyId,picFlag], function (data) {
      res.json({
        success: true,
        data: {
          uploadPath: data,
          imageStr: imagebase
        },
        message: ''
      })
    }, res);
  })
});

React小书:http://huziketang.mangojuice.top/books/react/lesson1
React 开发者学习线路图(2018):https://www.css88.com/archives/10111
React中文文档:https://react.css88.com/
WebPack 4 教程,从零配置到生产发布:https://www.css88.com/archives/9436
React入门教程:https://react.docschina.org/tutorial/tutorial.html#%E8%AF%BE%E5%89%8D%E5%87%86%E5%A4%87
React Router 教程:https://react-guide.github.io/react-router-cn/docs/guides/basics/IndexRoutes.html

因为 Kaminari 使用的必须是 ActiveRecord 对象,一开始报了个错,半天搞不懂,回头自己重新查询了一下资料,其实官方的 Github 上就已经写好了。

把代码贴出来一下其实挺简单的:

def index
user_name
= params[:user_name]
tempSQL = "select o.*,u.user_name,u.nick_name,u.phone,f.title from
orders o, users u, flies f where o.user_id = u.id and o.goods_id = f.id"
if
!user_name.nil?
puts 'run here'
tempSQL += " and u.user_name = '#{user_name}'"
end
@orders
= Kaminari.paginate_array(Order.find_by_sql(tempSQL)).page(params[:page])
end

Kaminari.paginate_array 中已经就能够完成转换功能了,大家继续,玩得开心哦。

Electron的开发相对还是蛮简单的,看了一写简单的资料就能快速上手开发了。留下三篇相对全的资料,以备查询:
开发:https://juejin.im/post/5a6a91276fb9a01cbd58ce32
打包:https://juejin.im/entry/5805e39ad20309006854e58f
https://www.jianshu.com/p/1c2ad78df208
更新:https://segmentfault.com/a/1190000012904543

剛好在使用CSS中發現,Google Font的服務一直無法使用。之前國内有 360 的替代方案。後來數字公司的服務就下綫了。導致用戶訪問速度一直非常慢。在網上搜過資料原來很多提供靜態資源,CDN服務的公司都相繼下綫了自己的產品。經過谷歌大法,通過一個個的嘗試最後確定 fonts.loli.net 的業務仍然可以使用。

先不多说,上图:

JSDuck文档说明

初步使用,感觉还不错,因为其使用的运行环境是 Gem,所以需要安装 Ruby 相关的环境。安装可以参考链接:https://my.oschina.net/lsjcoder/blog/1583072

常用的命令表如下:

参数 含义
— 或 空 需要生成文档的文件或者目录,也可以是一个集合
–output 文档存放的目录
–config 配置文件路径
–encoding 文档编码方式
–exclude 不生成文档的目录或文件,可以是一个集合
–title 文档标题
–footer 文档标尾
–head-html 文档页面的head中需要添加的内容
–body-html 文档页面的body中需要添加的内容
–css 额外添加的样式​
–welcome 欢迎页面
–guides 向导配置
–examples 示例配置
–categories 分类配置
–images 图片路径配置
–tags 自定义标签
–examples-base-url 示例文件的基础路径
–help 查看命令帮助
–version 查看当前版本

随着 JavaScript 在越来越多的工程中扮演越来越重要的角色,文档的完善越来越重要,同时也开始约束 JSer 慢慢开始学习工程化,系统化的编写代码。开始自我改造吧。

测试效果如图:


当鼠标划过的时候,需要显示更多的信息,因为GridPanel表格显示区域不够,所以把部分信息移动到 Tip 中显示。代码如下:

store: {
  type: 'EduCartoonStore',
  autoLoad: false,
  listeners: {
      'load': function() {
          // 得到当前的Gird
          var grid = Ext.getCmp('eduCartoonListGrid');
          var view = grid.getView();
          // 创建一个 Tip
          var tip = Ext.create('Ext.tip.ToolTip', {
              target: view.el,
              delegate: view.itemSelector,
              trackMouse: true,
              renderTo: Ext.getBody(),
              listeners: {
                  // 显示Tip
                  beforeshow: function updateTipBody(tip) {
                      var record = view.getRecord(tip.triggerElement);
                      var state = '';
                      var createTime = '';
                      var packageTime = '-';
                      var showHtml = '';
                      // 状态
                      if (record.data.status === 0) {
                        state = "<span style='color:red'>待审核</span>";
                      } else if (record.data.status === 1) {
                        state = "<span style='color:green'>已审核</span>";
                      } else if (record.data.status === -1) {
                        state = "<span style='color:grey'>已关闭</span>";
                      }
                      // 创建时间
                      createTime = formatDate(record.data.ctime);
                      // 打包时间
                      if (record.data.zipGenerateTime !== undefined) {
                        packageTime = formatDate(record.data.zipGenerateTime)
                      }
                      showHtml += '<p><b>ID编号</b>:' + record.data.id + '</p>';
                      showHtml += '<p><b>状态</b>:' + state + '</p>';
                      showHtml += '<p><b>创建时间</b>:' + createTime + '</p>';
                      showHtml += '<p><b>打包时间</b>:' + packageTime + '</p>';
                      tip.update(showHtml);
                  }
              }
          });
      }
  }
}

场景是这样的,用户编辑完文本之后,上传了一张图片。而这个时候并没有让编辑器重新获得焦点。就会导致提交的图片为:themes/default/images/spacer.gif 。原因是上传完成图片之后,数据正确返回但是并没有动态绑定到图片的 src 属性中。这个时候需要手动设置一次富文本编辑框的值,触发 change 事件。修改代码如下:

ueditor.all.js  24518行

function callback(){
try{
	var link, json, loader,
	body = (iframe.contentDocument || iframe.contentWindow.document).body,
	result = body.innerText || body.textContent || '';
	json = (new Function("return " + result))();
	link = me.options.imageUrlPrefix + json.url;
	if(json.state == 'SUCCESS' && json.url) {
		loader = me.document.getElementById(loadingId);
		loader.setAttribute('src', link);
		loader.setAttribute('_src', link);
		loader.setAttribute('title', json.title || '');
		loader.setAttribute('alt', json.original || '');
		loader.removeAttribute('id');
		domUtils.removeClasses(loader, 'loadingclass');
		me.execCommand('inserthtml', '');   // 就是这一行,记住了!!!
	} else {
		showErrorLoader && showErrorLoader(json.state);
	}
}catch(er){
	showErrorLoader && showErrorLoader(me.getLang('simpleupload.loadError'));
}
form.reset();
domUtils.un(iframe, 'load', callback);
}

修改源码后就可以解决这个问题啦。

因业务需要,只能动手来研究一个拖拽模块。研究之前觉得很简单,在我印象里好想类似的 Vue,jQuery 的插件非常多。应该是分分钟就可以搞定。结果花了很长的时间来研究这个玩意。现在把思路记下来方便下次查阅。

  • 创建拖拽对象
  • 确定“接受拖拽对象的”区域
  • 新建一个类,用于实现 ExtJS 中关于 “拖拽” 方法的实现
  • 使用新编写带有实现的类应用到拖拽对象中
  • 编写CSS来提升用户体验
  • 拖拽结束之后的节点复制和移动

基本上就是上述的几个步骤,为了方便查阅我把所有的脚本都构建在一个 JS 文件中,若真用到项目中,请严格按照 Sencha ExtJS 的 MVC 标准做代码的分离。代码如下:

/*
 * author:      liucan
 * email:       33370733@qq.com
 * description: 系统顶部模块
 */

// 在这里需要重写拖拽的方法,基础的 API 并没有基于业务的实现。
// 拖拽完成,必须要判定是否拖放在指定的区域中,并且长宽要一致
// 对其当前的容器元素,TOP,LEFT 必须为 0
var overrides = {
    startDrag: function () {
        this.originalXY = this.el.getXY();
    },
    onDrag: function () {
        this.el.addCls('item-draging');
    },
    onDragEnter: function (e, id) {
        // console.log('onDragEnter');
        if(id != this.el.dom.parentNode.id){
            this.el.addCls('item-drag-ok');
        }else{
            this.onDragOut();
        }
    },
    onDragOver: function (e, id) {
    },
    onDragOut: function (e, id) {
        this.el.removeCls('item-drag-ok');
    },
    onDragDrop: function (e, id) {
    },
    onInvalidDrop: function () {
        // 无效拖动,需要使用动画还原。
        this.invalidDrop = true;
        this.el.removeCls('item-draging');    
    },
    endDrag: function (e, id) {
        this.el.removeCls('item-draging');
        if(this.invalidDrop === true){
            // 无效的移动
            var animCfgObj = {
                easing: 'elasticOut',
                duration: 1,
                scope: this,
                callback: function(){
                    this.el.dom.style.postion = '';
                }
            }
            this.el.setXY(this.originalXY);
            delete this.invalidDrop;
        }else{
            // 判定 TARGET 的值,并且做好磁力贴效果
            // 复制节点到指定元素,删除原有节点
            var newNode = this.el;
            if(e.item){
                e.item.firstChild.append(newNode.dom)
                newNode.dom.style = "";
            }
        }
    }
};

var pinyinTpl = new Ext.XTemplate(
    '<div id="pinyin_panel" class="pinyin-panel">',
        '<tpl for=".">',
            '<div class="pinyin-item">{.}<div class="reset-this">x</div></div>',
        '</tpl>',
    '</div>'
);
var fontTpl = new Ext.XTemplate(
    '<div class="font-panel">',
        '<tpl for=".">',
            '<div class="thumb-wrap">',
                '<div class="thumb-pinyin"></div>',
                '<div class="thumb-font">{.}</div>',
            '</div>',
        '</tpl>',
        '<div style="clear: both"></div>',
    '</div>'
);

Ext.define('xjcms.view.util.Pinyin', {
    extend: 'Ext.window.Window',
    alias: 'widget.Pinyin',
    title: '生成拼音',
    requires: [
        'xjcms.view.util.pinyin.PinyinController',
        'xjcms.view.util.pinyin.PinyinModel'
    ],
    controller: {
        type: 'pinyin'
    },
    viewModel: 'pinyin',
    width: 600,
    minHeight: 320,
    layout: 'fit',
    scrollable: false,
    // 遮罩层
    modal: true,
    constrain: true,
    closable: true,
    autoDestroy: true,
    draggable: true,
    resizable: false,
    items: [{
        xtype: 'form',
        bodyPadding: '10 10 10 10',
        items: [{
            xtype: 'container',
            layout: 'hbox',
            items: [{
                xtype: 'textfield',
                fieldLabel: '文字',
                labelWidth: 60,
                width: 500,
                emptyText: '请输入文字',
                regex: /^([\u4e00-\u9fa5]+)/,
                regexText: '该输入框仅仅只支持输入汉字',
                value: ''
            }, {
                xtype: 'button',
                text: '生成拼音',
                handler: function () {
                    // word-lib/convert2Pinyin
                    var text = this.prev().getValue();
                    var vm = this.up('window').getViewModel();
                    var vmStores = vm.storeInfo;
                    var fontData = [];
                    text = Ext.util.Format.trim(text);
                    // 请求数据
                    Ext.Ajax.request({
                        url: 'https://easy-mock.com/mock/5ae02d829c4a407f53115d81/rms/py',
                        method: 'GET',
                        params: {
                            font: text
                        },
                        success: function (response, opts) {
                            var obj = Ext.decode(response.responseText);
                            // 中文字和拼音字母
                            var font = obj.data.font;
                            var py = obj.data.py;
                            vmStores['fontStore'] = Ext.create('Ext.data.Store', {
                                data: font
                            });
                            vmStores['pinyinStore'] = Ext.create('Ext.data.Store', {
                                data: py
                            });
                            vm.setStores(vmStores);
                        },
                        failure: function (response, opts) {
                            console.log('server-side failure with status code ' + response.status);
                        }
                    });
                }
            }]
        }, {
            xtype: 'dataview',
            tpl: pinyinTpl,
            itemSelector: 'div.pinyin-item',
            bind: {store: '{pinyinStore}'},
            listeners: {
                'refresh': function () {
                    var pinyins = Ext.select('.pinyin-item');
                    Ext.each(pinyins.elements, function (el) {
                        // 第一步,创建可拖动的元素
                        var dd = Ext.create('Ext.dd.DD', el, 'pyDDGroup', {
                            isTarget: false
                        });
                        Ext.apply(dd, overrides);
                    })
                }
            }
        }, {
            // 拼音的拖拽区域
            xtype: 'dataview',
            tpl: fontTpl,
            bind: {store: '{fontStore}'},
            itemSelector: 'div.thumb-wrap',
            emptyText: '<div class="font-none-data">暂时还没有任何文字哦</div>',
            listeners: {
                'refresh': function () {
                    var thumbWrap = Ext.select('.thumb-wrap');
                    // 第二步,创建可以用于接收的元素
                    Ext.each(thumbWrap.elements, function (el) {
                        var thumbWrapDDTarget = Ext.create('Ext.dd.DDTarget', el, 'pyDDGroup', {
                            ignoreSelf: false
                        });
                    })
                },
                'itemclick': function(thisViewObj, record, item, index, e, eOpts){
                    var clsName = e.target.className;
                    if(clsName === 'reset-this'){
                        // 移除这个元素
                        var pNode = e.target.parentNode;
                        // console.dir(pNode);
                        Ext.get('pinyin_panel').appendChild(pNode);
                    }
                }
            }
        }],
        buttons: [{
            xtype: 'button',
            text: '确定'
        }, {
            xtype: 'button',
            text: '取消',
            handler: function () {
                this.up('window').destroy();
            }
        }]
    }]
});