浏览代码

实现文档历史功能

Minho 8 年之前
父节点
当前提交
1fdb2c5df6

+ 5 - 15
commands/install.go

@@ -8,6 +8,7 @@ import (
 	"github.com/astaxie/beego/orm"
 	"github.com/lifei6671/godoc/conf"
 	"github.com/lifei6671/godoc/models"
+	"io/ioutil"
 )
 
 //系统安装.
@@ -32,26 +33,15 @@ func initialization() {
 
 	o := orm.NewOrm()
 
-	_, err := o.Raw(`INSERT INTO md_options (option_title, option_name, option_value) SELECT '是否启用注册','ENABLED_REGISTER','false' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'ENABLED_REGISTER');`).Exec()
+	b,err := ioutil.ReadFile("./data/data.sql")
 
 	if err != nil {
-		panic("ENABLED_REGISTER => " + err.Error())
+		panic(err.Error())
 		os.Exit(1)
 	}
-	_, err = o.Raw(`INSERT INTO md_options (option_title, option_name, option_value) SELECT '是否启用验证码','ENABLED_CAPTCHA','false' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'ENABLED_CAPTCHA');`).Exec()
-
-	if err != nil {
-		panic("ENABLED_CAPTCHA => " + err.Error())
-		os.Exit(1)
-	}
-	_, err = o.Raw(`INSERT INTO md_options (option_title, option_name, option_value) SELECT '启用匿名访问','ENABLE_ANONYMOUS','true' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'ENABLE_ANONYMOUS');`).Exec()
-
-	if err != nil {
-		panic("ENABLE_ANONYMOUS => " + err.Error())
-		os.Exit(1)
-	}
-	_, err = o.Raw(`INSERT INTO md_options (option_title, option_name, option_value) SELECT '站点名称','SITE_NAME','MinDoc' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'SITE_NAME');`).Exec()
+	sql := string(b)
 
+	_,err = o.Raw(sql).Exec()
 	if err != nil {
 		panic("SITE_NAME => " + err.Error())
 		os.Exit(1)

+ 46 - 0
commands/update.go

@@ -9,12 +9,20 @@ import (
 
 	"github.com/astaxie/beego"
 	"github.com/lifei6671/godoc/conf"
+	"github.com/astaxie/beego/orm"
 )
 
 //系统升级.
 func Update() {
 	if len(os.Args) >= 2 && os.Args[1] == "update" {
 
+		adapter := beego.AppConfig.String("db_adapter")
+
+		if adapter == "mysql" {
+			mysqlUpdate()
+		}else if adapter == "sqlite3" {
+			sqliteUpdate()
+		}
 		fmt.Println("update successed.")
 
 		os.Exit(0)
@@ -55,3 +63,41 @@ func CheckUpdate() {
 		os.Exit(0)
 	}
 }
+
+//MySQL 数据库更新表结构.
+func mysqlUpdate()  {
+	sql := `
+	IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=CurrentDatabase AND table_name = 'md_members' AND column_name = 'auth_method') THEN
+		ALTER TABLE md_members ADD auth_method VARCHAR(50) DEFAULT 'local' NULL;
+	END IF; `
+	o := orm.NewOrm()
+
+	_,err := o.Raw(sql).Exec()
+
+	if err != nil {
+		panic(fmt.Sprintf("error : 6001 => %s",err.Error()))
+		os.Exit(1)
+	}
+}
+
+//sqlite 数据库更新表结构.
+func sqliteUpdate()  {
+	o := orm.NewOrm()
+
+	var sqlite_master struct{
+		Name string
+	}
+
+
+	err := o.Raw("select * from sqlite_master where name='md_members' and sql like '%auth_method%' limit 1").QueryRow(&sqlite_master)
+	//查询是否已经存在 auth_method 列
+	if err == nil && sqlite_master.Name == ""{
+		_,err = o.Raw("ALTER TABLE md_members ADD auth_method VARCHAR(50) DEFAULT 'local' NULL;").Exec()
+		if err != nil {
+			panic(fmt.Sprintf("error : 6001 => %s",err.Error()))
+			os.Exit(1)
+		}
+	}
+
+	os.Exit(1)
+}

+ 125 - 15
controllers/document.go

@@ -891,6 +891,7 @@ func (c *DocumentController) Search()  {
 //文档历史列表.
 func (c *DocumentController) History() {
 	c.Prepare()
+	c.TplName = "document/history.tpl"
 
 	identify := c.GetString("identify")
 	doc_id, err := c.GetInt("doc_id", 0)
@@ -902,53 +903,162 @@ func (c *DocumentController) History() {
 		book, err := models.NewBook().FindByFieldFirst("identify", identify)
 		if err != nil {
 			beego.Error("FindByIdentify => ", err)
-			c.JsonResult(6002, "项目不存在或权限不足")
+			c.Data["ErrorMessage"] = "项目不存在或权限不足"
+			return
 		}
 		book_id = book.BookId
+		c.Data["Model"] = book
 	} else {
 		bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
 
 		if err != nil || bookResult.RoleId == conf.BookObserver {
 			beego.Error("FindByIdentify => ", err)
-			c.JsonResult(6002, "项目不存在或权限不足")
+			c.Data["ErrorMessage"] = "项目不存在或权限不足"
+			return
 		}
 		book_id = bookResult.BookId
+		c.Data["Model"] = bookResult
 	}
 
 	if doc_id <= 0 {
-		c.JsonResult(6001, "参数错误")
+		c.Data["ErrorMessage"] = "参数错误"
+		return
 	}
 
 	doc, err := models.NewDocument().Find(doc_id)
 
 	if err != nil {
 		beego.Error("Delete => ", err)
-		c.JsonResult(6003, "获取历史失败")
+		c.Data["ErrorMessage"] = "获取历史失败"
+		return
 	}
 	//如果文档所属项目错误
 	if doc.BookId != book_id {
-		c.JsonResult(6004, "参数错误")
+		c.Data["ErrorMessage"] = "参数错误"
+		return
 	}
 
 	historis,totalCount,err := models.NewDocumentHistory().FindToPager(doc_id,pageIndex,conf.PageSize)
 
 	if err != nil {
-		c.JsonResult(6005,"获取历史失败")
-	}
-	var data struct {
-		PageHtml string `json:"page_html"`
-		List []*models.DocumentHistorySimpleResult `json:"lists"`
+		beego.Error("FindToPager => ",err)
+		c.Data["ErrorMessage"] = "获取历史失败"
+		return
 	}
-	data.List = historis
+
+	c.Data["List"] = historis
+	c.Data["PageHtml"] = ""
+	c.Data["Document"] = doc
+
 	if totalCount > 0 {
 		html := utils.GetPagerHtml(c.Ctx.Request.RequestURI, pageIndex, conf.PageSize, totalCount)
 
-		data.PageHtml = string(html)
-	}else {
-		data.PageHtml = ""
+		c.Data["PageHtml"] =html
+	}
+}
+
+func (c *DocumentController) DeleteHistory() {
+	c.Prepare()
+	c.TplName = "document/history.tpl"
+
+	identify := c.GetString("identify")
+	doc_id, err := c.GetInt("doc_id", 0)
+	history_id,_ := c.GetInt("history_id",0)
+
+	if history_id <= 0 {
+		c.JsonResult(6001,"参数错误")
+	}
+	book_id := 0
+	//如果是超级管理员则忽略权限判断
+	if c.Member.Role == conf.MemberSuperRole {
+		book, err := models.NewBook().FindByFieldFirst("identify", identify)
+		if err != nil {
+			beego.Error("FindByIdentify => ", err)
+			c.JsonResult(6002, "项目不存在或权限不足")
+		}
+		book_id = book.BookId
+	} else {
+		bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
+
+		if err != nil || bookResult.RoleId == conf.BookObserver {
+			beego.Error("FindByIdentify => ", err)
+			c.JsonResult(6002, "项目不存在或权限不足")
+		}
+		book_id = bookResult.BookId
+	}
+
+	if doc_id <= 0 {
+		c.JsonResult(6001,"参数错误")
+	}
+
+	doc, err := models.NewDocument().Find(doc_id)
+
+	if err != nil {
+		beego.Error("Delete => ", err)
+		c.JsonResult(6001,"获取历史失败")
+	}
+	//如果文档所属项目错误
+	if doc.BookId != book_id {
+		c.JsonResult(6001,"参数错误")
+	}
+	err = models.NewDocumentHistory().Delete(history_id,doc_id)
+	if err != nil {
+		beego.Error(err)
+		c.JsonResult(6002,"删除失败")
+	}
+	c.JsonResult(0,"ok")
+}
+
+func (c *DocumentController) RestoreHistory()  {
+	c.Prepare()
+	c.TplName = "document/history.tpl"
+
+	identify := c.GetString("identify")
+	doc_id, err := c.GetInt("doc_id", 0)
+	history_id,_ := c.GetInt("history_id",0)
+
+	if history_id <= 0 {
+		c.JsonResult(6001,"参数错误")
+	}
+	book_id := 0
+	//如果是超级管理员则忽略权限判断
+	if c.Member.Role == conf.MemberSuperRole {
+		book, err := models.NewBook().FindByFieldFirst("identify", identify)
+		if err != nil {
+			beego.Error("FindByIdentify => ", err)
+			c.JsonResult(6002, "项目不存在或权限不足")
+		}
+		book_id = book.BookId
+	} else {
+		bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
+
+		if err != nil || bookResult.RoleId == conf.BookObserver {
+			beego.Error("FindByIdentify => ", err)
+			c.JsonResult(6002, "项目不存在或权限不足")
+		}
+		book_id = bookResult.BookId
+	}
+
+	if doc_id <= 0 {
+		c.JsonResult(6001,"参数错误")
 	}
 
-	c.JsonResult(0,"ok",data)
+	doc, err := models.NewDocument().Find(doc_id)
+
+	if err != nil {
+		beego.Error("Delete => ", err)
+		c.JsonResult(6001,"获取历史失败")
+	}
+	//如果文档所属项目错误
+	if doc.BookId != book_id {
+		c.JsonResult(6001,"参数错误")
+	}
+	err = models.NewDocumentHistory().Restore(history_id,doc_id,c.Member.MemberId)
+	if err != nil {
+		beego.Error(err)
+		c.JsonResult(6002,"删除失败")
+	}
+	c.JsonResult(0,"ok",doc)
 }
 
 //递归生成文档序列数组.

+ 17 - 0
data/data.sql

@@ -0,0 +1,17 @@
+INSERT INTO md_options (option_title, option_name, option_value) SELECT '是否启用注册','ENABLED_REGISTER','false' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'ENABLED_REGISTER');
+INSERT INTO md_options (option_title, option_name, option_value) SELECT '是否启用文档历史','ENABLE_DOCUMENT_HISTORY','true' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'ENABLE_DOCUMENT_HISTORY');
+INSERT INTO md_options (option_title, option_name, option_value) SELECT '是否启用验证码','ENABLED_CAPTCHA','false' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'ENABLED_CAPTCHA');
+INSERT INTO md_options (option_title, option_name, option_value) SELECT '启用匿名访问','ENABLE_ANONYMOUS','true' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'ENABLE_ANONYMOUS');
+INSERT INTO md_options (option_title, option_name, option_value) SELECT '站点名称','SITE_NAME','MinDoc' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'SITE_NAME');
+
+
+
+
+
+
+
+
+
+
+
+

+ 0 - 1
models/book.go

@@ -176,7 +176,6 @@ func (m *Book) FindToPager(pageIndex, pageSize ,memberId int) (books []*BookResu
 		Limit(pageSize).
 		Offset(offset)
 
-	logs.Info("",qb2.String())
 	_,err = o.Raw(qb2.String(),memberId).QueryRows(&books)
 	if err != nil {
 		logs.Error("分页查询项目列表 => ",err)

+ 25 - 6
models/document_history.go

@@ -49,6 +49,9 @@ func (m *DocumentHistory) TableNameWithPrefix() string {
 
 func NewDocumentHistory() *DocumentHistory {
 	return &DocumentHistory{}
+}
+func (m *DocumentHistory) Find()  {
+
 }
 //清空指定文档的历史.
 func (m *DocumentHistory) Clear(doc_id int) error {
@@ -60,18 +63,19 @@ func (m *DocumentHistory) Clear(doc_id int) error {
 }
 
 //删除历史.
-func (m *DocumentHistory) Delete(history_id int) error {
+func (m *DocumentHistory) Delete(history_id,doc_id int) error {
 	o := orm.NewOrm()
 
-	_, err := o.Raw("DELETE md_document_history WHERE history_id = ?", history_id).Exec()
+	_, err := o.QueryTable(m.TableNameWithPrefix()).Filter("history_id",history_id).Filter("document_id",doc_id).Delete()
+
 	return err
 }
 
 //恢复指定历史的文档.
-func (m *DocumentHistory) Restore(history_id int) error {
+func (m *DocumentHistory) Restore(history_id,doc_id,uid int) error {
 	o := orm.NewOrm()
 
-	err := o.QueryTable(m.TableNameWithPrefix()).Filter("history_id", history_id).One(m)
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("history_id", history_id).Filter("document_id",doc_id).One(m)
 
 	if err != nil {
 		return err
@@ -81,10 +85,25 @@ func (m *DocumentHistory) Restore(history_id int) error {
 	if err != nil {
 		return err
 	}
+	history := NewDocumentHistory()
+	history.DocumentId = doc_id
+	history.Content = doc.Content
+	history.Markdown = doc.Markdown
+	history.DocumentName = doc.DocumentName
+	history.ModifyAt = uid
+	history.MemberId = doc.MemberId
+	history.ParentId = doc.ParentId
+	history.Version = time.Now().Unix()
+	history.Action = "restore"
+	history.ActionName = "恢复文档"
+
+	history.InsertOrUpdate()
+
 	doc.DocumentName = m.DocumentName
 	doc.Content = m.Content
 	doc.Markdown = m.Markdown
 	doc.Release = m.Content
+	doc.Version = time.Now().Unix()
 
 	_, err = o.Update(doc)
 
@@ -111,10 +130,10 @@ func (m *DocumentHistory) FindToPager(doc_id, page_index, page_size int) (docs [
 
 	totalCount = 0
 
-	sql := `SELECT history.*,m1.account,m2.account as ModifyName
+	sql := `SELECT history.*,m1.account,m2.account as modify_name
 FROM md_document_history AS history
 LEFT JOIN md_members AS m1 ON history.member_id = m1.member_id
-LEFT JOIN md_members AS m2 ON history.member_id = m2.member_id
+LEFT JOIN md_members AS m2 ON history.modify_at = m2.member_id
 WHERE history.document_id = ? ORDER BY history.history_id DESC LIMIT ?,?;`
 
 	_, err = o.Raw(sql,doc_id,offset,page_size).QueryRows(&docs)

+ 3 - 1
models/member.go

@@ -22,7 +22,7 @@ type Member struct {
 	Account  string `orm:"size(100);unique;column(account)" json:"account"`
 	Password string `orm:"size(1000);column(password)" json:"-"`
 	//认证方式: local 本地数据库 /ldap LDAP
-	AuthMethod  string `orm:"size(10);column(auth_method);default(local)" json:"auth_method)"`
+	AuthMethod  string `orm:"column(auth_method);default(local);size(50);" json:"auth_method)"`
 	Description string `orm:"column(description);size(2000)" json:"description"`
 	Email       string `orm:"size(100);column(email);unique" json:"email"`
 	Phone       string `orm:"size(255);column(phone);null;default(null)" json:"phone"`
@@ -128,6 +128,8 @@ func (m *Member) ldapLogin(account string, password string) (*Member, error) {
 		m.AuthMethod = "ldap"
 		m.Avatar = "/static/images/headimgurl.jpg"
 		m.Role = beego.AppConfig.DefaultInt("ldap_user_role", 2)
+		m.CreateTime = time.Now()
+
 		err = m.Add()
 		if err != nil {
 			logs.Error("自动注册LDAP用户错误", err)

+ 3 - 1
routers/router.go

@@ -57,8 +57,10 @@ func init()  {
 	beego.Router("/api/:key/create",&controllers.DocumentController{},"post:Create")
 	beego.Router("/api/:key/delete", &controllers.DocumentController{},"post:Delete")
 	beego.Router("/api/:key/content/?:id",&controllers.DocumentController{},"*:Content")
-	beego.Router("/api/history", &controllers.DocumentController{},"get:History")
 
+	beego.Router("/history/get", &controllers.DocumentController{},"get:History")
+	beego.Router("/history/delete", &controllers.DocumentController{},"*:DeleteHistory")
+	beego.Router("/history/restore", &controllers.DocumentController{},"*:RestoreHistory")
 
 	beego.Router("/docs/:key", &controllers.DocumentController{},"*:Index")
 	beego.Router("/docs/:key/:id", &controllers.DocumentController{},"*:Read")

+ 59 - 21
static/js/editor.js

@@ -200,30 +200,68 @@ function showSuccess($msg,$id) {
     return true;
 }
 
-$(function () {
-    $("#documentHistoryModal").on("shown.bs.modal",function () {
-        var historyVue = new Vue({
-            el : "#documentHistoryModal",
-            data : {
-                lists : []
-            },
-            delimiters : ['${','}'],
-            methods : {
-
+window.documentHistory = function() {
+    layer.open({
+        type: 2,
+        title: '历史版本',
+        shadeClose: true,
+        shade: 0.8,
+        area: ['700px','80%'],
+        content: window.historyURL + "?identify=" + window.book.identify + "&doc_id=" + window.selectNode.id,
+        end : function () {
+            if(window.SelectedId){
+                var selected = {node:{
+                    id : window.SelectedId
+                }};
+                window.loadDocument(selected);
+                window.SelectedId = null;
             }
-        });
+        }
+    });
+};
 
-        $.ajax({
-            url : window.historyURL,
-            data : { "identify" : window.book.identify,"doc_id" : window.selectNode.id },
-            dataType :"json",
-            success : function (res) {
-                if(res.errcode === 0){
-                    historyVue.lists = res.data.lists;
-                }else{
-                    alert(res.message);
+$(function () {
+    window.vueApp = new Vue({
+        el : "#attachList",
+        data : {
+            lists : []
+        },
+        delimiters : ['${','}'],
+        methods : {
+            removeAttach : function ($attach_id) {
+                var $this = this;
+                var item = $this.lists.filter(function ($item) {
+                    return $item.attachment_id == $attach_id;
+                });
+
+                if(item && item[0].hasOwnProperty("state")){
+                    $this.lists = $this.lists.filter(function ($item) {
+                        return $item.attachment_id != $attach_id;
+                    });
+                    return;
                 }
+                $.ajax({
+                    url : window.removeAttachURL,
+                    type : "post",
+                    data : { "attach_id" : $attach_id},
+                    success : function (res) {
+                        console.log(res);
+                        if(res.errcode === 0){
+                            $this.lists = $this.lists.filter(function ($item) {
+                                return $item.attachment_id != $attach_id;
+                            });
+                        }else{
+                            layer.msg(res.message);
+                        }
+                    }
+                });
             }
-        });
+        },
+        watch : {
+            lists : function ($lists) {
+                $("#attachInfo").text(" " + $lists.length + " 个附件")
+            }
+        }
     });
+
 });

+ 3 - 9
static/js/html-editor.js

@@ -9,24 +9,18 @@ $(function () {
         "editor" : "wangEditor"
     };
     wangEditor.config.menus.splice(0,0,"|");
+    wangEditor.config.menus.splice(0,0,"history");
     wangEditor.config.menus.splice(0,0,"save");
     wangEditor.config.menus.splice(0,0,"release");
     wangEditor.config.menus.splice(29,0,"attach")
 
     //移除地图、背景色
     editor.config.menus = $.map(wangEditor.config.menus, function(item, key) {
-        if (item === 'bgcolor') {
-            return null;
-        }
+
         if (item === 'fullscreen') {
             return null;
         }
-        if (item === "undo"){
-            return null;
-        }
-        if (item === "redo"){
-            return null;
-        }
+
         return item;
     });
 

+ 2 - 3
static/js/markdown.js

@@ -61,7 +61,7 @@ $(function () {
        if(name === "attachment"){
            $("#uploadAttachModal").modal("show");
        }else if(name === "history"){
-            $("#documentHistoryModal").modal("show");
+           window.documentHistory();
        }else if(name === "save"){
             saveDocument(false);
 
@@ -126,7 +126,7 @@ $(function () {
      * 加载指定的文档到编辑器中
      * @param $node
      */
-    function loadDocument($node) {
+    window.loadDocument = function($node) {
         var index = layer.load(1, {
             shade: [0.1,'#fff'] //0.1透明度的白色背景
         });
@@ -175,7 +175,6 @@ $(function () {
 
             if(item.id === doc_id){
                 version = item.version;
-                console.log(item)
                 break;
             }
         }

+ 47 - 0
static/wangEditor/plugins/history-menu.js

@@ -0,0 +1,47 @@
+(function () {
+
+    // 获取 wangEditor 构造函数和 jquery
+    var E = window.wangEditor;
+    var $ = window.jQuery;
+
+    // 用 createMenu 方法创建菜单
+    E.createMenu(function (check) {
+
+        // 定义菜单id,不要和其他菜单id重复。编辑器自带的所有菜单id,可通过『参数配置-自定义菜单』一节查看
+        var menuId = 'history';
+
+        // check将检查菜单配置(『参数配置-自定义菜单』一节描述)中是否该菜单id,如果没有,则忽略下面的代码。
+        if (!check(menuId)) {
+            return;
+        }
+
+        // this 指向 editor 对象自身
+        var editor = this;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,  // 编辑器对象
+            id: menuId,  // 菜单id
+            title: '历史', // 菜单标题
+
+            // 正常状态和选中状态下的dom对象,样式需要自定义
+            $domNormal: $('<a href="#" tabindex="-1"><i class="fa fa-history" aria-hidden="true" name="history"></i></a>'),
+            $domSelected: $('<a href="#" tabindex="-1" class="selected"><i class="fa fa-history" aria-hidden="true" name="history"></i></a>')
+        });
+
+        // 菜单正常状态下,点击将触发该事件
+        menu.clickEvent = function (e) {
+            window.documentHistory();
+        };
+
+        // 菜单选中状态下,点击将触发该事件
+        menu.clickEventSelected = function (e) {
+
+        };
+
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+})();

+ 134 - 0
views/document/history.tpl

@@ -0,0 +1,134 @@
+
+<!DOCTYPE html>
+<html lang="zh-cn">
+<head>
+    <meta charset="utf-8">
+    <link rel="shortcut icon" href="/favicon.ico">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="renderer" content="webkit" />
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="author" content="SmartWiki" />
+    <title>历史版本 - Powered by MinDoc</title>
+
+    <!-- Bootstrap -->
+    <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
+
+    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+    <!--[if lt IE 9]>
+    <script src="/static/html5shiv/3.7.3/html5shiv.min.js"></script>
+    <script src="/static/respond.js/1.4.2/respond.min.js"></script>
+    <![endif]-->
+    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
+    <script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
+    <style type="text/css">
+        .container{margin: 5px auto;}
+    </style>
+</head>
+<body>
+<div class="container">
+    <div class="table-responsive">
+        <table class="table table-hover">
+            <thead>
+            <tr>
+                <td>#</td>
+                <td class="col-sm-6">修改时间</td>
+                <td class="col-sm-2">修改人</td>
+                <td class="col-sm=2">版本</td>
+                <td class="col-sm-2">操作</td>
+            </tr>
+            </thead>
+            <tbody>
+            {{range $index,$item := .List}}
+            <tr>
+                <td>{{$item.HistoryId}}</td>
+                <td>{{date $item.ModifyTime "Y-m-d H:i:s"}}</td>
+                <td>{{$item.ModifyName}}</td>
+                <td>{{$item.Version}}</td>
+                <td>
+                    <button class="btn btn-danger btn-sm delete-btn" data-id="{{$item.HistoryId}}" data-loading-text="删除中...">
+                        删除
+                    </button>
+                    <button class="btn btn-success btn-sm restore-btn" data-id="{{$item.HistoryId}}" data-loading-text="恢复中...">
+                        恢复
+                    </button>
+                </td>
+            </tr>
+            {{else}}
+            <tr>
+                <td colspan="6" class="text-center">暂无数据</td>
+            </tr>
+            {{end}}
+            </tbody>
+        </table>
+    </div>
+    <nav>
+        {{.PageHtml}}
+    </nav>
+</div>
+<!-- Include all compiled plugins (below), or include individual files as needed -->
+<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
+<script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
+<script type="text/javascript">
+    $(function () {
+        $(".delete-btn").on("click",function () {
+            var id = $(this).attr('data-id');
+            var $btn = $(this).button('loading');
+            var $then = $(this);
+
+            if(!id){
+                layer.msg('参数错误');
+            }else{
+                $.ajax({
+                    url : "{{urlfor "DocumentController.DeleteHistory"}}",
+                    type : "post",
+                    dataType : "json",
+                    data : { "identify" : "{{.Model.Identify}}","doc_id" : "{{.Document.DocumentId}}" ,"history_id" : id },
+                    success :function (res) {
+                        if(res.errcode === 0){
+                            $then.parents('tr').remove().empty();
+                        }else{
+                            layer.msg(res.message);
+                        }
+                    },
+                    error : function () {
+                        $btn.button('reset');
+                    }
+                })
+            }
+        });
+
+        $(".restore-btn").on("click",function () {
+            var id = $(this).attr('data-id');
+            var $btn = $(this).button('loading');
+            var $then = $(this);
+            var index = parent.layer.getFrameIndex(window.name);
+
+            if(!id){
+                layer.msg('参数错误');
+            }else{
+                $.ajax({
+                    url : "{{urlfor "DocumentController.RestoreHistory"}}",
+                    type : "post",
+                    dataType : "json",
+                    data : { "identify" : "{{.Model.Identify}}","doc_id" : "{{.Document.DocumentId}}" ,"history_id" : id },
+                    success :function (res) {
+                        if(res.errcode === 0){
+                            var $node = { "node" : { "id" : res.data.doc_id}};
+
+                            parent.loadDocument($node);
+                            parent.layer.close(index);
+                        }else{
+                            layer.msg(res.message);
+                        }
+                    },
+                    error : function () {
+                        $btn.button('reset');
+                    }
+                })
+            }
+        });
+    });
+</script>
+</body>
+</html>

+ 3 - 46
views/document/html_edit_template.tpl

@@ -19,7 +19,8 @@
         window.releaseURL = "{{urlfor "BookController.Release" ":key" .Model.Identify}}";
         window.sortURL = "{{urlfor "BookController.SaveSort" ":key" .Model.Identify}}";
         window.baiduMapKey = "{{.BaiDuMapKey}}";
-
+        window.historyURL = "{{urlfor "DocumentController.History"}}";
+        window.removeAttachURL = "{{urlfor "DocumentController.RemoveAttachment"}}";
         window.vueApp = null;
     </script>
     <!-- Bootstrap -->
@@ -190,54 +191,10 @@
 <script src="{{cdnjs "/static/wangEditor/plugins/save-menu.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/wangEditor/plugins/release-menu.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/wangEditor/plugins/attach-menu.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/wangEditor/plugins/history-menu.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
 <script src="{{cdnjs "/static/to-markdown/dist/to-markdown.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
-<script type="text/javascript">
-    window.vueApp = new Vue({
-        el : "#attachList",
-        data : {
-            lists : []
-        },
-        delimiters : ['${','}'],
-        methods : {
-            removeAttach : function ($attach_id) {
-                var $this = this;
-                var item = $this.lists.filter(function ($item) {
-                   return $item.attachment_id == $attach_id;
-                });
-
-                if(item && item[0].hasOwnProperty("state")){
-                   $this.lists = $this.lists.filter(function ($item) {
-                       return $item.attachment_id != $attach_id;
-                   });
-                   return;
-                }
-                $.ajax({
-                    url : "{{urlfor "DocumentController.RemoveAttachment"}}",
-                    type : "post",
-                    data : { "attach_id" : $attach_id},
-                    success : function (res) {
-                        console.log(res);
-                        if(res.errcode === 0){
-                            $this.lists = $this.lists.filter(function ($item) {
-                                return $item.attachment_id != $attach_id;
-                            });
-                        }else{
-                            layer.msg(res.message);
-                        }
-                    }
-                });
-            }
-        },
-        watch : {
-            lists : function ($lists) {
-                $("#attachInfo").text(" " + $lists.length + " 个附件")
-            }
-        }
-    });
-
-</script>
 <script src="/static/js/editor.js" type="text/javascript"></script>
 <script src="/static/js/html-editor.js" type="text/javascript"></script>
 <script type="text/javascript">

+ 3 - 66
views/document/markdown_edit_template.tpl

@@ -18,6 +18,7 @@
         window.releaseURL = "{{urlfor "BookController.Release" ":key" .Model.Identify}}";
         window.sortURL = "{{urlfor "BookController.SaveSort" ":key" .Model.Identify}}";
         window.historyURL = "{{urlfor "DocumentController.History"}}";
+        window.removeAttachURL = "{{urlfor "DocumentController.RemoveAttachment"}}";
     </script>
     <!-- Bootstrap -->
     <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
@@ -214,31 +215,8 @@
                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                 <h4 class="modal-title">文档历史记录</h4>
             </div>
-            <div class="modal-body text-center">
-                <template v-if="lists.length <= 0">
-                    暂无数据
-                </template>
-                <template v-else>
-                    <table class="table">
-                        <thead>
-                        <tr>
-                            <th>#</th><th>名称</th><th>修改时间</th><th>修改人</th><th>版本</th><th>操作</th>
-                        </tr>
-                        </thead>
-                        <tbody>
-                        <template v-for="item in lists">
-                        <tr>
-                            <td>${item.history_id}</td>
-                            <td>${item.action_name}</td>
-                            <td>${item.modify_time}</td>
-                            <td>${item.modify_name}</td>
-                            <td>${item.version}</td>
-                            <td></td>
-                        </tr>
-                        </template>
-                        </tbody>
-                    </table>
-                </template>
+            <div class="modal-body text-center" id="historyList">
+
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
@@ -256,48 +234,7 @@
 <script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
 <script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
-    window.vueApp = new Vue({
-        el : "#attachList",
-        data : {
-            lists : []
-        },
-        delimiters : ['${','}'],
-        methods : {
-            removeAttach : function ($attach_id) {
-                var $this = this;
-                var item = $this.lists.filter(function ($item) {
-                    return $item.attachment_id == $attach_id;
-                });
 
-                if(item && item[0].hasOwnProperty("state")){
-                    $this.lists = $this.lists.filter(function ($item) {
-                        return $item.attachment_id != $attach_id;
-                    });
-                    return;
-                }
-                $.ajax({
-                    url : "{{urlfor "DocumentController.RemoveAttachment"}}",
-                    type : "post",
-                    data : { "attach_id" : $attach_id},
-                    success : function (res) {
-                        console.log(res);
-                        if(res.errcode === 0){
-                            $this.lists = $this.lists.filter(function ($item) {
-                                return $item.attachment_id != $attach_id;
-                            });
-                        }else{
-                            layer.msg(res.message);
-                        }
-                    }
-                });
-            }
-        },
-        watch : {
-            lists : function ($lists) {
-                $("#attachInfo").text(" " + $lists.length + " 个附件")
-            }
-        }
-    });
 
 </script>
 <script src="/static/js/editor.js" type="text/javascript"></script>

+ 11 - 1
views/manager/setting.tpl

@@ -77,7 +77,17 @@
                                 </label>
                             </div>
                         </div>
-
+                        <div class="form-group">
+                            <label>启用文档历史</label>
+                            <div class="radio">
+                                <label class="radio-inline">
+                                    <input type="radio" {{if eq .ENABLE_DOCUMENT_HISTORY.OptionValue "true"}}checked{{end}} name="ENABLE_DOCUMENT_HISTORY" value="true">开启<span class="text"></span>
+                                </label>
+                                <label class="radio-inline">
+                                    <input type="radio" {{if eq .ENABLE_DOCUMENT_HISTORY.OptionValue "false"}}checked{{end}} name="ENABLE_DOCUMENT_HISTORY" value="false">关闭<span class="text"></span>
+                                </label>
+                            </div>
+                        </div>
                         <div class="form-group">
                             <button type="submit" id="btnSaveBookInfo" class="btn btn-success" data-loading-text="保存中...">保存修改</button>
                             <span id="form-error-message" class="error-message"></span>