Browse Source

1、实现文章列表、设置、编辑功能
2、实现文章附件功能

lifei6671 7 năm trước cách đây
mục cha
commit
86637ef581

+ 386 - 3
controllers/BlogController.go

@@ -3,6 +3,16 @@ package controllers
 import (
 	"strings"
 	"github.com/lifei6671/mindoc/models"
+	"time"
+	"github.com/astaxie/beego"
+	"github.com/lifei6671/mindoc/conf"
+	"github.com/lifei6671/mindoc/utils/pagination"
+	"strconv"
+	"fmt"
+	"os"
+	"net/http"
+	"path/filepath"
+	"github.com/astaxie/beego/orm"
 )
 
 type BlogController struct{
@@ -25,6 +35,24 @@ func (c *BlogController) List() {
 func (c *BlogController) ManageList() {
 	c.Prepare()
 	c.TplName = "blog/manage_list.tpl"
+
+	pageIndex, _ := c.GetInt("page", 1)
+
+	blogList,totalCount,err := models.NewBlog().FindToPager(pageIndex,conf.PageSize,c.Member.MemberId,"")
+
+	beego.Info(totalCount)
+	if err != nil {
+		c.ShowErrorPage(500,err.Error())
+	}
+	if totalCount > 0 {
+		pager := pagination.NewPagination(c.Ctx.Request, totalCount, conf.PageSize, c.BaseUrl())
+		c.Data["PageHtml"] = pager.HtmlPages()
+	} else {
+		c.Data["PageHtml"] = ""
+	}
+
+	c.Data["ModelList"] = blogList
+
 }
 
 //文章设置
@@ -39,10 +67,21 @@ func (c *BlogController) ManageSetting() {
 		orderIndex,_ := c.GetInt("order_index",0)
 		blogType,_ := c.GetInt("blog_type",0)
 		documentId,_ := c.GetInt("document_id",0)
-
+		blogExcerpt := c.GetString("excerpt","")
+		blogStatus := c.GetString("status","publish")
+		blogPassword := c.GetString("password","")
 		if blogTitle == "" {
 			c.JsonResult(6001,"文章标题不能为空")
 		}
+		if strings.Count(blogExcerpt,"") > 100 {
+			c.JsonResult(6008,"文章摘要必须小于500字符")
+		}
+		if blogStatus != "public" && blogStatus != "password" && blogStatus != "draft" {
+			blogStatus = "public"
+		}
+		if blogStatus == "password" && blogPassword == "" {
+			c.JsonResult(6010,"加密文章请设置密码")
+		}
 		if blogType != 0 && blogType != 1 {
 			c.JsonResult(6005,"未知的文章类型")
 		}else if documentId <= 0 && blogType == 1 {
@@ -73,6 +112,8 @@ func (c *BlogController) ManageSetting() {
 					c.JsonResult(6004,"文章标识已存在")
 				}
 			}
+			blog.Modified = time.Now()
+			blog.ModifyAt = c.Member.MemberId
 		}else{
 			//如果设置了文章标识
 			if blogIdentify != "" {
@@ -83,17 +124,47 @@ func (c *BlogController) ManageSetting() {
 
 			blog = models.NewBlog()
 			blog.MemberId = c.Member.MemberId
+			blog.Created = time.Now()
+		}
+		if blogIdentify == "" {
+			blog.BlogIdentify = fmt.Sprintf("%s-%d","post",time.Now().UnixNano())
+		}else{
+			blog.BlogIdentify = blogIdentify
 		}
-
 
 		blog.BlogTitle = blogTitle
-		blog.BlogIdentify = blogIdentify
+
 		blog.OrderIndex = orderIndex
 		blog.BlogType = blogType
 		if blogType == 1 {
 			blog.DocumentId = documentId
 		}
+		blog.BlogExcerpt = blogExcerpt
+		blog.BlogStatus = blogStatus
+		blog.Password = blogPassword
 
+		if err := blog.Save();err != nil {
+			beego.Error("保存文章失败 -> ",err)
+			c.JsonResult(6011,"保存文章失败")
+		}else{
+			c.JsonResult(0,"ok",blog)
+		}
+	}
+	if c.Ctx.Input.Referer() == "" {
+		c.Data["Referer"] = "javascript:history.back();"
+	}else{
+		c.Data["Referer"] = c.Ctx.Input.Referer()
+	}
+	blogId,err := strconv.Atoi(c.Ctx.Input.Param(":id"))
+
+	if err == nil {
+		blog,err := models.NewBlog().FindByIdAndMemberId(blogId,c.Member.MemberId)
+		if err != nil {
+			c.ShowErrorPage(500,err.Error())
+		}
+		c.Data["Model"] = blog
+	}else{
+		c.Data["Model"] = models.NewBlog()
 	}
 }
 
@@ -101,4 +172,316 @@ func (c *BlogController) ManageSetting() {
 func (c *BlogController) ManageEdit() {
 	c.Prepare()
 	c.TplName = "blog/manage_edit.tpl"
+
+	if c.Ctx.Input.IsPost() {
+		blogId,_ := c.GetInt("blogId",0)
+
+		if blogId <= 0 {
+			c.JsonResult(6001,"文章参数错误")
+		}
+		blogContent := c.GetString("content","")
+		blogHtml := c.GetString("htmlContent","")
+		version,_ := c.GetInt64("version",0)
+		cover := c.GetString("cover")
+
+		var blog *models.Blog
+		var err error
+
+		if c.Member.IsAdministrator() {
+			blog,err = models.NewBlog().Find(blogId)
+		}else{
+			blog,err = models.NewBlog().FindByIdAndMemberId(blogId,c.Member.MemberId)
+		}
+		if err != nil {
+			beego.Error("查询文章失败 ->",err)
+			c.JsonResult(6002,"查询文章失败")
+		}
+		if version > 0 && blog.Version != version && cover != "yes"{
+			c.JsonResult(6005,"文章已被修改")
+		}
+		if blog.BlogType == 1 {
+			doc,err := models.NewDocument().Find(blog.DocumentId)
+			if err != nil {
+				beego.Error("查询关联项目文档时出错 ->", err)
+				c.JsonResult(6003,"查询关联项目文档时出错")
+			}
+			doc.Markdown = blogContent
+			doc.Release = blogHtml
+			doc.Content = blogHtml
+			doc.ModifyTime = time.Now()
+			doc.ModifyAt = c.Member.MemberId
+			if err := doc.InsertOrUpdate("markdown","release","content","modify_time","modify_at");err != nil {
+				beego.Error("保存关联文档时出错 ->",err)
+				c.JsonResult(6004,"保存关联文档时出错")
+			}
+		}
+
+		blog.BlogContent = blogContent
+		blog.BlogRelease = blogHtml
+		blog.ModifyAt = c.Member.MemberId
+		blog.Modified = time.Now()
+
+
+		if err := blog.Save("blog_content","blog_release","modify_at","modify_time","version");err != nil {
+			beego.Error("保存文章失败 -> ",err)
+			c.JsonResult(6011,"保存文章失败")
+		}else{
+			c.JsonResult(0,"ok",blog)
+		}
+	}
+
+	blogId,_ := strconv.Atoi(c.Ctx.Input.Param(":id"))
+
+	if blogId <= 0 {
+		c.ShowErrorPage(500,"参数错误")
+	}
+	var blog *models.Blog
+	var err error
+
+	if c.Member.IsAdministrator() {
+		blog,err = models.NewBlog().Find(blogId)
+	}else{
+		blog,err = models.NewBlog().FindByIdAndMemberId(blogId,c.Member.MemberId)
+	}
+	if err != nil {
+		c.ShowErrorPage(404,"文章不存在或已删除")
+	}
+	c.Data["Model"] = blog
+}
+
+//删除文章
+func (c *BlogController) ManageDelete()  {
+	c.Prepare()
+	blogId,_ := c.GetInt("blog_id",0)
+
+	if blogId <= 0 {
+		c.JsonResult(6001,"参数错误")
+	}
+	var blog *models.Blog
+	var err error
+
+	if c.Member.IsAdministrator() {
+		blog,err = models.NewBlog().Find(blogId)
+	}else{
+		blog,err = models.NewBlog().FindByIdAndMemberId(blogId,c.Member.MemberId)
+	}
+	if err != nil {
+		c.JsonResult(6002,"文章不存在或已删除")
+	}
+
+	if err := blog.Delete(blogId); err != nil {
+		c.JsonResult(6003,"删除失败")
+	}else{
+		c.JsonResult(0,"删除成功")
+	}
+
+}
+
+// 上传附件或图片
+func (c *BlogController) Upload() {
+
+	blogId, _ := c.GetInt("blogId")
+
+	if blogId <= 0 {
+		c.JsonResult(6001, "参数错误")
+	}
+
+	name := "editormd-file-file"
+
+	file, moreFile, err := c.GetFile(name)
+	if err == http.ErrMissingFile {
+		name = "editormd-image-file"
+		file, moreFile, err = c.GetFile(name)
+		if err == http.ErrMissingFile {
+			c.JsonResult(6003, "没有发现需要上传的图片")
+		}
+	}
+
+	if err != nil {
+		c.JsonResult(6002, err.Error())
+	}
+
+	defer file.Close()
+
+	type Size interface {
+		Size() int64
+	}
+
+	if conf.GetUploadFileSize() > 0 && moreFile.Size > conf.GetUploadFileSize() {
+		c.JsonResult(6009, "查过文件允许的上传最大值")
+	}
+
+	ext := filepath.Ext(moreFile.Filename)
+
+	if ext == "" {
+		c.JsonResult(6003, "无法解析文件的格式")
+	}
+	//如果文件类型设置为 * 标识不限制文件类型
+	if beego.AppConfig.DefaultString("upload_file_ext", "") != "*" {
+		if !conf.IsAllowUploadFileExt(ext) {
+			c.JsonResult(6004, "不允许的文件类型")
+		}
+	}
+
+	// 如果是超级管理员,则不判断权限
+	if c.Member.IsAdministrator() {
+		_, err := models.NewBlog().Find(blogId)
+
+		if err != nil {
+			c.JsonResult(6006, "文档不存在或权限不足")
+		}
+
+	} else {
+		_, err := models.NewBlog().FindByIdAndMemberId(blogId, c.Member.MemberId)
+
+		if err != nil {
+			beego.Error("查询文章时出错 -> ", err)
+			if err == orm.ErrNoRows {
+				c.JsonResult(6006, "权限不足")
+			}
+
+			c.JsonResult(6001, err.Error())
+		}
+	}
+
+	fileName := "attach_" + strconv.FormatInt(time.Now().UnixNano(), 16)
+
+	filePath := filepath.Join(conf.WorkingDirectory, "uploads", "blog", time.Now().Format("200601"), fileName+ext)
+
+	path := filepath.Dir(filePath)
+
+	os.MkdirAll(path, os.ModePerm)
+
+	err = c.SaveToFile(name, filePath)
+
+	if err != nil {
+		beego.Error("SaveToFile => ", err)
+		c.JsonResult(6005, "保存文件失败")
+	}
+
+	var httpPath string
+	result := make(map[string]interface{})
+
+	if strings.EqualFold(ext, ".jpg") || strings.EqualFold(ext, ".jpeg") || strings.EqualFold(ext, ".png") || strings.EqualFold(ext, ".gif") {
+		httpPath = "/" + strings.Replace(strings.TrimPrefix(filePath, conf.WorkingDirectory), "\\", "/", -1)
+		if strings.HasPrefix(httpPath, "//") {
+			httpPath = conf.URLForWithCdnImage(string(httpPath[1:]))
+		}
+	} else {
+		attachment := models.NewAttachment()
+		attachment.BookId = 0
+		attachment.FileName = moreFile.Filename
+		attachment.CreateAt = c.Member.MemberId
+		attachment.FileExt = ext
+		attachment.FilePath = strings.TrimPrefix(filePath, conf.WorkingDirectory)
+		attachment.DocumentId = blogId
+
+		if fileInfo, err := os.Stat(filePath); err == nil {
+			attachment.FileSize = float64(fileInfo.Size())
+		}
+
+		attachment.HttpPath = httpPath
+
+		if err := attachment.Insert(); err != nil {
+			os.Remove(filePath)
+			beego.Error("保存文件附件失败 => ", err)
+			c.JsonResult(6006, "文件保存失败")
+		}
+		if attachment.HttpPath == "" {
+			attachment.HttpPath = conf.URLFor("BlogController.Download", ":id", blogId, ":attach_id", attachment.AttachmentId)
+
+			if err := attachment.Update(); err != nil {
+				beego.Error("SaveToFile => ", err)
+				c.JsonResult(6005, "保存文件失败")
+			}
+		}
+		result["attach"] = attachment
+	}
+
+	result["errcode"] = 0
+	result["success"] = 1
+	result["message"] = "ok"
+	result["url"] = httpPath
+	result["alt"] = fileName
+
+
+	c.Ctx.Output.JSON(result, true, false)
+	c.StopRun()
+}
+
+// 删除附件
+func (c *BlogController) RemoveAttachment() {
+	c.Prepare()
+	attachId, _ := c.GetInt("attach_id")
+
+	if attachId <= 0 {
+		c.JsonResult(6001, "参数错误")
+	}
+
+	attach, err := models.NewAttachment().Find(attachId)
+
+	if err != nil {
+		beego.Error(err)
+		c.JsonResult(6002, "附件不存在")
+	}
+
+	if !c.Member.IsAdministrator() {
+		_, err := models.NewBlog().FindByIdAndMemberId(attach.DocumentId,c.Member.MemberId)
+
+		if err != nil {
+			beego.Error(err)
+			c.JsonResult(6003, "文档不存在")
+		}
+	}
+
+
+	if err := attach.Delete();err != nil {
+		beego.Error(err)
+		c.JsonResult(6005, "删除失败")
+	}
+
+	os.Remove(filepath.Join(conf.WorkingDirectory, attach.FilePath))
+
+	c.JsonResult(0, "ok", attach)
+}
+
+//下载附件
+func (c *BlogController) Download() {
+	c.Prepare()
+
+	blogId, _ := strconv.Atoi(c.Ctx.Input.Param(":id"))
+	attachId, _ := strconv.Atoi(c.Ctx.Input.Param(":attach_id"))
+	password := c.GetString("password")
+
+	blog, err := models.NewBlog().Find(blogId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			c.ShowErrorPage(500, "文档不存在")
+		} else {
+			c.ShowErrorPage(500, "查询文章时异常")
+		}
+	}
+
+	if (c.Member != nil && !c.Member.IsAdministrator()) || ( blog.BlogStatus == "password" && password != blog.Password) {
+		c.ShowErrorPage(403, "没有下载权限")
+	}
+
+	// 查找附件
+	attachment, err := models.NewAttachment().Find(attachId)
+
+	if err != nil {
+		beego.Error("DownloadAttachment => ", err)
+		if err == orm.ErrNoRows {
+			c.ShowErrorPage(404,"附件不存在")
+		} else {
+			c.ShowErrorPage(500,"查询附件时出现异常")
+		}
+	}
+
+	if attachment.BookId !=0 || attachment.DocumentId != blogId {
+		c.ShowErrorPage(404,"附件不存在")
+	}
+
+	c.Ctx.Output.Download(filepath.Join(conf.WorkingDirectory, attachment.FilePath), attachment.FileName)
+	c.StopRun()
 }

+ 3 - 3
controllers/DocumentController.go

@@ -579,13 +579,13 @@ func (c *DocumentController) DownloadAttachment() {
 // 删除附件
 func (c *DocumentController) RemoveAttachment() {
 	c.Prepare()
-	attach_id, _ := c.GetInt("attach_id")
+	attachId, _ := c.GetInt("attach_id")
 
-	if attach_id <= 0 {
+	if attachId <= 0 {
 		c.JsonResult(6001, "参数错误")
 	}
 
-	attach, err := models.NewAttachment().Find(attach_id)
+	attach, err := models.NewAttachment().Find(attachId)
 
 	if err != nil {
 		beego.Error(err)

+ 5 - 3
controllers/ManagerController.go

@@ -657,17 +657,19 @@ func (c *ManagerController) AttachDetailed() {
 //删除附件.
 func (c *ManagerController) AttachDelete() {
 	c.Prepare()
-	attach_id, _ := c.GetInt("attach_id")
+	attachId, _ := c.GetInt("attach_id")
 
-	if attach_id <= 0 {
+	if attachId <= 0 {
 		c.Abort("404")
 	}
-	attach, err := models.NewAttachment().Find(attach_id)
+	attach, err := models.NewAttachment().Find(attachId)
 
 	if err != nil {
 		beego.Error("AttachDelete => ", err)
 		c.JsonResult(6001, err.Error())
 	}
+	attach.FilePath = filepath.Join(conf.WorkingDirectory,attach.FilePath)
+
 	if err := attach.Delete(); err != nil {
 		beego.Error("AttachDelete => ", err)
 		c.JsonResult(6002, err.Error())

+ 28 - 17
models/AttachmentModel.go

@@ -82,11 +82,11 @@ func (m *Attachment) Find(id int) (*Attachment, error) {
 
 	return m, err
 }
-
-func (m *Attachment) FindListByDocumentId(doc_id int) (attaches []*Attachment, err error) {
+//查询指定文档的附件列表
+func (m *Attachment) FindListByDocumentId(docId int) (attaches []*Attachment, err error) {
 	o := orm.NewOrm()
 
-	_, err = o.QueryTable(m.TableNameWithPrefix()).Filter("document_id", doc_id).OrderBy("-attachment_id").All(&attaches)
+	_, err = o.QueryTable(m.TableNameWithPrefix()).Filter("document_id", docId).Filter("book_id__gt",0).OrderBy("-attachment_id").All(&attaches)
 	return
 }
 
@@ -115,20 +115,31 @@ func (m *Attachment) FindToPager(pageIndex, pageSize int) (attachList []*Attachm
 		attach := &AttachmentResult{}
 		attach.Attachment = *item
 		attach.FileShortSize = filetil.FormatBytes(int64(attach.FileSize))
-
-		book := NewBook()
-
-		if e := o.QueryTable(book.TableNameWithPrefix()).Filter("book_id", item.BookId).One(book, "book_name"); e == nil {
-			attach.BookName = book.BookName
-		} else {
-			attach.BookName = "[不存在]"
-		}
-		doc := NewDocument()
-
-		if e := o.QueryTable(doc.TableNameWithPrefix()).Filter("document_id", item.DocumentId).One(doc, "document_name"); e == nil {
-			attach.DocumentName = doc.DocumentName
-		} else {
-			attach.DocumentName = "[不存在]"
+		//当项目ID为0标识是文章的附件
+		if item.BookId == 0 && item.DocumentId > 0 {
+			blog := NewBlog()
+			if err := o.QueryTable(blog.TableNameWithPrefix()).Filter("blog_id",item.DocumentId).One(blog,"blog_title");err  == nil {
+				attach.BookName = blog.BlogTitle
+			}else{
+				attach.BookName = "[文章不存在]"
+			}
+		}else {
+			book := NewBook()
+
+			if e := o.QueryTable(book.TableNameWithPrefix()).Filter("book_id", item.BookId).One(book, "book_name"); e == nil {
+				attach.BookName = book.BookName
+
+				doc := NewDocument()
+
+				if e := o.QueryTable(doc.TableNameWithPrefix()).Filter("document_id", item.DocumentId).One(doc, "document_name"); e == nil {
+					attach.DocumentName = doc.DocumentName
+				} else {
+					attach.DocumentName = "[文档不存在]"
+				}
+
+			} else {
+				attach.BookName = "[项目不存在]"
+			}
 		}
 		attach.LocalHttpPath = strings.Replace(item.FilePath, "\\", "/", -1)
 

+ 81 - 6
models/Blog.go

@@ -23,12 +23,12 @@ type Blog struct {
 	//链接到的项目中的文档ID
 	DocumentId int		`orm:"column(document_id);type(int);default(0)" json:"document_id"`
 	//文章摘要
-	BlogExcerpt string	`orm:"column(blog_excerpt);size(1500);unique" json:"blog_excerpt"`
+	BlogExcerpt string	`orm:"column(blog_excerpt);size(1500)" json:"blog_excerpt"`
 	//文章内容
 	BlogContent string	`orm:"column(blog_content);type(text);null" json:"blog_content"`
 	//发布后的文章内容
 	BlogRelease string 	`orm:"column(blog_release);type(text);null" json:"blog_release"`
-	//文章当前的状态,枚举enum(’publish’,’draft’,’private’,’static’,’object’)值,publish为已 发表,draft为草稿,private为私人内容(不会被公开) ,static(不详),object(不详)。默认为publish。
+	//文章当前的状态,枚举enum(’publish’,’draft’,’password’)值,publish为已 发表,draft为草稿,password 为私人内容(不会被公开) 。默认为publish。
 	BlogStatus string	`orm:"column(blog_status);size(100);default(publish)" json:"blog_status"`
 	//文章密码,varchar(100)值。文章编辑才可为文章设定一个密码,凭这个密码才能对文章进行重新强加或修改。
 	Password string		`orm:"column(password);size(100)" json:"-"`
@@ -36,9 +36,12 @@ type Blog struct {
 	Modified time.Time	`orm:"column(modify_time);type(datetime);auto_now" json:"modify_time"`
 	//修改人id
 	ModifyAt int		`orm:"column(modify_at);type(int)" json:"-"`
+	ModifyRealName string `orm:"-" json:"modify_real_name"`
 	//创建时间
 	Created time.Time	`orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
-	Version    int64         `orm:"type(bigint);column(version)" json:"version"`
+	CreateName string 	`orm:"-" json:"create_name"`
+	//版本号
+	Version    int64    `orm:"type(bigint);column(version)" json:"version"`
 }
 
 // 多字段唯一键
@@ -64,7 +67,7 @@ func (m *Blog) TableNameWithPrefix() string {
 
 func NewBlog() *Blog {
 	return &Blog{
-		Version: time.Now().Unix(),
+		BlogStatus: "public",
 	}
 }
 
@@ -109,7 +112,7 @@ func (b *Blog)Link() (*Blog,error)  {
 	//如果是链接文章,则需要从链接的项目中查找文章内容
 	if b.BlogType == 1 && b.DocumentId > 0{
 		doc := NewDocument()
-		if err := o.QueryTable(doc.TableNameWithPrefix()).Filter("document_id",b.DocumentId).One(doc,"");err != nil {
+		if err := o.QueryTable(doc.TableNameWithPrefix()).Filter("document_id",b.DocumentId).One(doc,"release","markdown");err != nil {
 			beego.Error("查询文章链接对象时出错 -> ",err)
 		}else{
 			b.BlogRelease = doc.Release
@@ -127,7 +130,79 @@ func (b *Blog) IsExist(identify string) bool {
 
 	return o.QueryTable(b.TableNameWithPrefix()).Filter("blog_identify",identify).Exist()
 }
+//保存文章
+func (b *Blog) Save(cols ...string) error {
+	o := orm.NewOrm()
+
+	if b.OrderIndex == 0 {
+		blog := NewBlog()
+		if err :=o.QueryTable(b.TableNameWithPrefix()).OrderBy("-blog_id").One(blog,"blog_id");err == nil{
+			b.OrderIndex = b.BlogId + 1;
+		}else{
+			c,_ := o.QueryTable(b.TableNameWithPrefix()).Count()
+			b.OrderIndex = int(c) + 1
+		}
+	}
+	var err error
+	b.Version = time.Now().Unix()
+	if b.BlogId > 0 {
+		b.Modified = time.Now()
+		_,err = o.Update(b,cols...)
+	}else{
+		b.Created = time.Now()
+		_,err = o.Insert(b)
+	}
+	return err
+}
+//分页查询文章列表
+func (b *Blog) FindToPager(pageIndex, pageSize int,memberId int,status string) (blogList []*Blog, totalCount int, err error) {
+
+	o := orm.NewOrm()
+
+	offset := (pageIndex - 1) * pageSize
+
+	query := o.QueryTable(b.TableNameWithPrefix());
+
+	if memberId > 0 {
+		query = query.Filter("member_id",memberId)
+	}
+	if status != "" {
+		query = query.Filter("blog_status",status)
+	}
+
 
-func (b *Blog) FindToPager() {
+	_,err = query.OrderBy("-order_index").Offset(offset).Limit(pageSize).All(&blogList)
 
+	if err != nil {
+		if err == orm.ErrNoRows {
+			return
+		}
+		beego.Error("获取文章列表时出错 ->",err)
+		return
+	}
+	count,err := query.Count()
+
+	if err != nil {
+		beego.Error("获取文章数量时出错 ->",err)
+		return nil,0,err
+	}
+	totalCount = int(count)
+	for _,blog := range blogList {
+		if blog.BlogType == 1 {
+			blog.Link()
+		}
+	}
+
+	return
 }
+
+//删除文章
+func (b *Blog) Delete(blogId int) error {
+	o := orm.NewOrm()
+
+	_,err := o.QueryTable(b.TableNameWithPrefix()).Filter("blog_id",blogId).Delete()
+	if err != nil {
+		beego.Error("删除文章失败 ->",err)
+	}
+	return err
+}

+ 21 - 12
models/attachment_result.go

@@ -34,27 +34,36 @@ func (m *AttachmentResult) Find(id int) (*AttachmentResult, error) {
 
 	m.Attachment = *attach
 
-	book := NewBook()
-
-	if e := o.QueryTable(book.TableNameWithPrefix()).Filter("book_id", attach.BookId).One(book, "book_name"); e == nil {
-		m.BookName = book.BookName
+	if attach.BookId == 0 && attach.DocumentId > 0 {
+		blog := NewBlog()
+		if err := o.QueryTable(blog.TableNameWithPrefix()).Filter("blog_id",attach.DocumentId).One(blog,"blog_title");err  == nil {
+			m.BookName = blog.BlogTitle
+		}else{
+			m.BookName = "[文章不存在]"
+		}
 	} else {
-		m.BookName = "[不存在]"
-	}
-	doc := NewDocument()
+		book := NewBook()
 
-	if e := o.QueryTable(doc.TableNameWithPrefix()).Filter("document_id", attach.DocumentId).One(doc, "document_name"); e == nil {
-		m.DocumentName = doc.DocumentName
-	} else {
-		m.DocumentName = "[不存在]"
-	}
+		if e := o.QueryTable(book.TableNameWithPrefix()).Filter("book_id", attach.BookId).One(book, "book_name"); e == nil {
+			m.BookName = book.BookName
+		} else {
+			m.BookName = "[不存在]"
+		}
+		doc := NewDocument()
 
+		if e := o.QueryTable(doc.TableNameWithPrefix()).Filter("document_id", attach.DocumentId).One(doc, "document_name"); e == nil {
+			m.DocumentName = doc.DocumentName
+		} else {
+			m.DocumentName = "[不存在]"
+		}
+	}
 	if attach.CreateAt > 0 {
 		member := NewMember()
 		if e := o.QueryTable(member.TableNameWithPrefix()).Filter("member_id", attach.CreateAt).One(member, "account"); e == nil {
 			m.Account = member.Account
 		}
 	}
+
 	m.FileShortSize = filetil.FormatBytes(int64(attach.FileSize))
 	m.LocalHttpPath = strings.Replace(m.FilePath, "\\", "/", -1)
 

+ 2 - 0
routers/filter.go

@@ -35,6 +35,8 @@ func init() {
 	beego.InsertFilter("/book", beego.BeforeRouter, FilterUser)
 	beego.InsertFilter("/book/*", beego.BeforeRouter, FilterUser)
 	beego.InsertFilter("/api/*", beego.BeforeRouter, FilterUser)
+	beego.InsertFilter("/blogs", beego.BeforeRouter,FilterUser)
+	beego.InsertFilter("/blogs/*", beego.BeforeRouter,FilterUser)
 
 	var FinishRouter = func(ctx *context.Context) {
 		ctx.ResponseWriter.Header().Add("MinDoc-Version", conf.VERSION)

+ 5 - 2
routers/router.go

@@ -65,11 +65,14 @@ func init() {
 
 	//管理文章的路由
 	beego.Router("/blogs", &controllers.BlogController{},"*:ManageList")
-	beego.Router("/blogs/setting/:id", &controllers.BlogController{}, "*:ManageSetting")
-	beego.Router("/blogs/edit/:id",&controllers.BlogController{}, "*:ManageEdit")
+	beego.Router("/blogs/setting/?:id", &controllers.BlogController{}, "*:ManageSetting")
+	beego.Router("/blogs/edit/?:id",&controllers.BlogController{}, "*:ManageEdit")
+	beego.Router("/blogs/delete",&controllers.BlogController{}, "post:ManageDelete")
+	beego.Router("/blogs/upload",&controllers.BlogController{}, "post:Upload")
 
 	//读文章的路由
 	beego.Router("/blog", &controllers.BlogController{}, "*:List")
+	beego.Router("/blog/attach/:id:int/:attach_id:int", &controllers.BlogController{},"get:Download")
 	beego.Router("/blog/:id",&controllers.BlogController{}, "*:Index")
 
 	beego.Router("/api/attach/remove/", &controllers.DocumentController{}, "post:RemoveAttachment")

+ 340 - 0
static/css/main.css

@@ -602,6 +602,346 @@ textarea{
 .pagination-container .pagination>.active>a, .pagination>.active>a:focus, .pagination>.active>a:hover, .pagination>.active>span, .pagination>.active>span:focus, .pagination>.active>span:hover{
     color: #272727;
 }
+/*************文章相关样式**********/
+.ui.items>.item>.content>.header {
+    display: inline-block;
+    margin: -.21425em 0 0;
+    font-family: 'PingFang SC','Helvetica Neue','Microsoft YaHei UI','Microsoft YaHei','Noto Sans CJK SC',Sathu,EucrosiaUPC,Arial,Helvetica,sans-serif;
+    font-weight: 700;
+    color: rgba(0,0,0,.85)
+}
+.ui.items>.item {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    margin: 1em 0;
+    width: 100%;
+    min-height: 0;
+    background: 0 0;
+    padding: 0;
+    border: none;
+    border-radius: 0;
+    -webkit-box-shadow: none;
+    box-shadow: none;
+    -webkit-transition: -webkit-box-shadow .1s ease;
+    transition: -webkit-box-shadow .1s ease;
+    transition: box-shadow .1s ease;
+    transition: box-shadow .1s ease,-webkit-box-shadow .1s ease;
+    z-index: ''
+}
+
+.ui.items>.item a {
+    cursor: pointer
+}
+
+.ui.items {
+    margin: 1.5em 0
+}
+
+.ui.items:first-child {
+    margin-top: 0!important
+}
+
+.ui.items:last-child {
+    margin-bottom: 0!important
+}
+
+.ui.items>.item:after {
+    display: block;
+    content: ' ';
+    height: 0;
+    clear: both;
+    overflow: hidden;
+    visibility: hidden
+}
+
+.ui.items>.item:first-child {
+    margin-top: 0
+}
+
+.ui.items>.item:last-child {
+    margin-bottom: 0
+}
+
+.ui.items>.item>.image {
+    position: relative;
+    -webkit-box-flex: 0;
+    -ms-flex: 0 0 auto;
+    flex: 0 0 auto;
+    display: block;
+    float: none;
+    margin: 0;
+    padding: 0;
+    max-height: '';
+    -ms-flex-item-align: top;
+    align-self: top
+}
+
+.ui.items>.item>.image>img {
+    display: block;
+    width: 100%;
+    height: auto;
+    border-radius: .125rem;
+    border: none
+}
+
+.ui.items>.item>.image:only-child>img {
+    border-radius: 0
+}
+
+.ui.items>.item>.content {
+    display: block;
+    -webkit-box-flex: 1;
+    -ms-flex: 1 1 auto;
+    flex: 1 1 auto;
+    background: 0 0;
+    margin: 0;
+    padding: 0;
+    -webkit-box-shadow: none;
+    box-shadow: none;
+    font-size: 1em;
+    border: none;
+    border-radius: 0
+}
+
+.ui.items>.item>.content:after {
+    display: block;
+    content: ' ';
+    height: 0;
+    clear: both;
+    overflow: hidden;
+    visibility: hidden
+}
+
+.ui.items>.item>.image+.content {
+    min-width: 0;
+    width: auto;
+    display: block;
+    margin-left: 0;
+    -ms-flex-item-align: top;
+    align-self: top;
+    padding-left: 1.5em
+}
+
+.ui.items>.item>.content>.header {
+    display: inline-block;
+    margin: -.21425em 0 0;
+    font-family: 'PingFang SC','Helvetica Neue','Microsoft YaHei UI','Microsoft YaHei','Noto Sans CJK SC',Sathu,EucrosiaUPC,Arial,Helvetica,sans-serif;
+    font-weight: 700;
+    color: rgba(0,0,0,.85)
+}
+
+.ui.items>.item>.content>.header:not(.ui) {
+    font-size: 1.14285714em
+}
+
+.ui.items>.item [class*="left floated"] {
+    float: left
+}
+
+.ui.items>.item [class*="right floated"] {
+    float: right
+}
+
+.ui.items>.item .content img {
+    -ms-flex-item-align: middle;
+    align-self: middle;
+    width: ''
+}
+
+.ui.items>.item .avatar img,.ui.items>.item img.avatar {
+    width: '';
+    height: '';
+    border-radius: 500rem
+}
+
+.ui.items>.item>.content>.description {
+    margin-top: .6em;
+    max-width: auto;
+    font-size: 1em;
+    line-height: 1.4285em;
+    color: rgba(0,0,0,.87);
+    min-height: 40px;
+}
+
+.ui.items>.item>.content p {
+    margin: 0 0 .5em
+}
+
+.ui.items>.item>.content p:last-child {
+    margin-bottom: 0
+}
+
+.ui.items>.item .meta {
+    margin: .5em 0 .5em;
+    font-size: 1em;
+    line-height: 1em;
+    color: rgba(0,0,0,.6)
+}
+
+.ui.items>.item .meta * {
+    margin-right: .3em
+}
+
+.ui.items>.item .meta :last-child {
+    margin-right: 0
+}
+
+.ui.items>.item .meta [class*="right floated"] {
+    margin-right: 0;
+    margin-left: .3em
+}
+
+.ui.items>.item>.content a:not(.ui) {
+    color: '';
+    -webkit-transition: color .1s ease;
+    transition: color .1s ease
+}
+
+.ui.items>.item>.content a:not(.ui):hover {
+    color: ''
+}
+
+.ui.items>.item>.content>a.header {
+    color: rgba(0,0,0,.85)
+}
+
+.ui.items>.item>.content>a.header:hover {
+    color: #1e70bf
+}
+
+.ui.items>.item .meta>a:not(.ui) {
+    color: rgba(0,0,0,.4)
+}
+
+.ui.items>.item .meta>a:not(.ui):hover {
+    color: rgba(0,0,0,.87)
+}
+
+.ui.items>.item>.content .favorite.icon {
+    cursor: pointer;
+    opacity: .75;
+    -webkit-transition: color .1s ease;
+    transition: color .1s ease
+}
+
+.ui.items>.item>.content .favorite.icon:hover {
+    opacity: 1;
+    color: #ffb70a
+}
+
+.ui.items>.item>.content .active.favorite.icon {
+    color: #ffe623
+}
+
+.ui.items>.item>.content .like.icon {
+    cursor: pointer;
+    opacity: .75;
+    -webkit-transition: color .1s ease;
+    transition: color .1s ease
+}
+
+.ui.items>.item>.content .like.icon:hover {
+    opacity: 1;
+    color: #ff2733
+}
+
+.ui.items>.item>.content .active.like.icon {
+    color: #ff2733
+}
+
+.ui.items>.item .extra {
+    display: block;
+    position: relative;
+    background: 0 0;
+    margin: .5rem 0 0;
+    width: 100%;
+    padding: 0 0 0;
+    top: 0;
+    left: 0;
+    color: rgba(0,0,0,.4);
+    -webkit-box-shadow: none;
+    box-shadow: none;
+    -webkit-transition: color .1s ease;
+    transition: color .1s ease;
+    border-top: none
+}
+
+.ui.items>.item .extra>* {
+    margin: .25rem .5rem .25rem 0
+}
+
+.ui.items>.item .extra>[class*="right floated"] {
+    margin: .25rem 0 .25rem .5rem
+}
+
+.ui.items>.item .extra:after {
+    display: block;
+    content: ' ';
+    height: 0;
+    clear: both;
+    overflow: hidden;
+    visibility: hidden
+}
+
+.ui.items>.item>.image:not(.ui) {
+    width: 175px
+}
+.ui.horizontal.list {
+    display: inline-block;
+    font-size: 0
+}
+
+.ui.horizontal.list>.item {
+    display: inline-block;
+    margin-left: 1em;
+    font-size: 1rem
+}
+
+.ui.horizontal.list:not(.celled)>.item:first-child {
+    margin-left: 0!important;
+    padding-left: 0!important
+}
+
+.ui.horizontal.list .list {
+    padding-left: 0;
+    padding-bottom: 0
+}
+.ui.horizontal.list a{
+    color: rgba(0,0,0,.4);
+}
+.ui.horizontal.list a:hover {
+    color: rgba(0,0,0,.87)
+}
+
+.ui.horizontal.list .list>.item>.content,.ui.horizontal.list .list>.item>.icon,.ui.horizontal.list .list>.item>.image,.ui.horizontal.list>.item>.content,.ui.horizontal.list>.item>.icon,.ui.horizontal.list>.item>.image {
+    vertical-align: middle
+}
+
+.ui.horizontal.list>.item:first-child,.ui.horizontal.list>.item:last-child {
+    padding-top: .21428571em;
+    padding-bottom: .21428571em
+}
+
+.ui.horizontal.list>.item>i.icon {
+    margin: 0;
+    padding: 0 .25em 0 0
+}
+
+.ui.horizontal.list>.item>.icon,.ui.horizontal.list>.item>.icon+.content {
+    float: none;
+    display: inline-block
+}
+.ui.teal.label {
+    background-color: #00b5ad!important;
+    border-color: #00b5ad!important;
+    color: #fff!important
+}
+.ui.items>.item {
+    border-bottom: 1px solid #efefef;
+    margin: 0;
+    padding: 1em 0
+}
 
 /**************网站底部样式*************************/
 .footer{

+ 149 - 0
static/js/blog.js

@@ -0,0 +1,149 @@
+$(function () {
+    editormd.katexURL = {
+        js  : window.baseUrl + "/static/katex/katex",
+        css : window.baseUrl + "/static/katex/katex"
+    };
+    window.editor = editormd("docEditor", {
+        width: "100%",
+        height: "100%",
+        path: window.baseUrl + "/static/editor.md/lib/",
+        toolbar: true,
+        placeholder: "本编辑器支持 Markdown 编辑,左边编写,右边预览。",
+        imageUpload: true,
+        imageFormats: ["jpg", "jpeg", "gif", "png", "JPG", "JPEG", "GIF", "PNG"],
+        imageUploadURL: window.imageUploadURL,
+        toolbarModes: "full",
+        fileUpload: true,
+        fileUploadURL: window.fileUploadURL,
+        taskList: true,
+        flowChart: true,
+        htmlDecode: "style,script,iframe,title,onmouseover,onmouseout,style",
+        lineNumbers: false,
+        sequenceDiagram: true,
+        tocStartLevel: 1,
+        tocm: true,
+        tex:true,
+        saveHTMLToTextarea: true,
+
+        onload: function() {
+            this.hideToolbar();
+            var keyMap = {
+                "Ctrl-S": function(cm) {
+                    saveBlog(false);
+                },
+                "Cmd-S": function(cm){
+                    saveBlog(false);
+                },
+                "Ctrl-A": function(cm) {
+                    cm.execCommand("selectAll");
+                }
+            };
+            this.addKeyMap(keyMap);
+
+
+            uploadImage("docEditor", function ($state, $res) {
+                if ($state === "before") {
+                    return layer.load(1, {
+                        shade: [0.1, '#fff'] // 0.1 透明度的白色背景
+                    });
+                } else if ($state === "success") {
+                    if ($res.errcode === 0) {
+                        var value = '![](' + $res.url + ')';
+                        window.editor.insertValue(value);
+                    }
+                }
+            });
+        },
+        onchange: function () {
+            resetEditorChanged(true);
+        }
+    });
+    /**
+     * 实现标题栏操作
+     */
+    $("#editormd-tools").on("click", "a[class!='disabled']", function () {
+        var name = $(this).find("i").attr("name");
+        if (name === "attachment") {
+            $("#uploadAttachModal").modal("show");
+        }else if (name === "save") {
+            saveBlog(false);
+        } else if (name === "template") {
+            $("#documentTemplateModal").modal("show");
+        } else if (name === "tasks") {
+            // 插入 GFM 任务列表
+            var cm = window.editor.cm;
+            var selection = cm.getSelection();
+
+            if (selection === "") {
+                cm.replaceSelection("- [x] " + selection);
+            } else {
+                var selectionText = selection.split("\n");
+
+                for (var i = 0, len = selectionText.length; i < len; i++) {
+                    selectionText[i] = (selectionText[i] === "") ? "" : "- [x] " + selectionText[i];
+                }
+                cm.replaceSelection(selectionText.join("\n"));
+            }
+        } else {
+            var action = window.editor.toolbarHandlers[name];
+
+            if (action !== "undefined") {
+                $.proxy(action, window.editor)();
+                window.editor.focus();
+            }
+        }
+    }) ;
+
+    /**
+     * 保存文章
+     * @param $is_cover
+     */
+    function saveBlog($is_cover) {
+        var content = window.editor.getMarkdown();
+        var html = window.editor.getPreviewedHTML();
+
+        $.ajax({
+            beforeSend: function () {
+                index = layer.load(1, { shade: [0.1, '#fff'] });
+            },
+            url: window.editURL,
+            data: { "blogId": window.blogId,"content": content,"htmlContent": html, "cover": $is_cover ? "yes" : "no","version" : window.blogVersion},
+            type: "post",
+            timeout : 30000,
+            dataType: "json",
+            success: function ($res) {
+                layer.close(index);
+                if ($res.errcode === 0) {
+                    resetEditorChanged(false);
+                    window.blogVersion = $res.data.version;
+                    console.log(window.blogVersion);
+                } else if($res.errcode === 6005) {
+                    var confirmIndex = layer.confirm('文档已被其他人修改确定覆盖已存在的文档吗?', {
+                        btn: ['确定', '取消'] // 按钮
+                    }, function() {
+                        layer.close(confirmIndex);
+                        saveBlog(true);
+                    });
+                } else {
+                    layer.msg(res.message);
+                }
+            },
+            error : function (XMLHttpRequest, textStatus, errorThrown) {
+                layer.close(index);
+                layer.msg("服务器错误:" +  errorThrown);
+            }
+        });
+    }
+    /**
+     * 设置编辑器变更状态
+     * @param $is_change
+     */
+    function resetEditorChanged($is_change) {
+        if ($is_change && !window.isLoad) {
+            $("#markdown-save").removeClass('disabled').addClass('change');
+        } else {
+            $("#markdown-save").removeClass('change').addClass('disabled');
+        }
+        window.isLoad = false;
+    }
+});

+ 301 - 0
views/blog/manage_edit.tpl

@@ -0,0 +1,301 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    <title>编辑文章 - Powered by MinDoc</title>
+    <script type="text/javascript">
+        window.baseUrl = "{{.BaseUrl}}";
+        window.editor = null;
+        window.editURL = "{{urlfor "BlogController.ManageEdit" "blogId" .Model.BlogId}}";
+        window.imageUploadURL = "{{urlfor "BlogController.Upload" "blogId" .Model.BlogId}}";
+        window.fileUploadURL = "";
+        window.blogId = {{.Model.BlogId}};
+        window.blogVersion = {{.Model.Version}};
+        window.removeAttachURL = "{{urlfor "DocumentController.RemoveAttachment"}}";
+    </script>
+    <!-- Bootstrap -->
+    <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/jstree/3.3.4/themes/default/style.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/editor.md/css/editormd.css"}}" rel="stylesheet">
+
+    <link href="{{cdncss "/static/css/jstree.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/highlight/styles/vs.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/webuploader/webuploader.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/css/markdown.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/prettify/themes/prettify.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/css/markdown.preview.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]-->
+</head>
+<body>
+
+<div class="m-manual manual-editor">
+    <div class="manual-head" id="editormd-tools" style="min-width: 1200px; position:absolute;">
+        <div class="editormd-group">
+            <a href="{{urlfor "BlogController.ManageList"}}" data-toggle="tooltip" data-title="返回"><i class="fa fa-chevron-left" aria-hidden="true"></i></a>
+        </div>
+        <div class="editormd-group">
+            <a href="javascript:;" id="markdown-save" data-toggle="tooltip" data-title="保存" class="disabled save"><i class="fa fa-save" aria-hidden="true" name="save"></i></a>
+        </div>
+        <div class="editormd-group">
+            <a href="javascript:;" data-toggle="tooltip" data-title="撤销 (Ctrl-Z)"><i class="fa fa-undo first" name="undo" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="重做 (Ctrl-Y)"><i class="fa fa-repeat last" name="redo" unselectable="on"></i></a>
+        </div>
+        <div class="editormd-group">
+            <a href="javascript:;" data-toggle="tooltip" data-title="粗体"><i class="fa fa-bold first" name="bold" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="斜体"><i class="fa fa-italic item" name="italic" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="删除线"><i class="fa fa-strikethrough last" name="del" unselectable="on"></i></a>
+        </div>
+        <div class="editormd-group">
+            <a href="javascript:;" data-toggle="tooltip" data-title="标题一"><i class="fa editormd-bold first" name="h1" unselectable="on">H1</i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="标题二"><i class="fa editormd-bold item" name="h2" unselectable="on">H2</i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="标题三"><i class="fa editormd-bold item" name="h3" unselectable="on">H3</i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="标题四"><i class="fa editormd-bold item" name="h4" unselectable="on">H4</i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="标题五"><i class="fa editormd-bold item" name="h5" unselectable="on">H5</i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="标题六"><i class="fa editormd-bold last" name="h6" unselectable="on">H6</i></a>
+        </div>
+        <div class="editormd-group">
+            <a href="javascript:;" data-toggle="tooltip" data-title="无序列表"><i class="fa fa-list-ul first" name="list-ul" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="有序列表"><i class="fa fa-list-ol item" name="list-ol" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="横线"><i class="fa fa-minus last" name="hr" unselectable="on"></i></a>
+        </div>
+        <div class="editormd-group">
+            <a href="javascript:;" data-toggle="tooltip" data-title="链接"><i class="fa fa-link first" name="link" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="引用链接"><i class="fa fa-anchor item" name="reference-link" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="添加图片"><i class="fa fa-picture-o item" name="image" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="行内代码"><i class="fa fa-code item" name="code" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="代码块" unselectable="on"><i class="fa fa-file-code-o item" name="code-block" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="添加表格"><i class="fa fa-table item" name="table" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="引用"><i class="fa fa-quote-right item" name="quote" unselectable="on"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="GFM 任务列表"><i class="fa fa-tasks item" name="tasks" aria-hidden="true"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="附件"><i class="fa fa-paperclip item" aria-hidden="true" name="attachment"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="模板"><i class="fa fa-tachometer last" name="template"></i></a>
+
+        </div>
+
+        <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-question-circle-o last" aria-hidden="true" name="help"></i></a>
+        </div>
+
+        <div class="editormd-group">
+            <a href="javascript:;" data-toggle="tooltip" data-title=""></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title=""></a>
+        </div>
+
+        <div class="clearfix"></div>
+    </div>
+    <div class="manual-body">
+        <div class="manual-editor-container" id="manualEditorContainer" style="min-width: 920px;left: 0;">
+            <div class="manual-editormd">
+                <div id="docEditor" class="manual-editormd-active"><textarea style="display:none;">{{.Model.BlogContent}}</textarea></div>
+            </div>
+            <div class="manual-editor-status">
+                <div id="attachInfo" class="item">0 个附件</div>
+            </div>
+        </div>
+
+    </div>
+</div>
+<!-- Modal -->
+
+<div class="modal fade" id="uploadAttachModal" tabindex="-1" role="dialog" aria-labelledby="uploadAttachModalLabel">
+    <div class="modal-dialog" role="document">
+        <form method="post" id="uploadAttachModalForm" class="form-horizontal">
+            <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" id="myModalLabel">上传附件</h4>
+                </div>
+                <div class="modal-body">
+                    <div class="attach-drop-panel">
+                        <div class="upload-container" id="filePicker"><i class="fa fa-upload" aria-hidden="true"></i></div>
+                    </div>
+                    <div class="attach-list" id="attachList">
+                        <template v-for="item in lists">
+                            <div class="attach-item" :id="item.attachment_id">
+                                <template v-if="item.state == 'wait'">
+                                    <div class="progress">
+                                        <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100">
+                                            <span class="sr-only">0% Complete (success)</span>
+                                        </div>
+                                    </div>
+                                </template>
+                                <template v-else-if="item.state == 'error'">
+                                    <span class="error-message">${item.message}</span>
+                                    <button type="button" class="btn btn-sm close" @click="removeAttach(item.attachment_id)">
+                                        <i class="fa fa-remove" aria-hidden="true"></i>
+                                    </button>
+                                </template>
+                                <template v-else>
+                                    <a :href="item.http_path" target="_blank" :title="item.file_name">${item.file_name}</a>
+                                    <span class="text">(${ formatBytes(item.file_size) })</span>
+                                    <span class="error-message">${item.message}</span>
+                                    <button type="button" class="btn btn-sm close" @click="removeAttach(item.attachment_id)">
+                                        <i class="fa fa-remove" aria-hidden="true"></i>
+                                    </button>
+                                    <div class="clearfix"></div>
+                                </template>
+                            </div>
+                        </template>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <span id="add-error-message" class="error-message"></span>
+                    <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
+                    <button type="button" class="btn btn-primary" id="btnUploadAttachFile" data-dismiss="modal">确定</button>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
+<!-- Modal -->
+
+
+<div class="modal fade" id="documentTemplateModal" tabindex="-1" role="dialog" aria-labelledby="请选择模板类型" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title" id="modal-title">请选择模板类型</h4>
+            </div>
+            <div class="modal-body template-list">
+                <div class="container">
+                    <div class="section">
+                        <a data-type="normal" href="javascript:;"><i class="fa fa-file-o"></i></a>
+                        <h3><a data-type="normal" href="javascript:;">普通文档</a></h3>
+                        <ul>
+                            <li>默认类型</li>
+                            <li>简单的文本文档</li>
+                        </ul>
+                    </div>
+                    <div class="section">
+                        <a data-type="api" href="javascript:;"><i class="fa fa-file-code-o"></i></a>
+                        <h3><a data-type="api" href="javascript:;">API文档</a></h3>
+                        <ul>
+                            <li>用于API文档速写</li>
+                            <li>支持代码高亮</li>
+                        </ul>
+                    </div>
+                    <div class="section">
+                        <a data-type="code" href="javascript:;"><i class="fa fa-book"></i></a>
+
+                        <h3><a data-type="code" href="javascript:;">数据字典</a></h3>
+                        <ul>
+                            <li>用于数据字典显示</li>
+                            <li>表格支持</li>
+                        </ul>
+                    </div>
+                </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>
+<template id="template-api">
+{{template "document/template_api.tpl"}}
+</template>
+<template id="template-code">
+{{template "document/template_code.tpl"}}
+</template>
+<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
+<script src="{{cdnjs "/static/vuejs/vue.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
+<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 src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
+<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/editor.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/blog.js"}}" type="text/javascript"></script>
+<script type="text/javascript">
+    $(function () {
+
+        $("#attachInfo").on("click",function () {
+            $("#uploadAttachModal").modal("show");
+        });
+        window.uploader = null;
+
+        $("#uploadAttachModal").on("shown.bs.modal",function () {
+            if(window.uploader === null){
+                try {
+                    window.uploader = WebUploader.create({
+                        auto: true,
+                        dnd : true,
+                        swf: '{{.BaseUrl}}/static/webuploader/Uploader.swf',
+                        server: '{{urlfor "BlogController.Upload"}}',
+                        formData : { "blogId" : {{.Model.BlogId}}},
+                        pick: "#filePicker",
+                        fileVal : "editormd-file-file",
+                        fileNumLimit : 1,
+                        compress : false
+                    }).on("beforeFileQueued",function (file) {
+                        uploader.reset();
+                    }).on( 'fileQueued', function( file ) {
+                        var item = {
+                            state : "wait",
+                            attachment_id : file.id,
+                            file_size : file.size,
+                            file_name : file.name,
+                            message : "正在上传"
+                        };
+                        window.vueApp.lists.splice(0,0,item);
+
+                    }).on("uploadError",function (file,reason) {
+                        for(var i in window.vueApp.lists){
+                            var item = window.vueApp.lists[i];
+                            if(item.attachment_id == file.id){
+                                item.state = "error";
+                                item.message = "上传失败";
+                                break;
+                            }
+                        }
+
+                    }).on("uploadSuccess",function (file, res) {
+
+                        for(var index in window.vueApp.lists){
+                            var item = window.vueApp.lists[index];
+                            if(item.attachment_id === file.id){
+                                if(res.errcode === 0) {
+                                    window.vueApp.lists.splice(index, 1, res.attach);
+
+                                }else{
+                                    item.message = res.message;
+                                    item.state = "error";
+                                }
+                                break;
+                            }
+                        }
+
+                    }).on("beforeFileQueued",function (file) {
+
+                    }).on("uploadComplete",function () {
+
+                    }).on("uploadProgress",function (file, percentage) {
+                        var $li = $( '#'+file.id ),
+                                $percent = $li.find('.progress .progress-bar');
+
+                        $percent.css( 'width', percentage * 100 + '%' );
+                    });
+                }catch(e){
+                    console.log(e);
+                }
+            }
+        });
+    });
+</script>
+</body>
+</html>

+ 45 - 379
views/blog/manage_list.tpl

@@ -36,167 +36,69 @@
                     <div class="box-head">
                         <strong class="box-title">文章列表</strong>
                         &nbsp;
-                        <button type="button" data-toggle="modal" data-target="#addBlogDialogModal" class="btn btn-success btn-sm pull-right">添加文章</button>
+                        <a href="{{urlfor "BlogController.ManageSetting"}}" class="btn btn-success btn-sm pull-right">添加文章</a>
                     </div>
                 </div>
-                <div class="box-body" id="bookList">
-                    <div class="book-list">
-                        <template v-if="lists.length <= 0">
-                            <div class="text-center">暂无数据</div>
-                        </template>
-                        <template v-else>
-
-                            <div class="list-item" v-for="item in lists">
-                                <div class="book-title">
-                                    <div class="pull-left">
-                                        <a :href="'{{.BaseUrl}}/book/' + item.identify + '/dashboard'" title="项目概要" data-toggle="tooltip">
-                                            <template v-if="item.privately_owned == 0">
-                                                <i class="fa fa-unlock" aria-hidden="true"></i>
-                                            </template>
-                                            <template v-else-if="item.privately_owned == 1">
-                                                <i class="fa fa-lock" aria-hidden="true"></i>
-                                            </template>
-                                            ${item.book_name}
-                                        </a>
+                <div class="box-body" id="blogList">
+                    <div class="ui items">
+                        {{range $index,$item := .ModelList}}
+                            <div class="item blog-item">
+                                <div class="content">
+                                    <a class="header" href="{{urlfor "BlogController.Index" ":id" $item.BlogId}}" target="_blank">
+                                        {{if eq $item.BlogStatus "password"}}
+                                        <div class="ui teal label horizontal" data-tooltip="加密">密</div>
+                                        {{end}}
+                                        {{$item.BlogTitle}}
+                                    </a>
+                                    <div class="description">
+                                        <p class="line-clamp">{{$item.BlogExcerpt}}&nbsp;</p>
                                     </div>
-                                    <div class="pull-right">
-                                        <div class="btn-group">
-                                            <a  :href="'{{.BaseUrl}}/book/' + item.identify + '/dashboard'" class="btn btn-default">设置</a>
-
-                                            <a href="javascript:;" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                                <span class="caret"></span>
-                                                <span class="sr-only">Toggle Dropdown</span>
-                                            </a>
-                                            <ul class="dropdown-menu">
-                                                <li><a :href="'{{urlfor "DocumentController.Index" ":key" ""}}' + item.identify" target="_blank">阅读</a></li>
-                                                <template v-if="item.role_id != 3">
-                                                    <li><a :href="'{{.BaseUrl}}/api/' + item.identify + '/edit'" target="_blank">编辑</a></li>
-                                                </template>
-                                                <template v-if="item.role_id == 0">
-                                                    <li><a :href="'javascript:deleteBook(\''+item.identify+'\');'">删除</a></li>
-                                                    <li><a :href="'javascript:copyBook(\''+item.identify+'\');'">复制</a></li>
-                                                </template>
-                                            </ul>
-
+                                    <div class="extra">
+                                        <div>
+                                            <div class="ui horizontal small list">
+                                                <div class="item"><i class="fa fa-clock-o"></i> {{date $item.Modified "Y-m-d H:i:s"}}</div>
+                                                <div class="item"><a href="{{urlfor "BlogController.ManageEdit" ":id" $item.BlogId}}" title="文章编辑"><i class="fa fa-edit"></i> 编辑</a></div>
+                                                <div class="item"><a class="delete-btn" title="删除文章" data-id="{{$item.BlogId}}"><i class="fa fa-trash"></i> 删除</a></div>
+                                                <div class="item"><a href="{{urlfor "BlogController.ManageSetting" ":id" $item.BlogId}}" title="文章设置" class="setting-btn"><i class="fa fa-gear"></i> 设置</a></div>
+                                            </div>
                                         </div>
-
-                                    {{/*<a :href="'{{urlfor "DocumentController.Index" ":key" ""}}' + item.identify" title="查看文档" data-toggle="tooltip" target="_blank"><i class="fa fa-eye"></i> 查看文档</a>*/}}
-                                    {{/*<template v-if="item.role_id != 3">*/}}
-                                    {{/*<a :href="'/api/' + item.identify + '/edit'" title="编辑文档" data-toggle="tooltip" target="_blank"><i class="fa fa-edit" aria-hidden="true"></i> 编辑文档</a>*/}}
-                                    {{/*</template>*/}}
                                     </div>
-                                    <div class="clearfix"></div>
-                                </div>
-                                <div class="desc-text">
-                                    <template v-if="item.description === ''">
-                                        &nbsp;
-                                    </template>
-                                    <template v-else="">
-                                        <a :href="'{{.BaseUrl}}/book/' + item.identify + '/dashboard'" title="项目概要" style="font-size: 12px;">
-                                            ${item.description}
-                                        </a>
-                                    </template>
-                                </div>
-                                <div class="info">
-                                <span title="创建时间" data-toggle="tooltip" data-placement="bottom"><i class="fa fa-clock-o"></i>
-                                    ${(new Date(item.create_time)).format("yyyy-MM-dd hh:mm:ss")}
-
-                                </span>
-                                    <span title="创建者" data-toggle="tooltip" data-placement="bottom"><i class="fa fa-user"></i> ${item.create_name}</span>
-                                    <span title="文档数量" data-toggle="tooltip" data-placement="bottom"><i class="fa fa-pie-chart"></i> ${item.doc_count}</span>
-                                    <span title="项目角色" data-toggle="tooltip" data-placement="bottom"><i class="fa fa-user-secret"></i> ${item.role_name}</span>
-                                    <template v-if="item.last_modify_text !== ''">
-                                        <span title="最后编辑" data-toggle="tooltip" data-placement="bottom"><i class="fa fa-pencil"></i> 最后编辑: ${item.last_modify_text}</span>
-                                    </template>
-
                                 </div>
                             </div>
-                        </template>
+                        {{else}}
+                        <div class="text-center">暂无文章</div>
+                        {{end}}
                     </div>
-                    <template v-if="lists.length >= 0">
-                        <nav class="pagination-container">
+                    <nav class="pagination-container">
                         {{.PageHtml}}
-                        </nav>
-                    </template>
+                    </nav>
                 </div>
             </div>
         </div>
     </div>
 {{template "widgets/footer.tpl" .}}
 </div>
-<!-- Modal -->
-<div class="modal fade" id="addBlogDialogModal" tabindex="-1" role="dialog" aria-labelledby="addBlogDialogModalLabel">
-    <div class="modal-dialog modal-lg" role="document" style="min-width: 900px;">
-        <form method="post" autocomplete="off" action="{{urlfor "BookController.Create"}}" id="addBookDialogForm" enctype="multipart/form-data">
-            <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" id="myModalLabel">添加文章</h4>
-                </div>
-                <div class="modal-body">
-                    <div class="form-group">
-                        <div class="pull-left">
-                            <div class="form-group">
-                                <input type="text" class="form-control" placeholder="标题(不超过100字)" name="book_name" id="bookName">
-                            </div>
-                            <div class="form-group">
-                                <div class="pull-left" style="padding: 7px 5px 6px 0">
-                                {{urlfor "DocumentController.Index" ":key" ""}}
-                                </div>
-                                <input type="text" class="form-control pull-left" style="width: 410px;vertical-align: middle" placeholder="项目唯一标识(不超过50字)" name="identify" id="identify">
-                                <div class="clearfix"></div>
-                                <p class="text" style="font-size: 12px;color: #999;margin-top: 6px;">文档标识只能包含小写字母、数字,以及“-”、“.”和“_”符号.</p>
-                            </div>
-                            <div class="form-group">
-                                <textarea name="description" id="description" class="form-control" placeholder="描述信息不超过500个字符" style="height: 90px;"></textarea>
-                            </div>
-                            <div class="form-group">
-                                <div class="col-lg-6">
-                                    <label>
-                                        <input type="radio" name="privately_owned" value="0" checked> 公开<span class="text">(任何人都可以访问)</span>
-                                    </label>
-                                </div>
-                                <div class="col-lg-6">
-                                    <label>
-                                        <input type="radio" name="privately_owned" value="1"> 私有<span class="text">(只要参与者或使用令牌才能访问)</span>
-                                    </label>
-                                </div>
-                                <div class="clearfix"></div>
-                            </div>
-                        </div>
-                    </div>
-                    <div class="clearfix"></div>
-                </div>
-                <div class="modal-footer">
-                    <span id="form-error-message"></span>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
-                    <button type="button" class="btn btn-success" id="btnSaveDocument" data-loading-text="保存中...">保存</button>
-                </div>
-            </div>
-        </form>
-    </div>
-</div>
-<!--END Modal-->
+
 
 <!-- Delete Book Modal -->
-<div class="modal fade" id="deleteBookModal" tabindex="-1" role="dialog" aria-labelledby="deleteBookModalLabel">
+<div class="modal fade" id="deleteBlogModal" tabindex="-1" role="dialog" aria-labelledby="deleteBlogModalLabel">
     <div class="modal-dialog" role="document">
-        <form method="post" id="deleteBookForm" action="{{urlfor "BookController.Delete"}}">
-            <input type="hidden" name="identify" value="">
+        <form method="post" id="deleteBlogForm" action="{{urlfor "BlogController.ManageDelete"}}">
+            <input type="hidden" name="blog_id" value="">
             <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>
+                    <h4 class="modal-title">删除文章</h4>
                 </div>
                 <div class="modal-body">
-                    <span style="font-size: 14px;font-weight: 400;">确定删除项目吗?</span>
+                    <span style="font-size: 14px;font-weight: 400;">确定删除文章吗?</span>
                     <p></p>
-                    <p class="text error-message">删除项目后将无法找回。</p>
+                    <p class="text error-message">删除文章后将无法找回。</p>
                 </div>
                 <div class="modal-footer">
                     <span id="form-error-message2" class="error-message"></span>
                     <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
-                    <button type="submit" id="btnDeleteBook" class="btn btn-primary" data-loading-text="删除中...">确定删除</button>
+                    <button type="submit" id="btnDeleteBlog" class="btn btn-primary" data-loading-text="删除中...">确定删除</button>
                 </div>
             </div>
         </form>
@@ -212,98 +114,13 @@
 <script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
 <script src="{{cdnjs "/static/js/main.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
-    /**
-     * 绘制项目封面
-     * @param $id
-     * @param $font
-     */
-    function drawBookCover($id,$font) {
-        var draw = document.getElementById($id);
-        //确认浏览器是否支持<canvas>元素
-        if (draw.getContext) {
-            var context = draw.getContext('2d');
-
-            //绘制红色矩形,绿色描边
-            context.fillStyle = '#eee';
-            context.strokeStyle = '#d4d4d5';
-            context.strokeRect(0,0,170,230);
-            context.fillRect(0,0,170,230);
-
-            //设置字体样式
-            context.font = "bold 20px SimSun";
-            context.textAlign = "left";
-            //设置字体填充颜色
-            context.fillStyle = "#3E403E";
-
-            var font = $font;
-
-            var lineWidth = 0; //当前行的绘制的宽度
-            var lastTextIndex = 0; //已经绘制上canvas最后的一个字符的下标
-            var drawWidth = 155,lineHeight = 25,drawStartX = 15,drawStartY=65;
-            //由于改变canvas 的高度会导致绘制的纹理被清空,所以,不预先绘制,先放入到一个数组当中
-            var arr = [];
-            for(var i = 0; i<font.length; i++){
-                //获取当前的截取的字符串的宽度
-                lineWidth = context.measureText(font.substr(lastTextIndex,i-lastTextIndex)).width;
-
-                if(lineWidth > drawWidth){
-                    //判断最后一位是否是标点符号
-                    if(judgePunctuationMarks(font[i-1])){
-                        arr.push(font.substr(lastTextIndex,i-lastTextIndex));
-                        lastTextIndex = i;
-                    }else{
-                        arr.push(font.substr(lastTextIndex,i-lastTextIndex-1));
-                        lastTextIndex = i-1;
-                    }
-                }
-                //将最后多余的一部分添加到数组
-                if(i === font.length - 1){
-                    arr.push(font.substr(lastTextIndex,i-lastTextIndex+1));
-                }
-            }
-
-            for(var i =0; i<arr.length; i++){
-                context.fillText(arr[i],drawStartX,drawStartY+i*lineHeight);
-            }
-
-            //判断是否是需要避开的标签符号
-            function judgePunctuationMarks(value) {
-                var arr = [".",",",";","?","!",":","\"",",","。","?","!",";",":","、"];
-                for(var i = 0; i< arr.length; i++){
-                    if(value === arr[i]){
-                        return true;
-                    }
-                }
-                return false;
-            }
-
-        }else{
-            console.log("浏览器不支持")
-        }
-    }
 
-    /**
-     * 将base64格式的图片转换为二进制
-     * @param dataURI
-     * @returns {Blob}
-     */
-    function dataURItoBlob(dataURI) {
-        var byteString = atob(dataURI.split(',')[1]);
-        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
-        var ab = new ArrayBuffer(byteString.length);
-        var ia = new Uint8Array(ab);
-        for (var i = 0; i < byteString.length; i++) {
-            ia[i] = byteString.charCodeAt(i);
-        }
-
-        return new Blob([ab], {type: mimeString});
-    }
     /**
      * 删除项目
      */
-    function deleteBook($id) {
-        $("#deleteBookModal").find("input[name='identify']").val($id);
-        $("#deleteBookModal").modal("show");
+    function deleteBlog($id) {
+        $("#deleteBlogModal").find("input[name='blog_id']").val($id);
+        $("#deleteBlogModal").modal("show");
     }
     function copyBook($id){
         var index = layer.load()
@@ -329,180 +146,29 @@
     }
 
     $(function () {
-        $("#addBookDialogModal").on("show.bs.modal",function () {
-            window.bookDialogModal = $(this).find("#addBookDialogForm").html();
-            drawBookCover("bookCover","默认封面");
-        }).on("hidden.bs.modal",function () {
-            $(this).find("#addBookDialogForm").html(window.bookDialogModal);
-        });
-        /**
-         * 创建项目
-         */
-        $("body").on("click","#btnSaveDocument",function () {
-            var $this = $(this);
-
-
-            var bookName = $.trim($("#bookName").val());
-            if (bookName === "") {
-                return showError("项目标题不能为空")
-            }
-            if (bookName.length > 100) {
-                return showError("项目标题必须小于100字符");
-            }
-
-            var identify = $.trim($("#identify").val());
-            if (identify === "") {
-                return showError("项目标识不能为空");
-            }
-            if (identify.length > 50) {
-                return showError("项目标识必须小于50字符");
-            }
-            var description = $.trim($("#description").val());
-
-            if (description.length > 500) {
-                return showError("描述信息不超过500个字符");
-            }
 
-            $this.button("loading");
-
-            var draw = document.getElementById("bookCover");
-            var form = document.getElementById("addBookDialogForm");
-            var fd = new FormData(form);
-            if (draw.getContext) {
-                var dataURL = draw.toDataURL("png", 100);
-
-                var blob = dataURItoBlob(dataURL);
-
-                fd.append('image-file', blob,(new Date()).valueOf() + ".png");
-            }
-
-            $.ajax({
-                url : "{{urlfor "BookController.Create"}}",
-                data: fd,
-                type: "POST",
-                dataType :"json",
-                processData: false,
-                contentType: false
-            }).success(function (res) {
-                $this.button("reset");
-                if (res.errcode === 0) {
-                    window.app.lists.splice(0, 0, res.data);
-                    $("#addBookDialogModal").modal("hide");
-                } else {
-                    showError(res.message);
-                }
-                $this.button("reset");
-            }).error(function () {
-                $this.button("reset");
-                return showError("服务器异常");
-            });
-            return false;
-        });
-        /**
-         * 当填写项目标题后,绘制项目封面
-         */
-        $("#bookName").on("blur",function () {
-            var txt = $(this).val();
-            if(txt !== ""){
-                drawBookCover("bookCover",txt);
-            }
+        $("#blogList .delete-btn").click(function () {
+            deleteBlog($(this).attr("data-id"));
         });
         /**
          * 删除项目
          */
         $("#deleteBookForm").ajaxForm({
             beforeSubmit : function () {
-                $("#btnDeleteBook").button("loading");
+                $("#btnDeleteBlog").button("loading");
             },
-            success : function (res) {
-                if(res.errcode === 0){
+            success : function ($res) {
+                if($res.errcode === 0){
                     window.location = window.location.href;
                 }else{
                     showError(res.message,"#form-error-message2");
                 }
-                $("#btnDeleteBook").button("reset");
+                $("#btnDeleteBlog").button("reset");
             },
             error : function () {
                 showError("服务器异常","#form-error-message2");
-                $("#btnDeleteBook").button("reset");
-            }
-        });
-
-        $("#btnImportBook").on("click",function () {
-            var $this = $(this);
-            var $then = $(this).parents("#importBookDialogForm");
-
-
-            var bookName = $.trim($then.find("input[name='book_name']").val());
-
-            if (bookName === "") {
-                return showError("项目标题不能为空","#import-book-form-error-message");
-            }
-            if (bookName.length > 100) {
-                return showError("项目标题必须小于100字符","#import-book-form-error-message");
-            }
-
-            var identify = $.trim($then.find("input[name='identify']").val());
-            if (identify === "") {
-                return showError("项目标识不能为空","#import-book-form-error-message");
-            }
-            var description = $.trim($then.find('textarea[name="description"]').val());
-            if (description.length > 500) {
-                return showError("描述信息不超过500个字符","#import-book-form-error-message");
-            }
-            var filesCount = $('#import-book-upload').fileinput('getFilesCount');
-            console.log(filesCount)
-            if (filesCount <= 0) {
-                return showError("请选择需要上传的文件","#import-book-form-error-message");
-            }
-            //$("#importBookDialogForm").submit();
-            $("#btnImportBook").button("loading");
-            $('#import-book-upload').fileinput('upload');
-
-        });
-        window.app = new Vue({
-            el : "#bookList",
-            data : {
-                lists : {{.Result}}
-            },
-            delimiters : ['${','}'],
-            methods : {
-            }
-        });
-        Vue.nextTick(function () {
-            $("[data-toggle='tooltip']").tooltip();
-        });
-
-        $("#import-book-upload").fileinput({
-            'uploadUrl':"{{urlfor "BookController.Import"}}",
-            'theme': 'fa',
-            'showPreview': false,
-            'showUpload' : false,
-            'required': true,
-            'validateInitialCount': true,
-            "language" : "zh",
-            'allowedFileExtensions': ['zip'],
-            'msgPlaceholder' : '请选择Zip文件',
-            'elErrorContainer' : "#import-book-form-error-message",
-            'uploadExtraData' : function () {
-                var book = {};
-                var $then = $("#importBookDialogForm");
-                book.book_name = $then.find("input[name='book_name']").val();
-                book.identify = $then.find("input[name='identify']").val();
-                book.description = $then.find('textarea[name="description"]').val()
-
-                return book;
-            }
-        });
-        $("#import-book-upload").on("fileuploaded",function (event, data, previewId, index){
-
-            if(data.response.errcode === 0 || data.response.errcode === '0'){
-                showSuccess(data.response.message,"#import-book-form-error-message");
-            }else{
-                showError(data.response.message,"#import-book-form-error-message");
+                $("#btnDeleteBlog").button("reset");
             }
-            $("#btnImportBook").button("reset");
-            return true;
         });
     });
 </script>

+ 153 - 0
views/blog/manage_setting.tpl

@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    <title>文章设置 - Powered by MinDoc</title>
+
+    <!-- Bootstrap -->
+    <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/css/main.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]-->
+</head>
+<body>
+<div class="manual-reader">
+{{template "widgets/header.tpl" .}}
+    <div class="container manual-body">
+        <div class="row">
+            <div class="page-left">
+                <ul class="menu">
+                    <li {{if eq .ControllerName "BookController"}}class="active"{{end}}><a href="{{urlfor "BookController.Index"}}" class="item"><i class="fa fa-sitemap" aria-hidden="true"></i> 我的项目</a> </li>
+                    <li {{if eq .ControllerName "BlogController"}}class="active"{{end}}><a href="{{urlfor "BlogController.ManageList"}}" class="item"><i class="fa fa-file" aria-hidden="true"></i> 我的文章</a> </li>
+                </ul>
+            </div>
+            <div class="page-right">
+                <div class="m-box">
+                    <div class="box-head">
+                        <strong class="box-title"> 文章设置</strong>
+                    </div>
+                </div>
+                <div class="box-body">
+                    <form method="post" id="gloablEditForm" action="{{urlfor "BlogController.ManageSetting"}}">
+                        <input type="hidden" name="id" id="blogId" value="{{.Model.BlogId}}">
+                        <input type="hidden" name="identify" value="{{.Model.BlogIdentify}}">
+                        <div class="form-group">
+                            <label>文章标题</label>
+                            <input type="text" class="form-control" name="title" id="title" placeholder="文章标题" value="{{.Model.BlogTitle}}">
+                        </div>
+
+
+                        <div class="form-group">
+                            <label>文章类型</label>
+                            <div class="radio">
+                                <label class="radio-inline">
+                                    <input type="radio" {{if eq .Model.BlogType 0}}checked{{end}} name="blog_type" value="0">普通文章<span class="text"></span>
+                                </label>
+                                <label class="radio-inline">
+                                    <input type="radio" {{if eq .Model.BlogType 1}}checked{{end}} name="blog_type" value="1">链接文章<span class="text"></span>
+                                </label>
+                            </div>
+                        </div>
+                        <div class="form-group" id="blogLinkDocument"{{if ne .Model.BlogType 1}} style="display: none;" {{end}}>
+                            <label>关联文档</label>
+                            <div class="row">
+                                <div class="col-sm-6">
+                                    <input type="text" class="form-control" placeholder="请选择项目">
+                                </div>
+                                <div class="col-sm-6">
+                                    <input type="text" class="form-control" placeholder="请选择文档">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label>文章状态</label>
+                            <div class="radio">
+                                <label class="radio-inline">
+                                    <input type="radio" {{if eq .Model.BlogStatus "public"}}checked{{end}} name="status" value="public">公开<span class="text"></span>
+                                </label>
+                                <label class="radio-inline">
+                                    <input type="radio" {{if eq .Model.BlogStatus "password"}}checked{{end}} name="status" value="password">加密<span class="text"></span>
+                                </label>
+                            </div>
+                        </div>
+                        <div class="form-group"{{if eq .Model.BlogStatus "public"}} style="display: none;"{{end}} id="blogPassword">
+                            <label>文章密码</label>
+                            <input type="password" class="form-control" name="password" id="password" placeholder="文章密码" value="{{.Model.Password}}" maxlength="20">
+                        </div>
+                        <div class="form-group">
+                            <label>文章摘要</label>
+                            <textarea rows="3" class="form-control" name="excerpt" style="height: 90px" placeholder="项目描述">{{.Model.BlogExcerpt}}</textarea>
+                            <p class="text">文章摘要不超过500个字符</p>
+                        </div>
+
+                        <div class="form-group">
+                            <button type="submit" id="btnSaveBlogInfo" class="btn btn-success" data-loading-text="保存中...">保存</button>
+                            <a href="{{.Referer}}" title="返回" class="btn btn-info">返回</a>
+                            <span id="form-error-message" class="error-message"></span>
+                        </div>
+                    </form>
+
+                    <div class="clearfix"></div>
+
+                </div>
+            </div>
+        </div>
+    </div>
+{{template "widgets/footer.tpl" .}}
+</div>
+
+
+<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/main.js"}}" type="text/javascript"></script>
+<script type="text/javascript">
+    $(function () {
+        $("#gloablEditForm").ajaxForm({
+            beforeSubmit : function () {
+                var title = $.trim($("#title").val());
+
+                if (title === ""){
+                    return showError("文章标题不能为空");
+                }
+                $("#btnSaveBlogInfo").button("loading");
+            },success : function ($res) {
+                if($res.errcode === 0) {
+                    showSuccess("保存成功");
+                    $("#blogId").val($res.data.blog_id);
+                }else{
+                    showError($res.message);
+                }
+                $("#btnSaveBlogInfo").button("reset");
+            }, error : function () {
+                showError("服务器异常.");
+                $("#btnSaveBlogInfo").button("reset");
+            }
+        }).find("input[name='status']").change(function () {
+           var $status = $(this).val();
+           if($status === "password"){
+               $("#blogPassword").show();
+           }else{
+               $("#blogPassword").hide();
+           }
+        });
+        $("#gloablEditForm").find("input[name='blog_type']").change(function () {
+            var $link = $(this).val();
+            if($link == 1){
+                $("#blogLinkDocument").show();
+            }else{
+                $("#blogLinkDocument").hide();
+            }
+        });
+    });
+</script>
+</body>
+</html>

+ 3 - 1
views/manager/attach_detailed.tpl

@@ -55,13 +55,15 @@
                             {{end}}
                     </div>
                     <div class="form-group">
-                        <label>项目名称</label>
+                        <label>项目/文章名称</label>
                         <input type="text" value="{{.Model.BookName}}" class="form-control input-readonly" readonly placeholder="项目名称">
                     </div>
+                    {{if ne .Model.BookId 0}}
                     <div class="form-group">
                         <label>文档名称</label>
                         <input type="text" value="{{.Model.DocumentName}}" class="form-control input-readonly" readonly placeholder="文档名称">
                     </div>
+                    {{end}}
                     <div class="form-group">
                         <label>文件路径</label>
                         <input type="text" value="{{.Model.FilePath}}" class="form-control input-readonly" readonly placeholder="文件路径">

+ 1 - 1
views/manager/attach_list.tpl

@@ -48,7 +48,7 @@
                             <tr>
                                 <th>#</th>
                                 <th>附件名称</th>
-                                <th>项目名称</th>
+                                <th>项目/文章名称</th>
                                 <th>文件大小</th>
                                 <th>是否存在</th>
                                 <th>操作</th>