1
0
Эх сурвалжийг харах

feat:实现自定义模板功能

lifei6671 7 жил өмнө
parent
commit
790b2aa611

+ 2 - 0
commands/command.go

@@ -91,9 +91,11 @@ func RegisterModel() {
 		new(models.Migration),
 		new(models.Label),
 		new(models.Blog),
+		new(models.Template),
 	)
 	gob.Register(models.Blog{})
 	gob.Register(models.Document{})
+	gob.Register(models.Template{})
 	//migrate.RegisterMigration()
 }
 

+ 22 - 11
controllers/BookController.go

@@ -197,6 +197,9 @@ func (c *BookController) SaveBook() {
 	bookResult.Description = description
 	bookResult.CommentStatus = commentStatus
 	bookResult.Label = tag
+
+	beego.Info("用户 [",c.Member.Account ,"] 修改了项目 ->",book)
+
 	c.JsonResult(0, "ok", bookResult)
 }
 
@@ -238,6 +241,7 @@ func (c *BookController) PrivatelyOwned() {
 		logs.Error("PrivatelyOwned => ", err)
 		c.JsonResult(6004, "保存失败")
 	}
+	beego.Info("用户 【",c.Member.Account,"]修改了项目权限 ->", state)
 	c.JsonResult(0, "ok")
 }
 
@@ -344,7 +348,7 @@ func (c *BookController) UploadCover() {
 	filePath = filepath.Join(conf.WorkingDirectory, "uploads", time.Now().Format("200601"), fileName+"_small"+ext)
 
 	//生成缩略图并保存到磁盘
-	err = graphics.ImageResizeSaveFile(subImg, 175, 230, filePath)
+	err = graphics.ImageResizeSaveFile(subImg, 350, 460, filePath)
 
 	if err != nil {
 		logs.Error("ImageResizeSaveFile => ", err.Error())
@@ -368,6 +372,7 @@ func (c *BookController) UploadCover() {
 	if oldCover != conf.GetDefaultCover() {
 		os.Remove("." + oldCover)
 	}
+	beego.Info("用户[",c.Member.Account,"]上传了项目封面 ->",book.BookName,book.BookId,book.Cover)
 
 	c.JsonResult(0, "ok", url)
 }
@@ -473,7 +478,7 @@ func (c *BookController) Create() {
 			}
 		}
 
-		if books, _ := book.FindByField("identify", identify,"book_id"); len(books) > 0 {
+		if books, _ := book.FindByField("identify", identify, "book_id"); len(books) > 0 {
 			c.JsonResult(6006, "项目标识已存在")
 		}
 
@@ -505,32 +510,34 @@ func (c *BookController) Create() {
 			beego.Error(err)
 		}
 
+		beego.Info("用户[",c.Member.Account,"]创建了项目 ->",book)
 		c.JsonResult(0, "ok", bookResult)
 	}
 	c.JsonResult(6001, "error")
 }
+
 //复制项目
-func (c *BookController) Copy(){
+func (c *BookController) Copy() {
 	if c.Ctx.Input.IsPost() {
 		//检查是否有复制项目的权限
-		if _,err := c.IsPermission(); err != nil{
-			c.JsonResult(500,err.Error())
+		if _, err := c.IsPermission(); err != nil {
+			c.JsonResult(500, err.Error())
 		}
 
 		identify := strings.TrimSpace(c.GetString("identify", ""))
 		if identify == "" {
-			c.JsonResult(6001,"参数错误")
+			c.JsonResult(6001, "参数错误")
 		}
 		book := models.NewBook()
 		err := book.Copy(identify)
 		if err != nil {
-			c.JsonResult(6002,"复制项目出错")
-		}else{
+			c.JsonResult(6002, "复制项目出错")
+		} else {
 			bookResult, err := models.NewBookResult().FindByIdentify(book.Identify, c.Member.MemberId)
 			if err != nil {
 				beego.Error("查询失败")
 			}
-			c.JsonResult(0,"ok",bookResult)
+			c.JsonResult(0, "ok", bookResult)
 		}
 	}
 }
@@ -572,7 +579,7 @@ func (c *BookController) Import() {
 		c.JsonResult(6004, "不支持的文件类型")
 	}
 
-	if books, _ := models.NewBook().FindByField("identify", identify,"book_id"); len(books) > 0 {
+	if books, _ := models.NewBook().FindByField("identify", identify, "book_id"); len(books) > 0 {
 		c.JsonResult(6006, "项目标识已存在")
 	}
 
@@ -602,9 +609,10 @@ func (c *BookController) Import() {
 	book.Editor = "markdown"
 	book.Theme = "default"
 
-
 	go book.ImportBook(tempPath)
 
+	beego.Info("用户[",c.Member.Account,"]导入了项目 ->",book)
+
 	c.JsonResult(0, "项目正在后台转换中,请稍后查看")
 }
 
@@ -640,6 +648,7 @@ func (c *BookController) CreateToken() {
 			logs.Error("生成阅读令牌失败 => ", err)
 			c.JsonResult(6003, "生成阅读令牌失败")
 		}
+		beego.Info("用户[",c.Member.Account,"]创建项目令牌 ->",book.PrivateToken)
 		c.JsonResult(0, "ok", conf.URLFor("DocumentController.Index", ":key", book.Identify, "token", book.PrivateToken))
 	} else {
 		book.PrivateToken = ""
@@ -647,6 +656,7 @@ func (c *BookController) CreateToken() {
 			logs.Error("CreateToken => ", err)
 			c.JsonResult(6004, "删除令牌失败")
 		}
+		beego.Info("用户[",c.Member.Account,"]创建项目令牌 ->",book.PrivateToken)
 		c.JsonResult(0, "ok", "")
 	}
 }
@@ -673,6 +683,7 @@ func (c *BookController) Delete() {
 		logs.Error("删除项目 => ", err)
 		c.JsonResult(6003, "删除失败")
 	}
+	beego.Info("用户[",c.Member.Account,"]删除了项目 ->",bookResult)
 	c.JsonResult(0, "ok")
 }
 

+ 12 - 9
controllers/DocumentController.go

@@ -30,6 +30,7 @@ import (
 	"fmt"
 	"github.com/lifei6671/mindoc/utils/filetil"
 	"github.com/lifei6671/mindoc/utils/gopool"
+	"github.com/astaxie/beego/logs"
 )
 
 // DocumentController struct
@@ -797,10 +798,12 @@ func (c *DocumentController) Content() {
 
 		//如果启用了自动发布
 		if autoRelease {
-			go func(identify string) {
-				models.NewBook().ReleaseContent(bookId)
-
-			}(identify)
+			go func() {
+				err := doc.ReleaseContent()
+				if err == nil {
+					logs.Informational("文档自动发布成功 -> document_id=%d;document_name=%s",doc.DocumentId, doc.DocumentName)
+				}
+			}()
 		}
 
 		c.JsonResult(0, "ok", doc)
@@ -1021,7 +1024,7 @@ func (c *DocumentController) History() {
 	if c.Member.IsAdministrator() {
 		book, err := models.NewBook().FindByFieldFirst("identify", identify)
 		if err != nil {
-			beego.Error("FindByIdentify => ", err)
+			beego.Error("查找项目失败 ->", err)
 			c.Data["ErrorMessage"] = "项目不存在或权限不足"
 			return
 		}
@@ -1031,7 +1034,7 @@ func (c *DocumentController) History() {
 	} else {
 		bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
 		if err != nil || bookResult.RoleId == conf.BookObserver {
-			beego.Error("FindByIdentify => ", err)
+			beego.Error("查找项目失败 ->", err)
 			c.Data["ErrorMessage"] = "项目不存在或权限不足"
 			return
 		}
@@ -1060,7 +1063,7 @@ func (c *DocumentController) History() {
 
 	historis, totalCount, err := models.NewDocumentHistory().FindToPager(docId, pageIndex, conf.PageSize)
 	if err != nil {
-		beego.Error("FindToPager => ", err)
+		beego.Error("分页查找文档历史失败 ->", err)
 		c.Data["ErrorMessage"] = "获取历史失败"
 		return
 	}
@@ -1094,7 +1097,7 @@ func (c *DocumentController) DeleteHistory() {
 	if c.Member.IsAdministrator() {
 		book, err := models.NewBook().FindByFieldFirst("identify", identify)
 		if err != nil {
-			beego.Error("FindByIdentify => ", err)
+			beego.Error("查找项目失败 ->", err)
 			c.JsonResult(6002, "项目不存在或权限不足")
 		}
 
@@ -1102,7 +1105,7 @@ func (c *DocumentController) DeleteHistory() {
 	} else {
 		bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
 		if err != nil || bookResult.RoleId == conf.BookObserver {
-			beego.Error("FindByIdentify => ", err)
+			beego.Error("查找项目失败 ->", err)
 			c.JsonResult(6002, "项目不存在或权限不足")
 		}
 

+ 125 - 0
controllers/TemplateController.go

@@ -0,0 +1,125 @@
+package controllers
+
+import (
+	"github.com/lifei6671/mindoc/models"
+	"github.com/astaxie/beego/orm"
+	"github.com/qiniu/x/errors.v7"
+	"strings"
+)
+
+type TemplateController struct {
+	BaseController
+	BookId int
+}
+
+func (c *TemplateController) isPermission() (error) {
+	c.Prepare()
+
+	if c.IsAjax() {
+		bookIdentify := c.GetString("identify", "")
+
+		if bookIdentify == "" {
+			return  errors.New("参数错误")
+		}
+
+		if !c.Member.IsAdministrator() {
+			book, err := models.NewBookResult().FindByIdentify(bookIdentify, c.Member.MemberId,"book_id")
+			if err != nil {
+				if err == orm.ErrNoRows {
+					return errors.New("项目不存在或没有权限")
+				}
+				return errors.New("查询项目模板失败")
+			}
+			c.BookId = book.BookId
+		}else{
+			book,err := models.NewBook().FindByIdentify(bookIdentify,"book_id")
+			if err != nil {
+				if err == orm.ErrNoRows {
+					return errors.New("项目不存在或没有权限")
+				}
+				return errors.New("查询项目模板失败")
+			}
+			c.BookId = book.BookId
+		}
+		return nil
+	}
+	return errors.New("请求方法不支持")
+}
+
+//获取模板列表
+func (c *TemplateController) List() {
+	if err := c.isPermission() ; err != nil {
+		c.JsonResult(500,err.Error())
+	}
+
+	templateList,err := models.NewTemplate().FindAllByBookId(c.BookId)
+
+	if err != nil {
+		if err == orm.ErrNoRows {
+			c.JsonResult(404,"没有模板")
+		}
+		c.JsonResult(500,"查询项目模板失败")
+	}
+	c.JsonResult(0,"OK",templateList)
+}
+
+func (c *TemplateController) Add() {
+	if err := c.isPermission() ; err != nil {
+		c.JsonResult(500,err.Error())
+	}
+
+	templateId, _ := c.GetInt("template_id",0)
+	content := c.GetString("content")
+	isGlobal,_ := c.GetInt("is_global",0)
+	templateName := c.GetString("template_name","")
+
+	if templateName == "" || strings.Count(templateName,"") > 300 {
+		c.JsonResult(500,"模板名称不能为空且必须小于300字")
+	}
+	template := models.NewTemplate()
+	template.TemplateId = templateId
+	template.BookId = c.BookId
+	template.TemplateContent = content
+	template.MemberId = c.Member.MemberId
+
+	if templateId > 0 {
+		template.ModifyAt = c.Member.MemberId
+	}
+	//只有管理员才能设置全局模板
+	if c.Member.IsAdministrator() {
+		template.IsGlobal = isGlobal
+	}else{
+		template.IsGlobal = 0
+	}
+
+	var cols []string
+
+	if templateId > 0 {
+		cols = []string{ "template_content", "modify_time","modify_at","version" }
+	}
+
+	if err := template.Save(cols...); err != nil {
+		c.JsonResult(500,"报错模板失败")
+	}
+	c.JsonResult(0,"OK",template)
+}
+
+func (c *TemplateController) Delete() {
+	if err := c.isPermission() ; err != nil {
+		c.JsonResult(500,err.Error())
+	}
+	templateId, _ := c.GetInt("template_id",0)
+
+	if c.Member.IsAdministrator() {
+		err := models.NewTemplate().Delete(templateId,0)
+		if err != nil {
+			c.JsonResult(500,"删除失败")
+		}
+	}else{
+		err := models.NewTemplate().Delete(templateId,c.Member.MemberId)
+		if err != nil {
+			c.JsonResult(500,"删除失败")
+		}
+	}
+	c.JsonResult(0,"OK")
+}

+ 14 - 57
models/BookModel.go

@@ -1,7 +1,6 @@
 package models
 
 import (
-	"bytes"
 	"crypto/md5"
 	"errors"
 	"fmt"
@@ -14,7 +13,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/PuerkitoBio/goquery"
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego/logs"
 	"github.com/astaxie/beego/orm"
@@ -24,6 +22,7 @@ import (
 	"github.com/lifei6671/mindoc/utils/requests"
 	"github.com/lifei6671/mindoc/utils/ziptil"
 	"gopkg.in/russross/blackfriday.v2"
+	"encoding/json"
 )
 
 // Book struct .
@@ -73,6 +72,15 @@ type Book struct {
 	IsUseFirstDocument int `orm:"column(is_use_first_document);type(int);default(0)" json:"is_use_first_document"`
 }
 
+func (b *Book) String()  string {
+	ret, err := json.Marshal(*b)
+
+	if err != nil {
+		return ""
+	}
+	return string(ret)
+}
+
 // TableName 获取对应数据库表名.
 func (book *Book) TableName() string {
 	return "books"
@@ -286,10 +294,10 @@ func (book *Book) FindByFieldFirst(field string, value interface{}) (*Book, erro
 }
 
 //根据项目标识查询项目
-func (book *Book) FindByIdentify(identify string) (*Book, error) {
+func (book *Book) FindByIdentify(identify string,cols ...string) (*Book, error) {
 	o := orm.NewOrm()
 
-	err := o.QueryTable(book.TableNameWithPrefix()).Filter("identify", identify).One(book)
+	err := o.QueryTable(book.TableNameWithPrefix()).Filter("identify", identify).One(book,cols...)
 
 	return book, err
 }
@@ -500,59 +508,8 @@ func (book *Book) ReleaseContent(bookId int) {
 		return
 	}
 	for _, item := range docs {
-		if item.Content != "" {
-			item.Release = item.Content
-			bufio := bytes.NewReader([]byte(item.Content))
-			//解析文档中非本站的链接,并设置为新窗口打开
-			if content, err := goquery.NewDocumentFromReader(bufio); err == nil {
-
-				content.Find("a").Each(func(i int, contentSelection *goquery.Selection) {
-					if src, ok := contentSelection.Attr("href"); ok {
-						if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
-							//beego.Info(src,conf.BaseUrl,strings.HasPrefix(src,conf.BaseUrl))
-							if conf.BaseUrl != "" && !strings.HasPrefix(src, conf.BaseUrl) {
-								contentSelection.SetAttr("target", "_blank")
-								if html, err := content.Html(); err == nil {
-									item.Release = html
-								}
-							}
-						}
-
-					}
-				})
-			}
-		}
-
-		attachList, err := NewAttachment().FindListByDocumentId(item.DocumentId)
-		if err == nil && len(attachList) > 0 {
-			content := bytes.NewBufferString("<div class=\"attach-list\"><strong>附件</strong><ul>")
-			for _, attach := range attachList {
-				if strings.HasPrefix(attach.HttpPath, "/") {
-					attach.HttpPath = strings.TrimSuffix(conf.BaseUrl, "/") + attach.HttpPath
-				}
-				li := fmt.Sprintf("<li><a href=\"%s\" target=\"_blank\" title=\"%s\">%s</a></li>", attach.HttpPath, attach.FileName, attach.FileName)
-
-				content.WriteString(li)
-			}
-			content.WriteString("</ul></div>")
-			item.Release += content.String()
-		}
-		_, err = o.Update(item, "release")
-		if err != nil {
-			beego.Error(fmt.Sprintf("发布失败 => %+v", item), err)
-		} else {
-			//当文档发布后,需要清除已缓存的转换文档和文档缓存
-			if doc, err := NewDocument().Find(item.DocumentId); err == nil {
-				doc.PutToCache()
-			} else {
-				doc.RemoveCache()
-			}
-
-			if err := os.RemoveAll(filepath.Join(conf.WorkingDirectory, "uploads", "books", strconv.Itoa(bookId))); err != nil {
-				beego.Error("删除已缓存的文档目录失败 => ",filepath.Join(conf.WorkingDirectory, "uploads", "books", strconv.Itoa(bookId)))
-			}
-
-		}
+		item.BookId = bookId
+		item.ReleaseContent()
 	}
 }
 

+ 12 - 2
models/BookResult.go

@@ -23,6 +23,7 @@ import (
 	"github.com/lifei6671/mindoc/utils/requests"
 	"github.com/lifei6671/mindoc/utils/gopool"
 	"net/http"
+	"encoding/json"
 )
 
 var(
@@ -69,8 +70,17 @@ func NewBookResult() *BookResult {
 	return &BookResult{}
 }
 
+func (b *BookResult) String() string {
+	ret, err := json.Marshal(*b)
+
+	if err != nil {
+		return ""
+	}
+	return string(ret)
+}
+
 // 根据项目标识查询项目以及指定用户权限的信息.
-func (m *BookResult) FindByIdentify(identify string, memberId int) (*BookResult, error) {
+func (m *BookResult) FindByIdentify(identify string, memberId int,cols ...string) (*BookResult, error) {
 	if identify == "" || memberId <= 0 {
 		return m, ErrInvalidParameter
 	}
@@ -78,7 +88,7 @@ func (m *BookResult) FindByIdentify(identify string, memberId int) (*BookResult,
 
 	book := NewBook()
 
-	err := o.QueryTable(book.TableNameWithPrefix()).Filter("identify", identify).One(book)
+	err := o.QueryTable(book.TableNameWithPrefix()).Filter("identify", identify).One(book,cols...)
 
 	if err != nil {
 		return m, err

+ 69 - 0
models/DocumentModel.go

@@ -10,6 +10,11 @@ import (
 	"github.com/astaxie/beego/orm"
 	"github.com/lifei6671/mindoc/cache"
 	"github.com/lifei6671/mindoc/conf"
+	"github.com/PuerkitoBio/goquery"
+	"strings"
+	"bytes"
+	"os"
+	"path/filepath"
 )
 
 // Document struct.
@@ -228,3 +233,67 @@ func (m *Document) IsExist(documentId int) bool {
 
 	return o.QueryTable(m.TableNameWithPrefix()).Filter("document_id", documentId).Exist()
 }
+
+//发布单篇文档
+func (item *Document) ReleaseContent() error {
+
+	o := orm.NewOrm()
+
+	bookId := item.BookId
+
+	if item.Content != "" {
+		item.Release = item.Content
+		bufio := bytes.NewReader([]byte(item.Content))
+		//解析文档中非本站的链接,并设置为新窗口打开
+		if content, err := goquery.NewDocumentFromReader(bufio); err == nil {
+
+			content.Find("a").Each(func(i int, contentSelection *goquery.Selection) {
+				if src, ok := contentSelection.Attr("href"); ok {
+					if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
+						//beego.Info(src,conf.BaseUrl,strings.HasPrefix(src,conf.BaseUrl))
+						if conf.BaseUrl != "" && !strings.HasPrefix(src, conf.BaseUrl) {
+							contentSelection.SetAttr("target", "_blank")
+							if html, err := content.Html(); err == nil {
+								item.Release = html
+							}
+						}
+					}
+
+				}
+			})
+		}
+	}
+
+	attachList, err := NewAttachment().FindListByDocumentId(item.DocumentId)
+	if err == nil && len(attachList) > 0 {
+		content := bytes.NewBufferString("<div class=\"attach-list\"><strong>附件</strong><ul>")
+		for _, attach := range attachList {
+			if strings.HasPrefix(attach.HttpPath, "/") {
+				attach.HttpPath = strings.TrimSuffix(conf.BaseUrl, "/") + attach.HttpPath
+			}
+			li := fmt.Sprintf("<li><a href=\"%s\" target=\"_blank\" title=\"%s\">%s</a></li>", attach.HttpPath, attach.FileName, attach.FileName)
+
+			content.WriteString(li)
+		}
+		content.WriteString("</ul></div>")
+		item.Release += content.String()
+	}
+	_, err = o.Update(item, "release")
+	if err != nil {
+		beego.Error(fmt.Sprintf("发布失败 => %+v", item), err)
+		return err
+	} else {
+		//当文档发布后,需要清除已缓存的转换文档和文档缓存
+		if doc, err := NewDocument().Find(item.DocumentId); err == nil {
+			doc.PutToCache()
+		} else {
+			doc.RemoveCache()
+		}
+
+		if err := os.RemoveAll(filepath.Join(conf.WorkingDirectory, "uploads", "books", strconv.Itoa(bookId))); err != nil {
+			beego.Error("删除已缓存的文档目录失败 => ",filepath.Join(conf.WorkingDirectory, "uploads", "books", strconv.Itoa(bookId)))
+			return err
+		}
+	}
+	return nil
+}

+ 152 - 0
models/Template.go

@@ -0,0 +1,152 @@
+package models
+
+import (
+	"time"
+	"github.com/lifei6671/mindoc/conf"
+	"github.com/astaxie/beego/orm"
+	"github.com/astaxie/beego/logs"
+	"github.com/astaxie/beego"
+	"errors"
+)
+
+type Template struct {
+	TemplateId	int 		`orm:"column(template_id);pk;auto;unique;" json:"template_id"`
+	TemplateName string 	`orm:"column(template_name);size(500);" json:"template_name"`
+	MemberId int 			`orm:"column(member_id);index" json:"member_id"`
+	BookId int				`orm:"column(book_id);index" json:"book_id"`
+	//是否是全局模板:0 否/1 是; 全局模板在所有项目中都可以使用;否则只能在创建模板的项目中使用
+	IsGlobal int			`orm:"column(is_global);default(0)" json:"is_global"`
+	TemplateContent string 	`orm:"column(template_content);type(text);null" json:"template_content"`
+	CreateTime time.Time     `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
+	ModifyTime time.Time     `orm:"column(modify_time);type(datetime);auto_now" json:"modify_time"`
+	ModifyAt   int           `orm:"column(modify_at);type(int)" json:"-"`
+	Version    int64         `orm:"type(bigint);column(version)" json:"version"`
+}
+
+// TableName 获取对应数据库表名.
+func (m *Template) TableName() string {
+	return "templates"
+}
+
+// TableEngine 获取数据使用的引擎.
+func (m *Template) TableEngine() string {
+	return "INNODB"
+}
+
+func (m *Template) TableNameWithPrefix() string {
+	return conf.GetDatabasePrefix() + m.TableName()
+}
+
+func NewTemplate() *Template  {
+	return &Template{}
+}
+
+//查询指定ID的模板
+func (t *Template) Find(templateId int) (*Template,error) {
+	if templateId <= 0 {
+		return t, ErrInvalidParameter
+	}
+
+	o := orm.NewOrm()
+
+	err := o.QueryTable(t.TableNameWithPrefix()).Filter("template_id",templateId).One(t)
+
+	if err != nil {
+		logs.Error("查询模板时失败 ->%s",err)
+	}
+	return t,err
+}
+
+//查询属于指定项目的模板.
+func (t *Template) FindByBookId(bookId int) ([]*Template,error) {
+	if bookId <= 0 {
+		return nil,ErrInvalidParameter
+	}
+	o := orm.NewOrm()
+
+	var templateList []*Template
+
+	_,err := o.QueryTable(t.TableNameWithPrefix()).Filter("book_id",bookId).OrderBy("-template_id").All(&templateList)
+
+	if err != nil {
+		beego.Error("查询模板列表失败 ->",err)
+	}
+	return templateList,err
+}
+
+//查询指定项目所有可用模板列表.
+func (t *Template) FindAllByBookId(bookId int) ([]*Template,error) {
+	if bookId <= 0 {
+		return nil,ErrInvalidParameter
+	}
+	o := orm.NewOrm()
+
+	cond := orm.NewCondition()
+
+	cond1 := cond.And("book_id",bookId).Or("is_global",1)
+
+	qs := o.QueryTable(t.TableNameWithPrefix())
+
+	var templateList []*Template
+
+	_,err := qs.SetCond(cond1).OrderBy("-template_id").All(&templateList)
+
+	if err != nil {
+		beego.Error("查询模板列表失败 ->",err)
+	}
+	return templateList,err
+}
+//删除一个模板
+func (t *Template) Delete(templateId int,memberId int) error {
+	if templateId <= 0 {
+		return ErrInvalidParameter
+	}
+
+	o := orm.NewOrm()
+
+	qs := o.QueryTable(t.TableNameWithPrefix()).Filter("template_id",templateId)
+
+	if memberId > 0 {
+		qs = qs.Filter("member_id",memberId)
+	}
+	_,err := qs.Delete()
+
+	if err != nil {
+		beego.Error("删除模板失败 ->",err)
+	}
+	return err
+}
+
+//添加或更新模板
+func (t *Template) Save(cols ...string) (err error) {
+
+	if t.BookId <= 0 {
+		return ErrInvalidParameter
+	}
+	o := orm.NewOrm()
+
+	if !o.QueryTable(NewBook()).Filter("book_id",t.BookId).Exist() {
+		return errors.New("项目不存在")
+	}
+	if !o.QueryTable(NewMember()).Filter("member_id",t.MemberId).Filter("status",0).Exist() {
+		return errors.New("用户已被禁用")
+	}
+	if t.TemplateId > 0 {
+		t.Version = time.Now().Unix()
+		t.ModifyTime = time.Now()
+		_,err = o.Update(t,cols...)
+	}else{
+		t.CreateTime = time.Now()
+		_,err = o.Insert(t)
+	}
+
+	return
+}
+
+
+
+
+
+
+
+

+ 5 - 0
routers/router.go

@@ -77,6 +77,11 @@ func init() {
 	beego.Router("/blog-attach/:id:int/:attach_id:int", &controllers.BlogController{},"get:Download")
 	beego.Router("/blog-:id([0-9]+).html",&controllers.BlogController{}, "*:Index")
 
+	//模板相关接口
+	beego.Router("/api/template/list", &controllers.TemplateController{},"post:List")
+	beego.Router("/api/template/add", &controllers.TemplateController{},"post:Add")
+	beego.Router("/api/template/remove", &controllers.TemplateController{},"post:Delete")
+
 	beego.Router("/api/attach/remove/", &controllers.DocumentController{}, "post:RemoveAttachment")
 	beego.Router("/api/:key/edit/?:id", &controllers.DocumentController{}, "*:Edit")
 	beego.Router("/api/upload", &controllers.DocumentController{}, "post:Upload")

+ 4 - 4
static/css/kancloud.css

@@ -442,14 +442,14 @@ table>tbody>tr:hover{
 
 .manual-article .article-head h1 {
     margin: 0;
-    font-size: 20px;
-    font-weight: 300;
+    font-size: 36px;
+    font-weight: 400;
     text-align: center;
-    line-height: 30px;
+    line-height: 42px;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
-    color: #7e888b
+    color: #636363;
 }
 .manual-article .article-head h3 {
     margin: 0;

+ 34 - 2
static/js/markdown.js

@@ -101,8 +101,8 @@ $(function () {
        } else if (name === "release") {
             if (Object.prototype.toString.call(window.documentCategory) === '[object Array]' && window.documentCategory.length > 0) {
                 if ($("#markdown-save").hasClass('change')) {
-                    var comfirm_result = confirm("编辑内容未保存,需要保存吗?")
-                    if (comfirm_result) {
+                    var confirm_result = confirm("编辑内容未保存,需要保存吗?");
+                    if (confirm_result) {
                         saveDocument(false, releaseBook);
                         return;
                     }
@@ -366,6 +366,10 @@ $(function () {
      */
     $("#documentTemplateModal").on("click", ".section>a[data-type]", function () {
         var $this = $(this).attr("data-type");
+        if($this === "customs"){
+            $("#displayCustomsTemplateModal").modal("show");
+            return;
+        }
         var body = $("#template-" + $this).html();
         if (body) {
             window.isLoad = true;
@@ -376,4 +380,32 @@ $(function () {
         }
         $("#documentTemplateModal").modal('hide');
     });
+
+    $("#displayCustomsTemplateModal").on("show.bs.modal",function () {
+        window.sessionStorage.setItem("displayCustomsTemplateList",$("#displayCustomsTemplateList").html());
+
+        var index ;
+        $.ajax({
+            beforeSend: function () {
+                index = layer.load(1, { shade: [0.1, '#fff'] });
+            },
+           url : window.template.listUrl,
+           data: {"identify":window.book.identify},
+           type: "POST",
+           dataType: "json",
+            success: function ($res) {
+
+            },
+            error : function () {
+
+            },
+            complete : function () {
+                layer.close(index);
+            }
+        });
+        $("#documentTemplateModal").modal("hide");
+    }).on("hidden.bs.modal",function () {
+        var cache = window.sessionStorage.getItem("displayCustomsTemplateList");
+        $("#displayCustomsTemplateList").html(cache);
+    });
 });

+ 60 - 1
views/document/markdown_edit_template.tpl

@@ -23,6 +23,7 @@
         window.historyURL = "{{urlfor "DocumentController.History"}}";
         window.removeAttachURL = "{{urlfor "DocumentController.RemoveAttachment"}}";
         window.highlightStyle = "{{.HighlightStyle}}";
+        window.template = { "listUrl" : "{{urlfor "TemplateController.List"}}", "deleteUrl" : "{{urlfor "TemplateController.Delete"}}", "saveUrl" :"{{urlfor "TemplateController.Add"}}"}
     </script>
     <!-- Bootstrap -->
     <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
@@ -231,7 +232,7 @@
 </div>
 
 <div class="modal fade" id="documentTemplateModal" tabindex="-1" role="dialog" aria-labelledby="请选择模板类型" aria-hidden="true">
-    <div class="modal-dialog">
+    <div class="modal-dialog" style="width: 780px;">
         <div class="modal-content">
             <div class="modal-header">
                 <h4 class="modal-title" id="modal-title">请选择模板类型</h4>
@@ -263,6 +264,16 @@
                             <li>表格支持</li>
                         </ul>
                     </div>
+                    <div class="section">
+                        <a data-type="customs" href="javascript:;"><i class="fa fa-book"></i></a>
+
+                        <h3><a data-type="customs" href="javascript:;">自定义模板</a></h3>
+                        <ul>
+                            <li>可将文档保存为自定义模板</li>
+                            <li>支持任意类型文档</li>
+                            <li>可以设置为全局模板</li>
+                        </ul>
+                    </div>
                 </div>
 
             </div>
@@ -272,6 +283,54 @@
         </div>
     </div>
 </div>
+
+<div class="modal fade" id="displayCustomsTemplateModal" tabindex="-1" role="dialog" aria-labelledby="displayCustomsTemplateModalLabel">
+    <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">&times;</span></button>
+                <h4 class="modal-title">自定义模板</h4>
+            </div>
+            <div class="modal-body text-center" id="displayCustomsTemplateList">
+                <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_format $item.ModifyTime "2006-01-02 15:04:05"}}</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>
+                            </td>
+                        </tr>
+                        {{else}}
+                        <tr>
+                            <td colspan="6" class="text-center">暂无数据</td>
+                        </tr>
+                        {{end}}
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
 <template id="template-normal">
 {{template "document/template_normal.tpl"}}
 </template>