mirror of https://github.com/mindoc-org/mindoc.git
实现文档历史
parent
79a464352e
commit
056edb71fc
|
@ -18,6 +18,7 @@ type BaseController struct {
|
|||
Member *models.Member
|
||||
Option map[string]string
|
||||
EnableAnonymous bool
|
||||
EnableDocumentHistory bool
|
||||
}
|
||||
|
||||
// Prepare 预处理.
|
||||
|
@ -25,7 +26,7 @@ func (c *BaseController) Prepare (){
|
|||
c.Data["SiteName"] = "MinDoc"
|
||||
c.Data["Member"] = models.Member{}
|
||||
c.EnableAnonymous = false
|
||||
|
||||
c.EnableDocumentHistory = false
|
||||
|
||||
if member,ok := c.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0{
|
||||
c.Member = &member
|
||||
|
@ -45,6 +46,9 @@ func (c *BaseController) Prepare (){
|
|||
if strings.EqualFold(item.OptionName,"ENABLE_ANONYMOUS") && item.OptionValue == "true" {
|
||||
c.EnableAnonymous = true
|
||||
}
|
||||
if strings.EqualFold(item.OptionName,"ENABLE_DOCUMENT_HISTORY") && item.OptionValue == "true" {
|
||||
c.EnableDocumentHistory = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/lifei6671/godoc/conf"
|
||||
"github.com/lifei6671/godoc/models"
|
||||
"github.com/lifei6671/godoc/utils/wkhtmltopdf"
|
||||
"github.com/lifei6671/godoc/utils"
|
||||
)
|
||||
|
||||
//DocumentController struct.
|
||||
|
@ -674,6 +675,18 @@ func (c *DocumentController) Content() {
|
|||
beego.Info("%d|", version, doc.Version)
|
||||
c.JsonResult(6005, "文档已被修改确定要覆盖吗?")
|
||||
}
|
||||
history := models.NewDocumentHistory()
|
||||
history.DocumentId = doc_id
|
||||
history.Content = doc.Content
|
||||
history.Markdown = doc.Markdown
|
||||
history.DocumentName = doc.DocumentName
|
||||
history.ModifyAt = c.Member.MemberId
|
||||
history.MemberId = doc.MemberId
|
||||
history.ParentId = doc.ParentId
|
||||
history.Version = time.Now().Unix()
|
||||
history.Action = "modify"
|
||||
history.ActionName = "修改文档"
|
||||
|
||||
if markdown == "" && content != "" {
|
||||
doc.Markdown = content
|
||||
} else {
|
||||
|
@ -685,6 +698,13 @@ func (c *DocumentController) Content() {
|
|||
beego.Error("InsertOrUpdate => ", err)
|
||||
c.JsonResult(6006, "保存失败")
|
||||
}
|
||||
//如果启用了文档历史,则添加历史文档
|
||||
if c.EnableDocumentHistory {
|
||||
_,err = history.InsertOrUpdate()
|
||||
if err != nil {
|
||||
beego.Error("DocumentHistory InsertOrUpdate => ",err)
|
||||
}
|
||||
}
|
||||
|
||||
c.JsonResult(0, "ok", doc)
|
||||
}
|
||||
|
@ -868,6 +888,69 @@ func (c *DocumentController) Search() {
|
|||
c.JsonResult(0,"ok",docs)
|
||||
}
|
||||
|
||||
//文档历史列表.
|
||||
func (c *DocumentController) History() {
|
||||
c.Prepare()
|
||||
|
||||
identify := c.GetString("identify")
|
||||
doc_id, err := c.GetInt("doc_id", 0)
|
||||
pageIndex, _ := c.GetInt("page", 1)
|
||||
|
||||
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(6003, "获取历史失败")
|
||||
}
|
||||
//如果文档所属项目错误
|
||||
if doc.BookId != book_id {
|
||||
c.JsonResult(6004, "参数错误")
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
data.List = historis
|
||||
if totalCount > 0 {
|
||||
html := utils.GetPagerHtml(c.Ctx.Request.RequestURI, pageIndex, conf.PageSize, totalCount)
|
||||
|
||||
data.PageHtml = string(html)
|
||||
}else {
|
||||
data.PageHtml = ""
|
||||
}
|
||||
|
||||
c.JsonResult(0,"ok",data)
|
||||
}
|
||||
|
||||
//递归生成文档序列数组.
|
||||
func RecursiveFun(parent_id int, prefix, dpath string, c *DocumentController, book *models.BookResult, docs []*models.Document, paths *list.List) {
|
||||
for _, item := range docs {
|
||||
|
|
|
@ -98,6 +98,7 @@ func (m *Document) RecursiveDocument(doc_id int) error {
|
|||
|
||||
if doc, err := m.Find(doc_id); err == nil {
|
||||
o.Delete(doc)
|
||||
NewDocumentHistory().Clear(doc.DocumentId)
|
||||
}
|
||||
|
||||
var docs []*Document
|
||||
|
|
|
@ -2,12 +2,15 @@ package models
|
|||
|
||||
import (
|
||||
"time"
|
||||
"github.com/lifei6671/godoc/conf"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/lifei6671/godoc/conf"
|
||||
)
|
||||
|
||||
type DocumentHistory struct {
|
||||
HistoryId int `orm:"column(history_id);pk;auto;unique" json:"history_id"`
|
||||
Action string `orm:"column(action);size(255)" json:"action"`
|
||||
ActionName string `orm:"column(action_name);size(255)" json:"action_name"`
|
||||
DocumentId int `orm:"column(document_id);type(int);index" json:"doc_id"`
|
||||
DocumentName string `orm:"column(document_name);size(500)" json:"doc_name"`
|
||||
ParentId int `orm:"column(parent_id);type(int);index;default(0)" json:"parent_id"`
|
||||
|
@ -19,6 +22,17 @@ type DocumentHistory struct {
|
|||
Version int64 `orm:"type(bigint);column(version)" json:"version"`
|
||||
}
|
||||
|
||||
type DocumentHistorySimpleResult struct {
|
||||
HistoryId int `json:"history_id"`
|
||||
ActionName string `json:"action_name"`
|
||||
MemberId int `json:"member_id"`
|
||||
Account string `json:"account"`
|
||||
ModifyAt int `json:"modify_at"`
|
||||
ModifyName string `json:"modify_name"`
|
||||
ModifyTime time.Time `json:"modify_time"`
|
||||
Version int64 `json:"version"`
|
||||
}
|
||||
|
||||
// TableName 获取对应数据库表名.
|
||||
func (m *DocumentHistory) TableName() string {
|
||||
return "document_history"
|
||||
|
@ -33,21 +47,83 @@ func (m *DocumentHistory) TableNameWithPrefix() string {
|
|||
return conf.GetDatabasePrefix() + m.TableName()
|
||||
}
|
||||
|
||||
func NewDocumentHistory() *DocumentHistory {
|
||||
return &DocumentHistory{}
|
||||
}
|
||||
//清空指定文档的历史.
|
||||
func (m *DocumentHistory) Clear(doc_id int) error {
|
||||
o := orm.NewOrm()
|
||||
|
||||
func (m *DocumentHistory) FindToPager(doc_id,page_index,page_size int) (docs []*DocumentHistory,totalCount int,err error) {
|
||||
_, err := o.Raw("DELETE md_document_history WHERE document_id = ?", doc_id).Exec()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
//删除历史.
|
||||
func (m *DocumentHistory) Delete(history_id int) error {
|
||||
o := orm.NewOrm()
|
||||
|
||||
_, err := o.Raw("DELETE md_document_history WHERE history_id = ?", history_id).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
//恢复指定历史的文档.
|
||||
func (m *DocumentHistory) Restore(history_id int) error {
|
||||
o := orm.NewOrm()
|
||||
|
||||
err := o.QueryTable(m.TableNameWithPrefix()).Filter("history_id", history_id).One(m)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
doc, err := NewDocument().Find(m.DocumentId)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
doc.DocumentName = m.DocumentName
|
||||
doc.Content = m.Content
|
||||
doc.Markdown = m.Markdown
|
||||
doc.Release = m.Content
|
||||
|
||||
_, err = o.Update(doc)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *DocumentHistory) InsertOrUpdate() (history *DocumentHistory,err error) {
|
||||
o := orm.NewOrm()
|
||||
history = m
|
||||
|
||||
if m.HistoryId > 0 {
|
||||
_,err = o.Update(m)
|
||||
}else{
|
||||
_,err = o.Insert(m)
|
||||
}
|
||||
return
|
||||
}
|
||||
//分页查询指定文档的历史.
|
||||
func (m *DocumentHistory) FindToPager(doc_id, page_index, page_size int) (docs []*DocumentHistorySimpleResult, totalCount int, err error) {
|
||||
|
||||
o := orm.NewOrm()
|
||||
|
||||
offset := (page_index - 1) * page_size
|
||||
|
||||
totalCount = 0
|
||||
_,err = o.QueryTable(m.TableNameWithPrefix()).Filter("document_id",doc_id).Offset(offset).Limit(page_size).All(docs)
|
||||
|
||||
sql := `SELECT history.*,m1.account,m2.account as ModifyName
|
||||
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
|
||||
WHERE history.document_id = ? ORDER BY history.history_id DESC LIMIT ?,?;`
|
||||
|
||||
_, err = o.Raw(sql,doc_id,offset,page_size).QueryRows(&docs)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var count int64
|
||||
count,err = o.QueryTable(m.TableNameWithPrefix()).Filter("document_id",doc_id).Count()
|
||||
count, err = o.QueryTable(m.TableNameWithPrefix()).Filter("document_id", doc_id).Count()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -55,4 +131,4 @@ func (m *DocumentHistory) FindToPager(doc_id,page_index,page_size int) (docs []*
|
|||
totalCount = int(count)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ 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("/docs/:key", &controllers.DocumentController{},"*:Index")
|
||||
|
|
|
@ -199,3 +199,31 @@ function showSuccess($msg,$id) {
|
|||
$($id).addClass("success-message").removeClass("error-message").text($msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$("#documentHistoryModal").on("shown.bs.modal",function () {
|
||||
var historyVue = new Vue({
|
||||
el : "#documentHistoryModal",
|
||||
data : {
|
||||
lists : []
|
||||
},
|
||||
delimiters : ['${','}'],
|
||||
methods : {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
$.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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -61,7 +61,7 @@ $(function () {
|
|||
if(name === "attachment"){
|
||||
$("#uploadAttachModal").modal("show");
|
||||
}else if(name === "history"){
|
||||
|
||||
$("#documentHistoryModal").modal("show");
|
||||
}else if(name === "save"){
|
||||
saveDocument(false);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
window.editURL = "{{urlfor "DocumentController.Content" ":key" .Model.Identify ":id" ""}}";
|
||||
window.releaseURL = "{{urlfor "BookController.Release" ":key" .Model.Identify}}";
|
||||
window.sortURL = "{{urlfor "BookController.SaveSort" ":key" .Model.Identify}}";
|
||||
window.historyURL = "{{urlfor "DocumentController.History"}}";
|
||||
</script>
|
||||
<!-- Bootstrap -->
|
||||
<link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
|
||||
|
@ -80,7 +81,7 @@
|
|||
|
||||
<div class="editormd-group pull-right">
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="关闭实时预览"><i class="fa fa-eye-slash first" name="watch" unselectable="on"></i></a>
|
||||
{{/*<a href="javascript:;" data-toggle="tooltip" data-title="修改历史"><i class="fa fa-history item" name="history" aria-hidden="true"></i></a>*/}}
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="修改历史"><i class="fa fa-history item" name="history" aria-hidden="true"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="边栏"><i class="fa fa-columns item" aria-hidden="true" name="sidebar"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="使用帮助"><i class="fa fa-question-circle-o last" aria-hidden="true" name="help"></i></a>
|
||||
</div>
|
||||
|
@ -205,6 +206,46 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="documentHistoryModal" tabindex="-1" role="dialog" aria-labelledby="documentHistoryModalModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</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>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
|
||||
<script src="{{cdnjs "/static/vuejs/vue.min.js"}}" type="text/javascript"></script>
|
||||
|
@ -212,7 +253,7 @@
|
|||
<script src="{{cdnjs "/static/webuploader/webuploader.min.js"}}" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/jstree/3.3.4/jstree.min.js"}}" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/editor.md/editormd.js"}}" type="text/javascript"></script>
|
||||
<script type="text/javascript" src="{{cdnjs "/static/layer/layer.js"}}"></script>
|
||||
<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({
|
||||
|
|
Loading…
Reference in New Issue