Minho 8 年 前
コミット
f91ad51e3f

+ 9 - 1
conf/app.conf.example

@@ -18,5 +18,13 @@ db_port=3306
 db_database=webhook_db
 db_username=root
 db_password=123456
+db_prefix=md_
 
-queue_size=50
+#项目默认封面
+cover=/static/images/book.jpg
+
+#默认头像
+avatar=/static/images/headimgurl.jpg
+
+#默认阅读令牌长度
+token_size=12

+ 8 - 0
conf/enumerate.go

@@ -45,4 +45,12 @@ func GetDatabasePrefix() string  {
 //获取默认头像
 func GetDefaultAvatar() string {
 	return beego.AppConfig.DefaultString("avatar","/static/images/headimgurl.jpg")
+}
+
+func GetTokenSize() int {
+	return beego.AppConfig.DefaultInt("token_size",12)
+}
+
+func GetDefaultCover() string {
+	return beego.AppConfig.DefaultString("cover","/static/images/book.jpg")
 }

+ 278 - 58
controllers/book.go

@@ -7,6 +7,10 @@ import (
 	"time"
 	"encoding/json"
 	"html/template"
+	"errors"
+	"fmt"
+	"path/filepath"
+	"os"
 
 	"github.com/lifei6671/godoc/models"
 	"github.com/lifei6671/godoc/utils"
@@ -14,6 +18,7 @@ import (
 	"github.com/astaxie/beego/orm"
 	"github.com/astaxie/beego/logs"
 	"github.com/lifei6671/godoc/conf"
+	"github.com/lifei6671/godoc/graphics"
 )
 
 type BookController struct {
@@ -83,14 +88,235 @@ func (c *BookController) Setting()  {
 
 	book,err := models.NewBookResult().FindByIdentify(key,c.Member.MemberId)
 	if err != nil {
+		if err == orm.ErrNoRows {
+			c.Abort("404")
+		}
 		if err == models.ErrPermissionDenied {
 			c.Abort("403")
 		}
 		c.Abort("500")
 	}
+	//如果不是创始人也不是管理员则不能操作
+	if book.RoleId != conf.BookFounder && book.RoleId != conf.BookAdmin {
+		c.Abort("403")
+	}
+	if book.PrivateToken != "" {
+		book.PrivateToken = c.BaseUrl() + beego.URLFor("DocumentController.Index",":key",book.Identify,"token",book.PrivateToken)
+	}
+	c.Data["Model"] = book
 
-	c.Data["Model"] = *book
+}
+
+//保存项目信息
+func (c *BookController) SaveBook()  {
+	bookResult,err := c.IsPermission()
+
+	if err != nil {
+		c.JsonResult(6001,err.Error())
+	}
+	book,err := models.NewBook().Find(bookResult.BookId)
+
+	if err != nil {
+		logs.Error("SaveBook => ",err)
+		c.JsonResult(6002,err.Error())
+	}
+
+	book_name := strings.TrimSpace(c.GetString("book_name"))
+	description := strings.TrimSpace(c.GetString("description",""))
+	comment_status := c.GetString("comment_status")
+	tag := strings.TrimSpace(c.GetString("label"))
+
+	if strings.Count(description,"") > 500 {
+		c.JsonResult(6004,"项目描述不能大于500字")
+	}
+	if comment_status != "open" && comment_status != "closed" && comment_status != "group_only" && comment_status != "registered_only" {
+		comment_status = "closed"
+	}
+	if tag != ""{
+		tags := strings.Split(tag,";")
+		if len(tags) > 10 {
+			c.JsonResult(6005,"最多允许添加10个标签")
+		}
+	}
+
+
+	book.BookName = book_name
+	book.Description = description
+	book.CommentStatus = comment_status
+	book.Label = tag
+
+	if err := book.Update();err != nil {
+		c.JsonResult(6006,"保存失败")
+	}
+	bookResult.BookName = book_name
+	bookResult.Description = description
+	bookResult.CommentStatus = comment_status
+	bookResult.Label = tag
+	c.JsonResult(0,"ok",bookResult)
+}
+
+func (c *BookController) PrivatelyOwned()  {
+
+	status := c.GetString("status")
+
+	if status != "open" && status != "close" {
+		c.JsonResult(6003,"参数错误")
+	}
+	state := 0
+	if status == "open" {
+		state = 0
+	}else{
+		state = 1
+	}
+
+	bookResult,err := c.IsPermission()
+
+	if err != nil {
+		c.JsonResult(6001,err.Error())
+	}
+	//只有创始人才能变更私有状态
+	if bookResult.RoleId != conf.BookFounder {
+		c.JsonResult(6002,"权限不足")
+	}
+	fmt.Printf("%+v",bookResult)
+
+	book,err := models.NewBook().Find(bookResult.BookId)
+
+	if err != nil {
+		c.JsonResult(6005,"项目不存在")
+	}
+	book.PrivatelyOwned = state
+
+	logs.Info("",state,status)
+
+	err = book.Update()
+
+	if err != nil {
+		logs.Error("PrivatelyOwned => ",err)
+		c.JsonResult(6004,"保存失败")
+	}
+	c.JsonResult(0,"ok")
+}
+
+// Transfer 转让项目.
+func (c *BookController) Transfer()  {
+	c.Prepare()
+	account := c.GetString("account")
+
+	if account == "" {
+		c.JsonResult(6004,"接受者账号不能为空")
+	}
+	member,err := models.NewMember().FindByAccount(account)
+
+	if err != nil {
+		logs.Error("FindByAccount => ",err)
+		c.JsonResult(6005,"接受用户不存在")
+	}
+	if member.Status != 0 {
+		c.JsonResult(6006,"接受用户已被禁用")
+	}
+	if member.MemberId == c.Member.MemberId {
+		c.JsonResult(6007,"不能转让给自己")
+	}
+	bookResult,err := c.IsPermission()
+
+	if err != nil {
+		c.JsonResult(6001,err.Error())
+	}
+
+	err = models.NewRelationship().Transfer(bookResult.BookId,c.Member.MemberId,member.MemberId)
+
+	if err != nil {
+		logs.Error("Transfer => ",err)
+		c.JsonResult(6008,err.Error())
+	}
+	c.JsonResult(0,"ok")
+}
+//上传项目封面.
+func (c *BookController) UploadCover()  {
+
+	bookResult,err := c.IsPermission()
+
+	if err != nil {
+		c.JsonResult(6001,err.Error())
+	}
+	book,err := models.NewBook().Find(bookResult.BookId)
+
+	if err != nil {
+		logs.Error("SaveBook => ",err)
+		c.JsonResult(6002,err.Error())
+	}
+
+	file,moreFile,err := c.GetFile("image-file")
+	defer file.Close()
+
+	if err != nil {
+		logs.Error("",err.Error())
+		c.JsonResult(500,"读取文件异常")
+	}
 
+	ext := filepath.Ext(moreFile.Filename)
+
+	if !strings.EqualFold(ext,".png") && !strings.EqualFold(ext,".jpg") && !strings.EqualFold(ext,".gif") && !strings.EqualFold(ext,".jpeg")  {
+		c.JsonResult(500,"不支持的图片格式")
+	}
+
+
+	x1 ,_ := strconv.ParseFloat(c.GetString("x"),10)
+	y1 ,_ := strconv.ParseFloat(c.GetString("y"),10)
+	w1 ,_ := strconv.ParseFloat(c.GetString("width"),10)
+	h1 ,_ := strconv.ParseFloat(c.GetString("height"),10)
+
+	x := int(x1)
+	y := int(y1)
+	width := int(w1)
+	height := int(h1)
+
+	fileName := "cover_" +  strconv.FormatInt(time.Now().UnixNano(), 16)
+
+	filePath := "uploads/" + time.Now().Format("200601") + "/" + fileName + ext
+
+	path := filepath.Dir(filePath)
+
+	os.MkdirAll(path, os.ModePerm)
+
+	err = c.SaveToFile("image-file",filePath)
+
+	if err != nil {
+		logs.Error("",err)
+		c.JsonResult(500,"图片保存失败")
+	}
+
+	//剪切图片
+	subImg,err := graphics.ImageCopyFromFile(filePath,x,y,width,height)
+
+	if err != nil{
+		logs.Error("graphics.ImageCopyFromFile => ",err)
+		c.JsonResult(500,"图片剪切")
+	}
+	//生成缩略图并保存到磁盘
+	err = graphics.ImageResizeSaveFile(subImg,175,230,filePath)
+
+	if err != nil {
+		logs.Error("ImageResizeSaveFile => ",err.Error())
+		c.JsonResult(500,"保存图片失败")
+	}
+
+	url := "/" + filePath
+
+	old_cover := book.Cover
+
+	book.Cover = url
+
+	if err := book.Update() ; err != nil {
+		c.JsonResult(6001,"保存图片失败")
+	}
+	//如果原封面不是默认封面则删除
+	if old_cover != conf.GetDefaultCover() {
+		os.Remove("." + old_cover)
+	}
+
+	c.JsonResult(0,"ok",url)
 }
 
 // Users 用户列表.
@@ -142,24 +368,15 @@ func (c *BookController) AddMember()  {
 	if identify == "" || account == ""{
 		c.JsonResult(6001,"参数错误")
 	}
-	book ,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
+	book ,err := c.IsPermission()
 
 	if err != nil {
-		if err == models.ErrPermissionDenied {
-			c.JsonResult(403,"权限不足")
-		}
-		if err == orm.ErrNoRows {
-			c.JsonResult(404,"项目不存在")
-		}
-		c.JsonResult(6002,err.Error())
-	}
-	if book.RoleId != 0 && book.RoleId != 1 {
-		c.JsonResult(403,"权限不足")
+		c.JsonResult(6001,err.Error())
 	}
 
 	member := models.NewMember()
 
-	if err := member.FindByAccount(account) ; err != nil {
+	if _,err := member.FindByAccount(account) ; err != nil {
 		c.JsonResult(404,"用户不存在")
 	}
 	if member.Status == 1 {
@@ -249,7 +466,7 @@ func (c *BookController) RemoveMember()  {
 		c.JsonResult(6001,"参数错误")
 	}
 	if member_id == c.Member.MemberId {
-		c.JsonResult(6006,"不能变更自己的权限")
+		c.JsonResult(6006,"不能删除自己")
 	}
 	book ,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
 
@@ -262,7 +479,8 @@ func (c *BookController) RemoveMember()  {
 		}
 		c.JsonResult(6002,err.Error())
 	}
-	if book.RoleId != 0 && book.RoleId != 1 {
+	//如果不是创始人也不是管理员则不能操作
+	if book.RoleId != conf.BookFounder && book.RoleId != conf.BookAdmin {
 		c.JsonResult(403,"权限不足")
 	}
 	err = models.NewRelationship().DeleteByBookIdAndMemberId(book.BookId,member_id)
@@ -344,18 +562,10 @@ func (p *BookController) Edit() {
 
 // CreateToken 创建访问来令牌.
 func (c *BookController) CreateToken() {
-	book_id,_ := c.GetInt("book_id",0)
 
-	if book_id <= 0{
-		c.JsonResult(6001,"参数错误")
-	}
-
-	book := models.NewBook()
+	action := c.GetString("action")
 
-	if err := book.Find(book_id);err != nil {
-		c.JsonResult(6001,"项目不存在")
-	}
-	bookResult ,err := models.NewBookResult().FindByIdentify("identify",c.Member.MemberId)
+	bookResult ,err := c.IsPermission()
 
 	if err != nil {
 		if err == models.ErrPermissionDenied {
@@ -367,50 +577,46 @@ func (c *BookController) CreateToken() {
 		logs.Error("生成阅读令牌失败 =>",err)
 		c.JsonResult(6002,err.Error())
 	}
-	//必须是管理员或创始人才能删除项目
-	if bookResult.RoleId != 0 && bookResult.RoleId != 1 {
-		c.JsonResult(403,"权限不足")
-	}
-	if bookResult.PrivatelyOwned == 0 {
-		c.JsonResult(6001,"公开项目不能创建阅读令牌")
+	book := models.NewBook()
+
+	if _,err := book.Find(bookResult.BookId);err != nil {
+		c.JsonResult(6001,"项目不存在")
 	}
+	if action == "create" {
+		if bookResult.PrivatelyOwned == 0 {
+			c.JsonResult(6001, "公开项目不能创建阅读令牌")
+		}
 
-	book.PrivateToken = string(utils.Krand(20,utils.KC_RAND_KIND_ALL))
-	if err := book.Update(); err != nil {
-		logs.Error("生成阅读令牌失败 => ",err)
-		c.JsonResult(6003,"生成阅读令牌失败")
+		book.PrivateToken = string(utils.Krand(conf.GetTokenSize(), utils.KC_RAND_KIND_ALL))
+		if err := book.Update(); err != nil {
+			logs.Error("生成阅读令牌失败 => ", err)
+			c.JsonResult(6003, "生成阅读令牌失败")
+		}
+		c.JsonResult(0, "ok", c.BaseUrl() +  beego.URLFor("DocumentController.Index",":key",book.Identify,"token",book.PrivateToken))
+	}else{
+		book.PrivateToken = ""
+		if err := book.Update();err != nil {
+			logs.Error("CreateToken => ",err)
+			c.JsonResult(6004,"删除令牌失败")
+		}
+		c.JsonResult(0,"ok","")
 	}
-	c.JsonResult(0,"ok", c.BaseUrl() + "?token="+ book.PrivateToken)
 }
 
 // Delete 删除项目.
 func (c *BookController) Delete() {
 	c.Prepare()
 
-	book_id,_ := c.GetInt("book_id",0)
-
-	if book_id <= 0{
-		c.JsonResult(6001,"参数错误")
-	}
-
-	book ,err := models.NewBookResult().FindByIdentify("identify",c.Member.MemberId)
+	bookResult ,err := c.IsPermission()
 
 	if err != nil {
-		if err == models.ErrPermissionDenied {
-			c.JsonResult(403,"权限不足")
-		}
-		if err == orm.ErrNoRows {
-			c.JsonResult(404,"项目不存在")
-		}
-		logs.Error("删除项目 =>",err)
-		c.JsonResult(6002,err.Error())
-	}
-	//必须是管理员或创始人才能删除项目
-	if book.RoleId != 0 && book.RoleId != 1 {
-		c.JsonResult(403,"权限不足")
+		c.JsonResult(6001,err.Error())
 	}
 
-	err = models.NewBook().ThoroughDeleteBook(book_id)
+	if bookResult.RoleId != conf.BookFounder {
+		c.JsonResult(6002,"只有创始人才能删除项目")
+	}
+	err = models.NewBook().ThoroughDeleteBook(bookResult.BookId)
 
 	if err == orm.ErrNoRows {
 		c.JsonResult(6002,"项目不存在")
@@ -422,7 +628,21 @@ func (c *BookController) Delete() {
 	c.JsonResult(0,"ok")
 }
 
-// Transfer 转让项目.
-func (p *BookController)Transfer()  {
+func (c *BookController) IsPermission() (*models.BookResult,error) {
+	identify := c.GetString("identify")
+	book ,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
 
+	if err != nil {
+		if err == models.ErrPermissionDenied {
+			return book,errors.New("权限不足")
+		}
+		if err == orm.ErrNoRows {
+			return book,errors.New("项目不存在")
+		}
+		return book,err
+	}
+	if book.RoleId != conf.BookAdmin && book.RoleId != conf.BookFounder {
+		return book,errors.New("权限不足")
+	}
+	return book,nil
 }

+ 113 - 4
controllers/manager.go

@@ -12,14 +12,20 @@ import (
 	"github.com/lifei6671/godoc/utils"
 	"github.com/lifei6671/godoc/models"
 	"github.com/astaxie/beego/orm"
+	"github.com/astaxie/beego"
 )
 
 type ManagerController struct {
 	BaseController
 }
 
-func (p *ManagerController) Index() {
-	p.TplName = "manager/index.tpl"
+func (c *ManagerController) Index() {
+	c.TplName = "manager/index.tpl"
+	if !c.Member.IsAdministrator() {
+		c.Abort("403")
+	}
+
+	c.Data["Model"] = models.NewDashboard().Query()
 }
 
 // 用户列表.
@@ -93,7 +99,7 @@ func (c *ManagerController) CreateMember() {
 
 	member := models.NewMember()
 
-	if err := member.FindByAccount(account); err == nil && member.MemberId > 0 {
+	if _,err := member.FindByAccount(account); err == nil && member.MemberId > 0 {
 		c.JsonResult(6005,"账号已存在")
 	}
 
@@ -145,6 +151,36 @@ func (c *ManagerController) UpdateMemberStatus()  {
 	c.JsonResult(0,"ok",member)
 }
 
+func (c *ManagerController) ChangeMemberRole()  {
+	c.Prepare()
+
+	if !c.Member.IsAdministrator() {
+		c.Abort("403")
+	}
+
+	member_id,_ := c.GetInt("member_id",0)
+	role ,_ := c.GetInt("role",0)
+	if member_id <= 0 {
+		c.JsonResult(6001,"参数错误")
+	}
+	if role != conf.MemberAdminRole && role != conf.MemberGeneralRole {
+		c.JsonResult(6001,"用户权限不正确")
+	}
+	member := models.NewMember()
+
+	if err := member.Find(member_id); err != nil {
+		c.JsonResult(6002,"用户不存在")
+	}
+	member.Role = role
+
+	if err := member.Update();err != nil {
+		logs.Error("",err)
+		c.JsonResult(6003,"用户权限设置失败")
+	}
+	member.ResolveRoleName()
+	c.JsonResult(0,"ok",member)
+}
+
 func (c *ManagerController) Books()  {
 	c.Prepare()
 	c.TplName = "manager/books.tpl"
@@ -168,6 +204,7 @@ func (c *ManagerController) Books()  {
 	c.Data["Lists"] = books
 }
 
+//编辑项目
 func (c *ManagerController) EditBook()  {
 	c.TplName = "manager/edit_book.tpl"
 	identify := c.GetString(":key")
@@ -179,9 +216,44 @@ func (c *ManagerController) EditBook()  {
 	if err != nil {
 		c.Abort("500")
 	}
+	if c.Ctx.Input.IsPost() {
+
+		book_name := strings.TrimSpace(c.GetString("book_name"))
+		description := strings.TrimSpace(c.GetString("description",""))
+		comment_status := c.GetString("comment_status")
+		tag := strings.TrimSpace(c.GetString("label"))
+
+		if strings.Count(description,"") > 500 {
+			c.JsonResult(6004,"项目描述不能大于500字")
+		}
+		if comment_status != "open" && comment_status != "closed" && comment_status != "group_only" && comment_status != "registered_only" {
+			comment_status = "closed"
+		}
+		if tag != ""{
+			tags := strings.Split(tag,";")
+			if len(tags) > 10 {
+				c.JsonResult(6005,"最多允许添加10个标签")
+			}
+		}
+
+
+		book.BookName = book_name
+		book.Description = description
+		book.CommentStatus = comment_status
+		book.Label = tag
+
+		if err := book.Update();err != nil {
+			c.JsonResult(6006,"保存失败")
+		}
+		c.JsonResult(0,"ok")
+	}
+	if book.PrivateToken != "" {
+		book.PrivateToken = c.BaseUrl() + beego.URLFor("DocumentController.Index",":key",book.Identify,"token",book.PrivateToken)
+	}
 	c.Data["Model"] = book
 }
 
+
 // 删除项目.
 func (c *ManagerController) DeleteBook()  {
 	c.Prepare()
@@ -202,17 +274,54 @@ func (c *ManagerController) DeleteBook()  {
 		c.JsonResult(6002,"项目不存在")
 	}
 	if err != nil {
-		logs.Error("",err)
+		logs.Error("DeleteBook => ",err)
 		c.JsonResult(6003,"删除失败")
 	}
 	c.JsonResult(0,"ok")
 }
 
+
+// CreateToken 创建访问来令牌.
+func (c *ManagerController) CreateToken() {
+
+	action := c.GetString("action")
+
+	identify := c.GetString("identify")
+
+	book,err := models.NewBook().FindByFieldFirst("identify",identify);
+
+	if err != nil {
+		c.JsonResult(6001,"项目不存在")
+	}
+	if action == "create" {
+
+		if book.PrivatelyOwned == 0 {
+			c.JsonResult(6001, "公开项目不能创建阅读令牌")
+		}
+
+		book.PrivateToken = string(utils.Krand(conf.GetTokenSize(), utils.KC_RAND_KIND_ALL))
+		if err := book.Update(); err != nil {
+			logs.Error("生成阅读令牌失败 => ", err)
+			c.JsonResult(6003, "生成阅读令牌失败")
+		}
+		c.JsonResult(0, "ok", c.BaseUrl() +  beego.URLFor("DocumentController.Index",":key",book.Identify,"token",book.PrivateToken))
+	}else{
+		book.PrivateToken = ""
+		if err := book.Update();err != nil {
+			logs.Error("CreateToken => ",err)
+			c.JsonResult(6004,"删除令牌失败")
+		}
+		c.JsonResult(0,"ok","")
+	}
+}
+
 func (c *ManagerController) Comments()  {
 	c.Prepare()
+	c.TplName = "manager/comments.tpl"
 	if !c.Member.IsAdministrator() {
 		c.Abort("403")
 	}
+
 }
 
 //DeleteComment 标记评论为已删除

+ 9 - 53
controllers/setting.go

@@ -1,22 +1,17 @@
 package controllers
 
 import (
-	"image"
 	"fmt"
 	"os"
 	"strings"
-	"image/jpeg"
-	"image/png"
-	"image/gif"
 	"path/filepath"
 	"strconv"
 	"time"
-	"io/ioutil"
-	"bytes"
 
 	"github.com/astaxie/beego/logs"
 	"github.com/lifei6671/godoc/models"
 	"github.com/lifei6671/godoc/utils"
+	"github.com/lifei6671/godoc/graphics"
 )
 
 type SettingController struct {
@@ -113,9 +108,9 @@ func (c *SettingController) Upload() {
 
 	fmt.Println(x,x1,y,y1)
 
-	fileName := "avatar_" +  strconv.FormatInt(int64(time.Now().Nanosecond()), 16)
+	fileName := "avatar_" +  strconv.FormatInt(time.Now().UnixNano(), 16)
 
-	filePath := "static/uploads/" + time.Now().Format("200601") + "/" + fileName + ext
+	filePath := "uploads/" + time.Now().Format("200601") + "/" + fileName + ext
 
 	path := filepath.Dir(filePath)
 
@@ -128,61 +123,22 @@ func (c *SettingController) Upload() {
 		c.JsonResult(500,"图片保存失败")
 	}
 
-	fileBytes,err := ioutil.ReadFile(filePath)
-
-	if err != nil {
-		logs.Error("",err)
-		c.JsonResult(500,"图片保存失败")
-	}
-
-	buf := bytes.NewBuffer(fileBytes)
-
-	m,_,err := image.Decode(buf)
-
-	if err != nil{
-		logs.Error("image.Decode => ",err)
-		c.JsonResult(500,"图片解码失败")
-	}
 
+	//剪切图片
+	subImg,err := graphics.ImageCopyFromFile(filePath,x,y,width,height)
 
-	var subImg image.Image
-
-	if rgbImg,ok := m.(*image.YCbCr); ok {
-		subImg = rgbImg.SubImage(image.Rect(x, y, x+width, y+height)).(*image.YCbCr) //图片裁剪x0 y0 x1 y1
-	}else if rgbImg,ok := m.(*image.RGBA); ok {
-		subImg = rgbImg.SubImage(image.Rect(x, y, x+width, y+height)).(*image.YCbCr) //图片裁剪x0 y0 x1 y1
-	}else if rgbImg,ok := m.(*image.NRGBA); ok {
-		subImg = rgbImg.SubImage(image.Rect(x, y, x+width, y+height)).(*image.YCbCr) //图片裁剪x0 y0 x1 y1
-	} else {
-		fmt.Println(m)
-		c.JsonResult(500,"图片解码失败")
-	}
-
-	f, err := os.OpenFile("./" + filePath, os.O_SYNC | os.O_RDWR, 0666)
-
-	if err != nil{
-		c.JsonResult(500,"保存图片失败")
-	}
-	defer f.Close()
-
-	if strings.EqualFold(ext,".jpg") || strings.EqualFold(ext,".jpeg"){
-
-		err = jpeg.Encode(f,subImg,&jpeg.Options{ Quality : 100 })
-	}else if strings.EqualFold(ext,".png") {
-		err = png.Encode(f,subImg)
-	}else if strings.EqualFold(ext,".gif") {
-		err = gif.Encode(f,subImg,&gif.Options{ NumColors : 256})
-	}
 	if err != nil {
-		logs.Error("图片剪切失败 => ",err.Error())
-		c.JsonResult(500,"图片剪切失败")
+		logs.Error("ImageCopyFromFile => ",err)
+		c.JsonResult(6001,"头像剪切失败")
 	}
 
+	err = graphics.SaveImage(filePath,subImg)
 
 	if err != nil {
 		logs.Error("保存文件失败 => ",err.Error())
 		c.JsonResult(500,"保存文件失败")
 	}
+
 	url := "/" + filePath
 
 	member := models.NewMember()

+ 49 - 0
graphics/copy.go

@@ -0,0 +1,49 @@
+package graphics
+
+import (
+	"image"
+	"os"
+	"errors"
+
+	"github.com/nfnt/resize"
+)
+
+func ImageCopy(src image.Image,x, y ,w, h int) (image.Image,error)  {
+
+	var subImg image.Image
+
+	if rgbImg,ok := src.(*image.YCbCr); ok {
+		subImg = rgbImg.SubImage(image.Rect(x, y, x+w, y+h)).(*image.YCbCr) //图片裁剪x0 y0 x1 y1
+	}else if rgbImg,ok := src.(*image.RGBA); ok {
+		subImg = rgbImg.SubImage(image.Rect(x, y, x+w, y+h)).(*image.YCbCr) //图片裁剪x0 y0 x1 y1
+	}else if rgbImg,ok := src.(*image.NRGBA); ok {
+		subImg = rgbImg.SubImage(image.Rect(x, y, x+w, y+h)).(*image.YCbCr) //图片裁剪x0 y0 x1 y1
+	} else {
+
+		return subImg,errors.New("图片解码失败")
+	}
+
+	return subImg,nil
+}
+
+func ImageCopyFromFile(p string,x, y ,w, h int) (image.Image,error) {
+	var src image.Image
+
+	file, err := os.Open(p)
+	if err != nil {
+		return src, err
+	}
+	defer file.Close()
+	src, _, err = image.Decode(file)
+
+	return ImageCopy(src, x, y, w, h)
+}
+
+func ImageResize(src image.Image,w,h int) (image.Image) {
+	return resize.Resize(uint(w), uint(h), src, resize.Lanczos3)
+}
+func ImageResizeSaveFile(src image.Image,width,height int,p string) error {
+	dst := resize.Resize(uint(width), uint(height), src, resize.Lanczos3)
+
+	return SaveImage(p,dst)
+}

+ 32 - 0
graphics/file.go

@@ -0,0 +1,32 @@
+package graphics
+
+import (
+	"image"
+	"os"
+	"path/filepath"
+	"strings"
+	"image/jpeg"
+	"image/png"
+	"image/gif"
+)
+// 将图片保存到指定的路径
+func SaveImage(p string, src image.Image) error {
+
+	f, err := os.OpenFile( p, os.O_SYNC | os.O_RDWR | os.O_CREATE, 0666)
+
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	ext := filepath.Ext(p)
+
+	if strings.EqualFold(ext, ".jpg") || strings.EqualFold(ext, ".jpeg") {
+
+		err = jpeg.Encode(f, src, &jpeg.Options{Quality : 100 })
+	} else if strings.EqualFold(ext, ".png") {
+		err = png.Encode(f, src)
+	} else if strings.EqualFold(ext, ".gif") {
+		err = gif.Encode(f, src, &gif.Options{NumColors : 256})
+	}
+	return err
+}

+ 0 - 14
models/base.go

@@ -1,20 +1,6 @@
 package models
 
-import "github.com/astaxie/beego/orm"
-
 type Model struct {
 
 }
 
-func (m *Model) Find(id int) error {
-	o := orm.NewOrm()
-
-	return o.Read(m)
-}
-
-func (m *Model) Insert() error  {
-	o := orm.NewOrm()
-	_,err := o.Insert(m)
-
-	return err
-}

+ 8 - 5
models/book.go

@@ -71,13 +71,16 @@ func (m *Book) Insert() error {
 	return err
 }
 
-func (m *Book) Find(id int) error {
+func (m *Book) Find(id int) (*Book,error) {
 	if id <= 0 {
-		return ErrInvalidParameter
+		return m,ErrInvalidParameter
 	}
 	o := orm.NewOrm()
 
-	return o.Read(m)
+
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id",id).One(m)
+
+	return m,err
 }
 
 func (m *Book) Update(cols... string) error  {
@@ -201,7 +204,7 @@ func (m *Book) ThoroughDeleteBook(id int) error {
 	}
 	sql3 := "DELETE FROM " + m.TableNameWithPrefix() + " WHERE book_id = ?"
 
-	_,err = o.Raw(sql3).Exec()
+	_,err = o.Raw(sql3,m.BookId).Exec()
 
 	if err != nil {
 		o.Rollback()
@@ -209,7 +212,7 @@ func (m *Book) ThoroughDeleteBook(id int) error {
 	}
 	sql4 := "DELETE FROM " + NewRelationship().TableNameWithPrefix() + " WHERE book_id = ?"
 
-	_,err = o.Raw(sql4).Exec()
+	_,err = o.Raw(sql4,m.BookId).Exec()
 
 	if err != nil {
 		o.Rollback()

+ 6 - 4
models/book_result.go

@@ -5,6 +5,7 @@ import (
 	"github.com/astaxie/beego/orm"
 	"strings"
 	"github.com/astaxie/beego/logs"
+	"github.com/lifei6671/godoc/conf"
 )
 
 type BookResult struct {
@@ -93,17 +94,18 @@ func (m *BookResult) FindByIdentify(identify string,member_id int) (*BookResult,
 	m.RoleId = relationship.RoleId
 	m.RelationshipId = relationship.RelationshipId
 
-	if m.RoleId == 0{
+	if m.RoleId == conf.BookFounder {
 		m.RoleName = "创始人"
-	}else if m.RoleId == 1 {
+	}else if m.RoleId == conf.BookAdmin {
 		m.RoleName = "管理员"
-	}else if m.RoleId == 2 {
+	}else if m.RoleId == conf.BookEditor {
 		m.RoleName = "编辑者"
-	}else if m.RoleId == 2 {
+	}else if m.RoleId == conf.BookObserver {
 		m.RoleName = "观察者"
 	}
 
 
+
 	doc := NewDocument()
 
 	err = o.QueryTable(doc.TableNameWithPrefix()).Filter("book_id",book.BookId).OrderBy("modify_time").One(doc)

+ 3 - 3
models/comment.go

@@ -87,9 +87,9 @@ func (m *Comment) Insert() error {
 	if err := document.Find(m.DocumentId); err != nil {
 		return err
 	}
-	book := NewBook()
+	book ,err := NewBook().Find(document.BookId);
 	//如果评论的项目不存在
-	if err := book.Find(document.BookId); err != nil {
+	if err != nil {
 		return err
 	}
 	//如果已关闭评论
@@ -124,7 +124,7 @@ func (m *Comment) Insert() error {
 		m.Author = "[匿名用户]"
 	}
 	m.BookId = book.BookId
-	_,err := o.Insert(m)
+	_,err = o.Insert(m)
 
 	return err
 }

+ 38 - 0
models/dashboard.go

@@ -0,0 +1,38 @@
+package models
+
+import "github.com/astaxie/beego/orm"
+
+type Dashboard struct {
+	BookNumber int64                `json:"book_number"`
+	DocumentNumber int64        `json:"document_number"`
+	MemberNumber int64        `json:"member_number"`
+	CommentNumber int64        `json:"comment_number"`
+	AttachmentNumber int64        `json:"attachment_number"`
+}
+
+func NewDashboard() *Dashboard {
+	return &Dashboard{}
+}
+
+func (m *Dashboard) Query() (*Dashboard) {
+	o := orm.NewOrm()
+
+	book_number,_ := o.QueryTable(NewBook().TableNameWithPrefix()).Count()
+
+	m.BookNumber = book_number
+
+	document_count,_ := o.QueryTable(NewDocument().TableNameWithPrefix()).Count()
+	m.DocumentNumber = document_count
+
+	member_number,_ := o.QueryTable(NewMember().TableNameWithPrefix()).Count()
+	m.MemberNumber = member_number
+
+	comment_number,_ := o.QueryTable(NewComment().TableNameWithPrefix()).Count()
+	m.CommentNumber = comment_number
+
+	attachment_number,_ := o.QueryTable(NewAttachment().TableNameWithPrefix()).Count()
+
+	m.AttachmentNumber = attachment_number
+
+	return m
+}

+ 14 - 10
models/member.go

@@ -103,6 +103,17 @@ func (m *Member) Find(id int) error{
 	if err := o.Read(m); err != nil {
 		return  err
 	}
+	if m.Role == conf.MemberSuperRole {
+		m.RoleName = "超级管理员"
+	}else if m.Role == conf.MemberAdminRole {
+		m.RoleName = "管理员"
+	}else if m.Role == conf.MemberGeneralRole {
+		m.RoleName = "普通用户"
+	}
+	return nil
+}
+
+func (m *Member) ResolveRoleName (){
 	if m.Role == 0 {
 		m.RoleName = "超级管理员"
 	}else if m.Role == 1 {
@@ -110,24 +121,17 @@ func (m *Member) Find(id int) error{
 	}else if m.Role == 2 {
 		m.RoleName = "普通用户"
 	}
-	return nil
 }
 
-func (m *Member) FindByAccount (account string) error  {
+func (m *Member) FindByAccount (account string) (*Member,error)  {
 	o := orm.NewOrm()
 
 	err := o.QueryTable(m.TableNameWithPrefix()).Filter("account",account).One(m)
 
 	if err == nil {
-		if m.Role == 0 {
-			m.RoleName = "超级管理员"
-		}else if m.Role == 1 {
-			m.RoleName = "管理员"
-		}else if m.Role == 2 {
-			m.RoleName = "普通用户"
-		}
+		m.ResolveRoleName()
 	}
-	return err
+	return m,err
 }
 
 func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member,int64,error)  {

+ 48 - 0
models/relationship.go

@@ -90,6 +90,14 @@ func (m *Relationship) FindForRoleId(book_id ,member_id int) (int,error) {
 	return relationship.RoleId,nil
 }
 
+func (m *Relationship) FindByBookIdAndMemberId(book_id ,member_id int) (*Relationship,error) {
+	o := orm.NewOrm()
+
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id",book_id).Filter("member_id",member_id).One(m)
+
+	return m,err
+}
+
 func (m *Relationship) Insert() error  {
 	o := orm.NewOrm()
 
@@ -127,6 +135,46 @@ func (m *Relationship) DeleteByBookIdAndMemberId(book_id,member_id int) error {
 
 }
 
+func (m *Relationship) Transfer(book_id,founder_id,receive_id int) error {
+	o := orm.NewOrm()
+
+	founder := NewRelationship()
+
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id",book_id).Filter("member_id",founder_id).One(founder)
+
+	if err != nil {
+		return err
+	}
+	if founder.RoleId != conf.BookFounder {
+		return errors.New("转让者不是创始人")
+	}
+	receive := NewRelationship()
+
+	err = o.QueryTable(m.TableNameWithPrefix()).Filter("book_id",book_id).Filter("member_id",receive_id).One(receive)
+
+	if err != orm.ErrNoRows && err != nil {
+		return err
+	}
+	o.Begin()
+
+	founder.RoleId = conf.BookAdmin
+
+	receive.MemberId = receive_id
+	receive.RoleId = conf.BookFounder
+	receive.BookId = book_id
+
+	if err := founder.Update();err != nil {
+		o.Rollback()
+		return err
+	}
+	if _,err := o.InsertOrUpdate(receive);err != nil {
+		o.Rollback()
+		return err
+	}
+	return o.Commit()
+}
+
+
 
 
 

+ 12 - 5
routers/router.go

@@ -17,9 +17,11 @@ func init()  {
 	beego.Router("/manager/users", &controllers.ManagerController{},"*:Users")
 	beego.Router("/manager/member/create", &controllers.ManagerController{},"post:CreateMember")
 	beego.Router("/manager/member/update-member-status",&controllers.ManagerController{},"post:UpdateMemberStatus")
+	beego.Router("/manager/member/change-member-role", &controllers.ManagerController{},"post:ChangeMemberRole")
 	beego.Router("/manager/books", &controllers.ManagerController{},"*:Books")
 	beego.Router("/manager/books/edit/:key", &controllers.ManagerController{},"*:EditBook")
 	beego.Router("/manager/comments", &controllers.ManagerController{},"*:Comments")
+	beego.Router("/manager/books/token", &controllers.ManagerController{},"post:CreateToken")
 
 	beego.Router("/setting", &controllers.SettingController{},"*:Index")
 	beego.Router("/setting/password", &controllers.SettingController{},"*:Password")
@@ -31,12 +33,17 @@ func init()  {
 	beego.Router("/book/:key/users", &controllers.BookController{},"*:Users")
 	beego.Router("/book/:key/edit", &controllers.BookController{},"*:Edit")
 	beego.Router("/book/create", &controllers.BookController{},"*:Create")
-	beego.Router("/book/member/create", &controllers.BookController{},"post:AddMember")
-	beego.Router("/book/member/change-role", &controllers.BookController{},"post:ChangeRole")
+	beego.Router("/book/users/create", &controllers.BookController{},"post:AddMember")
+	beego.Router("/book/users/change", &controllers.BookController{},"post:ChangeRole")
+	beego.Router("/book/users/delete", &controllers.BookController{},"post:RemoveMember")
+
+	beego.Router("/book/setting/save", &controllers.BookController{},"post:SaveBook")
+	beego.Router("/book/setting/open", &controllers.BookController{},"post:PrivatelyOwned")
+	beego.Router("/book/setting/transfer", &controllers.BookController{},"post:Transfer")
+	beego.Router("/book/setting/upload", &controllers.BookController{},"post:UploadCover")
+	beego.Router("/book/setting/token", &controllers.BookController{},"post:CreateToken")
+	beego.Router("/book/setting/delete", &controllers.BookController{},"post:Delete")
 
-	beego.Router("/book/:key/users/create", &controllers.BookMemberController{},"*:Create")
-	beego.Router("/book/:key/users/change", &controllers.BookMemberController{},"*:Change")
-	beego.Router("/book/:key/users/delete", &controllers.BookMemberController{},"*:Delete")
 
 	beego.Router("/docs/:key", &controllers.DocumentController{},"*:Index")
 	beego.Router("/docs/:key/:id", &controllers.DocumentController{},"*:Read")

+ 26 - 0
static/css/main.css

@@ -382,6 +382,32 @@ textarea{
     display: inline-block;
     height: 40px;
 }
+.manager .dashboard-item{
+    float: left;
+    width: 100px;
+    height: 115px;
+    padding: 10px;
+    font-size: 10px;
+    line-height: 1.4;
+    text-align: center;
+    background-color: #f9f9f9;
+    border: 1px solid #fff;
+    cursor: pointer;
+}
+.manager .dashboard-item:hover{
+    background-color: #563D7C;
+    color: #ffffff;
+}
+.manager .dashboard-item .fa{
+    margin-top: 5px;
+    margin-bottom: 10px;
+    font-size: 24px;
+}
+.manager .dashboard-item .fa-class {
+    display: block;
+    text-align: center;
+    word-wrap: break-word; /* Help out IE10+ with class names */
+}
 
 /**************网站底部样式*************************/
 .footer{

+ 10 - 4
static/js/main.js

@@ -2,13 +2,19 @@
     $("[data-toggle='tooltip']").tooltip();
 })();
 
-function showError($msg) {
-    $("#form-error-message").addClass("error-message").removeClass("success-message").text($msg);
+function showError($msg,$id) {
+    if(!$id){
+        $id = "#form-error-message"
+    }
+    $($id).addClass("error-message").removeClass("success-message").text($msg);
     return false;
 }
 
-function showSuccess($msg) {
-    $("#form-error-message").addClass("success-message").removeClass("error-message").text($msg);
+function showSuccess($msg,$id) {
+    if(!$id){
+        $id = "#form-error-message"
+    }
+    $($id).addClass("success-message").removeClass("error-message").text($msg);
     return true;
 }
 

+ 25 - 0
utils/file.go

@@ -0,0 +1,25 @@
+package utils
+
+import (
+	"strings"
+	"os"
+	"fmt"
+	"path/filepath"
+)
+
+func AbsolutePath(p string) (string,error) {
+
+	if strings.HasPrefix(p, "~") {
+		home := os.Getenv("HOME")
+		if home == "" {
+			panic(fmt.Sprintf("can not found HOME in envs, '%s' AbsPh Failed!", p))
+		}
+		p = fmt.Sprint(home, string(p[1:]))
+	}
+	s, err := filepath.Abs(p)
+
+	if nil != err {
+		return  "",err
+	}
+	return s,nil
+}

+ 2 - 0
views/book/create.tpl

@@ -28,7 +28,9 @@
                 <ul class="menu">
                     <li><a href="{{urlfor "BookController.Dashboard" ":key" "test"}}" class="item"><i class="fa fa-dashboard" aria-hidden="true"></i> 概要</a> </li>
                     <li><a href="{{urlfor "BookController.Users" ":key" "test"}}" class="item"><i class="fa fa-users" aria-hidden="true"></i> 成员</a> </li>
+                    {{if eq .Model.RoleId 0 1}}
                     <li class="active"><a href="{{urlfor "BookController.Setting" ":key" "test"}}" class="item"><i class="fa fa-gear" aria-hidden="true"></i> 设置</a> </li>
+                    {{end}}
                 </ul>
 
             </div>

+ 5 - 0
views/book/dashboard.tpl

@@ -28,7 +28,9 @@
                 <ul class="menu">
                     <li class="active"><a href="{{urlfor "BookController.Dashboard" ":key" .Model.Identify}}" class="item"><i class="fa fa-dashboard" aria-hidden="true"></i> 概要</a> </li>
                     <li><a href="{{urlfor "BookController.Users" ":key" .Model.Identify}}" class="item"><i class="fa fa-users" aria-hidden="true"></i> 成员</a> </li>
+                    {{if eq .Model.RoleId 0 1}}
                     <li><a href="{{urlfor "BookController.Setting" ":key" .Model.Identify}}" class="item"><i class="fa fa-gear" aria-hidden="true"></i> 设置</a> </li>
+                    {{end}}
                 </ul>
 
             </div>
@@ -45,7 +47,10 @@
                         </strong>
                         {{if ne .Model.RoleId 3}}
                         <a href="{{urlfor "BookController.Edit" ":key" .Model.Identify ":id" .Model.BookId}}" class="btn btn-default btn-sm pull-right" target="_blank"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
+                        {{end}}
                         <a href="{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" class="btn btn-default btn-sm pull-right" style="margin-right: 5px;" target="_blank"><i class="fa fa-eye"></i> 阅读</a>
+
+                        {{if eq .Model.RoleId 0 1 2}}
                         <a href="{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" class="btn btn-default btn-sm pull-right" style="margin-right: 5px;" target="_blank"><i class="fa fa-upload" aria-hidden="true"></i> 发布</a>
                         {{end}}
                     </div>

+ 8 - 1
views/book/index.tpl

@@ -47,7 +47,14 @@
                             <div class="book-title">
                                 <div class="pull-left">
                                     <a :href="'/book/' + item.identify + '/dashboard'" title="项目概要" data-toggle="tooltip">
-                                        <i class="fa fa-unlock" aria-hidden="true"></i> ${item.book_name}
+                                       <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>
                                 <div class="pull-right">

+ 320 - 20
views/book/setting.tpl

@@ -10,7 +10,8 @@
     <!-- Bootstrap -->
     <link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
     <link href="/static/font-awesome/css/font-awesome.min.css" rel="stylesheet">
-
+    <link href="/static/webuploader/webuploader.css" rel="stylesheet">
+    <link href="/static/cropper/2.3.4/cropper.min.css" rel="stylesheet">
     <link href="/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:// -->
@@ -36,25 +37,26 @@
                 <div class="m-box">
                     <div class="box-head">
                         <strong class="box-title"> 项目设置</strong>
-                        {{if eq .Model.RoleId 0 1}}
                         {{if eq .Model.RoleId 0}}
-                        <button type="button"  class="btn btn-success btn-sm pull-right">转让项目</button>
-                        {{end}}
-                        <button type="button"  class="btn btn-danger btn-sm pull-right" style="margin-right: 5px;">删除项目</button>
-                        {{end}}
+                        <button type="button"  class="btn btn-success btn-sm pull-right" data-toggle="modal" data-target="#transferBookModal">转让项目</button>
                         {{if eq .Model.PrivatelyOwned 1}}
-                        <button type="button"  class="btn btn-success btn-sm pull-right" style="margin-right: 5px;">转为公有</button>
+                        <button type="button"  class="btn btn-success btn-sm pull-right" data-toggle="modal" data-target="#changePrivatelyOwnedModal" style="margin-right: 5px;">转为公有</button>
                         {{else}}
-                        <button type="button"  class="btn btn-danger btn-sm pull-right" style="margin-right: 5px;">转为私有</button>
+                        <button type="button"  class="btn btn-danger btn-sm pull-right" data-toggle="modal" data-target="#changePrivatelyOwnedModal" style="margin-right: 5px;">转为私有</button>
                         {{end}}
+                        <button type="button"  class="btn btn-danger btn-sm pull-right" style="margin-right: 5px;" data-toggle="modal" data-target="#deleteBookModal">删除项目</button>
+
+                        {{end}}
+
                     </div>
                 </div>
                 <div class="box-body" style="padding-right: 200px;">
                     <div class="form-left">
-                        <form method="post" id="bookEditForm">
+                        <form method="post" id="bookEditForm" action="{{urlfor "BookController.SaveBook"}}">
+                            <input type="hidden" name="identify" value="{{.Model.Identify}}">
                             <div class="form-group">
                                 <label>标题</label>
-                                <input type="text" class="form-control" placeholder="项目名称" value="{{.Model.BookName}}">
+                                <input type="text" class="form-control" name="book_name" id="bookName" placeholder="项目名称" value="{{.Model.BookName}}">
                             </div>
                             <div class="form-group">
                                 <label>标识</label>
@@ -63,11 +65,11 @@
                             <div class="form-group">
                                 <label>描述</label>
                                 <textarea rows="3" class="form-control" name="description" style="height: 90px">{{.Model.Description}}</textarea>
-                                <p class="text">描述信息不超过300个字符</p>
+                                <p class="text">描述信息不超过500个字符</p>
                             </div>
                             <div class="form-group">
                                 <label>标签</label>
-                                <input type="text" class="form-control" placeholder="项目标签" value="{{.Model.Label}}">
+                                <input type="text" class="form-control" name="label" placeholder="项目标签" value="{{.Model.Label}}">
                                 <p class="text">最多允许添加10个标签,多个标签请用“;”分割</p>
                             </div>
                             <div class="form-group">
@@ -91,18 +93,18 @@
                             <div class="form-group">
                                 <label>访问令牌</label>
                                 <div class="row">
-                                    <div class="col-sm-8">
-                                        <input type="text" class="form-control" placeholder="访问令牌" readonly>
+                                    <div class="col-sm-10">
+                                        <input type="text" name="token" id="token" class="form-control" placeholder="访问令牌" readonly value="{{.Model.PrivateToken}}">
                                     </div>
-                                   <div class="col-sm-4">
-                                       <button class="btn btn-success btn-sm">重写生成</button>
-                                       <button class="btn btn-danger btn-sm">删除令牌</button>
+                                   <div class="col-sm-2">
+                                       <button type="button" class="btn btn-success btn-sm" id="createToken" data-loading-text="生成" data-action="create">生成</button>
+                                       <button type="button" class="btn btn-danger btn-sm" id="deleteToken" data-loading-text="删除" data-action="delete">删除</button>
                                    </div>
                                 </div>
                             </div>
                             {{end}}
                             <div class="form-group">
-                                <button type="submit" class="btn btn-success" data-loading-text="保存中...">保存修改</button>
+                                <button type="submit" id="btnSaveBookInfo" class="btn btn-success" data-loading-text="保存中...">保存修改</button>
                                 <span id="form-error-message" class="error-message"></span>
                             </div>
                         </form>
@@ -122,9 +124,307 @@
     </div>
     {{template "widgets/footer.tpl" .}}
 </div>
-<script src="/static/jquery/1.12.4/jquery.min.js"></script>
-<script src="/static/bootstrap/js/bootstrap.min.js"></script>
+<!-- Modal -->
+<div class="modal fade" id="changePrivatelyOwnedModal" tabindex="-1" role="dialog" aria-labelledby="changePrivatelyOwnedModalLabel">
+    <div class="modal-dialog" role="document">
+        <form method="post" action="{{urlfor "BookController.PrivatelyOwned" }}" id="changePrivatelyOwnedForm">
+            <input type="hidden" name="identify" value="{{.Model.Identify}}">
+            <input type="hidden" name="status" value="{{if eq .Model.PrivatelyOwned 0}}close{{else}}open{{end}}">
+        <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">
+                {{if eq .Model.PrivatelyOwned 0}}
+                    转为私有
+                    {{else}}
+                    转为共有
+                    {{end}}
+                </h4>
+            </div>
+            <div class="modal-body">
+                {{if eq .Model.PrivatelyOwned 0}}
+                <span style="font-size: 14px;font-weight: 400;">确定将项目转为私有吗?</span>
+                <p></p>
+                <p class="text error-message">转为私有后需要通过阅读令牌才能访问该项目。</p>
+                {{else}}
+                <span style="font-size: 14px;font-weight: 400;"> 确定将项目转为公有吗?</span>
+                <p></p>
+                <p class="text error-message">转为公有后所有人都可以访问该项目。</p>
+                {{end}}
+            </div>
+            <div class="modal-footer">
+                <span class="error-message" id="form-error-message1"></span>
+                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
+                <button type="submit" class="btn btn-primary">确定</button>
+            </div>
+        </div>
+        </form>
+    </div>
+</div>
+<!-- Start Modal -->
+<div class="modal fade" id="upload-logo-panel" tabindex="-1" role="dialog" aria-labelledby="修改头像" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                <h4 class="modal-title">修改头像</h4>
+            </div>
+            <div class="modal-body">
+                <div class="wraper">
+                    <div id="image-wraper">
+
+                    </div>
+                </div>
+                <div class="watch-crop-list">
+                    <div class="preview-title">预览</div>
+                    <ul>
+                        <li>
+                            <div class="img-preview preview-lg"></div>
+                        </li>
+                        <li>
+                            <div class="img-preview preview-sm"></div>
+                        </li>
+                    </ul>
+                </div>
+                <div style="clear: both"></div>
+            </div>
+            <div class="modal-footer">
+                <span id="error-message"></span>
+                <div id="filePicker" class="btn">选择</div>
+                <button type="button" id="saveImage" class="btn btn-success" style="height: 40px;width: 77px;" data-loading-text="上传中...">上传</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--END Modal-->
+
+<!-- Delete Book Modal -->
+<div class="modal fade" id="deleteBookModal" tabindex="-1" role="dialog" aria-labelledby="deleteBookModalLabel">
+    <div class="modal-dialog" role="document">
+        <form method="post" id="deleteBookForm" action="{{urlfor "BookController.Delete"}}">
+            <input type="hidden" name="identify" value="{{.Model.Identify}}">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                <h4 class="modal-title">删除项目</h4>
+            </div>
+            <div class="modal-body">
+                <span style="font-size: 14px;font-weight: 400;">确定删除项目吗?</span>
+                <p></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">确定删除</button>
+            </div>
+        </div>
+        </form>
+    </div>
+</div>
+<!-- Modal -->
+<div class="modal fade" id="transferBookModal" tabindex="-1" role="dialog" aria-labelledby="transferBookModalLabel">
+    <div class="modal-dialog" role="document">
+        <form action="{{urlfor "BookController.Transfer"}}" method="post" id="transferBookForm">
+            <input type="hidden" name="identify" value="{{.Model.Identify}}">
+        <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">
+                    <label class="col-sm-2 control-label">接收账号</label>
+                    <div class="col-sm-10">
+                        <input type="text" name="account" class="form-control" placeholder="接收者账号" id="receiveAccount" maxlength="50">
+                    </div>
+                </div>
+                <div class="clearfix"></div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
+                <button type="submit" id="btnTransferBook" class="btn btn-primary">确定转让</button>
+            </div>
+        </div>
+        </form>
+    </div>
+</div>
+
+<script src="/static/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
+<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
+<script src="/static/webuploader/webuploader.min.js" type="text/javascript"></script>
+<script src="/static/cropper/2.3.4/cropper.min.js" type="text/javascript"></script>
+<script src="/static/js/jquery.form.js" type="text/javascript"></script>
 <script src="/static/js/main.js" type="text/javascript"></script>
+<script type="text/javascript">
+    $(function () {
+        $("#upload-logo-panel").on("hidden.bs.modal",function () {
+            $("#upload-logo-panel").find(".modal-body").html(window.modalHtml);
+        }).on("show.bs.modal",function () {
+            window.modalHtml = $("#upload-logo-panel").find(".modal-body").html();
+        });
+
+       $("#changePrivatelyOwnedForm").ajaxForm({
+            success :function (res) {
+
+                if(res.errcode === 0){
+                    window.location = window.location.href;
+                }else{
+                    console.log(res.message)
+                    showError(res.message,"#form-error-message1");
+                }
+            }
+       });
+       $("#createToken,#deleteToken").on("click",function () {
+           var btn = $(this).button("loading");
+           var action = $(this).attr("data-action");
+          $.ajax({
+              url : "{{urlfor "BookController.CreateToken"}}",
+              type :"post",
+              data : { "identify" : {{.Model.Identify}} , "action" : action },
+              dataType : "json",
+              success : function (res) {
+                  if(res.errcode === 0){
+                      $("#token").val(res.data);
+                  }else{
+                      alert(res.message);
+                  }
+                  btn.button("reset");
+              },
+              error : function () {
+                  btn.button("reset");
+                  alert("服务器错误");
+              }
+          }) ;
+       });
+       $("#token").on("focus",function () {
+           $(this).select();
+       });
+       $("#bookEditForm").ajaxForm({
+           beforeSubmit : function () {
+               var bookName = $.trim($("#bookName").val());
+               if (bookName === "") {
+                   return showError("项目名称不能为空");
+               }
+                $("#btnSaveBookInfo").button("loading");
+           },
+           success : function (res) {
+                if(res.errcode === 0){
+                    showSuccess("保存成功")
+                }else{
+                    showError("保存失败")
+                }
+               $("#btnSaveBookInfo").button("reset");
+           },
+           error : function () {
+               showError("服务错误");
+               $("#btnSaveBookInfo").button("reset");
+           }
+       });
+       $("#deleteBookForm").ajaxForm({
+           success : function (res) {
+               if(res.errcode === 0){
+                   window.location = "{{urlfor "BookController.Index"}}";
+               }else{
+                   console.log(res.message)
+                   showError(res.message,"#form-error-message2");
+               }
+           }
+       });
+       $("#transferBookForm").ajaxForm({
+           beforeSubmit : function () {
+                var account = $.trim($("#receiveAccount").val());
+                if (account === ""){
+                    return showError("接受者账号不能为空")
+                }
+                $("#btnTransferBook").button("loading");
+           },
+           success : function (res) {
+                if(res.errcode === 0){
+                    window.location = window.location.href;
+                }else{
+                    showError(res.message);
+                }
+               $("#btnTransferBook").button("reset");
+           },
+           error : function () {
+               $("#btnTransferBook").button("reset");
+           }
+       });
+
+        try {
+            var uploader = WebUploader.create({
+                auto: false,
+                swf: '/static/webuploader/Uploader.swf',
+                server: '{{urlfor "BookController.UploadCover"}}',
+                formData : { "identify" : {{.Model.Identify}} },
+                pick: "#filePicker",
+                fileVal : "image-file",
+                fileNumLimit : 1,
+                compress : false,
+                accept: {
+                    title: 'Images',
+                    extensions: 'jpg,jpeg,png',
+                    mimeTypes: 'image/jpg,image/jpeg,image/png'
+                }
+            }).on("beforeFileQueued",function (file) {
+                uploader.reset();
+            }).on( 'fileQueued', function( file ) {
+                uploader.makeThumb( file, function( error, src ) {
+                    $img = '<img src="' + src +'" style="max-width: 360px;max-height: 360px;">';
+                    if ( error ) {
+                        $img.replaceWith('<span>不能预览</span>');
+                        return;
+                    }
+
+                    $("#image-wraper").html($img);
+                    window.ImageCropper = $('#image-wraper>img').cropper({
+                        aspectRatio: 175 / 230,
+                        dragMode : 'move',
+                        viewMode : 1,
+                        preview : ".img-preview"
+                    });
+                }, 1, 1 );
+            }).on("uploadError",function (file,reason) {
+                console.log(reason);
+                $("#error-message").text("上传失败:" + reason);
+
+            }).on("uploadSuccess",function (file, res) {
+
+                if(res.errcode === 0){
+                    console.log(res);
+                    $("#upload-logo-panel").modal('hide');
+                    $("#headimgurl").attr('src',res.data);
+                }else{
+                    $("#error-message").text(res.message);
+                }
+            }).on("beforeFileQueued",function (file) {
+                if(file.size > 1024*1024*2){
+                    uploader.removeFile(file);
+                    uploader.reset();
+                    alert("文件必须小于2MB");
+                    return false;
+                }
+            }).on("uploadComplete",function () {
+                $("#saveImage").button('reset');
+            });
+            $("#saveImage").on("click",function () {
+                var files = uploader.getFiles();
+                if(files.length > 0) {
+                    $("#saveImage").button('loading');
+                    var cropper = window.ImageCropper.cropper("getData");
+
+                    uploader.option("formData", cropper);
 
+                    uploader.upload();
+                }else{
+                    alert("请选择图片");
+                }
+            });
+        }catch(e){
+            console.log(e);
+        }
+    });
+</script>
 </body>
 </html>

+ 26 - 1
views/book/users.tpl

@@ -28,7 +28,9 @@
                 <ul class="menu">
                     <li><a href="{{urlfor "BookController.Dashboard" ":key" .Model.Identify}}" class="item"><i class="fa fa-dashboard" aria-hidden="true"></i> 概要</a> </li>
                     <li class="active"><a href="{{urlfor "BookController.Users" ":key" .Model.Identify}}" class="item"><i class="fa fa-users" aria-hidden="true"></i> 成员</a> </li>
+                    {{if eq .Model.RoleId 0 1}}
                     <li><a href="{{urlfor "BookController.Setting" ":key" .Model.Identify}}" class="item"><i class="fa fa-gear" aria-hidden="true"></i> 设置</a> </li>
+                    {{end}}
                 </ul>
 
             </div>
@@ -36,7 +38,9 @@
                 <div class="m-box">
                     <div class="box-head">
                         <strong class="box-title"> 成员管理</strong>
+                        {{if eq .Model.RoleId 0 1}}
                         <button type="button"  class="btn btn-success btn-sm pull-right" data-toggle="modal" data-target="#addBookMemberDialogModal"><i class="fa fa-user-plus" aria-hidden="true"></i> 添加成员</button>
+                        {{end}}
                     </div>
                 </div>
                 <div class="box-body">
@@ -64,7 +68,7 @@
                                                    <li><a href="javascript:;" @click="setBookMemberRole(item.member_id,3)">观察者</a> </li>
                                                </ul>
                                            </div>
-                                           <button type="button" class="btn btn-danger btn-sm">移除</button>
+                                           <button type="button" class="btn btn-danger btn-sm" @click="removeBookMember(item.member_id)">移除</button>
                                        </template>
                                        <template v-else>
                                            <template v-if="item.role_id == 1">
@@ -189,6 +193,27 @@
                             }
                         }
                     });
+                },
+                removeBookMember : function (member_id) {
+                    var $this = this;
+                    $.ajax({
+                        url : "{{urlfor "BookController.RemoveMember"}}",
+                        type :"post",
+                        dataType :"json",
+                        data :{ "identify" : $this.book.identify,"member_id" : member_id},
+                        success : function (res) {
+                            if(res.errcode === 0){
+                                for(var index in $this.lists){
+                                    if($this.lists[index].member_id === member_id){
+                                        $this.lists.splice(index,1);
+                                        break;
+                                    }
+                                }
+                            }else{
+                                alert(res.message);
+                            }
+                        }
+                    });
                 }
             }
         });

+ 6 - 1
views/manager/books.tpl

@@ -46,7 +46,12 @@
                                 <div class="book-title">
                                     <div class="pull-left">
                                         <a href="{{urlfor "ManagerController.EditBook" ":key" $item.Identify}}" title="编辑项目" data-toggle="tooltip">
-                                            <i class="fa fa-unlock" aria-hidden="true"></i> {{$item.BookName}}
+                                            {{if eq $item.PrivatelyOwned 0}}
+                                            <i class="fa fa-unlock" aria-hidden="true"></i>
+                                            {{else}}
+                                            <i class="fa fa-lock" aria-hidden="true"></i>
+                                            {{end}}
+                                            {{$item.BookName}}
                                         </a>
                                     </div>
                                     <div class="pull-right">

+ 51 - 0
views/manager/comments.tpl

@@ -0,0 +1,51 @@
+<!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="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
+    <link href="/static/font-awesome/css/font-awesome.min.css" rel="stylesheet">
+
+    <link href="/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><a href="{{urlfor "ManagerController.Index"}}" class="item"><i class="fa fa-dashboard" aria-hidden="true"></i> 仪表盘</a> </li>
+                    <li><a href="{{urlfor "ManagerController.Users" }}" class="item"><i class="fa fa-users" aria-hidden="true"></i> 用户管理</a> </li>
+                    <li><a href="{{urlfor "ManagerController.Books" }}" class="item"><i class="fa fa-gear" aria-hidden="true"></i> 项目管理</a> </li>
+                    <li class="active"><a href="{{urlfor "ManagerController.Comments" }}" class="item"><i class="fa fa-comments-o" 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">
+
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/static/jquery/1.12.4/jquery.min.js"></script>
+<script src="/static/bootstrap/js/bootstrap.min.js"></script>
+</body>
+</html>

+ 110 - 18
views/manager/edit_book.tpl

@@ -10,7 +10,8 @@
     <!-- Bootstrap -->
     <link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
     <link href="/static/font-awesome/css/font-awesome.min.css" rel="stylesheet">
-
+    <link href="/static/webuploader/webuploader.css" rel="stylesheet">
+    <link href="/static/cropper/2.3.4/cropper.min.css" rel="stylesheet">
     <link href="/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:// -->
@@ -36,15 +37,16 @@
                 <div class="m-box">
                     <div class="box-head">
                         <strong class="box-title"> 项目设置</strong>
-                        <button type="button"  class="btn btn-danger btn-sm pull-right" style="margin-right: 5px;">删除项目</button>
+                        <button type="button"  class="btn btn-danger btn-sm pull-right" style="margin-right: 5px;" data-toggle="modal" data-target="#deleteBookModal">删除项目</button>
                     </div>
                 </div>
                 <div class="box-body" style="padding-right: 200px;">
                     <div class="form-left">
-                        <form method="post" id="bookEditForm">
+                        <form method="post" id="bookEditForm" action="{{urlfor "ManagerController.EditBook"}}">
+                            <input type="hidden" name="identify" value="{{.Model.Identify}}">
                             <div class="form-group">
                                 <label>标题</label>
-                                <input type="text" class="form-control" placeholder="项目名称" value="{{.Model.BookName}}">
+                                <input type="text" class="form-control" name="book_name" id="bookName" placeholder="项目名称" value="{{.Model.BookName}}">
                             </div>
                             <div class="form-group">
                                 <label>标识</label>
@@ -53,11 +55,11 @@
                             <div class="form-group">
                                 <label>描述</label>
                                 <textarea rows="3" class="form-control" name="description" style="height: 90px">{{.Model.Description}}</textarea>
-                                <p class="text">描述信息不超过300个字符</p>
+                                <p class="text">描述信息不超过500个字符</p>
                             </div>
                             <div class="form-group">
                                 <label>标签</label>
-                                <input type="text" class="form-control" placeholder="项目标签" value="{{.Model.Label}}">
+                                <input type="text" class="form-control" name="label" placeholder="项目标签" value="{{.Model.Label}}">
                                 <p class="text">最多允许添加10个标签,多个标签请用“;”分割</p>
                             </div>
                             <div class="form-group">
@@ -81,27 +83,25 @@
                             <div class="form-group">
                                 <label>访问令牌</label>
                                 <div class="row">
-                                    <div class="col-sm-8">
-                                        <input type="text" class="form-control" placeholder="访问令牌" readonly>
+                                    <div class="col-sm-10">
+                                        <input type="text" name="token" id="token" class="form-control" placeholder="访问令牌" readonly value="{{.Model.PrivateToken}}">
                                     </div>
-                                    <div class="col-sm-4">
-                                        <button class="btn btn-success btn-sm">重写生成</button>
-                                        <button class="btn btn-danger btn-sm">删除令牌</button>
+                                    <div class="col-sm-2">
+                                        <button type="button" class="btn btn-success btn-sm" id="createToken" data-loading-text="生成" data-action="create">生成</button>
+                                        <button type="button" class="btn btn-danger btn-sm" id="deleteToken" data-loading-text="删除" data-action="delete">删除</button>
                                     </div>
                                 </div>
                             </div>
                             {{end}}
                             <div class="form-group">
-                                <button type="submit" class="btn btn-success" data-loading-text="保存中...">保存修改</button>
+                                <button type="submit" id="btnSaveBookInfo" class="btn btn-success" data-loading-text="保存中...">保存修改</button>
                                 <span id="form-error-message" class="error-message"></span>
                             </div>
                         </form>
                     </div>
                     <div class="form-right">
                         <label>
-                            <a href="javascript:;" data-toggle="modal" data-target="#upload-logo-panel">
-                                <img src="{{.Model.Cover}}" onerror="this.src='/static/images/book.png'" alt="封面" style="max-width: 120px;border: 1px solid #999" id="headimgurl">
-                            </a>
+                           <img src="{{.Model.Cover}}" onerror="this.src='/static/images/book.png'" alt="封面" style="max-width: 120px;border: 1px solid #999" id="headimgurl">
                         </label>
                     </div>
                     <div class="clearfix"></div>
@@ -112,9 +112,101 @@
     </div>
     {{template "widgets/footer.tpl" .}}
 </div>
-<script src="/static/jquery/1.12.4/jquery.min.js"></script>
-<script src="/static/bootstrap/js/bootstrap.min.js"></script>
-<script src="/static/js/main.js" type="text/javascript"></script>
+<!-- Delete Book Modal -->
+<div class="modal fade" id="deleteBookModal" tabindex="-1" role="dialog" aria-labelledby="deleteBookModalLabel">
+    <div class="modal-dialog" role="document">
+        <form method="post" id="deleteBookForm" action="{{urlfor "BookController.Delete"}}">
+            <input type="hidden" name="identify" value="{{.Model.Identify}}">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                    <h4 class="modal-title">删除项目</h4>
+                </div>
+                <div class="modal-body">
+                    <span style="font-size: 14px;font-weight: 400;">确定删除项目吗?</span>
+                    <p></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">确定删除</button>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
 
+<script src="/static/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
+<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
+<script src="/static/webuploader/webuploader.min.js" type="text/javascript"></script>
+<script src="/static/cropper/2.3.4/cropper.min.js" type="text/javascript"></script>
+<script src="/static/js/jquery.form.js" type="text/javascript"></script>
+<script src="/static/js/main.js" type="text/javascript"></script>
+<script type="text/javascript">
+    $(function () {
+        $("#upload-logo-panel").on("hidden.bs.modal",function () {
+            $("#upload-logo-panel").find(".modal-body").html(window.modalHtml);
+        }).on("show.bs.modal",function () {
+            window.modalHtml = $("#upload-logo-panel").find(".modal-body").html();
+        });
+        $("#createToken,#deleteToken").on("click",function () {
+            var btn = $(this).button("loading");
+            var action = $(this).attr("data-action");
+            $.ajax({
+                url : "{{urlfor "ManagerController.CreateToken"}}",
+                type :"post",
+                data : { "identify" : {{.Model.Identify}} , "action" : action },
+                dataType : "json",
+                success : function (res) {
+                    if(res.errcode === 0){
+                        $("#token").val(res.data);
+                    }else{
+                        alert(res.message);
+                    }
+                    btn.button("reset");
+                },
+                error : function () {
+                    btn.button("reset");
+                    alert("服务器错误");
+                }
+            }) ;
+        });
+        $("#token").on("focus",function () {
+            $(this).select();
+        });
+        $("#bookEditForm").ajaxForm({
+            beforeSubmit : function () {
+                var bookName = $.trim($("#bookName").val());
+                if (bookName === "") {
+                    return showError("项目名称不能为空");
+                }
+                $("#btnSaveBookInfo").button("loading");
+            },
+            success : function (res) {
+                if(res.errcode === 0){
+                    showSuccess("保存成功")
+                }else{
+                    showError("保存失败")
+                }
+                $("#btnSaveBookInfo").button("reset");
+            },
+            error : function () {
+                showError("服务错误");
+                $("#btnSaveBookInfo").button("reset");
+            }
+        });
+        $("#deleteBookForm").ajaxForm({
+            success : function (res) {
+                if(res.errcode === 0){
+                    window.location = "{{urlfor "ManagerController.Books"}}";
+                }else{
+                    console.log(res.message)
+                    showError(res.message,"#form-error-message2");
+                }
+            }
+        });
+    });
+</script>
 </body>
 </html>

+ 34 - 0
views/manager/index.tpl

@@ -32,6 +32,40 @@
                     <li><a href="{{urlfor "ManagerController.Comments" }}" class="item"><i class="fa fa-comments-o" 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 manager">
+                    <div class="dashboard-item">
+                        <span class="fa fa-book" aria-hidden="true"></span>
+                        <span class="fa-class">项目数量</span>
+                        <span class="fa-class">{{.Model.BookNumber}}</span>
+                    </div>
+                    <div class="dashboard-item">
+                        <span class="fa fa-file-text-o" aria-hidden="true"></span>
+                        <span class="fa-class">文章数量</span>
+                        <span class="fa-class">{{.Model.DocumentNumber}}</span>
+                    </div>
+                    <div class="dashboard-item">
+                        <span class="fa fa-users" aria-hidden="true"></span>
+                        <span class="fa-class">会员数量</span>
+                        <span class="fa-class">{{.Model.MemberNumber}}</span>
+                    </div>
+                    <div class="dashboard-item">
+                        <span class="fa fa-comments-o" aria-hidden="true"></span>
+                        <span class="fa-class">评论数量</span>
+                        <span class="fa-class">{{.Model.CommentNumber}}</span>
+                    </div>
+                    <div class="dashboard-item">
+                        <span class="fa fa-cloud-download" aria-hidden="true"></span>
+                        <span class="fa-class">附件数量</span>
+                        <span class="fa-class">{{.Model.AttachmentNumber}}</span>
+                    </div>
+                </div>
+            </div>
         </div>
     </div>
 </div>

+ 39 - 13
views/manager/users.tpl

@@ -68,11 +68,16 @@
                                         <template v-if="item.role == 0">
                                             超级管理员
                                         </template>
-                                        <template v-else-if="item.role === 1">
-                                            管理员
-                                        </template>
                                         <template v-else>
-                                            普通用户
+                                            <div class="btn-group">
+                                            <button type="button" class="btn btn-default btn-sm"  data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                                ${item.role_name}
+                                                <span class="caret"></span></button>
+                                            <ul class="dropdown-menu">
+                                                <li><a href="javascript:;" @click="setMemberRole(item.member_id,1)">管理员</a> </li>
+                                                <li><a href="javascript:;" @click="setMemberRole(item.member_id,2)">普通用户</a> </li>
+                                            </ul>
+                                            </div>
                                         </template>
                                     </td>
                                     <td>
@@ -84,15 +89,12 @@
                                         </template>
                                     </td>
                                     <td>
-                                        <template v-if="item.role == 0">
-                                            超级管理员
-                                        </template>
-                                        <template v-else>
-                                            <template v-if="item.status == 1">
-                                                <button type="button" class="btn btn-danger btn-sm" @click="setMemberStatus(item.member_id,0,$event)" data-loading-text="启用中...">禁用</button>
+                                        <template v-if="item.role != 0">
+                                            <template v-if="item.status == 0">
+                                                <button type="button" class="btn btn-danger btn-sm" @click="setMemberStatus(item.member_id,1,$event)" data-loading-text="启用中...">禁用</button>
                                             </template>
                                             <template v-else>
-                                                <button type="button" class="btn btn-success btn-sm" @click="setMemberStatus(item.member_id,1,$event)" data-loading-text="禁用中...">启用</button>
+                                                <button type="button" class="btn btn-success btn-sm" @click="setMemberStatus(item.member_id,0,$event)" data-loading-text="禁用中...">启用</button>
                                             </template>
                                         </template>
                                     </td>
@@ -236,9 +238,9 @@
                                 for (var index in $this.lists) {
                                     var item = $this.lists[index];
 
-                                    if (item.member_id == id) {
+                                    if (item.member_id === id) {
                                         console.log(item);
-                                        item.status = status;
+                                        $this.lists[index].status = status;
                                         break;
                                         //$this.lists.splice(index,1,item);
                                     }
@@ -249,6 +251,30 @@
                         }
                     })
 
+                },
+                setMemberRole : function (member_id, role) {
+                    var $this = this;
+                    $.ajax({
+                        url :"{{urlfor "ManagerController.ChangeMemberRole"}}",
+                        dataType :"json",
+                        type :"post",
+                        data : { "member_id" : member_id,"role" : role },
+                        success : function (res) {
+                            if(res.errcode === 0){
+                                for (var index in $this.lists) {
+                                    var item = $this.lists[index];
+
+                                    if (item.member_id === member_id) {
+
+                                        $this.lists.splice(index,1,res.data);
+                                        break;
+                                    }
+                                }
+                            }else{
+                                alert("操作失败:" + res.message);
+                            }
+                        }
+                    })
                 }
             }
         });