浏览代码

1、实现文章阅读、附件功能
2、实现与关联项目同步功能
3、实现附件删除功能

lifei6671 7 年之前
父节点
当前提交
2416af4528

+ 5 - 1
controllers/BaseController.go

@@ -157,6 +157,10 @@ func (c *BaseController) ShowErrorPage(errCode int, errMsg string) {
 	if err := beego.ExecuteViewPathTemplate(&buf, "errors/error.tpl", beego.BConfig.WebConfig.ViewsPath, map[string]interface{}{"ErrorMessage": errMsg, "ErrorCode": errCode, "BaseUrl": conf.BaseUrl}); err != nil {
 		c.Abort("500")
 	}
+	if errCode >= 200 && errCode <= 510 {
+		c.CustomAbort(errCode, buf.String())
+	}else{
+		c.CustomAbort(200, buf.String())
+	}
 
-	c.CustomAbort(200, buf.String())
 }

+ 160 - 15
controllers/BlogController.go

@@ -13,25 +13,89 @@ import (
 	"net/http"
 	"path/filepath"
 	"github.com/astaxie/beego/orm"
+	"html/template"
+	"encoding/json"
 )
 
 type BlogController struct{
 	BaseController
 }
 
-
+//文章阅读
 func (c *BlogController) Index() {
 	c.Prepare()
 	c.TplName = "blog/index.tpl"
+	blogId,_ := strconv.Atoi(c.Ctx.Input.Param(":id"))
+
+	if blogId <= 0{
+		c.ShowErrorPage(404,"页面不存在")
+	}
+	blogReadSession := fmt.Sprintf("blog:read:%d",blogId)
+
+	blog,err := models.NewBlog().Find(blogId)
+
+	if err != nil {
+		c.ShowErrorPage(404,"文章不存在")
+	}
+
+	if c.Ctx.Input.IsPost() {
+		password := c.GetString("password");
+
+		if blog.BlogStatus == "password" && password != blog.Password {
+			c.JsonResult(6001,"文章密码不正确")
+		}else if blog.BlogStatus == "password" && password == blog.Password {
+			//如果密码输入正确,则存入session中
+			c.CruSession.Set(blogReadSession,blogId)
+			c.JsonResult(0,"OK")
+		}
+		c.JsonResult(0,"OK")
+	}else if blog.BlogStatus == "password" && c.CruSession.Get(blogReadSession) == nil {
+		//如果不存在已输入密码的标记
+		c.TplName = "blog/index_password.tpl"
+	}
+	//加载文章附件
+	blog.LinkAttach();
+	c.Data["Model"] = blog
+	c.Data["Content"] = template.HTML(blog.BlogRelease)
+
+	if nextBlog,err := models.NewBlog().QueryNext(blogId);err == nil {
+		c.Data["Next"] = nextBlog
+	}
+	if preBlog,err := models.NewBlog().QueryPrevious(blogId);err == nil {
+		c.Data["Previous"] = preBlog
+	}
 }
 
+//文章列表
 func (c *BlogController) List() {
 	c.Prepare()
 	c.TplName = "blog/list.tpl"
+	pageIndex, _ := c.GetInt("page", 1)
+
+	var blogList []*models.Blog
+	var totalCount int
+	var  err error
+
+	blogList,totalCount,err = models.NewBlog().FindToPager(pageIndex,conf.PageSize,0,"")
+
+
+	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()
+		for _,blog := range blogList {
+			blog.Link()
+		}
+	} else {
+		c.Data["PageHtml"] = ""
+	}
 
+	c.Data["Lists"] = blogList
 }
 
-//管理后台
+//管理后台文章列表
 func (c *BlogController) ManageList() {
 	c.Prepare()
 	c.TplName = "blog/manage_list.tpl"
@@ -40,7 +104,6 @@ func (c *BlogController) ManageList() {
 
 	blogList,totalCount,err := models.NewBlog().FindToPager(pageIndex,conf.PageSize,c.Member.MemberId,"")
 
-	beego.Info(totalCount)
 	if err != nil {
 		c.ShowErrorPage(500,err.Error())
 	}
@@ -66,14 +129,17 @@ func (c *BlogController) ManageSetting() {
 		blogIdentify := c.GetString("identify")
 		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","")
+		documentIdentify := strings.TrimSpace(c.GetString("documentIdentify"))
+		bookIdentify := strings.TrimSpace(c.GetString("bookIdentify"))
+		documentId := 0
+
 		if blogTitle == "" {
 			c.JsonResult(6001,"文章标题不能为空")
 		}
-		if strings.Count(blogExcerpt,"") > 100 {
+		if strings.Count(blogExcerpt,"") > 500 {
 			c.JsonResult(6008,"文章摘要必须小于500字符")
 		}
 		if blogStatus != "public" && blogStatus != "password" && blogStatus != "draft" {
@@ -84,14 +150,33 @@ func (c *BlogController) ManageSetting() {
 		}
 		if blogType != 0 && blogType != 1 {
 			c.JsonResult(6005,"未知的文章类型")
-		}else if documentId <= 0 && blogType == 1 {
-			c.JsonResult(6006,"请选择链接的文章")
-		}else if blogType == 1 && documentId > 0 && !models.NewDocument().IsExist(documentId){
-			c.JsonResult(6007,"链接的文章不存在")
 		}
 		if strings.Count(blogTitle,"") > 200 {
 			c.JsonResult(6002,"文章标题不能大于200个字符")
 		}
+		//如果是关联文章,需要同步关联的文档
+		if blogType == 1 {
+			book,err := models.NewBook().FindByIdentify(bookIdentify)
+
+			if err != nil {
+				c.JsonResult(6011,"关联文档的项目不存在")
+			}
+			doc,err := models.NewDocument().FindByIdentityFirst(documentIdentify,book.BookId)
+			if  err != nil {
+				c.JsonResult(6003,"查询关联项目文档时出错")
+			}
+			documentId = doc.DocumentId
+
+			// 如果不是超级管理员,则校验权限
+			if !c.Member.IsAdministrator() {
+				bookResult, err := models.NewBookResult().FindByIdentify(book.Identify, c.Member.MemberId)
+
+				if err != nil || bookResult.RoleId == conf.BookObserver {
+					c.JsonResult(6002, "关联文档不存在或权限不足")
+				}
+			}
+		}
+
 		var blog *models.Blog
 		var err error
 		//如果文章ID存在,则从数据库中查询文章
@@ -157,11 +242,14 @@ func (c *BlogController) ManageSetting() {
 	}
 	blogId,err := strconv.Atoi(c.Ctx.Input.Param(":id"))
 
+	c.Data["DocumentIdentify"] = "";
+
 	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()
@@ -199,12 +287,28 @@ func (c *BlogController) ManageEdit() {
 		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,"查询关联项目文档时出错")
 			}
+			book, err := models.NewBook().Find(doc.BookId)
+			if err != nil {
+				c.JsonResult(6002, "项目不存在或权限不足")
+			}
+
+			// 如果不是超级管理员,则校验权限
+			if !c.Member.IsAdministrator() {
+				bookResult, err := models.NewBookResult().FindByIdentify(book.Identify, c.Member.MemberId)
+
+				if err != nil || bookResult.RoleId == conf.BookObserver {
+					beego.Error("FindByIdentify => ", err)
+					c.JsonResult(6002, "关联文档不存在或权限不足")
+				}
+			}
+
 			doc.Markdown = blogContent
 			doc.Release = blogHtml
 			doc.Content = blogHtml
@@ -246,6 +350,19 @@ func (c *BlogController) ManageEdit() {
 	if err != nil {
 		c.ShowErrorPage(404,"文章不存在或已删除")
 	}
+	blog.LinkAttach()
+
+	if len(blog.AttachList) > 0 {
+		returnJSON, err := json.Marshal(blog.AttachList)
+		if err != nil {
+			beego.Error("序列化文章附件时出错 ->",err)
+		}else{
+			c.Data["AttachList"] = template.JS(string(returnJSON))
+		}
+	}else{
+		c.Data["AttachList"] = template.JS("[]")
+	}
+
 	c.Data["Model"] = blog
 }
 
@@ -286,6 +403,15 @@ func (c *BlogController) Upload() {
 		c.JsonResult(6001, "参数错误")
 	}
 
+	blog,err := models.NewBlog().Find(blogId)
+
+	if err != nil {
+		c.JsonResult(6010,"文章不存在")
+	}
+	if !c.Member.IsAdministrator() && blog.MemberId != c.Member.MemberId {
+		c.JsonResult(6011,"没有文章的访问权限")
+	}
+
 	name := "editormd-file-file"
 
 	file, moreFile, err := c.GetFile(name)
@@ -361,7 +487,7 @@ func (c *BlogController) Upload() {
 
 	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, "//") {
@@ -375,7 +501,11 @@ func (c *BlogController) Upload() {
 		attachment.FileExt = ext
 		attachment.FilePath = strings.TrimPrefix(filePath, conf.WorkingDirectory)
 		attachment.DocumentId = blogId
-
+		//如果是关联文章,则将附件设置为关联文档的文档上
+		if blog.BlogType == 1 {
+			attachment.BookId = blog.BookId
+			attachment.DocumentId = blog.DocumentId
+		}
 		if fileInfo, err := os.Stat(filePath); err == nil {
 			attachment.FileSize = float64(fileInfo.Size())
 		}
@@ -413,11 +543,19 @@ func (c *BlogController) Upload() {
 func (c *BlogController) RemoveAttachment() {
 	c.Prepare()
 	attachId, _ := c.GetInt("attach_id")
+	blogId, _ := strconv.Atoi(c.Ctx.Input.Param(":id"))
 
 	if attachId <= 0 {
 		c.JsonResult(6001, "参数错误")
 	}
-
+	blog, err := models.NewBlog().Find(blogId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			c.ShowErrorPage(500, "文档不存在")
+		} else {
+			c.ShowErrorPage(500, "查询文章时异常")
+		}
+	}
 	attach, err := models.NewAttachment().Find(attachId)
 
 	if err != nil {
@@ -434,6 +572,11 @@ func (c *BlogController) RemoveAttachment() {
 		}
 	}
 
+	if blog.BlogType == 1 && attach.BookId != blog.BookId && attach.DocumentId != blog.DocumentId {
+		c.ShowErrorPage(404,"附件不存在")
+	}else if attach.BookId !=0 || attach.DocumentId != blogId {
+		c.ShowErrorPage(404,"附件不存在")
+	}
 
 	if err := attach.Delete();err != nil {
 		beego.Error(err)
@@ -461,8 +604,9 @@ func (c *BlogController) Download() {
 			c.ShowErrorPage(500, "查询文章时异常")
 		}
 	}
+	blogReadSession := fmt.Sprintf("blog:read:%d",blogId)
 
-	if (c.Member != nil && !c.Member.IsAdministrator()) || ( blog.BlogStatus == "password" && password != blog.Password) {
+	if (c.Member != nil && !c.Member.IsAdministrator()) || ( blog.BlogStatus == "password" && password != blog.Password && c.CruSession.Get(blogReadSession) == nil) {
 		c.ShowErrorPage(403, "没有下载权限")
 	}
 
@@ -477,8 +621,9 @@ func (c *BlogController) Download() {
 			c.ShowErrorPage(500,"查询附件时出现异常")
 		}
 	}
-
-	if attachment.BookId !=0 || attachment.DocumentId != blogId {
+	if blog.BlogType == 1 && attachment.BookId != blog.BookId && attachment.DocumentId != blog.DocumentId {
+		c.ShowErrorPage(404,"附件不存在")
+	}else if attachment.BookId !=0 || attachment.DocumentId != blogId {
 		c.ShowErrorPage(404,"附件不存在")
 	}
 

+ 110 - 6
models/Blog.go

@@ -18,10 +18,18 @@ type Blog struct {
 	OrderIndex int 		`orm:"column(order_index);type(int);default(0)" json:"order_index"`
 	//所属用户
 	MemberId  int		`orm:"column(member_id);type(int);default(0):index" json:"member_id"`
+	//用户头像
+	MemberAvatar string		`orm:"-" json:"member_avatar"`
 	//文章类型:0 普通文章/1 链接文章
 	BlogType int		`orm:"column(blog_type);type(int);default(0)" json:"blog_type"`
 	//链接到的项目中的文档ID
 	DocumentId int		`orm:"column(document_id);type(int);default(0)" json:"document_id"`
+	//文章的标识
+	DocumentIdentify string `orm:"-" json:"document_identify"`
+	//关联文档的项目标识
+	BookIdentify string 	`orm:"-" json:"book_identify"`
+	//关联文档的项目ID
+	BookId int 				`orm:"-" json:"book_id"`
 	//文章摘要
 	BlogExcerpt string	`orm:"column(blog_excerpt);size(1500)" json:"blog_excerpt"`
 	//文章内容
@@ -42,6 +50,8 @@ type Blog struct {
 	CreateName string 	`orm:"-" json:"create_name"`
 	//版本号
 	Version    int64    `orm:"type(bigint);column(version)" json:"version"`
+	//附件列表
+	AttachList []*Attachment `orm:"-" json:"attach_list"`
 }
 
 // 多字段唯一键
@@ -81,7 +91,8 @@ func (b *Blog) Find(blogId int) (*Blog,error) {
 		return nil,err
 	}
 
-	return b,nil
+
+	return b.Link()
 }
 //查找指定用户的指定文章
 func (b *Blog) FindByIdAndMemberId(blogId,memberId int) (*Blog,error) {
@@ -93,7 +104,7 @@ func (b *Blog) FindByIdAndMemberId(blogId,memberId int) (*Blog,error) {
 		return nil,err
 	}
 
-	return b,nil
+	return b.Link()
 }
 //根据文章标识查询文章
 func (b *Blog) FindByIdentify(identify string) (*Blog,error) {
@@ -106,18 +117,49 @@ func (b *Blog) FindByIdentify(identify string) (*Blog,error) {
 	}
 	return b,nil
 }
+
 //获取指定文章的链接内容
 func (b *Blog)Link() (*Blog,error)  {
 	o := orm.NewOrm()
 	//如果是链接文章,则需要从链接的项目中查找文章内容
-	if b.BlogType == 1 && b.DocumentId > 0{
+	if b.BlogType == 1 && b.DocumentId > 0 {
 		doc := NewDocument()
-		if err := o.QueryTable(doc.TableNameWithPrefix()).Filter("document_id",b.DocumentId).One(doc,"release","markdown");err != nil {
+		if err := o.QueryTable(doc.TableNameWithPrefix()).Filter("document_id",b.DocumentId).One(doc,"release","markdown","identify","book_id");err != nil {
 			beego.Error("查询文章链接对象时出错 -> ",err)
 		}else{
+			b.DocumentIdentify = doc.Identify
 			b.BlogRelease = doc.Release
 			//目前仅支持markdown文档进行链接
 			b.BlogContent = doc.Markdown
+			book := NewBook()
+			if err := o.QueryTable(book.TableNameWithPrefix()).Filter("book_id",doc.BookId).One(book,"identify");err != nil {
+				beego.Error("查询关联文档的项目时出错 ->",err)
+			}else{
+				b.BookIdentify = book.Identify
+				b.BookId = doc.BookId
+			}
+		}
+	}
+
+	if b.ModifyAt > 0{
+		member := NewMember()
+		if err := o.QueryTable(member.TableNameWithPrefix()).Filter("member_id",b.ModifyAt).One(member,"real_name","account"); err == nil {
+			if member.RealName != ""{
+				b.ModifyRealName = member.RealName
+			}else{
+				b.ModifyRealName = member.Account
+			}
+		}
+	}
+	if b.MemberId > 0 {
+		member := NewMember()
+		if err := o.QueryTable(member.TableNameWithPrefix()).Filter("member_id",b.MemberId).One(member,"real_name","account","avatar"); err == nil {
+			if member.RealName != ""{
+				b.CreateName = member.RealName
+			}else{
+				b.CreateName = member.Account
+			}
+			b.MemberAvatar = member.Avatar
 		}
 	}
 
@@ -130,11 +172,12 @@ 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 {
+	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;
@@ -149,11 +192,13 @@ func (b *Blog) Save(cols ...string) error {
 		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) {
 
@@ -171,7 +216,7 @@ func (b *Blog) FindToPager(pageIndex, pageSize int,memberId int,status string) (
 	}
 
 
-	_,err = query.OrderBy("-order_index").Offset(offset).Limit(pageSize).All(&blogList)
+	_,err = query.OrderBy("-order_index","-blog_id").Offset(offset).Limit(pageSize).All(&blogList)
 
 	if err != nil {
 		if err == orm.ErrNoRows {
@@ -205,4 +250,63 @@ func (b *Blog) Delete(blogId int) error {
 		beego.Error("删除文章失败 ->",err)
 	}
 	return err
+}
+
+//查询下一篇文章
+func (b *Blog) QueryNext(blogId int) (*Blog,error) {
+	o := orm.NewOrm()
+	blog := NewBlog()
+
+	if err := o.QueryTable(b.TableNameWithPrefix()).Filter("blog_id",blogId).One(blog,"order_index"); err != nil {
+		beego.Error("查询文章时出错 ->",err)
+		return b,err
+	}
+
+	err := o.QueryTable(b.TableNameWithPrefix()).Filter("order_index__gte",blog.OrderIndex).Filter("blog_id__gt",blogId).OrderBy("-order_index","-blog_id").One(blog)
+
+	if err != nil {
+		beego.Error("查询文章时出错 ->",err)
+	}
+	return blog,err
+}
+
+//查询下一篇文章
+func (b *Blog) QueryPrevious(blogId int) (*Blog,error) {
+	o := orm.NewOrm()
+	blog := NewBlog()
+
+	if err := o.QueryTable(b.TableNameWithPrefix()).Filter("blog_id",blogId).One(blog,"order_index"); err != nil {
+		beego.Error("查询文章时出错 ->",err)
+		return b,err
+	}
+
+	err := o.QueryTable(b.TableNameWithPrefix()).Filter("order_index__lte",blog.OrderIndex).Filter("blog_id__lt",blogId).OrderBy("-order_index","-blog_id").One(blog)
+
+	if err != nil {
+		beego.Error("查询文章时出错 ->",err)
+	}
+	return blog,err
+}
+
+//关联文章附件
+func (b *Blog) LinkAttach() (err error) {
+
+	o := orm.NewOrm()
+
+	var attachList []*Attachment
+	//当不是关联文章时,用文章ID去查询附件
+	if b.BlogType != 1 || b.DocumentId <= 0 {
+		_, err = o.QueryTable(NewAttachment().TableNameWithPrefix()).Filter("document_id", b.BlogId).Filter("book_id",0).All(&attachList)
+		if err != nil {
+			beego.Error("查询文章附件时出错 ->", err)
+		}
+	}else {
+		_, err = o.QueryTable(NewAttachment().TableNameWithPrefix()).Filter("document_id", b.DocumentId).Filter("book_id", b.BookId).All(&attachList)
+
+		if err != nil {
+			beego.Error("查询文章附件时出错 ->", err)
+		}
+	}
+	b.AttachList = attachList
+	return
 }

+ 1 - 0
models/DocumentModel.go

@@ -220,6 +220,7 @@ func (m *Document) FindListByBookId(bookId int) (docs []*Document, err error) {
 
 	return
 }
+
 //判断文章是否存在
 func (m *Document) IsExist(documentId int) bool {
 	o := orm.NewOrm()

+ 2 - 0
routers/router.go

@@ -69,6 +69,8 @@ func init() {
 	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("/blogs/attach/:id",&controllers.BlogController{}, "post:RemoveAttachment")
+
 
 	//读文章的路由
 	beego.Router("/blog", &controllers.BlogController{}, "*:List")

+ 15 - 0
static/js/blog.js

@@ -146,4 +146,19 @@ $(function () {
         }
         window.isLoad = false;
     }
+    /**
+     * 打开文档模板
+     */
+    $("#documentTemplateModal").on("click", ".section>a[data-type]", function () {
+        var $this = $(this).attr("data-type");
+        var body = $("#template-" + $this).html();
+        if (body) {
+            window.isLoad = true;
+            window.editor.clear();
+            window.editor.insertValue(body);
+            window.editor.setCursor({ line: 0, ch: 0 });
+            resetEditorChanged(true);
+        }
+        $("#documentTemplateModal").modal('hide');
+    });
 });

+ 143 - 0
views/blog/index.tpl

@@ -1 +1,144 @@
+<!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">
+    <meta name="renderer" content="webkit">
+    <meta name="author" content="Minho" />
+    <meta name="site" content="https://www.iminho.me" />
+    <meta name="keywords" content="{{.Model.BlogTitle}}">
+    <meta name="description" content="{{.Model.BlogExcerpt}}">
+    <title>{{.Model.BlogTitle}} - Powered by MinDoc</title>
 
+    <!-- Bootstrap -->
+    <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
+
+    <link href="{{cdncss "/static/jstree/3.3.4/themes/default/style.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/nprogress/nprogress.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/css/kancloud.css"}}?_=1531286622" rel="stylesheet">
+    <link href="{{cdncss "/static/css/jstree.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/editor.md/css/editormd.preview.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/prettify/themes/prettify.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/css/markdown.preview.css?_=1531286622"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/highlight/styles/vs.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/katex/katex.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/css/print.css"}}" media="print" 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]-->
+    <style type="text/css">
+        .header{
+            min-height: 1rem;
+            font-size: 26px;
+            font-weight: 400;
+            display: block;
+            margin: 20px auto;
+        }
+        .blog-meta{
+            display: inline-block;
+        }
+        .blog-meta>.item{
+            display: inline-block;
+            color: #666666;
+            vertical-align: middle;
+        }
+        .blog-footer{
+            margin: 25px auto;
+            /*border-top: 1px solid #E5E5E5;*/
+            padding: 20px 1px;
+            line-height: 35px;
+        }
+        .blog-footer span{
+            margin-right: 8px;
+            padding: 6px 8px;
+            font-size: 12px;
+            border: 1px solid #e3e3e3;
+            color: #4d4d4d
+        }
+        .blog-footer a:hover{
+            color: #ca0c16;
+        }
+        .footer{
+            margin-top: 0;
+        }
+        .user_img img {
+            display: block;
+            width: 24px;
+            height: 24px;
+            border-radius: 50%;
+            -o-object-fit: cover;
+            object-fit: cover;
+            overflow: hidden;
+        }
+    </style>
+</head>
+<body>
+<div class="manual-reader manual-container manual-search-reader">
+{{template "widgets/header.tpl" .}}
+    <div class="container manual-body">
+        <div class="search-head" style="border-bottom-width: 1px;">
+            <h1 class="header">
+               {{.Model.BlogTitle}}
+            </h1>
+            <div class="blog-meta">
+                <div class="item user_img"><img src="{{.Model.MemberAvatar}}" align="{{.Model.CreateName}}"> </div>
+                <div class="item">&nbsp;{{.Model.CreateName}}</div>
+                <div class="item">发布于</div>
+                <div class="item">{{date .Model.Created "Y-m-d H:i:s"}}</div>
+                <div class="item">{{.Model.ModifyRealName}}</div>
+                <div class="item">修改于</div>
+                <div class="item">{{date .Model.Modified "Y-m-d H:i:s"}}</div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="article-body  markdown-body editormd-preview-container content">
+                {{.Content}}
+                {{if .Model.AttachList}}
+                <div class="attach-list"><strong>附件</strong><ul>
+                {{range $index,$item := .Model.AttachList}}
+                <li><a href="{{$item.HttpPath}}" title="{{$item.FileName}}">{{$item.FileName}}</a> </li>
+                {{end}}
+                </ul>
+                {{end}}
+            </div>
+        </div>
+        <div class="row blog-footer">
+            <p>
+                <span>上一篇</span>
+            {{if .Previous}}
+                <a href="{{urlfor "BlogController.Index" ":id" .Previous.BlogId}}" title="{{.Previous.BlogTitle}}">{{.Previous.BlogTitle}}
+                </a>
+            {{else}}
+               无
+            {{end}}
+            </p>
+            <p>
+                <span>下一篇</span>
+            {{if .Next}}
+                <a href="{{urlfor "BlogController.Index" ":id" .Next.BlogId}}" title="{{.Next.BlogTitle}}">{{.Next.BlogTitle}}</a>
+            {{else}}
+                无
+            {{end}}
+            </p>
+        </div>
+    </div>
+{{template "widgets/footer.tpl" .}}
+</div>
+<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
+<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
+<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/highlight/highlight.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/highlight/highlightjs-line-numbers.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/jquery.highlight.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/kancloud.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/splitbar.js"}}" type="text/javascript"></script>
+</body>
+</html>

+ 138 - 0
views/blog/index_password.tpl

@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang="zh-cn">
+<head>
+    <meta charset="utf-8">
+    <link rel="shortcut icon" href="{{cdnimg "favicon.ico"}}">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="renderer" content="webkit" />
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>请输入文章密码 - Powered by MinDoc</title>
+    <script src="{{cdnjs "static/jquery/1.12.4/jquery.min.js"}}"></script>
+    <script src="{{cdnjs "static/js/jquery.form.js"}}"></script>
+    <style type="text/css">
+    body{ background: #f2f2f2;}
+    .d_button{ cursor: pointer;}
+    @media(min-width : 450px){
+        .auth_form{
+            width: 400px;
+            border: 1px solid #ccc;
+            background-color: #fff;
+            position: absolute;
+            top: 20%;
+            left: 50%;
+            margin-left: -220px;
+            padding: 20px;
+        }
+    .tit{
+        font-size: 18px;
+    }
+        .inp{
+            height: 30px;
+            width: 387px;
+            font-size: 14px;
+            padding: 5px;
+        }
+        .btn{
+            margin-top: 10px;
+            float: right;
+        }
+    }
+    @media(max-width : 449px){
+        body{
+            margin: 0 auto;
+        }
+    .auth_form{
+        background-color: #fff;
+        border-top: 1px solid #ccc;
+        border-bottom: 1px solid #ccc;
+        width: 100%;
+        margin-top: 40px;
+    }
+        .shell{
+            width: 90%;
+            margin: 10px auto;
+        }
+        .tit{
+            font-size: 18px;
+        }
+        .inp{
+            height: 30px;
+            width: 96.5%;
+            font-size: 14px;
+            padding: 5px;
+        }
+        .btn{
+            margin-top: 10px;
+            float: right;
+        }
+    }
+    .clear{
+        clear: both;
+    }
+    .button {
+        color: #fff;
+        background-color: #428bca;
+        border-radius: 4px;
+        display: inline-block;
+        padding: 6px 12px;
+        margin-bottom: 0;
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 1.42857143;
+        text-align: center;
+        white-space: nowrap;
+        vertical-align: middle;
+        -ms-touch-action: manipulation;
+        touch-action: manipulation;
+        cursor: pointer;
+        -webkit-user-select: none;
+        -moz-user-select: none;
+        -ms-user-select: none;
+        user-select: none;
+        border: 1px solid #357ebd;
+    }
+    </style>
+</head>
+<body>
+<div class="auth_form">
+<div class="shell">
+        <form action="{{urlfor "BlogController.Index" ":id" .Model.BlogId}}" method="post" id="auth_form">
+        <input type="hidden" value="{{.Model.BlogId}}" name="blogId" />
+    <div class="tit">
+        请输入浏览密码
+    </div>
+    <div style="margin-top: 10px;">
+        <input type="password" name="password" placeholder="浏览密码" class="inp"/>
+    </div>
+    <div class="btn">
+        <span id="error" style="color: #919191; font-size: 13px;"></span>
+        <input type="submit" value="提交" class="button"/>
+    </div>
+    <div class="clear"></div>
+</form>
+</div>
+</div>
+<script type="text/javascript">
+$("#auth_form").ajaxForm({
+    beforeSerialize: function () {
+        var pwd = $("#auth_form input[name='password']").val();
+        if (pwd === "") {
+            $("#error").html("请输入密码");
+            return false;
+        }
+    },
+    dataType: "json",
+    success: function (res) {
+        if (res.errcode === 0) {
+            window.location.reload();
+        } else {
+            $("#auth_form input[name='password']").val("").focus();
+            $("#error").html(res.message);
+        }
+    }
+});
+</script>
+
+        <script src="{{cdnjs "static/bootstrap/js/bootstrap.min.js"}}"></script>
+</body>
+</html>

+ 67 - 0
views/blog/list.tpl

@@ -0,0 +1,67 @@
+<!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]-->
+    <style type="text/css">
+        .footer{margin-top: 0;}
+        .label {
+            background-color: #00b5ad!important;
+            border-color: #00b5ad!important;
+            color: #fff!important;
+            font-weight: 400;
+        }
+    </style>
+</head>
+<body>
+<div class="manual-reader manual-container manual-search-reader">
+{{template "widgets/header.tpl" .}}
+    <div class="container manual-body">
+        <div class="row">
+            <div class="manual-list">
+            {{range $index,$item := .Lists}}
+                <div class="search-item">
+                    <div class="title">{{if eq $item.BlogStatus "password"}}<span class="label">密</span>{{end}} <a href="{{urlfor "BlogController.Index" ":id" $item.BlogId}}" title="{{$item.BlogTitle}}" target="_blank">{{$item.BlogTitle}}</a> </div>
+                    <div class="description">
+                    {{$item.BlogExcerpt}}
+                    </div>
+                    {{/*<div class="site">{{urlfor "BlogController.Index" ":id" $item.BlogId}}</div>*/}}
+                    <div class="source">
+                        <span class="item">作者:{{$item.CreateName}}</span>
+                        <span class="item">更新时间:{{date_format  $item.Modified "2006-01-02 15:04:05"}}</span>
+                    </div>
+                </div>
+            {{else}}
+                <div class="search-empty">
+                    <img src="{{cdnimg "/static/images/search_empty.png"}}" class="empty-image">
+                    <span class="empty-text">暂无文章</span>
+                </div>
+            {{end}}
+                <nav class="pagination-container">
+                {{.PageHtml}}
+                </nav>
+                <div class="clearfix"></div>
+            </div>
+        </div>
+    </div>
+{{template "widgets/footer.tpl" .}}
+</div>
+<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
+<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
+</body>
+</html>

+ 2 - 2
views/blog/manage_edit.tpl

@@ -14,7 +14,7 @@
         window.fileUploadURL = "";
         window.blogId = {{.Model.BlogId}};
         window.blogVersion = {{.Model.Version}};
-        window.removeAttachURL = "{{urlfor "DocumentController.RemoveAttachment"}}";
+        window.removeAttachURL = "{{urlfor "BlogController.RemoveAttachment" ":id" .Model.BlogId}}";
     </script>
     <!-- Bootstrap -->
     <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
@@ -223,7 +223,7 @@
 <script src="{{cdnjs "/static/js/blog.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
     $(function () {
-
+        window.vueApp.lists = {{.AttachList}};
         $("#attachInfo").on("click",function () {
             $("#uploadAttachModal").modal("show");
         });

+ 1 - 1
views/blog/manage_list.tpl

@@ -57,7 +57,7 @@
                                         <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 href="{{urlfor "BlogController.ManageEdit" ":id" $item.BlogId}}" title="文章编辑" target="_blank"><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>

+ 5 - 2
views/blog/manage_setting.tpl

@@ -39,6 +39,8 @@
                     <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}}">
+                        <input type="hidden" name="document_id" value="{{.Model.DocumentId}}">
+                        <input type="hidden" name="order_index" value="{{.Model.OrderIndex}}">
                         <div class="form-group">
                             <label>文章标题</label>
                             <input type="text" class="form-control" name="title" id="title" placeholder="文章标题" value="{{.Model.BlogTitle}}">
@@ -60,12 +62,13 @@
                             <label>关联文档</label>
                             <div class="row">
                                 <div class="col-sm-6">
-                                    <input type="text" class="form-control" placeholder="请选择项目">
+                                    <input type="text" class="form-control" placeholder="请输入项目标识" name="bookIdentify" value="{{.Model.BookIdentify}}">
                                 </div>
                                 <div class="col-sm-6">
-                                    <input type="text" class="form-control" placeholder="请选择文档">
+                                    <input type="text" class="form-control" placeholder="请输入文档标识" name="documentIdentify" value="{{.Model.DocumentIdentify}}">
                                 </div>
                             </div>
+
                         </div>
                         <div class="form-group">
                             <label>文章状态</label>

+ 3 - 2
views/label/list.tpl

@@ -28,16 +28,17 @@
             <strong class="search-title">显示标签列表</strong>
         </div>
         <div class="row">
-            {{if gt (.Labels|len) 0}}
+
             <div class="hide tag-container-outer" style="border: 0;margin-top: 0;padding: 5px 15px;min-height: 200px;">
                 <span class="tags">
                     {{range  $index,$item := .Labels}}
                     <a href="{{urlfor "LabelController.Index" ":key" $item.LabelName}}">{{$item.LabelName}}<span class="detail">{{$item.BookNumber}}</span></a>
+                    {{else}}
+                    <div class="text-center">暂无标签</div>
                     {{end}}
                 </span>
             </div>
 
-            {{end}}
             <nav class="pagination-container">
                 {{if gt .TotalPages 1}}
                 {{.PageHtml}}

+ 0 - 1
views/search/index.tpl

@@ -34,7 +34,6 @@
                     <div class="description">
                         {{str2html $item.Description}}
                     </div>
-                    <div class="site">{{$.BaseUrl}}{{urlfor "DocumentController.Read" ":key" $item.BookIdentify ":id" $item.Identify}}</div>
                     <div class="source">
                         <span class="item">来自:<a href="{{urlfor "DocumentController.Index" ":key" $item.BookIdentify}}" target="_blank">{{$item.BookName}}</a></span>
                         <span class="item">作者:{{$item.Author}}</span>

+ 1 - 1
views/widgets/header.tpl

@@ -13,7 +13,7 @@
                     <li {{if eq .ControllerName "HomeController"}}class="active"{{end}}>
                         <a href="{{urlfor "HomeController.Index" }}" title="首页">首页</a>
                     </li>
-                    <li {{if eq .ControllerName "BlogController"}}{{if eq  .ActionName "List"}}class="active"{{end}}{{end}}>
+                    <li {{if eq .ControllerName "BlogController"}}{{if eq  .ActionName "List" "Index"}}class="active"{{end}}{{end}}>
                         <a href="{{urlfor "BlogController.List" }}" title="文章">文章</a>
                     </li>
                     <li {{if eq .ControllerName "LabelController"}}class="active"{{end}}>