美文网首页
利用Ztree搭建后台树形结构

利用Ztree搭建后台树形结构

作者: 程序萌 | 来源:发表于2017-12-21 10:50 被阅读0次

gemfile中添加:

# 树插件
gem 'ztree'
gem 'ancestry'
gem 'nokogiri', '~> 1.6', '>= 1.6.8'

然后bundle install

用脚手架建立一个品目的类(这个是带命名空间的)

rails g scaffold ancient/Category 

db文件夹中的迁移文件中:

  def change
    create_table :categories do |t|
      t.string :ancestry      #必写
      t.string :name, :comment => "品目名称"
      t.integer  :ancestry_depth  #必写
      t.integer  :order_num   #必写

      t.timestamps
    end
  end

然后执行迁移 rake db:migrate

添加配置文件

在/app/assets/javascripts/admin_content.js中添加:

//= require ztree
//= require sortable_tree

在/app/assets/javascripts文件夹中创建一个sortable_tree.js文件,并加入下面代码:

var contentSelector = '.menu_content',
    className = "dark"
    // newCount = 1,

function getzTree() {
  return $.fn.zTree.getZTreeObj(treeOpts.current_tree);
}

function getChildren(ids,curTreeNode){
  var treeNode = curTreeNode.getParentNode()
  if (treeNode && treeNode.isParent){
    ids.push(treeNode.id);
    for(var obj in treeNode.children){
      // getChildren(ids,treeNode.children[obj]); //will get all nodes of the treeNode
      ids.push(treeNode.children[obj].id); // just get nodes for the current node
    }
  } else {
    ids.push(0);
    topTree = getzTree();
    for(var obj in topTree.getNodes()){
      ids.push(topTree.getNodes()[obj].id);
    }
  }
  return ids;
}

function beforeDrag(treeId, treeNodes) {
  for (var i=0,l=treeNodes.length; i<l; i++) {
    dragId = treeNodes[i].pId;
    if (treeNodes[i].drag === false) {
      return false;
    }
  }
  return true;
}

function beforeDrop(treeId, treeNodes, targetNode, moveType) {
  return targetNode.pId === dragId
}

function onDrop(event, treeId, treeNodes, targetNode, moveType) {
  var sortableIds = getChildren([],treeNodes[0])
  $.ajax({
    url: '/sort_tree',
    type: 'post',
    data: {sorted_array: sortableIds, model_name: treeOpts.model_name}
    // success: function (r){alert('OK')}
  })
};

function beforeEditName(treeId, treeNode) {
  className = (className === "dark" ? "":"dark");
  getzTree().selectNode(treeNode);
  $(contentSelector).load(treeOpts.url + treeNode.id +"/edit");
  return false;
}

function beforeRemove(treeId, treeNode) {
  className = (className === "dark" ? "":"dark");
  getzTree().selectNode(treeNode);
  if(confirm("确认删除 " + treeNode.name + " 吗,下级会一并被删除?")){
    $.ajax(treeOpts.url + treeNode.id +".json" ,{
      type:'post',
      data:{_method:"delete"},
      success:function(){
        $('.success').show();
        $('.success').fadeOut(2000);
      }, 
      error:function(){
        $('.failed').show();
        $('.failed').show(2000);
        return false;
      }
    })  
  }else{
    return false;
  }
}

function onRemove(e, treeId, treeNode) {
  
}

function beforeRename(treeId, treeNode, newName, isCancel) {
  className = (className === "dark" ? "":"dark");
  if (newName.length == 0) {
    alert("节点名称不能为空.");

    setTimeout(function(){getzTree().editName(treeNode)}, 10);
    return false;
  }
  return true;
}

function onRename(e, treeId, treeNode, isCancel) {
  return false;
}

function onClick(e, treeId, treeNode, isCancel) {
  $(contentSelector).load(treeOpts.url + treeNode.id +"/edit");
}

function addHoverDom(treeId, treeNode) {
  var sObj = $("#" + treeNode.tId + "_span");
  if (treeNode.editNameFlag || $("#addBtn_"+treeNode.tId).length>0) return;
  var addStr = "<span class='button add' id='addBtn_" + treeNode.tId
    + "' title='add node' onfocus='this.blur();'></span>";
  sObj.after(addStr);
  var btn = $("#addBtn_"+treeNode.tId);
  if (btn) btn.bind("click", function(){
    $(contentSelector).load(treeOpts.url + "new?parent_id="+ treeNode.id);
    return false;
  });
};

function removeHoverDom(treeId, treeNode) {
  $("#addBtn_"+treeNode.tId).unbind().remove();
};
  

在/app/helpers/tree_helper.rb文件夹中创建tree_helper.rb文件,并加入如下代码:

# -*- encoding : utf-8 -*-
module TreeHelper

  def init_tree_js(link_title = '')
    js = '$(document).ready(function(){
            $.fn.zTree.init($("#" + treeOpts.current_tree), ztreeSetting, treeOpts.zNodes);
          '
    js += '$(".new_btn").click(function (){
             $(".menu_content").load(treeOpts.url + "new");
               return false;
             })' unless link_title.empty?
    js += '});'
    javascript_tag(js)
  end

  def tree_content(link_title = '', opts = {})
    if link_title.empty?
      header = ''.html_safe
    else
      title_link = link_to link_title, opts[:url], :class => "btn btn-small btn-primary new_btn"
      header = content_tag :div, title_link + tip_messages, class: 'page-header'
    end
    right_content = content_tag :div, '', class: "menu_content fl"
    list_content = content_tag :div, tree_list(link_title, style: '') + right_content

    content_tag(:section, header + list_content, id: 'tables')
  end

  def tip_messages
    content_tag(:span, "操作成功", class: 'success hide') +
    content_tag(:span, "操作失败,原因:", class: 'failed hide')
  end

end

在/app/models/category.rb(这个是你创建的类的model)加入如下代码:

  has_ancestry
  validates :name, presence: true
  after_create :update_order_number

在/app/helpers/application_helper.rb中加入无限下拉框,代码如下:

  #  无限级联下拉框 搭配js
  def dynamic_selects(data_class, value_id, aim_id, text_id = '', prompt = '请选择', options = {})
    options = {:class => 'multi-level', :otype => data_class.to_s, :aim_id => aim_id, :text_id => text_id }.merge!(options)
    value_object = data_class.find_by_id(value_id)
    select_text = value_object.try(:has_children?) ? collection_select('value_object-parent-id', 0, value_object.children, :id, :name, {:selected => value_object.try(:id), :prompt => prompt}, options) : ''
    #aim_id = object.class.to_s.tableize.singularize + "_" + ref.to_s.singularize + "_id"
    aim_id = aim_id.to_s
    while value_object and value_object.parent
      select_text = collection_select('value_object-parent-id', value_object.id, value_object.parent.children, :id, :name, {:selected => value_object.try(:id), :prompt => prompt}, options) << select_text
      value_object = value_object.parent
    end
    select_text = collection_select('value_object-parent-id', 0, data_class.roots, :id, :name, {:selected => value_object.try(:id), :prompt => prompt}, options) << select_text

    raw select_text
  end

在/app/controllers/ancient/categories_controller.rb中加入:

 before_action :per_load, except: [:index, :new, :create, :sort]
  before_action :refuse_same_name, only: [:create_params]

  def index
    @roots = Category.all.to_sorted_nodes
  end

  def show
  end

  def new
    @category = Category.new(:parent_id => params[:parent_id])
    render layout: false
  end

  def edit
    render layout: false
  end

  def create
    @category = Category.new(ancient_category_params)
    flash[:success]= "创建成功" if @category.save
    redirect_to ancient_categories_url
  end

  def update
    flash[:sucess] = "更新成功" if @category.update_attributes(ancient_category_params)
    redirect_to ancient_categories_url
  end

  def destroy
    if @category.destroy
      respond_to do |format|
        flash[:sucess] = "删除成功"  #这个写法可能不一样,根据自己的设置,保证统一
        format.html{respond_back(@category)}
        format.json{render :json => true}
      end
    end
  end

  def edit_params
    @node = params[:name] ? find_param_node(@doc, params[:name]) : add_element(@doc.root,"<param>")
  end

  def important_params
    unless params[:firstline].blank?
      @doc.root.elements.each do |element|
        element.set_a("firstline", params[:firstline].include?(element.get_a("name")).to_s)
      end
      write_to_params
    end
    render layout: false
  end

  def update_params # need to refactor
    node = find_param_node(@doc, params[:old_name])

    %w(name show type).each {|key| node.set_a(key, params[key.to_sym])}
    Category::CheckedParams.each {|cp| node.set_a(cp.keys[0].to_s, params[cp.keys[0]] || "false")}

    #清空子节点
    node.elements.each {|e| e.remove }

    1.upto(params[:sub_count].to_i) do |i|
      next if !params["select#{i}"]
      select = add_element(node, '<select>')
      select.set_a("name", params["select#{i}"][:name])
      options = params["select#{i}"][:option].split("|")
      options.each {|option| add_element(select, '<option>').set_a("name", option)}
    end if params[:type] == "下拉型"
    write_to_params
    redirect_to_edit_params("修改参数信息成功", name: params[:name])
  end

  def create_params
    node = add_element(@doc.root,"<param>")
    node.set_a("name", params[:name])
    node.set_a("input", "true")
    write_to_params(@doc)
    redirect_to_edit_params("参数保存成功", name: params[:name])
  end

  def delete_params
    node = find_param_node(@doc, params[:name])
    return redirect_to_edit_params("下拉型参数不能被删除", name: params[:name]) if node.get_a("type") == "下拉型"
    node.remove
    write_to_params
    redirect_to_edit_params("删除节点成功")
  end

  def add_select
    name = params[:name]
    param_node = find_param_node(@doc, name)
    select_node = add_element(param_node,"<select>")
    write_to_params
    render partial: "select", locals: {index: param_node.elements.size - 1, select: select_node }
  end

  private

  NeedDocActions = %w(update_params edit_params add_select delete_params create_params important_params)

  def per_load
    @category ||= Category.unscoped.find(params[:id])
    get_doc(@category) if NeedDocActions.any? {|a| a == action_name}
  end

  def get_doc(category)
    if !category.params
      category.params = Setting.init_xml
      category.save
    end
    @doc ||= Nokogiri::XML(category.params)
  end

  def write_to_params(doc = @doc, str = '')
    @category.params = doc.to_xml
    @category.save
  end

  def refuse_same_name
    @doc.root.elements.each do |element|
      return redirect_to_edit_params("对不起,存在同名参数") if element.get_a("name") == params[:name]
    end
  end

  def redirect_to_edit_params(*args)
    msg, opts = args[0], args.extract_options!
    redirect_with_message(msg, opts.merge(id: params[:id], action: 'edit_params'))
  end

  def ancient_category_params
    params.require(:category).permit(:ancestry, :ancestry_depth, :name, :in_use, :parent_id) #根据你迁移文件中的字段去设置
  end

end

在/app/views/ancient/categories/index.html.erb:

<script type="text/javascript">
    $(document).ready(function(){
        window.treeOpts = ({
            current_tree: 'tree-list',
            url: '/ancient/categories/',
            model_name: 'Category',
            zNodes: <%= @roots.to_ztree_node.to_json.html_safe %>
        })
        window.ztreeSetting = <%= ztree_settings %>
    });
</script>
<%= tree_content('添加类目', url: new_ancient_category_path) %>

在/app/views/ancient/categories/new.html.erb:

<%= render :partial => 'form' %>

在/Users/apple/ligong/huananligong/app/views/ancient/categories/_form.html.erb:

<%= simple_form_for [:ancient, @category] do |f| %>
  <%= f.input :name, :label => "名称" %>
  <%= f.input :parent_id, :label => "上级品目" do %>
    <%= f.hidden_field :parent_id  %>
    <%= dynamic_selects Category, @category.parent_id, "category_parent_id" %>
  <% end %>
  <%= f.input :in_use, :label => "是否可用" %>
  <%= f.submit '确认', :class => "btn btn-success btn-small" %>
<% end %>

相关文章

网友评论

      本文标题:利用Ztree搭建后台树形结构

      本文链接:https://www.haomeiwen.com/subject/szmuextx.html