所有的

使用vue+elementUI编写运营活动生成工具
561
2017-6-25

前端切图的对象,很大一部分是公司营销活动类和纯展示类的页面,这一类页面通常有以下特点:


1.页面:切片(cuts的添加和删除)、功能区域(map、area或absolute操作对象)、模块(include的固定代码)、组件(components的调用和配置);

2:组件: 一份代码 + 一份数据,可复用组装。

3.有规则的数据监控和上线配置;

4.实时预览及快速发布上线。



以下为采用vue+elementUI制作运营活动工具的实践。

源码地址: JZ_Activity


活动上线流程:


1.新建活动页面,配置信息(页面标题,生成URL,分享图标及描述)






2.上传切好的各个切片(暂存于临时目录,活动生成后自动删除)



3.进入编辑页后选择组件(如:链接,功能性按钮,表单等等,可以根据需求自行开发),框选组件位置和尺寸



4.生成代码 =>

1)一键上传到服务器(需配置好对应上传目录及权限)

2)下载代码,编写无法用编辑器实现的特定需求后上传(活动代码压缩包);


组件开发流程:


1.新建vue组件.

2.进入活动编辑页

3.添加 - 选择组件 , 框选组件位置和尺寸


实现编辑页功能:


    import Vue from 'vue'
export default {
  components: {},
  data () {
    return {
      defaults:{
        width:0,height:0,left:0,top:0,
        memorySize : true,//记住上一次尺寸
        currentCutName:''
      },
      pageUrl:null,//生成地址
      info:{},//活动信息
      fileList : [],//切片列表
      currentMoveArea : null,//补充未快速响应的移动事件
      currentChangeArea : null,//补充未快速响应的移动事件
      areaTypeList :[
        {
          name:'组件库',
          type:2,
          data:[
            { key : 'slider-zhucai' , icon : 'menu' , text : '主材轮播',width:300,height:200},
            { key : 'slider-fangan' , icon : 'menu' , text : '整装方案轮播',width:300,height:200},
            { key : 'slider-ybj' , icon : 'menu' , text : '样板间轮播',width:300,height:200}
          ]
        },
        {
          name:'热区元素',
          type:3,
          data:[
            { key : 'open-form' , icon : 'view' , text : '弹出表单',width:100,height:50},
            { key : 'open-link' , icon : 'view' , text : '超链接',width:200,height:50}
          ]
        },
        {
          name:'表单录入',
          type:3,
          data:[
            { key : 'input-name' , icon : 'edit' , text : '姓名',width:200,height:50},
            { key : 'input-telephone' , icon : 'edit' , text : '电话',width:200,height:50},
            { key : 'input-mianji' , icon : 'edit' , text : '面积',width:200,height:50},
            { key : 'input-text' , icon : 'edit' , text : '文本',width:200,height:50},
            { key : 'input-submit' , icon : 'check' , text : '提交',width:200,height:50}
          ]
        }
      ]
    }
  },
  filters: {
    imgUrl (name) {
      return Vue.globalOptions.root + '/static/server/custom/images/' + name;
    }
  }, 
  methods: {
    init (info,fileList){
      this.info = info
      this.fileList = fileList
    },
    add (item,type){
      var width,height,left,top;
      if(this.defaults.memorySize && this.defaults.left != 0 && item.name == this.defaults.currentCutName){
          width = this.defaults.width;
          height = this.defaults.height;
          left = this.defaults.left;
          this.defaults.top += this.defaults.height + 10;//跟上一个area加点间距
          top = this.defaults.top
      }else{
          width = this.defaults.width = type.width;
          height = this.defaults.height = type.height;
          left = this.defaults.left = 100;
          top = this.defaults.top = 20;
      }
      this.defaults.currentCutName = item.name;
      item.areaList.push({
        type:type,
        drag:{
          move_flag:false,
          change_flag:false,
          mousePosition:{x:0,y:0},
          areaPosition:{x:0,y:0},
          areaSize:{width:0,height:0}
        },
        left:left,
        top:top,
        width:width,
        height:height
      });
    },
    clear (item){
      item.areaList = [];
      this.defaults.width = 0;
      this.defaults.height = 0;
      this.defaults.left = 0;
      this.defaults.top = 0;
    },
    move_down (event,area){
      if(!area.drag.move_flag){
        this.currentMoveArea = area;
        area.drag.move_flag = true;
        area.drag.mousePosition = {x:event.clientX,y:event.clientY};
        area.drag.areaPosition = {x:area.left,y:area.top};
      }
    },
    move_move (event,area){
      if(area.drag.move_flag){
        let left = area.drag.areaPosition.x + event.clientX - area.drag.mousePosition.x;
        let top  = area.drag.areaPosition.y + event.clientY - area.drag.mousePosition.y;
        area.left = left > 0 ? left : 0;
        area.top = top > 0 ? top : 0;
        if(this.defaults.memorySize){
          this.defaults.left = left;
          this.defaults.top = top;
          this.defaults.width = area.width;
          this.defaults.height = area.height;
        }
      }
    },
    move_up (event,area){
      if(area.drag.move_flag){
        area.drag.move_flag = false;
      }
    },
    remove (event,area,item){
      removeObjWithArr(item.areaList,area);
      //设置最后一个area为默认尺寸
      if(item.areaList.length>0){
        let lastArea = item.areaList[item.areaList.length-1];
        this.defaults.width = lastArea.width;
        this.defaults.height = lastArea.height;
        this.defaults.left = lastArea.left;
        this.defaults.top = lastArea.top;
      }else{
        this.defaults.width = 0;
        this.defaults.height = 0;
        this.defaults.left = 0;
        this.defaults.top = 0;
      }
    },
    change_down (event,area){
      event.cancelBubble = true;
      if(!area.drag.change_flag){
        this.currentChangeArea = area;
        area.drag.change_flag = true;
        area.drag.mousePosition = {x:event.clientX,y:event.clientY};
        area.drag.areaSize = {width:area.width,height:area.height};
      }
    },
    change_move (event,area){
      if(area.drag.change_flag){
        var width = area.drag.areaSize.width + event.clientX - area.drag.mousePosition.x;
        var height = area.drag.areaSize.height + event.clientY - area.drag.mousePosition.y;
        area.width = width > 50 ? width : 50;
        area.height = height > 30 ? height : 30;
        if(this.defaults.memorySize){
          this.defaults.width = width;
          this.defaults.height = height;
          this.defaults.left = area.left;
          this.defaults.top = area.top;
        }
      }
    },
    change_up (event,area){
      if(area.drag.change_flag){
        this.currentMoveArea = null;
        area.drag.change_flag = false;
      }
    },
    cut_move (event){//补充未快速响应的移动事件
      if(this.currentMoveArea != null){
        this.move_move(event,this.currentMoveArea);
      }else if(this.currentChangeArea != null){
        this.change_move(event,this.currentChangeArea);//补充未快速响应的移动事件
      }
    },
    cut_up (event){//结束未快速响应的移动事件
      if(this.currentMoveArea != null){
        this.currentMoveArea.drag.move_flag = false;
        this.currentMoveArea = null;
      }else if(this.currentChangeArea != null){
        this.currentChangeArea.drag.change_flag = false;
        this.currentChangeArea = null;
      }
    },
    save (){
      var form = {
          info:this.info,
          cutList:this.fileList
        };
        var container = this.$refs.container;
        var width;
        if (window.getComputedStyle) {
            width = window.getComputedStyle(container, null).width;
        } else { 
            width = container.currentStyle.width;
        }
        form.ratio = (parseFloat(width)/1920).toFixed(2);
        this.$post('activity/build', form, { emulateJSON: false }).then(function(data){
          data = JSON.parse(data.bodyText);
          if(data.code == 0 ){
            this.$message({
              message: '已存在的页面地址(请确认活动路径和页面名称是否重复)!',
              type: 'warning'
            });
          }else{
            this.pageUrl = "//localhost:8999/preview/" + this.info.url + '/' + (this.info.type=="1"?"pc":"mobile");
          }
          //this.$emit('close');
          //this.$emit('reload');
        })
    }
  }
}


生成数据结构:




代码生成:


点击生成活动时:提交到本地nodejs server,实现以下步骤:

1.新建活动代码目录build;

2.匹配活动模板,拼接HTML字符串,生成至build目录

3.拷贝默认携带的js,images等资源至build目录

4.生成build压缩包或上线代码

5.清空暂存目录

    module.exports.build = function(req,res,next){
    var info = req.body.info;
    var cutList = req.body.cutList;
    var ratio = req.body.ratio;
    var type = info.type == "1"? "pc":"mobile";
    var formIndex = 1;
    var calculate = function(value){
        if(type=="pc"){
            return (value/ratio).toFixed(2) + 'px';
        }else{
            return (value/ratio/1920*100/10).toFixed(2) + 'rem';
        }
    };
          
    //check dir exist
    var output = Config.root + '/server/custom/output/' + info.url;
    if(!fsExtra.pathExistsSync(output)){
        fsExtra.ensureDirSync(output);
    }
    if(fsExtra.pathExistsSync(output + '/' + type )){
        res.json({success:true,code:0});
        return;
    }else{
        fsExtra.ensureDirSync(output);
    }
    //make html
    var html_content = '';
    for(let c = 0 ; c < cutList.length ; c++){
        let cut = cutList[c];
        let initForm = false;
        if(cut.areaList.length == 0)
            html_content += '<img src="<%= images(\'' + cut.name + '\') %>" alt="家装季" />\n';
        else{
            html_content += '<div class="activity-module">\n';
            html_content += '<img src="<%= images(\'' + cut.name + '\') %>"  alt="家装季" />\n';
            for(let a = 0 ; a < cut.areaList.length ; a++){
                let area = cut.areaList[a];
                let styles = 'top:' + calculate(area.top) + ';left:' + calculate(area.left) + ';width:' + calculate(area.width) + ';height:' + calculate(area.height) + ';';
                switch(area.type.key){
                    case 'open-form':
                        html_content += '<button class="activity-button" style="'+ styles +'"></button>\n';
                        break;
                    case 'input-name':
                        if(!initForm){
                            initForm = true;
                            html_content += '<div id="j_form' + formIndex++ + '" form-baoming="' + c + '" >\n';
                        }
                        html_content += '<input type="text" class="c1" data-placeholder="请输入您的姓名" style="'+ styles +'" />\n';
                        break;
                    case 'input-telephone':
                        if(!initForm){
                            initForm = true;
                            html_content += '<div id="j_form' + formIndex++ + '" form-baoming="" >\n';
                        }
                        html_content += '<input type="tel" class="c2" data-placeholder="请输入您的联系方式" maxlength="11" style="'+ styles +'" />\n';
                        break;
                    case 'input-mianji':
                        if(!initForm){
                            initForm = true;
                            html_content += '<div id="j_form' + formIndex++ + '" form-baoming="" >\n';
                        }
                        html_content += '<input type="tel" class="c3" data-placeholder="请输入您的房屋面积" maxlength="11" style="'+ styles +'" />\n';
                        html_content = html_content.replace(new RegExp('form-baoming=\"' + c + '\"'),'form-baoming="baojia"');
                        break;
                    case 'input-text':
                        if(!initForm){
                            initForm = true;
                            html_content += '<div id="j_form' + formIndex++ + '" form-baoming="" >\n';
                        }
                        html_content += '<input type="text" data-placeholder="请输入您的房屋面积" maxlength="11" style="'+ styles +'" />\n';
                        break;
                    case 'input-submit':
                        if(!initForm){
                            initForm = true;
                            html_content += '<div id="j_form' + formIndex++ + '" form-baoming="" >\n';
                        }
                        html_content += '<button class="btn_submit" style="'+ styles +'"></button>\n';
                        break;
          
                }
            }
            if(initForm) html_content += '</div>\n';
            html_content += '</div>\n';
                      
        }
    }
    //build html
    var html_template_url = Config.root + '/server/custom/template/' + type + '.html';
    var html_build_url = Config.root + '/server/custom/build/' + type + '/' + type + '.html';
    var html = fs.readFileSync(html_template_url,"utf-8");
    html = html.replace(/\[node-build-activity-title\]/g,info.title);
    html = html.replace(/\[node-build-activity-modules-placeholder\]/g,html_content);
    fs.writeFile(html_build_url,html,function(err){
        if (err) throw err;
                  
        //build js
          
        //build images
        var images_build_url = Config.root + '/server/custom/build/' + type + '/' + type + '/images';
        fsExtra.removeSync(images_build_url);
        fsExtra.copySync(imagesUploadDir,images_build_url);
          
        //copy default images
        var images_default_url = Config.root + '/server/custom/template/' + type + '/images';
        fsExtra.copySync(images_default_url,images_build_url);
                  
        //copy all to custom
        var all_build_url = Config.root + '/server/custom/build/';
        fsExtra.copySync(all_build_url + type , output);
                  
        //clear pool
        fsExtra.removeSync(imagesUploadDir);
        res.json({success:true,code:1});
    });
          
          
};


源码地址: JZ_Activity


全部评论
(1)
评论速度
mores++

还可以输入 2000 个字符
添加表情