介绍
介绍
上一课福哥带着大家使用TFPHP框架给TFUMS项目的标签模块的增加了回收站功能,标签可以设置子级标签,子级标签又可以设置孙级标签,但是我们的标签管理列表无法体现这样的特点。
今天福哥要给标签功能增加一个面包屑功能,当我们切换到子级标签、孙级标签的时候可以通过面包屑上面的标签信息知道当前所处的标签层级位置,这个功能需要用到标签的父级ID,通过标签的父级ID可以寻本溯源找到本分支的“老祖”标签。
结构
模型
WEB-APP/Model/tag.inc.php
控制器
WEB-APP/Controller/admin/tag.inc.php
WEB-APP/Controller/api/admin/tag.inc.php
WEB-APP/Controller/api/admin/tag/.mapping.inc.php
WEB-APP/Controller/api/resource/tag.inc.php
WEB-APP/Controller/api/resource/tag/.mapping.inc.php
视图
WEB-APP/View/Template/admin/tag.html
js/pages/admin/tag.js
添加(Create)
模型
WEB-APP/Model/tag.inc.php
private function detectHasChild(int $userID, int $parentID): int { $tfdo = $this->tfphp->getDatabase()->getTFDO(); if($parentID > 0){ $parentChildsCount = $tfdo->fetchTotal("SELECT * FROM tfart_categories WHERE cPId = @int", array($parentID)); $ret = $this->update("tag", array( 'cTypeId'=>tag::T_TYPE, 'cId'=>$parentID, 'hasChild'=>($parentChildsCount > 0), 'updateDT'=>date("Y-m-d H:i:s"), )); if(!$ret){ return 1; } } return 0; } public function setParent(int $userID, int $tagID, int $parentID):int { // check exist $tagInfo = $this->getByTable("tag", array(tag::T_TYPE, $tagID)); if($tagInfo == null){ return 1; } $currParentID = $tagInfo['cPId']; // mod 1 $ret = $this->update("tag", array( 'cTypeId'=>tag::T_TYPE, 'cId'=>$tagID, 'cPId'=>$parentID, 'updateDT'=>date("Y-m-d H:i:s"), )); if(!$ret){ return 2; } // mod 2 if($currParentID > 0){ if($this->detectHasChild($userID, $currParentID) != 0){ return 3; } } // mod 3 if($parentID > 0){ if($this->detectHasChild($userID, $parentID) != 0){ return 4; } } return 0; }
控制器
WEB-APP/Controller/api/admin/tag.inc.php
private function doAdd(){ $req = $this->tfphp->getRequest(); $post = $req->post; $tag = new tag($this->tfphp); $pid = $req->get->get("pid"); $title = $post->get("title"); try{ // request test if($title == ""){ $this->tfphp->getResponse()->responseJSON_CM(200, 1012001, "错误请求"); } // add tag $ret = $tag->add($this->login->getLoginStatus()->mId, $title); switch ($ret){ case 1: $this->tfphp->getResponse()->responseJSON_CM(200, 1012002, "标签名称已经存在"); break; case 2: $this->tfphp->getResponse()->responseJSON_CM(200, 1012003, "保存失败"); break; } $tagId = $tag->getLastAddId(); // set parent if($pid > 0){ $tag->setParent($this->login->getLoginStatus()->mId, $tagId, $pid); } } catch(\Exception $e){ TFLog::getDefault($this->tfphp)->error($e->getMessage()); $this->tfphp->getResponse()->responseJSON_CM(200, 1012004, "添加失败"); } // output $this->tfphp->getResponse()->responseJSON_CM(200, 0, "OK", array( 'tagid'=>$tagId, )); }
视图
JS
_startAddForm: function() { var ex=this; var formOpts; formOpts = { url: this.baseUri + "api/admin/tag/_add?pid=" + this.pid, method: "post", validations: [ {type:"empty", name:"title", msg:"请填写标签名称"} ], onSuccess: function (d) { if(d.errcode == 0){ ex._dlg_add_form.close(); ex._list.refresh(null, true); } else{ $('form.add-form').tips({ text:d.errmsg }); } }, onError: function (d) { $('form.add-form').tips({ text:"服务器响应错误" }); }, onValidationError: function (form, name, msg) { $('form.add-form').tips({ text:msg }); $('form.add-form').find('[name="'+ name +'"]').focus(); } }; this._add_form = $('form.add-form').form(formOpts); },
查询(Retreive)
模型
WEB-APP/Model/tag.inc.php
public function loadTags(int $parentID, string $keyword, int $pageSize, int $pageNum, array $states=null):array { $tfdo = $this->tfphp->getDatabase()->getTFDO(); // list $sql = "SELECT a.cId, a.cName, a.hasChild, a.createDT, a.updateDT FROM tfart_categories a WHERE a.cPId = @int AND a.cStat = @int"; if($keyword != ""){ $sql .= " AND a.cName LIKE '%". str_replace("'", "\'", $keyword). "%'"; } $sql .= " ORDER BY a.cId DESC"; $params = array($parentID, tag::T_STATE_NORMAL); $total = $tfdo->fetchTotal($sql, $params); $page = new TFDataPage($this->tfphp, $total, $pageSize, $pageNum); $page->makeTeamlinkRange(6); $pageArr = $page->toArray(); $datas = $tfdo->fetchPart($sql, $pageArr['seekBegin'], $pageArr['fetchNums'], $params); if(is_array($datas)){ foreach($datas as $k => $data){ } } // locate $locateDatas = array(); if($parentID > 0){ while($parentID > 0){ $sql = "SELECT a.cId, a.cName, cPId FROM tfart_categories a WHERE a.cId = @int AND a.cStat = @int"; $params = array($parentID, tag::T_STATE_NORMAL); $locateData = $tfdo->fetchOne($sql, $params); if($locateData != null){ $locateDatas[] = $locateData; $parentID = $locateData['cPId']; } else{ break; } } } return array( 'page'=>$pageArr, 'data'=>$datas, 'locateData'=>$locateDatas, ); }
视图
HTML
<div class="row"> <div class="col-12"> <div class="tag-locate"> <span><a href="tag.html">标签管理</a></span> </div> </div> <div class="col-12"> <div class="tag-search"> <form class="search-form form-horizontal"> <div class="row"> <div class="col-12"> <div class="float-left"> </div> <div class="float-right"> <div class="form-inline"> <span><label>关键词</label></span> <span><input class="form-control" type="text" name="keyword" /></span> <span><button class="btn btn-primary btn-sm form-control">搜索</button></span> <span><button class="btn btn-fun btn-sm form-control add-btn">新建标签</button></span> </div> </div> </div> </div> </form> </div> </div> <div class="col-12"> <div class="tag-list"> <table class="table"> <thead> <tr> <th>ID</th> <th>标签</th> <th>子级</th> <th>创建时间</th> <th>更新时间</th> <th> </th> </tr> </thead> <tbody> </tbody> </table> <ul class="pagination"> <li><a>第一页</a></li> <li><a>前一页</a></li> <li class="active"><a>1</a></li> <li><a>2</a></li> <li><a>3</a></li> <li><a>4</a></li> <li><a>5</a></li> <li><a>6</a></li> <li><a>后一页</a></li> <li><a>最后页</a></li> </ul> </div> </div> </div>
JS
_startTable: function (_pn) { var ex = this; var tableOpts; tableOpts = { dataUrl: this.baseUri + "api/admin/tag/_list?pid=" + this.pid + "&pn={pn}", dataMethod: "post", dataRenderType: "classic", pn: _pn, attachRowEvent: function (obj, id) { var item = obj.find('tr[dataid="' + id + '"]'), dataid = id; item.find('.btn-mod').click(function () { // modify ex._openModForm(dataid); }); item.find('.btn-del').click(function () { // delete ex._openDelForm(dataid); }); }, onRenderTable: function (data, table) { var obj = table.find(".table").find("tbody"), locateObj = $('.tag-locate'), i, l; obj.find("tr").remove(); locateObj.find('span.locate-link').remove(); if(data.locateData){ for(i=data.locateData.length-1;i>=0;i--){ locateObj.append("<span class=\"locate-link\">> <a href=\"?pid=" + data.locateData[i].cId + "\">" + data.locateData[i].cName + "</a></span>"); } } }, onRenderRow: function (row, table) { var obj = table.find(".table").find("tbody"), extraBtns, extraClass; extraBtns = ''; extraClass = ''; row.cName = "<a href=\"?pid=" + row.cId + "\">" + row.cName + "</a>"; if(row.createDT == null){ row.createDT = ""; } if(row.updateDT == null){ row.updateDT = ""; } row.createDT = row.createDT.replace( /\s.*$/ ,""); row.updateDT = row.updateDT.replace( /\s.*$/ ,""); obj.append('<tr dataid="' + row.cId + '">' + ' <td>' + row.cId + '</td>' + ' <td>' + row.cName + '</td>' + ' <td>' + ((row.hasChild > 0) ? "是" : "") + '</td>' + ' <td>' + row.createDT + '</td>' + ' <td>' + row.updateDT + '</td>' + ' <td class="btns">' + ' <span class="btn btn-white btn-sm btn-mod">编辑</span>' + ' <span class="btn btn-white btn-sm btn-del">删除</span>' + ' </td>' + '</tr>'); this.attachRowEvent(obj, row.cId); }, onRenderPage: function (page, table) { var obj = table.find(".pagination"), html = ""; obj.html(""); if (page.pageNum > 1) { obj.append('<li><a page-num="1">第一页</a></li>'); obj.append('<li><a page-num="' + (page.pageNum - 1) + '">前一页</a></li>'); } if (page.pageTeamlinkRange) { for (var p = page.pageTeamlinkRange.low; p <= page.pageTeamlinkRange.high; p++) { obj.append('<li><a page-num="' + p + '">' + p + '</a></li>'); } obj.find('[page-num="' + page.pageNum + '"]').parent().addClass("active"); } if (page.pageNum < page.pageTotal) { obj.append('<li><a page-num="' + (page.pageNum + 1) + '">后一页</a></li>'); obj.append('<li><a page-num="' + page.pageTotal + '">最后页</a></li>'); } }, onError: function (d) { } }; this._list = $('.tag-list').table(tableOpts); }, _startSearchForm: function() { var ex=this; var formOpts; formOpts = { method: "post", onSuccess: function (d) { if(d.page && d.data){ } else{ $('form.search').tips({ text:d.errmsg }); } }, onError: function (d) { $('form.search').tips({ text:"服务器响应错误" }); }, onValidationError: function (form, name, msg) { $('form.search').tips({ text:msg }); $('form.search').find('[name="'+ name +'"]').focus(); } }; formOpts.table = this._list; this._search_form = $('form.search-form').form(formOpts); },
效果
列表
总结
今天福哥带着童鞋们给TFUMS项目的标签模块增加了面包屑功能,通过面包屑功能可以实现多级标签的数据结构的设计!